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/html/parser/HTMLInputStream.h" 37 #include "core/html/parser/HTMLScriptRunnerHost.h" 38 #include "core/html/parser/NestingLevelIncrementer.h" 39 #include "core/frame/Frame.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 // 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(EventTypeNames::load); 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 if (scriptLoader->executePotentiallyCrossOriginScript(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