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