Source/WebCore/ChangeLog

 12011-02-18 Tony Gentilcore <tonyg@chromium.org>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 Let the parser yield for layout before running scripts
 6 https://bugs.webkit.org/show_bug.cgi?id=54355
 7
 8 Prior to this patch, the parser would yield to perform a layout/paint before running a
 9 script only if the script or a stylesheet blocking the script is not loaded yet. Since we
 10 don't preload scan into the body while parsing the head, typically we'll block on a script
 11 early in the body that causes us to yield to do the first paint within a reasonable time.
 12
 13 However, I'm planning to change the PreloadScanner to scan into the body from the head.
 14 That significantly improves overall load time, but would hurt first paint time because
 15 fewer scripts would be blocked during parsing and thus wouldn't yield.
 16
 17 This change causes us to yield before running scripts if we haven't painted yet (regardless
 18 of whether or not the script is loaded). In addition to allowing the above mentioned
 19 PreloadScanner change to be implemented without regressing first paint time, this also
 20 improves first paint time by itself.
 21
 22 I tested Alexa's top 45 websites using Web Page Replay to control the content and simulate
 23 bandwidth. This patch improved average first paint time by 1% over an unlimited connection,
 24 6% over a 1Mbps connection and 11% over a 5Mbps connection. There was no statistically
 25 signifcant change in page load time.
 26
 27 Within the pages tested, 33 had no statistically significant change in time to first paint,
 28 12 improved, and none regressed. Of the improved, some of the standouts from the 1Mbps set
 29 are: 20% on youtube, 37% on wiki, 27% on ebay, 13% on cnn, 16% on espn, 74% on sohu.
 30
 31 * html/parser/HTMLDocumentParser.cpp:
 32 (WebCore::HTMLDocumentParser::canTakeNextToken): This is the new yield point.
 33 (WebCore::HTMLDocumentParser::pumpTokenizer): Remove ASSERT that we are not paused. isPaused
 34 means that we are waiting for a script. Bug 54574 changed pumpTokenizer() so that it does
 35 the right thing whether we are just before a token or waiting for a script. Now that we may
 36 yield before a token or before a script, this may be called while paused.
 37 * html/parser/HTMLParserScheduler.cpp:
 38 (WebCore::isLayoutTimerActive): Added a FIXME because r52919 changed minimumLayoutDelay()
 39 to return m_extraLayoutDelay instead of 0 as a minimum. So checking !minimumLayoutDelay()
 40 no longer works. The fix is to change it to check minimumLayoutDelay() ==
 41 m_extraLayoutDelay. But this is all the more reason to move this method onto Document. I'll
 42 do this in a follow up.
 43 (WebCore::HTMLParserScheduler::checkForYieldBeforeScript): Added.
 44 * html/parser/HTMLParserScheduler.h:
 45 (WebCore::HTMLParserScheduler::checkForYieldBeforeToken):
 46 * page/FrameView.h:
 47 (WebCore::FrameView::hasEverPainted): Added.
 48
1492011-02-16 Tony Gentilcore <tonyg@chromium.org>
250
351 Reviewed by Eric Seidel.

Source/WebCore/html/parser/HTMLDocumentParser.cpp

@@bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& ses
207207
208208 // The parser will pause itself when waiting on a script to load or run.
209209 if (m_treeBuilder->isPaused()) {
 210 if (mode == AllowYield && m_parserScheduler->checkForYieldBeforeScript(session))
 211 return false;
 212
210213 // If we're paused waiting for a script, we try to execute scripts before continuing.
211214 bool shouldContinueParsing = runScriptsForPausedTreeBuilder();
212215 m_treeBuilder->setPaused(!shouldContinueParsing);

@@bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& ses
224227 && document()->frame() && document()->frame()->navigationScheduler()->locationChangePending())
225228 return false;
226229
227  if (mode == AllowYield)
228  m_parserScheduler->checkForYieldBeforeToken(session);
 230 if (mode == AllowYield && m_parserScheduler->checkForYieldBeforeToken(session))
 231 return false;
229232
230233 return true;
231234}

@@bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& ses
233236void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
234237{
235238 ASSERT(!isStopped());
236  ASSERT(!m_treeBuilder->isPaused());
237239 ASSERT(!isScheduledForResume());
238240 // ASSERT that this object is both attached to the Document and protected.
239241 ASSERT(refCount() >= 2);

@@void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
246248 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), m_input.current().length(), m_tokenizer->lineNumber());
247249
248250 PumpSession session;
249  while (canTakeNextToken(mode, session) && !session.needsYield) {
 251 while (canTakeNextToken(mode, session)) {
250252 m_sourceTracker.start(m_input, m_token);
251253 if (!m_tokenizer->nextToken(m_input.current(), m_token))
252254 break;

Source/WebCore/html/parser/HTMLParserScheduler.cpp

@@HTMLParserScheduler::~HTMLParserScheduler()
7878static bool isLayoutTimerActive(Document* doc)
7979{
8080 ASSERT(doc);
 81 // FIXME: This is broken on Android because minimumLayoutDelay is never 0.
8182 return doc->view() && doc->view()->layoutPending() && !doc->minimumLayoutDelay();
8283}
8384

@@void HTMLParserScheduler::continueNextChunkTimerFired(Timer<HTMLParserScheduler>
9394 m_parser->resumeParsingAfterYield();
9495}
9596
 97bool HTMLParserScheduler::checkForYieldBeforeScript(PumpSession& session)
 98{
 99 // If we've never painted before and a layout is pending, yield prior to running
 100 // scripts to give the page a chance to paint earlier.
 101 Document* document = m_parser->document();
 102 bool needsFirstPaint = document->view() && !document->view()->hasEverPainted();
 103 if (needsFirstPaint && isLayoutTimerActive(document)) {
 104 session.needsYield = true;
 105 return true;
 106 }
 107 return false;
 108}
 109
96110void HTMLParserScheduler::scheduleForResume()
97111{
98112 m_continueNextChunkTimer.startOneShot(0);

Source/WebCore/html/parser/HTMLParserScheduler.h

@@public:
5757 ~HTMLParserScheduler();
5858
5959 // Inline as this is called after every token in the parser.
60  void checkForYieldBeforeToken(PumpSession& session)
 60 bool checkForYieldBeforeToken(PumpSession& session)
6161 {
6262 if (session.processedTokens > m_parserChunkSize) {
6363 session.processedTokens = 0;
6464 double elapsedTime = currentTime() - session.startTime;
65  if (elapsedTime > m_parserTimeLimit)
 65 if (elapsedTime > m_parserTimeLimit) {
6666 session.needsYield = true;
 67 return true;
 68 }
6769 }
6870 ++session.processedTokens;
 71 return false;
6972 }
 73 bool checkForYieldBeforeScript(PumpSession&);
7074
7175 void scheduleForResume();
7276 bool isScheduledForResume() const { return m_isSuspendedWithActiveTimer || m_continueNextChunkTimer.isActive(); }

Source/WebCore/page/FrameView.h

@@public:
207207 void setPaintBehavior(PaintBehavior);
208208 PaintBehavior paintBehavior() const;
209209 bool isPainting() const;
 210 bool hasEverPainted() const { return m_lastPaintTime; }
210211 void setNodeToDraw(Node*);
211212
212213 virtual void paintOverhangAreas(GraphicsContext*, const IntRect& horizontalOverhangArea, const IntRect& verticalOverhangArea, const IntRect& dirtyRect);