WebCore/html/HTMLScriptRunner.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 "DocLoader.h"
       
    32 #include "Element.h"
       
    33 #include "Event.h"
       
    34 #include "Frame.h"
       
    35 #include "HTMLScriptRunnerHost.h"
       
    36 #include "HTMLInputStream.h"
       
    37 #include "HTMLNames.h"
       
    38 #include "NotImplemented.h"
       
    39 #include "ScriptElement.h"
       
    40 #include "ScriptSourceCode.h"
       
    41 
       
    42 namespace WebCore {
       
    43 
       
    44 using namespace HTMLNames;
       
    45 
       
    46 class NestScript : public Noncopyable {
       
    47 public:
       
    48     NestScript(unsigned& nestingLevel, HTMLInputStream& inputStream)
       
    49         : m_nestingLevel(&nestingLevel)
       
    50         , m_savedInsertionPoint(inputStream)
       
    51     {
       
    52         ++(*m_nestingLevel);
       
    53     }
       
    54 
       
    55     ~NestScript()
       
    56     {
       
    57         --(*m_nestingLevel);
       
    58     }
       
    59 
       
    60 private:
       
    61     unsigned* m_nestingLevel;
       
    62     InsertionPointRecord m_savedInsertionPoint;
       
    63 };
       
    64 
       
    65 HTMLScriptRunner::HTMLScriptRunner(Document* document, HTMLScriptRunnerHost* host)
       
    66     : m_document(document)
       
    67     , m_host(host)
       
    68     , m_scriptNestingLevel(0)
       
    69     , m_hasScriptsWaitingForStylesheets(false)
       
    70 {
       
    71     ASSERT(m_host);
       
    72 }
       
    73 
       
    74 HTMLScriptRunner::~HTMLScriptRunner()
       
    75 {
       
    76     // FIXME: Should we be passed a "done loading/parsing" callback sooner than destruction?
       
    77     if (m_parsingBlockingScript.cachedScript() && m_parsingBlockingScript.watchingForLoad())
       
    78         stopWatchingForLoad(m_parsingBlockingScript);
       
    79 }
       
    80 
       
    81 static KURL documentURLForScriptExecution(Document* document)
       
    82 {
       
    83     if (!document || !document->frame())
       
    84         return KURL();
       
    85 
       
    86     // Use the URL of the currently active document for this frame.
       
    87     return document->frame()->document()->url();
       
    88 }
       
    89 
       
    90 inline PassRefPtr<Event> createScriptLoadEvent()
       
    91 {
       
    92     return Event::create(eventNames().loadEvent, false, false);
       
    93 }
       
    94 
       
    95 inline PassRefPtr<Event> createScriptErrorEvent()
       
    96 {
       
    97     return Event::create(eventNames().errorEvent, true, false);
       
    98 }
       
    99 
       
   100 ScriptSourceCode HTMLScriptRunner::sourceFromPendingScript(const PendingScript& script, bool& errorOccurred)
       
   101 {
       
   102     if (script.cachedScript()) {
       
   103         errorOccurred = script.cachedScript()->errorOccurred();
       
   104         ASSERT(script.cachedScript()->isLoaded());
       
   105         return ScriptSourceCode(script.cachedScript());
       
   106     }
       
   107     errorOccurred = false;
       
   108     return ScriptSourceCode(script.element->textContent(), documentURLForScriptExecution(m_document), script.startingLineNumber);
       
   109 }
       
   110 
       
   111 bool HTMLScriptRunner::isPendingScriptReady(const PendingScript& script)
       
   112 {
       
   113     m_hasScriptsWaitingForStylesheets = !m_document->haveStylesheetsLoaded();
       
   114     if (m_hasScriptsWaitingForStylesheets)
       
   115         return false;
       
   116     if (script.cachedScript() && !script.cachedScript()->isLoaded())
       
   117         return false;
       
   118     return true;
       
   119 }
       
   120 
       
   121 void HTMLScriptRunner::executePendingScript()
       
   122 {
       
   123     ASSERT(!m_scriptNestingLevel);
       
   124     ASSERT(m_document->haveStylesheetsLoaded());
       
   125     bool errorOccurred = false;
       
   126     ASSERT(isPendingScriptReady(m_parsingBlockingScript));
       
   127     ScriptSourceCode sourceCode = sourceFromPendingScript(m_parsingBlockingScript, errorOccurred);
       
   128 
       
   129     // Stop watching loads before executeScript to prevent recursion if the script reloads itself.
       
   130     if (m_parsingBlockingScript.cachedScript() && m_parsingBlockingScript.watchingForLoad())
       
   131         stopWatchingForLoad(m_parsingBlockingScript);
       
   132 
       
   133     // Clear the pending script before possible rentrancy from executeScript()
       
   134     RefPtr<Element> scriptElement = m_parsingBlockingScript.releaseElementAndClear();
       
   135     {
       
   136         NestScript nestingLevel(m_scriptNestingLevel, m_host->inputStream());
       
   137         if (errorOccurred)
       
   138             scriptElement->dispatchEvent(createScriptErrorEvent());
       
   139         else {
       
   140             executeScript(scriptElement.get(), sourceCode);
       
   141             scriptElement->dispatchEvent(createScriptLoadEvent());
       
   142         }
       
   143     }
       
   144     ASSERT(!m_scriptNestingLevel);
       
   145 }
       
   146 
       
   147 void HTMLScriptRunner::executeScript(Element* element, const ScriptSourceCode& sourceCode)
       
   148 {
       
   149     // FIXME: We do not block inline <script> tags on stylesheets for now.
       
   150     // When we do,  || !element->hasAttribute(srcAttr) should be removed from
       
   151     // the ASSERT below.  See https://bugs.webkit.org/show_bug.cgi?id=40047
       
   152     ASSERT(m_document->haveStylesheetsLoaded() || !element->hasAttribute(srcAttr));
       
   153     ScriptElement* scriptElement = toScriptElement(element);
       
   154     ASSERT(scriptElement);
       
   155     if (!scriptElement->shouldExecuteAsJavaScript())
       
   156         return;
       
   157     ASSERT(inScriptExecution());
       
   158     if (!m_document->frame())
       
   159         return;
       
   160     m_document->frame()->script()->executeScript(sourceCode);
       
   161 }
       
   162 
       
   163 void HTMLScriptRunner::watchForLoad(PendingScript& pendingScript)
       
   164 {
       
   165     ASSERT(!pendingScript.watchingForLoad());
       
   166     m_host->watchForLoad(pendingScript.cachedScript());
       
   167     pendingScript.setWatchingForLoad(true);
       
   168 }
       
   169 
       
   170 void HTMLScriptRunner::stopWatchingForLoad(PendingScript& pendingScript)
       
   171 {
       
   172     ASSERT(pendingScript.watchingForLoad());
       
   173     m_host->stopWatchingForLoad(pendingScript.cachedScript());
       
   174     pendingScript.setWatchingForLoad(false);
       
   175 }
       
   176 
       
   177 // This function should match 10.2.5.11 "An end tag whose tag name is 'script'"
       
   178 // Script handling lives outside the tree builder to keep the each class simple.
       
   179 bool HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, int startLine)
       
   180 {
       
   181     ASSERT(scriptElement);
       
   182     // FIXME: If scripting is disabled, always just return true;
       
   183 
       
   184     // Try to execute the script given to us.
       
   185     runScript(scriptElement.get(), startLine);
       
   186 
       
   187     if (haveParsingBlockingScript()) {
       
   188         if (m_scriptNestingLevel)
       
   189             return false; // Block the parser.  Unwind to the outermost HTMLScriptRunner::execute before continuing parsing.
       
   190         if (!executeParsingBlockingScripts())
       
   191             return false; // We still have a parsing blocking script, block the parser.
       
   192     }
       
   193     return true; // Scripts executed as expected, continue parsing.
       
   194 }
       
   195 
       
   196 bool HTMLScriptRunner::haveParsingBlockingScript() const
       
   197 {
       
   198     return !!m_parsingBlockingScript.element;
       
   199 }
       
   200 
       
   201 bool HTMLScriptRunner::executeParsingBlockingScripts()
       
   202 {
       
   203     while (haveParsingBlockingScript()) {
       
   204         // We only really need to check once.
       
   205         if (!isPendingScriptReady(m_parsingBlockingScript))
       
   206             return false;
       
   207         executePendingScript();
       
   208     }
       
   209     return true;
       
   210 }
       
   211 
       
   212 bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript)
       
   213 {
       
   214     ASSERT(!m_scriptNestingLevel);
       
   215     ASSERT(haveParsingBlockingScript());
       
   216     ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript);
       
   217     ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded());
       
   218     return executeParsingBlockingScripts();
       
   219 }
       
   220 
       
   221 bool HTMLScriptRunner::executeScriptsWaitingForStylesheets()
       
   222 {
       
   223     // Callers should check hasScriptsWaitingForStylesheets() before calling
       
   224     // to prevent parser or script re-entry during </style> parsing.
       
   225     ASSERT(hasScriptsWaitingForStylesheets());
       
   226     ASSERT(!m_scriptNestingLevel);
       
   227     ASSERT(m_document->haveStylesheetsLoaded());
       
   228     return executeParsingBlockingScripts();
       
   229 }
       
   230 
       
   231 void HTMLScriptRunner::requestScript(Element* script)
       
   232 {
       
   233     ASSERT(!m_parsingBlockingScript.element);
       
   234     AtomicString srcValue = script->getAttribute(srcAttr);
       
   235     // Allow the host to disllow script loads (using the XSSAuditor, etc.)
       
   236     if (!m_host->shouldLoadExternalScriptFromSrc(srcValue))
       
   237         return;
       
   238     // FIXME: We need to resolve the url relative to the element.
       
   239     if (!script->dispatchBeforeLoadEvent(srcValue))
       
   240         return;
       
   241     m_parsingBlockingScript.element = script;
       
   242     // This should correctly return 0 for empty or invalid srcValues.
       
   243     CachedScript* cachedScript = m_document->docLoader()->requestScript(srcValue, toScriptElement(script)->scriptCharset());
       
   244     if (!cachedScript) {
       
   245         notImplemented(); // Dispatch error event.
       
   246         return;
       
   247     }
       
   248 
       
   249     m_parsingBlockingScript.setCachedScript(cachedScript);
       
   250 
       
   251     // We only care about a load callback if cachedScript is not already
       
   252     // in the cache.  Callers will attempt to run the m_parsingBlockingScript
       
   253     // if possible before returning control to the parser.
       
   254     if (!m_parsingBlockingScript.cachedScript()->isLoaded())
       
   255         watchForLoad(m_parsingBlockingScript);
       
   256 }
       
   257 
       
   258 // This method is meant to match the HTML5 definition of "running a script"
       
   259 // http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script
       
   260 void HTMLScriptRunner::runScript(Element* script, int startingLineNumber)
       
   261 {
       
   262     ASSERT(!haveParsingBlockingScript());
       
   263     {
       
   264         NestScript nestingLevel(m_scriptNestingLevel, m_host->inputStream());
       
   265 
       
   266         // Check script type and language, current code uses ScriptElement::shouldExecuteAsJavaScript(), but that may not be HTML5 compliant.
       
   267         notImplemented(); // event for support
       
   268 
       
   269         if (script->hasAttribute(srcAttr)) {
       
   270             // FIXME: Handle defer and async
       
   271             requestScript(script);
       
   272         } else {
       
   273             // FIXME: We do not block inline <script> tags on stylesheets to match the
       
   274             // old parser for now.  See https://bugs.webkit.org/show_bug.cgi?id=40047
       
   275             ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), startingLineNumber);
       
   276             executeScript(script, sourceCode);
       
   277         }
       
   278     }
       
   279 }
       
   280 
       
   281 HTMLScriptRunner::PendingScript::~PendingScript()
       
   282 {
       
   283     if (m_cachedScript)
       
   284         m_cachedScript->removeClient(this);
       
   285 }
       
   286 
       
   287 PassRefPtr<Element> HTMLScriptRunner::PendingScript::releaseElementAndClear()
       
   288 {
       
   289     setCachedScript(0);
       
   290     startingLineNumber = 0;
       
   291     m_watchingForLoad = false;
       
   292     return element.release();
       
   293 }
       
   294 
       
   295 void HTMLScriptRunner::PendingScript::setCachedScript(CachedScript* cachedScript)
       
   296 {
       
   297     if (m_cachedScript == cachedScript)
       
   298         return;
       
   299     if (m_cachedScript)
       
   300         m_cachedScript->removeClient(this);
       
   301     m_cachedScript = cachedScript;
       
   302     if (m_cachedScript)
       
   303         m_cachedScript->addClient(this);
       
   304 }
       
   305 
       
   306 CachedScript* HTMLScriptRunner::PendingScript::cachedScript() const
       
   307 {
       
   308     return m_cachedScript.get();
       
   309 }
       
   310 
       
   311 }