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