1 | /* |
2 | * Copyright (C) 2010 Google, Inc. All Rights Reserved. |
3 | * Copyright (C) 2015-2017 Apple Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "HTMLDocumentParser.h" |
29 | |
30 | #include "CustomHeaderFields.h" |
31 | #include "CustomElementReactionQueue.h" |
32 | #include "DocumentFragment.h" |
33 | #include "DocumentLoader.h" |
34 | #include "Frame.h" |
35 | #include "HTMLDocument.h" |
36 | #include "HTMLParserScheduler.h" |
37 | #include "HTMLPreloadScanner.h" |
38 | #include "HTMLScriptRunner.h" |
39 | #include "HTMLTreeBuilder.h" |
40 | #include "HTMLUnknownElement.h" |
41 | #include "JSCustomElementInterface.h" |
42 | #include "LinkLoader.h" |
43 | #include "Microtasks.h" |
44 | #include "NavigationScheduler.h" |
45 | #include "ScriptElement.h" |
46 | #include "ThrowOnDynamicMarkupInsertionCountIncrementer.h" |
47 | |
48 | namespace WebCore { |
49 | |
50 | using namespace HTMLNames; |
51 | |
52 | HTMLDocumentParser::HTMLDocumentParser(HTMLDocument& document) |
53 | : ScriptableDocumentParser(document) |
54 | , m_options(document) |
55 | , m_tokenizer(m_options) |
56 | , m_scriptRunner(std::make_unique<HTMLScriptRunner>(document, static_cast<HTMLScriptRunnerHost&>(*this))) |
57 | , m_treeBuilder(std::make_unique<HTMLTreeBuilder>(*this, document, parserContentPolicy(), m_options)) |
58 | , m_parserScheduler(std::make_unique<HTMLParserScheduler>(*this)) |
59 | , m_xssAuditorDelegate(document) |
60 | , m_preloader(std::make_unique<HTMLResourcePreloader>(document)) |
61 | { |
62 | } |
63 | |
64 | Ref<HTMLDocumentParser> HTMLDocumentParser::create(HTMLDocument& document) |
65 | { |
66 | return adoptRef(*new HTMLDocumentParser(document)); |
67 | } |
68 | |
69 | inline HTMLDocumentParser::HTMLDocumentParser(DocumentFragment& fragment, Element& contextElement, ParserContentPolicy rawPolicy) |
70 | : ScriptableDocumentParser(fragment.document(), rawPolicy) |
71 | , m_options(fragment.document()) |
72 | , m_tokenizer(m_options) |
73 | , m_treeBuilder(std::make_unique<HTMLTreeBuilder>(*this, fragment, contextElement, parserContentPolicy(), m_options)) |
74 | , m_xssAuditorDelegate(fragment.document()) |
75 | { |
76 | // https://html.spec.whatwg.org/multipage/syntax.html#parsing-html-fragments |
77 | if (contextElement.isHTMLElement()) |
78 | m_tokenizer.updateStateFor(contextElement.tagQName().localName()); |
79 | m_xssAuditor.initForFragment(); |
80 | } |
81 | |
82 | inline Ref<HTMLDocumentParser> HTMLDocumentParser::create(DocumentFragment& fragment, Element& contextElement, ParserContentPolicy parserContentPolicy) |
83 | { |
84 | return adoptRef(*new HTMLDocumentParser(fragment, contextElement, parserContentPolicy)); |
85 | } |
86 | |
87 | HTMLDocumentParser::~HTMLDocumentParser() |
88 | { |
89 | ASSERT(!m_parserScheduler); |
90 | ASSERT(!m_pumpSessionNestingLevel); |
91 | ASSERT(!m_preloadScanner); |
92 | ASSERT(!m_insertionPreloadScanner); |
93 | } |
94 | |
95 | void HTMLDocumentParser::detach() |
96 | { |
97 | ScriptableDocumentParser::detach(); |
98 | |
99 | if (m_scriptRunner) |
100 | m_scriptRunner->detach(); |
101 | // FIXME: It seems wrong that we would have a preload scanner here. |
102 | // Yet during fast/dom/HTMLScriptElement/script-load-events.html we do. |
103 | m_preloadScanner = nullptr; |
104 | m_insertionPreloadScanner = nullptr; |
105 | m_parserScheduler = nullptr; // Deleting the scheduler will clear any timers. |
106 | } |
107 | |
108 | void HTMLDocumentParser::stopParsing() |
109 | { |
110 | DocumentParser::stopParsing(); |
111 | m_parserScheduler = nullptr; // Deleting the scheduler will clear any timers. |
112 | } |
113 | |
114 | // This kicks off "Once the user agent stops parsing" as described by: |
115 | // https://html.spec.whatwg.org/multipage/syntax.html#the-end |
116 | void HTMLDocumentParser::prepareToStopParsing() |
117 | { |
118 | ASSERT(!hasInsertionPoint()); |
119 | |
120 | // pumpTokenizer can cause this parser to be detached from the Document, |
121 | // but we need to ensure it isn't deleted yet. |
122 | Ref<HTMLDocumentParser> protectedThis(*this); |
123 | |
124 | // NOTE: This pump should only ever emit buffered character tokens, |
125 | // so ForceSynchronous vs. AllowYield should be meaningless. |
126 | pumpTokenizerIfPossible(ForceSynchronous); |
127 | |
128 | if (isStopped()) |
129 | return; |
130 | |
131 | DocumentParser::prepareToStopParsing(); |
132 | |
133 | // We will not have a scriptRunner when parsing a DocumentFragment. |
134 | if (m_scriptRunner) |
135 | document()->setReadyState(Document::Interactive); |
136 | |
137 | // Setting the ready state above can fire mutation event and detach us |
138 | // from underneath. In that case, just bail out. |
139 | if (isDetached()) |
140 | return; |
141 | |
142 | attemptToRunDeferredScriptsAndEnd(); |
143 | } |
144 | |
145 | inline bool HTMLDocumentParser::inPumpSession() const |
146 | { |
147 | return m_pumpSessionNestingLevel > 0; |
148 | } |
149 | |
150 | inline bool HTMLDocumentParser::shouldDelayEnd() const |
151 | { |
152 | return inPumpSession() || isWaitingForScripts() || isScheduledForResume() || isExecutingScript(); |
153 | } |
154 | |
155 | void HTMLDocumentParser::didBeginYieldingParser() |
156 | { |
157 | m_parserScheduler->didBeginYieldingParser(); |
158 | } |
159 | |
160 | void HTMLDocumentParser::didEndYieldingParser() |
161 | { |
162 | m_parserScheduler->didEndYieldingParser(); |
163 | } |
164 | |
165 | bool HTMLDocumentParser::isParsingFragment() const |
166 | { |
167 | return m_treeBuilder->isParsingFragment(); |
168 | } |
169 | |
170 | bool HTMLDocumentParser::processingData() const |
171 | { |
172 | return isScheduledForResume() || inPumpSession(); |
173 | } |
174 | |
175 | void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode) |
176 | { |
177 | if (isStopped() || isWaitingForScripts()) |
178 | return; |
179 | |
180 | // Once a resume is scheduled, HTMLParserScheduler controls when we next pump. |
181 | if (isScheduledForResume()) { |
182 | ASSERT(mode == AllowYield); |
183 | return; |
184 | } |
185 | |
186 | pumpTokenizer(mode); |
187 | } |
188 | |
189 | bool HTMLDocumentParser::isScheduledForResume() const |
190 | { |
191 | return m_parserScheduler && m_parserScheduler->isScheduledForResume(); |
192 | } |
193 | |
194 | // Used by HTMLParserScheduler |
195 | void HTMLDocumentParser::resumeParsingAfterYield() |
196 | { |
197 | // pumpTokenizer can cause this parser to be detached from the Document, |
198 | // but we need to ensure it isn't deleted yet. |
199 | Ref<HTMLDocumentParser> protectedThis(*this); |
200 | |
201 | // We should never be here unless we can pump immediately. |
202 | // Call pumpTokenizer() directly so that ASSERTS will fire if we're wrong. |
203 | pumpTokenizer(AllowYield); |
204 | endIfDelayed(); |
205 | } |
206 | |
207 | void HTMLDocumentParser::runScriptsForPausedTreeBuilder() |
208 | { |
209 | ASSERT(scriptingContentIsAllowed(parserContentPolicy())); |
210 | |
211 | if (std::unique_ptr<CustomElementConstructionData> constructionData = m_treeBuilder->takeCustomElementConstructionData()) { |
212 | ASSERT(!m_treeBuilder->hasParserBlockingScriptWork()); |
213 | |
214 | // https://html.spec.whatwg.org/#create-an-element-for-the-token |
215 | { |
216 | // Prevent document.open/write during reactions by allocating the incrementer before the reactions stack. |
217 | ThrowOnDynamicMarkupInsertionCountIncrementer incrementer(*document()); |
218 | |
219 | MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint(); |
220 | |
221 | CustomElementReactionStack reactionStack(document()->execState()); |
222 | auto& elementInterface = constructionData->elementInterface.get(); |
223 | auto newElement = elementInterface.constructElementWithFallback(*document(), constructionData->name); |
224 | m_treeBuilder->didCreateCustomOrFallbackElement(WTFMove(newElement), *constructionData); |
225 | } |
226 | return; |
227 | } |
228 | |
229 | TextPosition scriptStartPosition = TextPosition::belowRangePosition(); |
230 | if (auto scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition)) { |
231 | ASSERT(!m_treeBuilder->hasParserBlockingScriptWork()); |
232 | // We will not have a scriptRunner when parsing a DocumentFragment. |
233 | if (m_scriptRunner) |
234 | m_scriptRunner->execute(scriptElement.releaseNonNull(), scriptStartPosition); |
235 | } |
236 | } |
237 | |
238 | Document* HTMLDocumentParser::contextForParsingSession() |
239 | { |
240 | // The parsing session should interact with the document only when parsing |
241 | // non-fragments. Otherwise, we might delay the load event mistakenly. |
242 | if (isParsingFragment()) |
243 | return nullptr; |
244 | return document(); |
245 | } |
246 | |
247 | bool HTMLDocumentParser::pumpTokenizerLoop(SynchronousMode mode, bool parsingFragment, PumpSession& session) |
248 | { |
249 | do { |
250 | if (UNLIKELY(isWaitingForScripts())) { |
251 | if (mode == AllowYield && m_parserScheduler->shouldYieldBeforeExecutingScript(session)) |
252 | return true; |
253 | runScriptsForPausedTreeBuilder(); |
254 | // If we're paused waiting for a script, we try to execute scripts before continuing. |
255 | if (isWaitingForScripts() || isStopped()) |
256 | return false; |
257 | } |
258 | |
259 | // FIXME: It's wrong for the HTMLDocumentParser to reach back to the Frame, but this approach is |
260 | // how the parser has always handled stopping when the page assigns window.location. What should |
261 | // happen instead is that assigning window.location causes the parser to stop parsing cleanly. |
262 | // The problem is we're not prepared to do that at every point where we run JavaScript. |
263 | if (UNLIKELY(!parsingFragment && document()->frame() && document()->frame()->navigationScheduler().locationChangePending())) |
264 | return false; |
265 | |
266 | if (UNLIKELY(mode == AllowYield && m_parserScheduler->shouldYieldBeforeToken(session))) |
267 | return true; |
268 | |
269 | if (!parsingFragment) |
270 | m_sourceTracker.startToken(m_input.current(), m_tokenizer); |
271 | |
272 | auto token = m_tokenizer.nextToken(m_input.current()); |
273 | if (!token) |
274 | return false; |
275 | |
276 | if (!parsingFragment) { |
277 | m_sourceTracker.endToken(m_input.current(), m_tokenizer); |
278 | |
279 | // We do not XSS filter innerHTML, which means we (intentionally) fail |
280 | // http/tests/security/xssAuditor/dom-write-innerHTML.html |
281 | if (auto xssInfo = m_xssAuditor.filterToken(FilterTokenRequest(*token, m_sourceTracker, m_tokenizer.shouldAllowCDATA()))) |
282 | m_xssAuditorDelegate.didBlockScript(*xssInfo); |
283 | } |
284 | |
285 | constructTreeFromHTMLToken(token); |
286 | } while (!isStopped()); |
287 | |
288 | return false; |
289 | } |
290 | |
291 | void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode) |
292 | { |
293 | ASSERT(!isStopped()); |
294 | ASSERT(!isScheduledForResume()); |
295 | |
296 | // This is an attempt to check that this object is both attached to the Document and protected by something. |
297 | ASSERT(refCount() >= 2); |
298 | |
299 | PumpSession session(m_pumpSessionNestingLevel, contextForParsingSession()); |
300 | |
301 | m_xssAuditor.init(document(), &m_xssAuditorDelegate); |
302 | |
303 | bool shouldResume = pumpTokenizerLoop(mode, isParsingFragment(), session); |
304 | |
305 | // Ensure we haven't been totally deref'ed after pumping. Any caller of this |
306 | // function should be holding a RefPtr to this to ensure we weren't deleted. |
307 | ASSERT(refCount() >= 1); |
308 | |
309 | if (isStopped()) |
310 | return; |
311 | |
312 | if (shouldResume) |
313 | m_parserScheduler->scheduleForResume(); |
314 | |
315 | if (isWaitingForScripts()) { |
316 | ASSERT(m_tokenizer.isInDataState()); |
317 | if (!m_preloadScanner) { |
318 | m_preloadScanner = std::make_unique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor()); |
319 | m_preloadScanner->appendToEnd(m_input.current()); |
320 | } |
321 | m_preloadScanner->scan(*m_preloader, *document()); |
322 | } |
323 | // The viewport definition is known here, so we can load link preloads with media attributes. |
324 | if (document()->loader()) |
325 | LinkLoader::loadLinksFromHeader(document()->loader()->response().httpHeaderField(HTTPHeaderName::Link), document()->url(), *document(), LinkLoader::MediaAttributeCheck::MediaAttributeNotEmpty); |
326 | } |
327 | |
328 | void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLTokenizer::TokenPtr& rawToken) |
329 | { |
330 | AtomicHTMLToken token(*rawToken); |
331 | |
332 | // We clear the rawToken in case constructTreeFromAtomicToken |
333 | // synchronously re-enters the parser. We don't clear the token immedately |
334 | // for Character tokens because the AtomicHTMLToken avoids copying the |
335 | // characters by keeping a pointer to the underlying buffer in the |
336 | // HTMLToken. Fortunately, Character tokens can't cause us to re-enter |
337 | // the parser. |
338 | // |
339 | // FIXME: Stop clearing the rawToken once we start running the parser off |
340 | // the main thread or once we stop allowing synchronous JavaScript |
341 | // execution from parseAttribute. |
342 | if (rawToken->type() != HTMLToken::Character) { |
343 | // Clearing the TokenPtr makes sure we don't clear the HTMLToken a second time |
344 | // later when the TokenPtr is destroyed. |
345 | rawToken.clear(); |
346 | } |
347 | |
348 | m_treeBuilder->constructTree(WTFMove(token)); |
349 | } |
350 | |
351 | bool HTMLDocumentParser::hasInsertionPoint() |
352 | { |
353 | // FIXME: The wasCreatedByScript() branch here might not be fully correct. |
354 | // Our model of the EOF character differs slightly from the one in the spec |
355 | // because our treatment is uniform between network-sourced and script-sourced |
356 | // input streams whereas the spec treats them differently. |
357 | return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile()); |
358 | } |
359 | |
360 | void HTMLDocumentParser::insert(SegmentedString&& source) |
361 | { |
362 | if (isStopped()) |
363 | return; |
364 | |
365 | // pumpTokenizer can cause this parser to be detached from the Document, |
366 | // but we need to ensure it isn't deleted yet. |
367 | Ref<HTMLDocumentParser> protectedThis(*this); |
368 | |
369 | source.setExcludeLineNumbers(); |
370 | m_input.insertAtCurrentInsertionPoint(WTFMove(source)); |
371 | pumpTokenizerIfPossible(ForceSynchronous); |
372 | |
373 | if (isWaitingForScripts()) { |
374 | // Check the document.write() output with a separate preload scanner as |
375 | // the main scanner can't deal with insertions. |
376 | if (!m_insertionPreloadScanner) |
377 | m_insertionPreloadScanner = std::make_unique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor()); |
378 | m_insertionPreloadScanner->appendToEnd(source); |
379 | m_insertionPreloadScanner->scan(*m_preloader, *document()); |
380 | } |
381 | |
382 | endIfDelayed(); |
383 | } |
384 | |
385 | void HTMLDocumentParser::append(RefPtr<StringImpl>&& inputSource) |
386 | { |
387 | if (isStopped()) |
388 | return; |
389 | |
390 | // pumpTokenizer can cause this parser to be detached from the Document, |
391 | // but we need to ensure it isn't deleted yet. |
392 | Ref<HTMLDocumentParser> protectedThis(*this); |
393 | |
394 | String source { WTFMove(inputSource) }; |
395 | |
396 | if (m_preloadScanner) { |
397 | if (m_input.current().isEmpty() && !isWaitingForScripts()) { |
398 | // We have parsed until the end of the current input and so are now moving ahead of the preload scanner. |
399 | // Clear the scanner so we know to scan starting from the current input point if we block again. |
400 | m_preloadScanner = nullptr; |
401 | } else { |
402 | m_preloadScanner->appendToEnd(source); |
403 | if (isWaitingForScripts()) |
404 | m_preloadScanner->scan(*m_preloader, *document()); |
405 | } |
406 | } |
407 | |
408 | m_input.appendToEnd(source); |
409 | |
410 | if (inPumpSession()) { |
411 | // We've gotten data off the network in a nested write. |
412 | // We don't want to consume any more of the input stream now. Do |
413 | // not worry. We'll consume this data in a less-nested write(). |
414 | return; |
415 | } |
416 | |
417 | pumpTokenizerIfPossible(AllowYield); |
418 | |
419 | endIfDelayed(); |
420 | } |
421 | |
422 | void HTMLDocumentParser::end() |
423 | { |
424 | ASSERT(!isDetached()); |
425 | ASSERT(!isScheduledForResume()); |
426 | |
427 | // Informs the rest of WebCore that parsing is really finished (and deletes this). |
428 | m_treeBuilder->finished(); |
429 | } |
430 | |
431 | void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd() |
432 | { |
433 | ASSERT(isStopping()); |
434 | ASSERT(!hasInsertionPoint()); |
435 | if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing()) |
436 | return; |
437 | end(); |
438 | } |
439 | |
440 | void HTMLDocumentParser::attemptToEnd() |
441 | { |
442 | // finish() indicates we will not receive any more data. If we are waiting on |
443 | // an external script to load, we can't finish parsing quite yet. |
444 | |
445 | if (shouldDelayEnd()) { |
446 | m_endWasDelayed = true; |
447 | return; |
448 | } |
449 | prepareToStopParsing(); |
450 | } |
451 | |
452 | void HTMLDocumentParser::endIfDelayed() |
453 | { |
454 | // If we've already been detached, don't bother ending. |
455 | if (isDetached()) |
456 | return; |
457 | |
458 | if (!m_endWasDelayed || shouldDelayEnd()) |
459 | return; |
460 | |
461 | m_endWasDelayed = false; |
462 | prepareToStopParsing(); |
463 | } |
464 | |
465 | void HTMLDocumentParser::finish() |
466 | { |
467 | // FIXME: We should ASSERT(!m_parserStopped) here, since it does not |
468 | // makes sense to call any methods on DocumentParser once it's been stopped. |
469 | // However, FrameLoader::stop calls DocumentParser::finish unconditionally. |
470 | |
471 | // We're not going to get any more data off the network, so we tell the |
472 | // input stream we've reached the end of file. finish() can be called more |
473 | // than once, if the first time does not call end(). |
474 | if (!m_input.haveSeenEndOfFile()) |
475 | m_input.markEndOfFile(); |
476 | |
477 | attemptToEnd(); |
478 | } |
479 | |
480 | bool HTMLDocumentParser::isExecutingScript() const |
481 | { |
482 | return m_scriptRunner && m_scriptRunner->isExecutingScript(); |
483 | } |
484 | |
485 | TextPosition HTMLDocumentParser::textPosition() const |
486 | { |
487 | auto& currentString = m_input.current(); |
488 | return TextPosition(currentString.currentLine(), currentString.currentColumn()); |
489 | } |
490 | |
491 | bool HTMLDocumentParser::shouldAssociateConsoleMessagesWithTextPosition() const |
492 | { |
493 | return inPumpSession() && !isExecutingScript(); |
494 | } |
495 | |
496 | bool HTMLDocumentParser::isWaitingForScripts() const |
497 | { |
498 | // When the TreeBuilder encounters a </script> tag, it returns to the HTMLDocumentParser |
499 | // where the script is transfered from the treebuilder to the script runner. |
500 | // The script runner will hold the script until its loaded and run. During |
501 | // any of this time, we want to count ourselves as "waiting for a script" and thus |
502 | // run the preload scanner, as well as delay completion of parsing. |
503 | bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScriptWork(); |
504 | bool scriptRunnerHasBlockingScript = m_scriptRunner && m_scriptRunner->hasParserBlockingScript(); |
505 | // Since the parser is paused while a script runner has a blocking script, it should |
506 | // never be possible to end up with both objects holding a blocking script. |
507 | ASSERT(!(treeBuilderHasBlockingScript && scriptRunnerHasBlockingScript)); |
508 | // If either object has a blocking script, the parser should be paused. |
509 | return treeBuilderHasBlockingScript || scriptRunnerHasBlockingScript; |
510 | } |
511 | |
512 | void HTMLDocumentParser::resumeParsingAfterScriptExecution() |
513 | { |
514 | ASSERT(!isExecutingScript()); |
515 | ASSERT(!isWaitingForScripts()); |
516 | |
517 | // pumpTokenizer can cause this parser to be detached from the Document, |
518 | // but we need to ensure it isn't deleted yet. |
519 | Ref<HTMLDocumentParser> protectedThis(*this); |
520 | |
521 | m_insertionPreloadScanner = nullptr; |
522 | pumpTokenizerIfPossible(AllowYield); |
523 | endIfDelayed(); |
524 | } |
525 | |
526 | void HTMLDocumentParser::watchForLoad(PendingScript& pendingScript) |
527 | { |
528 | ASSERT(!pendingScript.isLoaded()); |
529 | // setClient would call notifyFinished if the load were complete. |
530 | // Callers do not expect to be re-entered from this call, so they should |
531 | // not an already-loaded PendingScript. |
532 | pendingScript.setClient(*this); |
533 | } |
534 | |
535 | void HTMLDocumentParser::stopWatchingForLoad(PendingScript& pendingScript) |
536 | { |
537 | pendingScript.clearClient(); |
538 | } |
539 | |
540 | void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan() |
541 | { |
542 | ASSERT(m_preloadScanner); |
543 | m_preloadScanner->appendToEnd(m_input.current()); |
544 | m_preloadScanner->scan(*m_preloader, *document()); |
545 | } |
546 | |
547 | void HTMLDocumentParser::notifyFinished(PendingScript& pendingScript) |
548 | { |
549 | // pumpTokenizer can cause this parser to be detached from the Document, |
550 | // but we need to ensure it isn't deleted yet. |
551 | Ref<HTMLDocumentParser> protectedThis(*this); |
552 | |
553 | // After Document parser is stopped or detached, the parser-inserted deferred script execution should be ignored. |
554 | if (isStopped()) |
555 | return; |
556 | |
557 | ASSERT(m_scriptRunner); |
558 | ASSERT(!isExecutingScript()); |
559 | if (isStopping()) { |
560 | attemptToRunDeferredScriptsAndEnd(); |
561 | return; |
562 | } |
563 | |
564 | m_scriptRunner->executeScriptsWaitingForLoad(pendingScript); |
565 | if (!isWaitingForScripts()) |
566 | resumeParsingAfterScriptExecution(); |
567 | } |
568 | |
569 | bool HTMLDocumentParser::hasScriptsWaitingForStylesheets() const |
570 | { |
571 | return m_scriptRunner && m_scriptRunner->hasScriptsWaitingForStylesheets(); |
572 | } |
573 | |
574 | void HTMLDocumentParser::executeScriptsWaitingForStylesheets() |
575 | { |
576 | // Document only calls this when the Document owns the DocumentParser |
577 | // so this will not be called in the DocumentFragment case. |
578 | ASSERT(m_scriptRunner); |
579 | // Ignore calls unless we have a script blocking the parser waiting on a |
580 | // stylesheet load. Otherwise we are currently parsing and this |
581 | // is a re-entrant call from encountering a </ style> tag. |
582 | if (!m_scriptRunner->hasScriptsWaitingForStylesheets()) |
583 | return; |
584 | |
585 | // pumpTokenizer can cause this parser to be detached from the Document, |
586 | // but we need to ensure it isn't deleted yet. |
587 | Ref<HTMLDocumentParser> protectedThis(*this); |
588 | m_scriptRunner->executeScriptsWaitingForStylesheets(); |
589 | if (!isWaitingForScripts()) |
590 | resumeParsingAfterScriptExecution(); |
591 | } |
592 | |
593 | void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment& fragment, Element& contextElement, ParserContentPolicy parserContentPolicy) |
594 | { |
595 | auto parser = create(fragment, contextElement, parserContentPolicy); |
596 | parser->insert(source); // Use insert() so that the parser will not yield. |
597 | parser->finish(); |
598 | ASSERT(!parser->processingData()); |
599 | parser->detach(); |
600 | } |
601 | |
602 | void HTMLDocumentParser::suspendScheduledTasks() |
603 | { |
604 | if (m_parserScheduler) |
605 | m_parserScheduler->suspend(); |
606 | } |
607 | |
608 | void HTMLDocumentParser::resumeScheduledTasks() |
609 | { |
610 | if (m_parserScheduler) |
611 | m_parserScheduler->resume(); |
612 | } |
613 | |
614 | } |
615 | |