• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "HTMLScriptRunner.h"
28 
29 #include "Attribute.h"
30 #include "CachedScript.h"
31 #include "CachedResourceLoader.h"
32 #include "Element.h"
33 #include "Event.h"
34 #include "Frame.h"
35 #include "HTMLInputStream.h"
36 #include "HTMLNames.h"
37 #include "HTMLScriptRunnerHost.h"
38 #include "IgnoreDestructiveWriteCountIncrementer.h"
39 #include "NestingLevelIncrementer.h"
40 #include "NotImplemented.h"
41 #include "ScriptElement.h"
42 #include "ScriptSourceCode.h"
43 
44 namespace WebCore {
45 
46 using namespace HTMLNames;
47 
HTMLScriptRunner(Document * document,HTMLScriptRunnerHost * host)48 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
49     : m_document(document)
50     , m_host(host)
51     , m_scriptNestingLevel(0)
52     , m_hasScriptsWaitingForStylesheets(false)
53 {
54     ASSERT(m_host);
55 }
56 
~HTMLScriptRunner()57 HTMLScriptRunner::~HTMLScriptRunner()
58 {
59     // FIXME: Should we be passed a "done loading/parsing" callback sooner than destruction?
60     if (m_parsingBlockingScript.cachedScript() && m_parsingBlockingScript.watchingForLoad())
61         stopWatchingForLoad(m_parsingBlockingScript);
62 
63     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
64         PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
65         if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
66             stopWatchingForLoad(pendingScript);
67     }
68 }
69 
detach()70 void HTMLScriptRunner::detach()
71 {
72     m_document = 0;
73 }
74 
documentURLForScriptExecution(Document * document)75 static KURL documentURLForScriptExecution(Document* document)
76 {
77     if (!document || !document->frame())
78         return KURL();
79 
80     // Use the URL of the currently active document for this frame.
81     return document->frame()->document()->url();
82 }
83 
createScriptLoadEvent()84 inline PassRefPtr<Event> createScriptLoadEvent()
85 {
86     return Event::create(eventNames().loadEvent, false, false);
87 }
88 
createScriptErrorEvent()89 inline PassRefPtr<Event> createScriptErrorEvent()
90 {
91     return Event::create(eventNames().errorEvent, true, false);
92 }
93 
sourceFromPendingScript(const PendingScript & script,bool & errorOccurred) const94 ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
95 {
96     if (script.cachedScript()) {
97         errorOccurred = script.cachedScript()->errorOccurred();
98         ASSERT(script.cachedScript()->isLoaded());
99         return ScriptSourceCode(script.cachedScript());
100     }
101     errorOccurred = false;
102     return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
103 }
104 
isPendingScriptReady(const PendingScript & script)105 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
106 {
107     m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
108     if (m_hasScriptsWaitingForStylesheets)
109         return false;
110     if (script.cachedScript() && !script.cachedScript()->isLoaded())
111         return false;
112     return true;
113 }
114 
executeParsingBlockingScript()115 void HTMLScriptRunner::executeParsingBlockingScript()
116 {
117     ASSERT(m_document);
118     ASSERT(!m_scriptNestingLevel);
119     ASSERT(m_document->haveStylesheetsLoaded());
120     ASSERT(isPendingScriptReady(m_parsingBlockingScript));
121 
122     InsertionPointRecord insertionPointRecord(m_host->inputStream());
123     executePendingScriptAndDispatchEvent(m_parsingBlockingScript);
124 }
125 
executePendingScriptAndDispatchEvent(PendingScript & pendingScript)126 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript)
127 {
128     bool errorOccurred = false;
129     ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
130 
131     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
132     if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
133         stopWatchingForLoad(pendingScript);
134 
135     // Clear the pending script before possible rentrancy from executeScript()
136     RefPtr<Element> element = pendingScript.releaseElementAndClear();
137     if (ScriptElement* scriptElement = toScriptElement(element.get())) {
138         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
139         IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
140         if (errorOccurred)
141             element->dispatchEvent(createScriptErrorEvent());
142         else {
143             ASSERT(isExecutingScript());
144             scriptElement->executeScript(sourceCode);
145             element->dispatchEvent(createScriptLoadEvent());
146         }
147     }
148     ASSERT(!m_scriptNestingLevel);
149 }
150 
watchForLoad(PendingScript & pendingScript)151 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
152 {
153     ASSERT(!pendingScript.watchingForLoad());
154     m_host->watchForLoad(pendingScript.cachedScript());
155     pendingScript.setWatchingForLoad(true);
156 }
157 
stopWatchingForLoad(PendingScript & pendingScript)158 void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
159 {
160     ASSERT(pendingScript.watchingForLoad());
161     m_host->stopWatchingForLoad(pendingScript.cachedScript());
162     pendingScript.setWatchingForLoad(false);
163 }
164 
165 // This function should match 10.2.5.11 "An end tag whose tag name is 'script'"
166 // Script handling lives outside the tree builder to keep the each class simple.
execute(PassRefPtr<Element> scriptElement,const TextPosition1 & scriptStartPosition)167 bool HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, const TextPosition1& scriptStartPosition)
168 {
169     ASSERT(scriptElement);
170     // FIXME: If scripting is disabled, always just return true;
171 
172     bool hadPreloadScanner = m_host->hasPreloadScanner();
173 
174     // Try to execute the script given to us.
175     runScript(scriptElement.get(), scriptStartPosition);
176 
177     if (haveParsingBlockingScript()) {
178         if (m_scriptNestingLevel)
179             return false; // Block the parser.  Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
180         // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
181         if (!hadPreloadScanner && m_host->hasPreloadScanner())
182             m_host->appendCurrentInputStreamToPreloadScannerAndScan();
183         if (!executeParsingBlockingScripts())
184             return false; // We still have a parsing blocking script, block the parser.
185     }
186     return true; // Scripts executed as expected, continue parsing.
187 }
188 
haveParsingBlockingScript() const189 bool HTMLScriptRunner::haveParsingBlockingScript() const
190 {
191     return !!m_parsingBlockingScript.element();
192 }
193 
executeParsingBlockingScripts()194 bool HTMLScriptRunner::executeParsingBlockingScripts()
195 {
196     while (haveParsingBlockingScript()) {
197         // We only really need to check once.
198         if (!isPendingScriptReady(m_parsingBlockingScript))
199             return false;
200         executeParsingBlockingScript();
201     }
202     return true;
203 }
204 
executeScriptsWaitingForLoad(CachedResource * cachedScript)205 bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
206 {
207     ASSERT(!m_scriptNestingLevel);
208     ASSERT(haveParsingBlockingScript());
209     ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript);
210     ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded());
211     return executeParsingBlockingScripts();
212 }
213 
executeScriptsWaitingForStylesheets()214 bool HTMLScriptRunner::executeScriptsWaitingForStylesheets()
215 {
216     ASSERT(m_document);
217     // Callers should check hasScriptsWaitingForStylesheets() before calling
218     // to prevent parser or script re-entry during </style> parsing.
219     ASSERT(hasScriptsWaitingForStylesheets());
220     ASSERT(!m_scriptNestingLevel);
221     ASSERT(m_document->haveStylesheetsLoaded());
222     return executeParsingBlockingScripts();
223 }
224 
executeScriptsWaitingForParsing()225 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
226 {
227     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
228         ASSERT(!m_scriptNestingLevel);
229         ASSERT(!haveParsingBlockingScript());
230         ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript());
231         if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) {
232             watchForLoad(m_scriptsToExecuteAfterParsing.first());
233             return false;
234         }
235         PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
236         executePendingScriptAndDispatchEvent(first);
237         if (!m_document)
238             return false;
239     }
240     return true;
241 }
242 
requestParsingBlockingScript(Element * element)243 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
244 {
245     if (!requestPendingScript(m_parsingBlockingScript, element))
246         return;
247 
248     ASSERT(m_parsingBlockingScript.cachedScript());
249 
250     // We only care about a load callback if cachedScript is not already
251     // in the cache.  Callers will attempt to run the m_parsingBlockingScript
252     // if possible before returning control to the parser.
253     if (!m_parsingBlockingScript.cachedScript()->isLoaded())
254         watchForLoad(m_parsingBlockingScript);
255 }
256 
requestDeferredScript(Element * element)257 void HTMLScriptRunner::requestDeferredScript(Element* element)
258 {
259     PendingScript pendingScript;
260     if (!requestPendingScript(pendingScript, element))
261         return;
262 
263     ASSERT(pendingScript.cachedScript());
264     m_scriptsToExecuteAfterParsing.append(pendingScript);
265 }
266 
requestPendingScript(PendingScript & pendingScript,Element * script) const267 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
268 {
269     ASSERT(!pendingScript.element());
270     pendingScript.setElement(script);
271     // This should correctly return 0 for empty or invalid srcValues.
272     CachedScript* cachedScript = toScriptElement(script)->cachedScript().get();
273     if (!cachedScript) {
274         notImplemented(); // Dispatch error event.
275         return false;
276     }
277     pendingScript.setCachedScript(cachedScript);
278     return true;
279 }
280 
281 // This method is meant to match the HTML5 definition of "running a script"
282 // http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
runScript(Element * script,const TextPosition1 & scriptStartPosition)283 void HTMLScriptRunner::runScript(Element* script, const TextPosition1& scriptStartPosition)
284 {
285     ASSERT(m_document);
286     ASSERT(!haveParsingBlockingScript());
287     {
288         InsertionPointRecord insertionPointRecord(m_host->inputStream());
289         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
290 
291         ScriptElement* scriptElement = toScriptElement(script);
292         ASSERT(scriptElement);
293 
294         scriptElement->prepareScript(scriptStartPosition);
295 
296         if (!scriptElement->willBeParserExecuted())
297             return;
298 
299         if (scriptElement->willExecuteWhenDocumentFinishedParsing())
300             requestDeferredScript(script);
301         else if (scriptElement->readyToBeParserExecuted()) {
302             if (m_scriptNestingLevel == 1) {
303                 m_parsingBlockingScript.setElement(script);
304                 m_parsingBlockingScript.setStartingPosition(scriptStartPosition);
305             } else {
306                 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
307                 scriptElement->executeScript(sourceCode);
308             }
309         } else
310             requestParsingBlockingScript(script);
311     }
312 }
313 
314 }
315