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 "NestingLevelIncrementer.h"
40#include "NotImplemented.h"
41#include "ScriptElement.h"
42#include "ScriptSourceCode.h"
43
44namespace WebCore {
45
46using namespace HTMLNames;
47
48HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
49    : m_document(document)
50    , m_host(host)
51    , m_scriptNestingLevel(0)
52    , m_hasScriptsWaitingForStylesheets(false)
53{
54    ASSERT(m_host);
55}
56
57HTMLScriptRunner::~HTMLScriptRunner()
58{
59    // FIXME: Should we be passed a "done loading/parsing" callback sooner than destruction?
60    if (m_parsingBlockingScript.cachedScript() && m_parsingBlockingScript.watchingForLoad())
61        stopWatchingForLoad(m_parsingBlockingScript);
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 KURL documentURLForScriptExecution(Document* document)
76{
77    if (!document || !document->frame())
78        return KURL();
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
89inline PassRefPtr<Event> createScriptErrorEvent()
90{
91    return Event::create(eventNames().errorEvent, true, false);
92}
93
94ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
95{
96    if (script.cachedScript()) {
97        errorOccurred = script.cachedScript()->errorOccurred();
98        ASSERT(script.cachedScript()->isLoaded());
99        return ScriptSourceCode(script.cachedScript());
100    }
101    errorOccurred = false;
102    return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
103}
104
105bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
106{
107    m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
108    if (m_hasScriptsWaitingForStylesheets)
109        return false;
110    if (script.cachedScript() && !script.cachedScript()->isLoaded())
111        return false;
112    return true;
113}
114
115void HTMLScriptRunner::executeParsingBlockingScript()
116{
117    ASSERT(m_document);
118    ASSERT(!m_scriptNestingLevel);
119    ASSERT(m_document->haveStylesheetsLoaded());
120    ASSERT(isPendingScriptReady(m_parsingBlockingScript));
121
122    InsertionPointRecord insertionPointRecord(m_host->inputStream());
123    executePendingScriptAndDispatchEvent(m_parsingBlockingScript);
124}
125
126void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript)
127{
128    bool errorOccurred = false;
129    ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
130
131    // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
132    if (pendingScript.cachedScript() && pendingScript.watchingForLoad())
133        stopWatchingForLoad(pendingScript);
134
135    // Clear the pending script before possible rentrancy from executeScript()
136    RefPtr<Element> element = pendingScript.releaseElementAndClear();
137    if (ScriptElement* scriptElement = toScriptElement(element.get())) {
138        NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
139        IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
140        if (errorOccurred)
141            element->dispatchEvent(createScriptErrorEvent());
142        else {
143            ASSERT(isExecutingScript());
144            scriptElement->executeScript(sourceCode);
145            element->dispatchEvent(createScriptLoadEvent());
146        }
147    }
148    ASSERT(!m_scriptNestingLevel);
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.
167bool HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, const TextPosition1& scriptStartPosition)
168{
169    ASSERT(scriptElement);
170    // FIXME: If scripting is disabled, always just return true;
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 (haveParsingBlockingScript()) {
178        if (m_scriptNestingLevel)
179            return false; // Block the parser.  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        if (!executeParsingBlockingScripts())
184            return false; // We still have a parsing blocking script, block the parser.
185    }
186    return true; // Scripts executed as expected, continue parsing.
187}
188
189bool HTMLScriptRunner::haveParsingBlockingScript() const
190{
191    return !!m_parsingBlockingScript.element();
192}
193
194bool HTMLScriptRunner::executeParsingBlockingScripts()
195{
196    while (haveParsingBlockingScript()) {
197        // We only really need to check once.
198        if (!isPendingScriptReady(m_parsingBlockingScript))
199            return false;
200        executeParsingBlockingScript();
201    }
202    return true;
203}
204
205bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
206{
207    ASSERT(!m_scriptNestingLevel);
208    ASSERT(haveParsingBlockingScript());
209    ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript);
210    ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded());
211    return executeParsingBlockingScripts();
212}
213
214bool HTMLScriptRunner::executeScriptsWaitingForStylesheets()
215{
216    ASSERT(m_document);
217    // Callers should check hasScriptsWaitingForStylesheets() before calling
218    // to prevent parser or script re-entry during </style> parsing.
219    ASSERT(hasScriptsWaitingForStylesheets());
220    ASSERT(!m_scriptNestingLevel);
221    ASSERT(m_document->haveStylesheetsLoaded());
222    return executeParsingBlockingScripts();
223}
224
225bool HTMLScriptRunner::executeScriptsWaitingForParsing()
226{
227    while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
228        ASSERT(!m_scriptNestingLevel);
229        ASSERT(!haveParsingBlockingScript());
230        ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript());
231        if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) {
232            watchForLoad(m_scriptsToExecuteAfterParsing.first());
233            return false;
234        }
235        PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
236        executePendingScriptAndDispatchEvent(first);
237        if (!m_document)
238            return false;
239    }
240    return true;
241}
242
243void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
244{
245    if (!requestPendingScript(m_parsingBlockingScript, element))
246        return;
247
248    ASSERT(m_parsingBlockingScript.cachedScript());
249
250    // We only care about a load callback if cachedScript is not already
251    // in the cache.  Callers will attempt to run the m_parsingBlockingScript
252    // if possible before returning control to the parser.
253    if (!m_parsingBlockingScript.cachedScript()->isLoaded())
254        watchForLoad(m_parsingBlockingScript);
255}
256
257void HTMLScriptRunner::requestDeferredScript(Element* element)
258{
259    PendingScript pendingScript;
260    if (!requestPendingScript(pendingScript, element))
261        return;
262
263    ASSERT(pendingScript.cachedScript());
264    m_scriptsToExecuteAfterParsing.append(pendingScript);
265}
266
267bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
268{
269    ASSERT(!pendingScript.element());
270    pendingScript.setElement(script);
271    // This should correctly return 0 for empty or invalid srcValues.
272    CachedScript* cachedScript = toScriptElement(script)->cachedScript().get();
273    if (!cachedScript) {
274        notImplemented(); // Dispatch error event.
275        return false;
276    }
277    pendingScript.setCachedScript(cachedScript);
278    return true;
279}
280
281// This method is meant to match the HTML5 definition of "running a script"
282// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
283void HTMLScriptRunner::runScript(Element* script, const TextPosition1& scriptStartPosition)
284{
285    ASSERT(m_document);
286    ASSERT(!haveParsingBlockingScript());
287    {
288        InsertionPointRecord insertionPointRecord(m_host->inputStream());
289        NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
290
291        ScriptElement* scriptElement = toScriptElement(script);
292        ASSERT(scriptElement);
293
294        scriptElement->prepareScript(scriptStartPosition);
295
296        if (!scriptElement->willBeParserExecuted())
297            return;
298
299        if (scriptElement->willExecuteWhenDocumentFinishedParsing())
300            requestDeferredScript(script);
301        else if (scriptElement->readyToBeParserExecuted()) {
302            if (m_scriptNestingLevel == 1) {
303                m_parsingBlockingScript.setElement(script);
304                m_parsingBlockingScript.setStartingPosition(scriptStartPosition);
305            } else {
306                ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
307                scriptElement->executeScript(sourceCode);
308            }
309        } else
310            requestParsingBlockingScript(script);
311    }
312}
313
314}
315