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