Bug 464754 - nsIContentPolicy.shouldLoad() called without context for scripts. r...
[mozilla-central.git] / content / base / src / nsScriptLoader.cpp
blob3279cbb4eabd695a7c1b5d065a72f1fd56633db2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim: ft=cpp tw=78 sw=2 et ts=2
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications.
20 * Portions created by the Initial Developer are Copyright (C) 2001
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Vidur Apparao <vidur@netscape.com> (original author)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 * A class that handles loading and evaluation of <script> elements.
44 #include "nsScriptLoader.h"
45 #include "nsIDOMCharacterData.h"
46 #include "nsParserUtils.h"
47 #include "nsIMIMEHeaderParam.h"
48 #include "nsICharsetConverterManager.h"
49 #include "nsIUnicodeDecoder.h"
50 #include "nsIContent.h"
51 #include "nsGkAtoms.h"
52 #include "nsNetUtil.h"
53 #include "nsIScriptGlobalObject.h"
54 #include "nsIScriptContext.h"
55 #include "nsIScriptRuntime.h"
56 #include "nsIScriptSecurityManager.h"
57 #include "nsIPrincipal.h"
58 #include "nsContentPolicyUtils.h"
59 #include "nsIDOMWindow.h"
60 #include "nsIHttpChannel.h"
61 #include "nsIScriptElement.h"
62 #include "nsIDOMHTMLScriptElement.h"
63 #include "nsIDocShell.h"
64 #include "jscntxt.h"
65 #include "nsContentUtils.h"
66 #include "nsUnicharUtils.h"
67 #include "nsAutoPtr.h"
68 #include "nsIXPConnect.h"
69 #include "nsContentErrors.h"
70 #include "nsIParser.h"
71 #include "nsThreadUtils.h"
73 //////////////////////////////////////////////////////////////
74 // Per-request data structure
75 //////////////////////////////////////////////////////////////
77 class nsScriptLoadRequest : public nsISupports {
78 public:
79 nsScriptLoadRequest(nsIScriptElement* aElement,
80 PRUint32 aVersion)
81 : mElement(aElement),
82 mLoading(PR_TRUE),
83 mIsInline(PR_TRUE),
84 mJSVersion(aVersion), mLineNo(1)
88 NS_DECL_ISUPPORTS
90 void FireScriptAvailable(nsresult aResult)
92 mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
94 void FireScriptEvaluated(nsresult aResult)
96 mElement->ScriptEvaluated(aResult, mElement, mIsInline);
99 PRBool IsPreload()
101 return mElement == nsnull;
104 nsCOMPtr<nsIScriptElement> mElement;
105 PRPackedBool mLoading; // Are we still waiting for a load to complete?
106 PRPackedBool mDefer; // Is execution defered?
107 PRPackedBool mIsInline; // Is the script inline or loaded?
108 nsString mScriptText; // Holds script for loaded scripts
109 PRUint32 mJSVersion;
110 nsCOMPtr<nsIURI> mURI;
111 nsCOMPtr<nsIURI> mFinalURI;
112 PRInt32 mLineNo;
115 // The nsScriptLoadRequest is passed as the context to necko, and thus
116 // it needs to be threadsafe. Necko won't do anything with this
117 // context, but it will AddRef and Release it on other threads.
118 NS_IMPL_THREADSAFE_ISUPPORTS0(nsScriptLoadRequest)
120 //////////////////////////////////////////////////////////////
122 //////////////////////////////////////////////////////////////
124 nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
125 : mDocument(aDocument),
126 mBlockerCount(0),
127 mEnabled(PR_TRUE)
131 nsScriptLoader::~nsScriptLoader()
133 mObservers.Clear();
135 for (PRInt32 i = 0; i < mRequests.Count(); i++) {
136 mRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
139 // Unblock the kids, in case any of them moved to a different document
140 // subtree in the meantime and therefore aren't actually going away.
141 for (PRUint32 j = 0; j < mPendingChildLoaders.Length(); ++j) {
142 mPendingChildLoaders[j]->RemoveExecuteBlocker();
146 NS_IMPL_ISUPPORTS1(nsScriptLoader, nsIStreamLoaderObserver)
148 // Helper method for checking if the script element is an event-handler
149 // This means that it has both a for-attribute and a event-attribute.
150 // Also, if the for-attribute has a value that matches "\s*window\s*",
151 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
152 // eventhandler. (both matches are case insensitive).
153 // This is how IE seems to filter out a window's onload handler from a
154 // <script for=... event=...> element.
156 static PRBool
157 IsScriptEventHandler(nsIScriptElement *aScriptElement)
159 nsCOMPtr<nsIContent> contElement = do_QueryInterface(aScriptElement);
160 NS_ASSERTION(contElement, "nsIScriptElement isn't nsIContent");
162 nsAutoString forAttr, eventAttr;
163 if (!contElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) ||
164 !contElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) {
165 return PR_FALSE;
168 const nsAString& for_str = nsContentUtils::TrimWhitespace(forAttr);
169 if (!for_str.LowerCaseEqualsLiteral("window")) {
170 return PR_TRUE;
173 // We found for="window", now check for event="onload".
174 const nsAString& event_str = nsContentUtils::TrimWhitespace(eventAttr, PR_FALSE);
175 if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
176 nsCaseInsensitiveStringComparator())) {
177 // It ain't "onload.*".
179 return PR_TRUE;
182 nsAutoString::const_iterator start, end;
183 event_str.BeginReading(start);
184 event_str.EndReading(end);
186 start.advance(6); // advance past "onload"
188 if (start != end && *start != '(' && *start != ' ') {
189 // We got onload followed by something other than space or
190 // '('. Not good enough.
192 return PR_TRUE;
195 return PR_FALSE;
198 nsresult
199 nsScriptLoader::CheckContentPolicy(nsScriptLoadRequest *aRequest,
200 nsISupports *aContext,
201 const nsAString &aType)
203 PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
204 nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
205 aRequest->mURI,
206 mDocument->NodePrincipal(),
207 aContext,
208 NS_LossyConvertUTF16toASCII(aType),
209 nsnull, //extra
210 &shouldLoad,
211 nsContentUtils::GetContentPolicy(),
212 nsContentUtils::GetSecurityManager());
213 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
214 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
215 return NS_ERROR_CONTENT_BLOCKED;
217 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
220 return NS_OK;
223 nsresult
224 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType)
226 // Check that the containing page is allowed to load this URI.
227 nsresult rv = nsContentUtils::GetSecurityManager()->
228 CheckLoadURIWithPrincipal(mDocument->NodePrincipal(), aRequest->mURI,
229 nsIScriptSecurityManager::ALLOW_CHROME);
231 NS_ENSURE_SUCCESS(rv, rv);
233 // After the security manager, the content-policy stuff gets a veto
234 nsISupports *context = aRequest->mElement.get()
235 ? static_cast<nsISupports *>(aRequest->mElement.get())
236 : static_cast<nsISupports *>(mDocument);
237 rv = CheckContentPolicy(aRequest, context, aType);
238 if (NS_FAILED(rv)) {
239 return rv;
242 nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
243 nsCOMPtr<nsIStreamLoader> loader;
245 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->GetScriptGlobalObject()));
246 nsIDocShell *docshell = window->GetDocShell();
248 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
250 nsCOMPtr<nsIChannel> channel;
251 rv = NS_NewChannel(getter_AddRefs(channel),
252 aRequest->mURI, nsnull, loadGroup,
253 prompter, nsIRequest::LOAD_NORMAL);
254 NS_ENSURE_SUCCESS(rv, rv);
256 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
257 if (httpChannel) {
258 // HTTP content negotation has little value in this context.
259 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
260 NS_LITERAL_CSTRING("*/*"),
261 PR_FALSE);
262 httpChannel->SetReferrer(mDocument->GetDocumentURI());
265 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
266 NS_ENSURE_SUCCESS(rv, rv);
268 return channel->AsyncOpen(loader, aRequest);
271 PRBool
272 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
273 nsIURI * const &aURI) const
275 PRBool same;
276 return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
277 same;
280 nsresult
281 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
283 // We need a document to evaluate scripts.
284 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
286 // Check to see if scripts has been turned off.
287 if (!mEnabled || !mDocument->IsScriptEnabled()) {
288 return NS_ERROR_NOT_AVAILABLE;
291 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
293 // Check that the script is not an eventhandler
294 if (IsScriptEventHandler(aElement)) {
295 return NS_CONTENT_SCRIPT_IS_EVENTHANDLER;
298 // Script evaluation can also be disabled in the current script
299 // context even though it's enabled in the document.
300 // XXX - still hard-coded for JS here, even though another language
301 // may be specified. Should this check be made *after* we examine
302 // the attributes to locate the script-type?
303 // For now though, if JS is disabled we assume every language is
304 // disabled.
305 // XXX is this different from the mDocument->IsScriptEnabled() call?
306 nsIScriptGlobalObject *globalObject = mDocument->GetScriptGlobalObject();
307 if (!globalObject) {
308 return NS_ERROR_NOT_AVAILABLE;
311 nsIScriptContext *context = globalObject->GetScriptContext(
312 nsIProgrammingLanguage::JAVASCRIPT);
314 // If scripts aren't enabled in the current context, there's no
315 // point in going on.
316 if (!context || !context->GetScriptsEnabled()) {
317 return NS_ERROR_NOT_AVAILABLE;
320 // Default script language is whatever the root content specifies
321 // (which may come from a header or http-meta tag), or if there
322 // is no root content, from the script global object.
323 nsCOMPtr<nsIContent> rootContent = mDocument->GetRootContent();
324 PRUint32 typeID = rootContent ? rootContent->GetScriptTypeID() :
325 context->GetScriptTypeID();
326 PRUint32 version = 0;
327 nsAutoString language, type, src;
328 nsresult rv = NS_OK;
330 // Check the type attribute to determine language and version.
331 // If type exists, it trumps the deprecated 'language='
332 aElement->GetScriptType(type);
333 if (!type.IsEmpty()) {
334 nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParser =
335 do_GetService("@mozilla.org/network/mime-hdrparam;1");
336 NS_ENSURE_TRUE(mimeHdrParser, NS_ERROR_FAILURE);
338 NS_ConvertUTF16toUTF8 typeAndParams(type);
340 nsAutoString mimeType;
341 rv = mimeHdrParser->GetParameter(typeAndParams, nsnull,
342 EmptyCString(), PR_FALSE, nsnull,
343 mimeType);
344 NS_ENSURE_SUCCESS(rv, rv);
346 // Javascript keeps the fast path, optimized for most-likely type
347 // Table ordered from most to least likely JS MIME types.
348 // See bug 62485, feel free to add <script type="..."> survey data to it,
349 // or to a new bug once 62485 is closed.
350 static const char *jsTypes[] = {
351 "text/javascript",
352 "text/ecmascript",
353 "application/javascript",
354 "application/ecmascript",
355 "application/x-javascript",
356 nsnull
359 PRBool isJavaScript = PR_FALSE;
360 for (PRInt32 i = 0; jsTypes[i]; i++) {
361 if (mimeType.LowerCaseEqualsASCII(jsTypes[i])) {
362 isJavaScript = PR_TRUE;
363 break;
366 if (isJavaScript)
367 typeID = nsIProgrammingLanguage::JAVASCRIPT;
368 else {
369 // Use the object factory to locate a matching language.
370 nsCOMPtr<nsIScriptRuntime> runtime;
371 rv = NS_GetScriptRuntime(mimeType, getter_AddRefs(runtime));
372 if (NS_FAILED(rv) || runtime == nsnull) {
373 // Failed to get the explicitly specified language
374 NS_WARNING("Failed to find a scripting language");
375 typeID = nsIProgrammingLanguage::UNKNOWN;
376 } else
377 typeID = runtime->GetScriptTypeID();
379 if (typeID != nsIProgrammingLanguage::UNKNOWN) {
380 // Get the version string, and ensure the language supports it.
381 nsAutoString versionName;
382 rv = mimeHdrParser->GetParameter(typeAndParams, "version",
383 EmptyCString(), PR_FALSE, nsnull,
384 versionName);
385 if (NS_FAILED(rv)) {
386 // no version attribute - version remains 0.
387 if (rv != NS_ERROR_INVALID_ARG)
388 return rv;
389 } else {
390 nsCOMPtr<nsIScriptRuntime> runtime;
391 rv = NS_GetScriptRuntimeByID(typeID, getter_AddRefs(runtime));
392 if (NS_FAILED(rv)) {
393 NS_ERROR("Failed to locate the language with this ID");
394 return rv;
396 rv = runtime->ParseVersion(versionName, &version);
397 if (NS_FAILED(rv)) {
398 NS_WARNING("This script language version is not supported - ignored");
399 typeID = nsIProgrammingLanguage::UNKNOWN;
404 // Some js specifics yet to be abstracted.
405 if (typeID == nsIProgrammingLanguage::JAVASCRIPT) {
406 nsAutoString value;
408 rv = mimeHdrParser->GetParameter(typeAndParams, "e4x",
409 EmptyCString(), PR_FALSE, nsnull,
410 value);
411 if (NS_FAILED(rv)) {
412 if (rv != NS_ERROR_INVALID_ARG)
413 return rv;
414 } else {
415 if (value.Length() == 1 && value[0] == '1')
416 // This means that we need to set JSOPTION_XML in the JS options.
417 // We re-use our knowledge of the implementation to reuse
418 // JSVERSION_HAS_XML as a safe version flag.
419 // If version has JSVERSION_UNKNOWN (-1), then this is still OK.
420 version |= JSVERSION_HAS_XML;
423 } else {
424 // no 'type=' element
425 // "language" is a deprecated attribute of HTML, so we check it only for
426 // HTML script elements.
427 nsCOMPtr<nsIDOMHTMLScriptElement> htmlScriptElement =
428 do_QueryInterface(aElement);
429 if (htmlScriptElement) {
430 htmlScriptElement->GetAttribute(NS_LITERAL_STRING("language"), language);
431 if (!language.IsEmpty()) {
432 if (nsParserUtils::IsJavaScriptLanguage(language, &version))
433 typeID = nsIProgrammingLanguage::JAVASCRIPT;
434 else
435 typeID = nsIProgrammingLanguage::UNKNOWN;
436 // IE, Opera, etc. do not respect language version, so neither should
437 // we at this late date in the browser wars saga. Note that this change
438 // affects HTML but not XUL or SVG (but note also that XUL has its own
439 // code to check nsParserUtils::IsJavaScriptLanguage -- that's probably
440 // a separate bug, one we may not be able to fix short of XUL2). See
441 // bug 255895 (https://bugzilla.mozilla.org/show_bug.cgi?id=255895).
442 NS_ASSERTION(JSVERSION_DEFAULT == 0,
443 "We rely on all languages having 0 as a version default");
444 version = 0;
449 // If we don't know the language, we don't know how to evaluate
450 if (typeID == nsIProgrammingLanguage::UNKNOWN) {
451 return NS_ERROR_NOT_AVAILABLE;
453 // If not from a chrome document (which is always trusted), we need some way
454 // of checking the language is "safe". Currently the only other language
455 // impl is Python, and that is *not* safe in untrusted code - so fixing
456 // this isn't a priority.!
457 // See also similar code in nsXULContentSink.cpp
458 if (typeID != nsIProgrammingLanguage::JAVASCRIPT &&
459 !nsContentUtils::IsChromeDoc(mDocument)) {
460 NS_WARNING("Untrusted language called from non-chrome - ignored");
461 return NS_ERROR_NOT_AVAILABLE;
464 nsCOMPtr<nsIContent> eltContent(do_QueryInterface(aElement));
465 eltContent->SetScriptTypeID(typeID);
467 PRBool hadPendingRequests = !!GetFirstPendingRequest();
469 // Did we preload this request?
470 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
471 nsRefPtr<nsScriptLoadRequest> request;
472 if (scriptURI) {
473 nsTArray<PreloadInfo>::index_type i =
474 mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
475 if (i != nsTArray<PreloadInfo>::NoIndex) {
476 request = mPreloads[i].mRequest;
477 request->mElement = aElement;
478 request->mJSVersion = version;
479 request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
480 mPreloads.RemoveElementAt(i);
482 rv = CheckContentPolicy(request, aElement, type);
483 if (NS_FAILED(rv)) {
484 // Note, we're dropping our last ref to request here.
485 return rv;
488 if (!request->mLoading && !request->mDefer && !hadPendingRequests &&
489 ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript()) {
490 return ProcessRequest(request);
493 // Not done loading yet. Move into the real requests queue and wait.
494 mRequests.AppendObject(request);
496 if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts() &&
497 !request->mDefer) {
498 nsContentUtils::AddScriptRunner(new nsRunnableMethod<nsScriptLoader>(this,
499 &nsScriptLoader::ProcessPendingRequests));
502 return request->mDefer ? NS_OK : NS_ERROR_HTMLPARSER_BLOCK;
506 // Create a request object for this script
507 request = new nsScriptLoadRequest(aElement, version);
508 NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
510 request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
512 // First check to see if this is an external script
513 if (scriptURI) {
514 request->mURI = scriptURI;
515 request->mIsInline = PR_FALSE;
516 request->mLoading = PR_TRUE;
518 rv = StartLoad(request, type);
519 if (NS_FAILED(rv)) {
520 return rv;
522 } else {
523 request->mLoading = PR_FALSE;
524 request->mIsInline = PR_TRUE;
525 request->mURI = mDocument->GetDocumentURI();
527 request->mLineNo = aElement->GetScriptLineNumber();
529 // If we've got existing pending requests, add ourselves
530 // to this list.
531 if (!request->mDefer && !hadPendingRequests &&
532 ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript()) {
533 return ProcessRequest(request);
537 // Add the request to our requests list
538 NS_ENSURE_TRUE(mRequests.AppendObject(request),
539 NS_ERROR_OUT_OF_MEMORY);
541 if (request->mDefer) {
542 return NS_OK;
545 // If there weren't any pending requests before, and this one is
546 // ready to execute, do that as soon as it's safe.
547 if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts()) {
548 nsContentUtils::AddScriptRunner(new nsRunnableMethod<nsScriptLoader>(this,
549 &nsScriptLoader::ProcessPendingRequests));
552 // Added as pending request, now we can send blocking back
553 return NS_ERROR_HTMLPARSER_BLOCK;
556 nsresult
557 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
559 NS_ASSERTION(ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript(),
560 "Caller forgot to check ReadyToExecuteScripts()");
562 NS_ENSURE_ARG(aRequest);
563 nsAFlatString* script;
564 nsAutoString textData;
566 // If there's no script text, we try to get it from the element
567 if (aRequest->mIsInline) {
568 // XXX This is inefficient - GetText makes multiple
569 // copies.
570 aRequest->mElement->GetScriptText(textData);
572 script = &textData;
574 else {
575 script = &aRequest->mScriptText;
578 FireScriptAvailable(NS_OK, aRequest);
579 nsresult rv = EvaluateScript(aRequest, *script);
580 FireScriptEvaluated(rv, aRequest);
582 return rv;
585 void
586 nsScriptLoader::FireScriptAvailable(nsresult aResult,
587 nsScriptLoadRequest* aRequest)
589 for (PRInt32 i = 0; i < mObservers.Count(); i++) {
590 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
591 obs->ScriptAvailable(aResult, aRequest->mElement,
592 aRequest->mIsInline, aRequest->mURI,
593 aRequest->mLineNo);
596 aRequest->FireScriptAvailable(aResult);
599 void
600 nsScriptLoader::FireScriptEvaluated(nsresult aResult,
601 nsScriptLoadRequest* aRequest)
603 for (PRInt32 i = 0; i < mObservers.Count(); i++) {
604 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
605 obs->ScriptEvaluated(aResult, aRequest->mElement,
606 aRequest->mIsInline);
609 aRequest->FireScriptEvaluated(aResult);
612 nsresult
613 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
614 const nsAFlatString& aScript)
616 nsresult rv = NS_OK;
618 // We need a document to evaluate scripts.
619 if (!mDocument) {
620 return NS_ERROR_FAILURE;
623 nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
624 if (!pwin || !pwin->IsInnerWindow()) {
625 return NS_ERROR_FAILURE;
627 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
628 NS_ASSERTION(globalObject, "windows must be global objects");
630 // Get the script-type to be used by this element.
631 nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
632 NS_ASSERTION(scriptContent, "no content - what is default script-type?");
633 PRUint32 stid = scriptContent ? scriptContent->GetScriptTypeID() :
634 nsIProgrammingLanguage::JAVASCRIPT;
635 // and make sure we are setup for this type of script.
636 rv = globalObject->EnsureScriptEnvironment(stid);
637 if (NS_FAILED(rv))
638 return rv;
640 // Make sure context is a strong reference since we access it after
641 // we've executed a script, which may cause all other references to
642 // the context to go away.
643 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(stid);
644 if (!context) {
645 return NS_ERROR_FAILURE;
648 nsIURI* uri = aRequest->mFinalURI ? aRequest->mFinalURI : aRequest->mURI;
650 PRBool oldProcessingScriptTag = context->GetProcessingScriptTag();
651 context->SetProcessingScriptTag(PR_TRUE);
653 // Update our current script.
654 nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
655 mCurrentScript = aRequest->mElement;
657 nsCAutoString url;
658 nsContentUtils::GetWrapperSafeScriptFilename(mDocument, uri, url);
660 PRBool isUndefined;
661 rv = context->EvaluateString(aScript,
662 globalObject->GetScriptGlobal(stid),
663 mDocument->NodePrincipal(), url.get(),
664 aRequest->mLineNo, aRequest->mJSVersion, nsnull,
665 &isUndefined);
667 // Put the old script back in case it wants to do anything else.
668 mCurrentScript = oldCurrent;
670 JSContext *cx = nsnull; // Initialize this to keep GCC happy.
671 if (stid == nsIProgrammingLanguage::JAVASCRIPT) {
672 cx = (JSContext *)context->GetNativeContext();
673 ::JS_BeginRequest(cx);
674 ::JS_ReportPendingException(cx);
677 context->SetProcessingScriptTag(oldProcessingScriptTag);
679 if (stid == nsIProgrammingLanguage::JAVASCRIPT) {
680 NS_ASSERTION(!::JS_IsExceptionPending(cx),
681 "JS_ReportPendingException wasn't called");
682 ::JS_EndRequest(cx);
684 return rv;
687 nsScriptLoadRequest*
688 nsScriptLoader::GetFirstPendingRequest()
690 for (PRInt32 i = 0; i < mRequests.Count(); ++i) {
691 if (!mRequests[i]->mDefer) {
692 return mRequests[i];
696 return nsnull;
699 void
700 nsScriptLoader::ProcessPendingRequestsAsync()
702 if (GetFirstPendingRequest() || !mPendingChildLoaders.IsEmpty()) {
703 nsCOMPtr<nsIRunnable> ev = new nsRunnableMethod<nsScriptLoader>(this,
704 &nsScriptLoader::ProcessPendingRequests);
706 NS_DispatchToCurrentThread(ev);
710 void
711 nsScriptLoader::ProcessPendingRequests()
713 nsRefPtr<nsScriptLoadRequest> request;
714 while (ReadyToExecuteScripts() &&
715 (request = GetFirstPendingRequest()) &&
716 !request->mLoading) {
717 mRequests.RemoveObject(request);
718 ProcessRequest(request);
721 while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
722 nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
723 mPendingChildLoaders.RemoveElementAt(0);
724 child->RemoveExecuteBlocker();
728 PRBool
729 nsScriptLoader::ReadyToExecuteScripts()
731 // Make sure the SelfReadyToExecuteScripts check is first, so that
732 // we don't block twice on an ancestor.
733 if (!SelfReadyToExecuteScripts()) {
734 return PR_FALSE;
737 for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
738 nsScriptLoader* ancestor = doc->ScriptLoader();
739 if (!ancestor->SelfReadyToExecuteScripts() &&
740 ancestor->AddPendingChildLoader(this)) {
741 AddExecuteBlocker();
742 return PR_FALSE;
746 return PR_TRUE;
750 // This function was copied from nsParser.cpp. It was simplified a bit.
751 static PRBool
752 DetectByteOrderMark(const unsigned char* aBytes, PRInt32 aLen, nsCString& oCharset)
754 if (aLen < 2)
755 return PR_FALSE;
757 switch(aBytes[0]) {
758 case 0xEF:
759 if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) {
760 // EF BB BF
761 // Win2K UTF-8 BOM
762 oCharset.Assign("UTF-8");
764 break;
765 case 0xFE:
766 if (0xFF == aBytes[1]) {
767 // FE FF
768 // UTF-16, big-endian
769 oCharset.Assign("UTF-16BE");
771 break;
772 case 0xFF:
773 if (0xFE == aBytes[1]) {
774 // FF FE
775 // UTF-16, little-endian
776 oCharset.Assign("UTF-16LE");
778 break;
780 return !oCharset.IsEmpty();
783 /* static */ nsresult
784 nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const PRUint8* aData,
785 PRUint32 aLength, const nsString& aHintCharset,
786 nsIDocument* aDocument, nsString& aString)
788 if (!aLength) {
789 aString.Truncate();
790 return NS_OK;
793 nsCAutoString characterSet;
795 nsresult rv = NS_OK;
796 if (aChannel) {
797 rv = aChannel->GetContentCharset(characterSet);
800 if (!aHintCharset.IsEmpty() && (NS_FAILED(rv) || characterSet.IsEmpty())) {
801 // charset name is always ASCII.
802 LossyCopyUTF16toASCII(aHintCharset, characterSet);
805 if (NS_FAILED(rv) || characterSet.IsEmpty()) {
806 DetectByteOrderMark(aData, aLength, characterSet);
809 if (characterSet.IsEmpty()) {
810 // charset from document default
811 characterSet = aDocument->GetDocumentCharacterSet();
814 if (characterSet.IsEmpty()) {
815 // fall back to ISO-8859-1, see bug 118404
816 characterSet.AssignLiteral("ISO-8859-1");
819 nsCOMPtr<nsICharsetConverterManager> charsetConv =
820 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
822 nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
824 if (NS_SUCCEEDED(rv) && charsetConv) {
825 rv = charsetConv->GetUnicodeDecoder(characterSet.get(),
826 getter_AddRefs(unicodeDecoder));
827 if (NS_FAILED(rv)) {
828 // fall back to ISO-8859-1 if charset is not supported. (bug 230104)
829 rv = charsetConv->GetUnicodeDecoderRaw("ISO-8859-1",
830 getter_AddRefs(unicodeDecoder));
834 // converts from the charset to unicode
835 if (NS_SUCCEEDED(rv)) {
836 PRInt32 unicodeLength = 0;
838 rv = unicodeDecoder->GetMaxLength(reinterpret_cast<const char*>(aData),
839 aLength, &unicodeLength);
840 if (NS_SUCCEEDED(rv)) {
841 if (!EnsureStringLength(aString, unicodeLength))
842 return NS_ERROR_OUT_OF_MEMORY;
844 PRUnichar *ustr = aString.BeginWriting();
846 PRInt32 consumedLength = 0;
847 PRInt32 originalLength = aLength;
848 PRInt32 convertedLength = 0;
849 PRInt32 bufferLength = unicodeLength;
850 do {
851 rv = unicodeDecoder->Convert(reinterpret_cast<const char*>(aData),
852 (PRInt32 *) &aLength, ustr,
853 &unicodeLength);
854 if (NS_FAILED(rv)) {
855 // if we failed, we consume one byte, replace it with U+FFFD
856 // and try the conversion again.
857 ustr[unicodeLength++] = (PRUnichar)0xFFFD;
858 ustr += unicodeLength;
860 unicodeDecoder->Reset();
862 aData += ++aLength;
863 consumedLength += aLength;
864 aLength = originalLength - consumedLength;
865 convertedLength += unicodeLength;
866 unicodeLength = bufferLength - convertedLength;
867 } while (NS_FAILED(rv) && (originalLength > consumedLength) && (bufferLength > convertedLength));
868 aString.SetLength(convertedLength);
871 return rv;
874 NS_IMETHODIMP
875 nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
876 nsISupports* aContext,
877 nsresult aStatus,
878 PRUint32 aStringLen,
879 const PRUint8* aString)
881 nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
882 NS_ASSERTION(request, "null request in stream complete handler");
883 NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
885 nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
886 aString);
887 if (NS_FAILED(rv)) {
888 if (!mRequests.RemoveObject(request)) {
889 mPreloads.RemoveElement(request, PreloadRequestComparator());
890 } else {
891 FireScriptAvailable(rv, request);
895 // Process our request and/or any pending ones
896 ProcessPendingRequests();
898 return NS_OK;
901 nsresult
902 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
903 nsIStreamLoader* aLoader,
904 nsresult aStatus,
905 PRUint32 aStringLen,
906 const PRUint8* aString)
908 if (NS_FAILED(aStatus)) {
909 return aStatus;
912 // If we don't have a document, then we need to abort further
913 // evaluation.
914 if (!mDocument) {
915 return NS_ERROR_NOT_AVAILABLE;
918 // If the load returned an error page, then we need to abort
919 nsCOMPtr<nsIRequest> req;
920 nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
921 NS_ASSERTION(req, "StreamLoader's request went away prematurely");
922 NS_ENSURE_SUCCESS(rv, rv);
924 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
925 if (httpChannel) {
926 PRBool requestSucceeded;
927 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
928 if (NS_SUCCEEDED(rv) && !requestSucceeded) {
929 return NS_ERROR_NOT_AVAILABLE;
933 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
934 NS_GetFinalChannelURI(channel, getter_AddRefs(aRequest->mFinalURI));
935 if (aStringLen) {
936 // Check the charset attribute to determine script charset.
937 nsAutoString hintCharset;
938 if (!aRequest->IsPreload()) {
939 aRequest->mElement->GetScriptCharset(hintCharset);
940 } else {
941 nsTArray<PreloadInfo>::index_type i =
942 mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator());
943 NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping");
944 hintCharset = mPreloads[i].mCharset;
946 rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
947 aRequest->mScriptText);
949 NS_ENSURE_SUCCESS(rv, rv);
951 if (!ShouldExecuteScript(mDocument, channel)) {
952 return NS_ERROR_NOT_AVAILABLE;
956 // This assertion could fire errorously if we ran out of memory when
957 // inserting the request in the array. However it's an unlikely case
958 // so if you see this assertion it is likely something else that is
959 // wrong, especially if you see it more than once.
960 NS_ASSERTION(mRequests.IndexOf(aRequest) >= 0 ||
961 mPreloads.Contains(aRequest, PreloadRequestComparator()),
962 "aRequest should be pending!");
964 // Mark this as loaded
965 aRequest->mLoading = PR_FALSE;
967 return NS_OK;
970 /* static */
971 PRBool
972 nsScriptLoader::ShouldExecuteScript(nsIDocument* aDocument,
973 nsIChannel* aChannel)
975 if (!aChannel) {
976 return PR_FALSE;
979 PRBool hasCert;
980 nsIPrincipal* docPrincipal = aDocument->NodePrincipal();
981 docPrincipal->GetHasCertificate(&hasCert);
982 if (!hasCert) {
983 return PR_TRUE;
986 nsCOMPtr<nsIPrincipal> channelPrincipal;
987 nsresult rv = nsContentUtils::GetSecurityManager()->
988 GetChannelPrincipal(aChannel, getter_AddRefs(channelPrincipal));
989 NS_ENSURE_SUCCESS(rv, PR_FALSE);
991 NS_ASSERTION(channelPrincipal, "Gotta have a principal here!");
993 // If the channel principal isn't at least as powerful as the
994 // document principal, then we don't execute the script.
995 PRBool subsumes;
996 rv = channelPrincipal->Subsumes(docPrincipal, &subsumes);
997 return NS_SUCCEEDED(rv) && subsumes;
1000 void
1001 nsScriptLoader::EndDeferringScripts()
1003 mDeferEnabled = PR_FALSE;
1004 for (PRUint32 i = 0; i < (PRUint32)mRequests.Count(); ++i) {
1005 mRequests[i]->mDefer = PR_FALSE;
1008 ProcessPendingRequests();
1011 void
1012 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
1013 const nsAString &aType)
1015 nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(nsnull, 0);
1016 if (!request) {
1017 return;
1020 request->mURI = aURI;
1021 request->mIsInline = PR_FALSE;
1022 request->mLoading = PR_TRUE;
1023 request->mDefer = PR_FALSE; // This is computed later when we go to execute the
1024 // script.
1025 nsresult rv = StartLoad(request, aType);
1026 if (NS_FAILED(rv)) {
1027 return;
1030 PreloadInfo *pi = mPreloads.AppendElement();
1031 pi->mRequest = request;
1032 pi->mCharset = aCharset;