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 "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 53namespace WebCore { 54 55ScriptElement::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 71ScriptElement::~ScriptElement() 72{ 73 stopLoadRequest(); 74} 75 76void ScriptElement::insertedIntoDocument() 77{ 78 if (!m_parserInserted) 79 prepareScript(); // FIXME: Provide a real starting line number here. 80} 81 82void ScriptElement::removedFromDocument() 83{ 84 // Eventually stop loading any not-yet-finished content 85 stopLoadRequest(); 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 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 138bool 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 158bool 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 241bool 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 261void 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 286void ScriptElement::stopLoadRequest() 287{ 288 if (m_cachedScript) { 289 if (!m_willBeParserExecuted) 290 m_cachedScript->removeClient(this); 291 m_cachedScript = 0; 292 } 293} 294 295void 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 308void 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 319bool ScriptElement::ignoresLoadRequest() const 320{ 321 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument(); 322} 323 324bool 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 340String 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 367ScriptElement* 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