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/core/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 #include "public/platform/Platform.h"
     42 
     43 namespace blink {
     44 
     45 using namespace HTMLNames;
     46 
     47 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
     48     : m_document(document)
     49     , m_host(host)
     50     , m_scriptNestingLevel(0)
     51     , m_hasScriptsWaitingForResources(false)
     52     , m_parserBlockingScriptAlreadyLoaded(false)
     53 {
     54     ASSERT(m_host);
     55 }
     56 
     57 HTMLScriptRunner::~HTMLScriptRunner()
     58 {
     59 #if ENABLE(OILPAN)
     60     // If the document is destructed without having explicitly
     61     // detached the parser (and this script runner object), perform
     62     // detach steps now. This will happen if the Document, the parser
     63     // and this script runner object are swept out in the same GC.
     64     detach();
     65 #else
     66     // Verify that detach() has been called.
     67     ASSERT(!m_document);
     68 #endif
     69 }
     70 
     71 void HTMLScriptRunner::detach()
     72 {
     73     if (!m_document)
     74         return;
     75 
     76     m_parserBlockingScript.stopWatchingForLoad(this);
     77 
     78     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
     79         PendingScript pendingScript = m_scriptsToExecuteAfterParsing.takeFirst();
     80         pendingScript.stopWatchingForLoad(this);
     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 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
    106 {
    107     m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
    108     if (m_hasScriptsWaitingForResources)
    109         return false;
    110     return script.isReady();
    111 }
    112 
    113 void HTMLScriptRunner::executeParsingBlockingScript()
    114 {
    115     ASSERT(m_document);
    116     ASSERT(!isExecutingScript());
    117     ASSERT(m_document->isScriptExecutionReady());
    118     ASSERT(isPendingScriptReady(m_parserBlockingScript));
    119 
    120     InsertionPointRecord insertionPointRecord(m_host->inputStream());
    121     executePendingScriptAndDispatchEvent(m_parserBlockingScript, PendingScript::ParsingBlocking);
    122 }
    123 
    124 void HTMLScriptRunner::executePendingScriptAndDispatchEvent(PendingScript& pendingScript, PendingScript::Type pendingScriptType)
    125 {
    126     bool errorOccurred = false;
    127     double loadFinishTime = pendingScript.resource() && pendingScript.resource()->url().protocolIsInHTTPFamily() ? pendingScript.resource()->loadFinishTime() : 0;
    128     ScriptSourceCode sourceCode = pendingScript.getSource(documentURLForScriptExecution(m_document), errorOccurred);
    129 
    130     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
    131     pendingScript.stopWatchingForLoad(this);
    132 
    133     if (!isExecutingScript()) {
    134         Microtask::performCheckpoint();
    135         if (pendingScriptType == PendingScript::ParsingBlocking) {
    136             m_hasScriptsWaitingForResources = !m_document->isScriptExecutionReady();
    137             // The parser cannot be unblocked as a microtask requested another resource
    138             if (m_hasScriptsWaitingForResources)
    139                 return;
    140         }
    141     }
    142 
    143     // Clear the pending script before possible rentrancy from executeScript()
    144     RefPtrWillBeRawPtr<Element> element = pendingScript.releaseElementAndClear();
    145     double compilationFinishTime = 0;
    146     if (ScriptLoader* scriptLoader = toScriptLoaderIfPossible(element.get())) {
    147         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
    148         IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_document);
    149         if (errorOccurred)
    150             scriptLoader->dispatchErrorEvent();
    151         else {
    152             ASSERT(isExecutingScript());
    153             scriptLoader->executeScript(sourceCode, &compilationFinishTime);
    154             element->dispatchEvent(createScriptLoadEvent());
    155         }
    156     }
    157     // The exact value doesn't matter; valid time stamps are much bigger than this value.
    158     const double epsilon = 1;
    159     if (pendingScriptType == PendingScript::ParsingBlocking && !m_parserBlockingScriptAlreadyLoaded && compilationFinishTime > epsilon && loadFinishTime > epsilon) {
    160         blink::Platform::current()->histogramCustomCounts("WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled", (compilationFinishTime - loadFinishTime) * 1000, 0, 10000, 50);
    161     }
    162 
    163     ASSERT(!isExecutingScript());
    164 }
    165 
    166 void HTMLScriptRunner::notifyFinished(Resource* cachedResource)
    167 {
    168     m_host->notifyScriptLoaded(cachedResource);
    169 }
    170 
    171 // Implements the steps for 'An end tag whose tag name is "script"'
    172 // http://whatwg.org/html#scriptEndTag
    173 // Script handling lives outside the tree builder to keep each class simple.
    174 void HTMLScriptRunner::execute(PassRefPtrWillBeRawPtr<Element> scriptElement, const TextPosition& scriptStartPosition)
    175 {
    176     ASSERT(scriptElement);
    177     // FIXME: If scripting is disabled, always just return.
    178 
    179     bool hadPreloadScanner = m_host->hasPreloadScanner();
    180 
    181     // Try to execute the script given to us.
    182     runScript(scriptElement.get(), scriptStartPosition);
    183 
    184     if (hasParserBlockingScript()) {
    185         if (isExecutingScript())
    186             return; // Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
    187         // If preload scanner got created, it is missing the source after the current insertion point. Append it and scan.
    188         if (!hadPreloadScanner && m_host->hasPreloadScanner())
    189             m_host->appendCurrentInputStreamToPreloadScannerAndScan();
    190         executeParsingBlockingScripts();
    191     }
    192 }
    193 
    194 bool HTMLScriptRunner::hasParserBlockingScript() const
    195 {
    196     return !!m_parserBlockingScript.element();
    197 }
    198 
    199 void HTMLScriptRunner::executeParsingBlockingScripts()
    200 {
    201     while (hasParserBlockingScript() && isPendingScriptReady(m_parserBlockingScript))
    202         executeParsingBlockingScript();
    203 }
    204 
    205 void HTMLScriptRunner::executeScriptsWaitingForLoad(Resource* resource)
    206 {
    207     ASSERT(!isExecutingScript());
    208     ASSERT(hasParserBlockingScript());
    209     ASSERT_UNUSED(resource, m_parserBlockingScript.resource() == resource);
    210     ASSERT(m_parserBlockingScript.isReady());
    211     executeParsingBlockingScripts();
    212 }
    213 
    214 void HTMLScriptRunner::executeScriptsWaitingForResources()
    215 {
    216     ASSERT(m_document);
    217     // Callers should check hasScriptsWaitingForResources() before calling
    218     // to prevent parser or script re-entry during </style> parsing.
    219     ASSERT(hasScriptsWaitingForResources());
    220     ASSERT(!isExecutingScript());
    221     ASSERT(m_document->isScriptExecutionReady());
    222     executeParsingBlockingScripts();
    223 }
    224 
    225 bool HTMLScriptRunner::executeScriptsWaitingForParsing()
    226 {
    227     while (!m_scriptsToExecuteAfterParsing.isEmpty()) {
    228         ASSERT(!isExecutingScript());
    229         ASSERT(!hasParserBlockingScript());
    230         ASSERT(m_scriptsToExecuteAfterParsing.first().resource());
    231         if (!m_scriptsToExecuteAfterParsing.first().isReady()) {
    232             m_scriptsToExecuteAfterParsing.first().watchForLoad(this);
    233             return false;
    234         }
    235         PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst();
    236         executePendingScriptAndDispatchEvent(first, PendingScript::Deferred);
    237         // FIXME: What is this m_document check for?
    238         if (!m_document)
    239             return false;
    240     }
    241     return true;
    242 }
    243 
    244 void HTMLScriptRunner::requestParsingBlockingScript(Element* element)
    245 {
    246     if (!requestPendingScript(m_parserBlockingScript, element))
    247         return;
    248 
    249     ASSERT(m_parserBlockingScript.resource());
    250 
    251     // Exclude already loaded resources (from memory cache) and reloads from the
    252     // computation of
    253     // WebCore.Scripts.ParsingBlocking.TimeBetweenLoadedAndCompiled (done after
    254     // the script is compiled).
    255     m_parserBlockingScriptAlreadyLoaded = m_parserBlockingScript.resource()->isLoaded() || m_parserBlockingScript.resource()->resourceToRevalidate();
    256     blink::Platform::current()->histogramEnumeration("WebCore.Scripts.ParsingBlocking.AlreadyLoaded", m_parserBlockingScriptAlreadyLoaded ? 1 : 0, 2);
    257 
    258     // We only care about a load callback if resource is not already
    259     // in the cache. Callers will attempt to run the m_parserBlockingScript
    260     // if possible before returning control to the parser.
    261     if (!m_parserBlockingScript.isReady()) {
    262         if (m_document->frame())
    263             ScriptStreamer::startStreaming(m_parserBlockingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::ParsingBlocking);
    264         m_parserBlockingScript.watchForLoad(this);
    265     }
    266 }
    267 
    268 void HTMLScriptRunner::requestDeferredScript(Element* element)
    269 {
    270     PendingScript pendingScript;
    271     if (!requestPendingScript(pendingScript, element))
    272         return;
    273 
    274     ASSERT(pendingScript.resource());
    275     if (m_document->frame() && !pendingScript.isReady())
    276         ScriptStreamer::startStreaming(pendingScript, m_document->frame()->settings(), ScriptState::forMainWorld(m_document->frame()), PendingScript::Deferred);
    277 
    278     m_scriptsToExecuteAfterParsing.append(pendingScript);
    279 }
    280 
    281 bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const
    282 {
    283     ASSERT(!pendingScript.element());
    284     pendingScript.setElement(script);
    285     // This should correctly return 0 for empty or invalid srcValues.
    286     ScriptResource* resource = toScriptLoaderIfPossible(script)->resource().get();
    287     if (!resource) {
    288         notImplemented(); // Dispatch error event.
    289         return false;
    290     }
    291     pendingScript.setScriptResource(resource);
    292     return true;
    293 }
    294 
    295 // Implements the initial steps for 'An end tag whose tag name is "script"'
    296 // http://whatwg.org/html#scriptEndTag
    297 void HTMLScriptRunner::runScript(Element* script, const TextPosition& scriptStartPosition)
    298 {
    299     ASSERT(m_document);
    300     ASSERT(!hasParserBlockingScript());
    301     {
    302         ScriptLoader* scriptLoader = toScriptLoaderIfPossible(script);
    303 
    304         // This contains both and ASSERTION and a null check since we should not
    305         // be getting into the case of a null script element, but seem to be from
    306         // time to time. The assertion is left in to help find those cases and
    307         // is being tracked by <https://bugs.webkit.org/show_bug.cgi?id=60559>.
    308         ASSERT(scriptLoader);
    309         if (!scriptLoader)
    310             return;
    311 
    312         ASSERT(scriptLoader->isParserInserted());
    313 
    314         if (!isExecutingScript())
    315             Microtask::performCheckpoint();
    316 
    317         InsertionPointRecord insertionPointRecord(m_host->inputStream());
    318         NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel);
    319 
    320         scriptLoader->prepareScript(scriptStartPosition);
    321 
    322         if (!scriptLoader->willBeParserExecuted())
    323             return;
    324 
    325         if (scriptLoader->willExecuteWhenDocumentFinishedParsing()) {
    326             requestDeferredScript(script);
    327         } else if (scriptLoader->readyToBeParserExecuted()) {
    328             if (m_scriptNestingLevel == 1) {
    329                 m_parserBlockingScript.setElement(script);
    330                 m_parserBlockingScript.setStartingPosition(scriptStartPosition);
    331             } else {
    332                 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition);
    333                 scriptLoader->executeScript(sourceCode);
    334             }
    335         } else {
    336             requestParsingBlockingScript(script);
    337         }
    338     }
    339 }
    340 
    341 void HTMLScriptRunner::trace(Visitor* visitor)
    342 {
    343     visitor->trace(m_document);
    344     visitor->trace(m_host);
    345     visitor->trace(m_parserBlockingScript);
    346     visitor->trace(m_scriptsToExecuteAfterParsing);
    347 }
    348 
    349 }
    350