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 "HTMLScriptRunner.h"
29 #include "Attribute.h"
30 #include "CachedScript.h"
31 #include "CachedResourceLoader.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"
46 using namespace HTMLNames;
48 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
49 : m_document(document)
51 , m_scriptNestingLevel(0)
52 , m_hasScriptsWaitingForStylesheets(false)
57 HTMLScriptRunner::~HTMLScriptRunner()
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);
63 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
64 PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
65 if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
66 stopWatchingForLoad(pendingScript);
70 void HTMLScriptRunner::detach()
75 static KURL documentURLForScriptExecution(Document* document)
77 if (!document || !document->frame())
80 // Use the URL of the currently active document for this frame.
81 return document->frame()->document()->url();
84 inline PassRefPtr<Event> createScriptLoadEvent()
86 return Event::create(eventNames().loadEvent, false, false);
89 ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
91 if (script.cachedScript()) {
92 errorOccurred = script.cachedScript()->errorOccurred();
93 ASSERT(script.cachedScript()->isLoaded());
94 return ScriptSourceCode(script.cachedScript());
96 errorOccurred = false;
97 return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
100 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
102 m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
103 if (m_hasScriptsWaitingForStylesheets)
105 if (script.cachedScript() && !script.cachedScript()->isLoaded())
110 void HTMLScriptRunner::executeParsingBlockingScript()
113 ASSERT(!m_scriptNestingLevel);
114 ASSERT(m_document->haveStylesheetsLoaded());
115 ASSERT(isPendingScriptReady(m_parsingBlockingScript));
117 InsertionPointRecord insertionPointRecord(m_host->inputStream());
118 executePendingScriptAndDispatchEvent(m_parsingBlockingScript);
121 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript)
123 bool errorOccurred = false;
124 ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
126 // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
127 if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
128 stopWatchingForLoad(pendingScript);
130 // Clear the pending script before possible rentrancy from executeScript()
131 RefPtr<Element> element = pendingScript.releaseElementAndClear();
132 if (ScriptElement* scriptElement = toScriptElement(element.get())) {
133 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
134 IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
136 scriptElement->dispatchErrorEvent();
138 ASSERT(isExecutingScript());
139 scriptElement->executeScript(sourceCode);
140 element->dispatchEvent(createScriptLoadEvent());
143 ASSERT(!m_scriptNestingLevel);
146 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
148 ASSERT(!pendingScript.watchingForLoad());
149 m_host->watchForLoad(pendingScript.cachedScript());
150 pendingScript.setWatchingForLoad(true);
153 void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
155 ASSERT(pendingScript.watchingForLoad());
156 m_host->stopWatchingForLoad(pendingScript.cachedScript());
157 pendingScript.setWatchingForLoad(false);
160 // This function should match 10.2.5.11 "An end tag whose tag name is 'script'"
161 // Script handling lives outside the tree builder to keep the each class simple.
162 bool HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, const TextPosition1& scriptStartPosition)
164 ASSERT(scriptElement);
165 // FIXME: If scripting is disabled, always just return true;
167 bool hadPreloadScanner = m_host->hasPreloadScanner();
169 // Try to execute the script given to us.
170 runScript(scriptElement.get(), scriptStartPosition);
172 if (haveParsingBlockingScript()) {
173 if (m_scriptNestingLevel)
174 return false; // Block the parser. Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
175 // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
176 if (!hadPreloadScanner && m_host->hasPreloadScanner())
177 m_host->appendCurrentInputStreamToPreloadScannerAndScan();
178 if (!executeParsingBlockingScripts())
179 return false; // We still have a parsing blocking script, block the parser.
181 return true; // Scripts executed as expected, continue parsing.
184 bool HTMLScriptRunner::haveParsingBlockingScript() const
186 return !!m_parsingBlockingScript.element();
189 bool HTMLScriptRunner::executeParsingBlockingScripts()
191 while (haveParsingBlockingScript()) {
192 // We only really need to check once.
193 if (!isPendingScriptReady(m_parsingBlockingScript))
195 executeParsingBlockingScript();
200 bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
202 ASSERT(!m_scriptNestingLevel);
203 ASSERT(haveParsingBlockingScript());
204 ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript);
205 ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded());
206 return executeParsingBlockingScripts();
209 bool HTMLScriptRunner::executeScriptsWaitingForStylesheets()
212 // Callers should check hasScriptsWaitingForStylesheets() before calling
213 // to prevent parser or script re-entry during </style> parsing.
214 ASSERT(hasScriptsWaitingForStylesheets());
215 ASSERT(!m_scriptNestingLevel);
216 ASSERT(m_document->haveStylesheetsLoaded());
217 return executeParsingBlockingScripts();
220 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
222 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
223 ASSERT(!m_scriptNestingLevel);
224 ASSERT(!haveParsingBlockingScript());
225 ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript());
226 if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) {
227 watchForLoad(m_scriptsToExecuteAfterParsing.first());
230 PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
231 executePendingScriptAndDispatchEvent(first);
238 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
240 if (!requestPendingScript(m_parsingBlockingScript, element))
243 ASSERT(m_parsingBlockingScript.cachedScript());
245 // We only care about a load callback if cachedScript is not already
246 // in the cache. Callers will attempt to run the m_parsingBlockingScript
247 // if possible before returning control to the parser.
248 if (!m_parsingBlockingScript.cachedScript()->isLoaded())
249 watchForLoad(m_parsingBlockingScript);
252 void HTMLScriptRunner::requestDeferredScript(Element* element)
254 PendingScript pendingScript;
255 if (!requestPendingScript(pendingScript, element))
258 ASSERT(pendingScript.cachedScript());
259 m_scriptsToExecuteAfterParsing.append(pendingScript);
262 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
264 ASSERT(!pendingScript.element());
265 pendingScript.setElement(script);
266 // This should correctly return 0 for empty or invalid srcValues.
267 CachedScript* cachedScript = toScriptElement(script)->cachedScript().get();
269 notImplemented(); // Dispatch error event.
272 pendingScript.setCachedScript(cachedScript);
276 // This method is meant to match the HTML5 definition of "running a script"
277 // http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
278 void HTMLScriptRunner::runScript(Element* script, const TextPosition1& scriptStartPosition)
281 ASSERT(!haveParsingBlockingScript());
283 InsertionPointRecord insertionPointRecord(m_host->inputStream());
284 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
286 ScriptElement* scriptElement = toScriptElement(script);
288 // This contains both and ASSERTION and a null check since we should not
289 // be getting into the case of a null script element, but seem to be from
290 // time to time. The assertion is left in to help find those cases and
291 // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
292 ASSERT(scriptElement);
296 scriptElement->prepareScript(scriptStartPosition);
298 if (!scriptElement->willBeParserExecuted())
301 if (scriptElement->willExecuteWhenDocumentFinishedParsing())
302 requestDeferredScript(script);
303 else if (scriptElement->readyToBeParserExecuted()) {
304 if (m_scriptNestingLevel == 1) {
305 m_parsingBlockingScript.setElement(script);
306 m_parsingBlockingScript.setStartingPosition(scriptStartPosition);
308 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
309 scriptElement->executeScript(sourceCode);
312 requestParsingBlockingScript(script);