Bug 1065216 - Dispatch a fetch event to workers when controlled pages initiate a network load. r=baku,mayhemer,smaug

This commit is contained in:
Josh Matthews
2015-02-18 14:10:52 -05:00
parent 4ae1c1fcea
commit 6857251ced
27 changed files with 1450 additions and 148 deletions

View File

@@ -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)
{ {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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',
}, },

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View 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;
};

View File

@@ -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',

View File

@@ -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)
{ {

View File

@@ -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
{ {

View File

@@ -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;
} }

View File

@@ -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,

View 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"]]);

View 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

View 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>

View 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);
}
}

View File

@@ -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]

View 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>

View File

@@ -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);
}; };

View File

@@ -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;

View File

@@ -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.

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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;
}; };

View File

@@ -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);
}); });