Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / parser / HTMLScriptRunner.cpp
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 "core/html/parser/HTMLScriptRunner.h"
28
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"
42
43 namespace blink {
44
45 using namespace HTMLNames;
46
47 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
48     : m_document(document)
49     , m_host(host)
50     , m_scriptNestingLevel(0)
51     , m_hasScriptsWaitingForResources(false)
52     , m_parserBlockingScriptAlreadyLoaded(false)
53 {
54     ASSERT(m_host);
55 }
56
57 HTMLScriptRunner::~HTMLScriptRunner()
58 {
59 #if ENABLE(OILPAN)
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.
64     detach();
65 #else
66     // Verify that detach() has been called.
67     ASSERT(!m_document);
68 #endif
69 }
70
71 void HTMLScriptRunner::detach()
72 {
73     if (!m_document)
74         return;
75
76     m_parserBlockingScript.stopWatchingForLoad(this);
77
78     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
79         PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
80         pendingScript.stopWatchingForLoad(this);
81     }
82     m_document = nullptr;
83 }
84
85 static KURL documentURLForScriptExecution(Document* document)
86 {
87     if (!document)
88         return KURL();
89
90     if (!document->frame()) {
91         if (document->importsController())
92             return document->url();
93         return KURL();
94     }
95
96     // Use the URL of the currently active document for this frame.
97     return document->frame()->document()->url();
98 }
99
100 inline PassRefPtrWillBeRawPtr<Event> createScriptLoadEvent()
101 {
102     return Event::create(EventTypeNames::load);
103 }
104
105 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
106 {
107     m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
108     if (m_hasScriptsWaitingForResources)
109         return false;
110     return script.isReady();
111 }
112
113 void HTMLScriptRunner::executeParsingBlockingScript()
114 {
115     ASSERT(m_document);
116     ASSERT(!isExecutingScript());
117     ASSERT(m_document->isScriptExecutionReady());
118     ASSERT(isPendingScriptReady(m_parserBlockingScript));
119
120     InsertionPointRecord insertionPointRecord(m_host->inputStream());
121     executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScript::ParsingBlocking);
122 }
123
124 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScript::Type pendingScriptType)
125 {
126     bool errorOccurred = false;
127     double loadFinishTime = pendingScript.resource() && pendingScript.resource()->url().protocolIsInHTTPFamily() ? pendingScript.resource()->loadFinishTime() : 0;
128     ScriptSourceCode sourceCode = pendingScript.getSource(documentURLForScriptExecution(m_document), errorOccurred);
129
130     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
131     pendingScript.stopWatchingForLoad(this);
132
133     if (!isExecutingScript()) {
134         Microtask::performCheckpoint();
135         if (pendingScriptType == PendingScript::ParsingBlocking) {
136             m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
137             // The parser cannot be unblocked as a microtask requested another resource
138             if (m_hasScriptsWaitingForResources)
139                 return;
140         }
141     }
142
143     // Clear the pending script before possible rentrancy from executeScript()
144     RefPtrWillBeRawPtr<Element> element = pendingScript.releaseElementAndClear();
145     double compilationFinishTime = 0;
146     if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element.get())) {
147         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
148         IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
149         if (errorOccurred)
150             scriptLoader->dispatchErrorEvent();
151         else {
152             ASSERT(isExecutingScript());
153             scriptLoader->executeScript(sourceCode, &compilationFinishTime);
154             element->dispatchEvent(createScriptLoadEvent());
155         }
156     }
157     // The exact value doesn't matter; valid time stamps are much bigger than this value.
158     const double epsilon = 1;
159     if (pendingScriptType == PendingScript::ParsingBlocking && !m_parserBlockingScriptAlreadyLoaded && compilationFinishTime > epsilon && loadFinishTime > epsilon) {
160         blink::Platform::current()->histogramCustomCounts("WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled", (compilationFinishTime - loadFinishTime) * 1000, 0, 10000, 50);
161     }
162
163     ASSERT(!isExecutingScript());
164 }
165
166 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
167 {
168     m_host->notifyScriptLoaded(cachedResource);
169 }
170
171 // Implements the steps for 'An end tag whose tag name is "script"'
172 // http://whatwg.org/html#scriptEndTag
173 // Script handling lives outside the tree builder to keep each class simple.
174 void HTMLScriptRunner::execute(PassRefPtrWillBeRawPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
175 {
176     ASSERT(scriptElement);
177     // FIXME: If scripting is disabled, always just return.
178
179     bool hadPreloadScanner = m_host->hasPreloadScanner();
180
181     // Try to execute the script given to us.
182     runScript(scriptElement.get(), scriptStartPosition);
183
184     if (hasParserBlockingScript()) {
185         if (isExecutingScript())
186             return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
187         // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
188         if (!hadPreloadScanner && m_host->hasPreloadScanner())
189             m_host->appendCurrentInputStreamToPreloadScannerAndScan();
190         executeParsingBlockingScripts();
191     }
192 }
193
194 bool HTMLScriptRunner::hasParserBlockingScript() const
195 {
196     return !!m_parserBlockingScript.element();
197 }
198
199 void HTMLScriptRunner::executeParsingBlockingScripts()
200 {
201     while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
202         executeParsingBlockingScript();
203 }
204
205 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
206 {
207     ASSERT(!isExecutingScript());
208     ASSERT(hasParserBlockingScript());
209     ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
210     ASSERT(m_parserBlockingScript.isReady());
211     executeParsingBlockingScripts();
212 }
213
214 void HTMLScriptRunner::executeScriptsWaitingForResources()
215 {
216     ASSERT(m_document);
217     // Callers should check hasScriptsWaitingForResources() before calling
218     // to prevent parser or script re-entry during </style> parsing.
219     ASSERT(hasScriptsWaitingForResources());
220     ASSERT(!isExecutingScript());
221     ASSERT(m_document->isScriptExecutionReady());
222     executeParsingBlockingScripts();
223 }
224
225 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
226 {
227     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
228         ASSERT(!isExecutingScript());
229         ASSERT(!hasParserBlockingScript());
230         ASSERT(m_scriptsToExecuteAfterParsing.first().resource());
231         if (!m_scriptsToExecuteAfterParsing.first().isReady()) {
232             m_scriptsToExecuteAfterParsing.first().watchForLoad(this);
233             return false;
234         }
235         PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
236         executePendingScriptAndDispatchEvent(first, PendingScript::Deferred);
237         // FIXME: What is this m_document check for?
238         if (!m_document)
239             return false;
240     }
241     return true;
242 }
243
244 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
245 {
246     if (!requestPendingScript(m_parserBlockingScript, element))
247         return;
248
249     ASSERT(m_parserBlockingScript.resource());
250
251     // Exclude already loaded resources (from memory cache) and reloads from the
252     // computation of
253     // WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled (done after
254     // the script is compiled).
255     m_parserBlockingScriptAlreadyLoaded = m_parserBlockingScript.resource()->isLoaded() || m_parserBlockingScript.resource()->resourceToRevalidate();
256     blink::Platform::current()->histogramEnumeration("WebCore.Scripts.ParsingBlocking.AlreadyLoaded", m_parserBlockingScriptAlreadyLoaded ? 1 : 0, 2);
257
258     // We only care about a load callback if resource is not already
259     // in the cache. Callers will attempt to run the m_parserBlockingScript
260     // if possible before returning control to the parser.
261     if (!m_parserBlockingScript.isReady()) {
262         if (m_document->frame())
263             ScriptStreamer::startStreaming(m_parserBlockingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::ParsingBlocking);
264         m_parserBlockingScript.watchForLoad(this);
265     }
266 }
267
268 void HTMLScriptRunner::requestDeferredScript(Element* element)
269 {
270     PendingScript pendingScript;
271     if (!requestPendingScript(pendingScript, element))
272         return;
273
274     ASSERT(pendingScript.resource());
275     if (m_document->frame() && !pendingScript.isReady())
276         ScriptStreamer::startStreaming(pendingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::Deferred);
277
278     m_scriptsToExecuteAfterParsing.append(pendingScript);
279 }
280
281 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
282 {
283     ASSERT(!pendingScript.element());
284     pendingScript.setElement(script);
285     // This should correctly return 0 for empty or invalid srcValues.
286     ScriptResource* resource = toScriptLoaderIfPossible(script)->resource().get();
287     if (!resource) {
288         notImplemented(); // Dispatch error event.
289         return false;
290     }
291     pendingScript.setScriptResource(resource);
292     return true;
293 }
294
295 // Implements the initial steps for 'An end tag whose tag name is "script"'
296 // http://whatwg.org/html#scriptEndTag
297 void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
298 {
299     ASSERT(m_document);
300     ASSERT(!hasParserBlockingScript());
301     {
302         ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
303
304         // This contains both and ASSERTION and a null check since we should not
305         // be getting into the case of a null script element, but seem to be from
306         // time to time. The assertion is left in to help find those cases and
307         // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
308         ASSERT(scriptLoader);
309         if (!scriptLoader)
310             return;
311
312         ASSERT(scriptLoader->isParserInserted());
313
314         if (!isExecutingScript())
315             Microtask::performCheckpoint();
316
317         InsertionPointRecord insertionPointRecord(m_host->inputStream());
318         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
319
320         scriptLoader->prepareScript(scriptStartPosition);
321
322         if (!scriptLoader->willBeParserExecuted())
323             return;
324
325         if (scriptLoader->willExecuteWhenDocumentFinishedParsing()) {
326             requestDeferredScript(script);
327         } else if (scriptLoader->readyToBeParserExecuted()) {
328             if (m_scriptNestingLevel == 1) {
329                 m_parserBlockingScript.setElement(script);
330                 m_parserBlockingScript.setStartingPosition(scriptStartPosition);
331             } else {
332                 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
333                 scriptLoader->executeScript(sourceCode);
334             }
335         } else {
336             requestParsingBlockingScript(script);
337         }
338     }
339 }
340
341 void HTMLScriptRunner::trace(Visitor* visitor)
342 {
343     visitor->trace(m_document);
344     visitor->trace(m_host);
345     visitor->trace(m_parserBlockingScript);
346     visitor->trace(m_scriptsToExecuteAfterParsing);
347 }
348
349 }