2010-06-26 Tony Gentilcore <tonyg@chromium.org>
[WebKit.git] / WebCore / html / HTMLDocumentParser.cpp
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 "DocumentFragment.h"
30 #include "Element.h"
31 #include "Frame.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>
40
41 #if ENABLE(INSPECTOR)
42 #include "InspectorTimelineAgent.h"
43 #endif
44
45 namespace WebCore {
46
47 namespace {
48
49 class NestingLevelIncrementer : public Noncopyable {
50 public:
51     explicit NestingLevelIncrementer(int& counter)
52         : m_counter(&counter)
53     {
54         ++(*m_counter);
55     }
56
57     ~NestingLevelIncrementer()
58     {
59         --(*m_counter);
60     }
61
62 private:
63     int* m_counter;
64 };
65
66 } // namespace
67
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)
76 {
77     begin();
78 }
79
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)
88 {
89     begin();
90 }
91
92 HTMLDocumentParser::~HTMLDocumentParser()
93 {
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.
98 }
99
100 void HTMLDocumentParser::begin()
101 {
102     // FIXME: Should we reset the tokenizer?
103 }
104
105 void HTMLDocumentParser::stopParsing()
106 {
107     DocumentParser::stopParsing();
108     m_parserScheduler.clear(); // Deleting the scheduler will clear any timers.
109 }
110
111 bool HTMLDocumentParser::processingData() const
112 {
113     return isScheduledForResume() || inWrite();
114 }
115
116 void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode)
117 {
118     if (m_parserStopped || m_treeBuilder->isPaused())
119         return;
120
121     // Once a resume is scheduled, HTMLParserScheduler controls when we next pump.
122     if (isScheduledForResume()) {
123         ASSERT(mode == AllowYield);
124         return;
125     }
126
127     pumpTokenizer(mode);
128 }
129
130 bool HTMLDocumentParser::isScheduledForResume() const
131 {
132     return m_parserScheduler && m_parserScheduler->isScheduledForResume();
133 }
134
135 // Used by HTMLParserScheduler
136 void HTMLDocumentParser::resumeParsingAfterYield()
137 {
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);
141 }
142
143 bool HTMLDocumentParser::runScriptsForPausedTreeBuilder()
144 {
145     ASSERT(m_treeBuilder->isPaused());
146
147     int scriptStartLine = 0;
148     RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartLine);
149     // We will not have a scriptRunner when parsing a DocumentFragment.
150     if (!m_scriptRunner)
151         return true;
152     return m_scriptRunner->execute(scriptElement.release(), scriptStartLine);
153 }
154
155 void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
156 {
157     ASSERT(!m_parserStopped);
158     ASSERT(!m_treeBuilder->isPaused());
159     ASSERT(!isScheduledForResume());
160
161     // We tell the InspectorTimelineAgent about every pump, even if we
162     // end up pumping nothing.  It can filter out empty pumps itself.
163     willPumpLexer();
164
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))
169             break;
170
171         m_treeBuilder->constructTreeFromToken(m_token);
172         m_token.clear();
173
174         // The parser will pause itself when waiting on a script to load or run.
175         if (!m_treeBuilder->isPaused())
176             continue;
177
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)
182             break;
183     }
184
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());
190         }
191         m_preloadScanner->scan();
192     }
193
194     didPumpLexer();
195 }
196
197 void HTMLDocumentParser::willPumpLexer()
198 {
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());
205 #endif
206 }
207
208 void HTMLDocumentParser::didPumpLexer()
209 {
210 #if ENABLE(INSPECTOR)
211     if (InspectorTimelineAgent* timelineAgent = m_document->inspectorTimelineAgent())
212         timelineAgent->didWriteHTML(m_tokenizer->lineNumber());
213 #endif
214 }
215
216 void HTMLDocumentParser::insert(const SegmentedString& source)
217 {
218     if (m_parserStopped)
219         return;
220
221     NestingLevelIncrementer nestingLevelIncrementer(m_writeNestingLevel);
222
223     SegmentedString excludedLineNumberSource(source);
224     excludedLineNumberSource.setExcludeLineNumbers();
225     m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource);
226     pumpTokenizerIfPossible(ForceSynchronous);
227     endIfDelayed();
228 }
229
230 void HTMLDocumentParser::append(const SegmentedString& source)
231 {
232     if (m_parserStopped)
233         return;
234
235     NestingLevelIncrementer nestingLevelIncrementer(m_writeNestingLevel);
236
237     m_input.appendToEnd(source);
238     if (m_preloadScanner)
239         m_preloadScanner->appendToEnd(source);
240
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().
245         return;
246     }
247
248     pumpTokenizerIfPossible(AllowYield);
249     endIfDelayed();
250 }
251
252 void HTMLDocumentParser::end()
253 {
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);
258
259     // Informs the the rest of WebCore that parsing is really finished (and deletes this).
260     m_treeBuilder->finished();
261 }
262
263 void HTMLDocumentParser::attemptToEnd()
264 {
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.
267
268     if (inWrite() || isWaitingForScripts() || inScriptExecution() || isScheduledForResume()) {
269         m_endWasDelayed = true;
270         return;
271     }
272     end();
273 }
274
275 void HTMLDocumentParser::endIfDelayed()
276 {
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())
280         return;
281
282     m_endWasDelayed = false;
283     end();
284 }
285
286 void HTMLDocumentParser::finish()
287 {
288     // We're not going to get any more data off the network, so we close the
289     // input stream to indicate EOF.
290     m_input.close();
291     attemptToEnd();
292 }
293
294 bool HTMLDocumentParser::finishWasCalled()
295 {
296     return m_input.isClosed();
297 }
298
299 // This function is virtual and just for the DocumentParser interface.
300 bool HTMLDocumentParser::isExecutingScript() const
301 {
302     return inScriptExecution();
303 }
304
305 // This function is non-virtual and used throughout the implementation.
306 bool HTMLDocumentParser::inScriptExecution() const
307 {
308     if (!m_scriptRunner)
309         return false;
310     return m_scriptRunner->inScriptExecution();
311 }
312
313 int HTMLDocumentParser::lineNumber() const
314 {
315     return m_tokenizer->lineNumber();
316 }
317
318 int HTMLDocumentParser::columnNumber() const
319 {
320     return m_tokenizer->columnNumber();
321 }
322
323 LegacyHTMLTreeBuilder* HTMLDocumentParser::htmlTreeBuilder() const
324 {
325     return m_treeBuilder->legacyTreeBuilder();
326 }
327
328 bool HTMLDocumentParser::isWaitingForScripts() const
329 {
330     return m_treeBuilder->isPaused();
331 }
332
333 void HTMLDocumentParser::resumeParsingAfterScriptExecution()
334 {
335     ASSERT(!inScriptExecution());
336     ASSERT(!m_treeBuilder->isPaused());
337
338     pumpTokenizerIfPossible(AllowYield);
339
340     // The document already finished parsing we were just waiting on scripts when finished() was called.
341     endIfDelayed();
342 }
343
344 void HTMLDocumentParser::watchForLoad(CachedResource* cachedScript)
345 {
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);
351 }
352
353 void HTMLDocumentParser::stopWatchingForLoad(CachedResource* cachedScript)
354 {
355     cachedScript->removeClient(this);
356 }
357
358 bool HTMLDocumentParser::shouldLoadExternalScriptFromSrc(const AtomicString& srcValue)
359 {
360     if (!m_XSSAuditor)
361         return true;
362     return m_XSSAuditor->canLoadExternalScriptFromSrc(srcValue);
363 }
364
365 void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
366 {
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();
377 }
378
379 void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
380 {
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())
388         return;
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();
398 }
399
400 ScriptController* HTMLDocumentParser::script() const
401 {
402     return m_document->frame() ? m_document->frame()->script() : 0;
403 }
404
405 void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment* fragment, FragmentScriptingPermission scriptingPermission)
406 {
407     HTMLDocumentParser parser(fragment, scriptingPermission);
408     parser.insert(source); // Use insert() so that the parser will not yield.
409     parser.finish();
410     ASSERT(!parser.processingData()); // Make sure we're done. <rdar://problem/3963151>
411 }
412
413 }