/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // vim: ft=cpp tw=78 sw=2 et ts=2 /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla. * * The Initial Developer of the Original Code is * Netscape Communications. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Vidur Apparao <vidur@netscape.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsScriptLoader.h" #include "nsIDOMCharacterData.h" #include "nsParserUtils.h" #include "nsIMIMEHeaderParam.h" #include "nsICharsetConverterManager.h" #include "nsIUnicodeDecoder.h" #include "nsIContent.h" #include "nsHTMLAtoms.h" #include "nsNetUtil.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptContext.h" #include "nsINodeInfo.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsContentPolicyUtils.h" #include "nsPIDOMWindow.h" #include "nsIHttpChannel.h" #include "nsIScriptElement.h" #include "nsIDOMHTMLScriptElement.h" #include "nsIDocShell.h" #include "jsapi.h" #include "nsContentUtils.h" #include "nsUnicharUtils.h" #include "nsAutoPtr.h" #include "nsIEventQueue.h" #include "nsEventQueueUtils.h" static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); ////////////////////////////////////////////////////////////// // Per-request data structure ////////////////////////////////////////////////////////////// class nsScriptLoadRequest : public nsISupports { public: nsScriptLoadRequest(nsIScriptElement* aElement, nsIScriptLoaderObserver* aObserver, const char* aVersionString, PRBool aHasE4XOption); virtual ~nsScriptLoadRequest(); NS_DECL_ISUPPORTS void FireScriptAvailable(nsresult aResult, const nsAFlatString& aScript); void FireScriptEvaluated(nsresult aResult); nsCOMPtr<nsIScriptElement> mElement; nsCOMPtr<nsIScriptLoaderObserver> mObserver; PRPackedBool mLoading; // Are we still waiting for a load to complete? PRPackedBool mWasPending; // Processed immediately or pending PRPackedBool mIsInline; // Is the script inline or loaded? PRPackedBool mHasE4XOption; // Has E4X=1 script type parameter nsString mScriptText; // Holds script for loaded scripts const char* mJSVersion; // We don't own this string nsCOMPtr<nsIURI> mURI; PRInt32 mLineNo; }; nsScriptLoadRequest::nsScriptLoadRequest(nsIScriptElement* aElement, nsIScriptLoaderObserver* aObserver, const char* aVersionString, PRBool aHasE4XOption) : mElement(aElement), mObserver(aObserver), mLoading(PR_TRUE), mWasPending(PR_FALSE), mIsInline(PR_TRUE), mHasE4XOption(aHasE4XOption), mJSVersion(aVersionString), mLineNo(1) { } nsScriptLoadRequest::~nsScriptLoadRequest() { } // The nsScriptLoadRequest is passed as the context to necko, and thus // it needs to be threadsafe. Necko won't do anything with this // context, but it will AddRef and Release it on other threads. NS_IMPL_THREADSAFE_ISUPPORTS0(nsScriptLoadRequest) void nsScriptLoadRequest::FireScriptAvailable(nsresult aResult, const nsAFlatString& aScript) { if (mObserver) { mObserver->ScriptAvailable(aResult, mElement, mIsInline, mWasPending, mURI, mLineNo, aScript); } } void nsScriptLoadRequest::FireScriptEvaluated(nsresult aResult) { if (mObserver) { mObserver->ScriptEvaluated(aResult, mElement, mIsInline, mWasPending); } } ////////////////////////////////////////////////////////////// // PLEvent for delayed running of scripts ////////////////////////////////////////////////////////////// class nsScriptLoaderEvent : public PLEvent { public: nsScriptLoaderEvent(nsScriptLoader* aScriptLoader) : mScriptLoader(aScriptLoader) { PL_InitEvent(this, aScriptLoader, Handle, Destroy); } PR_STATIC_CALLBACK(void*) Handle(PLEvent* aEvent); PR_STATIC_CALLBACK(void) Destroy(PLEvent* aEvent); nsRefPtr<nsScriptLoader> mScriptLoader; }; /* static */ void * PR_CALLBACK nsScriptLoaderEvent::Handle(PLEvent* aEvent) { NS_STATIC_CAST(nsScriptLoaderEvent*, aEvent)->mScriptLoader->ProcessPendingReqests(); return nsnull; } /* static */ void PR_CALLBACK nsScriptLoaderEvent::Destroy(PLEvent* aEvent) { delete NS_STATIC_CAST(nsScriptLoaderEvent*, aEvent); } ////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////// nsScriptLoader::nsScriptLoader() : mDocument(nsnull), mEnabled(PR_TRUE), mHadPendingScripts(PR_FALSE), mBlockerCount(0) { } nsScriptLoader::~nsScriptLoader() { mObservers.Clear(); PRInt32 count = mPendingRequests.Count(); for (PRInt32 i = 0; i < count; i++) { nsScriptLoadRequest* req = mPendingRequests[i]; if (req) { req->FireScriptAvailable(NS_ERROR_ABORT, EmptyString()); } } mPendingRequests.Clear(); } NS_INTERFACE_MAP_BEGIN(nsScriptLoader) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptLoader) NS_INTERFACE_MAP_ENTRY(nsIScriptLoader) NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(nsScriptLoader) NS_IMPL_RELEASE(nsScriptLoader) /* void init (in nsIDocument aDocument); */ NS_IMETHODIMP nsScriptLoader::Init(nsIDocument *aDocument) { mDocument = aDocument; return NS_OK; } /* void dropDocumentReference (); */ NS_IMETHODIMP nsScriptLoader::DropDocumentReference() { mDocument = nsnull; return NS_OK; } /* void addObserver (in nsIScriptLoaderObserver aObserver); */ NS_IMETHODIMP nsScriptLoader::AddObserver(nsIScriptLoaderObserver *aObserver) { NS_ENSURE_ARG(aObserver); mObservers.AppendObject(aObserver); return NS_OK; } /* void removeObserver (in nsIScriptLoaderObserver aObserver); */ NS_IMETHODIMP nsScriptLoader::RemoveObserver(nsIScriptLoaderObserver *aObserver) { NS_ENSURE_ARG(aObserver); mObservers.RemoveObject(aObserver); return NS_OK; } PRBool nsScriptLoader::InNonScriptingContainer(nsIScriptElement* aScriptElement) { nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aScriptElement)); nsCOMPtr<nsIDOMNode> parent; node->GetParentNode(getter_AddRefs(parent)); while (parent) { nsCOMPtr<nsIContent> content(do_QueryInterface(parent)); if (!content) { break; } nsINodeInfo *nodeInfo = content->GetNodeInfo(); NS_ASSERTION(nodeInfo, "element without node info"); if (nodeInfo) { nsIAtom *localName = nodeInfo->NameAtom(); // XXX noframes and noembed are currently unconditionally not // displayed and processed. This might change if we support either // prefs or per-document container settings for not allowing // frames or plugins. if (content->IsContentOfType(nsIContent::eHTML) && ((localName == nsHTMLAtoms::iframe) || (localName == nsHTMLAtoms::noframes) || (localName == nsHTMLAtoms::noembed))) { return PR_TRUE; } } node = parent; node->GetParentNode(getter_AddRefs(parent)); } return PR_FALSE; } // Helper method for checking if the script element is an event-handler // This means that it has both a for-attribute and a event-attribute. // Also, if the for-attribute has a value that matches "\s*window\s*", // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an // eventhandler. (both matches are case insensitive). // This is how IE seems to filter out a window's onload handler from a // <script for=... event=...> element. PRBool nsScriptLoader::IsScriptEventHandler(nsIScriptElement *aScriptElement) { nsCOMPtr<nsIContent> contElement = do_QueryInterface(aScriptElement); if (!contElement || !contElement->HasAttr(kNameSpaceID_None, nsHTMLAtoms::_event) || !contElement->HasAttr(kNameSpaceID_None, nsHTMLAtoms::_for)) { return PR_FALSE; } nsAutoString str; nsresult rv = contElement->GetAttr(kNameSpaceID_None, nsHTMLAtoms::_for, str); NS_ENSURE_SUCCESS(rv, PR_FALSE); const nsAString& for_str = nsContentUtils::TrimWhitespace(str); if (!for_str.LowerCaseEqualsLiteral("window")) { return PR_TRUE; } // We found for="window", now check for event="onload". rv = contElement->GetAttr(kNameSpaceID_None, nsHTMLAtoms::_event, str); NS_ENSURE_SUCCESS(rv, PR_FALSE); const nsAString& event_str = nsContentUtils::TrimWhitespace(str, PR_FALSE); if (event_str.Length() < 6) { // String too short, can't be "onload". return PR_TRUE; } if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"), nsCaseInsensitiveStringComparator())) { // It ain't "onload.*". return PR_TRUE; } nsAutoString::const_iterator start, end; event_str.BeginReading(start); event_str.EndReading(end); start.advance(6); // advance past "onload" if (start != end && *start != '(' && *start != ' ') { // We got onload followed by something other than space or // '('. Not good enough. return PR_TRUE; } return PR_FALSE; } /* void processScriptElement (in nsIScriptElement aElement, in nsIScriptLoaderObserver aObserver); */ NS_IMETHODIMP nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement, nsIScriptLoaderObserver *aObserver) { PRBool fireErrorNotification; nsresult rv = DoProcessScriptElement(aElement, aObserver, &fireErrorNotification); if (fireErrorNotification) { // Note that rv _can_ be a success code here. It just can't be NS_OK. NS_ASSERTION(rv != NS_OK, "Firing error notification for NS_OK?"); FireErrorNotification(rv, aElement, aObserver); } return rv; } nsresult nsScriptLoader::DoProcessScriptElement(nsIScriptElement *aElement, nsIScriptLoaderObserver *aObserver, PRBool* aFireErrorNotification) { // Default to firing the error notification until we've actually gotten to // loading or running the script. *aFireErrorNotification = PR_TRUE; NS_ENSURE_ARG(aElement); // We need a document to evaluate scripts. NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); // Check to see that the element is not in a container that // suppresses script evaluation within it and that we should be // evaluating scripts for this document in the first place. if (!mEnabled || !mDocument->IsScriptEnabled() || InNonScriptingContainer(aElement)) { return NS_ERROR_NOT_AVAILABLE; } // Check that the script is not an eventhandler if (IsScriptEventHandler(aElement)) { return NS_CONTENT_SCRIPT_IS_EVENTHANDLER; } // Script evaluation can also be disabled in the current script // context even though it's enabled in the document. nsIScriptGlobalObject *globalObject = mDocument->GetScriptGlobalObject(); if (globalObject) { nsIScriptContext *context = globalObject->GetContext(); // If scripts aren't enabled in the current context, there's no // point in going on. if (context && !context->GetScriptsEnabled()) { return NS_ERROR_NOT_AVAILABLE; } } PRBool isJavaScript = PR_TRUE; PRBool hasE4XOption = PR_FALSE; const char* jsVersionString = nsnull; nsAutoString language, type, src; // "language" is a deprecated attribute of HTML, so we check it only for // HTML script elements. nsCOMPtr<nsIDOMHTMLScriptElement> htmlScriptElement = do_QueryInterface(aElement); if (htmlScriptElement) { // Check the language attribute first, so type can trump language. htmlScriptElement->GetAttribute(NS_LITERAL_STRING("language"), language); if (!language.IsEmpty()) { isJavaScript = nsParserUtils::IsJavaScriptLanguage(language, &jsVersionString); // IE, Opera, etc. do not respect language version, so neither should // we at this late date in the browser wars saga. Note that this change // affects HTML but not XUL or SVG (but note also that XUL has its own // code to check nsParserUtils::IsJavaScriptLanguage -- that's probably // a separate bug, one we may not be able to fix short of XUL2). See // bug 255895 (https://bugzilla.mozilla.org/show_bug.cgi?id=255895). jsVersionString = ::JS_VersionToString(JSVERSION_DEFAULT); } } nsresult rv = NS_OK; // Check the type attribute to determine language and version. aElement->GetScriptType(type); if (!type.IsEmpty()) { nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParser = do_GetService("@mozilla.org/network/mime-hdrparam;1"); NS_ENSURE_TRUE(mimeHdrParser, NS_ERROR_FAILURE); NS_ConvertUTF16toUTF8 typeAndParams(type); nsAutoString mimeType; rv = mimeHdrParser->GetParameter(typeAndParams, nsnull, EmptyCString(), PR_FALSE, nsnull, mimeType); NS_ENSURE_SUCCESS(rv, rv); // Table ordered from most to least likely JS MIME types. // See bug 62485, feel free to add <script type="..."> survey data to it, // or to a new bug once 62485 is closed. static const char *jsTypes[] = { "text/javascript", "text/ecmascript", "application/javascript", "application/ecmascript", "application/x-javascript", nsnull }; isJavaScript = PR_FALSE; for (PRInt32 i = 0; jsTypes[i]; i++) { if (mimeType.LowerCaseEqualsASCII(jsTypes[i])) { isJavaScript = PR_TRUE; break; } } if (isJavaScript) { JSVersion jsVersion = JSVERSION_DEFAULT; nsAutoString value; rv = mimeHdrParser->GetParameter(typeAndParams, "version", EmptyCString(), PR_FALSE, nsnull, value); if (NS_FAILED(rv)) { if (rv != NS_ERROR_INVALID_ARG) return rv; } else { if (value.Length() != 3 || value[0] != '1' || value[1] != '.') jsVersion = JSVERSION_UNKNOWN; else switch (value[2]) { case '0': jsVersion = JSVERSION_1_0; break; case '1': jsVersion = JSVERSION_1_1; break; case '2': jsVersion = JSVERSION_1_2; break; case '3': jsVersion = JSVERSION_1_3; break; case '4': jsVersion = JSVERSION_1_4; break; case '5': jsVersion = JSVERSION_1_5; break; case '6': jsVersion = JSVERSION_1_6; break; default: jsVersion = JSVERSION_UNKNOWN; } } jsVersionString = ::JS_VersionToString(jsVersion); rv = mimeHdrParser->GetParameter(typeAndParams, "e4x", EmptyCString(), PR_FALSE, nsnull, value); if (NS_FAILED(rv)) { if (rv != NS_ERROR_INVALID_ARG) return rv; } else { if (value.Length() == 1 && value[0] == '1') hasE4XOption = PR_TRUE; } } } // If this isn't JavaScript, we don't know how to evaluate. // XXX How and where should we deal with other scripting languages? // See bug 255942 (https://bugzilla.mozilla.org/show_bug.cgi?id=255942). if (!isJavaScript) { return NS_ERROR_NOT_AVAILABLE; } // Create a request object for this script nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(aElement, aObserver, jsVersionString, hasE4XOption); NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY); // First check to see if this is an external script nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI(); if (scriptURI) { // Check that the containing page is allowed to load this URI. nsIPrincipal *docPrincipal = mDocument->GetPrincipal(); NS_ENSURE_TRUE(docPrincipal, NS_ERROR_UNEXPECTED); rv = nsContentUtils::GetSecurityManager()-> CheckLoadURIWithPrincipal(docPrincipal, scriptURI, nsIScriptSecurityManager::ALLOW_CHROME); NS_ENSURE_SUCCESS(rv, rv); // After the security manager, the content-policy stuff gets a veto if (globalObject) { PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; nsIURI *docURI = mDocument->GetDocumentURI(); rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, scriptURI, docURI, aElement, NS_LossyConvertUCS2toASCII(type), nsnull, //extra &shouldLoad); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { return NS_ERROR_CONTENT_BLOCKED; } return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; } request->mURI = scriptURI; request->mIsInline = PR_FALSE; request->mWasPending = PR_TRUE; request->mLoading = PR_TRUE; // Add the request to our pending requests list mPendingRequests.AppendObject(request); nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup(); nsCOMPtr<nsIStreamLoader> loader; nsIDocShell *docshell = globalObject->GetDocShell(); nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell)); nsCOMPtr<nsIChannel> channel; rv = NS_NewChannel(getter_AddRefs(channel), scriptURI, nsnull, loadGroup, prompter, nsIRequest::LOAD_NORMAL); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); if (httpChannel) { // HTTP content negotation has little value in this context. httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), NS_LITERAL_CSTRING("*/*"), PR_FALSE); httpChannel->SetReferrer(mDocument->GetDocumentURI()); } rv = NS_NewStreamLoader(getter_AddRefs(loader), channel, this, request); } if (NS_FAILED(rv)) { mPendingRequests.RemoveObject(request); return rv; } // At this point we've successfully started the load, so we need not call // FireErrorNotification anymore. *aFireErrorNotification = PR_FALSE; } } else { request->mLoading = PR_FALSE; request->mIsInline = PR_TRUE; request->mURI = mDocument->GetDocumentURI(); request->mLineNo = aElement->GetScriptLineNumber(); // If we've got existing pending requests, add ourselves // to this list. if (mPendingRequests.Count() > 0 || mBlockerCount > 0) { request->mWasPending = PR_TRUE; NS_ENSURE_TRUE(mPendingRequests.AppendObject(request), NS_ERROR_OUT_OF_MEMORY); } else { request->mWasPending = PR_FALSE; rv = ProcessRequest(request); } // We're either going to, or have run this inline script, so we shouldn't // call FireErrorNotification for it. *aFireErrorNotification = PR_FALSE; } return rv; } nsresult nsScriptLoader::GetCurrentScript(nsIScriptElement **aElement) { NS_ENSURE_ARG_POINTER(aElement); *aElement = mCurrentScript; NS_IF_ADDREF(*aElement); return NS_OK; } void nsScriptLoader::FireErrorNotification(nsresult aResult, nsIScriptElement* aElement, nsIScriptLoaderObserver* aObserver) { PRInt32 count = mObservers.Count(); for (PRInt32 i = 0; i < count; i++) { nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i]; if (observer) { observer->ScriptAvailable(aResult, aElement, PR_TRUE, PR_FALSE, nsnull, 0, EmptyString()); } } if (aObserver) { aObserver->ScriptAvailable(aResult, aElement, PR_TRUE, PR_FALSE, nsnull, 0, EmptyString()); } } nsresult nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest) { NS_ENSURE_ARG(aRequest); nsAFlatString* script; nsAutoString textData; // If there's no script text, we try to get it from the element if (aRequest->mIsInline) { // XXX This is inefficient - GetText makes multiple // copies. aRequest->mElement->GetScriptText(textData); script = &textData; } else { script = &aRequest->mScriptText; } FireScriptAvailable(NS_OK, aRequest, *script); nsresult rv = EvaluateScript(aRequest, *script); FireScriptEvaluated(rv, aRequest); return rv; } void nsScriptLoader::FireScriptAvailable(nsresult aResult, nsScriptLoadRequest* aRequest, const nsAFlatString& aScript) { PRInt32 count = mObservers.Count(); for (PRInt32 i = 0; i < count; i++) { nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i]; if (observer) { observer->ScriptAvailable(aResult, aRequest->mElement, aRequest->mIsInline, aRequest->mWasPending, aRequest->mURI, aRequest->mLineNo, aScript); } } aRequest->FireScriptAvailable(aResult, aScript); } void nsScriptLoader::FireScriptEvaluated(nsresult aResult, nsScriptLoadRequest* aRequest) { PRInt32 count = mObservers.Count(); for (PRInt32 i = 0; i < count; i++) { nsCOMPtr<nsIScriptLoaderObserver> observer = mObservers[i]; if (observer) { observer->ScriptEvaluated(aResult, aRequest->mElement, aRequest->mIsInline, aRequest->mWasPending); } } aRequest->FireScriptEvaluated(aResult); } nsresult nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, const nsAFlatString& aScript) { nsresult rv = NS_OK; // We need a document to evaluate scripts. if (!mDocument) { return NS_ERROR_FAILURE; } nsIScriptGlobalObject *globalObject = mDocument->GetScriptGlobalObject(); nsCOMPtr<nsPIDOMWindow> pwin(do_QueryInterface(globalObject)); NS_ENSURE_TRUE(pwin && pwin->IsInnerWindow(), NS_ERROR_FAILURE); // Make sure context is a strong reference since we access it after // we've executed a script, which may cause all other references to // the context to go away. nsCOMPtr<nsIScriptContext> context = globalObject->GetContext(); if (!context) { return NS_ERROR_FAILURE; } nsIPrincipal *principal = mDocument->GetPrincipal(); // We can survive without a principal, but we really should // have one. NS_ASSERTION(principal, "principal required for document"); nsCAutoString url; if (aRequest->mURI) { rv = aRequest->mURI->GetSpec(url); if (NS_FAILED(rv)) { return rv; } } PRBool oldProcessingScriptTag = context->GetProcessingScriptTag(); context->SetProcessingScriptTag(PR_TRUE); JSContext *cx = (JSContext *)context->GetNativeContext(); uint32 options = ::JS_GetOptions(cx); JSBool changed = (aRequest->mHasE4XOption ^ !!(options & JSOPTION_XML)); if (changed) { ::JS_SetOptions(cx, aRequest->mHasE4XOption ? options | JSOPTION_XML : options & ~JSOPTION_XML); } // Update our current script. nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript; mCurrentScript = aRequest->mElement; PRBool isUndefined; context->EvaluateString(aScript, globalObject->GetGlobalJSObject(), principal, url.get(), aRequest->mLineNo, aRequest->mJSVersion, nsnull, &isUndefined); // Put the old script back in case it wants to do anything else. mCurrentScript = oldCurrent; ::JS_ReportPendingException(cx); if (changed) { ::JS_SetOptions(cx, options); } context->SetProcessingScriptTag(oldProcessingScriptTag); nsCOMPtr<nsIXPCNativeCallContext> ncc; nsContentUtils::XPConnect()-> GetCurrentNativeCallContext(getter_AddRefs(ncc)); if (ncc) { ncc->SetExceptionWasThrown(PR_FALSE); } return rv; } void nsScriptLoader::ProcessPendingRequestsAsync() { if (mPendingRequests.Count()) { nsCOMPtr<nsIEventQueue> uiThreadQueue; NS_GetMainEventQ(getter_AddRefs(uiThreadQueue)); if (!uiThreadQueue) { return; } PLEvent *evt = new nsScriptLoaderEvent(this); if (!evt) { return; } nsresult rv = uiThreadQueue->PostEvent(evt); if (NS_FAILED(rv)) { PL_DestroyEvent(evt); } } } void nsScriptLoader::ProcessPendingReqests() { nsRefPtr<nsScriptLoadRequest> request = mPendingRequests.SafeObjectAt(0); while (request && !request->mLoading && mBlockerCount == 0) { mPendingRequests.RemoveObjectAt(0); ProcessRequest(request); request = mPendingRequests.SafeObjectAt(0); } } // This function was copied from nsParser.cpp. It was simplified a bit. static PRBool DetectByteOrderMark(const unsigned char* aBytes, PRInt32 aLen, nsCString& oCharset) { if (aLen < 2) return PR_FALSE; switch(aBytes[0]) { case 0xEF: if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) { // EF BB BF // Win2K UTF-8 BOM oCharset.Assign("UTF-8"); } break; case 0xFE: if (0xFF == aBytes[1]) { // FE FF // UTF-16, big-endian oCharset.Assign("UTF-16BE"); } break; case 0xFF: if (0xFE == aBytes[1]) { // FF FE // UTF-16, little-endian oCharset.Assign("UTF-16LE"); } break; } return !oCharset.IsEmpty(); } /* static */ nsresult nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const PRUint8* aData, PRUint32 aLength, const nsString& aHintCharset, nsIDocument* aDocument, nsString& aString) { if (!aLength) { aString.Truncate(); return NS_OK; } nsCAutoString characterSet; nsresult rv = NS_OK; if (aChannel) { rv = aChannel->GetContentCharset(characterSet); } if (!aHintCharset.IsEmpty() && (NS_FAILED(rv) || characterSet.IsEmpty())) { // charset name is always ASCII. LossyCopyUTF16toASCII(aHintCharset, characterSet); } if (NS_FAILED(rv) || characterSet.IsEmpty()) { DetectByteOrderMark(aData, aLength, characterSet); } if (characterSet.IsEmpty()) { // charset from document default characterSet = aDocument->GetDocumentCharacterSet(); } if (characterSet.IsEmpty()) { // fall back to ISO-8859-1, see bug 118404 characterSet.AssignLiteral("ISO-8859-1"); } nsCOMPtr<nsICharsetConverterManager> charsetConv = do_GetService(kCharsetConverterManagerCID, &rv); nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder; if (NS_SUCCEEDED(rv) && charsetConv) { rv = charsetConv->GetUnicodeDecoder(characterSet.get(), getter_AddRefs(unicodeDecoder)); if (NS_FAILED(rv)) { // fall back to ISO-8859-1 if charset is not supported. (bug 230104) rv = charsetConv->GetUnicodeDecoderRaw("ISO-8859-1", getter_AddRefs(unicodeDecoder)); } } // converts from the charset to unicode if (NS_SUCCEEDED(rv)) { PRInt32 unicodeLength = 0; rv = unicodeDecoder->GetMaxLength(NS_REINTERPRET_CAST(const char*, aData), aLength, &unicodeLength); if (NS_SUCCEEDED(rv)) { if (!EnsureStringLength(aString, unicodeLength)) return NS_ERROR_OUT_OF_MEMORY; PRUnichar *ustr = aString.BeginWriting(); PRInt32 consumedLength = 0; PRInt32 originalLength = aLength; PRInt32 convertedLength = 0; PRInt32 bufferLength = unicodeLength; do { rv = unicodeDecoder->Convert(NS_REINTERPRET_CAST(const char*, aData), (PRInt32 *) &aLength, ustr, &unicodeLength); if (NS_FAILED(rv)) { // if we failed, we consume one byte, replace it with U+FFFD // and try the conversion again. ustr[unicodeLength++] = (PRUnichar)0xFFFD; ustr += unicodeLength; unicodeDecoder->Reset(); } aData += ++aLength; consumedLength += aLength; aLength = originalLength - consumedLength; convertedLength += unicodeLength; unicodeLength = bufferLength - convertedLength; } while (NS_FAILED(rv) && (originalLength > consumedLength) && (bufferLength > convertedLength)); aString.SetLength(convertedLength); } } return rv; } NS_IMETHODIMP nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, nsresult aStatus, PRUint32 stringLen, const PRUint8* string) { nsresult rv; nsScriptLoadRequest* request = NS_STATIC_CAST(nsScriptLoadRequest*, aContext); NS_ASSERTION(request, "null request in stream complete handler"); if (!request) { return NS_ERROR_FAILURE; } if (NS_FAILED(aStatus)) { mPendingRequests.RemoveObject(request); FireScriptAvailable(aStatus, request, EmptyString()); ProcessPendingReqests(); return NS_OK; } // If we don't have a document, then we need to abort further // evaluation. if (!mDocument) { mPendingRequests.RemoveObject(request); FireScriptAvailable(NS_ERROR_NOT_AVAILABLE, request, EmptyString()); ProcessPendingReqests(); return NS_OK; } // If the load returned an error page, then we need to abort nsCOMPtr<nsIRequest> req; rv = aLoader->GetRequest(getter_AddRefs(req)); NS_ASSERTION(req, "StreamLoader's request went away prematurely"); if (NS_FAILED(rv)) return rv; // XXX Should this remove the pending request? nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(req)); if (httpChannel) { PRBool requestSucceeded; rv = httpChannel->GetRequestSucceeded(&requestSucceeded); if (NS_SUCCEEDED(rv) && !requestSucceeded) { mPendingRequests.RemoveObject(request); FireScriptAvailable(NS_ERROR_NOT_AVAILABLE, request, EmptyString()); ProcessPendingReqests(); return NS_OK; } } nsCOMPtr<nsIChannel> channel = do_QueryInterface(req); if (stringLen) { // Check the charset attribute to determine script charset. nsAutoString hintCharset; request->mElement->GetScriptCharset(hintCharset); rv = ConvertToUTF16(channel, string, stringLen, hintCharset, mDocument, request->mScriptText); NS_ASSERTION(NS_SUCCEEDED(rv), "Could not convert external JavaScript to Unicode!"); if (NS_FAILED(rv)) { mPendingRequests.RemoveObject(request); FireScriptAvailable(rv, request, EmptyString()); ProcessPendingReqests(); return NS_OK; } if (!ShouldExecuteScript(mDocument, channel)) { return NS_ERROR_NOT_AVAILABLE; } } // If we're not the first in the pending list, we mark ourselves // as loaded and just stay on the list. NS_ASSERTION(mPendingRequests.Count() > 0, "aContext is a pending request!"); if (mPendingRequests[0] != request) { request->mLoading = PR_FALSE; return NS_OK; } mPendingRequests.RemoveObject(request); ProcessRequest(request); // Process any pending requests ProcessPendingReqests(); return NS_OK; } /** * Get the "final" URI for a channel. This is either the same as GetURI or * GetOriginalURI, depending on whether this channel has * nsIChanel::LOAD_REPLACE set. For channels without that flag set, the final * URI is the original URI, while for ones with the flag the final URI is the * channel URI. */ static nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri) { *uri = nsnull; nsLoadFlags loadFlags = 0; nsresult rv = channel->GetLoadFlags(&loadFlags); NS_ENSURE_SUCCESS(rv, rv); if (loadFlags & nsIChannel::LOAD_REPLACE) { return channel->GetURI(uri); } return channel->GetOriginalURI(uri); } static nsresult GetChannelPrincipal(nsIChannel* aChannel, nsIPrincipal** aPrincipal) { NS_PRECONDITION(aChannel, "Must have channel!"); nsCOMPtr<nsISupports> owner; aChannel->GetOwner(getter_AddRefs(owner)); if (owner) { CallQueryInterface(owner, aPrincipal); if (*aPrincipal) { return NS_OK; } } // OK, get the principal from the URI. Make sure this does the same thing // as nsDocument::Reset and nsXULDocument::StartDocumentLoad. nsCOMPtr<nsIURI> uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); return nsContentUtils::GetSecurityManager()-> GetCodebasePrincipal(uri, aPrincipal); } /* static */ PRBool nsScriptLoader::ShouldExecuteScript(nsIDocument* aDocument, nsIChannel* aChannel) { if (!aChannel) { return PR_FALSE; } PRBool hasCert; nsIPrincipal *docPrincipal = aDocument->GetPrincipal(); docPrincipal->GetHasCertificate(&hasCert); if (!hasCert) { return PR_TRUE; } nsCOMPtr<nsIPrincipal> channelPrincipal; nsresult rv = GetChannelPrincipal(aChannel, getter_AddRefs(channelPrincipal)); NS_ENSURE_SUCCESS(rv, PR_FALSE); NS_ASSERTION(channelPrincipal, "Gotta have a principal here!"); // If the document principal is a cert principal and is not the same // as the channel principal, then we don't execute the script. PRBool equal; rv = docPrincipal->Equals(channelPrincipal, &equal); return NS_SUCCEEDED(rv) && equal; } NS_IMETHODIMP nsScriptLoader::GetEnabled(PRBool *aEnabled) { NS_ENSURE_ARG_POINTER(aEnabled); *aEnabled = mEnabled; return NS_OK; } NS_IMETHODIMP nsScriptLoader::SetEnabled(PRBool aEnabled) { mEnabled = aEnabled; return NS_OK; }