Update To 11.40.268.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     m_parserBlockingScript.releaseElementAndClear();
78
79     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
80         PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
81         pendingScript.stopWatchingForLoad(this);
82         pendingScript.releaseElementAndClear();
83     }
84     m_document = nullptr;
85 }
86
87 static KURL documentURLForScriptExecution(Document* document)
88 {
89     if (!document)
90         return KURL();
91
92     if (!document->frame()) {
93         if (document->importsController())
94             return document->url();
95         return KURL();
96     }
97
98     // Use the URL of the currently active document for this frame.
99     return document->frame()->document()->url();
100 }
101
102 inline PassRefPtrWillBeRawPtr<Event> createScriptLoadEvent()
103 {
104     return Event::create(EventTypeNames::load);
105 }
106
107 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
108 {
109     m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
110     if (m_hasScriptsWaitingForResources)
111         return false;
112     return script.isReady();
113 }
114
115 void HTMLScriptRunner::executeParsingBlockingScript()
116 {
117     ASSERT(m_document);
118     ASSERT(!isExecutingScript());
119     ASSERT(m_document->isScriptExecutionReady());
120     ASSERT(isPendingScriptReady(m_parserBlockingScript));
121
122     InsertionPointRecord insertionPointRecord(m_host->inputStream());
123     executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScript::ParsingBlocking);
124 }
125
126 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScript::Type pendingScriptType)
127 {
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);
131
132     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
133     pendingScript.stopWatchingForLoad(this);
134
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)
141                 return;
142         }
143     }
144
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);
151         if (errorOccurred)
152             scriptLoader->dispatchErrorEvent();
153         else {
154             ASSERT(isExecutingScript());
155             scriptLoader->executeScript(sourceCode, &compilationFinishTime);
156             element->dispatchEvent(createScriptLoadEvent());
157         }
158     }
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);
163     }
164
165     ASSERT(!isExecutingScript());
166 }
167
168 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
169 {
170     m_host->notifyScriptLoaded(cachedResource);
171 }
172
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)
177 {
178     ASSERT(scriptElement);
179     // FIXME: If scripting is disabled, always just return.
180
181     bool hadPreloadScanner = m_host->hasPreloadScanner();
182
183     // Try to execute the script given to us.
184     runScript(scriptElement.get(), scriptStartPosition);
185
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();
193     }
194 }
195
196 bool HTMLScriptRunner::hasParserBlockingScript() const
197 {
198     return !!m_parserBlockingScript.element();
199 }
200
201 void HTMLScriptRunner::executeParsingBlockingScripts()
202 {
203     while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
204         executeParsingBlockingScript();
205 }
206
207 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
208 {
209     ASSERT(!isExecutingScript());
210     ASSERT(hasParserBlockingScript());
211     ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
212     ASSERT(m_parserBlockingScript.isReady());
213     executeParsingBlockingScripts();
214 }
215
216 void HTMLScriptRunner::executeScriptsWaitingForResources()
217 {
218     ASSERT(m_document);
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();
225 }
226
227 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
228 {
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);
235             return false;
236         }
237         PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
238         executePendingScriptAndDispatchEvent(first, PendingScript::Deferred);
239         // FIXME: What is this m_document check for?
240         if (!m_document)
241             return false;
242     }
243     return true;
244 }
245
246 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
247 {
248     if (!requestPendingScript(m_parserBlockingScript, element))
249         return;
250
251     ASSERT(m_parserBlockingScript.resource());
252
253     // Exclude already loaded resources (from memory cache) and reloads from the
254     // computation of
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);
259
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);
267     }
268 }
269
270 void HTMLScriptRunner::requestDeferredScript(Element* element)
271 {
272     PendingScript pendingScript;
273     if (!requestPendingScript(pendingScript, element))
274         return;
275
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);
279
280     m_scriptsToExecuteAfterParsing.append(pendingScript);
281 }
282
283 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
284 {
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();
289     if (!resource) {
290         notImplemented(); // Dispatch error event.
291         return false;
292     }
293     pendingScript.setScriptResource(resource);
294     return true;
295 }
296
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)
300 {
301     ASSERT(m_document);
302     ASSERT(!hasParserBlockingScript());
303     {
304         ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
305
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);
311         if (!scriptLoader)
312             return;
313
314         ASSERT(scriptLoader->isParserInserted());
315
316         if (!isExecutingScript())
317             Microtask::performCheckpoint();
318
319         InsertionPointRecord insertionPointRecord(m_host->inputStream());
320         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
321
322         scriptLoader->prepareScript(scriptStartPosition);
323
324         if (!scriptLoader->willBeParserExecuted())
325             return;
326
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);
333             } else {
334                 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
335                 scriptLoader->executeScript(sourceCode);
336             }
337         } else {
338             requestParsingBlockingScript(script);
339         }
340     }
341 }
342
343 void HTMLScriptRunner::trace(Visitor* visitor)
344 {
345     visitor->trace(m_document);
346     visitor->trace(m_host);
347     visitor->trace(m_parserBlockingScript);
348     visitor->trace(m_scriptsToExecuteAfterParsing);
349 }
350
351 }