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 "HTMLScriptRunner.h"
28
29#include "Attribute.h"
30#include "CachedScript.h"
31#include "CachedResourceLoader.h"
32#include "Element.h"
33#include "Event.h"
34#include "Frame.h"
35#include "HTMLInputStream.h"
36#include "HTMLNames.h"
37#include "HTMLScriptRunnerHost.h"
38#include "IgnoreDestructiveWriteCountIncrementer.h"
39#include "MutationObserver.h"
40#include "NestingLevelIncrementer.h"
41#include "NotImplemented.h"
42#include "ScriptElement.h"
43#include "ScriptSourceCode.h"
44
45namespace WebCore {
46
47using namespace HTMLNames;
48
49HTMLScriptRunner::HTMLScriptRunner(Document& document, HTMLScriptRunnerHost& host)
50    : m_document(&document)
51    , m_host(host)
52    , m_scriptNestingLevel(0)
53    , m_hasScriptsWaitingForStylesheets(false)
54{
55}
56
57HTMLScriptRunner::~HTMLScriptRunner()
58{
59    // FIXME: Should we be passed a "done loading/parsing" callback sooner than destruction?
60    if (m_parserBlockingScript.cachedScript() && m_parserBlockingScript.watchingForLoad())
61        stopWatchingForLoad(m_parserBlockingScript);
62
63    while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
64        PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
65        if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
66            stopWatchingForLoad(pendingScript);
67    }
68}
69
70void HTMLScriptRunner::detach()
71{
72    m_document = 0;
73}
74
75static URL documentURLForScriptExecution(Document* document)
76{
77    if (!document || !document->frame())
78        return URL();
79
80    // Use the URL of the currently active document for this frame.
81    return document->frame()->document()->url();
82}
83
84inline PassRefPtr<Event> createScriptLoadEvent()
85{
86    return Event::create(eventNames().loadEvent, false, false);
87}
88
89ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
90{
91    if (script.cachedScript()) {
92        errorOccurred = script.cachedScript()->errorOccurred();
93        ASSERT(script.cachedScript()->isLoaded());
94        return ScriptSourceCode(script.cachedScript());
95    }
96    errorOccurred = false;
97    return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
98}
99
100bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
101{
102    if (!m_document)
103        return false;
104    m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
105    if (m_hasScriptsWaitingForStylesheets)
106        return false;
107    if (script.cachedScript() && !script.cachedScript()->isLoaded())
108        return false;
109    return true;
110}
111
112void HTMLScriptRunner::executeParsingBlockingScript()
113{
114    ASSERT(m_document);
115    ASSERT(!isExecutingScript());
116    ASSERT(m_document->haveStylesheetsLoaded());
117    ASSERT(isPendingScriptReady(m_parserBlockingScript));
118
119    InsertionPointRecord insertionPointRecord(m_host.inputStream());
120    executePendingScriptAndDispatchEvent(m_parserBlockingScript);
121}
122
123void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript)
124{
125    bool errorOccurred = false;
126    ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
127
128    // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
129    if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
130        stopWatchingForLoad(pendingScript);
131
132    if (!isExecutingScript())
133        MutationObserver::deliverAllMutations();
134
135    // Clear the pending script before possible rentrancy from executeScript()
136    RefPtr<Element> element = pendingScript.releaseElementAndClear();
137    if (ScriptElement* scriptElement = toScriptElementIfPossible(element.get())) {
138        NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
139        IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
140        if (errorOccurred)
141            scriptElement->dispatchErrorEvent();
142        else {
143            ASSERT(isExecutingScript());
144            scriptElement->executeScript(sourceCode);
145            element->dispatchEvent(createScriptLoadEvent());
146        }
147    }
148    ASSERT(!isExecutingScript());
149}
150
151void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
152{
153    ASSERT(!pendingScript.watchingForLoad());
154    m_host.watchForLoad(pendingScript.cachedScript());
155    pendingScript.setWatchingForLoad(true);
156}
157
158void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
159{
160    ASSERT(pendingScript.watchingForLoad());
161    m_host.stopWatchingForLoad(pendingScript.cachedScript());
162    pendingScript.setWatchingForLoad(false);
163}
164
165// This function should match 10.2.5.11 "An end tag whose tag name is 'script'"
166// Script handling lives outside the tree builder to keep the each class simple.
167void HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
168{
169    ASSERT(scriptElement);
170    // FIXME: If scripting is disabled, always just return.
171
172    bool hadPreloadScanner = m_host.hasPreloadScanner();
173
174    // Try to execute the script given to us.
175    runScript(scriptElement.get(), scriptStartPosition);
176
177    if (hasParserBlockingScript()) {
178        if (isExecutingScript())
179            return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
180        // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
181        if (!hadPreloadScanner && m_host.hasPreloadScanner())
182            m_host.appendCurrentInputStreamToPreloadScannerAndScan();
183        executeParsingBlockingScripts();
184    }
185}
186
187bool HTMLScriptRunner::hasParserBlockingScript() const
188{
189    return !!m_parserBlockingScript.element();
190}
191
192void HTMLScriptRunner::executeParsingBlockingScripts()
193{
194    while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
195        executeParsingBlockingScript();
196}
197
198void HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
199{
200    ASSERT(!isExecutingScript());
201    ASSERT(hasParserBlockingScript());
202    ASSERT_UNUSED(cachedScript, m_parserBlockingScript.cachedScript() == cachedScript);
203    ASSERT(m_parserBlockingScript.cachedScript()->isLoaded());
204    executeParsingBlockingScripts();
205}
206
207void HTMLScriptRunner::executeScriptsWaitingForStylesheets()
208{
209    ASSERT(m_document);
210    // Callers should check hasScriptsWaitingForStylesheets() before calling
211    // to prevent parser or script re-entry during </style> parsing.
212    ASSERT(hasScriptsWaitingForStylesheets());
213    ASSERT(!isExecutingScript());
214    ASSERT(m_document->haveStylesheetsLoaded());
215    executeParsingBlockingScripts();
216}
217
218bool HTMLScriptRunner::executeScriptsWaitingForParsing()
219{
220    while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
221        ASSERT(!isExecutingScript());
222        ASSERT(!hasParserBlockingScript());
223        ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript());
224        if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) {
225            watchForLoad(m_scriptsToExecuteAfterParsing.first());
226            return false;
227        }
228        PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
229        executePendingScriptAndDispatchEvent(first);
230        // FIXME: What is this m_document check for?
231        if (!m_document)
232            return false;
233    }
234    return true;
235}
236
237void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
238{
239    if (!requestPendingScript(m_parserBlockingScript, element))
240        return;
241
242    ASSERT(m_parserBlockingScript.cachedScript());
243
244    // We only care about a load callback if cachedScript is not already
245    // in the cache. Callers will attempt to run the m_parserBlockingScript
246    // if possible before returning control to the parser.
247    if (!m_parserBlockingScript.cachedScript()->isLoaded())
248        watchForLoad(m_parserBlockingScript);
249}
250
251void HTMLScriptRunner::requestDeferredScript(Element* element)
252{
253    PendingScript pendingScript;
254    if (!requestPendingScript(pendingScript, element))
255        return;
256
257    ASSERT(pendingScript.cachedScript());
258    m_scriptsToExecuteAfterParsing.append(pendingScript);
259}
260
261bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
262{
263    ASSERT(!pendingScript.element());
264    pendingScript.setElement(script);
265    // This should correctly return 0 for empty or invalid srcValues.
266    CachedScript* cachedScript = toScriptElementIfPossible(script)->cachedScript().get();
267    if (!cachedScript) {
268        notImplemented(); // Dispatch error event.
269        return false;
270    }
271    pendingScript.setCachedScript(cachedScript);
272    return true;
273}
274
275// This method is meant to match the HTML5 definition of "running a script"
276// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
277void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
278{
279    ASSERT(m_document);
280    ASSERT(!hasParserBlockingScript());
281    {
282        ScriptElement* scriptElement = toScriptElementIfPossible(script);
283
284        // This contains both and ASSERTION and a null check since we should not
285        // be getting into the case of a null script element, but seem to be from
286        // time to time. The assertion is left in to help find those cases and
287        // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
288        ASSERT(scriptElement);
289        if (!scriptElement)
290            return;
291
292        // FIXME: This may be too agressive as we always deliver mutations at
293        // every script element, even if it's not ready to execute yet. There's
294        // unfortuantely no obvious way to tell if prepareScript is going to
295        // execute the script from out here.
296        if (!isExecutingScript())
297            MutationObserver::deliverAllMutations();
298
299        InsertionPointRecord insertionPointRecord(m_host.inputStream());
300        NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
301
302        scriptElement->prepareScript(scriptStartPosition);
303
304        if (!scriptElement->willBeParserExecuted())
305            return;
306
307        if (scriptElement->willExecuteWhenDocumentFinishedParsing())
308            requestDeferredScript(script);
309        else if (scriptElement->readyToBeParserExecuted()) {
310            if (m_scriptNestingLevel == 1) {
311                m_parserBlockingScript.setElement(script);
312                m_parserBlockingScript.setStartingPosition(scriptStartPosition);
313            } else {
314                ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
315                scriptElement->executeScript(sourceCode);
316            }
317        } else
318            requestParsingBlockingScript(script);
319    }
320}
321
322}
323