HTMLScriptRunner.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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 44namespace WebCore { 45 46using namespace HTMLNames; 47 48HTMLScriptRunner::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 57HTMLScriptRunner::~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 70void HTMLScriptRunner::detach() 71{ 72 m_document = 0; 73} 74 75static 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 84inline PassRefPtr<Event> createScriptLoadEvent() 85{ 86 return Event::create(eventNames().loadEvent, false, false); 87} 88 89inline PassRefPtr<Event> createScriptErrorEvent() 90{ 91 return Event::create(eventNames().errorEvent, true, false); 92} 93 94ScriptSourceCode 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 105bool 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 115void 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 126void 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 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. 167bool HTMLScriptRunner::execute(PassRefPtr<Element> scriptElement, const TextPosition1& scriptStartPosition) 168{ 169 ASSERT(scriptElement); 170 // FIXME: If scripting is disabled, always just return true; 171 172 // Try to execute the script given to us. 173 runScript(scriptElement.get(), scriptStartPosition); 174 175 if (haveParsingBlockingScript()) { 176 if (m_scriptNestingLevel) 177 return false; // Block the parser. Unwind to the outermost HTMLScriptRunner::execute before continuing parsing. 178 if (!executeParsingBlockingScripts()) 179 return false; // We still have a parsing blocking script, block the parser. 180 } 181 return true; // Scripts executed as expected, continue parsing. 182} 183 184bool HTMLScriptRunner::haveParsingBlockingScript() const 185{ 186 return !!m_parsingBlockingScript.element(); 187} 188 189bool HTMLScriptRunner::executeParsingBlockingScripts() 190{ 191 while (haveParsingBlockingScript()) { 192 // We only really need to check once. 193 if (!isPendingScriptReady(m_parsingBlockingScript)) 194 return false; 195 executeParsingBlockingScript(); 196 } 197 return true; 198} 199 200bool HTMLScriptRunner::executeScriptsWaitingForLoad(CachedResource* cachedScript) 201{ 202 ASSERT(!m_scriptNestingLevel); 203 ASSERT(haveParsingBlockingScript()); 204 ASSERT_UNUSED(cachedScript, m_parsingBlockingScript.cachedScript() == cachedScript); 205 ASSERT(m_parsingBlockingScript.cachedScript()->isLoaded()); 206 return executeParsingBlockingScripts(); 207} 208 209bool HTMLScriptRunner::executeScriptsWaitingForStylesheets() 210{ 211 ASSERT(m_document); 212 // Callers should check hasScriptsWaitingForStylesheets() before calling 213 // to prevent parser or script re-entry during </style> parsing. 214 ASSERT(hasScriptsWaitingForStylesheets()); 215 ASSERT(!m_scriptNestingLevel); 216 ASSERT(m_document->haveStylesheetsLoaded()); 217 return executeParsingBlockingScripts(); 218} 219 220bool HTMLScriptRunner::executeScriptsWaitingForParsing() 221{ 222 while (!m_scriptsToExecuteAfterParsing.isEmpty()) { 223 ASSERT(!m_scriptNestingLevel); 224 ASSERT(!haveParsingBlockingScript()); 225 ASSERT(m_scriptsToExecuteAfterParsing.first().cachedScript()); 226 if (!m_scriptsToExecuteAfterParsing.first().cachedScript()->isLoaded()) { 227 watchForLoad(m_scriptsToExecuteAfterParsing.first()); 228 return false; 229 } 230 PendingScript first = m_scriptsToExecuteAfterParsing.takeFirst(); 231 executePendingScriptAndDispatchEvent(first); 232 if (!m_document) 233 return false; 234 } 235 return true; 236} 237 238void HTMLScriptRunner::requestParsingBlockingScript(Element* element) 239{ 240 if (!requestPendingScript(m_parsingBlockingScript, element)) 241 return; 242 243 ASSERT(m_parsingBlockingScript.cachedScript()); 244 245 // We only care about a load callback if cachedScript is not already 246 // in the cache. Callers will attempt to run the m_parsingBlockingScript 247 // if possible before returning control to the parser. 248 if (!m_parsingBlockingScript.cachedScript()->isLoaded()) 249 watchForLoad(m_parsingBlockingScript); 250} 251 252void HTMLScriptRunner::requestDeferredScript(Element* element) 253{ 254 PendingScript pendingScript; 255 if (!requestPendingScript(pendingScript, element)) 256 return; 257 258 ASSERT(pendingScript.cachedScript()); 259 m_scriptsToExecuteAfterParsing.append(pendingScript); 260} 261 262bool HTMLScriptRunner::requestPendingScript(PendingScript& pendingScript, Element* script) const 263{ 264 ASSERT(!pendingScript.element()); 265 const AtomicString& srcValue = script->getAttribute(srcAttr); 266 // Allow the host to disllow script loads (using the XSSAuditor, etc.) 267 // FIXME: this check should be performed on the final URL in a redirect chain. 268 if (!m_host->shouldLoadExternalScriptFromSrc(srcValue)) 269 return false; 270 // FIXME: We need to resolve the url relative to the element. 271 if (!script->dispatchBeforeLoadEvent(srcValue)) 272 return false; 273 pendingScript.setElement(script); 274 // This should correctly return 0 for empty or invalid srcValues. 275 CachedScript* cachedScript = m_document->cachedResourceLoader()->requestScript(srcValue, toScriptElement(script)->scriptCharset()); 276 if (!cachedScript) { 277 notImplemented(); // Dispatch error event. 278 return false; 279 } 280 pendingScript.setCachedScript(cachedScript); 281 return true; 282} 283 284// This method is meant to match the HTML5 definition of "running a script" 285// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#running-a-script 286void HTMLScriptRunner::runScript(Element* script, const TextPosition1& scriptStartPosition) 287{ 288 ASSERT(m_document); 289 ASSERT(!haveParsingBlockingScript()); 290 { 291 InsertionPointRecord insertionPointRecord(m_host->inputStream()); 292 NestingLevelIncrementer nestingLevelIncrementer(m_scriptNestingLevel); 293 294 ScriptElement* scriptElement = toScriptElement(script); 295 ASSERT(scriptElement); 296 if (!scriptElement->shouldExecuteAsJavaScript()) 297 return; 298 299 if (script->hasAttribute(srcAttr)) { 300 if (script->hasAttribute(asyncAttr)) // Async takes precendence over defer. 301 return; // Asynchronous scripts handle themselves. 302 303 if (script->hasAttribute(deferAttr)) 304 requestDeferredScript(script); 305 else 306 requestParsingBlockingScript(script); 307 } else if (!m_document->haveStylesheetsLoaded() && m_scriptNestingLevel == 1) { 308 // Block inline script execution on stylesheet load, unless we are in document.write(). 309 // The latter case can only happen if a script both triggers a stylesheet load 310 // and writes an inline script. Since write is blocking we have to execute the 311 // written script immediately, ignoring the pending sheets. 312 m_parsingBlockingScript.setElement(script); 313 m_parsingBlockingScript.setStartingPosition(scriptStartPosition); 314 } else { 315 ASSERT(isExecutingScript()); 316 ScriptSourceCode sourceCode(script->textContent(), documentURLForScriptExecution(m_document), scriptStartPosition); 317 scriptElement->executeScript(sourceCode); 318 } 319 } 320} 321 322} 323