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/events/Event.h"
     32 #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h"
     33 #include "core/dom/Microtask.h"
     34 #include "core/dom/ScriptLoader.h"
     35 #include "core/fetch/ScriptResource.h"
     36 #include "core/frame/LocalFrame.h"
     37 #include "core/html/parser/HTMLInputStream.h"
     38 #include "core/html/parser/HTMLScriptRunnerHost.h"
     39 #include "core/html/parser/NestingLevelIncrementer.h"
     40 #include "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 #if ENABLE(OILPAN)
     58     // If the document is destructed without having explicitly
     59     // detached the parser (and this script runner object), perform
     60     // detach steps now. This will happen if the Document, the parser
     61     // and this script runner object are swept out in the same GC.
     62     detach();
     63 #else
     64     // Verify that detach() has been called.
     65     ASSERT(!m_document);
     66 #endif
     67 }
     68 
     69 void HTMLScriptRunner::detach()
     70 {
     71     if (!m_document)
     72         return;
     73 
     74     if (m_parserBlockingScript.resource() && m_parserBlockingScript.watchingForLoad())
     75         stopWatchingForLoad(m_parserBlockingScript);
     76 
     77     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
     78         PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
     79         if (pendingScript.resource() && pendingScript.watchingForLoad())
     80             stopWatchingForLoad(pendingScript);
     81     }
     82     m_document = nullptr;
     83 }
     84 
     85 static KURL documentURLForScriptExecution(Document* document)
     86 {
     87     if (!document)
     88         return KURL();
     89 
     90     if (!document->frame()) {
     91         if (document->importsController())
     92             return document->url();
     93         return KURL();
     94     }
     95 
     96     // Use the URL of the currently active document for this frame.
     97     return document->frame()->document()->url();
     98 }
     99 
    100 inline PassRefPtrWillBeRawPtr<Event> createScriptLoadEvent()
    101 {
    102     return Event::create(EventTypeNames::load);
    103 }
    104 
    105 ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred) const
    106 {
    107     if (script.resource()) {
    108         errorOccurred = script.resource()->errorOccurred();
    109         ASSERT(script.resource()->isLoaded());
    110         return ScriptSourceCode(script.resource());
    111     }
    112     errorOccurred = false;
    113     return ScriptSourceCode(script.element()->textContent(), documentURLForScriptExecution(m_document), script.startingPosition());
    114 }
    115 
    116 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
    117 {
    118     m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
    119     if (m_hasScriptsWaitingForResources)
    120         return false;
    121     if (script.resource() && !script.resource()->isLoaded())
    122         return false;
    123     return true;
    124 }
    125 
    126 void HTMLScriptRunner::executeParsingBlockingScript()
    127 {
    128     ASSERT(m_document);
    129     ASSERT(!isExecutingScript());
    130     ASSERT(m_document->isScriptExecutionReady());
    131     ASSERT(isPendingScriptReady(m_parserBlockingScript));
    132 
    133     InsertionPointRecord insertionPointRecord(m_host->inputStream());
    134     executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScriptBlockingParser);
    135 }
    136 
    137 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScriptType pendingScriptType)
    138 {
    139     bool errorOccurred = false;
    140     ScriptSourceCode sourceCode = sourceFromPendingScript(pendingScript, errorOccurred);
    141 
    142     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
    143     if (pendingScript.resource() && pendingScript.watchingForLoad())
    144         stopWatchingForLoad(pendingScript);
    145 
    146     if (!isExecutingScript()) {
    147         Microtask::performCheckpoint();
    148         if (pendingScriptType == PendingScriptBlockingParser) {
    149             m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
    150             // The parser cannot be unblocked as a microtask requested another resource
    151             if (m_hasScriptsWaitingForResources)
    152                 return;
    153         }
    154     }
    155 
    156     // Clear the pending script before possible rentrancy from executeScript()
    157     RefPtrWillBeRawPtr<Element> element = pendingScript.releaseElementAndClear();
    158     if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element.get())) {
    159         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
    160         IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
    161         if (errorOccurred)
    162             scriptLoader->dispatchErrorEvent();
    163         else {
    164             ASSERT(isExecutingScript());
    165             scriptLoader->executeScript(sourceCode);
    166             element->dispatchEvent(createScriptLoadEvent());
    167         }
    168     }
    169     ASSERT(!isExecutingScript());
    170 }
    171 
    172 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
    173 {
    174     ASSERT(!pendingScript.watchingForLoad());
    175     ASSERT(!pendingScript.resource()->isLoaded());
    176     // addClient() will call notifyFinished() if the load is complete.
    177     // Callers do not expect to be re-entered from this call, so they
    178     // should not become a client of an already-loaded Resource.
    179     pendingScript.resource()->addClient(this);
    180     pendingScript.setWatchingForLoad(true);
    181 }
    182 
    183 void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
    184 {
    185     ASSERT(pendingScript.watchingForLoad());
    186     pendingScript.resource()->removeClient(this);
    187     pendingScript.setWatchingForLoad(false);
    188 }
    189 
    190 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
    191 {
    192     m_host->notifyScriptLoaded(cachedResource);
    193 }
    194 
    195 // Implements the steps for 'An end tag whose tag name is "script"'
    196 // http://whatwg.org/html#scriptEndTag
    197 // Script handling lives outside the tree builder to keep each class simple.
    198 void HTMLScriptRunner::execute(PassRefPtrWillBeRawPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
    199 {
    200     ASSERT(scriptElement);
    201     // FIXME: If scripting is disabled, always just return.
    202 
    203     bool hadPreloadScanner = m_host->hasPreloadScanner();
    204 
    205     // Try to execute the script given to us.
    206     runScript(scriptElement.get(), scriptStartPosition);
    207 
    208     if (hasParserBlockingScript()) {
    209         if (isExecutingScript())
    210             return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
    211         // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
    212         if (!hadPreloadScanner && m_host->hasPreloadScanner())
    213             m_host->appendCurrentInputStreamToPreloadScannerAndScan();
    214         executeParsingBlockingScripts();
    215     }
    216 }
    217 
    218 bool HTMLScriptRunner::hasParserBlockingScript() const
    219 {
    220     return !!m_parserBlockingScript.element();
    221 }
    222 
    223 void HTMLScriptRunner::executeParsingBlockingScripts()
    224 {
    225     while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
    226         executeParsingBlockingScript();
    227 }
    228 
    229 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
    230 {
    231     ASSERT(!isExecutingScript());
    232     ASSERT(hasParserBlockingScript());
    233     ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
    234     ASSERT(m_parserBlockingScript.resource()->isLoaded());
    235     executeParsingBlockingScripts();
    236 }
    237 
    238 void HTMLScriptRunner::executeScriptsWaitingForResources()
    239 {
    240     ASSERT(m_document);
    241     // Callers should check hasScriptsWaitingForResources() before calling
    242     // to prevent parser or script re-entry during </style> parsing.
    243     ASSERT(hasScriptsWaitingForResources());
    244     ASSERT(!isExecutingScript());
    245     ASSERT(m_document->isScriptExecutionReady());
    246     executeParsingBlockingScripts();
    247 }
    248 
    249 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
    250 {
    251     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
    252         ASSERT(!isExecutingScript());
    253         ASSERT(!hasParserBlockingScript());
    254         ASSERT(m_scriptsToExecuteAfterParsing.first().resource());
    255         if (!m_scriptsToExecuteAfterParsing.first().resource()->isLoaded()) {
    256             watchForLoad(m_scriptsToExecuteAfterParsing.first());
    257             return false;
    258         }
    259         PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
    260         executePendingScriptAndDispatchEvent(first, PendingScriptDeferred);
    261         // FIXME: What is this m_document check for?
    262         if (!m_document)
    263             return false;
    264     }
    265     return true;
    266 }
    267 
    268 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
    269 {
    270     if (!requestPendingScript(m_parserBlockingScript, element))
    271         return;
    272 
    273     ASSERT(m_parserBlockingScript.resource());
    274 
    275     // We only care about a load callback if resource is not already
    276     // in the cache. Callers will attempt to run the m_parserBlockingScript
    277     // if possible before returning control to the parser.
    278     if (!m_parserBlockingScript.resource()->isLoaded())
    279         watchForLoad(m_parserBlockingScript);
    280 }
    281 
    282 void HTMLScriptRunner::requestDeferredScript(Element* element)
    283 {
    284     PendingScript pendingScript;
    285     if (!requestPendingScript(pendingScript, element))
    286         return;
    287 
    288     ASSERT(pendingScript.resource());
    289     m_scriptsToExecuteAfterParsing.append(pendingScript);
    290 }
    291 
    292 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
    293 {
    294     ASSERT(!pendingScript.element());
    295     pendingScript.setElement(script);
    296     // This should correctly return 0 for empty or invalid srcValues.
    297     ScriptResource* resource = toScriptLoaderIfPossible(script)->resource().get();
    298     if (!resource) {
    299         notImplemented(); // Dispatch error event.
    300         return false;
    301     }
    302     pendingScript.setScriptResource(resource);
    303     return true;
    304 }
    305 
    306 // Implements the initial steps for 'An end tag whose tag name is "script"'
    307 // http://whatwg.org/html#scriptEndTag
    308 void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
    309 {
    310     ASSERT(m_document);
    311     ASSERT(!hasParserBlockingScript());
    312     {
    313         ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
    314 
    315         // This contains both and ASSERTION and a null check since we should not
    316         // be getting into the case of a null script element, but seem to be from
    317         // time to time. The assertion is left in to help find those cases and
    318         // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
    319         ASSERT(scriptLoader);
    320         if (!scriptLoader)
    321             return;
    322 
    323         ASSERT(scriptLoader->isParserInserted());
    324 
    325         if (!isExecutingScript())
    326             Microtask::performCheckpoint();
    327 
    328         InsertionPointRecord insertionPointRecord(m_host->inputStream());
    329         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
    330 
    331         scriptLoader->prepareScript(scriptStartPosition);
    332 
    333         if (!scriptLoader->willBeParserExecuted())
    334             return;
    335 
    336         if (scriptLoader->willExecuteWhenDocumentFinishedParsing()) {
    337             requestDeferredScript(script);
    338         } else if (scriptLoader->readyToBeParserExecuted()) {
    339             if (m_scriptNestingLevel == 1) {
    340                 m_parserBlockingScript.setElement(script);
    341                 m_parserBlockingScript.setStartingPosition(scriptStartPosition);
    342             } else {
    343                 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
    344                 scriptLoader->executeScript(sourceCode);
    345             }
    346         } else {
    347             requestParsingBlockingScript(script);
    348         }
    349     }
    350 }
    351 
    352 void HTMLScriptRunner::trace(Visitor* visitor)
    353 {
    354     visitor->trace(m_document);
    355     visitor->trace(m_host);
    356     visitor->trace(m_parserBlockingScript);
    357     visitor->trace(m_scriptsToExecuteAfterParsing);
    358 }
    359 
    360 }
    361