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