Bug 1065216 - Dispatch a fetch event to workers when controlled pages initiate a network load. r=baku,mayhemer,smaug
This commit is contained in:
@@ -52,6 +52,7 @@
|
|||||||
#include "nsIAuthPrompt2.h"
|
#include "nsIAuthPrompt2.h"
|
||||||
#include "nsIChannelEventSink.h"
|
#include "nsIChannelEventSink.h"
|
||||||
#include "nsIAsyncVerifyRedirectCallback.h"
|
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||||
|
#include "nsIServiceWorkerManager.h"
|
||||||
#include "nsIScriptSecurityManager.h"
|
#include "nsIScriptSecurityManager.h"
|
||||||
#include "nsIScriptObjectPrincipal.h"
|
#include "nsIScriptObjectPrincipal.h"
|
||||||
#include "nsIScrollableFrame.h"
|
#include "nsIScrollableFrame.h"
|
||||||
@@ -1041,6 +1042,7 @@ NS_INTERFACE_MAP_BEGIN(nsDocShell)
|
|||||||
NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
|
NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
|
||||||
NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands)
|
NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands)
|
||||||
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
|
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
|
||||||
|
NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
|
||||||
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)
|
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)
|
||||||
|
|
||||||
///*****************************************************************************
|
///*****************************************************************************
|
||||||
@@ -13921,6 +13923,52 @@ nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNavigate, bool* aShouldIntercept)
|
||||||
|
{
|
||||||
|
*aShouldIntercept = false;
|
||||||
|
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
|
||||||
|
if (!swm) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aIsNavigate) {
|
||||||
|
return swm->IsAvailableForURI(aURI, aShouldIntercept);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIDocument> doc = GetDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return swm->IsControlled(doc, aShouldIntercept);
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
|
||||||
|
if (!swm) {
|
||||||
|
aChannel->Cancel();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNavigation = false;
|
||||||
|
nsresult rv = aChannel->GetIsNavigation(&isNavigation);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIDocument> doc;
|
||||||
|
|
||||||
|
if (!isNavigation) {
|
||||||
|
doc = GetDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return swm->DispatchFetchEvent(doc, aChannel);
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
|
nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "nsIDocShell.h"
|
#include "nsIDocShell.h"
|
||||||
#include "nsIDocShellTreeItem.h"
|
#include "nsIDocShellTreeItem.h"
|
||||||
#include "nsIBaseWindow.h"
|
#include "nsIBaseWindow.h"
|
||||||
|
#include "nsINetworkInterceptController.h"
|
||||||
#include "nsIScrollable.h"
|
#include "nsIScrollable.h"
|
||||||
#include "nsITextScroll.h"
|
#include "nsITextScroll.h"
|
||||||
#include "nsIContentViewerContainer.h"
|
#include "nsIContentViewerContainer.h"
|
||||||
@@ -152,6 +153,7 @@ class nsDocShell MOZ_FINAL
|
|||||||
, public nsILinkHandler
|
, public nsILinkHandler
|
||||||
, public nsIClipboardCommands
|
, public nsIClipboardCommands
|
||||||
, public nsIDOMStorageManager
|
, public nsIDOMStorageManager
|
||||||
|
, public nsINetworkInterceptController
|
||||||
, public mozilla::SupportsWeakPtr<nsDocShell>
|
, public mozilla::SupportsWeakPtr<nsDocShell>
|
||||||
{
|
{
|
||||||
friend class nsDSURIContentListener;
|
friend class nsDSURIContentListener;
|
||||||
@@ -182,6 +184,7 @@ public:
|
|||||||
NS_DECL_NSIAUTHPROMPTPROVIDER
|
NS_DECL_NSIAUTHPROMPTPROVIDER
|
||||||
NS_DECL_NSICLIPBOARDCOMMANDS
|
NS_DECL_NSICLIPBOARDCOMMANDS
|
||||||
NS_DECL_NSIWEBSHELLSERVICES
|
NS_DECL_NSIWEBSHELLSERVICES
|
||||||
|
NS_DECL_NSINETWORKINTERCEPTCONTROLLER
|
||||||
NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager())
|
NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager())
|
||||||
|
|
||||||
NS_IMETHOD Stop() MOZ_OVERRIDE {
|
NS_IMETHOD Stop() MOZ_OVERRIDE {
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ protected:
|
|||||||
DECL_SHIM(nsIApplicationCacheContainer, NSIAPPLICATIONCACHECONTAINER)
|
DECL_SHIM(nsIApplicationCacheContainer, NSIAPPLICATIONCACHECONTAINER)
|
||||||
#undef DECL_SHIM
|
#undef DECL_SHIM
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an ExternalResource for aURI. aViewer and aLoadGroup might be null
|
* Add an ExternalResource for aURI. aViewer and aLoadGroup might be null
|
||||||
* when this is called if the URI didn't result in an XML document. This
|
* when this is called if the URI didn't result in an XML document. This
|
||||||
|
|||||||
@@ -418,6 +418,14 @@ DOMInterfaces = {
|
|||||||
'nativeType': 'mozilla::dom::workers::ExtendableEvent',
|
'nativeType': 'mozilla::dom::workers::ExtendableEvent',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'FetchEvent': {
|
||||||
|
'headerFile': 'ServiceWorkerEvents.h',
|
||||||
|
'nativeType': 'mozilla::dom::workers::FetchEvent',
|
||||||
|
'binaryNames': {
|
||||||
|
'request': 'request_'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'FileList': {
|
'FileList': {
|
||||||
'headerFile': 'mozilla/dom/File.h',
|
'headerFile': 'mozilla/dom/File.h',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -866,6 +866,11 @@ FetchBody<Request>::FetchBody();
|
|||||||
template
|
template
|
||||||
FetchBody<Response>::FetchBody();
|
FetchBody<Response>::FetchBody();
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
FetchBody<Derived>::~FetchBody()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true if addref succeeded.
|
// Returns true if addref succeeded.
|
||||||
// Always succeeds on main thread.
|
// Always succeeds on main thread.
|
||||||
// May fail on worker if RegisterFeature() fails. In that case, it will release
|
// May fail on worker if RegisterFeature() fails. In that case, it will release
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ public:
|
|||||||
void
|
void
|
||||||
CancelPump();
|
CancelPump();
|
||||||
|
|
||||||
|
void
|
||||||
|
SetBodyUsed()
|
||||||
|
{
|
||||||
|
mBodyUsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Always set whenever the FetchBody is created on the worker thread.
|
// Always set whenever the FetchBody is created on the worker thread.
|
||||||
workers::WorkerPrivate* mWorkerPrivate;
|
workers::WorkerPrivate* mWorkerPrivate;
|
||||||
|
|
||||||
@@ -145,15 +151,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
FetchBody();
|
FetchBody();
|
||||||
|
|
||||||
virtual ~FetchBody()
|
virtual ~FetchBody();
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
SetBodyUsed()
|
|
||||||
{
|
|
||||||
mBodyUsed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
SetMimeType(ErrorResult& aRv);
|
SetMimeType(ErrorResult& aRv);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "domstubs.idl"
|
#include "domstubs.idl"
|
||||||
|
|
||||||
interface nsIDocument;
|
interface nsIDocument;
|
||||||
|
interface nsIInterceptedChannel;
|
||||||
interface nsIPrincipal;
|
interface nsIPrincipal;
|
||||||
interface nsIURI;
|
interface nsIURI;
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports
|
|||||||
[noscript] void UnregisterFailed();
|
[noscript] void UnregisterFailed();
|
||||||
};
|
};
|
||||||
|
|
||||||
[builtinclass, uuid(861b55e9-d6ac-47cf-a528-8590e9b44de6)]
|
[builtinclass, uuid(464882c8-81c0-4620-b9c4-44c12085b65b)]
|
||||||
interface nsIServiceWorkerManager : nsISupports
|
interface nsIServiceWorkerManager : nsISupports
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +52,15 @@ interface nsIServiceWorkerManager : nsISupports
|
|||||||
// Remove ready pending Promise
|
// Remove ready pending Promise
|
||||||
void removeReadyPromise(in nsIDOMWindow aWindow);
|
void removeReadyPromise(in nsIDOMWindow aWindow);
|
||||||
|
|
||||||
|
// Returns true if a ServiceWorker is available for the scope of aURI.
|
||||||
|
bool isAvailableForURI(in nsIURI aURI);
|
||||||
|
|
||||||
|
// Returns true if a given document is currently controlled by a ServiceWorker
|
||||||
|
bool isControlled(in nsIDocument aDocument);
|
||||||
|
|
||||||
|
// Cause a fetch event to be dispatched to the worker global associated with the given document.
|
||||||
|
void dispatchFetchEvent(in nsIDocument aDoc, in nsIInterceptedChannel aChannel);
|
||||||
|
|
||||||
// aTarget MUST be a ServiceWorkerRegistration.
|
// aTarget MUST be a ServiceWorkerRegistration.
|
||||||
[noscript] void AddRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
|
[noscript] void AddRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
|
||||||
[noscript] void RemoveRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
|
[noscript] void RemoveRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
|
||||||
|
|||||||
27
dom/webidl/FetchEvent.webidl
Normal file
27
dom/webidl/FetchEvent.webidl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* 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/.
|
||||||
|
*
|
||||||
|
* For more information on this interface, please see
|
||||||
|
* http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Constructor(DOMString type, optional FetchEventInit eventInitDict),
|
||||||
|
Func="mozilla::dom::workers::ServiceWorkerVisible",
|
||||||
|
Exposed=(ServiceWorker)]
|
||||||
|
interface FetchEvent : Event {
|
||||||
|
readonly attribute Request request;
|
||||||
|
readonly attribute ServiceWorkerClient client; // The window issuing the request.
|
||||||
|
readonly attribute boolean isReload;
|
||||||
|
|
||||||
|
[Throws] void respondWith(Promise<Response> r);
|
||||||
|
Promise<Response> forwardTo(USVString url);
|
||||||
|
Promise<Response> default();
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary FetchEventInit : EventInit {
|
||||||
|
Request request;
|
||||||
|
ServiceWorkerClient client;
|
||||||
|
boolean isReload;
|
||||||
|
};
|
||||||
@@ -132,6 +132,7 @@ WEBIDL_FILES = [
|
|||||||
'EventTarget.webidl',
|
'EventTarget.webidl',
|
||||||
'ExtendableEvent.webidl',
|
'ExtendableEvent.webidl',
|
||||||
'Fetch.webidl',
|
'Fetch.webidl',
|
||||||
|
'FetchEvent.webidl',
|
||||||
'File.webidl',
|
'File.webidl',
|
||||||
'FileList.webidl',
|
'FileList.webidl',
|
||||||
'FileMode.webidl',
|
'FileMode.webidl',
|
||||||
|
|||||||
@@ -5,9 +5,20 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "ServiceWorkerEvents.h"
|
#include "ServiceWorkerEvents.h"
|
||||||
|
#include "ServiceWorkerClient.h"
|
||||||
|
|
||||||
|
#include "nsINetworkInterceptController.h"
|
||||||
|
#include "nsIOutputStream.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
|
#include "nsComponentManagerUtils.h"
|
||||||
|
#include "nsServiceManagerUtils.h"
|
||||||
|
#include "nsStreamUtils.h"
|
||||||
|
#include "nsNetCID.h"
|
||||||
|
|
||||||
|
#include "mozilla/dom/FetchEventBinding.h"
|
||||||
|
#include "mozilla/dom/PromiseNativeHandler.h"
|
||||||
|
#include "mozilla/dom/Request.h"
|
||||||
|
#include "mozilla/dom/Response.h"
|
||||||
#include "mozilla/dom/WorkerScope.h"
|
#include "mozilla/dom/WorkerScope.h"
|
||||||
#include "mozilla/dom/workers/bindings/ServiceWorker.h"
|
#include "mozilla/dom/workers/bindings/ServiceWorker.h"
|
||||||
|
|
||||||
@@ -15,6 +26,271 @@ using namespace mozilla::dom;
|
|||||||
|
|
||||||
BEGIN_WORKERS_NAMESPACE
|
BEGIN_WORKERS_NAMESPACE
|
||||||
|
|
||||||
|
FetchEvent::FetchEvent(EventTarget* aOwner)
|
||||||
|
: Event(aOwner, nullptr, nullptr)
|
||||||
|
, mWindowId(0)
|
||||||
|
, mIsReload(false)
|
||||||
|
, mWaitToRespond(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchEvent::~FetchEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FetchEvent::PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
|
||||||
|
uint64_t aWindowId)
|
||||||
|
{
|
||||||
|
mChannel = aChannel;
|
||||||
|
mServiceWorker = aServiceWorker;
|
||||||
|
mWindowId = aWindowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ already_AddRefed<FetchEvent>
|
||||||
|
FetchEvent::Constructor(const GlobalObject& aGlobal,
|
||||||
|
const nsAString& aType,
|
||||||
|
const FetchEventInit& aOptions,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsRefPtr<EventTarget> owner = do_QueryObject(aGlobal.GetAsSupports());
|
||||||
|
MOZ_ASSERT(owner);
|
||||||
|
nsRefPtr<FetchEvent> e = new FetchEvent(owner);
|
||||||
|
bool trusted = e->Init(owner);
|
||||||
|
e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
|
||||||
|
e->SetTrusted(trusted);
|
||||||
|
e->mRequest = aOptions.mRequest.WasPassed() ?
|
||||||
|
&aOptions.mRequest.Value() : nullptr;
|
||||||
|
e->mIsReload = aOptions.mIsReload.WasPassed() ?
|
||||||
|
aOptions.mIsReload.Value() : false;
|
||||||
|
e->mClient = aOptions.mClient.WasPassed() ?
|
||||||
|
&aOptions.mClient.Value() : nullptr;
|
||||||
|
return e.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class CancelChannelRunnable MOZ_FINAL : public nsRunnable
|
||||||
|
{
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||||
|
public:
|
||||||
|
explicit CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
|
||||||
|
: mChannel(aChannel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHOD Run()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
nsresult rv = mChannel->Cancel();
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FinishResponse MOZ_FINAL : public nsRunnable
|
||||||
|
{
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||||
|
public:
|
||||||
|
explicit FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
|
||||||
|
: mChannel(aChannel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHOD
|
||||||
|
Run()
|
||||||
|
{
|
||||||
|
AssertIsOnMainThread();
|
||||||
|
nsresult rv = mChannel->FinishSynthesizedResponse();
|
||||||
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to finish synthesized response");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RespondWithHandler MOZ_FINAL : public PromiseNativeHandler
|
||||||
|
{
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
|
||||||
|
public:
|
||||||
|
RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
|
||||||
|
: mInterceptedChannel(aChannel)
|
||||||
|
, mServiceWorker(aServiceWorker)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
|
||||||
|
|
||||||
|
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
|
||||||
|
|
||||||
|
void CancelRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RespondWithClosure
|
||||||
|
{
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
|
||||||
|
|
||||||
|
explicit RespondWithClosure(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
|
||||||
|
: mInterceptedChannel(aChannel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
|
||||||
|
{
|
||||||
|
nsAutoPtr<RespondWithClosure> data(static_cast<RespondWithClosure*>(aClosure));
|
||||||
|
nsCOMPtr<nsIRunnable> event;
|
||||||
|
if (NS_SUCCEEDED(aStatus)) {
|
||||||
|
event = new FinishResponse(data->mInterceptedChannel);
|
||||||
|
} else {
|
||||||
|
event = new CancelChannelRunnable(data->mInterceptedChannel);
|
||||||
|
}
|
||||||
|
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(event)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class MOZ_STACK_CLASS AutoCancel
|
||||||
|
{
|
||||||
|
nsRefPtr<RespondWithHandler> mOwner;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AutoCancel(RespondWithHandler* aOwner)
|
||||||
|
: mOwner(aOwner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoCancel()
|
||||||
|
{
|
||||||
|
if (mOwner) {
|
||||||
|
mOwner->CancelRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
mOwner = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
|
||||||
|
{
|
||||||
|
AutoCancel autoCancel(this);
|
||||||
|
|
||||||
|
if (!aValue.isObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<Response> response;
|
||||||
|
nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIInputStream> body;
|
||||||
|
response->GetBody(getter_AddRefs(body));
|
||||||
|
if (NS_WARN_IF(!body) || NS_WARN_IF(response->BodyUsed())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response->SetBodyUsed();
|
||||||
|
|
||||||
|
nsCOMPtr<nsIOutputStream> responseBody;
|
||||||
|
rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsAutoPtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel));
|
||||||
|
|
||||||
|
nsCOMPtr<nsIEventTarget> stsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
||||||
|
if (NS_WARN_IF(!stsThread)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rv = NS_AsyncCopy(body, responseBody, stsThread, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
|
||||||
|
RespondWithCopyComplete, closure.forget());
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoCancel.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
|
||||||
|
{
|
||||||
|
CancelRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RespondWithHandler::CancelRequest()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIRunnable> runnable = new CancelChannelRunnable(mInterceptedChannel);
|
||||||
|
NS_DispatchToMainThread(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
void
|
||||||
|
FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (mWaitToRespond) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mWaitToRespond = true;
|
||||||
|
nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker);
|
||||||
|
aPromise.AppendNativeHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<ServiceWorkerClient>
|
||||||
|
FetchEvent::Client()
|
||||||
|
{
|
||||||
|
if (!mClient) {
|
||||||
|
mClient = new ServiceWorkerClient(GetParentObject(), mWindowId);
|
||||||
|
}
|
||||||
|
nsRefPtr<ServiceWorkerClient> client = mClient;
|
||||||
|
return client.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<Promise>
|
||||||
|
FetchEvent::ForwardTo(const nsAString& aUrl)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||||
|
MOZ_ASSERT(global);
|
||||||
|
ErrorResult result;
|
||||||
|
nsRefPtr<Promise> promise = Promise::Create(global, result);
|
||||||
|
if (NS_WARN_IF(result.Failed())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||||
|
return promise.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<Promise>
|
||||||
|
FetchEvent::Default()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||||
|
MOZ_ASSERT(global);
|
||||||
|
ErrorResult result;
|
||||||
|
nsRefPtr<Promise> promise = Promise::Create(global, result);
|
||||||
|
if (result.Failed()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||||
|
return promise.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMPL_ADDREF_INHERITED(FetchEvent, Event)
|
||||||
|
NS_IMPL_RELEASE_INHERITED(FetchEvent, Event)
|
||||||
|
|
||||||
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchEvent)
|
||||||
|
NS_INTERFACE_MAP_END_INHERITING(Event)
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, Event, mRequest, mClient)
|
||||||
|
|
||||||
ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
|
ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
|
||||||
: Event(aOwner, nullptr, nullptr)
|
: Event(aOwner, nullptr, nullptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,12 +8,87 @@
|
|||||||
|
|
||||||
#include "mozilla/dom/Event.h"
|
#include "mozilla/dom/Event.h"
|
||||||
#include "mozilla/dom/ExtendableEventBinding.h"
|
#include "mozilla/dom/ExtendableEventBinding.h"
|
||||||
|
#include "mozilla/dom/FetchEventBinding.h"
|
||||||
#include "mozilla/dom/InstallEventBinding.h"
|
#include "mozilla/dom/InstallEventBinding.h"
|
||||||
#include "mozilla/dom/Promise.h"
|
#include "mozilla/dom/Promise.h"
|
||||||
|
#include "nsProxyRelease.h"
|
||||||
|
|
||||||
|
class nsIInterceptedChannel;
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
class Request;
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
BEGIN_WORKERS_NAMESPACE
|
BEGIN_WORKERS_NAMESPACE
|
||||||
|
|
||||||
class ServiceWorker;
|
class ServiceWorker;
|
||||||
|
class ServiceWorkerClient;
|
||||||
|
|
||||||
|
class FetchEvent MOZ_FINAL : public Event
|
||||||
|
{
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
|
||||||
|
nsRefPtr<ServiceWorkerClient> mClient;
|
||||||
|
nsRefPtr<Request> mRequest;
|
||||||
|
uint64_t mWindowId;
|
||||||
|
bool mIsReload;
|
||||||
|
bool mWaitToRespond;
|
||||||
|
protected:
|
||||||
|
explicit FetchEvent(EventTarget* aOwner);
|
||||||
|
~FetchEvent();
|
||||||
|
|
||||||
|
public:
|
||||||
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchEvent, Event)
|
||||||
|
NS_FORWARD_TO_EVENT
|
||||||
|
|
||||||
|
virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE
|
||||||
|
{
|
||||||
|
return FetchEventBinding::Wrap(aCx, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
|
||||||
|
uint64_t aWindowId);
|
||||||
|
|
||||||
|
static already_AddRefed<FetchEvent>
|
||||||
|
Constructor(const GlobalObject& aGlobal,
|
||||||
|
const nsAString& aType,
|
||||||
|
const FetchEventInit& aOptions,
|
||||||
|
ErrorResult& aRv);
|
||||||
|
|
||||||
|
bool
|
||||||
|
WaitToRespond() const
|
||||||
|
{
|
||||||
|
return mWaitToRespond;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request*
|
||||||
|
Request_() const
|
||||||
|
{
|
||||||
|
return mRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
already_AddRefed<ServiceWorkerClient>
|
||||||
|
Client();
|
||||||
|
|
||||||
|
bool
|
||||||
|
IsReload() const
|
||||||
|
{
|
||||||
|
return mIsReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RespondWith(Promise& aPromise, ErrorResult& aRv);
|
||||||
|
|
||||||
|
already_AddRefed<Promise>
|
||||||
|
ForwardTo(const nsAString& aUrl);
|
||||||
|
|
||||||
|
already_AddRefed<Promise>
|
||||||
|
Default();
|
||||||
|
};
|
||||||
|
|
||||||
class ExtendableEvent : public Event
|
class ExtendableEvent : public Event
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
#include "nsIStreamLoader.h"
|
#include "nsIStreamLoader.h"
|
||||||
#include "nsIHttpChannel.h"
|
#include "nsIHttpChannel.h"
|
||||||
#include "nsIHttpChannelInternal.h"
|
#include "nsIHttpChannelInternal.h"
|
||||||
|
#include "nsIHttpHeaderVisitor.h"
|
||||||
|
#include "nsINetworkInterceptController.h"
|
||||||
#include "nsPIDOMWindow.h"
|
#include "nsPIDOMWindow.h"
|
||||||
|
#include "nsDebug.h"
|
||||||
|
|
||||||
#include "jsapi.h"
|
#include "jsapi.h"
|
||||||
|
|
||||||
@@ -19,9 +22,13 @@
|
|||||||
#include "mozilla/dom/BindingUtils.h"
|
#include "mozilla/dom/BindingUtils.h"
|
||||||
#include "mozilla/dom/DOMError.h"
|
#include "mozilla/dom/DOMError.h"
|
||||||
#include "mozilla/dom/ErrorEvent.h"
|
#include "mozilla/dom/ErrorEvent.h"
|
||||||
|
#include "mozilla/dom/Headers.h"
|
||||||
#include "mozilla/dom/InstallEventBinding.h"
|
#include "mozilla/dom/InstallEventBinding.h"
|
||||||
|
#include "mozilla/dom/InternalHeaders.h"
|
||||||
#include "mozilla/dom/Navigator.h"
|
#include "mozilla/dom/Navigator.h"
|
||||||
#include "mozilla/dom/PromiseNativeHandler.h"
|
#include "mozilla/dom/PromiseNativeHandler.h"
|
||||||
|
#include "mozilla/dom/Request.h"
|
||||||
|
#include "mozilla/dom/RootedDictionary.h"
|
||||||
#include "mozilla/ipc/BackgroundChild.h"
|
#include "mozilla/ipc/BackgroundChild.h"
|
||||||
#include "mozilla/ipc/PBackgroundChild.h"
|
#include "mozilla/ipc/PBackgroundChild.h"
|
||||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||||
@@ -2121,6 +2128,267 @@ ServiceWorkerManager::GetServiceWorkerForScope(nsIDOMWindow* aWindow,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FetchEventRunnable : public WorkerRunnable
|
||||||
|
, public nsIHttpHeaderVisitor {
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
|
||||||
|
nsTArray<nsCString> mHeaderNames;
|
||||||
|
nsTArray<nsCString> mHeaderValues;
|
||||||
|
uint64_t mWindowId;
|
||||||
|
nsCString mSpec;
|
||||||
|
nsCString mMethod;
|
||||||
|
bool mIsReload;
|
||||||
|
public:
|
||||||
|
FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
|
||||||
|
uint64_t aWindowId)
|
||||||
|
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
|
||||||
|
, mInterceptedChannel(aChannel)
|
||||||
|
, mServiceWorker(aServiceWorker)
|
||||||
|
, mWindowId(aWindowId)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aWorkerPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
|
||||||
|
NS_IMETHOD
|
||||||
|
VisitHeader(const nsACString& aHeader, const nsACString& aValue)
|
||||||
|
{
|
||||||
|
mHeaderNames.AppendElement(aHeader);
|
||||||
|
mHeaderValues.AppendElement(aValue);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
Init()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIChannel> channel;
|
||||||
|
nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
rv = channel->GetURI(getter_AddRefs(uri));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
rv = uri->GetSpec(mSpec);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
|
||||||
|
NS_ENSURE_TRUE(httpChannel, NS_ERROR_NOT_AVAILABLE);
|
||||||
|
|
||||||
|
rv = httpChannel->GetRequestMethod(mMethod);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
uint32_t loadFlags;
|
||||||
|
rv = channel->GetLoadFlags(&loadFlags);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
//TODO(jdm): we should probably include reload-ness in the loadinfo or as a separate load flag
|
||||||
|
mIsReload = false;
|
||||||
|
|
||||||
|
rv = httpChannel->VisitRequestHeaders(this);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aWorkerPrivate);
|
||||||
|
return DispatchFetchEvent(aCx, aWorkerPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
~FetchEventRunnable() {}
|
||||||
|
|
||||||
|
class ResumeRequest MOZ_FINAL : public nsRunnable {
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||||
|
public:
|
||||||
|
explicit ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
|
||||||
|
: mChannel(aChannel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHOD Run()
|
||||||
|
{
|
||||||
|
AssertIsOnMainThread();
|
||||||
|
nsresult rv = mChannel->ResetInterception();
|
||||||
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to resume intercepted network request");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aCx);
|
||||||
|
MOZ_ASSERT(aWorkerPrivate);
|
||||||
|
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
|
||||||
|
GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
|
||||||
|
|
||||||
|
RequestOrUSVString requestInfo;
|
||||||
|
*requestInfo.SetAsUSVString().ToAStringPtr() = NS_ConvertUTF8toUTF16(mSpec);
|
||||||
|
|
||||||
|
RootedDictionary<RequestInit> reqInit(aCx);
|
||||||
|
reqInit.mMethod.Construct(mMethod);
|
||||||
|
|
||||||
|
nsRefPtr<InternalHeaders> internalHeaders = new InternalHeaders(HeadersGuardEnum::Request);
|
||||||
|
MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length());
|
||||||
|
for (uint32_t i = 0; i < mHeaderNames.Length(); i++) {
|
||||||
|
ErrorResult rv;
|
||||||
|
internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], rv);
|
||||||
|
if (NS_WARN_IF(rv.Failed())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders);
|
||||||
|
reqInit.mHeaders.Construct();
|
||||||
|
reqInit.mHeaders.Value().SetAsHeaders() = headers;
|
||||||
|
|
||||||
|
//TODO(jdm): set request body
|
||||||
|
//TODO(jdm): set request same-origin mode and credentials
|
||||||
|
|
||||||
|
ErrorResult rv;
|
||||||
|
nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, rv);
|
||||||
|
if (NS_WARN_IF(rv.Failed())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RootedDictionary<FetchEventInit> init(aCx);
|
||||||
|
init.mRequest.Construct();
|
||||||
|
init.mRequest.Value() = request;
|
||||||
|
init.mBubbles = false;
|
||||||
|
init.mCancelable = true;
|
||||||
|
init.mIsReload.Construct(mIsReload);
|
||||||
|
nsRefPtr<FetchEvent> event =
|
||||||
|
FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, rv);
|
||||||
|
if (NS_WARN_IF(rv.Failed())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
event->PostInit(mInterceptedChannel, mServiceWorker, mWindowId);
|
||||||
|
event->SetTrusted(true);
|
||||||
|
|
||||||
|
nsRefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
|
||||||
|
nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) {
|
||||||
|
nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
|
||||||
|
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChannel* aChannel)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aChannel);
|
||||||
|
nsCOMPtr<nsISupports> serviceWorker;
|
||||||
|
|
||||||
|
bool isNavigation = false;
|
||||||
|
nsresult rv = aChannel->GetIsNavigation(&isNavigation);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
if (!isNavigation) {
|
||||||
|
MOZ_ASSERT(aDoc);
|
||||||
|
rv = GetDocumentController(aDoc->GetWindow(), getter_AddRefs(serviceWorker));
|
||||||
|
} else {
|
||||||
|
nsCOMPtr<nsIChannel> internalChannel;
|
||||||
|
rv = aChannel->GetChannel(getter_AddRefs(internalChannel));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
rv = internalChannel->GetURI(getter_AddRefs(uri));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
nsRefPtr<ServiceWorkerRegistrationInfo> registration =
|
||||||
|
GetServiceWorkerRegistrationInfo(uri);
|
||||||
|
// This should only happen if IsAvailableForURI() returned true.
|
||||||
|
MOZ_ASSERT(registration);
|
||||||
|
MOZ_ASSERT(registration->mActiveWorker);
|
||||||
|
|
||||||
|
nsRefPtr<ServiceWorker> sw;
|
||||||
|
rv = CreateServiceWorker(registration->mPrincipal,
|
||||||
|
registration->mActiveWorker->ScriptSpec(),
|
||||||
|
registration->mScope,
|
||||||
|
getter_AddRefs(sw));
|
||||||
|
serviceWorker = sw.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
|
||||||
|
new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
|
||||||
|
|
||||||
|
uint64_t windowId = aDoc ? aDoc->GetInnerWindow()->WindowID() : 0;
|
||||||
|
|
||||||
|
nsRefPtr<ServiceWorker> sw = static_cast<ServiceWorker*>(serviceWorker.get());
|
||||||
|
nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
|
||||||
|
new nsMainThreadPtrHolder<ServiceWorker>(sw));
|
||||||
|
|
||||||
|
nsRefPtr<FetchEventRunnable> event =
|
||||||
|
new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, windowId);
|
||||||
|
rv = event->Init();
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
AutoJSAPI api;
|
||||||
|
api.Init();
|
||||||
|
if (NS_WARN_IF(!event->Dispatch(api.cx()))) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ServiceWorkerManager::IsAvailableForURI(nsIURI* aURI, bool* aIsAvailable)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aURI);
|
||||||
|
MOZ_ASSERT(aIsAvailable);
|
||||||
|
nsRefPtr<ServiceWorkerRegistrationInfo> registration =
|
||||||
|
GetServiceWorkerRegistrationInfo(aURI);
|
||||||
|
*aIsAvailable = registration && registration->mActiveWorker;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ServiceWorkerManager::IsControlled(nsIDocument* aDoc, bool* aIsControlled)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aDoc);
|
||||||
|
MOZ_ASSERT(aIsControlled);
|
||||||
|
nsRefPtr<ServiceWorkerRegistrationInfo> registration;
|
||||||
|
nsresult rv = GetDocumentRegistration(aDoc, getter_AddRefs(registration));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
*aIsControlled = !!registration;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc,
|
||||||
|
ServiceWorkerRegistrationInfo** aRegistrationInfo)
|
||||||
|
{
|
||||||
|
nsRefPtr<ServiceWorkerRegistrationInfo> registration;
|
||||||
|
if (!mControlledDocuments.Get(aDoc, getter_AddRefs(registration))) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the document is controlled, the current worker MUST be non-null.
|
||||||
|
if (!registration->mActiveWorker) {
|
||||||
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
registration.forget(aRegistrationInfo);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The .controller is for the registration associated with the document when
|
* The .controller is for the registration associated with the document when
|
||||||
* the document was loaded.
|
* the document was loaded.
|
||||||
@@ -2137,21 +2405,16 @@ ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow, nsISupports**
|
|||||||
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
||||||
|
|
||||||
nsRefPtr<ServiceWorkerRegistrationInfo> registration;
|
nsRefPtr<ServiceWorkerRegistrationInfo> registration;
|
||||||
if (!mControlledDocuments.Get(doc, getter_AddRefs(registration))) {
|
nsresult rv = GetDocumentRegistration(doc, getter_AddRefs(registration));
|
||||||
return NS_ERROR_FAILURE;
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the document is controlled, the current worker MUST be non-null.
|
|
||||||
if (!registration->mActiveWorker) {
|
|
||||||
return NS_ERROR_NOT_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
nsRefPtr<ServiceWorker> serviceWorker;
|
nsRefPtr<ServiceWorker> serviceWorker;
|
||||||
nsresult rv = CreateServiceWorkerForWindow(window,
|
rv = CreateServiceWorkerForWindow(window,
|
||||||
registration->mActiveWorker->ScriptSpec(),
|
registration->mActiveWorker->ScriptSpec(),
|
||||||
registration->mScope,
|
registration->mScope,
|
||||||
getter_AddRefs(serviceWorker));
|
getter_AddRefs(serviceWorker));
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -399,6 +399,9 @@ private:
|
|||||||
nsresult
|
nsresult
|
||||||
Update(ServiceWorkerRegistrationInfo* aRegistration);
|
Update(ServiceWorkerRegistrationInfo* aRegistration);
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
GetDocumentRegistration(nsIDocument* aDoc, ServiceWorkerRegistrationInfo** aRegistrationInfo);
|
||||||
|
|
||||||
NS_IMETHOD
|
NS_IMETHOD
|
||||||
CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
|
CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
|
||||||
const nsACString& aScriptSpec,
|
const nsACString& aScriptSpec,
|
||||||
|
|||||||
54
dom/workers/test/serviceworkers/fetch/fetch_tests.js
Normal file
54
dom/workers/test/serviceworkers/fetch/fetch_tests.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
function fetch(name, onload, onerror, headers) {
|
||||||
|
expectAsyncResult();
|
||||||
|
|
||||||
|
onload = onload || function() {
|
||||||
|
my_ok(false, "XHR load should not complete successfully");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
onerror = onerror || function() {
|
||||||
|
my_ok(false, "XHR load should be intercepted successfully");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
|
var x = new XMLHttpRequest();
|
||||||
|
x.open('GET', name, true);
|
||||||
|
x.onload = function() { onload(x) };
|
||||||
|
x.onerror = function() { onerror(x) };
|
||||||
|
headers = headers || [];
|
||||||
|
headers.forEach(function(header) {
|
||||||
|
x.setRequestHeader(header[0], header[1]);
|
||||||
|
});
|
||||||
|
x.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('synthesized.txt', function(xhr) {
|
||||||
|
my_ok(xhr.status == 200, "load should be successful");
|
||||||
|
my_ok(xhr.responseText == "synthesized response body", "load should have synthesized response");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('ignored.txt', function(xhr) {
|
||||||
|
my_ok(xhr.status == 404, "load should be uninterrupted");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('rejected.txt', null, function(xhr) {
|
||||||
|
my_ok(xhr.status == 0, "load should not complete");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('nonresponse.txt', null, function(xhr) {
|
||||||
|
my_ok(xhr.status == 0, "load should not complete");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('nonresponse2.txt', null, function(xhr) {
|
||||||
|
my_ok(xhr.status == 0, "load should not complete");
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('headers.txt', function(xhr) {
|
||||||
|
my_ok(xhr.status == 200, "load should be successful");
|
||||||
|
my_ok(xhr.responseText == "1", "request header checks should have passed");
|
||||||
|
finish();
|
||||||
|
}, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]);
|
||||||
29
dom/workers/test/serviceworkers/fetch/fetch_worker_script.js
Normal file
29
dom/workers/test/serviceworkers/fetch/fetch_worker_script.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
function my_ok(v, msg) {
|
||||||
|
postMessage({type: "ok", value: v, msg: msg});
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
postMessage('finish');
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectAsyncResult() {
|
||||||
|
postMessage('expect');
|
||||||
|
}
|
||||||
|
|
||||||
|
expectAsyncResult();
|
||||||
|
try {
|
||||||
|
var success = false;
|
||||||
|
importScripts("nonexistent_imported_script.js");
|
||||||
|
} catch(x) {
|
||||||
|
}
|
||||||
|
|
||||||
|
my_ok(success, "worker imported script should be intercepted");
|
||||||
|
finish();
|
||||||
|
|
||||||
|
function check_intercepted_script() {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
importScripts('fetch_tests.js')
|
||||||
|
|
||||||
|
finish(); //corresponds to the gExpected increment before creating this worker
|
||||||
147
dom/workers/test/serviceworkers/fetch/index.html
Normal file
147
dom/workers/test/serviceworkers/fetch/index.html
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<!--
|
||||||
|
Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
-->
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Bug 94048 - test install event.</title>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none"></div>
|
||||||
|
<div id="style-test" style="background-color: white"></div>
|
||||||
|
<pre id="test"></pre>
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
function my_ok(result, msg) {
|
||||||
|
window.opener.postMessage({status: "ok", result: result, message: msg}, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_intercepted_script() {
|
||||||
|
document.getElementById('intercepted-script').test_result =
|
||||||
|
document.currentScript == document.getElementById('intercepted-script');
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch(name, onload, onerror, headers) {
|
||||||
|
gExpected++;
|
||||||
|
|
||||||
|
onload = onload || function() {
|
||||||
|
my_ok(false, "load should not complete successfully");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
onerror = onerror || function() {
|
||||||
|
my_ok(false, "load should be intercepted successfully");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
|
var x = new XMLHttpRequest();
|
||||||
|
x.open('GET', name, true);
|
||||||
|
x.onload = function() { onload(x) };
|
||||||
|
x.onerror = function() { onerror(x) };
|
||||||
|
headers = headers || [];
|
||||||
|
headers.forEach(function(header) {
|
||||||
|
x.setRequestHeader(header[0], header[1]);
|
||||||
|
});
|
||||||
|
x.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gExpected = 0;
|
||||||
|
var gEncountered = 0;
|
||||||
|
function finish() {
|
||||||
|
gEncountered++;
|
||||||
|
if (gEncountered == gExpected) {
|
||||||
|
window.opener.postMessage({status: "done"}, "*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_onload(creator, complete) {
|
||||||
|
gExpected++;
|
||||||
|
var elem = creator();
|
||||||
|
elem.onload = function() {
|
||||||
|
complete.call(elem);
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
elem.onerror = function() {
|
||||||
|
my_ok(false, elem.tagName + " load should complete successfully");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectAsyncResult() {
|
||||||
|
gExpected++;
|
||||||
|
}
|
||||||
|
|
||||||
|
my_ok(navigator.serviceWorker.controller != null, "should be controlled");
|
||||||
|
</script>
|
||||||
|
<script src="fetch_tests.js"></script>
|
||||||
|
<script>
|
||||||
|
test_onload(function() {
|
||||||
|
var elem = document.createElement('img');
|
||||||
|
elem.src = "nonexistent_image.gifs";
|
||||||
|
elem.id = 'intercepted-img';
|
||||||
|
return elem;
|
||||||
|
}, function() {
|
||||||
|
my_ok(this.complete, "image should be complete");
|
||||||
|
my_ok(this.naturalWidth == 1 && this.naturalHeight == 1, "image should be 1x1 gif");
|
||||||
|
});
|
||||||
|
|
||||||
|
test_onload(function() {
|
||||||
|
var elem = document.createElement('script');
|
||||||
|
elem.id = 'intercepted-script';
|
||||||
|
elem.src = "nonexistent_script.js";
|
||||||
|
return elem;
|
||||||
|
}, function() {
|
||||||
|
my_ok(this.test_result, "script load should be intercepted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test_onload(function() {
|
||||||
|
var elem = document.createElement('link');
|
||||||
|
elem.href = "nonexistent_stylesheet.css";
|
||||||
|
elem.rel = "stylesheet";
|
||||||
|
return elem;
|
||||||
|
}, function() {
|
||||||
|
var styled = document.getElementById('style-test');
|
||||||
|
my_ok(window.getComputedStyle(styled).backgroundColor == 'rgb(0, 0, 0)',
|
||||||
|
"stylesheet load should be intercepted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test_onload(function() {
|
||||||
|
var elem = document.createElement('iframe');
|
||||||
|
elem.id = 'intercepted-iframe';
|
||||||
|
elem.src = "nonexistent_page.html";
|
||||||
|
return elem;
|
||||||
|
}, function() {
|
||||||
|
my_ok(this.test_result, "iframe load should be intercepted");
|
||||||
|
});
|
||||||
|
|
||||||
|
gExpected++;
|
||||||
|
var worker = new Worker('nonexistent_worker_script.js');
|
||||||
|
worker.onmessage = function(e) {
|
||||||
|
my_ok(e.data == "worker-intercept-success", "worker load intercepted");
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
worker.onerror = function() {
|
||||||
|
my_ok(false, "worker load should be intercepted");
|
||||||
|
};
|
||||||
|
|
||||||
|
gExpected++;
|
||||||
|
var worker = new Worker('fetch_worker_script.js');
|
||||||
|
worker.onmessage = function(e) {
|
||||||
|
if (e.data == "finish") {
|
||||||
|
finish();
|
||||||
|
} else if (e.data == "expect") {
|
||||||
|
gExpected++;
|
||||||
|
} else if (e.data.type == "ok") {
|
||||||
|
my_ok(e.data.value, e.data.msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.onerror = function() {
|
||||||
|
my_ok(false, "worker should not cause any errors");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
dom/workers/test/serviceworkers/fetch_event_worker.js
Normal file
86
dom/workers/test/serviceworkers/fetch_event_worker.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
onfetch = function(ev) {
|
||||||
|
if (ev.request.url.contains("synthesized.txt")) {
|
||||||
|
var p = new Promise(function(resolve) {
|
||||||
|
var r = new Response("synthesized response body", {});
|
||||||
|
resolve(r);
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("ignored.txt")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("rejected.txt")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonresponse.txt")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(5);
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonresponse2.txt")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve({});
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("headers.txt")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
var ok = true;
|
||||||
|
ok &= ev.request.headers.get("X-Test1") == "header1";
|
||||||
|
ok &= ev.request.headers.get("X-Test2") == "header2";
|
||||||
|
var r = new Response(ok.toString(), {});
|
||||||
|
resolve(r);
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_image.gif")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response(atob("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs"), {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_script.js")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response("check_intercepted_script();", {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_stylesheet.css")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response("#style-test { background-color: black !important; }", {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_page.html")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response("<script>window.frameElement.test_result = true;</script>", {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_worker_script.js")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response("postMessage('worker-intercept-success')", {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ev.request.url.contains("nonexistent_imported_script.js")) {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
resolve(new Response("check_intercepted_script();", {}));
|
||||||
|
});
|
||||||
|
ev.respondWith(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ support-files =
|
|||||||
worker.js
|
worker.js
|
||||||
worker2.js
|
worker2.js
|
||||||
worker3.js
|
worker3.js
|
||||||
|
fetch_event_worker.js
|
||||||
parse_error_worker.js
|
parse_error_worker.js
|
||||||
activate_event_error_worker.js
|
activate_event_error_worker.js
|
||||||
install_event_worker.js
|
install_event_worker.js
|
||||||
@@ -20,10 +21,14 @@ support-files =
|
|||||||
worker_unregister.js
|
worker_unregister.js
|
||||||
worker_update.js
|
worker_update.js
|
||||||
message_posting_worker.js
|
message_posting_worker.js
|
||||||
|
fetch/index.html
|
||||||
|
fetch/fetch_worker_script.js
|
||||||
|
fetch/fetch_tests.js
|
||||||
|
|
||||||
[test_unregister.html]
|
[test_unregister.html]
|
||||||
skip-if = true # Bug 1133805
|
skip-if = true # Bug 1133805
|
||||||
[test_installation_simple.html]
|
[test_installation_simple.html]
|
||||||
|
[test_fetch_event.html]
|
||||||
[test_get_serviced.html]
|
[test_get_serviced.html]
|
||||||
[test_install_event.html]
|
[test_install_event.html]
|
||||||
[test_navigator.html]
|
[test_navigator.html]
|
||||||
|
|||||||
61
dom/workers/test/serviceworkers/test_fetch_event.html
Normal file
61
dom/workers/test/serviceworkers/test_fetch_event.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<!--
|
||||||
|
Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
-->
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Bug 94048 - test install event.</title>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none"></div>
|
||||||
|
<pre id="test"></pre>
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
|
||||||
|
function simpleRegister() {
|
||||||
|
var p = navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" });
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testController() {
|
||||||
|
var p = new Promise(function(resolve, reject) {
|
||||||
|
window.onmessage = function(e) {
|
||||||
|
if (e.data.status == "ok") {
|
||||||
|
ok(e.data.result, e.data.message);
|
||||||
|
} else if (e.data.status == "done") {
|
||||||
|
window.onmessage = null;
|
||||||
|
w.close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var w = window.open("fetch/index.html");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTest() {
|
||||||
|
simpleRegister()
|
||||||
|
.then(testController)
|
||||||
|
.then(function() {
|
||||||
|
SimpleTest.finish();
|
||||||
|
}).catch(function(e) {
|
||||||
|
ok(false, "Some test failed with error " + e);
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.serviceWorkers.enabled", true],
|
||||||
|
["dom.serviceWorkers.testing.enabled", true],
|
||||||
|
["dom.fetch.enabled", true]
|
||||||
|
]}, runTest);
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface nsIHttpChannelInternal;
|
interface nsIChannel;
|
||||||
interface nsIOutputStream;
|
interface nsIOutputStream;
|
||||||
interface nsIURI;
|
interface nsIURI;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ interface nsIURI;
|
|||||||
* which do not implement nsIChannel.
|
* which do not implement nsIChannel.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[scriptable, uuid(0b5f82a7-5824-4a0d-bf5c-8a8a7684c0c8)]
|
[scriptable, uuid(9d127b63-dfad-484d-a0e1-cb82697a095b)]
|
||||||
interface nsIInterceptedChannel : nsISupports
|
interface nsIInterceptedChannel : nsISupports
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +37,28 @@ interface nsIInterceptedChannel : nsISupports
|
|||||||
* after this point.
|
* after this point.
|
||||||
*/
|
*/
|
||||||
void finishSynthesizedResponse();
|
void finishSynthesizedResponse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the pending intercepted request.
|
||||||
|
* @return NS_ERROR_FAILURE if the response has already been synthesized or
|
||||||
|
* the original request has been instructed to continue.
|
||||||
|
*/
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The synthesized response body to be produced.
|
||||||
|
*/
|
||||||
|
readonly attribute nsIOutputStream responseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying channel object that was intercepted.
|
||||||
|
*/
|
||||||
|
readonly attribute nsIChannel channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the underlying request was caused by a navigation attempt.
|
||||||
|
*/
|
||||||
|
readonly attribute bool isNavigation;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +67,7 @@ interface nsIInterceptedChannel : nsISupports
|
|||||||
* request should be intercepted before any network request is initiated.
|
* request should be intercepted before any network request is initiated.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[scriptable, uuid(b3ad3e9b-91d8-44d0-a0c5-dc2e9374f599)]
|
[scriptable, uuid(69150b77-b561-43a2-bfba-7301dd5a35d0)]
|
||||||
interface nsINetworkInterceptController : nsISupports
|
interface nsINetworkInterceptController : nsISupports
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -53,15 +75,15 @@ interface nsINetworkInterceptController : nsISupports
|
|||||||
* requests until specifically instructed to do so.
|
* requests until specifically instructed to do so.
|
||||||
*
|
*
|
||||||
* @param aURI the URI being requested by a channel
|
* @param aURI the URI being requested by a channel
|
||||||
|
* @param aIsNavigate True if the request is for a navigation, false for a fetch.
|
||||||
*/
|
*/
|
||||||
bool shouldPrepareForIntercept(in nsIURI aURI);
|
bool shouldPrepareForIntercept(in nsIURI aURI, in bool aIsNavigate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification when a given intercepted channel is prepared to accept a synthesized
|
* Notification when a given intercepted channel is prepared to accept a synthesized
|
||||||
* response via the provided stream.
|
* response via the provided stream.
|
||||||
*
|
*
|
||||||
* @param aChannel the controlling interface for a channel that has been intercepted
|
* @param aChannel the controlling interface for a channel that has been intercepted
|
||||||
* @param aStream a stream directly into the channel's synthesized response body
|
|
||||||
*/
|
*/
|
||||||
void channelIntercepted(in nsIInterceptedChannel aChannel, in nsIOutputStream aStream);
|
void channelIntercepted(in nsIInterceptedChannel aChannel);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1933,6 +1933,12 @@ HttpBaseChannel::GetURIPrincipal()
|
|||||||
return mPrincipal;
|
return mPrincipal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
HttpBaseChannel::IsNavigation()
|
||||||
|
{
|
||||||
|
return mLoadFlags & LOAD_DOCUMENT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
HttpBaseChannel::ShouldIntercept()
|
HttpBaseChannel::ShouldIntercept()
|
||||||
{
|
{
|
||||||
@@ -1940,7 +1946,9 @@ HttpBaseChannel::ShouldIntercept()
|
|||||||
GetCallback(controller);
|
GetCallback(controller);
|
||||||
bool shouldIntercept = false;
|
bool shouldIntercept = false;
|
||||||
if (controller && !mForceNoIntercept) {
|
if (controller && !mForceNoIntercept) {
|
||||||
nsresult rv = controller->ShouldPrepareForIntercept(mURI, &shouldIntercept);
|
nsresult rv = controller->ShouldPrepareForIntercept(mURI,
|
||||||
|
IsNavigation(),
|
||||||
|
&shouldIntercept);
|
||||||
NS_ENSURE_SUCCESS(rv, false);
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
}
|
}
|
||||||
return shouldIntercept;
|
return shouldIntercept;
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ public:
|
|||||||
const NetAddr& GetPeerAddr() { return mPeerAddr; }
|
const NetAddr& GetPeerAddr() { return mPeerAddr; }
|
||||||
|
|
||||||
public: /* Necko internal use only... */
|
public: /* Necko internal use only... */
|
||||||
|
bool IsNavigation();
|
||||||
|
|
||||||
// Return whether upon a redirect code of httpStatus for method, the
|
// Return whether upon a redirect code of httpStatus for method, the
|
||||||
// request method should be rewritten to GET.
|
// request method should be rewritten to GET.
|
||||||
|
|||||||
@@ -56,6 +56,111 @@ static_assert(FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE == 250,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A stream listener interposed between the nsInputStreamPump used for intercepted channels
|
||||||
|
// and this channel's original listener. This is only used to ensure the original listener
|
||||||
|
// sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
|
||||||
|
class InterceptStreamListener : public nsIStreamListener
|
||||||
|
, public nsIProgressEventSink
|
||||||
|
{
|
||||||
|
nsRefPtr<HttpChannelChild> mOwner;
|
||||||
|
nsCOMPtr<nsISupports> mContext;
|
||||||
|
virtual ~InterceptStreamListener() {}
|
||||||
|
public:
|
||||||
|
InterceptStreamListener(HttpChannelChild* aOwner, nsISupports* aContext)
|
||||||
|
: mOwner(aOwner)
|
||||||
|
, mContext(aContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_DECL_ISUPPORTS
|
||||||
|
NS_DECL_NSIREQUESTOBSERVER
|
||||||
|
NS_DECL_NSISTREAMLISTENER
|
||||||
|
NS_DECL_NSIPROGRESSEVENTSINK
|
||||||
|
|
||||||
|
void Cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS(InterceptStreamListener,
|
||||||
|
nsIStreamListener,
|
||||||
|
nsIRequestObserver,
|
||||||
|
nsIProgressEventSink)
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||||
|
{
|
||||||
|
if (mOwner) {
|
||||||
|
mOwner->DoOnStartRequest(mOwner, mContext);
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptStreamListener::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
|
||||||
|
nsresult status, const char16_t* aStatusArg)
|
||||||
|
{
|
||||||
|
if (mOwner) {
|
||||||
|
mOwner->DoOnStatus(mOwner, status);
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptStreamListener::OnProgress(nsIRequest* aRequest, nsISupports* aContext,
|
||||||
|
int64_t aProgress, int64_t aProgressMax)
|
||||||
|
{
|
||||||
|
if (mOwner) {
|
||||||
|
mOwner->DoOnProgress(mOwner, aProgress, aProgressMax);
|
||||||
|
}
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
||||||
|
nsIInputStream* aInputStream, uint64_t aOffset,
|
||||||
|
uint32_t aCount)
|
||||||
|
{
|
||||||
|
if (!mOwner) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t loadFlags;
|
||||||
|
mOwner->GetLoadFlags(&loadFlags);
|
||||||
|
|
||||||
|
if (!(loadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
mOwner->GetURI(getter_AddRefs(uri));
|
||||||
|
|
||||||
|
nsAutoCString host;
|
||||||
|
uri->GetHost(host);
|
||||||
|
|
||||||
|
OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
|
||||||
|
|
||||||
|
int64_t progress = aOffset + aCount;
|
||||||
|
OnProgress(mOwner, aContext, progress, mOwner->GetResponseHead()->ContentLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
|
||||||
|
{
|
||||||
|
if (mOwner) {
|
||||||
|
mOwner->DoPreOnStopRequest(aStatusCode);
|
||||||
|
mOwner->DoOnStopRequest(mOwner, mContext);
|
||||||
|
}
|
||||||
|
Cleanup();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InterceptStreamListener::Cleanup()
|
||||||
|
{
|
||||||
|
mOwner = nullptr;
|
||||||
|
mContext = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// HttpChannelChild
|
// HttpChannelChild
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -852,6 +957,10 @@ HttpChannelChild::DoNotifyListenerCleanup()
|
|||||||
LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
|
LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
|
||||||
if (mIPCOpen)
|
if (mIPCOpen)
|
||||||
PHttpChannelChild::Send__delete__(this);
|
PHttpChannelChild::Send__delete__(this);
|
||||||
|
if (mInterceptListener) {
|
||||||
|
mInterceptListener->Cleanup();
|
||||||
|
mInterceptListener = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteSelfEvent : public ChannelEvent
|
class DeleteSelfEvent : public ChannelEvent
|
||||||
@@ -1237,6 +1346,10 @@ HttpChannelChild::Cancel(nsresult status)
|
|||||||
mStatus = status;
|
mStatus = status;
|
||||||
if (RemoteChannelExists())
|
if (RemoteChannelExists())
|
||||||
SendCancel(status);
|
SendCancel(status);
|
||||||
|
if (mSynthesizedResponsePump) {
|
||||||
|
mSynthesizedResponsePump->Cancel(status);
|
||||||
|
}
|
||||||
|
mInterceptListener = nullptr;
|
||||||
}
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
@@ -1343,89 +1456,6 @@ HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A stream listener interposed between the nsInputStreamPump used for intercepted channels
|
|
||||||
// and this channel's original listener. This is only used to ensure the original listener
|
|
||||||
// sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
|
|
||||||
class InterceptStreamListener : public nsIStreamListener
|
|
||||||
, public nsIProgressEventSink
|
|
||||||
{
|
|
||||||
nsRefPtr<HttpChannelChild> mOwner;
|
|
||||||
nsCOMPtr<nsISupports> mContext;
|
|
||||||
virtual ~InterceptStreamListener() {}
|
|
||||||
public:
|
|
||||||
InterceptStreamListener(HttpChannelChild* aOwner, nsISupports* aContext)
|
|
||||||
: mOwner(aOwner)
|
|
||||||
, mContext(aContext)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_DECL_ISUPPORTS
|
|
||||||
NS_DECL_NSIREQUESTOBSERVER
|
|
||||||
NS_DECL_NSISTREAMLISTENER
|
|
||||||
NS_DECL_NSIPROGRESSEVENTSINK
|
|
||||||
};
|
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(InterceptStreamListener,
|
|
||||||
nsIStreamListener,
|
|
||||||
nsIRequestObserver,
|
|
||||||
nsIProgressEventSink)
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
|
||||||
{
|
|
||||||
mOwner->DoOnStartRequest(mOwner, mContext);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
InterceptStreamListener::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
|
|
||||||
nsresult status, const char16_t* aStatusArg)
|
|
||||||
{
|
|
||||||
mOwner->DoOnStatus(mOwner, status);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
InterceptStreamListener::OnProgress(nsIRequest* aRequest, nsISupports* aContext,
|
|
||||||
int64_t aProgress, int64_t aProgressMax)
|
|
||||||
{
|
|
||||||
mOwner->DoOnProgress(mOwner, aProgress, aProgressMax);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
InterceptStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
|
||||||
nsIInputStream* aInputStream, uint64_t aOffset,
|
|
||||||
uint32_t aCount)
|
|
||||||
{
|
|
||||||
uint32_t loadFlags;
|
|
||||||
mOwner->GetLoadFlags(&loadFlags);
|
|
||||||
|
|
||||||
if (!(loadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
|
|
||||||
nsCOMPtr<nsIURI> uri;
|
|
||||||
mOwner->GetURI(getter_AddRefs(uri));
|
|
||||||
|
|
||||||
nsAutoCString host;
|
|
||||||
uri->GetHost(host);
|
|
||||||
|
|
||||||
OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
|
|
||||||
|
|
||||||
int64_t progress = aOffset + aCount;
|
|
||||||
OnProgress(mOwner, aContext, progress, mOwner->GetResponseHead()->ContentLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
|
||||||
InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
|
|
||||||
{
|
|
||||||
mOwner->DoPreOnStopRequest(aStatusCode);
|
|
||||||
mOwner->DoOnStopRequest(mOwner, mContext);
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
|
HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
|
||||||
{
|
{
|
||||||
@@ -2043,6 +2073,7 @@ HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
|
|||||||
void
|
void
|
||||||
HttpChannelChild::ResetInterception()
|
HttpChannelChild::ResetInterception()
|
||||||
{
|
{
|
||||||
|
mInterceptListener->Cleanup();
|
||||||
mInterceptListener = nullptr;
|
mInterceptListener = nullptr;
|
||||||
|
|
||||||
// Continue with the original cross-process request
|
// Continue with the original cross-process request
|
||||||
@@ -2051,7 +2082,7 @@ HttpChannelChild::ResetInterception()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
HttpChannelChild::OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead,
|
HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
|
||||||
nsInputStreamPump* aPump)
|
nsInputStreamPump* aPump)
|
||||||
{
|
{
|
||||||
mSynthesizedResponsePump = aPump;
|
mSynthesizedResponsePump = aPump;
|
||||||
@@ -2063,6 +2094,10 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseH
|
|||||||
nsresult rv = mSynthesizedResponsePump->Suspend();
|
nsresult rv = mSynthesizedResponsePump->Suspend();
|
||||||
NS_ENSURE_SUCCESS_VOID(rv);
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mCanceled) {
|
||||||
|
mSynthesizedResponsePump->Cancel(mStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}} // mozilla::net
|
}} // mozilla::net
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ private:
|
|||||||
|
|
||||||
// Override this channel's pending response with a synthesized one. The content will be
|
// Override this channel's pending response with a synthesized one. The content will be
|
||||||
// asynchronously read from the pump.
|
// asynchronously read from the pump.
|
||||||
void OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead, nsInputStreamPump* aPump);
|
void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead, nsInputStreamPump* aPump);
|
||||||
|
|
||||||
RequestHeaderTuples mClientSetRequestHeaders;
|
RequestHeaderTuples mClientSetRequestHeaders;
|
||||||
nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
|
nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ DoAddCacheEntryHeaders(nsHttpChannel *self,
|
|||||||
|
|
||||||
NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
|
NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
|
||||||
|
|
||||||
InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
|
InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController,
|
||||||
|
bool aIsNavigation)
|
||||||
: mController(aController)
|
: mController(aController)
|
||||||
|
, mIsNavigation(aIsNavigation)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,21 +37,36 @@ InterceptedChannelBase::~InterceptedChannelBase()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelBase::GetResponseBody(nsIOutputStream** aStream)
|
||||||
|
{
|
||||||
|
NS_IF_ADDREF(*aStream = mResponseBody);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InterceptedChannelBase::EnsureSynthesizedResponse()
|
InterceptedChannelBase::EnsureSynthesizedResponse()
|
||||||
{
|
{
|
||||||
if (mSynthesizedResponseHead.isNothing()) {
|
if (mSynthesizedResponseHead.isNothing()) {
|
||||||
mSynthesizedResponseHead.emplace();
|
mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InterceptedChannelBase::DoNotifyController(nsIOutputStream* aOut)
|
InterceptedChannelBase::DoNotifyController()
|
||||||
{
|
{
|
||||||
nsresult rv = mController->ChannelIntercepted(this, aOut);
|
nsresult rv = mController->ChannelIntercepted(this);
|
||||||
|
mController = nullptr;
|
||||||
NS_ENSURE_SUCCESS_VOID(rv);
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelBase::GetIsNavigation(bool* aIsNavigation)
|
||||||
|
{
|
||||||
|
*aIsNavigation = mIsNavigation;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
|
InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
|
||||||
{
|
{
|
||||||
@@ -57,7 +74,7 @@ InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACSt
|
|||||||
|
|
||||||
nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
|
nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
|
||||||
// Overwrite any existing header.
|
// Overwrite any existing header.
|
||||||
nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header.get());
|
nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header.get());
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
@@ -65,7 +82,7 @@ InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACSt
|
|||||||
InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
|
InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
|
||||||
nsINetworkInterceptController* aController,
|
nsINetworkInterceptController* aController,
|
||||||
nsICacheEntry* aEntry)
|
nsICacheEntry* aEntry)
|
||||||
: InterceptedChannelBase(aController)
|
: InterceptedChannelBase(aController, aChannel->IsNavigation())
|
||||||
, mChannel(aChannel)
|
, mChannel(aChannel)
|
||||||
, mSynthesizedCacheEntry(aEntry)
|
, mSynthesizedCacheEntry(aEntry)
|
||||||
{
|
{
|
||||||
@@ -76,10 +93,17 @@ InterceptedChannelChrome::NotifyController()
|
|||||||
{
|
{
|
||||||
nsCOMPtr<nsIOutputStream> out;
|
nsCOMPtr<nsIOutputStream> out;
|
||||||
|
|
||||||
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
|
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
|
||||||
NS_ENSURE_SUCCESS_VOID(rv);
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
|
|
||||||
DoNotifyController(out);
|
DoNotifyController();
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
|
||||||
|
{
|
||||||
|
NS_IF_ADDREF(*aChannel = mChannel);
|
||||||
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
@@ -132,7 +156,7 @@ InterceptedChannelChrome::FinishSynthesizedResponse()
|
|||||||
|
|
||||||
rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
|
rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
|
||||||
mChannel->GetRequestHead(),
|
mChannel->GetRequestHead(),
|
||||||
mSynthesizedResponseHead.ptr(), securityInfo);
|
mSynthesizedResponseHead.ref(), securityInfo);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
nsCOMPtr<nsIURI> uri;
|
nsCOMPtr<nsIURI> uri;
|
||||||
@@ -156,10 +180,24 @@ InterceptedChannelChrome::FinishSynthesizedResponse()
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelChrome::Cancel()
|
||||||
|
{
|
||||||
|
if (!mChannel) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to use AsyncAbort instead of Cancel since there's no active pump
|
||||||
|
// to cancel which will provide OnStart/OnStopRequest to the channel.
|
||||||
|
nsresult rv = mChannel->AsyncAbort(NS_BINDING_ABORTED);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
|
InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
|
||||||
nsINetworkInterceptController* aController,
|
nsINetworkInterceptController* aController,
|
||||||
nsIStreamListener* aListener)
|
nsIStreamListener* aListener)
|
||||||
: InterceptedChannelBase(aController)
|
: InterceptedChannelBase(aController, aChannel->IsNavigation())
|
||||||
, mChannel(aChannel)
|
, mChannel(aChannel)
|
||||||
, mStreamListener(aListener)
|
, mStreamListener(aListener)
|
||||||
{
|
{
|
||||||
@@ -169,11 +207,18 @@ void
|
|||||||
InterceptedChannelContent::NotifyController()
|
InterceptedChannelContent::NotifyController()
|
||||||
{
|
{
|
||||||
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
|
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
|
||||||
getter_AddRefs(mSynthesizedOutput),
|
getter_AddRefs(mResponseBody),
|
||||||
0, UINT32_MAX, true, true);
|
0, UINT32_MAX, true, true);
|
||||||
NS_ENSURE_SUCCESS_VOID(rv);
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
|
|
||||||
DoNotifyController(mSynthesizedOutput);
|
DoNotifyController();
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelContent::GetChannel(nsIChannel** aChannel)
|
||||||
|
{
|
||||||
|
NS_IF_ADDREF(*aChannel = mChannel);
|
||||||
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
@@ -183,7 +228,7 @@ InterceptedChannelContent::ResetInterception()
|
|||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSynthesizedOutput = nullptr;
|
mResponseBody = nullptr;
|
||||||
mSynthesizedInput = nullptr;
|
mSynthesizedInput = nullptr;
|
||||||
|
|
||||||
mChannel->ResetInterception();
|
mChannel->ResetInterception();
|
||||||
@@ -194,7 +239,7 @@ InterceptedChannelContent::ResetInterception()
|
|||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
|
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
|
||||||
{
|
{
|
||||||
if (!mSynthesizedOutput) {
|
if (!mResponseBody) {
|
||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +249,7 @@ InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACS
|
|||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
InterceptedChannelContent::FinishSynthesizedResponse()
|
InterceptedChannelContent::FinishSynthesizedResponse()
|
||||||
{
|
{
|
||||||
if (!mChannel) {
|
if (NS_WARN_IF(!mChannel)) {
|
||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,19 +257,36 @@ InterceptedChannelContent::FinishSynthesizedResponse()
|
|||||||
|
|
||||||
nsresult rv = nsInputStreamPump::Create(getter_AddRefs(mStoragePump), mSynthesizedInput,
|
nsresult rv = nsInputStreamPump::Create(getter_AddRefs(mStoragePump), mSynthesizedInput,
|
||||||
int64_t(-1), int64_t(-1), 0, 0, true);
|
int64_t(-1), int64_t(-1), 0, 0, true);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
mSynthesizedInput->Close();
|
mSynthesizedInput->Close();
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSynthesizedOutput = nullptr;
|
mResponseBody = nullptr;
|
||||||
|
|
||||||
rv = mStoragePump->AsyncRead(mStreamListener, nullptr);
|
rv = mStoragePump->AsyncRead(mStreamListener, nullptr);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ptr(), mStoragePump);
|
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(), mStoragePump);
|
||||||
|
|
||||||
mChannel = nullptr;
|
mChannel = nullptr;
|
||||||
|
mStreamListener = nullptr;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
InterceptedChannelContent::Cancel()
|
||||||
|
{
|
||||||
|
if (!mChannel) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to use AsyncAbort instead of Cancel since there's no active pump
|
||||||
|
// to cancel which will provide OnStart/OnStopRequest to the channel.
|
||||||
|
nsresult rv = mChannel->AsyncAbort(NS_BINDING_ABORTED);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
mChannel = nullptr;
|
||||||
|
mStreamListener = nullptr;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,22 +30,32 @@ protected:
|
|||||||
// The interception controller to notify about the successful channel interception
|
// The interception controller to notify about the successful channel interception
|
||||||
nsCOMPtr<nsINetworkInterceptController> mController;
|
nsCOMPtr<nsINetworkInterceptController> mController;
|
||||||
|
|
||||||
|
// The stream to write the body of the synthesized response
|
||||||
|
nsCOMPtr<nsIOutputStream> mResponseBody;
|
||||||
|
|
||||||
// Response head for use when synthesizing
|
// Response head for use when synthesizing
|
||||||
Maybe<nsHttpResponseHead> mSynthesizedResponseHead;
|
Maybe<nsAutoPtr<nsHttpResponseHead>> mSynthesizedResponseHead;
|
||||||
|
|
||||||
|
// Whether this intercepted channel was performing a navigation.
|
||||||
|
bool mIsNavigation;
|
||||||
|
|
||||||
void EnsureSynthesizedResponse();
|
void EnsureSynthesizedResponse();
|
||||||
void DoNotifyController(nsIOutputStream* aOut);
|
void DoNotifyController();
|
||||||
nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
|
nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
|
||||||
|
|
||||||
virtual ~InterceptedChannelBase();
|
virtual ~InterceptedChannelBase();
|
||||||
public:
|
public:
|
||||||
explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
|
InterceptedChannelBase(nsINetworkInterceptController* aController,
|
||||||
|
bool aIsNavigation);
|
||||||
|
|
||||||
// Notify the interception controller that the channel has been intercepted
|
// Notify the interception controller that the channel has been intercepted
|
||||||
// and prepare the response body output stream.
|
// and prepare the response body output stream.
|
||||||
virtual void NotifyController() = 0;
|
virtual void NotifyController() = 0;
|
||||||
|
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
|
|
||||||
|
NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD GetIsNavigation(bool* aIsNavigation) MOZ_OVERRIDE;
|
||||||
};
|
};
|
||||||
|
|
||||||
class InterceptedChannelChrome : public InterceptedChannelBase
|
class InterceptedChannelChrome : public InterceptedChannelBase
|
||||||
@@ -60,7 +70,11 @@ public:
|
|||||||
nsINetworkInterceptController* aController,
|
nsINetworkInterceptController* aController,
|
||||||
nsICacheEntry* aEntry);
|
nsICacheEntry* aEntry);
|
||||||
|
|
||||||
NS_DECL_NSIINTERCEPTEDCHANNEL
|
NS_IMETHOD ResetInterception() MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD FinishSynthesizedResponse() MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD GetChannel(nsIChannel** aChannel) MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD Cancel() MOZ_OVERRIDE;
|
||||||
|
|
||||||
virtual void NotifyController() MOZ_OVERRIDE;
|
virtual void NotifyController() MOZ_OVERRIDE;
|
||||||
};
|
};
|
||||||
@@ -70,8 +84,7 @@ class InterceptedChannelContent : public InterceptedChannelBase
|
|||||||
// The actual channel being intercepted.
|
// The actual channel being intercepted.
|
||||||
nsRefPtr<HttpChannelChild> mChannel;
|
nsRefPtr<HttpChannelChild> mChannel;
|
||||||
|
|
||||||
// Writeable buffer for use when synthesizing a response in a child process
|
// Reader-side of the response body when synthesizing in a child proces
|
||||||
nsCOMPtr<nsIOutputStream> mSynthesizedOutput;
|
|
||||||
nsCOMPtr<nsIInputStream> mSynthesizedInput;
|
nsCOMPtr<nsIInputStream> mSynthesizedInput;
|
||||||
|
|
||||||
// Pump to read the synthesized body in child processes
|
// Pump to read the synthesized body in child processes
|
||||||
@@ -85,7 +98,11 @@ public:
|
|||||||
nsINetworkInterceptController* aController,
|
nsINetworkInterceptController* aController,
|
||||||
nsIStreamListener* aListener);
|
nsIStreamListener* aListener);
|
||||||
|
|
||||||
NS_DECL_NSIINTERCEPTEDCHANNEL
|
NS_IMETHOD ResetInterception() MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD FinishSynthesizedResponse() MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD GetChannel(nsIChannel** aChannel) MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) MOZ_OVERRIDE;
|
||||||
|
NS_IMETHOD Cancel() MOZ_OVERRIDE;
|
||||||
|
|
||||||
virtual void NotifyController() MOZ_OVERRIDE;
|
virtual void NotifyController() MOZ_OVERRIDE;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,19 +55,19 @@ function make_channel(url, body, cb) {
|
|||||||
this.numChecks++;
|
this.numChecks++;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
channelIntercepted: function(channel, stream) {
|
channelIntercepted: function(channel) {
|
||||||
channel.QueryInterface(Ci.nsIInterceptedChannel);
|
channel.QueryInterface(Ci.nsIInterceptedChannel);
|
||||||
if (body) {
|
if (body) {
|
||||||
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||||
.createInstance(Ci.nsIStringInputStream);
|
.createInstance(Ci.nsIStringInputStream);
|
||||||
synthesized.data = body;
|
synthesized.data = body;
|
||||||
|
|
||||||
NetUtil.asyncCopy(synthesized, stream, function() {
|
NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
|
||||||
channel.finishSynthesizedResponse();
|
channel.finishSynthesizedResponse();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(channel, stream);
|
cb(channel);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -143,12 +143,12 @@ add_test(function() {
|
|||||||
|
|
||||||
// ensure that the channel waits for a decision and synthesizes headers correctly
|
// ensure that the channel waits for a decision and synthesizes headers correctly
|
||||||
add_test(function() {
|
add_test(function() {
|
||||||
var chan = make_channel(URL + '/body', null, function(channel, stream) {
|
var chan = make_channel(URL + '/body', null, function(channel) {
|
||||||
do_timeout(100, function() {
|
do_timeout(100, function() {
|
||||||
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||||
.createInstance(Ci.nsIStringInputStream);
|
.createInstance(Ci.nsIStringInputStream);
|
||||||
synthesized.data = NON_REMOTE_BODY;
|
synthesized.data = NON_REMOTE_BODY;
|
||||||
NetUtil.asyncCopy(synthesized, stream, function() {
|
NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
|
||||||
channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length);
|
channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length);
|
||||||
channel.finishSynthesizedResponse();
|
channel.finishSynthesizedResponse();
|
||||||
});
|
});
|
||||||
@@ -169,12 +169,12 @@ add_test(function() {
|
|||||||
|
|
||||||
// ensure that the intercepted channel supports suspend/resume
|
// ensure that the intercepted channel supports suspend/resume
|
||||||
add_test(function() {
|
add_test(function() {
|
||||||
var chan = make_channel(URL + '/body', null, function(intercepted, stream) {
|
var chan = make_channel(URL + '/body', null, function(intercepted) {
|
||||||
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||||
.createInstance(Ci.nsIStringInputStream);
|
.createInstance(Ci.nsIStringInputStream);
|
||||||
synthesized.data = NON_REMOTE_BODY;
|
synthesized.data = NON_REMOTE_BODY;
|
||||||
|
|
||||||
NetUtil.asyncCopy(synthesized, stream, function() {
|
NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
|
||||||
// set the content-type to ensure that the stream converter doesn't hold up notifications
|
// set the content-type to ensure that the stream converter doesn't hold up notifications
|
||||||
// and cause the test to fail
|
// and cause the test to fail
|
||||||
intercepted.synthesizeHeader("Content-Type", "text/plain");
|
intercepted.synthesizeHeader("Content-Type", "text/plain");
|
||||||
@@ -185,6 +185,65 @@ add_test(function() {
|
|||||||
CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY), null);
|
CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ensure that the intercepted channel can be cancelled
|
||||||
|
add_test(function() {
|
||||||
|
var chan = make_channel(URL + '/body', null, function(intercepted) {
|
||||||
|
intercepted.cancel();
|
||||||
|
});
|
||||||
|
chan.asyncOpen(new ChannelListener(run_next_test, null,
|
||||||
|
CL_EXPECT_FAILURE), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ensure that the channel can't be cancelled via nsIInterceptedChannel after making a decision
|
||||||
|
add_test(function() {
|
||||||
|
var chan = make_channel(URL + '/body', null, function(chan) {
|
||||||
|
chan.resetInterception();
|
||||||
|
do_timeout(0, function() {
|
||||||
|
var gotexception = false;
|
||||||
|
try {
|
||||||
|
chan.cancel();
|
||||||
|
} catch (x) {
|
||||||
|
gotexception = true;
|
||||||
|
}
|
||||||
|
do_check_true(gotexception);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chan.asyncOpen(new ChannelListener(handle_remote_response, null), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ensure that the intercepted channel can be canceled during the response
|
||||||
|
add_test(function() {
|
||||||
|
var chan = make_channel(URL + '/body', null, function(intercepted) {
|
||||||
|
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||||
|
.createInstance(Ci.nsIStringInputStream);
|
||||||
|
synthesized.data = NON_REMOTE_BODY;
|
||||||
|
|
||||||
|
NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
|
||||||
|
let channel = intercepted.channel;
|
||||||
|
intercepted.finishSynthesizedResponse();
|
||||||
|
channel.cancel(Cr.NS_BINDING_ABORTED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chan.asyncOpen(new ChannelListener(run_next_test, null,
|
||||||
|
CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ensure that the intercepted channel can be canceled before the response
|
||||||
|
add_test(function() {
|
||||||
|
var chan = make_channel(URL + '/body', null, function(intercepted) {
|
||||||
|
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||||
|
.createInstance(Ci.nsIStringInputStream);
|
||||||
|
synthesized.data = NON_REMOTE_BODY;
|
||||||
|
|
||||||
|
NetUtil.asyncCopy(synthesized, intercepted.responseBody, function() {
|
||||||
|
intercepted.channel.cancel(Cr.NS_BINDING_ABORTED);
|
||||||
|
intercepted.finishSynthesizedResponse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chan.asyncOpen(new ChannelListener(run_next_test, null,
|
||||||
|
CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL), null);
|
||||||
|
});
|
||||||
|
|
||||||
add_test(function() {
|
add_test(function() {
|
||||||
httpServer.stop(run_next_test);
|
httpServer.stop(run_next_test);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user