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 "core/html/parser/HTMLScriptRunner.h"
29 #include "bindings/core/v8/ScriptSourceCode.h"
30 #include "core/dom/Element.h"
31 #include "core/events/Event.h"
32 #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h"
33 #include "core/dom/Microtask.h"
34 #include "core/dom/ScriptLoader.h"
35 #include "core/fetch/ScriptResource.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/html/parser/HTMLInputStream.h"
38 #include "core/html/parser/HTMLScriptRunnerHost.h"
39 #include "core/html/parser/NestingLevelIncrementer.h"
40 #include "platform/NotImplemented.h"
44 using namespace HTMLNames;
46 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
47 : m_document(document)
49 , m_scriptNestingLevel(0)
50 , m_hasScriptsWaitingForResources(false)
55 HTMLScriptRunner::~HTMLScriptRunner()
58 // If the document is destructed without having explicitly
59 // detached the parser (and this script runner object), perform
60 // detach steps now. This will happen if the Document, the parser
61 // and this script runner object are swept out in the same GC.
64 // Verify that detach() has been called.
69 void HTMLScriptRunner::detach()
74 if (m_parserBlockingScript.resource() && m_parserBlockingScript.watchingForLoad())
75 stopWatchingForLoad(m_parserBlockingScript);
77 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
78 PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
79 if (pendingScript.resource() && pendingScript.watchingForLoad())
80 stopWatchingForLoad(pendingScript);
85 static KURL documentURLForScriptExecution(Document* document)
90 if (!document->frame()) {
91 if (document->importsController())
92 return document->url();
96 // Use the URL of the currently active document for this frame.
97 return document->frame()->document()->url();
100 inline PassRefPtrWillBeRawPtr<Event> createScriptLoadEvent()
102 return Event::create(EventTypeNames::load);
105 ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
107 if (script.resource()) {
108 errorOccurred = script.resource()->errorOccurred();
109 ASSERT(script.resource()->isLoaded());
110 return ScriptSourceCode(script.resource());
112 errorOccurred = false;
113 return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
116 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
118 m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
119 if (m_hasScriptsWaitingForResources)
121 if (script.resource() && !script.resource()->isLoaded())
126 void HTMLScriptRunner::executeParsingBlockingScript()
129 ASSERT(!isExecutingScript());
130 ASSERT(m_document->isScriptExecutionReady());
131 ASSERT(isPendingScriptReady(m_parserBlockingScript));
133 InsertionPointRecord insertionPointRecord(m_host->inputStream());
134 executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScriptBlockingParser);
137 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScriptType pendingScriptType)
139 bool errorOccurred = false;
140 ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
142 // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
143 if (pendingScript.resource() && pendingScript.watchingForLoad())
144 stopWatchingForLoad(pendingScript);
146 if (!isExecutingScript()) {
147 Microtask::performCheckpoint();
148 if (pendingScriptType == PendingScriptBlockingParser) {
149 m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
150 // The parser cannot be unblocked as a microtask requested another resource
151 if (m_hasScriptsWaitingForResources)
156 // Clear the pending script before possible rentrancy from executeScript()
157 RefPtrWillBeRawPtr<Element> element = pendingScript.releaseElementAndClear();
158 if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element.get())) {
159 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
160 IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
162 scriptLoader->dispatchErrorEvent();
164 ASSERT(isExecutingScript());
165 scriptLoader->executeScript(sourceCode);
166 element->dispatchEvent(createScriptLoadEvent());
169 ASSERT(!isExecutingScript());
172 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
174 ASSERT(!pendingScript.watchingForLoad());
175 ASSERT(!pendingScript.resource()->isLoaded());
176 // addClient() will call notifyFinished() if the load is complete.
177 // Callers do not expect to be re-entered from this call, so they
178 // should not become a client of an already-loaded Resource.
179 pendingScript.resource()->addClient(this);
180 pendingScript.setWatchingForLoad(true);
183 void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
185 ASSERT(pendingScript.watchingForLoad());
186 pendingScript.resource()->removeClient(this);
187 pendingScript.setWatchingForLoad(false);
190 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
192 m_host->notifyScriptLoaded(cachedResource);
195 // Implements the steps for 'An end tag whose tag name is "script"'
196 // http://whatwg.org/html#scriptEndTag
197 // Script handling lives outside the tree builder to keep each class simple.
198 void HTMLScriptRunner::execute(PassRefPtrWillBeRawPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
200 ASSERT(scriptElement);
201 // FIXME: If scripting is disabled, always just return.
203 bool hadPreloadScanner = m_host->hasPreloadScanner();
205 // Try to execute the script given to us.
206 runScript(scriptElement.get(), scriptStartPosition);
208 if (hasParserBlockingScript()) {
209 if (isExecutingScript())
210 return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
211 // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
212 if (!hadPreloadScanner && m_host->hasPreloadScanner())
213 m_host->appendCurrentInputStreamToPreloadScannerAndScan();
214 executeParsingBlockingScripts();
218 bool HTMLScriptRunner::hasParserBlockingScript() const
220 return !!m_parserBlockingScript.element();
223 void HTMLScriptRunner::executeParsingBlockingScripts()
225 while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
226 executeParsingBlockingScript();
229 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
231 ASSERT(!isExecutingScript());
232 ASSERT(hasParserBlockingScript());
233 ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
234 ASSERT(m_parserBlockingScript.resource()->isLoaded());
235 executeParsingBlockingScripts();
238 void HTMLScriptRunner::executeScriptsWaitingForResources()
241 // Callers should check hasScriptsWaitingForResources() before calling
242 // to prevent parser or script re-entry during </style> parsing.
243 ASSERT(hasScriptsWaitingForResources());
244 ASSERT(!isExecutingScript());
245 ASSERT(m_document->isScriptExecutionReady());
246 executeParsingBlockingScripts();
249 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
251 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
252 ASSERT(!isExecutingScript());
253 ASSERT(!hasParserBlockingScript());
254 ASSERT(m_scriptsToExecuteAfterParsing.first().resource());
255 if (!m_scriptsToExecuteAfterParsing.first().resource()->isLoaded()) {
256 watchForLoad(m_scriptsToExecuteAfterParsing.first());
259 PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
260 executePendingScriptAndDispatchEvent(first, PendingScriptDeferred);
261 // FIXME: What is this m_document check for?
268 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
270 if (!requestPendingScript(m_parserBlockingScript, element))
273 ASSERT(m_parserBlockingScript.resource());
275 // We only care about a load callback if resource is not already
276 // in the cache. Callers will attempt to run the m_parserBlockingScript
277 // if possible before returning control to the parser.
278 if (!m_parserBlockingScript.resource()->isLoaded())
279 watchForLoad(m_parserBlockingScript);
282 void HTMLScriptRunner::requestDeferredScript(Element* element)
284 PendingScript pendingScript;
285 if (!requestPendingScript(pendingScript, element))
288 ASSERT(pendingScript.resource());
289 m_scriptsToExecuteAfterParsing.append(pendingScript);
292 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
294 ASSERT(!pendingScript.element());
295 pendingScript.setElement(script);
296 // This should correctly return 0 for empty or invalid srcValues.
297 ScriptResource* resource = toScriptLoaderIfPossible(script)->resource().get();
299 notImplemented(); // Dispatch error event.
302 pendingScript.setScriptResource(resource);
306 // Implements the initial steps for 'An end tag whose tag name is "script"'
307 // http://whatwg.org/html#scriptEndTag
308 void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
311 ASSERT(!hasParserBlockingScript());
313 ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
315 // This contains both and ASSERTION and a null check since we should not
316 // be getting into the case of a null script element, but seem to be from
317 // time to time. The assertion is left in to help find those cases and
318 // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
319 ASSERT(scriptLoader);
323 ASSERT(scriptLoader->isParserInserted());
325 if (!isExecutingScript())
326 Microtask::performCheckpoint();
328 InsertionPointRecord insertionPointRecord(m_host->inputStream());
329 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
331 scriptLoader->prepareScript(scriptStartPosition);
333 if (!scriptLoader->willBeParserExecuted())
336 if (scriptLoader->willExecuteWhenDocumentFinishedParsing()) {
337 requestDeferredScript(script);
338 } else if (scriptLoader->readyToBeParserExecuted()) {
339 if (m_scriptNestingLevel == 1) {
340 m_parserBlockingScript.setElement(script);
341 m_parserBlockingScript.setStartingPosition(scriptStartPosition);
343 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
344 scriptLoader->executeScript(sourceCode);
347 requestParsingBlockingScript(script);
352 void HTMLScriptRunner::trace(Visitor* visitor)
354 visitor->trace(m_document);
355 visitor->trace(m_host);
356 visitor->trace(m_parserBlockingScript);
357 visitor->trace(m_scriptsToExecuteAfterParsing);