HTMLScriptRunner.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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    // Try to execute the script given to us.
173    runScript(scriptElement.get(), scriptStartPosition);
174
175    if (haveParsingBlockingScript()) {
176        if (m_scriptNestingLevel)
177            return false; // Block the parser.  Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
178        if (!executeParsingBlockingScripts())
179            return false; // We still have a parsing blocking script, block the parser.
180    }
181    return true; // Scripts executed as expected, continue parsing.
182}
183
184bool HTMLScriptRunner::haveParsingBlockingScript() const
185{
186    return !!m_parsingBlockingScript.element();
187}
188
189bool HTMLScriptRunner::executeParsingBlockingScripts()
190{
191    while (haveParsingBlockingScript()) {
192        // We only really need to check once.
193        if (!isPendingScriptReady(m_parsingBlockingScript))
194            return false;
195        executeParsingBlockingScript();
196    }
197    return true;
198}
199
200bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
201{
202    ASSERT(!m_scriptNestingLevel);
203    ASSERT(haveParsingBlockingScript());
204    ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript);
205    ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded());
206    return executeParsingBlockingScripts();
207}
208
209bool HTMLScriptRunner::executeScriptsWaitingForStylesheets()
210{
211    ASSERT(m_document);
212    // Callers should check hasScriptsWaitingForStylesheets() before calling
213    // to prevent parser or script re-entry during </style> parsing.
214    ASSERT(hasScriptsWaitingForStylesheets());
215    ASSERT(!m_scriptNestingLevel);
216    ASSERT(m_document->haveStylesheetsLoaded());
217    return executeParsingBlockingScripts();
218}
219
220bool HTMLScriptRunner::executeScriptsWaitingForParsing()
221{
222    while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
223        ASSERT(!m_scriptNestingLevel);
224        ASSERT(!haveParsingBlockingScript());
225        ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript());
226        if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) {
227            watchForLoad(m_scriptsToExecuteAfterParsing.first());
228            return false;
229        }
230        PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
231        executePendingScriptAndDispatchEvent(first);
232        if (!m_document)
233            return false;
234    }
235    return true;
236}
237
238void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
239{
240    if (!requestPendingScript(m_parsingBlockingScript, element))
241        return;
242
243    ASSERT(m_parsingBlockingScript.cachedScript());
244
245    // We only care about a load callback if cachedScript is not already
246    // in the cache.  Callers will attempt to run the m_parsingBlockingScript
247    // if possible before returning control to the parser.
248    if (!m_parsingBlockingScript.cachedScript()->isLoaded())
249        watchForLoad(m_parsingBlockingScript);
250}
251
252void HTMLScriptRunner::requestDeferredScript(Element* element)
253{
254    PendingScript pendingScript;
255    if (!requestPendingScript(pendingScript, element))
256        return;
257
258    ASSERT(pendingScript.cachedScript());
259    m_scriptsToExecuteAfterParsing.append(pendingScript);
260}
261
262bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
263{
264    ASSERT(!pendingScript.element());
265    const AtomicString& srcValue = script->getAttribute(srcAttr);
266    // Allow the host to disllow script loads (using the XSSAuditor, etc.)
267    // FIXME: this check should be performed on the final URL in a redirect chain.
268    if (!m_host->shouldLoadExternalScriptFromSrc(srcValue))
269        return false;
270    // FIXME: We need to resolve the url relative to the element.
271    if (!script->dispatchBeforeLoadEvent(srcValue))
272        return false;
273    pendingScript.setElement(script);
274    // This should correctly return 0 for empty or invalid srcValues.
275    CachedScript* cachedScript = m_document->cachedResourceLoader()->requestScript(srcValue, toScriptElement(script)->scriptCharset());
276    if (!cachedScript) {
277        notImplemented(); // Dispatch error event.
278        return false;
279    }
280    pendingScript.setCachedScript(cachedScript);
281    return true;
282}
283
284// This method is meant to match the HTML5 definition of "running a script"
285// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
286void HTMLScriptRunner::runScript(Element* script, const TextPosition1& scriptStartPosition)
287{
288    ASSERT(m_document);
289    ASSERT(!haveParsingBlockingScript());
290    {
291        InsertionPointRecord insertionPointRecord(m_host->inputStream());
292        NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
293
294        ScriptElement* scriptElement = toScriptElement(script);
295        ASSERT(scriptElement);
296        if (!scriptElement->shouldExecuteAsJavaScript())
297            return;
298
299        if (script->hasAttribute(srcAttr)) {
300            if (script->hasAttribute(asyncAttr)) // Async takes precendence over defer.
301                return; // Asynchronous scripts handle themselves.
302
303            if (script->hasAttribute(deferAttr))
304                requestDeferredScript(script);
305            else
306                requestParsingBlockingScript(script);
307        } else if (!m_document->haveStylesheetsLoaded() && m_scriptNestingLevel == 1) {
308            // Block inline script execution on stylesheet load, unless we are in document.write().
309            // The latter case can only happen if a script both triggers a stylesheet load
310            // and writes an inline script. Since write is blocking we have to execute the
311            // written script immediately, ignoring the pending sheets.
312            m_parsingBlockingScript.setElement(script);
313            m_parsingBlockingScript.setStartingPosition(scriptStartPosition);
314        } else {
315            ASSERT(isExecutingScript());
316            ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
317            scriptElement->executeScript(sourceCode);
318        }
319    }
320}
321
322}
323