Back to index

lightning-sunbird  0.9+nobinonly
nsScriptLoader.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 // vim: ft=cpp tw=78 sw=2 et ts=2
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is Mozilla.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Vidur Apparao <vidur@netscape.com> (original author)
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nsScriptLoader.h"
00041 #include "nsIDOMCharacterData.h"
00042 #include "nsParserUtils.h"
00043 #include "nsIMIMEHeaderParam.h"
00044 #include "nsICharsetConverterManager.h"
00045 #include "nsIUnicodeDecoder.h"
00046 #include "nsIContent.h"
00047 #include "nsHTMLAtoms.h"
00048 #include "nsNetUtil.h"
00049 #include "nsIScriptGlobalObject.h"
00050 #include "nsIScriptContext.h"
00051 #include "nsINodeInfo.h"
00052 #include "nsIScriptSecurityManager.h"
00053 #include "nsIPrincipal.h"
00054 #include "nsContentPolicyUtils.h"
00055 #include "nsPIDOMWindow.h"
00056 #include "nsIHttpChannel.h"
00057 #include "nsIScriptElement.h"
00058 #include "nsIDOMHTMLScriptElement.h"
00059 #include "nsIDocShell.h"
00060 #include "jsapi.h"
00061 #include "nsContentUtils.h"
00062 #include "nsUnicharUtils.h"
00063 #include "nsAutoPtr.h"
00064 #include "nsIEventQueue.h"
00065 #include "nsEventQueueUtils.h"
00066 
00067 static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
00068 
00070 // Per-request data structure
00072 
00073 class nsScriptLoadRequest : public nsISupports {
00074 public:
00075   nsScriptLoadRequest(nsIScriptElement* aElement,
00076                       nsIScriptLoaderObserver* aObserver,
00077                       const char* aVersionString,
00078                       PRBool aHasE4XOption);
00079   virtual ~nsScriptLoadRequest();
00080 
00081   NS_DECL_ISUPPORTS
00082 
00083   void FireScriptAvailable(nsresult aResult,
00084                            const nsAFlatString& aScript);
00085   void FireScriptEvaluated(nsresult aResult);
00086 
00087   nsCOMPtr<nsIScriptElement> mElement;
00088   nsCOMPtr<nsIScriptLoaderObserver> mObserver;
00089   PRPackedBool mLoading;             // Are we still waiting for a load to complete?
00090   PRPackedBool mWasPending;          // Processed immediately or pending
00091   PRPackedBool mIsInline;            // Is the script inline or loaded?
00092   PRPackedBool mHasE4XOption;        // Has E4X=1 script type parameter
00093   nsString mScriptText;              // Holds script for loaded scripts
00094   const char* mJSVersion;            // We don't own this string
00095   nsCOMPtr<nsIURI> mURI;
00096   PRInt32 mLineNo;
00097 };
00098 
00099 nsScriptLoadRequest::nsScriptLoadRequest(nsIScriptElement* aElement,
00100                                          nsIScriptLoaderObserver* aObserver,
00101                                          const char* aVersionString,
00102                                          PRBool aHasE4XOption) :
00103   mElement(aElement), mObserver(aObserver),
00104   mLoading(PR_TRUE), mWasPending(PR_FALSE),
00105   mIsInline(PR_TRUE), mHasE4XOption(aHasE4XOption),
00106   mJSVersion(aVersionString), mLineNo(1)
00107 {
00108 }
00109 
00110 nsScriptLoadRequest::~nsScriptLoadRequest()
00111 {
00112 }
00113 
00114 
00115 // The nsScriptLoadRequest is passed as the context to necko, and thus
00116 // it needs to be threadsafe. Necko won't do anything with this
00117 // context, but it will AddRef and Release it on other threads.
00118 
00119 NS_IMPL_THREADSAFE_ISUPPORTS0(nsScriptLoadRequest)
00120 
00121 void
00122 nsScriptLoadRequest::FireScriptAvailable(nsresult aResult,
00123                                          const nsAFlatString& aScript)
00124 {
00125   if (mObserver) {
00126     mObserver->ScriptAvailable(aResult, mElement, mIsInline, mWasPending,
00127                                mURI, mLineNo,
00128                                aScript);
00129   }
00130 }
00131 
00132 void
00133 nsScriptLoadRequest::FireScriptEvaluated(nsresult aResult)
00134 {
00135   if (mObserver) {
00136     mObserver->ScriptEvaluated(aResult, mElement, mIsInline, mWasPending);
00137   }
00138 }
00139 
00141 // PLEvent for delayed running of scripts
00143 
00144 class nsScriptLoaderEvent : public PLEvent
00145 {
00146 public:
00147   nsScriptLoaderEvent(nsScriptLoader* aScriptLoader)
00148     : mScriptLoader(aScriptLoader)
00149   {
00150     PL_InitEvent(this, aScriptLoader, Handle, Destroy);
00151   }
00152   
00153   PR_STATIC_CALLBACK(void*) Handle(PLEvent* aEvent);
00154   PR_STATIC_CALLBACK(void) Destroy(PLEvent* aEvent);
00155 
00156   nsRefPtr<nsScriptLoader> mScriptLoader;
00157 };
00158 
00159 /* static */ void * PR_CALLBACK
00160 nsScriptLoaderEvent::Handle(PLEvent* aEvent)
00161 {
00162   NS_STATIC_CAST(nsScriptLoaderEvent*, aEvent)->mScriptLoader->ProcessPendingReqests();
00163 
00164   return nsnull;
00165 }
00166 
00167 /* static */ void PR_CALLBACK
00168 nsScriptLoaderEvent::Destroy(PLEvent* aEvent)
00169 {
00170   delete NS_STATIC_CAST(nsScriptLoaderEvent*, aEvent);
00171 }
00172 
00174 //
00176 
00177 nsScriptLoader::nsScriptLoader()
00178   : mDocument(nsnull),
00179     mEnabled(PR_TRUE),
00180     mHadPendingScripts(PR_FALSE),
00181     mBlockerCount(0)
00182 {
00183 }
00184 
00185 nsScriptLoader::~nsScriptLoader()
00186 {
00187   mObservers.Clear();
00188 
00189   PRInt32 count = mPendingRequests.Count();
00190   for (PRInt32 i = 0; i < count; i++) {
00191     nsScriptLoadRequest* req = mPendingRequests[i];
00192     if (req) {
00193       req->FireScriptAvailable(NS_ERROR_ABORT, EmptyString());
00194     }
00195   }
00196 
00197   mPendingRequests.Clear();
00198 }
00199 
00200 NS_INTERFACE_MAP_BEGIN(nsScriptLoader)
00201   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptLoader)
00202   NS_INTERFACE_MAP_ENTRY(nsIScriptLoader)
00203   NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
00204 NS_INTERFACE_MAP_END
00205 
00206 NS_IMPL_ADDREF(nsScriptLoader)
00207 NS_IMPL_RELEASE(nsScriptLoader)
00208 
00209 /* void init (in nsIDocument aDocument); */
00210 NS_IMETHODIMP
00211 nsScriptLoader::Init(nsIDocument *aDocument)
00212 {
00213   mDocument = aDocument;
00214 
00215   return NS_OK;
00216 }
00217 
00218 /* void dropDocumentReference (); */
00219 NS_IMETHODIMP
00220 nsScriptLoader::DropDocumentReference()
00221 {
00222   mDocument = nsnull;
00223 
00224   return NS_OK;
00225 }
00226 
00227 /* void addObserver (in nsIScriptLoaderObserver aObserver); */
00228 NS_IMETHODIMP
00229 nsScriptLoader::AddObserver(nsIScriptLoaderObserver *aObserver)
00230 {
00231   NS_ENSURE_ARG(aObserver);
00232 
00233   mObservers.AppendObject(aObserver);
00234 
00235   return NS_OK;
00236 }
00237 
00238 /* void removeObserver (in nsIScriptLoaderObserver aObserver); */
00239 NS_IMETHODIMP
00240 nsScriptLoader::RemoveObserver(nsIScriptLoaderObserver *aObserver)
00241 {
00242   NS_ENSURE_ARG(aObserver);
00243 
00244   mObservers.RemoveObject(aObserver);
00245 
00246   return NS_OK;
00247 }
00248 
00249 PRBool
00250 nsScriptLoader::InNonScriptingContainer(nsIScriptElement* aScriptElement)
00251 {
00252   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aScriptElement));
00253   nsCOMPtr<nsIDOMNode> parent;
00254 
00255   node->GetParentNode(getter_AddRefs(parent));
00256   while (parent) {
00257     nsCOMPtr<nsIContent> content(do_QueryInterface(parent));
00258     if (!content) {
00259       break;
00260     }
00261 
00262     nsINodeInfo *nodeInfo = content->GetNodeInfo();
00263     NS_ASSERTION(nodeInfo, "element without node info");
00264 
00265     if (nodeInfo) {
00266       nsIAtom *localName = nodeInfo->NameAtom();
00267 
00268       // XXX noframes and noembed are currently unconditionally not
00269       // displayed and processed. This might change if we support either
00270       // prefs or per-document container settings for not allowing
00271       // frames or plugins.
00272       if (content->IsContentOfType(nsIContent::eHTML) &&
00273           ((localName == nsHTMLAtoms::iframe) ||
00274            (localName == nsHTMLAtoms::noframes) ||
00275            (localName == nsHTMLAtoms::noembed))) {
00276         return PR_TRUE;
00277       }
00278     }
00279 
00280     node = parent;
00281     node->GetParentNode(getter_AddRefs(parent));
00282   }
00283 
00284   return PR_FALSE;
00285 }
00286 
00287 // Helper method for checking if the script element is an event-handler
00288 // This means that it has both a for-attribute and a event-attribute.
00289 // Also, if the for-attribute has a value that matches "\s*window\s*",
00290 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
00291 // eventhandler. (both matches are case insensitive).
00292 // This is how IE seems to filter out a window's onload handler from a
00293 // <script for=... event=...> element.
00294 
00295 PRBool
00296 nsScriptLoader::IsScriptEventHandler(nsIScriptElement *aScriptElement)
00297 {
00298   nsCOMPtr<nsIContent> contElement = do_QueryInterface(aScriptElement);
00299   if (!contElement ||
00300       !contElement->HasAttr(kNameSpaceID_None, nsHTMLAtoms::_event) ||
00301       !contElement->HasAttr(kNameSpaceID_None, nsHTMLAtoms::_for)) {
00302       return PR_FALSE;
00303   }
00304 
00305   nsAutoString str;
00306   nsresult rv = contElement->GetAttr(kNameSpaceID_None, nsHTMLAtoms::_for,
00307                                      str);
00308   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00309 
00310   const nsAString& for_str = nsContentUtils::TrimWhitespace(str);
00311 
00312   if (!for_str.LowerCaseEqualsLiteral("window")) {
00313     return PR_TRUE;
00314   }
00315 
00316   // We found for="window", now check for event="onload".
00317 
00318   rv = contElement->GetAttr(kNameSpaceID_None, nsHTMLAtoms::_event, str);
00319   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00320 
00321   const nsAString& event_str = nsContentUtils::TrimWhitespace(str, PR_FALSE);
00322 
00323   if (event_str.Length() < 6) {
00324     // String too short, can't be "onload".
00325 
00326     return PR_TRUE;
00327   }
00328 
00329   if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
00330                         nsCaseInsensitiveStringComparator())) {
00331     // It ain't "onload.*".
00332 
00333     return PR_TRUE;
00334   }
00335 
00336   nsAutoString::const_iterator start, end;
00337   event_str.BeginReading(start);
00338   event_str.EndReading(end);
00339 
00340   start.advance(6); // advance past "onload"
00341 
00342   if (start != end && *start != '(' && *start != ' ') {
00343     // We got onload followed by something other than space or
00344     // '('. Not good enough.
00345 
00346     return PR_TRUE;
00347   }
00348 
00349   return PR_FALSE;
00350 }
00351 
00352 /* void processScriptElement (in nsIScriptElement aElement, in nsIScriptLoaderObserver aObserver); */
00353 NS_IMETHODIMP
00354 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement,
00355                                      nsIScriptLoaderObserver *aObserver)
00356 {
00357   PRBool fireErrorNotification;
00358   nsresult rv = DoProcessScriptElement(aElement, aObserver,
00359                                        &fireErrorNotification);
00360   if (fireErrorNotification) {
00361     // Note that rv _can_ be a success code here.  It just can't be NS_OK.
00362     NS_ASSERTION(rv != NS_OK, "Firing error notification for NS_OK?");
00363     FireErrorNotification(rv, aElement, aObserver);
00364   }
00365 
00366   return rv;  
00367 }
00368 
00369 nsresult
00370 nsScriptLoader::DoProcessScriptElement(nsIScriptElement *aElement,
00371                                        nsIScriptLoaderObserver *aObserver,
00372                                        PRBool* aFireErrorNotification)
00373 {
00374   // Default to firing the error notification until we've actually gotten to
00375   // loading or running the script.
00376   *aFireErrorNotification = PR_TRUE;
00377   
00378   NS_ENSURE_ARG(aElement);
00379 
00380   // We need a document to evaluate scripts.
00381   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
00382 
00383   // Check to see that the element is not in a container that
00384   // suppresses script evaluation within it and that we should be
00385   // evaluating scripts for this document in the first place.
00386   if (!mEnabled || !mDocument->IsScriptEnabled() ||
00387       InNonScriptingContainer(aElement)) {
00388     return NS_ERROR_NOT_AVAILABLE;
00389   }
00390 
00391   // Check that the script is not an eventhandler
00392   if (IsScriptEventHandler(aElement)) {
00393     return NS_CONTENT_SCRIPT_IS_EVENTHANDLER;
00394   }
00395 
00396   // Script evaluation can also be disabled in the current script
00397   // context even though it's enabled in the document.
00398   nsIScriptGlobalObject *globalObject = mDocument->GetScriptGlobalObject();
00399   if (globalObject)
00400   {
00401     nsIScriptContext *context = globalObject->GetContext();
00402 
00403     // If scripts aren't enabled in the current context, there's no
00404     // point in going on.
00405     if (context && !context->GetScriptsEnabled()) {
00406       return NS_ERROR_NOT_AVAILABLE;
00407     }
00408   }
00409 
00410   PRBool isJavaScript = PR_TRUE;
00411   PRBool hasE4XOption = PR_FALSE;
00412   const char* jsVersionString = nsnull;
00413   nsAutoString language, type, src;
00414 
00415   // "language" is a deprecated attribute of HTML, so we check it only for
00416   // HTML script elements.
00417   nsCOMPtr<nsIDOMHTMLScriptElement> htmlScriptElement =
00418     do_QueryInterface(aElement);
00419   if (htmlScriptElement) {
00420     // Check the language attribute first, so type can trump language.
00421     htmlScriptElement->GetAttribute(NS_LITERAL_STRING("language"), language);
00422     if (!language.IsEmpty()) {
00423       isJavaScript = nsParserUtils::IsJavaScriptLanguage(language,
00424                                                          &jsVersionString);
00425 
00426       // IE, Opera, etc. do not respect language version, so neither should
00427       // we at this late date in the browser wars saga.  Note that this change
00428       // affects HTML but not XUL or SVG (but note also that XUL has its own
00429       // code to check nsParserUtils::IsJavaScriptLanguage -- that's probably
00430       // a separate bug, one we may not be able to fix short of XUL2).  See
00431       // bug 255895 (https://bugzilla.mozilla.org/show_bug.cgi?id=255895).
00432       jsVersionString = ::JS_VersionToString(JSVERSION_DEFAULT);
00433     }
00434   }
00435 
00436   nsresult rv = NS_OK;
00437 
00438   // Check the type attribute to determine language and version.
00439   aElement->GetScriptType(type);
00440   if (!type.IsEmpty()) {
00441     nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParser =
00442       do_GetService("@mozilla.org/network/mime-hdrparam;1");
00443     NS_ENSURE_TRUE(mimeHdrParser, NS_ERROR_FAILURE);
00444 
00445     NS_ConvertUTF16toUTF8 typeAndParams(type);
00446 
00447     nsAutoString mimeType;
00448     rv = mimeHdrParser->GetParameter(typeAndParams, nsnull,
00449                                      EmptyCString(), PR_FALSE, nsnull,
00450                                      mimeType);
00451     NS_ENSURE_SUCCESS(rv, rv);
00452 
00453     // Table ordered from most to least likely JS MIME types.
00454     // See bug 62485, feel free to add <script type="..."> survey data to it,
00455     // or to a new bug once 62485 is closed.
00456     static const char *jsTypes[] = {
00457       "text/javascript",
00458       "text/ecmascript",
00459       "application/javascript",
00460       "application/ecmascript",
00461       "application/x-javascript",
00462       nsnull
00463     };
00464 
00465     isJavaScript = PR_FALSE;
00466     for (PRInt32 i = 0; jsTypes[i]; i++) {
00467       if (mimeType.LowerCaseEqualsASCII(jsTypes[i])) {
00468         isJavaScript = PR_TRUE;
00469         break;
00470       }
00471     }
00472 
00473     if (isJavaScript) {
00474       JSVersion jsVersion = JSVERSION_DEFAULT;
00475       nsAutoString value;
00476       rv = mimeHdrParser->GetParameter(typeAndParams, "version",
00477                                        EmptyCString(), PR_FALSE, nsnull,
00478                                        value);
00479       if (NS_FAILED(rv)) {
00480         if (rv != NS_ERROR_INVALID_ARG)
00481           return rv;
00482       } else {
00483         if (value.Length() != 3 || value[0] != '1' || value[1] != '.')
00484           jsVersion = JSVERSION_UNKNOWN;
00485         else switch (value[2]) {
00486           case '0': jsVersion = JSVERSION_1_0; break;
00487           case '1': jsVersion = JSVERSION_1_1; break;
00488           case '2': jsVersion = JSVERSION_1_2; break;
00489           case '3': jsVersion = JSVERSION_1_3; break;
00490           case '4': jsVersion = JSVERSION_1_4; break;
00491           case '5': jsVersion = JSVERSION_1_5; break;
00492           case '6': jsVersion = JSVERSION_1_6; break;
00493           case '7': jsVersion = JSVERSION_1_7; break;
00494           default:  jsVersion = JSVERSION_UNKNOWN;
00495         }
00496       }
00497       jsVersionString = ::JS_VersionToString(jsVersion);
00498 
00499       rv = mimeHdrParser->GetParameter(typeAndParams, "e4x",
00500                                        EmptyCString(), PR_FALSE, nsnull,
00501                                        value);
00502       if (NS_FAILED(rv)) {
00503         if (rv != NS_ERROR_INVALID_ARG)
00504           return rv;
00505       } else {
00506         if (value.Length() == 1 && value[0] == '1')
00507           hasE4XOption = PR_TRUE;
00508       }
00509     }
00510   }
00511 
00512   // If this isn't JavaScript, we don't know how to evaluate.
00513   // XXX How and where should we deal with other scripting languages?
00514   //     See bug 255942 (https://bugzilla.mozilla.org/show_bug.cgi?id=255942).
00515   if (!isJavaScript) {
00516     return NS_ERROR_NOT_AVAILABLE;
00517   }
00518 
00519   // Create a request object for this script
00520   nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(aElement, aObserver, jsVersionString, hasE4XOption);
00521   NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
00522 
00523   // First check to see if this is an external script
00524   nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
00525   if (scriptURI) {
00526     // Check that the containing page is allowed to load this URI.
00527     nsIPrincipal *docPrincipal = mDocument->GetPrincipal();
00528     NS_ENSURE_TRUE(docPrincipal, NS_ERROR_UNEXPECTED);
00529     rv = nsContentUtils::GetSecurityManager()->
00530       CheckLoadURIWithPrincipal(docPrincipal, scriptURI,
00531                                 nsIScriptSecurityManager::ALLOW_CHROME);
00532 
00533     NS_ENSURE_SUCCESS(rv, rv);
00534 
00535     // After the security manager, the content-policy stuff gets a veto
00536     if (globalObject) {
00537       PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
00538       nsIURI *docURI = mDocument->GetDocumentURI();
00539       rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
00540                                      scriptURI,
00541                                      docURI,
00542                                      aElement,
00543                                      NS_LossyConvertUCS2toASCII(type),
00544                                      nsnull,    //extra
00545                                      &shouldLoad);
00546       if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
00547         if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
00548           return NS_ERROR_CONTENT_BLOCKED;
00549         }
00550         return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
00551       }
00552 
00553       request->mURI = scriptURI;
00554       request->mIsInline = PR_FALSE;
00555       request->mWasPending = PR_TRUE;
00556       request->mLoading = PR_TRUE;
00557 
00558       // Add the request to our pending requests list
00559       mPendingRequests.AppendObject(request);
00560 
00561       nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
00562       nsCOMPtr<nsIStreamLoader> loader;
00563 
00564       nsIDocShell *docshell = globalObject->GetDocShell();
00565 
00566       nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
00567 
00568       nsCOMPtr<nsIChannel> channel;
00569       rv = NS_NewChannel(getter_AddRefs(channel),
00570                          scriptURI, nsnull, loadGroup,
00571                          prompter, nsIRequest::LOAD_NORMAL);
00572       if (NS_SUCCEEDED(rv)) {
00573         nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
00574         if (httpChannel) {
00575           // HTTP content negotation has little value in this context.
00576           httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
00577                                         NS_LITERAL_CSTRING("*/*"),
00578                                         PR_FALSE);
00579           httpChannel->SetReferrer(mDocument->GetDocumentURI());
00580         }
00581         rv = NS_NewStreamLoader(getter_AddRefs(loader), channel, this, request);
00582       }
00583       if (NS_FAILED(rv)) {
00584         mPendingRequests.RemoveObject(request);
00585         return rv;
00586       }
00587 
00588       // At this point we've successfully started the load, so we need not call
00589       // FireErrorNotification anymore.
00590       *aFireErrorNotification = PR_FALSE;
00591     }
00592   } else {
00593     request->mLoading = PR_FALSE;
00594     request->mIsInline = PR_TRUE;
00595     request->mURI = mDocument->GetDocumentURI();
00596 
00597     request->mLineNo = aElement->GetScriptLineNumber();
00598 
00599     // If we've got existing pending requests, add ourselves
00600     // to this list.
00601     if (mPendingRequests.Count() > 0 || mBlockerCount > 0) {
00602       request->mWasPending = PR_TRUE;
00603       NS_ENSURE_TRUE(mPendingRequests.AppendObject(request),
00604                      NS_ERROR_OUT_OF_MEMORY);
00605     }
00606     else {
00607       request->mWasPending = PR_FALSE;
00608       rv = ProcessRequest(request);
00609     }
00610 
00611     // We're either going to, or have run this inline script, so we shouldn't
00612     // call FireErrorNotification for it.
00613     *aFireErrorNotification = PR_FALSE;
00614   }
00615 
00616   return rv;
00617 }
00618 
00619 nsresult
00620 nsScriptLoader::GetCurrentScript(nsIScriptElement **aElement)
00621 {
00622   NS_ENSURE_ARG_POINTER(aElement);
00623   *aElement = mCurrentScript;
00624 
00625   NS_IF_ADDREF(*aElement);
00626 
00627   return NS_OK;
00628 }
00629 
00630 void
00631 nsScriptLoader::FireErrorNotification(nsresult aResult,
00632                                       nsIScriptElement* aElement,
00633                                       nsIScriptLoaderObserver* aObserver)
00634 {
00635   PRInt32 count = mObservers.Count();
00636   for (PRInt32 i = 0; i < count; i++) {
00637     nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i];
00638 
00639     if (observer) {
00640       observer->ScriptAvailable(aResult, aElement,
00641                                 PR_TRUE, PR_FALSE,
00642                                 nsnull, 0,
00643                                 EmptyString());
00644     }
00645   }
00646 
00647   if (aObserver) {
00648     aObserver->ScriptAvailable(aResult, aElement,
00649                                PR_TRUE, PR_FALSE,
00650                                nsnull, 0,
00651                                EmptyString());
00652   }
00653 }
00654 
00655 nsresult
00656 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
00657 {
00658   NS_ENSURE_ARG(aRequest);
00659   nsAFlatString* script;
00660   nsAutoString textData;
00661 
00662   // If there's no script text, we try to get it from the element
00663   if (aRequest->mIsInline) {
00664     // XXX This is inefficient - GetText makes multiple
00665     // copies.
00666     aRequest->mElement->GetScriptText(textData);
00667 
00668     script = &textData;
00669   }
00670   else {
00671     script = &aRequest->mScriptText;
00672   }
00673 
00674   FireScriptAvailable(NS_OK, aRequest, *script);
00675   nsresult rv = EvaluateScript(aRequest, *script);
00676   FireScriptEvaluated(rv, aRequest);
00677 
00678   return rv;
00679 }
00680 
00681 void
00682 nsScriptLoader::FireScriptAvailable(nsresult aResult,
00683                                     nsScriptLoadRequest* aRequest,
00684                                     const nsAFlatString& aScript)
00685 {
00686   PRInt32 count = mObservers.Count();
00687   for (PRInt32 i = 0; i < count; i++) {
00688     nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i];
00689 
00690     if (observer) {
00691       observer->ScriptAvailable(aResult, aRequest->mElement,
00692                                 aRequest->mIsInline, aRequest->mWasPending,
00693                                 aRequest->mURI, aRequest->mLineNo,
00694                                 aScript);
00695     }
00696   }
00697 
00698   aRequest->FireScriptAvailable(aResult, aScript);
00699 }
00700 
00701 void
00702 nsScriptLoader::FireScriptEvaluated(nsresult aResult,
00703                                     nsScriptLoadRequest* aRequest)
00704 {
00705   PRInt32 count = mObservers.Count();
00706   for (PRInt32 i = 0; i < count; i++) {
00707     nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i];
00708 
00709     if (observer) {
00710       observer->ScriptEvaluated(aResult, aRequest->mElement,
00711                                 aRequest->mIsInline, aRequest->mWasPending);
00712     }
00713   }
00714 
00715   aRequest->FireScriptEvaluated(aResult);
00716 }
00717 
00718 nsresult
00719 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
00720                                const nsAFlatString& aScript)
00721 {
00722   nsresult rv = NS_OK;
00723 
00724   // We need a document to evaluate scripts.
00725   if (!mDocument) {
00726     return NS_ERROR_FAILURE;
00727   }
00728 
00729   nsIScriptGlobalObject *globalObject = mDocument->GetScriptGlobalObject();
00730 
00731   nsCOMPtr<nsPIDOMWindow> pwin(do_QueryInterface(globalObject));
00732   NS_ENSURE_TRUE(pwin && pwin->IsInnerWindow(), NS_ERROR_FAILURE);
00733 
00734   // Make sure context is a strong reference since we access it after
00735   // we've executed a script, which may cause all other references to
00736   // the context to go away.
00737   nsCOMPtr<nsIScriptContext> context = globalObject->GetContext();
00738   if (!context) {
00739     return NS_ERROR_FAILURE;
00740   }
00741 
00742   nsIPrincipal *principal = mDocument->GetPrincipal();
00743   // We can survive without a principal, but we really should
00744   // have one.
00745   NS_ASSERTION(principal, "principal required for document");
00746 
00747   nsCAutoString url;
00748 
00749   if (aRequest->mURI) {
00750     rv = aRequest->mURI->GetSpec(url);
00751     if (NS_FAILED(rv)) {
00752       return rv;
00753     }
00754   }
00755 
00756   PRBool oldProcessingScriptTag = context->GetProcessingScriptTag();
00757   context->SetProcessingScriptTag(PR_TRUE);
00758 
00759   JSContext *cx = (JSContext *)context->GetNativeContext();
00760   uint32 options = ::JS_GetOptions(cx);
00761   JSBool changed = (aRequest->mHasE4XOption ^ !!(options & JSOPTION_XML));
00762   if (changed) {
00763     ::JS_SetOptions(cx,
00764                     aRequest->mHasE4XOption
00765                     ? options | JSOPTION_XML
00766                     : options & ~JSOPTION_XML);
00767   }
00768 
00769   // Update our current script.
00770   nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
00771   mCurrentScript = aRequest->mElement;
00772 
00773   PRBool isUndefined;
00774   rv = context->EvaluateString(aScript, globalObject->GetGlobalJSObject(),
00775                                principal, url.get(), aRequest->mLineNo,
00776                                aRequest->mJSVersion, nsnull, &isUndefined);
00777 
00778   // Put the old script back in case it wants to do anything else.
00779   mCurrentScript = oldCurrent;
00780 
00781   ::JS_ReportPendingException(cx);
00782   if (changed) {
00783     ::JS_SetOptions(cx, options);
00784   }
00785 
00786   context->SetProcessingScriptTag(oldProcessingScriptTag);
00787 
00788   nsCOMPtr<nsIXPCNativeCallContext> ncc;
00789   nsContentUtils::XPConnect()->
00790     GetCurrentNativeCallContext(getter_AddRefs(ncc));
00791 
00792   if (ncc) {
00793     ncc->SetExceptionWasThrown(PR_FALSE);
00794   }
00795 
00796   return rv;
00797 }
00798 
00799 void
00800 nsScriptLoader::ProcessPendingRequestsAsync()
00801 {
00802   if (mPendingRequests.Count()) {
00803     nsCOMPtr<nsIEventQueue> uiThreadQueue;
00804     NS_GetMainEventQ(getter_AddRefs(uiThreadQueue));
00805     if (!uiThreadQueue) {
00806       return;
00807     }
00808 
00809     PLEvent *evt = new nsScriptLoaderEvent(this);
00810     if (!evt) {
00811       return;
00812     }
00813 
00814     nsresult rv = uiThreadQueue->PostEvent(evt);
00815     if (NS_FAILED(rv)) {
00816       PL_DestroyEvent(evt);
00817     }
00818   }
00819 }
00820 
00821 void
00822 nsScriptLoader::ProcessPendingReqests()
00823 {
00824   nsRefPtr<nsScriptLoadRequest> request = mPendingRequests.SafeObjectAt(0);
00825   while (request && !request->mLoading && mBlockerCount == 0) {
00826     mPendingRequests.RemoveObjectAt(0);
00827     ProcessRequest(request);
00828     request = mPendingRequests.SafeObjectAt(0);
00829   }
00830 }
00831 
00832 
00833 // This function was copied from nsParser.cpp. It was simplified a bit.
00834 static PRBool
00835 DetectByteOrderMark(const unsigned char* aBytes, PRInt32 aLen, nsCString& oCharset)
00836 {
00837   if (aLen < 2)
00838     return PR_FALSE;
00839 
00840   switch(aBytes[0]) {
00841   case 0xEF:
00842     if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) {
00843       // EF BB BF
00844       // Win2K UTF-8 BOM
00845       oCharset.Assign("UTF-8");
00846     }
00847     break;
00848   case 0xFE:
00849     if (0xFF == aBytes[1]) {
00850       // FE FF
00851       // UTF-16, big-endian
00852       oCharset.Assign("UTF-16BE");
00853     }
00854     break;
00855   case 0xFF:
00856     if (0xFE == aBytes[1]) {
00857       // FF FE
00858       // UTF-16, little-endian
00859       oCharset.Assign("UTF-16LE");
00860     }
00861     break;
00862   }
00863   return !oCharset.IsEmpty();
00864 }
00865 
00866 /* static */ nsresult
00867 nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const PRUint8* aData,
00868                                PRUint32 aLength, const nsString& aHintCharset,
00869                                nsIDocument* aDocument, nsString& aString)
00870 {
00871   if (!aLength) {
00872     aString.Truncate();
00873     return NS_OK;
00874   }
00875 
00876   nsCAutoString characterSet;
00877 
00878   nsresult rv = NS_OK;
00879   if (aChannel) {
00880     rv = aChannel->GetContentCharset(characterSet);
00881   }
00882 
00883   if (!aHintCharset.IsEmpty() && (NS_FAILED(rv) || characterSet.IsEmpty())) {
00884     // charset name is always ASCII.
00885     LossyCopyUTF16toASCII(aHintCharset, characterSet);
00886   }
00887 
00888   if (NS_FAILED(rv) || characterSet.IsEmpty()) {
00889     DetectByteOrderMark(aData, aLength, characterSet);
00890   }
00891 
00892   if (characterSet.IsEmpty()) {
00893     // charset from document default
00894     characterSet = aDocument->GetDocumentCharacterSet();
00895   }
00896 
00897   if (characterSet.IsEmpty()) {
00898     // fall back to ISO-8859-1, see bug 118404
00899     characterSet.AssignLiteral("ISO-8859-1");
00900   }
00901 
00902   nsCOMPtr<nsICharsetConverterManager> charsetConv =
00903     do_GetService(kCharsetConverterManagerCID, &rv);
00904 
00905   nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
00906 
00907   if (NS_SUCCEEDED(rv) && charsetConv) {
00908     rv = charsetConv->GetUnicodeDecoder(characterSet.get(),
00909                                         getter_AddRefs(unicodeDecoder));
00910     if (NS_FAILED(rv)) {
00911       // fall back to ISO-8859-1 if charset is not supported. (bug 230104)
00912       rv = charsetConv->GetUnicodeDecoderRaw("ISO-8859-1",
00913                                              getter_AddRefs(unicodeDecoder));
00914     }
00915   }
00916 
00917   // converts from the charset to unicode
00918   if (NS_SUCCEEDED(rv)) {
00919     PRInt32 unicodeLength = 0;
00920 
00921     rv = unicodeDecoder->GetMaxLength(NS_REINTERPRET_CAST(const char*, aData),
00922                                       aLength, &unicodeLength);
00923     if (NS_SUCCEEDED(rv)) {
00924       if (!EnsureStringLength(aString, unicodeLength))
00925         return NS_ERROR_OUT_OF_MEMORY;
00926 
00927       PRUnichar *ustr = aString.BeginWriting();
00928 
00929       PRInt32 consumedLength = 0;
00930       PRInt32 originalLength = aLength;
00931       PRInt32 convertedLength = 0;
00932       PRInt32 bufferLength = unicodeLength;
00933       do {
00934         rv = unicodeDecoder->Convert(NS_REINTERPRET_CAST(const char*, aData),
00935                                      (PRInt32 *) &aLength, ustr,
00936                                      &unicodeLength);
00937         if (NS_FAILED(rv)) {
00938           // if we failed, we consume one byte, replace it with U+FFFD
00939           // and try the conversion again.
00940           ustr[unicodeLength++] = (PRUnichar)0xFFFD;
00941           ustr += unicodeLength;
00942 
00943           unicodeDecoder->Reset();
00944         }
00945         aData += ++aLength;
00946         consumedLength += aLength;
00947         aLength = originalLength - consumedLength;
00948         convertedLength += unicodeLength;
00949         unicodeLength = bufferLength - convertedLength;
00950       } while (NS_FAILED(rv) && (originalLength > consumedLength) && (bufferLength > convertedLength));
00951       aString.SetLength(convertedLength);
00952     }
00953   }
00954   return rv;
00955 }
00956 
00957 NS_IMETHODIMP
00958 nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
00959                                  nsISupports* aContext,
00960                                  nsresult aStatus,
00961                                  PRUint32 stringLen,
00962                                  const PRUint8* string)
00963 {
00964   nsresult rv;
00965   nsScriptLoadRequest* request = NS_STATIC_CAST(nsScriptLoadRequest*, aContext);
00966   NS_ASSERTION(request, "null request in stream complete handler");
00967   if (!request) {
00968     return NS_ERROR_FAILURE;
00969   }
00970 
00971   if (NS_FAILED(aStatus)) {
00972     mPendingRequests.RemoveObject(request);
00973     FireScriptAvailable(aStatus, request, EmptyString());
00974     ProcessPendingReqests();
00975     return NS_OK;
00976   }
00977 
00978   // If we don't have a document, then we need to abort further
00979   // evaluation.
00980   if (!mDocument) {
00981     mPendingRequests.RemoveObject(request);
00982     FireScriptAvailable(NS_ERROR_NOT_AVAILABLE, request,
00983                         EmptyString());
00984     ProcessPendingReqests();
00985     return NS_OK;
00986   }
00987 
00988   // If the load returned an error page, then we need to abort
00989   nsCOMPtr<nsIRequest> req;
00990   rv = aLoader->GetRequest(getter_AddRefs(req));
00991   NS_ASSERTION(req, "StreamLoader's request went away prematurely");
00992   if (NS_FAILED(rv)) return rv;  // XXX Should this remove the pending request?
00993   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(req));
00994   if (httpChannel) {
00995     PRBool requestSucceeded;
00996     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
00997     if (NS_SUCCEEDED(rv) && !requestSucceeded) {
00998       mPendingRequests.RemoveObject(request);
00999       FireScriptAvailable(NS_ERROR_NOT_AVAILABLE, request,
01000                           EmptyString());
01001       ProcessPendingReqests();
01002       return NS_OK;
01003     }
01004   }
01005 
01006   nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
01007   if (stringLen) {
01008     // Check the charset attribute to determine script charset.
01009     nsAutoString hintCharset;
01010     request->mElement->GetScriptCharset(hintCharset);
01011     rv = ConvertToUTF16(channel, string, stringLen, hintCharset, mDocument,
01012                         request->mScriptText);
01013 
01014     NS_ASSERTION(NS_SUCCEEDED(rv),
01015                  "Could not convert external JavaScript to Unicode!");
01016     if (NS_FAILED(rv)) {
01017       mPendingRequests.RemoveObject(request);
01018       FireScriptAvailable(rv, request, EmptyString());
01019       ProcessPendingReqests();
01020       return NS_OK;
01021     }
01022 
01023     if (!ShouldExecuteScript(mDocument, channel)) {
01024       return NS_ERROR_NOT_AVAILABLE;
01025     }
01026   }
01027 
01028 
01029   // If we're not the first in the pending list, we mark ourselves
01030   // as loaded and just stay on the list.
01031   NS_ASSERTION(mPendingRequests.Count() > 0, "aContext is a pending request!");
01032   if (mPendingRequests[0] != request) {
01033     request->mLoading = PR_FALSE;
01034     return NS_OK;
01035   }
01036 
01037   mPendingRequests.RemoveObject(request);
01038   ProcessRequest(request);
01039 
01040   // Process any pending requests
01041   ProcessPendingReqests();
01042 
01043   return NS_OK;
01044 }
01045 
01053 static nsresult
01054 NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri)
01055 {
01056     *uri = nsnull;
01057     nsLoadFlags loadFlags = 0;
01058     nsresult rv = channel->GetLoadFlags(&loadFlags);
01059     NS_ENSURE_SUCCESS(rv, rv);
01060     
01061     if (loadFlags & nsIChannel::LOAD_REPLACE) {
01062         return channel->GetURI(uri);
01063     }
01064     
01065     return channel->GetOriginalURI(uri);
01066 }
01067 
01068 static nsresult
01069 GetChannelPrincipal(nsIChannel* aChannel, nsIPrincipal** aPrincipal)
01070 {
01071   NS_PRECONDITION(aChannel, "Must have channel!");
01072   nsCOMPtr<nsISupports> owner;
01073   aChannel->GetOwner(getter_AddRefs(owner));
01074   if (owner) {
01075     CallQueryInterface(owner, aPrincipal);
01076     if (*aPrincipal) {
01077       return NS_OK;
01078     }
01079   }
01080 
01081   // OK, get the principal from the URI.  Make sure this does the same thing
01082   // as nsDocument::Reset and nsXULDocument::StartDocumentLoad.
01083   nsCOMPtr<nsIURI> uri;
01084   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
01085   NS_ENSURE_SUCCESS(rv, rv);
01086 
01087   return nsContentUtils::GetSecurityManager()->
01088     GetCodebasePrincipal(uri, aPrincipal);
01089 }
01090 
01091 /* static */
01092 PRBool
01093 nsScriptLoader::ShouldExecuteScript(nsIDocument* aDocument,
01094                                     nsIChannel* aChannel)
01095 {
01096   if (!aChannel) {
01097     return PR_FALSE;
01098   }
01099 
01100   PRBool hasCert;
01101   nsIPrincipal *docPrincipal = aDocument->GetPrincipal();
01102   docPrincipal->GetHasCertificate(&hasCert);
01103   if (!hasCert) {
01104     return PR_TRUE;
01105   }
01106 
01107   nsCOMPtr<nsIPrincipal> channelPrincipal;
01108   nsresult rv = GetChannelPrincipal(aChannel,
01109                                     getter_AddRefs(channelPrincipal));
01110   NS_ENSURE_SUCCESS(rv, PR_FALSE);
01111 
01112   NS_ASSERTION(channelPrincipal, "Gotta have a principal here!");
01113 
01114   // If the document principal is a cert principal and is not the same
01115   // as the channel principal, then we don't execute the script.
01116   PRBool equal;
01117   rv = docPrincipal->Equals(channelPrincipal, &equal);
01118   return NS_SUCCEEDED(rv) && equal;
01119 }
01120 
01121 NS_IMETHODIMP
01122 nsScriptLoader::GetEnabled(PRBool *aEnabled)
01123 {
01124   NS_ENSURE_ARG_POINTER(aEnabled);
01125   *aEnabled = mEnabled;
01126   return NS_OK;
01127 }
01128 
01129 NS_IMETHODIMP
01130 nsScriptLoader::SetEnabled(PRBool aEnabled)
01131 {
01132   mEnabled = aEnabled;
01133   return NS_OK;
01134 }