| OLD | NEW |
| (Empty) |
| 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 reserv
ed. | |
| 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 "core/dom/ScriptElement.h" | |
| 26 | |
| 27 #include "HTMLNames.h" | |
| 28 #include "SVGNames.h" | |
| 29 #include "bindings/v8/ScriptController.h" | |
| 30 #include "bindings/v8/ScriptSourceCode.h" | |
| 31 #include "core/dom/Document.h" | |
| 32 #include "core/dom/Event.h" | |
| 33 #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h" | |
| 34 #include "core/dom/ScriptRunner.h" | |
| 35 #include "core/dom/ScriptableDocumentParser.h" | |
| 36 #include "core/dom/Text.h" | |
| 37 #include "core/html/HTMLScriptElement.h" | |
| 38 #include "core/html/parser/HTMLParserIdioms.h" | |
| 39 #include "core/loader/cache/CachedResourceLoader.h" | |
| 40 #include "core/loader/cache/CachedResourceRequest.h" | |
| 41 #include "core/loader/cache/CachedScript.h" | |
| 42 #include "core/page/ContentSecurityPolicy.h" | |
| 43 #include "core/page/Frame.h" | |
| 44 #include "core/platform/MIMETypeRegistry.h" | |
| 45 #include "core/svg/SVGScriptElement.h" | |
| 46 #include "weborigin/SecurityOrigin.h" | |
| 47 #include "wtf/StdLibExtras.h" | |
| 48 #include "wtf/text/StringBuilder.h" | |
| 49 #include "wtf/text/StringHash.h" | |
| 50 #include "wtf/text/TextPosition.h" | |
| 51 | |
| 52 namespace WebCore { | |
| 53 | |
| 54 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool already
Started) | |
| 55 : m_element(element) | |
| 56 , m_cachedScript(0) | |
| 57 , m_startLineNumber(WTF::OrdinalNumber::beforeFirst()) | |
| 58 , m_parserInserted(parserInserted) | |
| 59 , m_isExternalScript(false) | |
| 60 , m_alreadyStarted(alreadyStarted) | |
| 61 , m_haveFiredLoad(false) | |
| 62 , m_willBeParserExecuted(false) | |
| 63 , m_readyToBeParserExecuted(false) | |
| 64 , m_willExecuteWhenDocumentFinishedParsing(false) | |
| 65 , m_forceAsync(!parserInserted) | |
| 66 , m_willExecuteInOrder(false) | |
| 67 { | |
| 68 ASSERT(m_element); | |
| 69 if (parserInserted && element->document()->scriptableDocumentParser() && !el
ement->document()->isInDocumentWrite()) | |
| 70 m_startLineNumber = element->document()->scriptableDocumentParser()->lin
eNumber(); | |
| 71 } | |
| 72 | |
| 73 ScriptElement::~ScriptElement() | |
| 74 { | |
| 75 stopLoadRequest(); | |
| 76 } | |
| 77 | |
| 78 void ScriptElement::insertedInto(ContainerNode* insertionPoint) | |
| 79 { | |
| 80 if (insertionPoint->inDocument() && !m_parserInserted) | |
| 81 prepareScript(); // FIXME: Provide a real starting line number here. | |
| 82 } | |
| 83 | |
| 84 void ScriptElement::childrenChanged() | |
| 85 { | |
| 86 if (!m_parserInserted && m_element->inDocument()) | |
| 87 prepareScript(); // FIXME: Provide a real starting line number here. | |
| 88 } | |
| 89 | |
| 90 void ScriptElement::handleSourceAttribute(const String& sourceUrl) | |
| 91 { | |
| 92 if (ignoresLoadRequest() || sourceUrl.isEmpty()) | |
| 93 return; | |
| 94 | |
| 95 prepareScript(); // FIXME: Provide a real starting line number here. | |
| 96 } | |
| 97 | |
| 98 void ScriptElement::handleAsyncAttribute() | |
| 99 { | |
| 100 m_forceAsync = false; | |
| 101 } | |
| 102 | |
| 103 // Helper function | |
| 104 static bool isLegacySupportedJavaScriptLanguage(const String& language) | |
| 105 { | |
| 106 // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts on
ly javascript1.1 - javascript1.3. | |
| 107 // Mozilla 1.8 and WinIE 7 both accept javascript and livescript. | |
| 108 // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't. | |
| 109 // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace. | |
| 110 // We want to accept all the values that either of these browsers accept, bu
t not other values. | |
| 111 | |
| 112 // FIXME: This function is not HTML5 compliant. These belong in the MIME reg
istry as "text/javascript<version>" entries. | |
| 113 typedef HashSet<String, CaseFoldingHash> LanguageSet; | |
| 114 DEFINE_STATIC_LOCAL(LanguageSet, languages, ()); | |
| 115 if (languages.isEmpty()) { | |
| 116 languages.add("javascript"); | |
| 117 languages.add("javascript"); | |
| 118 languages.add("javascript1.0"); | |
| 119 languages.add("javascript1.1"); | |
| 120 languages.add("javascript1.2"); | |
| 121 languages.add("javascript1.3"); | |
| 122 languages.add("javascript1.4"); | |
| 123 languages.add("javascript1.5"); | |
| 124 languages.add("javascript1.6"); | |
| 125 languages.add("javascript1.7"); | |
| 126 languages.add("livescript"); | |
| 127 languages.add("ecmascript"); | |
| 128 languages.add("jscript"); | |
| 129 } | |
| 130 | |
| 131 return languages.contains(language); | |
| 132 } | |
| 133 | |
| 134 void ScriptElement::dispatchErrorEvent() | |
| 135 { | |
| 136 m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false
)); | |
| 137 } | |
| 138 | |
| 139 void ScriptElement::dispatchLoadEvent() | |
| 140 { | |
| 141 ASSERT(!haveFiredLoadEvent()); | |
| 142 if (ScriptElementClient* client = this->client()) | |
| 143 client->dispatchLoadEvent(); | |
| 144 setHaveFiredLoadEvent(true); | |
| 145 } | |
| 146 | |
| 147 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes)
const | |
| 148 { | |
| 149 // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is us
ed here to maintain backwards compatibility with existing layout tests. The spec
ific violations are: | |
| 150 // - Allowing type=javascript. type= should only support MIME types, such as
text/javascript. | |
| 151 // - Allowing a different set of languages for language= and type=. language
= supports Javascript 1.1 and 1.4-1.6, but type= does not. | |
| 152 | |
| 153 String type = client()->typeAttributeValue(); | |
| 154 String language = client()->languageAttributeValue(); | |
| 155 if (type.isEmpty() && language.isEmpty()) | |
| 156 return true; // Assume text/javascript. | |
| 157 if (type.isEmpty()) { | |
| 158 type = "text/" + language.lower(); | |
| 159 if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySup
portedJavaScriptLanguage(language)) | |
| 160 return true; | |
| 161 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSp
ace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLeg
acySupportedJavaScriptLanguage(type))) | |
| 162 return true; | |
| 163 return false; | |
| 164 } | |
| 165 | |
| 166 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script | |
| 167 bool ScriptElement::prepareScript(const TextPosition& scriptStartPosition, Legac
yTypeSupport supportLegacyTypes) | |
| 168 { | |
| 169 if (m_alreadyStarted) | |
| 170 return false; | |
| 171 | |
| 172 ScriptElementClient* client = this->client(); | |
| 173 | |
| 174 bool wasParserInserted; | |
| 175 if (m_parserInserted) { | |
| 176 wasParserInserted = true; | |
| 177 m_parserInserted = false; | |
| 178 } else | |
| 179 wasParserInserted = false; | |
| 180 | |
| 181 if (wasParserInserted && !client->asyncAttributeValue()) | |
| 182 m_forceAsync = true; | |
| 183 | |
| 184 // FIXME: HTML5 spec says we should check that all children are either comme
nts or empty text nodes. | |
| 185 if (!client->hasSourceAttribute() && !m_element->firstChild()) | |
| 186 return false; | |
| 187 | |
| 188 if (!m_element->inDocument()) | |
| 189 return false; | |
| 190 | |
| 191 if (!isScriptTypeSupported(supportLegacyTypes)) | |
| 192 return false; | |
| 193 | |
| 194 if (wasParserInserted) { | |
| 195 m_parserInserted = true; | |
| 196 m_forceAsync = false; | |
| 197 } | |
| 198 | |
| 199 m_alreadyStarted = true; | |
| 200 | |
| 201 // FIXME: If script is parser inserted, verify it's still in the original do
cument. | |
| 202 Document* document = m_element->document(); | |
| 203 | |
| 204 // FIXME: Eventually we'd like to evaluate scripts which are inserted into a | |
| 205 // viewless document but this'll do for now. | |
| 206 // See http://bugs.webkit.org/show_bug.cgi?id=5727 | |
| 207 if (!document->frame()) | |
| 208 return false; | |
| 209 | |
| 210 if (!document->frame()->script()->canExecuteScripts(AboutToExecuteScript)) | |
| 211 return false; | |
| 212 | |
| 213 if (!isScriptForEventSupported()) | |
| 214 return false; | |
| 215 | |
| 216 if (!client->charsetAttributeValue().isEmpty()) | |
| 217 m_characterEncoding = client->charsetAttributeValue(); | |
| 218 else | |
| 219 m_characterEncoding = document->charset(); | |
| 220 | |
| 221 if (client->hasSourceAttribute()) | |
| 222 if (!requestScript(client->sourceAttributeValue())) | |
| 223 return false; | |
| 224 | |
| 225 if (client->hasSourceAttribute() && client->deferAttributeValue() && m_parse
rInserted && !client->asyncAttributeValue()) { | |
| 226 m_willExecuteWhenDocumentFinishedParsing = true; | |
| 227 m_willBeParserExecuted = true; | |
| 228 } else if (client->hasSourceAttribute() && m_parserInserted && !client->asyn
cAttributeValue()) | |
| 229 m_willBeParserExecuted = true; | |
| 230 else if (!client->hasSourceAttribute() && m_parserInserted && !document->hav
eStylesheetsAndImportsLoaded()) { | |
| 231 m_willBeParserExecuted = true; | |
| 232 m_readyToBeParserExecuted = true; | |
| 233 } else if (client->hasSourceAttribute() && !client->asyncAttributeValue() &&
!m_forceAsync) { | |
| 234 m_willExecuteInOrder = true; | |
| 235 document->scriptRunner()->queueScriptForExecution(this, m_cachedScript,
ScriptRunner::IN_ORDER_EXECUTION); | |
| 236 m_cachedScript->addClient(this); | |
| 237 } else if (client->hasSourceAttribute()) { | |
| 238 document->scriptRunner()->queueScriptForExecution(this, m_cachedScript,
ScriptRunner::ASYNC_EXECUTION); | |
| 239 m_cachedScript->addClient(this); | |
| 240 } else { | |
| 241 // Reset line numbering for nested writes. | |
| 242 TextPosition position = document->isInDocumentWrite() ? TextPosition() :
scriptStartPosition; | |
| 243 KURL scriptURL = (!document->isInDocumentWrite() && m_parserInserted) ?
document->url() : KURL(); | |
| 244 executeScript(ScriptSourceCode(scriptContent(), scriptURL, position)); | |
| 245 } | |
| 246 | |
| 247 return true; | |
| 248 } | |
| 249 | |
| 250 bool ScriptElement::requestScript(const String& sourceUrl) | |
| 251 { | |
| 252 ASSERT(m_element); | |
| 253 | |
| 254 RefPtr<Document> originalDocument = m_element->document(); | |
| 255 if (!m_element->dispatchBeforeLoadEvent(sourceUrl)) | |
| 256 return false; | |
| 257 if (!m_element->inDocument() || m_element->document() != originalDocument) | |
| 258 return false; | |
| 259 | |
| 260 ASSERT(!m_cachedScript); | |
| 261 if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl).isEmpty()) { | |
| 262 CachedResourceRequest request(ResourceRequest(m_element->document()->com
pleteURL(sourceUrl)), m_element->localName()); | |
| 263 | |
| 264 String crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossori
ginAttr); | |
| 265 if (!crossOriginMode.isNull()) { | |
| 266 StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMo
de, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials; | |
| 267 request.setPotentiallyCrossOriginEnabled(m_element->document()->secu
rityOrigin(), allowCredentials); | |
| 268 } | |
| 269 request.setCharset(scriptCharset()); | |
| 270 | |
| 271 bool isValidScriptNonce = m_element->document()->contentSecurityPolicy()
->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr)); | |
| 272 if (isValidScriptNonce) | |
| 273 request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy); | |
| 274 | |
| 275 m_cachedScript = m_element->document()->cachedResourceLoader()->requestS
cript(request); | |
| 276 m_isExternalScript = true; | |
| 277 } | |
| 278 | |
| 279 if (m_cachedScript) { | |
| 280 return true; | |
| 281 } | |
| 282 | |
| 283 dispatchErrorEvent(); | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 bool isHTMLScriptElement(Element* element) | |
| 288 { | |
| 289 return element->hasTagName(HTMLNames::scriptTag); | |
| 290 } | |
| 291 | |
| 292 bool isSVGScriptElement(Element* element) | |
| 293 { | |
| 294 return element->hasTagName(SVGNames::scriptTag); | |
| 295 } | |
| 296 | |
| 297 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode) | |
| 298 { | |
| 299 ASSERT(m_alreadyStarted); | |
| 300 | |
| 301 if (sourceCode.isEmpty()) | |
| 302 return; | |
| 303 | |
| 304 RefPtr<Document> document = m_element->document(); | |
| 305 Frame* frame = document->frame(); | |
| 306 | |
| 307 bool shouldBypassMainWorldContentSecurityPolicy = (frame && frame->script()-
>shouldBypassMainWorldContentSecurityPolicy()) || document->contentSecurityPolic
y()->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr)); | |
| 308 | |
| 309 if (!m_isExternalScript && (!shouldBypassMainWorldContentSecurityPolicy && !
document->contentSecurityPolicy()->allowInlineScript(document->url(), m_startLin
eNumber))) | |
| 310 return; | |
| 311 | |
| 312 if (m_isExternalScript && m_cachedScript && !m_cachedScript->mimeTypeAllowed
ByNosniff()) { | |
| 313 document->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "R
efused to execute script from '" + m_cachedScript->url().elidedString() + "' bec
ause its MIME type ('" + m_cachedScript->mimeType() + "') is not executable, and
strict MIME type checking is enabled."); | |
| 314 return; | |
| 315 } | |
| 316 | |
| 317 if (frame) { | |
| 318 { | |
| 319 IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountI
ncrementer(m_isExternalScript ? document.get() : 0); | |
| 320 | |
| 321 if (isHTMLScriptElement(m_element)) | |
| 322 document->pushCurrentScript(toHTMLScriptElement(m_element)); | |
| 323 | |
| 324 // Create a script from the script element node, using the script | |
| 325 // block's source and the script block's type. | |
| 326 // Note: This is where the script is compiled and actually executed. | |
| 327 frame->script()->executeScriptInMainWorld(sourceCode); | |
| 328 | |
| 329 if (isHTMLScriptElement(m_element)) { | |
| 330 ASSERT(document->currentScript() == m_element); | |
| 331 document->popCurrentScript(); | |
| 332 } | |
| 333 } | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 void ScriptElement::stopLoadRequest() | |
| 338 { | |
| 339 if (m_cachedScript) { | |
| 340 if (!m_willBeParserExecuted) | |
| 341 m_cachedScript->removeClient(this); | |
| 342 m_cachedScript = 0; | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 void ScriptElement::execute(CachedScript* cachedScript) | |
| 347 { | |
| 348 ASSERT(!m_willBeParserExecuted); | |
| 349 ASSERT(cachedScript); | |
| 350 if (cachedScript->errorOccurred()) | |
| 351 dispatchErrorEvent(); | |
| 352 else if (!cachedScript->wasCanceled()) { | |
| 353 executeScript(ScriptSourceCode(cachedScript)); | |
| 354 dispatchLoadEvent(); | |
| 355 } | |
| 356 cachedScript->removeClient(this); | |
| 357 } | |
| 358 | |
| 359 void ScriptElement::notifyFinished(CachedResource* resource) | |
| 360 { | |
| 361 ASSERT(!m_willBeParserExecuted); | |
| 362 | |
| 363 // CachedResource possibly invokes this notifyFinished() more than | |
| 364 // once because ScriptElement doesn't unsubscribe itself from | |
| 365 // CachedResource here and does it in execute() instead. | |
| 366 // We use m_cachedScript to check if this function is already called. | |
| 367 ASSERT_UNUSED(resource, resource == m_cachedScript); | |
| 368 if (!m_cachedScript) | |
| 369 return; | |
| 370 if (!m_element->document()->cachedResourceLoader()->canAccess(m_cachedScript
.get())) { | |
| 371 dispatchErrorEvent(); | |
| 372 return; | |
| 373 } | |
| 374 | |
| 375 if (m_willExecuteInOrder) | |
| 376 m_element->document()->scriptRunner()->notifyScriptReady(this, ScriptRun
ner::IN_ORDER_EXECUTION); | |
| 377 else | |
| 378 m_element->document()->scriptRunner()->notifyScriptReady(this, ScriptRun
ner::ASYNC_EXECUTION); | |
| 379 | |
| 380 m_cachedScript = 0; | |
| 381 } | |
| 382 | |
| 383 bool ScriptElement::ignoresLoadRequest() const | |
| 384 { | |
| 385 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !elemen
t() || !element()->inDocument(); | |
| 386 } | |
| 387 | |
| 388 bool ScriptElement::isScriptForEventSupported() const | |
| 389 { | |
| 390 String eventAttribute = client()->eventAttributeValue(); | |
| 391 String forAttribute = client()->forAttributeValue(); | |
| 392 if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) { | |
| 393 forAttribute = forAttribute.stripWhiteSpace(); | |
| 394 if (!equalIgnoringCase(forAttribute, "window")) | |
| 395 return false; | |
| 396 | |
| 397 eventAttribute = eventAttribute.stripWhiteSpace(); | |
| 398 if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(e
ventAttribute, "onload()")) | |
| 399 return false; | |
| 400 } | |
| 401 return true; | |
| 402 } | |
| 403 | |
| 404 String ScriptElement::scriptContent() const | |
| 405 { | |
| 406 return m_element->textFromChildren(); | |
| 407 } | |
| 408 | |
| 409 ScriptElementClient* ScriptElement::client() const | |
| 410 { | |
| 411 if (isHTMLScriptElement(m_element)) | |
| 412 return toHTMLScriptElement(m_element); | |
| 413 | |
| 414 if (isSVGScriptElement(m_element)) | |
| 415 return toSVGScriptElement(m_element); | |
| 416 | |
| 417 ASSERT_NOT_REACHED(); | |
| 418 return 0; | |
| 419 } | |
| 420 | |
| 421 ScriptElement* toScriptElementIfPossible(Element* element) | |
| 422 { | |
| 423 if (isHTMLScriptElement(element)) | |
| 424 return toHTMLScriptElement(element)->scriptElement(); | |
| 425 | |
| 426 if (isSVGScriptElement(element)) | |
| 427 return toSVGScriptElement(element)->scriptElement(); | |
| 428 | |
| 429 return 0; | |
| 430 } | |
| 431 | |
| 432 } | |
| OLD | NEW |