2 * Copyright (C) 2010 Google, Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "HTMLDocumentParser.h"
29 #include "DocumentFragment.h"
32 #include "HTMLParserScheduler.h"
33 #include "HTMLTokenizer.h"
34 #include "HTMLPreloadScanner.h"
35 #include "HTMLScriptRunner.h"
36 #include "HTMLTreeBuilder.h"
37 #include "HTMLDocument.h"
38 #include "XSSAuditor.h"
39 #include <wtf/CurrentTime.h>
42 #include "InspectorTimelineAgent.h"
49 class NestingLevelIncrementer : public Noncopyable {
51 explicit NestingLevelIncrementer(int& counter)
57 ~NestingLevelIncrementer()
68 HTMLDocumentParser::HTMLDocumentParser(HTMLDocument* document, bool reportErrors)
69 : DocumentParser(document)
70 , m_tokenizer(new HTMLTokenizer)
71 , m_scriptRunner(new HTMLScriptRunner(document, this))
72 , m_treeBuilder(new HTMLTreeBuilder(m_tokenizer.get(), document, reportErrors))
73 , m_parserScheduler(new HTMLParserScheduler(this))
74 , m_endWasDelayed(false)
75 , m_writeNestingLevel(0)
80 // FIXME: Member variables should be grouped into self-initializing structs to
81 // minimize code duplication between these constructors.
82 HTMLDocumentParser::HTMLDocumentParser(DocumentFragment* fragment, FragmentScriptingPermission scriptingPermission)
83 : DocumentParser(fragment->document())
84 , m_tokenizer(new HTMLTokenizer)
85 , m_treeBuilder(new HTMLTreeBuilder(m_tokenizer.get(), fragment, scriptingPermission))
86 , m_endWasDelayed(false)
87 , m_writeNestingLevel(0)
92 HTMLDocumentParser::~HTMLDocumentParser()
94 // FIXME: We'd like to ASSERT that normal operation of this class clears
95 // out any delayed actions, but we can't because we're unceremoniously
96 // deleted. If there were a required call to some sort of cancel function,
97 // then we could ASSERT some invariants here.
100 void HTMLDocumentParser::begin()
102 // FIXME: Should we reset the tokenizer?
105 void HTMLDocumentParser::stopParsing()
107 DocumentParser::stopParsing();
108 m_parserScheduler.clear(); // Deleting the scheduler will clear any timers.
111 bool HTMLDocumentParser::processingData() const
113 return isScheduledForResume() || inWrite();
116 void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode)
118 if (m_parserStopped || m_treeBuilder->isPaused())
121 // Once a resume is scheduled, HTMLParserScheduler controls when we next pump.
122 if (isScheduledForResume()) {
123 ASSERT(mode == AllowYield);
130 bool HTMLDocumentParser::isScheduledForResume() const
132 return m_parserScheduler && m_parserScheduler->isScheduledForResume();
135 // Used by HTMLParserScheduler
136 void HTMLDocumentParser::resumeParsingAfterYield()
138 // We should never be here unless we can pump immediately. Call pumpTokenizer()
139 // directly so that ASSERTS will fire if we're wrong.
140 pumpTokenizer(AllowYield);
143 bool HTMLDocumentParser::runScriptsForPausedTreeBuilder()
145 ASSERT(m_treeBuilder->isPaused());
147 int scriptStartLine = 0;
148 RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartLine);
149 // We will not have a scriptRunner when parsing a DocumentFragment.
152 return m_scriptRunner->execute(scriptElement.release(), scriptStartLine);
155 void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
157 ASSERT(!m_parserStopped);
158 ASSERT(!m_treeBuilder->isPaused());
159 ASSERT(!isScheduledForResume());
161 // We tell the InspectorTimelineAgent about every pump, even if we
162 // end up pumping nothing. It can filter out empty pumps itself.
165 HTMLParserScheduler::PumpSession session;
166 // FIXME: This loop body has is now too long and needs cleanup.
167 while (mode == ForceSynchronous || (!m_parserStopped && m_parserScheduler->shouldContinueParsing(session))) {
168 if (!m_tokenizer->nextToken(m_input.current(), m_token))
171 m_treeBuilder->constructTreeFromToken(m_token);
174 // The parser will pause itself when waiting on a script to load or run.
175 if (!m_treeBuilder->isPaused())
178 // If we're paused waiting for a script, we try to execute scripts before continuing.
179 bool shouldContinueParsing = runScriptsForPausedTreeBuilder();
180 m_treeBuilder->setPaused(!shouldContinueParsing);
181 if (!shouldContinueParsing)
185 if (isWaitingForScripts()) {
186 ASSERT(m_tokenizer->state() == HTMLTokenizer::DataState);
187 if (!m_preloadScanner) {
188 m_preloadScanner.set(new HTMLPreloadScanner(m_document));
189 m_preloadScanner->appendToEnd(m_input.current());
191 m_preloadScanner->scan();
197 void HTMLDocumentParser::willPumpLexer()
199 #if ENABLE(INSPECTOR)
200 // FIXME: m_input.current().length() is only accurate if we
201 // end up parsing the whole buffer in this pump. We should pass how
202 // much we parsed as part of didWriteHTML instead of willWriteHTML.
203 if (InspectorTimelineAgent* timelineAgent = m_document->inspectorTimelineAgent())
204 timelineAgent->willWriteHTML(m_input.current().length(), m_tokenizer->lineNumber());
208 void HTMLDocumentParser::didPumpLexer()
210 #if ENABLE(INSPECTOR)
211 if (InspectorTimelineAgent* timelineAgent = m_document->inspectorTimelineAgent())
212 timelineAgent->didWriteHTML(m_tokenizer->lineNumber());
216 void HTMLDocumentParser::insert(const SegmentedString& source)
221 NestingLevelIncrementer nestingLevelIncrementer(m_writeNestingLevel);
223 SegmentedString excludedLineNumberSource(source);
224 excludedLineNumberSource.setExcludeLineNumbers();
225 m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource);
226 pumpTokenizerIfPossible(ForceSynchronous);
230 void HTMLDocumentParser::append(const SegmentedString& source)
235 NestingLevelIncrementer nestingLevelIncrementer(m_writeNestingLevel);
237 m_input.appendToEnd(source);
238 if (m_preloadScanner)
239 m_preloadScanner->appendToEnd(source);
241 if (m_writeNestingLevel > 1) {
242 // We've gotten data off the network in a nested write.
243 // We don't want to consume any more of the input stream now. Do
244 // not worry. We'll consume this data in a less-nested write().
248 pumpTokenizerIfPossible(AllowYield);
252 void HTMLDocumentParser::end()
254 ASSERT(!isScheduledForResume());
255 // NOTE: This pump should only ever emit buffered character tokens,
256 // so ForceSynchronous vs. AllowYield should be meaningless.
257 pumpTokenizerIfPossible(ForceSynchronous);
259 // Informs the the rest of WebCore that parsing is really finished (and deletes this).
260 m_treeBuilder->finished();
263 void HTMLDocumentParser::attemptToEnd()
265 // finish() indicates we will not receive any more data. If we are waiting on
266 // an external script to load, we can't finish parsing quite yet.
268 if (inWrite() || isWaitingForScripts() || inScriptExecution() || isScheduledForResume()) {
269 m_endWasDelayed = true;
275 void HTMLDocumentParser::endIfDelayed()
277 // We don't check inWrite() here since inWrite() will be true if this was
278 // called from write().
279 if (!m_endWasDelayed || isWaitingForScripts() || inScriptExecution() || isScheduledForResume())
282 m_endWasDelayed = false;
286 void HTMLDocumentParser::finish()
288 // We're not going to get any more data off the network, so we close the
289 // input stream to indicate EOF.
294 bool HTMLDocumentParser::finishWasCalled()
296 return m_input.isClosed();
299 // This function is virtual and just for the DocumentParser interface.
300 bool HTMLDocumentParser::isExecutingScript() const
302 return inScriptExecution();
305 // This function is non-virtual and used throughout the implementation.
306 bool HTMLDocumentParser::inScriptExecution() const
310 return m_scriptRunner->inScriptExecution();
313 int HTMLDocumentParser::lineNumber() const
315 return m_tokenizer->lineNumber();
318 int HTMLDocumentParser::columnNumber() const
320 return m_tokenizer->columnNumber();
323 LegacyHTMLTreeBuilder* HTMLDocumentParser::htmlTreeBuilder() const
325 return m_treeBuilder->legacyTreeBuilder();
328 bool HTMLDocumentParser::isWaitingForScripts() const
330 return m_treeBuilder->isPaused();
333 void HTMLDocumentParser::resumeParsingAfterScriptExecution()
335 ASSERT(!inScriptExecution());
336 ASSERT(!m_treeBuilder->isPaused());
338 pumpTokenizerIfPossible(AllowYield);
340 // The document already finished parsing we were just waiting on scripts when finished() was called.
344 void HTMLDocumentParser::watchForLoad(CachedResource* cachedScript)
346 ASSERT(!cachedScript->isLoaded());
347 // addClient would call notifyFinished if the load were complete.
348 // Callers do not expect to be re-entered from this call, so they should
349 // not an already-loaded CachedResource.
350 cachedScript->addClient(this);
353 void HTMLDocumentParser::stopWatchingForLoad(CachedResource* cachedScript)
355 cachedScript->removeClient(this);
358 bool HTMLDocumentParser::shouldLoadExternalScriptFromSrc(const AtomicString& srcValue)
362 return m_XSSAuditor->canLoadExternalScriptFromSrc(srcValue);
365 void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
367 ASSERT(m_scriptRunner);
368 ASSERT(!inScriptExecution());
369 ASSERT(m_treeBuilder->isPaused());
370 // Note: We only ever wait on one script at a time, so we always know this
371 // is the one we were waiting on and can un-pause the tree builder.
372 m_treeBuilder->setPaused(false);
373 bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForLoad(cachedResource);
374 m_treeBuilder->setPaused(!shouldContinueParsing);
375 if (shouldContinueParsing)
376 resumeParsingAfterScriptExecution();
379 void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
381 // Document only calls this when the Document owns the DocumentParser
382 // so this will not be called in the DocumentFragment case.
383 ASSERT(m_scriptRunner);
384 // Ignore calls unless we have a script blocking the parser waiting on a
385 // stylesheet load. Otherwise we are currently parsing and this
386 // is a re-entrant call from encountering a </ style> tag.
387 if (!m_scriptRunner->hasScriptsWaitingForStylesheets())
389 ASSERT(!m_scriptRunner->inScriptExecution());
390 ASSERT(m_treeBuilder->isPaused());
391 // Note: We only ever wait on one script at a time, so we always know this
392 // is the one we were waiting on and can un-pause the tree builder.
393 m_treeBuilder->setPaused(false);
394 bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForStylesheets();
395 m_treeBuilder->setPaused(!shouldContinueParsing);
396 if (shouldContinueParsing)
397 resumeParsingAfterScriptExecution();
400 ScriptController* HTMLDocumentParser::script() const
402 return m_document->frame() ? m_document->frame()->script() : 0;
405 void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment* fragment, FragmentScriptingPermission scriptingPermission)
407 HTMLDocumentParser parser(fragment, scriptingPermission);
408 parser.insert(source); // Use insert() so that the parser will not yield.
410 ASSERT(!parser.processingData()); // Make sure we're done. <rdar://problem/3963151>