1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org) 5 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 6 * Copyright (C) 2008 Nikolas Zimmermann <zimmermann@kde.org> 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 */ 23 24#include "config.h" 25#include "ScriptElement.h" 26 27#include "CachedResourceLoader.h" 28#include "CachedResourceRequest.h" 29#include "CachedScript.h" 30#include "ContentSecurityPolicy.h" 31#include "CrossOriginAccessControl.h" 32#include "CurrentScriptIncrementer.h" 33#include "Event.h" 34#include "Frame.h" 35#include "FrameLoader.h" 36#include "HTMLNames.h" 37#include "HTMLParserIdioms.h" 38#include "IgnoreDestructiveWriteCountIncrementer.h" 39#include "MIMETypeRegistry.h" 40#include "Page.h" 41#include "SVGNames.h" 42#include "SVGScriptElement.h" 43#include "ScriptController.h" 44#include "ScriptRunner.h" 45#include "ScriptSourceCode.h" 46#include "ScriptableDocumentParser.h" 47#include "SecurityOrigin.h" 48#include "Settings.h" 49#include "TextNodeTraversal.h" 50#include <bindings/ScriptValue.h> 51#include <inspector/ScriptCallStack.h> 52#include <wtf/StdLibExtras.h> 53#include <wtf/text/StringBuilder.h> 54#include <wtf/text/StringHash.h> 55 56namespace WebCore { 57 58ScriptElement::ScriptElement(Element& element, bool parserInserted, bool alreadyStarted) 59 : m_element(element) 60 , m_cachedScript(0) 61 , m_startLineNumber(WTF::OrdinalNumber::beforeFirst()) 62 , m_parserInserted(parserInserted) 63 , m_isExternalScript(false) 64 , m_alreadyStarted(alreadyStarted) 65 , m_haveFiredLoad(false) 66 , m_willBeParserExecuted(false) 67 , m_readyToBeParserExecuted(false) 68 , m_willExecuteWhenDocumentFinishedParsing(false) 69 , m_forceAsync(!parserInserted) 70 , m_willExecuteInOrder(false) 71 , m_requestUsesAccessControl(false) 72{ 73 if (parserInserted && m_element.document().scriptableDocumentParser() && !m_element.document().isInDocumentWrite()) 74 m_startLineNumber = m_element.document().scriptableDocumentParser()->textPosition().m_line; 75} 76 77ScriptElement::~ScriptElement() 78{ 79 stopLoadRequest(); 80} 81 82void ScriptElement::insertedInto(ContainerNode& insertionPoint) 83{ 84 if (insertionPoint.inDocument() && !m_parserInserted) 85 prepareScript(); // FIXME: Provide a real starting line number here. 86} 87 88void ScriptElement::childrenChanged() 89{ 90 if (!m_parserInserted && m_element.inDocument()) 91 prepareScript(); // FIXME: Provide a real starting line number here. 92} 93 94void ScriptElement::handleSourceAttribute(const String& sourceUrl) 95{ 96 if (ignoresLoadRequest() || sourceUrl.isEmpty()) 97 return; 98 99 prepareScript(); // FIXME: Provide a real starting line number here. 100} 101 102void ScriptElement::handleAsyncAttribute() 103{ 104 m_forceAsync = false; 105} 106 107// Helper function 108static bool isLegacySupportedJavaScriptLanguage(const String& language) 109{ 110 // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3. 111 // Mozilla 1.8 and WinIE 7 both accept javascript and livescript. 112 // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't. 113 // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace. 114 // We want to accept all the values that either of these browsers accept, but not other values. 115 116 // FIXME: This function is not HTML5 compliant. These belong in the MIME registry as "text/javascript<version>" entries. 117 typedef HashSet<String, CaseFoldingHash> LanguageSet; 118 DEPRECATED_DEFINE_STATIC_LOCAL(LanguageSet, languages, ()); 119 if (languages.isEmpty()) { 120 languages.add("javascript"); 121 languages.add("javascript"); 122 languages.add("javascript1.0"); 123 languages.add("javascript1.1"); 124 languages.add("javascript1.2"); 125 languages.add("javascript1.3"); 126 languages.add("javascript1.4"); 127 languages.add("javascript1.5"); 128 languages.add("javascript1.6"); 129 languages.add("javascript1.7"); 130 languages.add("livescript"); 131 languages.add("ecmascript"); 132 languages.add("jscript"); 133 } 134 135 return languages.contains(language); 136} 137 138void ScriptElement::dispatchErrorEvent() 139{ 140 m_element.dispatchEvent(Event::create(eventNames().errorEvent, false, false)); 141} 142 143bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const 144{ 145 // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are: 146 // - Allowing type=javascript. type= should only support MIME types, such as text/javascript. 147 // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not. 148 149 String type = typeAttributeValue(); 150 String language = languageAttributeValue(); 151 if (type.isEmpty() && language.isEmpty()) 152 return true; // Assume text/javascript. 153 if (type.isEmpty()) { 154 type = "text/" + language.lower(); 155 if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySupportedJavaScriptLanguage(language)) 156 return true; 157 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type))) 158 return true; 159 return false; 160} 161 162// http://dev.w3.org/html5/spec/Overview.html#prepare-a-script 163bool ScriptElement::prepareScript(const TextPosition& scriptStartPosition, LegacyTypeSupport supportLegacyTypes) 164{ 165 if (m_alreadyStarted) 166 return false; 167 168 bool wasParserInserted; 169 if (m_parserInserted) { 170 wasParserInserted = true; 171 m_parserInserted = false; 172 } else 173 wasParserInserted = false; 174 175 if (wasParserInserted && !asyncAttributeValue()) 176 m_forceAsync = true; 177 178 // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes. 179 if (!hasSourceAttribute() && !m_element.firstChild()) 180 return false; 181 182 if (!m_element.inDocument()) 183 return false; 184 185 if (!isScriptTypeSupported(supportLegacyTypes)) 186 return false; 187 188 if (wasParserInserted) { 189 m_parserInserted = true; 190 m_forceAsync = false; 191 } 192 193 m_alreadyStarted = true; 194 195 // FIXME: If script is parser inserted, verify it's still in the original document. 196 Document& document = m_element.document(); 197 198 // FIXME: Eventually we'd like to evaluate scripts which are inserted into a 199 // viewless document but this'll do for now. 200 // See http://bugs.webkit.org/show_bug.cgi?id=5727 201 if (!document.frame()) 202 return false; 203 204 if (!document.frame()->script().canExecuteScripts(AboutToExecuteScript)) 205 return false; 206 207 if (!isScriptForEventSupported()) 208 return false; 209 210 if (!charsetAttributeValue().isEmpty()) 211 m_characterEncoding = charsetAttributeValue(); 212 else 213 m_characterEncoding = document.charset(); 214 215 if (hasSourceAttribute()) 216 if (!requestScript(sourceAttributeValue())) 217 return false; 218 219 if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) { 220 m_willExecuteWhenDocumentFinishedParsing = true; 221 m_willBeParserExecuted = true; 222 } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue()) 223 m_willBeParserExecuted = true; 224 else if (!hasSourceAttribute() && m_parserInserted && !document.haveStylesheetsLoaded()) { 225 m_willBeParserExecuted = true; 226 m_readyToBeParserExecuted = true; 227 } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) { 228 m_willExecuteInOrder = true; 229 document.scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION); 230 m_cachedScript->addClient(this); 231 } else if (hasSourceAttribute()) { 232 m_element.document().scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION); 233 m_cachedScript->addClient(this); 234 } else { 235 // Reset line numbering for nested writes. 236 TextPosition position = document.isInDocumentWrite() ? TextPosition() : scriptStartPosition; 237 executeScript(ScriptSourceCode(scriptContent(), document.url(), position)); 238 } 239 240 return true; 241} 242 243bool ScriptElement::requestScript(const String& sourceUrl) 244{ 245 Ref<Document> originalDocument(m_element.document()); 246 if (!m_element.dispatchBeforeLoadEvent(sourceUrl)) 247 return false; 248 if (!m_element.inDocument() || &m_element.document() != &originalDocument.get()) 249 return false; 250 251 ASSERT(!m_cachedScript); 252 if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl).isEmpty()) { 253 CachedResourceRequest request(ResourceRequest(m_element.document().completeURL(sourceUrl))); 254 255 String crossOriginMode = m_element.fastGetAttribute(HTMLNames::crossoriginAttr); 256 if (!crossOriginMode.isNull()) { 257 m_requestUsesAccessControl = true; 258 StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials; 259 updateRequestForAccessControl(request.mutableResourceRequest(), m_element.document().securityOrigin(), allowCredentials); 260 } 261 request.setCharset(scriptCharset()); 262 request.setInitiator(&element()); 263 264 m_cachedScript = m_element.document().cachedResourceLoader()->requestScript(request); 265 m_isExternalScript = true; 266 } 267 268 if (m_cachedScript) { 269 return true; 270 } 271 272 dispatchErrorEvent(); 273 return false; 274} 275 276void ScriptElement::executeScript(const ScriptSourceCode& sourceCode) 277{ 278 ASSERT(m_alreadyStarted); 279 280 if (sourceCode.isEmpty()) 281 return; 282 283 if (!m_isExternalScript && !m_element.document().contentSecurityPolicy()->allowInlineScript(m_element.document().url(), m_startLineNumber)) 284 return; 285 286#if ENABLE(NOSNIFF) 287 if (m_isExternalScript && m_cachedScript && !m_cachedScript->mimeTypeAllowedByNosniff()) { 288 m_element.document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Refused to execute script from '" + m_cachedScript->url().stringCenterEllipsizedToLength() + "' because its MIME type ('" + m_cachedScript->mimeType() + "') is not executable, and strict MIME type checking is enabled."); 289 return; 290 } 291#endif 292 293 Ref<Document> document(m_element.document()); 294 if (Frame* frame = document->frame()) { 295 IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? &document.get() : 0); 296 CurrentScriptIncrementer currentScriptIncrementer(&document.get(), &m_element); 297 298 // Create a script from the script element node, using the script 299 // block's source and the script block's type. 300 // Note: This is where the script is compiled and actually executed. 301 frame->script().evaluate(sourceCode); 302 } 303} 304 305void ScriptElement::stopLoadRequest() 306{ 307 if (m_cachedScript) { 308 if (!m_willBeParserExecuted) 309 m_cachedScript->removeClient(this); 310 m_cachedScript = 0; 311 } 312} 313 314void ScriptElement::execute(CachedScript* cachedScript) 315{ 316 ASSERT(!m_willBeParserExecuted); 317 ASSERT(cachedScript); 318 if (cachedScript->errorOccurred()) 319 dispatchErrorEvent(); 320 else if (!cachedScript->wasCanceled()) { 321 executeScript(ScriptSourceCode(cachedScript)); 322 dispatchLoadEvent(); 323 } 324 cachedScript->removeClient(this); 325} 326 327void ScriptElement::notifyFinished(CachedResource* resource) 328{ 329 ASSERT(!m_willBeParserExecuted); 330 331 // CachedResource possibly invokes this notifyFinished() more than 332 // once because ScriptElement doesn't unsubscribe itself from 333 // CachedResource here and does it in execute() instead. 334 // We use m_cachedScript to check if this function is already called. 335 ASSERT_UNUSED(resource, resource == m_cachedScript); 336 if (!m_cachedScript) 337 return; 338 339 if (m_requestUsesAccessControl 340 && !m_element.document().securityOrigin()->canRequest(m_cachedScript->response().url()) 341 && !m_cachedScript->passesAccessControlCheck(m_element.document().securityOrigin())) { 342 343 dispatchErrorEvent(); 344 DEPRECATED_DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin script load denied by Cross-Origin Resource Sharing policy."))); 345 m_element.document().addConsoleMessage(MessageSource::JS, MessageLevel::Error, consoleMessage); 346 return; 347 } 348 349 if (m_willExecuteInOrder) 350 m_element.document().scriptRunner()->notifyScriptReady(this, ScriptRunner::IN_ORDER_EXECUTION); 351 else 352 m_element.document().scriptRunner()->notifyScriptReady(this, ScriptRunner::ASYNC_EXECUTION); 353 354 m_cachedScript = 0; 355} 356 357bool ScriptElement::ignoresLoadRequest() const 358{ 359 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element.inDocument(); 360} 361 362bool ScriptElement::isScriptForEventSupported() const 363{ 364 String eventAttribute = eventAttributeValue(); 365 String forAttribute = forAttributeValue(); 366 if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) { 367 forAttribute = forAttribute.stripWhiteSpace(); 368 if (!equalIgnoringCase(forAttribute, "window")) 369 return false; 370 371 eventAttribute = eventAttribute.stripWhiteSpace(); 372 if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()")) 373 return false; 374 } 375 return true; 376} 377 378String ScriptElement::scriptContent() const 379{ 380 return TextNodeTraversal::contentsAsString(&m_element); 381} 382 383ScriptElement* toScriptElementIfPossible(Element* element) 384{ 385 if (isHTMLScriptElement(element)) 386 return toHTMLScriptElement(element); 387 388 if (isSVGScriptElement(element)) 389 return toSVGScriptElement(element); 390 391 return 0; 392} 393 394} 395