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 "Element.h"
32#include "Frame.h"
33#include "HTMLNames.h"
34#include "HTMLParserScheduler.h"
35#include "HTMLTokenizer.h"
36#include "HTMLPreloadScanner.h"
37#include "HTMLScriptRunner.h"
38#include "HTMLTreeBuilder.h"
39#include "HTMLDocument.h"
40#include "InspectorInstrumentation.h"
41#include "NestingLevelIncrementer.h"
42#include "Settings.h"
43
44namespace WebCore {
45
46using namespace HTMLNames;
47
48namespace {
49
50// This is a direct transcription of step 4 from:
51// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#fragment-case
52HTMLTokenizer::State tokenizerStateForContextElement(Element* contextElement, bool reportErrors)
53{
54    if (!contextElement)
55        return HTMLTokenizer::DataState;
56
57    const QualifiedName& contextTag = contextElement->tagQName();
58
59    if (contextTag.matches(titleTag) || contextTag.matches(textareaTag))
60        return HTMLTokenizer::RCDATAState;
61    if (contextTag.matches(styleTag)
62        || contextTag.matches(xmpTag)
63        || contextTag.matches(iframeTag)
64        || (contextTag.matches(noembedTag) && HTMLTreeBuilder::pluginsEnabled(contextElement->document()->frame()))
65        || (contextTag.matches(noscriptTag) && HTMLTreeBuilder::scriptEnabled(contextElement->document()->frame()))
66        || contextTag.matches(noframesTag))
67        return reportErrors ? HTMLTokenizer::RAWTEXTState : HTMLTokenizer::PLAINTEXTState;
68    if (contextTag.matches(scriptTag))
69        return reportErrors ? HTMLTokenizer::ScriptDataState : HTMLTokenizer::PLAINTEXTState;
70    if (contextTag.matches(plaintextTag))
71        return HTMLTokenizer::PLAINTEXTState;
72    return HTMLTokenizer::DataState;
73}
74
75} // namespace
76
77HTMLDocumentParser::HTMLDocumentParser(HTMLDocument* document, bool reportErrors)
78    : ScriptableDocumentParser(document)
79    , m_tokenizer(HTMLTokenizer::create(usePreHTML5ParserQuirks(document)))
80    , m_scriptRunner(HTMLScriptRunner::create(document, this))
81    , m_treeBuilder(HTMLTreeBuilder::create(this, document, reportErrors, usePreHTML5ParserQuirks(document)))
82    , m_parserScheduler(HTMLParserScheduler::create(this))
83    , m_xssFilter(this)
84    , m_endWasDelayed(false)
85    , m_pumpSessionNestingLevel(0)
86{
87}
88
89// FIXME: Member variables should be grouped into self-initializing structs to
90// minimize code duplication between these constructors.
91HTMLDocumentParser::HTMLDocumentParser(DocumentFragment* fragment, Element* contextElement, FragmentScriptingPermission scriptingPermission)
92    : ScriptableDocumentParser(fragment->document())
93    , m_tokenizer(HTMLTokenizer::create(usePreHTML5ParserQuirks(fragment->document())))
94    , m_treeBuilder(HTMLTreeBuilder::create(this, fragment, contextElement, scriptingPermission, usePreHTML5ParserQuirks(fragment->document())))
95    , m_xssFilter(this)
96    , m_endWasDelayed(false)
97    , m_pumpSessionNestingLevel(0)
98{
99    bool reportErrors = false; // For now document fragment parsing never reports errors.
100    m_tokenizer->setState(tokenizerStateForContextElement(contextElement, reportErrors));
101}
102
103HTMLDocumentParser::~HTMLDocumentParser()
104{
105    ASSERT(!m_parserScheduler);
106    ASSERT(!m_pumpSessionNestingLevel);
107    ASSERT(!m_preloadScanner);
108}
109
110void HTMLDocumentParser::detach()
111{
112    DocumentParser::detach();
113    if (m_scriptRunner)
114        m_scriptRunner->detach();
115    m_treeBuilder->detach();
116    // FIXME: It seems wrong that we would have a preload scanner here.
117    // Yet during fast/dom/HTMLScriptElement/script-load-events.html we do.
118    m_preloadScanner.clear();
119    m_parserScheduler.clear(); // Deleting the scheduler will clear any timers.
120}
121
122void HTMLDocumentParser::stopParsing()
123{
124    DocumentParser::stopParsing();
125    m_parserScheduler.clear(); // Deleting the scheduler will clear any timers.
126}
127
128// This kicks off "Once the user agent stops parsing" as described by:
129// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end
130void HTMLDocumentParser::prepareToStopParsing()
131{
132    ASSERT(!hasInsertionPoint());
133
134    // pumpTokenizer can cause this parser to be detached from the Document,
135    // but we need to ensure it isn't deleted yet.
136    RefPtr<HTMLDocumentParser> protect(this);
137
138    // NOTE: This pump should only ever emit buffered character tokens,
139    // so ForceSynchronous vs. AllowYield should be meaningless.
140    pumpTokenizerIfPossible(ForceSynchronous);
141
142    if (isStopped())
143        return;
144
145    DocumentParser::prepareToStopParsing();
146
147    // We will not have a scriptRunner when parsing a DocumentFragment.
148    if (m_scriptRunner)
149        document()->setReadyState(Document::Interactive);
150
151    attemptToRunDeferredScriptsAndEnd();
152}
153
154bool HTMLDocumentParser::isParsingFragment() const
155{
156    return m_treeBuilder->isParsingFragment();
157}
158
159bool HTMLDocumentParser::processingData() const
160{
161    return isScheduledForResume() || inPumpSession();
162}
163
164void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode)
165{
166    if (isStopped() || m_treeBuilder->isPaused())
167        return;
168
169    // Once a resume is scheduled, HTMLParserScheduler controls when we next pump.
170    if (isScheduledForResume()) {
171        ASSERT(mode == AllowYield);
172        return;
173    }
174
175    pumpTokenizer(mode);
176}
177
178bool HTMLDocumentParser::isScheduledForResume() const
179{
180    return m_parserScheduler && m_parserScheduler->isScheduledForResume();
181}
182
183// Used by HTMLParserScheduler
184void HTMLDocumentParser::resumeParsingAfterYield()
185{
186    // pumpTokenizer can cause this parser to be detached from the Document,
187    // but we need to ensure it isn't deleted yet.
188    RefPtr<HTMLDocumentParser> protect(this);
189
190    // We should never be here unless we can pump immediately.  Call pumpTokenizer()
191    // directly so that ASSERTS will fire if we're wrong.
192    pumpTokenizer(AllowYield);
193    endIfDelayed();
194}
195
196bool HTMLDocumentParser::runScriptsForPausedTreeBuilder()
197{
198    ASSERT(m_treeBuilder->isPaused());
199
200    TextPosition1 scriptStartPosition = TextPosition1::belowRangePosition();
201    RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition);
202    // We will not have a scriptRunner when parsing a DocumentFragment.
203    if (!m_scriptRunner)
204        return true;
205    return m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
206}
207
208bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& session)
209{
210    if (isStopped())
211        return false;
212
213    // The parser will pause itself when waiting on a script to load or run.
214    if (m_treeBuilder->isPaused()) {
215        if (mode == AllowYield)
216            m_parserScheduler->checkForYieldBeforeScript(session);
217
218        // If we don't run the script, we cannot allow the next token to be taken.
219        if (session.needsYield)
220            return false;
221
222        // If we're paused waiting for a script, we try to execute scripts before continuing.
223        bool shouldContinueParsing = runScriptsForPausedTreeBuilder();
224        m_treeBuilder->setPaused(!shouldContinueParsing);
225        if (!shouldContinueParsing || isStopped())
226            return false;
227    }
228
229    // FIXME: It's wrong for the HTMLDocumentParser to reach back to the
230    //        Frame, but this approach is how the old parser handled
231    //        stopping when the page assigns window.location.  What really
232    //        should happen is that assigning window.location causes the
233    //        parser to stop parsing cleanly.  The problem is we're not
234    //        perpared to do that at every point where we run JavaScript.
235    if (!isParsingFragment()
236        && document()->frame() && document()->frame()->navigationScheduler()->locationChangePending())
237        return false;
238
239    if (mode == AllowYield)
240        m_parserScheduler->checkForYieldBeforeToken(session);
241
242    return true;
243}
244
245void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
246{
247    ASSERT(!isStopped());
248    ASSERT(!isScheduledForResume());
249    // ASSERT that this object is both attached to the Document and protected.
250    ASSERT(refCount() >= 2);
251
252    PumpSession session(m_pumpSessionNestingLevel);
253
254    // We tell the InspectorInstrumentation about every pump, even if we
255    // end up pumping nothing.  It can filter out empty pumps itself.
256    // FIXME: m_input.current().length() is only accurate if we
257    // end up parsing the whole buffer in this pump.  We should pass how
258    // much we parsed as part of didWriteHTML instead of willWriteHTML.
259    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), m_input.current().length(), m_tokenizer->lineNumber());
260
261    while (canTakeNextToken(mode, session) && !session.needsYield) {
262        if (!isParsingFragment())
263            m_sourceTracker.start(m_input, m_token);
264
265        if (!m_tokenizer->nextToken(m_input.current(), m_token))
266            break;
267
268        if (!isParsingFragment()) {
269            m_sourceTracker.end(m_input, m_token);
270
271            // We do not XSS filter innerHTML, which means we (intentionally) fail
272            // http/tests/security/xssAuditor/dom-write-innerHTML.html
273            m_xssFilter.filterToken(m_token);
274        }
275
276        m_treeBuilder->constructTreeFromToken(m_token);
277        ASSERT(m_token.isUninitialized());
278    }
279
280    // Ensure we haven't been totally deref'ed after pumping. Any caller of this
281    // function should be holding a RefPtr to this to ensure we weren't deleted.
282    ASSERT(refCount() >= 1);
283
284    if (isStopped())
285        return;
286
287    if (session.needsYield)
288        m_parserScheduler->scheduleForResume();
289
290    if (isWaitingForScripts()) {
291        ASSERT(m_tokenizer->state() == HTMLTokenizer::DataState);
292        if (!m_preloadScanner) {
293            m_preloadScanner.set(new HTMLPreloadScanner(document()));
294            m_preloadScanner->appendToEnd(m_input.current());
295        }
296        m_preloadScanner->scan();
297    }
298
299    InspectorInstrumentation::didWriteHTML(cookie, m_tokenizer->lineNumber());
300}
301
302bool HTMLDocumentParser::hasInsertionPoint()
303{
304    // FIXME: The wasCreatedByScript() branch here might not be fully correct.
305    //        Our model of the EOF character differs slightly from the one in
306    //        the spec because our treatment is uniform between network-sourced
307    //        and script-sourced input streams whereas the spec treats them
308    //        differently.
309    return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile());
310}
311
312void HTMLDocumentParser::insert(const SegmentedString& source)
313{
314    if (isStopped())
315        return;
316
317    // pumpTokenizer can cause this parser to be detached from the Document,
318    // but we need to ensure it isn't deleted yet.
319    RefPtr<HTMLDocumentParser> protect(this);
320
321    SegmentedString excludedLineNumberSource(source);
322    excludedLineNumberSource.setExcludeLineNumbers();
323    m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource);
324    pumpTokenizerIfPossible(ForceSynchronous);
325
326    if (isWaitingForScripts()) {
327        // Check the document.write() output with a separate preload scanner as
328        // the main scanner can't deal with insertions.
329        HTMLPreloadScanner preloadScanner(document());
330        preloadScanner.appendToEnd(source);
331        preloadScanner.scan();
332    }
333
334    endIfDelayed();
335}
336
337void HTMLDocumentParser::append(const SegmentedString& source)
338{
339    if (isStopped())
340        return;
341
342    // pumpTokenizer can cause this parser to be detached from the Document,
343    // but we need to ensure it isn't deleted yet.
344    RefPtr<HTMLDocumentParser> protect(this);
345
346    if (m_preloadScanner) {
347        if (m_input.current().isEmpty() && !isWaitingForScripts()) {
348            // We have parsed until the end of the current input and so are now moving ahead of the preload scanner.
349            // Clear the scanner so we know to scan starting from the current input point if we block again.
350            m_preloadScanner.clear();
351        } else {
352            m_preloadScanner->appendToEnd(source);
353            if (isWaitingForScripts())
354                m_preloadScanner->scan();
355        }
356    }
357
358    m_input.appendToEnd(source);
359
360    if (inPumpSession()) {
361        // We've gotten data off the network in a nested write.
362        // We don't want to consume any more of the input stream now.  Do
363        // not worry.  We'll consume this data in a less-nested write().
364        return;
365    }
366
367    pumpTokenizerIfPossible(AllowYield);
368
369    endIfDelayed();
370}
371
372void HTMLDocumentParser::end()
373{
374    ASSERT(!isDetached());
375    ASSERT(!isScheduledForResume());
376
377    // Informs the the rest of WebCore that parsing is really finished (and deletes this).
378    m_treeBuilder->finished();
379}
380
381void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()
382{
383    ASSERT(isStopping());
384    ASSERT(!hasInsertionPoint());
385    if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing())
386        return;
387    end();
388}
389
390void HTMLDocumentParser::attemptToEnd()
391{
392    // finish() indicates we will not receive any more data. If we are waiting on
393    // an external script to load, we can't finish parsing quite yet.
394
395    if (shouldDelayEnd()) {
396        m_endWasDelayed = true;
397        return;
398    }
399    prepareToStopParsing();
400}
401
402void HTMLDocumentParser::endIfDelayed()
403{
404    // If we've already been detached, don't bother ending.
405    if (isDetached())
406        return;
407
408    if (!m_endWasDelayed || shouldDelayEnd())
409        return;
410
411    m_endWasDelayed = false;
412    prepareToStopParsing();
413}
414
415void HTMLDocumentParser::finish()
416{
417    // FIXME: We should ASSERT(!m_parserStopped) here, since it does not
418    // makes sense to call any methods on DocumentParser once it's been stopped.
419    // However, FrameLoader::stop calls Document::finishParsing unconditionally
420    // which in turn calls m_parser->finish().
421
422    // We're not going to get any more data off the network, so we tell the
423    // input stream we've reached the end of file.  finish() can be called more
424    // than once, if the first time does not call end().
425    if (!m_input.haveSeenEndOfFile())
426        m_input.markEndOfFile();
427    attemptToEnd();
428}
429
430bool HTMLDocumentParser::finishWasCalled()
431{
432    return m_input.haveSeenEndOfFile();
433}
434
435// This function is virtual and just for the DocumentParser interface.
436bool HTMLDocumentParser::isExecutingScript() const
437{
438    return inScriptExecution();
439}
440
441// This function is non-virtual and used throughout the implementation.
442bool HTMLDocumentParser::inScriptExecution() const
443{
444    if (!m_scriptRunner)
445        return false;
446    return m_scriptRunner->isExecutingScript();
447}
448
449String HTMLDocumentParser::sourceForToken(const HTMLToken& token)
450{
451    return m_sourceTracker.sourceForToken(token);
452}
453
454int HTMLDocumentParser::lineNumber() const
455{
456    return m_tokenizer->lineNumber();
457}
458
459TextPosition0 HTMLDocumentParser::textPosition() const
460{
461    const SegmentedString& currentString = m_input.current();
462    WTF::ZeroBasedNumber line = currentString.currentLine();
463    WTF::ZeroBasedNumber column = currentString.currentColumn();
464    ASSERT(m_tokenizer->lineNumber() == line.zeroBasedInt());
465
466    return TextPosition0(line, column);
467}
468
469bool HTMLDocumentParser::isWaitingForScripts() const
470{
471    return m_treeBuilder->isPaused();
472}
473
474void HTMLDocumentParser::resumeParsingAfterScriptExecution()
475{
476    ASSERT(!inScriptExecution());
477    ASSERT(!m_treeBuilder->isPaused());
478
479    pumpTokenizerIfPossible(AllowYield);
480    endIfDelayed();
481}
482
483void HTMLDocumentParser::watchForLoad(CachedResource* cachedScript)
484{
485    ASSERT(!cachedScript->isLoaded());
486    // addClient would call notifyFinished if the load were complete.
487    // Callers do not expect to be re-entered from this call, so they should
488    // not an already-loaded CachedResource.
489    cachedScript->addClient(this);
490}
491
492void HTMLDocumentParser::stopWatchingForLoad(CachedResource* cachedScript)
493{
494    cachedScript->removeClient(this);
495}
496
497void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan()
498{
499    ASSERT(m_preloadScanner);
500    m_preloadScanner->appendToEnd(m_input.current());
501    m_preloadScanner->scan();
502}
503
504void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
505{
506    // pumpTokenizer can cause this parser to be detached from the Document,
507    // but we need to ensure it isn't deleted yet.
508    RefPtr<HTMLDocumentParser> protect(this);
509
510    ASSERT(m_scriptRunner);
511    ASSERT(!inScriptExecution());
512    if (isStopping()) {
513        attemptToRunDeferredScriptsAndEnd();
514        return;
515    }
516
517    ASSERT(m_treeBuilder->isPaused());
518    // Note: We only ever wait on one script at a time, so we always know this
519    // is the one we were waiting on and can un-pause the tree builder.
520    m_treeBuilder->setPaused(false);
521    bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForLoad(cachedResource);
522    m_treeBuilder->setPaused(!shouldContinueParsing);
523    if (shouldContinueParsing)
524        resumeParsingAfterScriptExecution();
525}
526
527void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
528{
529    // Document only calls this when the Document owns the DocumentParser
530    // so this will not be called in the DocumentFragment case.
531    ASSERT(m_scriptRunner);
532    // Ignore calls unless we have a script blocking the parser waiting on a
533    // stylesheet load.  Otherwise we are currently parsing and this
534    // is a re-entrant call from encountering a </ style> tag.
535    if (!m_scriptRunner->hasScriptsWaitingForStylesheets())
536        return;
537
538    // pumpTokenizer can cause this parser to be detached from the Document,
539    // but we need to ensure it isn't deleted yet.
540    RefPtr<HTMLDocumentParser> protect(this);
541
542    ASSERT(!m_scriptRunner->isExecutingScript());
543    ASSERT(m_treeBuilder->isPaused());
544    // Note: We only ever wait on one script at a time, so we always know this
545    // is the one we were waiting on and can un-pause the tree builder.
546    m_treeBuilder->setPaused(false);
547    bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForStylesheets();
548    m_treeBuilder->setPaused(!shouldContinueParsing);
549    if (shouldContinueParsing)
550        resumeParsingAfterScriptExecution();
551}
552
553ScriptController* HTMLDocumentParser::script() const
554{
555    return document()->frame() ? document()->frame()->script() : 0;
556}
557
558void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment* fragment, Element* contextElement, FragmentScriptingPermission scriptingPermission)
559{
560    RefPtr<HTMLDocumentParser> parser = HTMLDocumentParser::create(fragment, contextElement, scriptingPermission);
561    parser->insert(source); // Use insert() so that the parser will not yield.
562    parser->finish();
563    ASSERT(!parser->processingData()); // Make sure we're done. <rdar://problem/3963151>
564    parser->detach(); // Allows ~DocumentParser to assert it was detached before destruction.
565}
566
567bool HTMLDocumentParser::usePreHTML5ParserQuirks(Document* document)
568{
569    ASSERT(document);
570    return document->settings() && document->settings()->usePreHTML5ParserQuirks();
571}
572
573void HTMLDocumentParser::suspendScheduledTasks()
574{
575    if (m_parserScheduler)
576        m_parserScheduler->suspend();
577}
578
579void HTMLDocumentParser::resumeScheduledTasks()
580{
581    if (m_parserScheduler)
582        m_parserScheduler->resume();
583}
584
585}
586