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