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 "DocLoader.h" 29 #include "Document.h" 30 #include "Frame.h" 31 #include "FrameLoader.h" 32 #include "HTMLNames.h" 33 #include "HTMLScriptElement.h" 34 #include "MIMETypeRegistry.h" 35 #include "ScriptController.h" 36 #include "ScriptSourceCode.h" 37 #include "ScriptValue.h" 38 #include "StringHash.h" 39 #include "Text.h" 40 #include <wtf/StdLibExtras.h> 41 42 #if ENABLE(SVG) 43 #include "SVGNames.h" 44 #include "SVGScriptElement.h" 45 #endif 46 47 namespace WebCore { 48 49 void ScriptElement::insertedIntoDocument(ScriptElementData& data, const String& sourceUrl) 50 { 51 if (data.createdByParser()) 52 return; 53 54 if (!sourceUrl.isEmpty()) { 55 data.requestScript(sourceUrl); 56 return; 57 } 58 59 // If there's an empty script node, we shouldn't evaluate the script 60 // because if a script is inserted afterwards (by setting text or innerText) 61 // it should be evaluated, and evaluateScript only evaluates a script once. 62 data.evaluateScript(ScriptSourceCode(data.scriptContent(), data.element()->document()->url())); // FIXME: Provide a real starting line number here. 63 } 64 65 void ScriptElement::removedFromDocument(ScriptElementData& data) 66 { 67 // Eventually stop loading any not-yet-finished content 68 data.stopLoadRequest(); 69 } 70 71 void ScriptElement::childrenChanged(ScriptElementData& data) 72 { 73 if (data.createdByParser()) 74 return; 75 76 Element* element = data.element(); 77 78 // If a node is inserted as a child of the script element 79 // and the script element has been inserted in the document 80 // we evaluate the script. 81 if (element->inDocument() && element->firstChild()) 82 data.evaluateScript(ScriptSourceCode(data.scriptContent(), element->document()->url())); // FIXME: Provide a real starting line number here 83 } 84 85 void ScriptElement::finishParsingChildren(ScriptElementData& data, const String& sourceUrl) 86 { 87 // The parser just reached </script>. If we have no src and no text, 88 // allow dynamic loading later. 89 if (sourceUrl.isEmpty() && data.scriptContent().isEmpty()) 90 data.setCreatedByParser(false); 91 } 92 93 void ScriptElement::handleSourceAttribute(ScriptElementData& data, const String& sourceUrl) 94 { 95 if (data.ignoresLoadRequest() || sourceUrl.isEmpty()) 96 return; 97 98 data.requestScript(sourceUrl); 99 } 100 101 // Helper function 102 static bool isSupportedJavaScriptLanguage(const String& language) 103 { 104 typedef HashSet<String, CaseFoldingHash> LanguageSet; 105 DEFINE_STATIC_LOCAL(LanguageSet, languages, ()); 106 if (languages.isEmpty()) { 107 languages.add("javascript"); 108 languages.add("javascript"); 109 languages.add("javascript1.0"); 110 languages.add("javascript1.1"); 111 languages.add("javascript1.2"); 112 languages.add("javascript1.3"); 113 languages.add("javascript1.4"); 114 languages.add("javascript1.5"); 115 languages.add("javascript1.6"); 116 languages.add("javascript1.7"); 117 languages.add("livescript"); 118 languages.add("ecmascript"); 119 languages.add("jscript"); 120 } 121 122 return languages.contains(language); 123 } 124 125 // ScriptElementData 126 ScriptElementData::ScriptElementData(ScriptElement* scriptElement, Element* element) 127 : m_scriptElement(scriptElement) 128 , m_element(element) 129 , m_cachedScript(0) 130 , m_createdByParser(false) 131 , m_requested(false) 132 , m_evaluated(false) 133 , m_firedLoad(false) 134 { 135 ASSERT(m_scriptElement); 136 ASSERT(m_element); 137 } 138 139 ScriptElementData::~ScriptElementData() 140 { 141 stopLoadRequest(); 142 } 143 144 void ScriptElementData::requestScript(const String& sourceUrl) 145 { 146 Document* document = m_element->document(); 147 148 // FIXME: Eventually we'd like to evaluate scripts which are inserted into a 149 // viewless document but this'll do for now. 150 // See http://bugs.webkit.org/show_bug.cgi?id=5727 151 if (!document->frame()) 152 return; 153 154 if (!m_element->dispatchBeforeLoadEvent(sourceUrl)) 155 return; 156 157 ASSERT(!m_cachedScript); 158 m_cachedScript = document->docLoader()->requestScript(sourceUrl, scriptCharset()); 159 m_requested = true; 160 161 // m_createdByParser is never reset - always resied at the initial value set while parsing. 162 // m_evaluated is left untouched as well to avoid script reexecution, if a <script> element 163 // is removed and reappended to the document. 164 m_firedLoad = false; 165 166 if (m_cachedScript) { 167 m_cachedScript->addClient(this); 168 return; 169 } 170 171 m_scriptElement->dispatchErrorEvent(); 172 } 173 174 void ScriptElementData::evaluateScript(const ScriptSourceCode& sourceCode) 175 { 176 if (m_evaluated || sourceCode.isEmpty() || !shouldExecuteAsJavaScript()) 177 return; 178 179 if (Frame* frame = m_element->document()->frame()) { 180 if (!frame->script()->canExecuteScripts()) 181 return; 182 183 m_evaluated = true; 184 185 frame->script()->evaluate(sourceCode); 186 Document::updateStyleForAllDocuments(); 187 } 188 } 189 190 void ScriptElementData::stopLoadRequest() 191 { 192 if (m_cachedScript) { 193 m_cachedScript->removeClient(this); 194 m_cachedScript = 0; 195 } 196 } 197 198 void ScriptElementData::execute(CachedScript* cachedScript) 199 { 200 ASSERT(cachedScript); 201 if (cachedScript->errorOccurred()) 202 m_scriptElement->dispatchErrorEvent(); 203 else { 204 evaluateScript(ScriptSourceCode(cachedScript)); 205 m_scriptElement->dispatchLoadEvent(); 206 } 207 cachedScript->removeClient(this); 208 } 209 210 void ScriptElementData::notifyFinished(CachedResource* o) 211 { 212 ASSERT_UNUSED(o, o == m_cachedScript); 213 m_element->document()->executeScriptSoon(this, m_cachedScript); 214 m_cachedScript = 0; 215 } 216 217 bool ScriptElementData::ignoresLoadRequest() const 218 { 219 return m_evaluated || m_requested || m_createdByParser || !m_element->inDocument(); 220 } 221 222 bool ScriptElementData::shouldExecuteAsJavaScript() const 223 { 224 /* 225 Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3. 226 Mozilla 1.8 and WinIE 7 both accept javascript and livescript. 227 WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't. 228 Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace. 229 We want to accept all the values that either of these browsers accept, but not other values. 230 */ 231 String type = m_scriptElement->typeAttributeValue(); 232 if (!type.isEmpty()) { 233 if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower())) 234 return false; 235 } else { 236 String language = m_scriptElement->languageAttributeValue(); 237 if (!language.isEmpty() && !isSupportedJavaScriptLanguage(language)) 238 return false; 239 } 240 241 // No type or language is specified, so we assume the script to be JavaScript. 242 // We don't yet support setting event listeners via the 'for' attribute for scripts. 243 // If there is such an attribute it's likely better to not execute the script than to do so 244 // immediately and unconditionally. 245 // FIXME: After <rdar://problem/4471751> / https://bugs.webkit.org/show_bug.cgi?id=16915 are resolved 246 // and we support the for syntax in script tags, this check can be removed and we should just 247 // return 'true' here. 248 String forAttribute = m_scriptElement->forAttributeValue(); 249 return forAttribute.isEmpty(); 250 } 251 252 String ScriptElementData::scriptCharset() const 253 { 254 // First we try to get encoding from charset attribute. 255 String charset = m_scriptElement->charsetAttributeValue().stripWhiteSpace(); 256 257 // If charset has not been declared in script tag, fall back to frame encoding. 258 if (charset.isEmpty()) { 259 if (Frame* frame = m_element->document()->frame()) 260 charset = frame->loader()->encoding(); 261 } 262 263 return charset; 264 } 265 266 String ScriptElementData::scriptContent() const 267 { 268 Vector<UChar> val; 269 Text* firstTextNode = 0; 270 bool foundMultipleTextNodes = false; 271 272 for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) { 273 if (!n->isTextNode()) 274 continue; 275 276 Text* t = static_cast<Text*>(n); 277 if (foundMultipleTextNodes) 278 append(val, t->data()); 279 else if (firstTextNode) { 280 append(val, firstTextNode->data()); 281 append(val, t->data()); 282 foundMultipleTextNodes = true; 283 } else 284 firstTextNode = t; 285 } 286 287 if (firstTextNode && !foundMultipleTextNodes) 288 return firstTextNode->data(); 289 290 return String::adopt(val); 291 } 292 293 ScriptElement* toScriptElement(Element* element) 294 { 295 if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag)) 296 return static_cast<HTMLScriptElement*>(element); 297 298 #if ENABLE(SVG) 299 if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag)) 300 return static_cast<SVGScriptElement*>(element); 301 #endif 302 303 return 0; 304 } 305 306 } 307