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