Changeset 277818 in webkit
- Timestamp:
- May 20, 2021, 12:40:04 PM (5 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 6 edited
-
ChangeLog (modified) (1 diff)
-
dom/ScriptElement.h (modified) (2 diffs)
-
html/parser/HTMLDocumentParser.cpp (modified) (1 diff)
-
html/parser/HTMLParserScheduler.cpp (modified) (4 diffs)
-
html/parser/HTMLParserScheduler.h (modified) (5 diffs)
-
html/parser/HTMLTreeBuilder.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r277816 r277818 1 2021-05-20 Antti Koivisto <antti@apple.com> 2 3 HTML parser should yield more aggressively 4 https://bugs.webkit.org/show_bug.cgi?id=224609 5 rdar://73458064 6 7 Reviewed by Darin Adler. 8 9 During page loading we may spend substantial amount of time in individual invocations of 10 the HTML parser. This can be a problem since it blocks rendering updates and so visual 11 page load progression. 12 13 Most of this time is not actually spend parsing, it rather goes into synchronous script 14 execution and DOM work triggered by scripts. This patch adds a more effective mechanism 15 where we may decide to yield the parser before executing a script. 16 17 * dom/ScriptElement.h: 18 * html/parser/HTMLDocumentParser.cpp: 19 (WebCore::HTMLDocumentParser::pumpTokenizerLoop): 20 * html/parser/HTMLParserScheduler.cpp: 21 (WebCore::parserTimeLimit): 22 (WebCore::PumpSession::PumpSession): 23 (WebCore::HTMLParserScheduler::HTMLParserScheduler): 24 (WebCore::HTMLParserScheduler::shouldYieldBeforeExecutingScript): 25 26 Consider yielding before script execution after 16ms has elapsed and at least 256 tokens have been parsed. 27 Only yield for synchronous scripts. 28 Don't yield on very short inline scripts (this is an imperfect way to try to guess the execution cost). 29 30 * html/parser/HTMLParserScheduler.h: 31 (WebCore::HTMLParserScheduler::shouldYieldBeforeToken): 32 (WebCore::HTMLParserScheduler::checkForYield): 33 34 Don't reset the token count, instead track the last yield check point. 35 36 * html/parser/HTMLTreeBuilder.h: 37 (WebCore::HTMLTreeBuilder::scriptToProcess const): 38 1 39 2021-05-20 Chris Dumez <cdumez@apple.com> 2 40 -
trunk/Source/WebCore/dom/ScriptElement.h
r256808 r277818 56 56 57 57 virtual bool hasAsyncAttribute() const = 0; 58 virtual bool hasDeferAttribute() const = 0; 59 virtual bool hasSourceAttribute() const = 0; 60 virtual bool hasNoModuleAttribute() const = 0; 58 61 59 62 // XML parser calls these … … 115 118 virtual String forAttributeValue() const = 0; 116 119 virtual String eventAttributeValue() const = 0; 117 virtual bool hasDeferAttribute() const = 0;118 virtual bool hasSourceAttribute() const = 0;119 virtual bool hasNoModuleAttribute() const = 0;120 120 virtual ReferrerPolicy referrerPolicy() const = 0; 121 121 -
trunk/Source/WebCore/html/parser/HTMLDocumentParser.cpp
r272622 r277818 259 259 do { 260 260 if (UNLIKELY(isWaitingForScripts())) { 261 if (mode == AllowYield && m_parserScheduler->shouldYieldBeforeExecutingScript( session))261 if (mode == AllowYield && m_parserScheduler->shouldYieldBeforeExecutingScript(m_treeBuilder->scriptToProcess(), session)) 262 262 return true; 263 263 264 runScriptsForPausedTreeBuilder(); 264 265 // If we're paused waiting for a script, we try to execute scripts before continuing. -
trunk/Source/WebCore/html/parser/HTMLParserScheduler.cpp
r254087 r277818 32 32 #include "HTMLDocumentParser.h" 33 33 #include "Page.h" 34 35 // defaultParserTimeLimit is the seconds the parser will run in one write() call 36 // before yielding. Inline <script> execution can cause it to exceed the limit. 37 // FIXME: We would like this value to be 0.2. 38 static const double defaultParserTimeLimit = 0.500; 34 #include "ScriptElement.h" 39 35 40 36 namespace WebCore { 41 37 42 static doubleparserTimeLimit(Page* page)38 static Seconds parserTimeLimit(Page* page) 43 39 { 40 // Always yield after exceeding this. 41 constexpr auto defaultParserTimeLimit = 500_ms; 42 44 43 // We're using the poorly named customHTMLTokenizerTimeDelay setting. 45 44 if (page && page->hasCustomHTMLTokenizerTimeDelay()) 46 return page->customHTMLTokenizerTimeDelay();45 return Seconds(page->customHTMLTokenizerTimeDelay()); 47 46 return defaultParserTimeLimit; 48 47 } … … 66 65 : NestingLevelIncrementer(nestingLevel) 67 66 , ActiveParserSession(document) 68 // Setting processedTokens to INT_MAX causes us to check for yields69 // after any token during any parse where yielding is allowed.70 // At that time we'll initialize startTime.71 , processedTokens(INT_MAX)72 , didSeeScript(false)73 67 { 74 68 } … … 78 72 HTMLParserScheduler::HTMLParserScheduler(HTMLDocumentParser& parser) 79 73 : m_parser(parser) 80 , m_parserTimeLimit( Seconds(parserTimeLimit(m_parser.document()->page())))74 , m_parserTimeLimit(parserTimeLimit(m_parser.document()->page())) 81 75 , m_continueNextChunkTimer(*this, &HTMLParserScheduler::continueNextChunkTimerFired) 82 76 , m_isSuspendedWithActiveTimer(false) … … 105 99 } 106 100 107 bool HTMLParserScheduler::shouldYieldBeforeExecutingScript( PumpSession& session)101 bool HTMLParserScheduler::shouldYieldBeforeExecutingScript(const ScriptElement* scriptElement, PumpSession& session) 108 102 { 109 103 // If we've never painted before and a layout is pending, yield prior to running 110 104 // scripts to give the page a chance to paint earlier. 111 105 RefPtr<Document> document = m_parser.document(); 112 bool needsFirstPaint = document->view() && !document->view()->hasEverPainted(); 106 113 107 session.didSeeScript = true; 108 109 if (!document->body()) 110 return false; 111 112 if (!document->haveStylesheetsLoaded()) 113 return false; 114 114 115 115 if (UNLIKELY(m_documentHasActiveParserYieldTokens)) 116 116 return true; 117 117 118 return needsFirstPaint && document->isLayoutTimerActive(); 118 auto elapsedTime = MonotonicTime::now() - session.startTime; 119 120 constexpr auto elapsedTimeLimit = 16_ms; 121 // Require at least some new parsed content before yielding. 122 constexpr auto tokenLimit = 256; 123 // Don't yield on very short inline scripts. This is an imperfect way to try to guess the execution cost. 124 constexpr auto inlineScriptLengthLimit = 1024; 125 126 if (elapsedTime < elapsedTimeLimit) 127 return false; 128 if (session.processedTokens < tokenLimit) 129 return false; 130 131 if (scriptElement) { 132 // Async and deferred scripts are not executed by the parser. 133 if (scriptElement->hasAsyncAttribute() || scriptElement->hasDeferAttribute()) 134 return false; 135 if (!scriptElement->hasSourceAttribute() && scriptElement->scriptContent().length() < inlineScriptLengthLimit) 136 return false; 137 } 138 139 return true; 119 140 } 120 141 -
trunk/Source/WebCore/html/parser/HTMLParserScheduler.h
r254087 r277818 38 38 class Document; 39 39 class HTMLDocumentParser; 40 class ScriptElement; 40 41 41 42 class ActiveParserSession { … … 53 54 ~PumpSession(); 54 55 55 unsigned processedTokens; 56 MonotonicTime startTime; 57 bool didSeeScript; 56 unsigned processedTokens { 0 }; 57 unsigned processedTokensOnLastCheck { 0 }; 58 MonotonicTime startTime { MonotonicTime::now() }; 59 bool didSeeScript { false }; 58 60 }; 59 61 … … 73 75 return true; 74 76 75 if (UNLIKELY(session.processedTokens > numberOfTokensBeforeCheckingForYield || session.didSeeScript))77 if (UNLIKELY(session.processedTokens > session.processedTokensOnLastCheck + numberOfTokensBeforeCheckingForYield || session.didSeeScript)) 76 78 return checkForYield(session); 77 79 … … 79 81 return false; 80 82 } 81 bool shouldYieldBeforeExecutingScript( PumpSession&);83 bool shouldYieldBeforeExecutingScript(const ScriptElement*, PumpSession&); 82 84 83 85 void scheduleForResume(); … … 109 111 bool checkForYield(PumpSession& session) 110 112 { 111 session.processedTokens = 1;113 session.processedTokensOnLastCheck = session.processedTokens; 112 114 session.didSeeScript = false; 113 114 // MonotonicTime::now() can be expensive. By delaying, we avoided calling115 // MonotonicTime::now() when constructing non-yielding PumpSessions.116 if (!session.startTime) {117 session.startTime = MonotonicTime::now();118 return false;119 }120 115 121 116 Seconds elapsedTime = MonotonicTime::now() - session.startTime; -
trunk/Source/WebCore/html/parser/HTMLTreeBuilder.h
r254087 r277818 67 67 // Must be called to take the parser-blocking script before calling the parser again. 68 68 RefPtr<ScriptElement> takeScriptToProcess(TextPosition& scriptStartPosition); 69 const ScriptElement* scriptToProcess() const { return m_scriptToProcess.get(); } 69 70 70 71 std::unique_ptr<CustomElementConstructionData> takeCustomElementConstructionData() { return WTFMove(m_customElementToConstruct); }
Note:
See TracChangeset
for help on using the changeset viewer.