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"
41 #include "public/platform/Platform.h"
45 using namespace HTMLNames;
47 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
48 : m_document(document)
50 , m_scriptNestingLevel(0)
51 , m_hasScriptsWaitingForResources(false)
52 , m_parserBlockingScriptAlreadyLoaded(false)
57 HTMLScriptRunner::~HTMLScriptRunner()
60 // If the document is destructed without having explicitly
61 // detached the parser (and this script runner object), perform
62 // detach steps now. This will happen if the Document, the parser
63 // and this script runner object are swept out in the same GC.
66 // Verify that detach() has been called.
71 void HTMLScriptRunner::detach()
76 m_parserBlockingScript.stopWatchingForLoad(this);
77 m_parserBlockingScript.releaseElementAndClear();
79 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
80 PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
81 pendingScript.stopWatchingForLoad(this);
82 pendingScript.releaseElementAndClear();
87 static KURL documentURLForScriptExecution(Document* document)
92 if (!document->frame()) {
93 if (document->importsController())
94 return document->url();
98 // Use the URL of the currently active document for this frame.
99 return document->frame()->document()->url();
102 inline PassRefPtrWillBeRawPtr<Event> createScriptLoadEvent()
104 return Event::create(EventTypeNames::load);
107 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
109 m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
110 if (m_hasScriptsWaitingForResources)
112 return script.isReady();
115 void HTMLScriptRunner::executeParsingBlockingScript()
118 ASSERT(!isExecutingScript());
119 ASSERT(m_document->isScriptExecutionReady());
120 ASSERT(isPendingScriptReady(m_parserBlockingScript));
122 InsertionPointRecord insertionPointRecord(m_host->inputStream());
123 executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScript::ParsingBlocking);
126 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScript::Type pendingScriptType)
128 bool errorOccurred = false;
129 double loadFinishTime = pendingScript.resource() && pendingScript.resource()->url().protocolIsInHTTPFamily() ? pendingScript.resource()->loadFinishTime() : 0;
130 ScriptSourceCode sourceCode = pendingScript.getSource(documentURLForScriptExecution(m_document), errorOccurred);
132 // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
133 pendingScript.stopWatchingForLoad(this);
135 if (!isExecutingScript()) {
136 Microtask::performCheckpoint();
137 if (pendingScriptType == PendingScript::ParsingBlocking) {
138 m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
139 // The parser cannot be unblocked as a microtask requested another resource
140 if (m_hasScriptsWaitingForResources)
145 // Clear the pending script before possible rentrancy from executeScript()
146 RefPtrWillBeRawPtr<Element> element = pendingScript.releaseElementAndClear();
147 double compilationFinishTime = 0;
148 if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element.get())) {
149 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
150 IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
152 scriptLoader->dispatchErrorEvent();
154 ASSERT(isExecutingScript());
155 scriptLoader->executeScript(sourceCode, &compilationFinishTime);
156 element->dispatchEvent(createScriptLoadEvent());
159 // The exact value doesn't matter; valid time stamps are much bigger than this value.
160 const double epsilon = 1;
161 if (pendingScriptType == PendingScript::ParsingBlocking && !m_parserBlockingScriptAlreadyLoaded && compilationFinishTime > epsilon && loadFinishTime > epsilon) {
162 blink::Platform::current()->histogramCustomCounts("WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled", (compilationFinishTime - loadFinishTime) * 1000, 0, 10000, 50);
165 ASSERT(!isExecutingScript());
168 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
170 m_host->notifyScriptLoaded(cachedResource);
173 // Implements the steps for 'An end tag whose tag name is "script"'
174 // http://whatwg.org/html#scriptEndTag
175 // Script handling lives outside the tree builder to keep each class simple.
176 void HTMLScriptRunner::execute(PassRefPtrWillBeRawPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
178 ASSERT(scriptElement);
179 // FIXME: If scripting is disabled, always just return.
181 bool hadPreloadScanner = m_host->hasPreloadScanner();
183 // Try to execute the script given to us.
184 runScript(scriptElement.get(), scriptStartPosition);
186 if (hasParserBlockingScript()) {
187 if (isExecutingScript())
188 return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
189 // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
190 if (!hadPreloadScanner && m_host->hasPreloadScanner())
191 m_host->appendCurrentInputStreamToPreloadScannerAndScan();
192 executeParsingBlockingScripts();
196 bool HTMLScriptRunner::hasParserBlockingScript() const
198 return !!m_parserBlockingScript.element();
201 void HTMLScriptRunner::executeParsingBlockingScripts()
203 while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
204 executeParsingBlockingScript();
207 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
209 ASSERT(!isExecutingScript());
210 ASSERT(hasParserBlockingScript());
211 ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
212 ASSERT(m_parserBlockingScript.isReady());
213 executeParsingBlockingScripts();
216 void HTMLScriptRunner::executeScriptsWaitingForResources()
219 // Callers should check hasScriptsWaitingForResources() before calling
220 // to prevent parser or script re-entry during </style> parsing.
221 ASSERT(hasScriptsWaitingForResources());
222 ASSERT(!isExecutingScript());
223 ASSERT(m_document->isScriptExecutionReady());
224 executeParsingBlockingScripts();
227 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
229 while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
230 ASSERT(!isExecutingScript());
231 ASSERT(!hasParserBlockingScript());
232 ASSERT(m_scriptsToExecuteAfterParsing.first().resource());
233 if (!m_scriptsToExecuteAfterParsing.first().isReady()) {
234 m_scriptsToExecuteAfterParsing.first().watchForLoad(this);
237 PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
238 executePendingScriptAndDispatchEvent(first, PendingScript::Deferred);
239 // FIXME: What is this m_document check for?
246 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
248 if (!requestPendingScript(m_parserBlockingScript, element))
251 ASSERT(m_parserBlockingScript.resource());
253 // Exclude already loaded resources (from memory cache) and reloads from the
255 // WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled (done after
256 // the script is compiled).
257 m_parserBlockingScriptAlreadyLoaded = m_parserBlockingScript.resource()->isLoaded() || m_parserBlockingScript.resource()->resourceToRevalidate();
258 blink::Platform::current()->histogramEnumeration("WebCore.Scripts.ParsingBlocking.AlreadyLoaded", m_parserBlockingScriptAlreadyLoaded ? 1 : 0, 2);
260 // We only care about a load callback if resource is not already
261 // in the cache. Callers will attempt to run the m_parserBlockingScript
262 // if possible before returning control to the parser.
263 if (!m_parserBlockingScript.isReady()) {
264 if (m_document->frame())
265 ScriptStreamer::startStreaming(m_parserBlockingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::ParsingBlocking);
266 m_parserBlockingScript.watchForLoad(this);
270 void HTMLScriptRunner::requestDeferredScript(Element* element)
272 PendingScript pendingScript;
273 if (!requestPendingScript(pendingScript, element))
276 ASSERT(pendingScript.resource());
277 if (m_document->frame() && !pendingScript.isReady())
278 ScriptStreamer::startStreaming(pendingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::Deferred);
280 m_scriptsToExecuteAfterParsing.append(pendingScript);
283 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
285 ASSERT(!pendingScript.element());
286 pendingScript.setElement(script);
287 // This should correctly return 0 for empty or invalid srcValues.
288 ScriptResource* resource = toScriptLoaderIfPossible(script)->resource().get();
290 notImplemented(); // Dispatch error event.
293 pendingScript.setScriptResource(resource);
297 // Implements the initial steps for 'An end tag whose tag name is "script"'
298 // http://whatwg.org/html#scriptEndTag
299 void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
302 ASSERT(!hasParserBlockingScript());
304 ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
306 // This contains both and ASSERTION and a null check since we should not
307 // be getting into the case of a null script element, but seem to be from
308 // time to time. The assertion is left in to help find those cases and
309 // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
310 ASSERT(scriptLoader);
314 ASSERT(scriptLoader->isParserInserted());
316 if (!isExecutingScript())
317 Microtask::performCheckpoint();
319 InsertionPointRecord insertionPointRecord(m_host->inputStream());
320 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
322 scriptLoader->prepareScript(scriptStartPosition);
324 if (!scriptLoader->willBeParserExecuted())
327 if (scriptLoader->willExecuteWhenDocumentFinishedParsing()) {
328 requestDeferredScript(script);
329 } else if (scriptLoader->readyToBeParserExecuted()) {
330 if (m_scriptNestingLevel == 1) {
331 m_parserBlockingScript.setElement(script);
332 m_parserBlockingScript.setStartingPosition(scriptStartPosition);
334 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
335 scriptLoader->executeScript(sourceCode);
338 requestParsingBlockingScript(script);
343 void HTMLScriptRunner::trace(Visitor* visitor)
345 visitor->trace(m_document);
346 visitor->trace(m_host);
347 visitor->trace(m_parserBlockingScript);
348 visitor->trace(m_scriptsToExecuteAfterParsing);