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