Source/WebCore/ChangeLog

 12011-02-14 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::resumeParsingAfterYield): The scheduler may not call this
 33 method when resuming from a yield before a token or a yield before a script. So it checks
 34 whether the parser is paused (waiting for a script) and runs scripts first.
 35 (WebCore::HTMLDocumentParser::runScriptsForPausedTreeBuilder): Moved setPaused bit into this
 36 method so that resumeParsingAfterYield doesn't have to duplicate it from the pumpTokenizer
 37 loop.
 38 (WebCore::HTMLDocumentParser::pumpTokenizer): After taking each </script> from the
 39 tokenizer, give the scheduler a chance to determine whether to yield before running that
 40 script.
 41 (WebCore::HTMLDocumentParser::isWaitingForScripts): Inline this method since it is now call
 42 for every token processed.
 43 * html/parser/HTMLDocumentParser.h:
 44 * html/parser/HTMLParserScheduler.cpp:
 45 (WebCore::isLayoutTimerActive): Added a FIXME because r52919 changed minimumLayoutDelay()
 46 to return m_extraLayoutDelay instead of 0 as a minimum. So checking !minimumLayoutDelay()
 47 no longer works. The fix is to change it to check minimumLayoutDelay() ==
 48 m_extraLayoutDelay. But this is all the more reason to move this method onto Document. I'll
 49 do this in a follow up.
 50 (WebCore::HTMLParserScheduler::shouldRunScriptNow): Added.
 51 * html/parser/HTMLParserScheduler.h:
 52 * page/FrameView.h:
 53 (WebCore::FrameView::hasEverPainted): Added.
 54
1552011-02-15 Adam Barth <abarth@webkit.org>
256
357 Reviewed by Eric Seidel.

Source/WebCore/html/parser/HTMLDocumentParser.cpp

@@void HTMLDocumentParser::resumeParsingAfterYield()
182182 // but we need to ensure it isn't deleted yet.
183183 RefPtr<HTMLDocumentParser> protect(this);
184184
 185 if (isWaitingForScripts()) {
 186 runScriptsForPausedTreeBuilder();
 187
 188 // JavaScript may have stopped or detached the parser.
 189 if (isStopped() || isWaitingForScripts())
 190 return;
 191 }
 192
185193 // We should never be here unless we can pump immediately. Call pumpTokenizer()
186194 // directly so that ASSERTS will fire if we're wrong.
187195 pumpTokenizer(AllowYield);
188196 endIfDelayed();
189197}
190198
191 bool HTMLDocumentParser::runScriptsForPausedTreeBuilder()
 199void HTMLDocumentParser::runScriptsForPausedTreeBuilder()
192200{
193  ASSERT(m_treeBuilder->isPaused());
 201 ASSERT(isWaitingForScripts());
194202
195203 TextPosition1 scriptStartPosition = TextPosition1::belowRangePosition();
196204 RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition);
197205 // We will not have a scriptRunner when parsing a DocumentFragment.
198  if (!m_scriptRunner)
199  return true;
200  return m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
 206 if (!m_scriptRunner) {
 207 m_treeBuilder->setPaused(false);
 208 return;
 209 }
 210 bool didExecute = m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
 211 m_treeBuilder->setPaused(!didExecute);
201212}
202213
203214void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
204215{
205216 ASSERT(!isStopped());
206  ASSERT(!m_treeBuilder->isPaused());
 217 ASSERT(!isWaitingForScripts());
207218 ASSERT(!isScheduledForResume());
208219 // ASSERT that this object is both attached to the Document and protected.
209220 ASSERT(refCount() >= 2);

@@void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
246257 if (!m_treeBuilder->isPaused())
247258 continue;
248259
249  // If we're paused waiting for a script, we try to execute scripts before continuing.
250  bool shouldContinueParsing = runScriptsForPausedTreeBuilder();
251  m_treeBuilder->setPaused(!shouldContinueParsing);
 260 if (mode == AllowYield && !m_parserScheduler->shouldRunScriptNow())
 261 break;
 262
 263 runScriptsForPausedTreeBuilder();
252264
253265 // JavaScript may have stopped or detached the parser.
254266 if (isStopped())
255267 return;
256268
257  if (!shouldContinueParsing)
 269 // The parser will pause itself when waiting on a script to load or run.
 270 if (m_treeBuilder->isPaused())
258271 break;
259272 }
260273

@@bool HTMLDocumentParser::isWaitingForScripts() const
439452void HTMLDocumentParser::resumeParsingAfterScriptExecution()
440453{
441454 ASSERT(!inScriptExecution());
442  ASSERT(!m_treeBuilder->isPaused());
 455 ASSERT(!isWaitingForScripts());
443456
444457 m_preloadScanner.clear();
445458 pumpTokenizerIfPossible(AllowYield);

@@void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
478491 return;
479492 }
480493
481  ASSERT(m_treeBuilder->isPaused());
 494 ASSERT(isWaitingForScripts());
482495 // Note: We only ever wait on one script at a time, so we always know this
483496 // is the one we were waiting on and can un-pause the tree builder.
484497 m_treeBuilder->setPaused(false);

@@void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
504517 RefPtr<HTMLDocumentParser> protect(this);
505518
506519 ASSERT(!m_scriptRunner->isExecutingScript());
507  ASSERT(m_treeBuilder->isPaused());
 520 ASSERT(isWaitingForScripts());
508521 // Note: We only ever wait on one script at a time, so we always know this
509522 // is the one we were waiting on and can un-pause the tree builder.
510523 m_treeBuilder->setPaused(false);

Source/WebCore/html/parser/HTMLDocumentParser.h

@@private:
119119 void pumpTokenizer(SynchronousMode);
120120 void pumpTokenizerIfPossible(SynchronousMode);
121121
122  bool runScriptsForPausedTreeBuilder();
 122 void runScriptsForPausedTreeBuilder();
123123 void resumeParsingAfterScriptExecution();
124124
125125 void begin();

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::shouldRunScriptNow()
 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 m_continueNextChunkTimer.startOneShot(0);
 105 return false;
 106 }
 107 return true;
 108}
 109
96110void HTMLParserScheduler::suspend()
97111{
98112 ASSERT(!m_isSuspendedWithActiveTimer);

Source/WebCore/html/parser/HTMLParserScheduler.h

@@public:
7070 ++session.processedTokens;
7171 return true;
7272 }
 73
 74 bool shouldRunScriptNow();
7375
7476 bool isScheduledForResume() const { return m_isSuspendedWithActiveTimer || m_continueNextChunkTimer.isActive(); }
7577

Source/WebCore/page/FrameView.h

@@public:
201201 void setPaintBehavior(PaintBehavior);
202202 PaintBehavior paintBehavior() const;
203203 bool isPainting() const;
 204 bool hasEverPainted() const { return m_lastPaintTime; }
204205 void setNodeToDraw(Node*);
205206
206207 virtual void paintOverhangAreas(GraphicsContext*, const IntRect& horizontalOverhangArea, const IntRect& verticalOverhangArea, const IntRect& dirtyRect);