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 |