Home | History | Annotate | Download | only in parser
      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 
     44 namespace WebCore {
     45 
     46 using namespace HTMLNames;
     47 
     48 HTMLScriptRunner::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 
     57 HTMLScriptRunner::~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 
     70 void HTMLScriptRunner::detach()
     71 {
     72     m_document = 0;
     73 }
     74 
     75 static 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 
     84 inline PassRefPtr<Event> createScriptLoadEvent()
     85 {
     86     return Event::create(eventNames().loadEvent, false, false);
     87 }
     88 
     89 inline PassRefPtr<Event> createScriptErrorEvent()
     90 {
     91     return Event::create(eventNames().errorEvent, true, false);
     92 }
     93 
     94 ScriptSourceCode 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 
    105 bool 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 
    115 void 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 
    126 void 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 
    151 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
    152 {
    153     ASSERT(!pendingScript.watchingForLoad());
    154     m_host->watchForLoad(pendingScript.cachedScript());
    155     pendingScript.setWatchingForLoad(true);
    156 }
    157 
    158 void 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.
    167 bool 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 
    189 bool HTMLScriptRunner::haveParsingBlockingScript() const
    190 {
    191     return !!m_parsingBlockingScript.element();
    192 }
    193 
    194 bool 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 
    205 bool 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 
    214 bool 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 
    225 bool 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 
    243 void 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 
    257 void 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 
    267 bool 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
    283 void 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