1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 6 * Copyright (C) 2008 Nikolas Zimmermann <zimmermann (at) 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 "CachedScript.h" 28 #include "CachedResourceLoader.h" 29 #include "ContentSecurityPolicy.h" 30 #include "Document.h" 31 #include "DocumentParser.h" 32 #include "Frame.h" 33 #include "FrameLoader.h" 34 #include "HTMLNames.h" 35 #include "HTMLScriptElement.h" 36 #include "IgnoreDestructiveWriteCountIncrementer.h" 37 #include "MIMETypeRegistry.h" 38 #include "Page.h" 39 #include "ScriptController.h" 40 #include "ScriptRunner.h" 41 #include "ScriptSourceCode.h" 42 #include "ScriptValue.h" 43 #include "Settings.h" 44 #include "Text.h" 45 #include <wtf/StdLibExtras.h> 46 #include <wtf/text/StringHash.h> 47 48 #if ENABLE(SVG) 49 #include "SVGNames.h" 50 #include "SVGScriptElement.h" 51 #endif 52 53 namespace WebCore { 54 55 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool alreadyStarted) 56 : m_element(element) 57 , m_cachedScript(0) 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 } 70 71 ScriptElement::~ScriptElement() 72 { 73 stopLoadRequest(); 74 } 75 76 void ScriptElement::insertedIntoDocument() 77 { 78 if (!m_parserInserted) 79 prepareScript(); // FIXME: Provide a real starting line number here. 80 } 81 82 void ScriptElement::removedFromDocument() 83 { 84 // Eventually stop loading any not-yet-finished content 85 stopLoadRequest(); 86 } 87 88 void ScriptElement::childrenChanged() 89 { 90 if (!m_parserInserted && m_element->inDocument()) 91 prepareScript(); // FIXME: Provide a real starting line number here. 92 } 93 94 void 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 102 void ScriptElement::handleAsyncAttribute() 103 { 104 m_forceAsync = false; 105 } 106 107 // Helper function 108 static 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 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 138 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const 139 { 140 // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are: 141 // - Allowing type=javascript. type= should only support MIME types, such as text/javascript. 142 // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not. 143 144 String type = typeAttributeValue(); 145 String language = languageAttributeValue(); 146 if (type.isEmpty() && language.isEmpty()) 147 return true; // Assume text/javascript. 148 if (type.isEmpty()) { 149 type = "text/" + language.lower(); 150 if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySupportedJavaScriptLanguage(language)) 151 return true; 152 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type))) 153 return true; 154 return false; 155 } 156 157 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script 158 bool ScriptElement::prepareScript(const TextPosition1& scriptStartPosition, LegacyTypeSupport supportLegacyTypes) 159 { 160 if (m_alreadyStarted) 161 return false; 162 163 bool wasParserInserted; 164 if (m_parserInserted) { 165 wasParserInserted = true; 166 m_parserInserted = false; 167 } else 168 wasParserInserted = false; 169 170 if (wasParserInserted && !asyncAttributeValue()) 171 m_forceAsync = true; 172 173 // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes. 174 if (!hasSourceAttribute() && !m_element->firstChild()) 175 return false; 176 177 if (!m_element->inDocument()) 178 return false; 179 180 if (!isScriptTypeSupported(supportLegacyTypes)) 181 return false; 182 183 if (wasParserInserted) { 184 m_parserInserted = true; 185 m_forceAsync = false; 186 } 187 188 m_alreadyStarted = true; 189 190 // FIXME: If script is parser inserted, verify it's still in the original document. 191 192 // FIXME: Eventually we'd like to evaluate scripts which are inserted into a 193 // viewless document but this'll do for now. 194 // See http://bugs.webkit.org/show_bug.cgi?id=5727 195 if (!m_element->document()->frame()) 196 return false; 197 198 if (!m_element->document()->frame()->script()->canExecuteScripts(AboutToExecuteScript)) 199 return false; 200 201 // FIXME: This is non-standard. Remove this after https://bugs.webkit.org/show_bug.cgi?id=62412. 202 Node* ancestor = m_element->parentNode(); 203 while (ancestor) { 204 if (ancestor->isSVGShadowRoot()) 205 return false; 206 ancestor = ancestor->parentNode(); 207 } 208 209 if (!isScriptForEventSupported()) 210 return false; 211 212 if (!charsetAttributeValue().isEmpty()) 213 m_characterEncoding = charsetAttributeValue(); 214 else 215 m_characterEncoding = m_element->document()->charset(); 216 217 if (hasSourceAttribute()) 218 if (!requestScript(sourceAttributeValue())) 219 return false; 220 221 if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) { 222 m_willExecuteWhenDocumentFinishedParsing = true; 223 m_willBeParserExecuted = true; 224 } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue()) 225 m_willBeParserExecuted = true; 226 else if (!hasSourceAttribute() && m_parserInserted && !m_element->document()->haveStylesheetsLoaded()) { 227 m_willBeParserExecuted = true; 228 m_readyToBeParserExecuted = true; 229 } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) { 230 m_willExecuteInOrder = true; 231 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION); 232 m_cachedScript->addClient(this); 233 } else if (hasSourceAttribute()) 234 m_cachedScript->addClient(this); 235 else 236 executeScript(ScriptSourceCode(scriptContent(), m_element->document()->url(), scriptStartPosition)); 237 238 return true; 239 } 240 241 bool ScriptElement::requestScript(const String& sourceUrl) 242 { 243 RefPtr<Document> originalDocument = m_element->document(); 244 if (!m_element->dispatchBeforeLoadEvent(sourceUrl)) 245 return false; 246 if (!m_element->inDocument() || m_element->document() != originalDocument) 247 return false; 248 249 ASSERT(!m_cachedScript); 250 // FIXME: If sourceUrl is empty, we should dispatchErrorEvent(). 251 m_cachedScript = m_element->document()->cachedResourceLoader()->requestScript(sourceUrl, scriptCharset()); 252 m_isExternalScript = true; 253 254 if (m_cachedScript) 255 return true; 256 257 dispatchErrorEvent(); 258 return false; 259 } 260 261 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode) 262 { 263 ASSERT(m_alreadyStarted); 264 265 if (sourceCode.isEmpty()) 266 return; 267 268 if (!m_isExternalScript && !m_element->document()->contentSecurityPolicy()->allowInlineScript()) 269 return; 270 271 RefPtr<Document> document = m_element->document(); 272 ASSERT(document); 273 if (Frame* frame = document->frame()) { 274 { 275 IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? document.get() : 0); 276 // Create a script from the script element node, using the script 277 // block's source and the script block's type. 278 // Note: This is where the script is compiled and actually executed. 279 frame->script()->evaluate(sourceCode); 280 } 281 282 Document::updateStyleForAllDocuments(); 283 } 284 } 285 286 void ScriptElement::stopLoadRequest() 287 { 288 if (m_cachedScript) { 289 if (!m_willBeParserExecuted) 290 m_cachedScript->removeClient(this); 291 m_cachedScript = 0; 292 } 293 } 294 295 void ScriptElement::execute(CachedScript* cachedScript) 296 { 297 ASSERT(!m_willBeParserExecuted); 298 ASSERT(cachedScript); 299 if (cachedScript->errorOccurred()) 300 dispatchErrorEvent(); 301 else { 302 executeScript(ScriptSourceCode(cachedScript)); 303 dispatchLoadEvent(); 304 } 305 cachedScript->removeClient(this); 306 } 307 308 void ScriptElement::notifyFinished(CachedResource* o) 309 { 310 ASSERT(!m_willBeParserExecuted); 311 ASSERT_UNUSED(o, o == m_cachedScript); 312 if (m_willExecuteInOrder) 313 m_element->document()->scriptRunner()->notifyInOrderScriptReady(); 314 else 315 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION); 316 m_cachedScript = 0; 317 } 318 319 bool ScriptElement::ignoresLoadRequest() const 320 { 321 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument(); 322 } 323 324 bool ScriptElement::isScriptForEventSupported() const 325 { 326 String eventAttribute = eventAttributeValue(); 327 String forAttribute = forAttributeValue(); 328 if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) { 329 forAttribute = forAttribute.stripWhiteSpace(); 330 if (!equalIgnoringCase(forAttribute, "window")) 331 return false; 332 333 eventAttribute = eventAttribute.stripWhiteSpace(); 334 if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()")) 335 return false; 336 } 337 return true; 338 } 339 340 String ScriptElement::scriptContent() const 341 { 342 Vector<UChar> val; 343 Text* firstTextNode = 0; 344 bool foundMultipleTextNodes = false; 345 346 for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) { 347 if (!n->isTextNode()) 348 continue; 349 350 Text* t = static_cast<Text*>(n); 351 if (foundMultipleTextNodes) 352 append(val, t->data()); 353 else if (firstTextNode) { 354 append(val, firstTextNode->data()); 355 append(val, t->data()); 356 foundMultipleTextNodes = true; 357 } else 358 firstTextNode = t; 359 } 360 361 if (firstTextNode && !foundMultipleTextNodes) 362 return firstTextNode->data(); 363 364 return String::adopt(val); 365 } 366 367 ScriptElement* toScriptElement(Element* element) 368 { 369 if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag)) 370 return static_cast<HTMLScriptElement*>(element); 371 372 #if ENABLE(SVG) 373 if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag)) 374 return static_cast<SVGScriptElement*>(element); 375 #endif 376 377 return 0; 378 } 379 380 } 381