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