diff options
Diffstat (limited to 'dom/jsurl')
24 files changed, 2047 insertions, 0 deletions
diff --git a/dom/jsurl/crashtests/1018583.html b/dom/jsurl/crashtests/1018583.html new file mode 100644 index 000000000..8ede367c6 --- /dev/null +++ b/dom/jsurl/crashtests/1018583.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<!-- Make sure we don't go into an infinite loop --> +<img src="javascript:while(true){}"> diff --git a/dom/jsurl/crashtests/1180389.html b/dom/jsurl/crashtests/1180389.html new file mode 100644 index 000000000..b6b6443cd --- /dev/null +++ b/dom/jsurl/crashtests/1180389.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + window.location.protocol = "javascript"; +</script> diff --git a/dom/jsurl/crashtests/341963-1.html b/dom/jsurl/crashtests/341963-1.html new file mode 100644 index 000000000..2cdb467d1 --- /dev/null +++ b/dom/jsurl/crashtests/341963-1.html @@ -0,0 +1,8 @@ +<script> + +location = "javascript:x.x.x"; + +for(var i in {}) + ; + +</script> diff --git a/dom/jsurl/crashtests/344874-1.html b/dom/jsurl/crashtests/344874-1.html new file mode 100644 index 000000000..4df2723e4 --- /dev/null +++ b/dom/jsurl/crashtests/344874-1.html @@ -0,0 +1,27 @@ +<html> +<head> +<script> + +var img; + +function boo() +{ + img = document.getElementById("img"); + setScriptSrc(); +} + +function setScriptSrc() +{ + img.src = "javascript:setScriptSrc();"; +} + +</script> +</head> + +<body onload="setTimeout(boo, 30);"> + +<img src="../../../../testing/crashtest/images/tree.gif" id="img"> + +</body> + +</html> diff --git a/dom/jsurl/crashtests/344996-1.xhtml b/dom/jsurl/crashtests/344996-1.xhtml new file mode 100644 index 000000000..d9a5bbfcc --- /dev/null +++ b/dom/jsurl/crashtests/344996-1.xhtml @@ -0,0 +1,41 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> + +<script> +<![CDATA[ + +var a; +var b; + +function foopy() +{ + a = document.getElementById("a"); + b = document.getElementById("b"); + + var img = document.getElementById("img"); + var rx = document.getElementById("rx"); + + img.setAttribute('src', "javascript:aC(a, b);"); + aC(rx, a); + + document.documentElement.removeAttribute("class"); +} + +// This has to be a top-level function to avoid hitting bug 344890. +function aC(q1, q2) { q1.appendChild(q2); } + +]]> +</script> + +</head> + +<body onload="setTimeout(foopy, 30);"> + +<span id="a" style="border: 1px solid green;">A<img src="../../../../testing/crashtest/images/tree.gif" id="img" /></span> + +<span id="b">B</span> + +<div data="text/plain,Hi!" style="border: 1px solid blue; display: block;" id="rx" /> + +</body> +</html> diff --git a/dom/jsurl/crashtests/457050-1-inner.html b/dom/jsurl/crashtests/457050-1-inner.html new file mode 100644 index 000000000..54521c9a5 --- /dev/null +++ b/dom/jsurl/crashtests/457050-1-inner.html @@ -0,0 +1,8 @@ +<html> +<head> +<link rel="stylesheet" type="text/css" href="javascript:'p { color: green }'"> +</head> +<body onload="parent.next(window);"> +<p>Hello</p> +</body> +</html> diff --git a/dom/jsurl/crashtests/457050-1.html b/dom/jsurl/crashtests/457050-1.html new file mode 100644 index 000000000..17e3faf8e --- /dev/null +++ b/dom/jsurl/crashtests/457050-1.html @@ -0,0 +1,18 @@ +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +var i = 0; +function next(w) +{ + ++i; + if (i == 1) + w.location.reload(); + if (i == 2) + document.documentElement.removeAttribute("class"); +} +</script> +</head> +<body> +<iframe src="457050-1-inner.html"></iframe> +</body> +</html> diff --git a/dom/jsurl/crashtests/crashtests.list b/dom/jsurl/crashtests/crashtests.list new file mode 100644 index 000000000..73c36b6ac --- /dev/null +++ b/dom/jsurl/crashtests/crashtests.list @@ -0,0 +1,6 @@ +load 341963-1.html +load 344874-1.html +load 344996-1.xhtml +load 457050-1.html +load 1018583.html +load 1180389.html diff --git a/dom/jsurl/moz.build b/dom/jsurl/moz.build new file mode 100644 index 000000000..f5579b64d --- /dev/null +++ b/dom/jsurl/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsJSProtocolHandler.h', +] + +UNIFIED_SOURCES += [ + 'nsJSProtocolHandler.cpp', +] + +LOCAL_INCLUDES += [ + "/dom/base", + "/netwerk/base", +] + +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') + +MOCHITEST_MANIFESTS += ['test/mochitest.ini'] diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp new file mode 100644 index 000000000..cdb63f890 --- /dev/null +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -0,0 +1,1432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 sw=4 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "jsapi.h" +#include "jswrapper.h" +#include "nsCRT.h" +#include "nsError.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsJSProtocolHandler.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" + +#include "nsIStreamListener.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "nsIConsoleService.h" +#include "nsXPIDLString.h" +#include "prprf.h" +#include "nsEscape.h" +#include "nsIWebNavigation.h" +#include "nsIDocShell.h" +#include "nsIContentViewer.h" +#include "nsIXPConnect.h" +#include "nsContentUtils.h" +#include "nsNullPrincipal.h" +#include "nsJSUtils.h" +#include "nsThreadUtils.h" +#include "nsIScriptChannel.h" +#include "nsIDocument.h" +#include "nsILoadInfo.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIContentSecurityPolicy.h" +#include "nsSandboxFlags.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsILoadInfo.h" +#include "nsContentSecurityManager.h" + +#include "mozilla/ipc/URIUtils.h" + +using mozilla::dom::AutoEntryScript; + +static NS_DEFINE_CID(kJSURICID, NS_JSURI_CID); + +class nsJSThunk : public nsIInputStream +{ +public: + nsJSThunk(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSIINPUTSTREAM(mInnerStream) + + nsresult Init(nsIURI* uri); + nsresult EvaluateScript(nsIChannel *aChannel, + PopupControlState aPopupState, + uint32_t aExecutionPolicy, + nsPIDOMWindowInner *aOriginalInnerWindow); + +protected: + virtual ~nsJSThunk(); + + nsCOMPtr<nsIInputStream> mInnerStream; + nsCString mScript; + nsCString mURL; +}; + +// +// nsISupports implementation... +// +NS_IMPL_ISUPPORTS(nsJSThunk, nsIInputStream) + + +nsJSThunk::nsJSThunk() +{ +} + +nsJSThunk::~nsJSThunk() +{ +} + +nsresult nsJSThunk::Init(nsIURI* uri) +{ + NS_ENSURE_ARG_POINTER(uri); + + // Get the script string to evaluate... + nsresult rv = uri->GetPath(mScript); + if (NS_FAILED(rv)) return rv; + + // Get the url. + rv = uri->GetSpec(mURL); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +static bool +IsISO88591(const nsString& aString) +{ + for (nsString::const_char_iterator c = aString.BeginReading(), + c_end = aString.EndReading(); + c < c_end; ++c) { + if (*c > 255) + return false; + } + return true; +} + +static +nsIScriptGlobalObject* GetGlobalObject(nsIChannel* aChannel) +{ + // Get the global object owner from the channel + nsCOMPtr<nsIDocShell> docShell; + NS_QueryNotificationCallbacks(aChannel, docShell); + if (!docShell) { + NS_WARNING("Unable to get a docShell from the channel!"); + return nullptr; + } + + // So far so good: get the script global from its docshell + nsIScriptGlobalObject* global = docShell->GetScriptGlobalObject(); + + NS_ASSERTION(global, + "Unable to get an nsIScriptGlobalObject from the " + "docShell!"); + return global; +} + +nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel, + PopupControlState aPopupState, + uint32_t aExecutionPolicy, + nsPIDOMWindowInner *aOriginalInnerWindow) +{ + if (aExecutionPolicy == nsIScriptChannel::NO_EXECUTION) { + // Nothing to do here. + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } + + NS_ENSURE_ARG_POINTER(aChannel); + + // Get principal of code for execution + nsCOMPtr<nsISupports> owner; + aChannel->GetOwner(getter_AddRefs(owner)); + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(owner); + if (!principal) { + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + if (loadInfo && loadInfo->GetForceInheritPrincipal()) { + principal = loadInfo->PrincipalToInherit(); + if (!principal) { + principal = loadInfo->TriggeringPrincipal(); + } + } else { + // No execution without a principal! + NS_ASSERTION(!owner, "Non-principal owner?"); + NS_WARNING("No principal to execute JS with"); + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } + } + + nsresult rv; + + // CSP check: javascript: URIs disabled unless "inline" scripts are + // allowed. + nsCOMPtr<nsIContentSecurityPolicy> csp; + rv = principal->GetCsp(getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + if (csp) { + bool allowsInlineScript = true; + rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, + EmptyString(), // aNonce + true, // aParserCreated + EmptyString(), // aContent + 0, // aLineNumber + &allowsInlineScript); + + //return early if inline scripts are not allowed + if (!allowsInlineScript) { + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } + } + + // Get the global object we should be running on. + nsIScriptGlobalObject* global = GetGlobalObject(aChannel); + if (!global) { + return NS_ERROR_FAILURE; + } + + // Sandboxed document check: javascript: URI's are disabled + // in a sandboxed document unless 'allow-scripts' was specified. + nsIDocument* doc = aOriginalInnerWindow->GetExtantDoc(); + if (doc && doc->HasScriptsBlockedBySandbox()) { + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } + + // Push our popup control state + nsAutoPopupStatePusher popupStatePusher(aPopupState); + + // Make sure we still have the same inner window as we used to. + nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(global); + nsPIDOMWindowInner *innerWin = win->GetCurrentInnerWindow(); + + if (innerWin != aOriginalInnerWindow) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIScriptGlobalObject> innerGlobal = do_QueryInterface(innerWin); + + mozilla::DebugOnly<nsCOMPtr<nsIDOMWindow>> domWindow(do_QueryInterface(global, &rv)); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + // So far so good: get the script context from its owner. + nsCOMPtr<nsIScriptContext> scriptContext = global->GetContext(); + if (!scriptContext) + return NS_ERROR_FAILURE; + + nsAutoCString script(mScript); + // Unescape the script + NS_UnescapeURL(script); + + + nsCOMPtr<nsIScriptSecurityManager> securityManager; + securityManager = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + // New script entry point required, due to the "Create a script" step of + // http://www.whatwg.org/specs/web-apps/current-work/#javascript-protocol + nsAutoMicroTask mt; + AutoEntryScript aes(innerGlobal, "javascript: URI", true); + JSContext* cx = aes.cx(); + JS::Rooted<JSObject*> globalJSObject(cx, innerGlobal->GetGlobalJSObject()); + NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED); + + //-- Don't execute unless the script principal subsumes the + // principal of the context. + nsIPrincipal* objectPrincipal = nsContentUtils::ObjectPrincipal(globalJSObject); + + bool subsumes; + rv = principal->Subsumes(objectPrincipal, &subsumes); + if (NS_FAILED(rv)) + return rv; + + if (!subsumes) { + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } + + // Fail if someone tries to execute in a global with system principal. + if (nsContentUtils::IsSystemPrincipal(objectPrincipal)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + JS::Rooted<JS::Value> v (cx, JS::UndefinedValue()); + // Finally, we have everything needed to evaluate the expression. + JS::CompileOptions options(cx); + options.setFileAndLine(mURL.get(), 1) + .setVersion(JSVERSION_DEFAULT); + nsJSUtils::EvaluateOptions evalOptions(cx); + evalOptions.setCoerceToString(true); + rv = nsJSUtils::EvaluateString(cx, NS_ConvertUTF8toUTF16(script), + globalJSObject, options, evalOptions, &v); + + if (NS_FAILED(rv) || !(v.isString() || v.isUndefined())) { + return NS_ERROR_MALFORMED_URI; + } else if (v.isUndefined()) { + return NS_ERROR_DOM_RETVAL_UNDEFINED; + } else { + MOZ_ASSERT(rv != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW, + "How did we get a non-undefined return value?"); + nsAutoJSString result; + if (!result.init(cx, v)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char *bytes; + uint32_t bytesLen; + NS_NAMED_LITERAL_CSTRING(isoCharset, "ISO-8859-1"); + NS_NAMED_LITERAL_CSTRING(utf8Charset, "UTF-8"); + const nsCString *charset; + if (IsISO88591(result)) { + // For compatibility, if the result is ISO-8859-1, we use + // ISO-8859-1, so that people can compatibly create images + // using javascript: URLs. + bytes = ToNewCString(result); + bytesLen = result.Length(); + charset = &isoCharset; + } + else { + bytes = ToNewUTF8String(result, &bytesLen); + charset = &utf8Charset; + } + aChannel->SetContentCharset(*charset); + if (bytes) + rv = NS_NewByteInputStream(getter_AddRefs(mInnerStream), + bytes, bytesLen, + NS_ASSIGNMENT_ADOPT); + else + rv = NS_ERROR_OUT_OF_MEMORY; + } + + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsJSChannel : public nsIChannel, + public nsIStreamListener, + public nsIScriptChannel, + public nsIPropertyBag2 +{ +public: + nsJSChannel(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSISCRIPTCHANNEL + NS_FORWARD_SAFE_NSIPROPERTYBAG(mPropertyBag) + NS_FORWARD_SAFE_NSIPROPERTYBAG2(mPropertyBag) + + nsresult Init(nsIURI *aURI); + + // Actually evaluate the script. + void EvaluateScript(); + +protected: + virtual ~nsJSChannel(); + + nsresult StopAll(); + + void NotifyListener(); + + void CleanupStrongRefs(); + +protected: + nsCOMPtr<nsIChannel> mStreamChannel; + nsCOMPtr<nsIPropertyBag2> mPropertyBag; + nsCOMPtr<nsIStreamListener> mListener; // Our final listener + nsCOMPtr<nsISupports> mContext; // The context passed to AsyncOpen + nsCOMPtr<nsPIDOMWindowInner> mOriginalInnerWindow; // The inner window our load + // started against. + // If we blocked onload on a document in AsyncOpen, this is the document we + // did it on. + nsCOMPtr<nsIDocument> mDocumentOnloadBlockedOn; + + nsresult mStatus; // Our status + + nsLoadFlags mLoadFlags; + nsLoadFlags mActualLoadFlags; // See AsyncOpen + + RefPtr<nsJSThunk> mIOThunk; + PopupControlState mPopupState; + uint32_t mExecutionPolicy; + bool mIsAsync; + bool mIsActive; + bool mOpenedStreamChannel; +}; + +nsJSChannel::nsJSChannel() : + mStatus(NS_OK), + mLoadFlags(LOAD_NORMAL), + mActualLoadFlags(LOAD_NORMAL), + mPopupState(openOverridden), + mExecutionPolicy(NO_EXECUTION), + mIsAsync(true), + mIsActive(false), + mOpenedStreamChannel(false) +{ +} + +nsJSChannel::~nsJSChannel() +{ +} + +nsresult nsJSChannel::StopAll() +{ + nsresult rv = NS_ERROR_UNEXPECTED; + nsCOMPtr<nsIWebNavigation> webNav; + NS_QueryNotificationCallbacks(mStreamChannel, webNav); + + NS_ASSERTION(webNav, "Can't get nsIWebNavigation from channel!"); + if (webNav) { + rv = webNav->Stop(nsIWebNavigation::STOP_ALL); + } + + return rv; +} + +nsresult nsJSChannel::Init(nsIURI *aURI) +{ + RefPtr<nsJSURI> jsURI; + nsresult rv = aURI->QueryInterface(kJSURICID, + getter_AddRefs(jsURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the nsIStreamIO layer used by the nsIStreamIOChannel. + mIOThunk = new nsJSThunk(); + + // Create a stock input stream channel... + // Remember, until AsyncOpen is called, the script will not be evaluated + // and the underlying Input Stream will not be created... + nsCOMPtr<nsIChannel> channel; + + nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create(); + + // If the resultant script evaluation actually does return a value, we + // treat it as html. + // The following channel is never openend, so it does not matter what + // securityFlags we pass; let's follow the principle of least privilege. + rv = NS_NewInputStreamChannel(getter_AddRefs(channel), + aURI, + mIOThunk, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_OTHER, + NS_LITERAL_CSTRING("text/html")); + if (NS_FAILED(rv)) return rv; + + rv = mIOThunk->Init(aURI); + if (NS_SUCCEEDED(rv)) { + mStreamChannel = channel; + mPropertyBag = do_QueryInterface(channel); + nsCOMPtr<nsIWritablePropertyBag2> writableBag = + do_QueryInterface(channel); + if (writableBag && jsURI->GetBaseURI()) { + writableBag->SetPropertyAsInterface(NS_LITERAL_STRING("baseURI"), + jsURI->GetBaseURI()); + } + } + + return rv; +} + +// +// nsISupports implementation... +// + +NS_IMPL_ISUPPORTS(nsJSChannel, nsIChannel, nsIRequest, nsIRequestObserver, + nsIStreamListener, nsIScriptChannel, nsIPropertyBag, + nsIPropertyBag2) + +// +// nsIRequest implementation... +// + +NS_IMETHODIMP +nsJSChannel::GetName(nsACString &aResult) +{ + return mStreamChannel->GetName(aResult); +} + +NS_IMETHODIMP +nsJSChannel::IsPending(bool *aResult) +{ + *aResult = mIsActive; + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::GetStatus(nsresult *aResult) +{ + if (NS_SUCCEEDED(mStatus) && mOpenedStreamChannel) { + return mStreamChannel->GetStatus(aResult); + } + + *aResult = mStatus; + + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::Cancel(nsresult aStatus) +{ + mStatus = aStatus; + + if (mOpenedStreamChannel) { + mStreamChannel->Cancel(aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::Suspend() +{ + return mStreamChannel->Suspend(); +} + +NS_IMETHODIMP +nsJSChannel::Resume() +{ + return mStreamChannel->Resume(); +} + +// +// nsIChannel implementation +// + +NS_IMETHODIMP +nsJSChannel::GetOriginalURI(nsIURI * *aURI) +{ + return mStreamChannel->GetOriginalURI(aURI); +} + +NS_IMETHODIMP +nsJSChannel::SetOriginalURI(nsIURI *aURI) +{ + return mStreamChannel->SetOriginalURI(aURI); +} + +NS_IMETHODIMP +nsJSChannel::GetURI(nsIURI * *aURI) +{ + return mStreamChannel->GetURI(aURI); +} + +NS_IMETHODIMP +nsJSChannel::Open(nsIInputStream **aResult) +{ + nsresult rv = mIOThunk->EvaluateScript(mStreamChannel, mPopupState, + mExecutionPolicy, + mOriginalInnerWindow); + NS_ENSURE_SUCCESS(rv, rv); + + return mStreamChannel->Open(aResult); +} + +NS_IMETHODIMP +nsJSChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +NS_IMETHODIMP +nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) +{ +#ifdef DEBUG + { + nsCOMPtr<nsILoadInfo> loadInfo = nsIChannel::GetLoadInfo(); + MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 || + loadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + } +#endif + + NS_ENSURE_ARG(aListener); + + // First make sure that we have a usable inner window; we'll want to make + // sure that we execute against that inner and no other. + nsIScriptGlobalObject* global = GetGlobalObject(this); + if (!global) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsPIDOMWindowOuter> win(do_QueryInterface(global)); + NS_ASSERTION(win, "Our global is not a window??"); + + // Make sure we create a new inner window if one doesn't already exist (see + // bug 306630). + mOriginalInnerWindow = win->EnsureInnerWindow(); + if (!mOriginalInnerWindow) { + return NS_ERROR_NOT_AVAILABLE; + } + + mListener = aListener; + mContext = aContext; + + mIsActive = true; + + // Temporarily set the LOAD_BACKGROUND flag to suppress load group observer + // notifications (and hence nsIWebProgressListener notifications) from + // being dispatched. This is required since we suppress LOAD_DOCUMENT_URI, + // which means that the DocLoader would not generate document start and + // stop notifications (see bug 257875). + mActualLoadFlags = mLoadFlags; + mLoadFlags |= LOAD_BACKGROUND; + + // Add the javascript channel to its loadgroup so that we know if + // network loads were canceled or not... + nsCOMPtr<nsILoadGroup> loadGroup; + mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + nsresult rv = loadGroup->AddRequest(this, nullptr); + if (NS_FAILED(rv)) { + mIsActive = false; + CleanupStrongRefs(); + return rv; + } + } + + mDocumentOnloadBlockedOn = mOriginalInnerWindow->GetExtantDoc(); + if (mDocumentOnloadBlockedOn) { + // If we're a document channel, we need to actually block onload on our + // _parent_ document. This is because we don't actually set our + // LOAD_DOCUMENT_URI flag, so a docloader we're loading in as the + // document channel will claim to not be busy, and our parent's onload + // could fire too early. + nsLoadFlags loadFlags; + mStreamChannel->GetLoadFlags(&loadFlags); + if (loadFlags & LOAD_DOCUMENT_URI) { + mDocumentOnloadBlockedOn = + mDocumentOnloadBlockedOn->GetParentDocument(); + } + } + if (mDocumentOnloadBlockedOn) { + mDocumentOnloadBlockedOn->BlockOnload(); + } + + + mPopupState = win->GetPopupControlState(); + + void (nsJSChannel::*method)(); + if (mIsAsync) { + // post an event to do the rest + method = &nsJSChannel::EvaluateScript; + } else { + EvaluateScript(); + if (mOpenedStreamChannel) { + // That will handle notifying things + return NS_OK; + } + + NS_ASSERTION(NS_FAILED(mStatus), "We should have failed _somehow_"); + + // mStatus is going to be NS_ERROR_DOM_RETVAL_UNDEFINED if we didn't + // have any content resulting from the execution and NS_BINDING_ABORTED + // if something we did causes our own load to be stopped. Return + // success in those cases, and error out in all others. + if (mStatus != NS_ERROR_DOM_RETVAL_UNDEFINED && + mStatus != NS_BINDING_ABORTED) { + // Note that calling EvaluateScript() handled removing us from the + // loadgroup and marking us as not active anymore. + CleanupStrongRefs(); + return mStatus; + } + + // We're returning success from asyncOpen(), but we didn't open a + // stream channel. We'll have to notify ourselves, but make sure to do + // it asynchronously. + method = &nsJSChannel::NotifyListener; + } + + nsresult rv = NS_DispatchToCurrentThread(mozilla::NewRunnableMethod(this, method)); + + if (NS_FAILED(rv)) { + loadGroup->RemoveRequest(this, nullptr, rv); + mIsActive = false; + CleanupStrongRefs(); + } + return rv; +} + +NS_IMETHODIMP +nsJSChannel::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr<nsIStreamListener> listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +void +nsJSChannel::EvaluateScript() +{ + // Synchronously execute the script... + // mIsActive is used to indicate the the request is 'busy' during the + // the script evaluation phase. This means that IsPending() will + // indicate the the request is busy while the script is executing... + + // Note that we want to be in the loadgroup and pending while we evaluate + // the script, so that we find out if the loadgroup gets canceled by the + // script execution (in which case we shouldn't pump out data even if the + // script returns it). + + if (NS_SUCCEEDED(mStatus)) { + nsresult rv = mIOThunk->EvaluateScript(mStreamChannel, mPopupState, + mExecutionPolicy, + mOriginalInnerWindow); + + // Note that evaluation may have canceled us, so recheck mStatus again + if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + } + + // Remove the javascript channel from its loadgroup... + nsCOMPtr<nsILoadGroup> loadGroup; + mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + loadGroup->RemoveRequest(this, nullptr, mStatus); + } + + // Reset load flags to their original value... + mLoadFlags = mActualLoadFlags; + + // We're no longer active, it's now up to the stream channel to do + // the loading, if needed. + mIsActive = false; + + if (NS_FAILED(mStatus)) { + if (mIsAsync) { + NotifyListener(); + } + return; + } + + // EvaluateScript() succeeded, and we were not canceled, that + // means there's data to parse as a result of evaluating the + // script. + + // Get the stream channels load flags (!= mLoadFlags). + nsLoadFlags loadFlags; + mStreamChannel->GetLoadFlags(&loadFlags); + + uint32_t disposition; + if (NS_FAILED(mStreamChannel->GetContentDisposition(&disposition))) + disposition = nsIChannel::DISPOSITION_INLINE; + if (loadFlags & LOAD_DOCUMENT_URI && disposition != nsIChannel::DISPOSITION_ATTACHMENT) { + // We're loaded as the document channel and not expecting to download + // the result. If we go on, we'll blow away the current document. Make + // sure that's ok. If so, stop all pending network loads. + + nsCOMPtr<nsIDocShell> docShell; + NS_QueryNotificationCallbacks(mStreamChannel, docShell); + if (docShell) { + nsCOMPtr<nsIContentViewer> cv; + docShell->GetContentViewer(getter_AddRefs(cv)); + + if (cv) { + bool okToUnload; + + if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && + !okToUnload) { + // The user didn't want to unload the current + // page, translate this into an undefined + // return from the javascript: URL... + mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED; + } + } + } + + if (NS_SUCCEEDED(mStatus)) { + mStatus = StopAll(); + } + } + + if (NS_FAILED(mStatus)) { + if (mIsAsync) { + NotifyListener(); + } + return; + } + + mStatus = mStreamChannel->AsyncOpen(this, mContext); + if (NS_SUCCEEDED(mStatus)) { + // mStreamChannel will call OnStartRequest and OnStopRequest on + // us, so we'll be sure to call them on our listener. + mOpenedStreamChannel = true; + + // Now readd ourselves to the loadgroup so we can receive + // cancellation notifications. + mIsActive = true; + if (loadGroup) { + mStatus = loadGroup->AddRequest(this, nullptr); + + // If AddRequest failed, that's OK. The key is to make sure we get + // cancelled if needed, and that call just canceled us if it + // failed. We'll still get notified by the stream channel when it + // finishes. + } + + } else if (mIsAsync) { + NotifyListener(); + } + + return; +} + +void +nsJSChannel::NotifyListener() +{ + mListener->OnStartRequest(this, mContext); + mListener->OnStopRequest(this, mContext, mStatus); + + CleanupStrongRefs(); +} + +void +nsJSChannel::CleanupStrongRefs() +{ + mListener = nullptr; + mContext = nullptr; + mOriginalInnerWindow = nullptr; + if (mDocumentOnloadBlockedOn) { + mDocumentOnloadBlockedOn->UnblockOnload(false); + mDocumentOnloadBlockedOn = nullptr; + } +} + +NS_IMETHODIMP +nsJSChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = mLoadFlags; + + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + // Figure out whether the LOAD_BACKGROUND bit in aLoadFlags is + // actually right. + bool bogusLoadBackground = false; + if (mIsActive && !(mActualLoadFlags & LOAD_BACKGROUND) && + (aLoadFlags & LOAD_BACKGROUND)) { + // We're getting a LOAD_BACKGROUND, but it's probably just our own fake + // flag being mirrored to us. The one exception is if our loadgroup is + // LOAD_BACKGROUND. + bool loadGroupIsBackground = false; + nsCOMPtr<nsILoadGroup> loadGroup; + mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + nsLoadFlags loadGroupFlags; + loadGroup->GetLoadFlags(&loadGroupFlags); + loadGroupIsBackground = ((loadGroupFlags & LOAD_BACKGROUND) != 0); + } + bogusLoadBackground = !loadGroupIsBackground; + } + + // Classifying a javascript: URI doesn't help us, and requires + // NSS to boot, which we don't have in content processes. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=617838. + aLoadFlags &= ~LOAD_CLASSIFY_URI; + + // Since the javascript channel is never the actual channel that + // any data is loaded through, don't ever set the + // LOAD_DOCUMENT_URI flag on it, since that could lead to two + // 'document channels' in the loadgroup if a javascript: URL is + // loaded while a document is being loaded in the same window. + + // XXXbz this, and a whole lot of other hackery, could go away if we'd just + // cancel the current document load on javascript: load start like IE does. + + mLoadFlags = aLoadFlags & ~LOAD_DOCUMENT_URI; + + if (bogusLoadBackground) { + aLoadFlags = aLoadFlags & ~LOAD_BACKGROUND; + } + + mActualLoadFlags = aLoadFlags; + + // ... but the underlying stream channel should get this bit, if + // set, since that'll be the real document channel if the + // javascript: URL generated data. + + return mStreamChannel->SetLoadFlags(aLoadFlags); +} + +NS_IMETHODIMP +nsJSChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup) +{ + return mStreamChannel->GetLoadGroup(aLoadGroup); +} + +NS_IMETHODIMP +nsJSChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + if (aLoadGroup) { + bool streamPending; + nsresult rv = mStreamChannel->IsPending(&streamPending); + NS_ENSURE_SUCCESS(rv, rv); + + if (streamPending) { + nsCOMPtr<nsILoadGroup> curLoadGroup; + mStreamChannel->GetLoadGroup(getter_AddRefs(curLoadGroup)); + + if (aLoadGroup != curLoadGroup) { + // Move the stream channel to our new loadgroup. Make sure to + // add it before removing it, so that we don't trigger onload + // by accident. + aLoadGroup->AddRequest(mStreamChannel, nullptr); + if (curLoadGroup) { + curLoadGroup->RemoveRequest(mStreamChannel, nullptr, + NS_BINDING_RETARGETED); + } + } + } + } + + return mStreamChannel->SetLoadGroup(aLoadGroup); +} + +NS_IMETHODIMP +nsJSChannel::GetOwner(nsISupports* *aOwner) +{ + return mStreamChannel->GetOwner(aOwner); +} + +NS_IMETHODIMP +nsJSChannel::SetOwner(nsISupports* aOwner) +{ + return mStreamChannel->SetOwner(aOwner); +} + +NS_IMETHODIMP +nsJSChannel::GetLoadInfo(nsILoadInfo* *aLoadInfo) +{ + return mStreamChannel->GetLoadInfo(aLoadInfo); +} + +NS_IMETHODIMP +nsJSChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + return mStreamChannel->SetLoadInfo(aLoadInfo); +} + +NS_IMETHODIMP +nsJSChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks) +{ + return mStreamChannel->GetNotificationCallbacks(aCallbacks); +} + +NS_IMETHODIMP +nsJSChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) +{ + return mStreamChannel->SetNotificationCallbacks(aCallbacks); +} + +NS_IMETHODIMP +nsJSChannel::GetSecurityInfo(nsISupports * *aSecurityInfo) +{ + return mStreamChannel->GetSecurityInfo(aSecurityInfo); +} + +NS_IMETHODIMP +nsJSChannel::GetContentType(nsACString &aContentType) +{ + return mStreamChannel->GetContentType(aContentType); +} + +NS_IMETHODIMP +nsJSChannel::SetContentType(const nsACString &aContentType) +{ + return mStreamChannel->SetContentType(aContentType); +} + +NS_IMETHODIMP +nsJSChannel::GetContentCharset(nsACString &aContentCharset) +{ + return mStreamChannel->GetContentCharset(aContentCharset); +} + +NS_IMETHODIMP +nsJSChannel::SetContentCharset(const nsACString &aContentCharset) +{ + return mStreamChannel->SetContentCharset(aContentCharset); +} + +NS_IMETHODIMP +nsJSChannel::GetContentDisposition(uint32_t *aContentDisposition) +{ + return mStreamChannel->GetContentDisposition(aContentDisposition); +} + +NS_IMETHODIMP +nsJSChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + return mStreamChannel->SetContentDisposition(aContentDisposition); +} + +NS_IMETHODIMP +nsJSChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) +{ + return mStreamChannel->GetContentDispositionFilename(aContentDispositionFilename); +} + +NS_IMETHODIMP +nsJSChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) +{ + return mStreamChannel->SetContentDispositionFilename(aContentDispositionFilename); +} + +NS_IMETHODIMP +nsJSChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) +{ + return mStreamChannel->GetContentDispositionHeader(aContentDispositionHeader); +} + +NS_IMETHODIMP +nsJSChannel::GetContentLength(int64_t *aContentLength) +{ + return mStreamChannel->GetContentLength(aContentLength); +} + +NS_IMETHODIMP +nsJSChannel::SetContentLength(int64_t aContentLength) +{ + return mStreamChannel->SetContentLength(aContentLength); +} + +NS_IMETHODIMP +nsJSChannel::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); + + return mListener->OnStartRequest(this, aContext); +} + +NS_IMETHODIMP +nsJSChannel::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) +{ + NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); + + return mListener->OnDataAvailable(this, aContext, aInputStream, aOffset, + aCount); +} + +NS_IMETHODIMP +nsJSChannel::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStreamListener> listener = mListener; + + CleanupStrongRefs(); + + // Make sure aStatus matches what GetStatus() returns + if (NS_FAILED(mStatus)) { + aStatus = mStatus; + } + + nsresult rv = listener->OnStopRequest(this, aContext, aStatus); + + nsCOMPtr<nsILoadGroup> loadGroup; + mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + loadGroup->RemoveRequest(this, nullptr, mStatus); + } + + mIsActive = false; + + return rv; +} + +NS_IMETHODIMP +nsJSChannel::SetExecutionPolicy(uint32_t aPolicy) +{ + NS_ENSURE_ARG(aPolicy <= EXECUTE_NORMAL); + + mExecutionPolicy = aPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::GetExecutionPolicy(uint32_t* aPolicy) +{ + *aPolicy = mExecutionPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::SetExecuteAsync(bool aIsAsync) +{ + if (!mIsActive) { + mIsAsync = aIsAsync; + } + // else ignore this call + NS_WARNING_ASSERTION(!mIsActive, + "Calling SetExecuteAsync on active channel?"); + + return NS_OK; +} + +NS_IMETHODIMP +nsJSChannel::GetExecuteAsync(bool* aIsAsync) +{ + *aIsAsync = mIsAsync; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsJSProtocolHandler::nsJSProtocolHandler() +{ +} + +nsresult +nsJSProtocolHandler::Init() +{ + return NS_OK; +} + +nsJSProtocolHandler::~nsJSProtocolHandler() +{ +} + +NS_IMPL_ISUPPORTS(nsJSProtocolHandler, nsIProtocolHandler) + +nsresult +nsJSProtocolHandler::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsJSProtocolHandler* ph = new nsJSProtocolHandler(); + NS_ADDREF(ph); + nsresult rv = ph->Init(); + if (NS_SUCCEEDED(rv)) { + rv = ph->QueryInterface(aIID, aResult); + } + NS_RELEASE(ph); + return rv; +} + +nsresult +nsJSProtocolHandler::EnsureUTF8Spec(const nsAFlatCString &aSpec, const char *aCharset, + nsACString &aUTF8Spec) +{ + aUTF8Spec.Truncate(); + + nsresult rv; + + if (!mTextToSubURI) { + mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsAutoString uStr; + rv = mTextToSubURI->UnEscapeNonAsciiURI(nsDependentCString(aCharset), aSpec, uStr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!IsASCII(uStr)) { + rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(uStr), + esc_AlwaysCopy | esc_OnlyNonASCII, aUTF8Spec, + mozilla::fallible); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsJSProtocolHandler::GetScheme(nsACString &result) +{ + result = "javascript"; + return NS_OK; +} + +NS_IMETHODIMP +nsJSProtocolHandler::GetDefaultPort(int32_t *result) +{ + *result = -1; // no port for javascript: URLs + return NS_OK; +} + +NS_IMETHODIMP +nsJSProtocolHandler::GetProtocolFlags(uint32_t *result) +{ + *result = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT | + URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE | URI_OPENING_EXECUTES_SCRIPT; + return NS_OK; +} + +NS_IMETHODIMP +nsJSProtocolHandler::NewURI(const nsACString &aSpec, + const char *aCharset, + nsIURI *aBaseURI, + nsIURI **result) +{ + nsresult rv; + + // javascript: URLs (currently) have no additional structure beyond that + // provided by standard URLs, so there is no "outer" object given to + // CreateInstance. + + nsCOMPtr<nsIURI> url = new nsJSURI(aBaseURI); + + if (!aCharset || !nsCRT::strcasecmp("UTF-8", aCharset)) + rv = url->SetSpec(aSpec); + else { + nsAutoCString utf8Spec; + rv = EnsureUTF8Spec(PromiseFlatCString(aSpec), aCharset, utf8Spec); + if (NS_SUCCEEDED(rv)) { + if (utf8Spec.IsEmpty()) + rv = url->SetSpec(aSpec); + else + rv = url->SetSpec(utf8Spec); + } + } + + if (NS_FAILED(rv)) { + return rv; + } + + url.forget(result); + return rv; +} + +NS_IMETHODIMP +nsJSProtocolHandler::NewChannel2(nsIURI* uri, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(uri); + + RefPtr<nsJSChannel> channel = new nsJSChannel(); + if (!channel) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = channel->Init(uri); + NS_ENSURE_SUCCESS(rv, rv); + + // set the loadInfo on the new channel + rv = channel->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_SUCCEEDED(rv)) { + channel.forget(result); + } + return rv; +} + +NS_IMETHODIMP +nsJSProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result) +{ + return NewChannel2(uri, nullptr, result); +} + +NS_IMETHODIMP +nsJSProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +//////////////////////////////////////////////////////////// +// nsJSURI implementation +static NS_DEFINE_CID(kThisSimpleURIImplementationCID, + NS_THIS_SIMPLEURI_IMPLEMENTATION_CID); + + +NS_IMPL_ADDREF_INHERITED(nsJSURI, mozilla::net::nsSimpleURI) +NS_IMPL_RELEASE_INHERITED(nsJSURI, mozilla::net::nsSimpleURI) + +NS_INTERFACE_MAP_BEGIN(nsJSURI) + if (aIID.Equals(kJSURICID)) + foundInterface = static_cast<nsIURI*>(this); + else if (aIID.Equals(kThisSimpleURIImplementationCID)) { + // Need to return explicitly here, because if we just set foundInterface + // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into + // nsSimplURI::QueryInterface and finding something for this CID. + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + else +NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI) + +// nsISerializable methods: + +NS_IMETHODIMP +nsJSURI::Read(nsIObjectInputStream* aStream) +{ + nsresult rv = mozilla::net::nsSimpleURI::Read(aStream); + if (NS_FAILED(rv)) return rv; + + bool haveBase; + rv = aStream->ReadBoolean(&haveBase); + if (NS_FAILED(rv)) return rv; + + if (haveBase) { + nsCOMPtr<nsISupports> supports; + rv = aStream->ReadObject(true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) return rv; + mBaseURI = do_QueryInterface(supports); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsJSURI::Write(nsIObjectOutputStream* aStream) +{ + nsresult rv = mozilla::net::nsSimpleURI::Write(aStream); + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteBoolean(mBaseURI != nullptr); + if (NS_FAILED(rv)) return rv; + + if (mBaseURI) { + rv = aStream->WriteObject(mBaseURI, true); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +// nsIIPCSerializableURI +void +nsJSURI::Serialize(mozilla::ipc::URIParams& aParams) +{ + using namespace mozilla::ipc; + + JSURIParams jsParams; + URIParams simpleParams; + + mozilla::net::nsSimpleURI::Serialize(simpleParams); + + jsParams.simpleParams() = simpleParams; + if (mBaseURI) { + SerializeURI(mBaseURI, jsParams.baseURI()); + } else { + jsParams.baseURI() = mozilla::void_t(); + } + + aParams = jsParams; +} + +bool +nsJSURI::Deserialize(const mozilla::ipc::URIParams& aParams) +{ + using namespace mozilla::ipc; + + if (aParams.type() != URIParams::TJSURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const JSURIParams& jsParams = aParams.get_JSURIParams(); + mozilla::net::nsSimpleURI::Deserialize(jsParams.simpleParams()); + + if (jsParams.baseURI().type() != OptionalURIParams::Tvoid_t) { + mBaseURI = DeserializeURI(jsParams.baseURI().get_URIParams()); + } else { + mBaseURI = nullptr; + } + return true; +} + +// nsSimpleURI methods: +/* virtual */ mozilla::net::nsSimpleURI* +nsJSURI::StartClone(mozilla::net::nsSimpleURI::RefHandlingEnum refHandlingMode, + const nsACString& newRef) +{ + nsCOMPtr<nsIURI> baseClone; + if (mBaseURI) { + // Note: We preserve ref on *base* URI, regardless of ref handling mode. + nsresult rv = mBaseURI->Clone(getter_AddRefs(baseClone)); + if (NS_FAILED(rv)) { + return nullptr; + } + } + + nsJSURI* url = new nsJSURI(baseClone); + SetRefOnClone(url, refHandlingMode, newRef); + return url; +} + +/* virtual */ nsresult +nsJSURI::EqualsInternal(nsIURI* aOther, + mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aOther); + NS_PRECONDITION(aResult, "null pointer for outparam"); + + RefPtr<nsJSURI> otherJSURI; + nsresult rv = aOther->QueryInterface(kJSURICID, + getter_AddRefs(otherJSURI)); + if (NS_FAILED(rv)) { + *aResult = false; // aOther is not a nsJSURI --> not equal. + return NS_OK; + } + + // Compare the member data that our base class knows about. + if (!mozilla::net::nsSimpleURI::EqualsInternal(otherJSURI, aRefHandlingMode)) { + *aResult = false; + return NS_OK; + } + + // Compare the piece of additional member data that we add to base class. + nsIURI* otherBaseURI = otherJSURI->GetBaseURI(); + + if (mBaseURI) { + // (As noted in StartClone, we always honor refs on mBaseURI) + return mBaseURI->Equals(otherBaseURI, aResult); + } + + *aResult = !otherBaseURI; + return NS_OK; +} + +NS_IMETHODIMP +nsJSURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + *aClassIDNoAlloc = kJSURICID; + return NS_OK; +} + diff --git a/dom/jsurl/nsJSProtocolHandler.h b/dom/jsurl/nsJSProtocolHandler.h new file mode 100644 index 000000000..e374f7695 --- /dev/null +++ b/dom/jsurl/nsJSProtocolHandler.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsJSProtocolHandler_h___ +#define nsJSProtocolHandler_h___ + +#include "mozilla/Attributes.h" +#include "nsIProtocolHandler.h" +#include "nsITextToSubURI.h" +#include "nsIURI.h" +#include "nsIMutable.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" +#include "nsSimpleURI.h" + +#define NS_JSPROTOCOLHANDLER_CID \ +{ /* bfc310d2-38a0-11d3-8cd3-0060b0fc14a3 */ \ + 0xbfc310d2, \ + 0x38a0, \ + 0x11d3, \ + {0x8c, 0xd3, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \ +} + +#define NS_JSURI_CID \ +{ /* 58f089ee-512a-42d2-a935-d0c874128930 */ \ + 0x58f089ee, \ + 0x512a, \ + 0x42d2, \ + {0xa9, 0x35, 0xd0, 0xc8, 0x74, 0x12, 0x89, 0x30} \ +} + +#define NS_JSPROTOCOLHANDLER_CONTRACTID \ + NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "javascript" + + +class nsJSProtocolHandler : public nsIProtocolHandler +{ +public: + NS_DECL_ISUPPORTS + + // nsIProtocolHandler methods: + NS_DECL_NSIPROTOCOLHANDLER + + // nsJSProtocolHandler methods: + nsJSProtocolHandler(); + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + nsresult Init(); + +protected: + virtual ~nsJSProtocolHandler(); + + nsresult EnsureUTF8Spec(const nsAFlatCString &aSpec, const char *aCharset, + nsACString &aUTF8Spec); + + nsCOMPtr<nsITextToSubURI> mTextToSubURI; +}; + + +class nsJSURI : public mozilla::net::nsSimpleURI +{ +public: + using mozilla::net::nsSimpleURI::Read; + using mozilla::net::nsSimpleURI::Write; + + nsJSURI() {} + + explicit nsJSURI(nsIURI* aBaseURI) : mBaseURI(aBaseURI) {} + + nsIURI* GetBaseURI() const + { + return mBaseURI; + } + + NS_DECL_ISUPPORTS_INHERITED + + // nsIURI overrides + virtual mozilla::net::nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode, + const nsACString& newRef) override; + + // nsISerializable overrides + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + // nsIIPCSerializableURI overrides + NS_DECL_NSIIPCSERIALIZABLEURI + + // Override the nsIClassInfo method GetClassIDNoAlloc to make sure our + // nsISerializable impl works right. + NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) override; + //NS_IMETHOD QueryInterface( const nsIID& aIID, void** aInstancePtr ); + +protected: + virtual ~nsJSURI() {} + + virtual nsresult EqualsInternal(nsIURI* other, + RefHandlingEnum refHandlingMode, + bool* result) override; +private: + nsCOMPtr<nsIURI> mBaseURI; +}; + +#endif /* nsJSProtocolHandler_h___ */ diff --git a/dom/jsurl/test/fail.html b/dom/jsurl/test/fail.html new file mode 100644 index 000000000..d8f57acc2 --- /dev/null +++ b/dom/jsurl/test/fail.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<body> +<script> +parent.passJSUrl = false; +parent.finishTest(); +</script> +</body> +</html> diff --git a/dom/jsurl/test/form-submit.html b/dom/jsurl/test/form-submit.html new file mode 100644 index 000000000..f12414efb --- /dev/null +++ b/dom/jsurl/test/form-submit.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<head> +<script> + function test() { + var form = document.getElementById("test"); + form.action = "pass.html"; + form.submit(); + } +</script> +<body> +<form id="test" action="javascript:test()"> +<input type="submit" value="Submit the form; you should PASS when done"> +</form> +</body> diff --git a/dom/jsurl/test/load-stopping-1a.html b/dom/jsurl/test/load-stopping-1a.html new file mode 100644 index 000000000..09f1c6995 --- /dev/null +++ b/dom/jsurl/test/load-stopping-1a.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<head> +<script> +// location = 'javascript: "Test"'; +</script> +</head> +<body> + <script>parent.passJSUrl1 = true;</script> +</body> diff --git a/dom/jsurl/test/load-stopping-1b.html b/dom/jsurl/test/load-stopping-1b.html new file mode 100644 index 000000000..ddbc5d1c6 --- /dev/null +++ b/dom/jsurl/test/load-stopping-1b.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<head> +</head> +<body> +<script>parent.passJSUrl2 = true</script> + +</body> diff --git a/dom/jsurl/test/load-stopping-1c.html b/dom/jsurl/test/load-stopping-1c.html new file mode 100644 index 000000000..1fcab4169 --- /dev/null +++ b/dom/jsurl/test/load-stopping-1c.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<head> +<script> + location = 'javascript:void("Test")'; +</script> +</head> +<body> +<script>parent.passJSUrl3 = true</script> +</body> diff --git a/dom/jsurl/test/load-stopping-1d.html b/dom/jsurl/test/load-stopping-1d.html new file mode 100644 index 000000000..5e265ba64 --- /dev/null +++ b/dom/jsurl/test/load-stopping-1d.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<head> +<script> + function foo() { + return "aaa"; + } +</script> +</head> +<body> +<img src="javascript: foo()"> +<script>parent.passJSUrl4 = true</script> +</body> diff --git a/dom/jsurl/test/mochitest.ini b/dom/jsurl/test/mochitest.ini new file mode 100644 index 000000000..c38271066 --- /dev/null +++ b/dom/jsurl/test/mochitest.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = + fail.html + form-submit.html + load-stopping-1a.html + load-stopping-1b.html + load-stopping-1c.html + load-stopping-1d.html + pass.html + +[test_bug351633-1.html] +[test_bug351633-2.html] +[test_bug351633-3.html] +[test_bug351633-4.html] +[test_bug384981.html] diff --git a/dom/jsurl/test/pass.html b/dom/jsurl/test/pass.html new file mode 100644 index 000000000..c73afdccd --- /dev/null +++ b/dom/jsurl/test/pass.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<body> +<script> +parent.passJSUrl = true; +parent.finishTest(); +</script> +</body> +</html> diff --git a/dom/jsurl/test/test_bug351633-1.html b/dom/jsurl/test/test_bug351633-1.html new file mode 100644 index 000000000..b8fdd72e1 --- /dev/null +++ b/dom/jsurl/test/test_bug351633-1.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=351633 +--> +<head> + <title>Test for Bug 351633: Form submission</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=351633">Mozilla Bug 351633</a> +<p id="display"> + <iframe id="frame" src="form-submit.html"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 351633 **/ +var passJSUrl = false; + +function finishTest() { + is(passJSUrl, true, "Unexpected result"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + $("frame").contentDocument.getElementById("test").submit(); +} + +addLoadEvent(runTest); + +</script> +</pre> +</body> +</html> + diff --git a/dom/jsurl/test/test_bug351633-2.html b/dom/jsurl/test/test_bug351633-2.html new file mode 100644 index 000000000..25f064421 --- /dev/null +++ b/dom/jsurl/test/test_bug351633-2.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=351633 +--> +<head> + <title>Test for Bug 351633</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=351633">Mozilla Bug 351633</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 351633 **/ +var passJSUrl1 = false; +var passJSUrl2 = false; +var passJSUrl3 = false; +var passJSUrl4 = false; + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + is(passJSUrl1, true, "Should have stopped load before getting here"); + is(passJSUrl2, true, "Should not have stopped load where we didn't set " + + "location"); + is(passJSUrl3, true, "Should not have stopped load where javascript: URI " + + "didn't return text"); + is(passJSUrl4, true, "Should not have stopped load where javascript: URI " + + "wasn't set on the document itself"); + SimpleTest.finish(); +}); +</script> +</pre> +<p id="display"> + <iframe src="load-stopping-1a.html"></iframe> + <iframe src="load-stopping-1b.html"></iframe> + <iframe src="load-stopping-1c.html"></iframe> + <iframe src="load-stopping-1d.html"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +</body> +</html> + diff --git a/dom/jsurl/test/test_bug351633-3.html b/dom/jsurl/test/test_bug351633-3.html new file mode 100644 index 000000000..43abc21d9 --- /dev/null +++ b/dom/jsurl/test/test_bug351633-3.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=351633 +--> +<head> + <title>Test for Bug 351633</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=351633">Mozilla Bug 351633</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var passJSUrl; +var passJSUrl2; +var finishTest; + +/** Test for Bug 351633 **/ +function runTests() { + $("testframe1").onload = test1; + // test2 will be called as finishTest + $("testframe3").onload = test3; + $("testframe4").onload = test4; + $("testframe5").onload = test5; + passJSUrl = false; + window.testframe1.location.href = + 'javascript:"<script>parent.passJSUrl = true</' + 'script>"' +} + +function test1() { + is(passJSUrl, true, "Script should have run in child"); + + passJSUrl = false; + passJSUrl2 = true; + finishTest = test2; + + window.testframe2.location.href = + 'javascript: location = "pass.html"; ' + + '"<script>parent.passJSUrl2 = false</' + 'script>"' +} + +function test2() { + is(passJSUrl, true, "pass.html should have loaded"); + is(passJSUrl2, true, "<script> should not have run"); + + passJSUrl = true; + passJSUrl2 = false; + finishTest = function() { }; + + window.testframe3.location.href = 'fail.html'; + window.testframe3.location.href = + 'javascript: "<script>parent.passJSUrl2 = true</' + 'script>"' +} + +function test3() { + if (window.testframe3.location.href == 'fail.html') { + // Ignore this call; we expect the javascript: URI to still load. Note + // that whether onload fires for the fail.html load before the event for + // the javascript: URI execution runs is a timing issue, so we can't depend + // on the ordering. + return; + } + + // Since fail.html could have loaded, the value of passJSUrl here is random + + // Something is bogus here. Maybe we're ending up with the href being the + // javascript: URI even though it hasn't run yet? Or something? In any + // case, this test fails on some tinderboxen but not others.... Need to + // sort it out. + // is(passJSUrl2, true, "<script> should have run"); + + passJSUrl = false; + passJSUrl2 = true; + finishTest = function() { }; + + window.testframe4.location.href = 'pass.html'; + window.testframe4.location.href = + 'javascript:void("<script>parent.passJSUrl2 = false</' + 'script>")'; +} + +function test4() { + is(passJSUrl, true, "pass.html should have loaded again"); + is(passJSUrl2, true, "<script> should not have run in void"); + + passJSUrl = false; + passJSUrl2 = true; + finishTest = function() { }; + + window.testframe5.location.href = + 'javascript:"<script>parent.passJSUrl2 = false</' + 'script>"'; + window.testframe5.location.href = 'pass.html'; +} + +function test5() { + is(passJSUrl, true, "pass.html should have loaded yet again"); + is(passJSUrl2, true, "javascript: load should have been canceled"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTests); + +</script> +</pre> +<p id="display"> + <iframe name="testframe1" id="testframe1"></iframe> + <iframe name="testframe2" id="testframe2"></iframe> + <iframe name="testframe3" id="testframe3"></iframe> + <iframe name="testframe4" id="testframe4"></iframe> + <iframe name="testframe5" id="testframe5"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +</body> +</html> + diff --git a/dom/jsurl/test/test_bug351633-4.html b/dom/jsurl/test/test_bug351633-4.html new file mode 100644 index 000000000..c38a83b7a --- /dev/null +++ b/dom/jsurl/test/test_bug351633-4.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=351633 +--> +<head> + <title>Test for Bug 351633</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=351633">Mozilla Bug 351633</a> +<p id="display"> + <iframe name="x" id="x"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 351633 **/ + +var str; +str = "a"; +window.x.location.href = + "javascript: parent.str += 'c'; void(parent.finishTest());" +str += "b"; + +SimpleTest.waitForExplicitFinish(); + +function finishTest() { + is (str, "abc", "Unexpected ordering"); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/dom/jsurl/test/test_bug384981.html b/dom/jsurl/test/test_bug384981.html new file mode 100644 index 000000000..b736f0468 --- /dev/null +++ b/dom/jsurl/test/test_bug384981.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=384981 +--> +<head> + <title>Test for Bug 384981</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <!-- Make sure we wait for the stylesheet load --> + <script>document.documentElement.offsetWidth</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384981">Mozilla Bug 384981</a> +<p id="display"> + <iframe src="javascript:'content'"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 384981 **/ +SimpleTest.waitForExplicitFinish() +addLoadEvent(function() { + is(window.frames[0].document.documentElement.textContent, "content", + "Onload should not fire before subframe loads"); +}); +addLoadEvent(SimpleTest.finish); + +</script> +</pre> +</body> +</html> + |