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
Родитель 7f6b520731
Коммит cb61a019fc
27 изменённых файлов: 1450 добавлений и 148 удалений

Просмотреть файл

@ -52,6 +52,7 @@
#include "nsIAuthPrompt2.h"
#include "nsIChannelEventSink.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIServiceWorkerManager.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScrollableFrame.h"
@ -1041,6 +1042,7 @@ NS_INTERFACE_MAP_BEGIN(nsDocShell)
NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)
///*****************************************************************************
@ -13921,6 +13923,52 @@ nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
#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
nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
{

Просмотреть файл

@ -12,6 +12,7 @@
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIBaseWindow.h"
#include "nsINetworkInterceptController.h"
#include "nsIScrollable.h"
#include "nsITextScroll.h"
#include "nsIContentViewerContainer.h"
@ -152,6 +153,7 @@ class nsDocShell MOZ_FINAL
, public nsILinkHandler
, public nsIClipboardCommands
, public nsIDOMStorageManager
, public nsINetworkInterceptController
, public mozilla::SupportsWeakPtr<nsDocShell>
{
friend class nsDSURIContentListener;
@ -182,6 +184,7 @@ public:
NS_DECL_NSIAUTHPROMPTPROVIDER
NS_DECL_NSICLIPBOARDCOMMANDS
NS_DECL_NSIWEBSHELLSERVICES
NS_DECL_NSINETWORKINTERCEPTCONTROLLER
NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager())
NS_IMETHOD Stop() MOZ_OVERRIDE {

Просмотреть файл

@ -622,7 +622,7 @@ protected:
DECL_SHIM(nsIApplicationCacheContainer, NSIAPPLICATIONCACHECONTAINER)
#undef DECL_SHIM
};
/**
* 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

Просмотреть файл

@ -418,6 +418,14 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::workers::ExtendableEvent',
},
'FetchEvent': {
'headerFile': 'ServiceWorkerEvents.h',
'nativeType': 'mozilla::dom::workers::FetchEvent',
'binaryNames': {
'request': 'request_'
},
},
'FileList': {
'headerFile': 'mozilla/dom/File.h',
},

Просмотреть файл

@ -866,6 +866,11 @@ FetchBody<Request>::FetchBody();
template
FetchBody<Response>::FetchBody();
template <class Derived>
FetchBody<Derived>::~FetchBody()
{
}
// Returns true if addref succeeded.
// Always succeeds on main thread.
// May fail on worker if RegisterFeature() fails. In that case, it will release

Просмотреть файл

@ -135,6 +135,12 @@ public:
void
CancelPump();
void
SetBodyUsed()
{
mBodyUsed = true;
}
// Always set whenever the FetchBody is created on the worker thread.
workers::WorkerPrivate* mWorkerPrivate;
@ -145,15 +151,7 @@ public:
protected:
FetchBody();
virtual ~FetchBody()
{
}
void
SetBodyUsed()
{
mBodyUsed = true;
}
virtual ~FetchBody();
void
SetMimeType(ErrorResult& aRv);

Просмотреть файл

@ -6,6 +6,7 @@
#include "domstubs.idl"
interface nsIDocument;
interface nsIInterceptedChannel;
interface nsIPrincipal;
interface nsIURI;
@ -18,7 +19,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports
[noscript] void UnregisterFailed();
};
[builtinclass, uuid(861b55e9-d6ac-47cf-a528-8590e9b44de6)]
[builtinclass, uuid(464882c8-81c0-4620-b9c4-44c12085b65b)]
interface nsIServiceWorkerManager : nsISupports
{
/**
@ -51,6 +52,15 @@ interface nsIServiceWorkerManager : nsISupports
// Remove ready pending Promise
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.
[noscript] void AddRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
[noscript] void RemoveRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);

Просмотреть файл

@ -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',
'ExtendableEvent.webidl',
'Fetch.webidl',
'FetchEvent.webidl',
'File.webidl',
'FileList.webidl',
'FileMode.webidl',

Просмотреть файл

@ -5,9 +5,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerEvents.h"
#include "ServiceWorkerClient.h"
#include "nsINetworkInterceptController.h"
#include "nsIOutputStream.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/workers/bindings/ServiceWorker.h"
@ -15,6 +26,271 @@ using namespace mozilla::dom;
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)
: Event(aOwner, nullptr, nullptr)
{

Просмотреть файл

@ -8,12 +8,87 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/ExtendableEventBinding.h"
#include "mozilla/dom/FetchEventBinding.h"
#include "mozilla/dom/InstallEventBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsProxyRelease.h"
class nsIInterceptedChannel;
namespace mozilla {
namespace dom {
class Request;
} // namespace dom
} // namespace mozilla
BEGIN_WORKERS_NAMESPACE
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
{

Просмотреть файл

@ -11,7 +11,10 @@
#include "nsIStreamLoader.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsINetworkInterceptController.h"
#include "nsPIDOMWindow.h"
#include "nsDebug.h"
#include "jsapi.h"
@ -19,9 +22,13 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/InstallEventBinding.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
@ -2121,6 +2128,267 @@ ServiceWorkerManager::GetServiceWorkerForScope(nsIDOMWindow* aWindow,
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 document was loaded.
@ -2137,21 +2405,16 @@ ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow, nsISupports**
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
nsRefPtr<ServiceWorkerRegistrationInfo> registration;
if (!mControlledDocuments.Get(doc, getter_AddRefs(registration))) {
return NS_ERROR_FAILURE;
nsresult rv = GetDocumentRegistration(doc, getter_AddRefs(registration));
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;
nsresult rv = CreateServiceWorkerForWindow(window,
registration->mActiveWorker->ScriptSpec(),
registration->mScope,
getter_AddRefs(serviceWorker));
rv = CreateServiceWorkerForWindow(window,
registration->mActiveWorker->ScriptSpec(),
registration->mScope,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

Просмотреть файл

@ -399,6 +399,9 @@ private:
nsresult
Update(ServiceWorkerRegistrationInfo* aRegistration);
nsresult
GetDocumentRegistration(nsIDocument* aDoc, ServiceWorkerRegistrationInfo** aRegistrationInfo);
NS_IMETHOD
CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
const nsACString& aScriptSpec,

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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
worker2.js
worker3.js
fetch_event_worker.js
parse_error_worker.js
activate_event_error_worker.js
install_event_worker.js
@ -20,10 +21,14 @@ support-files =
worker_unregister.js
worker_update.js
message_posting_worker.js
fetch/index.html
fetch/fetch_worker_script.js
fetch/fetch_tests.js
[test_unregister.html]
skip-if = true # Bug 1133805
[test_installation_simple.html]
[test_fetch_event.html]
[test_get_serviced.html]
[test_install_event.html]
[test_navigator.html]

Просмотреть файл

@ -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"
interface nsIHttpChannelInternal;
interface nsIChannel;
interface nsIOutputStream;
interface nsIURI;
@ -16,7 +16,7 @@ interface nsIURI;
* which do not implement nsIChannel.
*/
[scriptable, uuid(0b5f82a7-5824-4a0d-bf5c-8a8a7684c0c8)]
[scriptable, uuid(9d127b63-dfad-484d-a0e1-cb82697a095b)]
interface nsIInterceptedChannel : nsISupports
{
/**
@ -37,6 +37,28 @@ interface nsIInterceptedChannel : nsISupports
* after this point.
*/
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.
*/
[scriptable, uuid(b3ad3e9b-91d8-44d0-a0c5-dc2e9374f599)]
[scriptable, uuid(69150b77-b561-43a2-bfba-7301dd5a35d0)]
interface nsINetworkInterceptController : nsISupports
{
/**
@ -53,15 +75,15 @@ interface nsINetworkInterceptController : nsISupports
* requests until specifically instructed to do so.
*
* @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
* response via the provided stream.
*
* @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;
}
bool
HttpBaseChannel::IsNavigation()
{
return mLoadFlags & LOAD_DOCUMENT_URI;
}
bool
HttpBaseChannel::ShouldIntercept()
{
@ -1940,7 +1946,9 @@ HttpBaseChannel::ShouldIntercept()
GetCallback(controller);
bool shouldIntercept = false;
if (controller && !mForceNoIntercept) {
nsresult rv = controller->ShouldPrepareForIntercept(mURI, &shouldIntercept);
nsresult rv = controller->ShouldPrepareForIntercept(mURI,
IsNavigation(),
&shouldIntercept);
NS_ENSURE_SUCCESS(rv, false);
}
return shouldIntercept;

Просмотреть файл

@ -241,7 +241,7 @@ public:
const NetAddr& GetPeerAddr() { return mPeerAddr; }
public: /* Necko internal use only... */
bool IsNavigation();
// Return whether upon a redirect code of httpStatus for method, the
// 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
//-----------------------------------------------------------------------------
@ -852,6 +957,10 @@ HttpChannelChild::DoNotifyListenerCleanup()
LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
if (mIPCOpen)
PHttpChannelChild::Send__delete__(this);
if (mInterceptListener) {
mInterceptListener->Cleanup();
mInterceptListener = nullptr;
}
}
class DeleteSelfEvent : public ChannelEvent
@ -1237,6 +1346,10 @@ HttpChannelChild::Cancel(nsresult status)
mStatus = status;
if (RemoteChannelExists())
SendCancel(status);
if (mSynthesizedResponsePump) {
mSynthesizedResponsePump->Cancel(status);
}
mInterceptListener = nullptr;
}
return NS_OK;
}
@ -1343,89 +1456,6 @@ HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
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
HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
{
@ -2043,6 +2073,7 @@ HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
void
HttpChannelChild::ResetInterception()
{
mInterceptListener->Cleanup();
mInterceptListener = nullptr;
// Continue with the original cross-process request
@ -2051,7 +2082,7 @@ HttpChannelChild::ResetInterception()
}
void
HttpChannelChild::OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead,
HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
nsInputStreamPump* aPump)
{
mSynthesizedResponsePump = aPump;
@ -2063,6 +2094,10 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseH
nsresult rv = mSynthesizedResponsePump->Suspend();
NS_ENSURE_SUCCESS_VOID(rv);
}
if (mCanceled) {
mSynthesizedResponsePump->Cancel(mStatus);
}
}
}} // mozilla::net

Просмотреть файл

@ -159,7 +159,7 @@ private:
// Override this channel's pending response with a synthesized one. The content will be
// asynchronously read from the pump.
void OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead, nsInputStreamPump* aPump);
void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead, nsInputStreamPump* aPump);
RequestHeaderTuples mClientSetRequestHeaders;
nsCOMPtr<nsIChildChannel> mRedirectChannelChild;

Просмотреть файл

@ -26,8 +26,10 @@ DoAddCacheEntryHeaders(nsHttpChannel *self,
NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController,
bool aIsNavigation)
: 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
InterceptedChannelBase::EnsureSynthesizedResponse()
{
if (mSynthesizedResponseHead.isNothing()) {
mSynthesizedResponseHead.emplace();
mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
}
}
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_IMETHODIMP
InterceptedChannelBase::GetIsNavigation(bool* aIsNavigation)
{
*aIsNavigation = mIsNavigation;
return NS_OK;
}
nsresult
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;
// Overwrite any existing header.
nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header.get());
nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header.get());
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -65,7 +82,7 @@ InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACSt
InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
nsINetworkInterceptController* aController,
nsICacheEntry* aEntry)
: InterceptedChannelBase(aController)
: InterceptedChannelBase(aController, aChannel->IsNavigation())
, mChannel(aChannel)
, mSynthesizedCacheEntry(aEntry)
{
@ -76,10 +93,17 @@ InterceptedChannelChrome::NotifyController()
{
nsCOMPtr<nsIOutputStream> out;
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
NS_ENSURE_SUCCESS_VOID(rv);
DoNotifyController(out);
DoNotifyController();
}
NS_IMETHODIMP
InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
{
NS_IF_ADDREF(*aChannel = mChannel);
return NS_OK;
}
NS_IMETHODIMP
@ -132,7 +156,7 @@ InterceptedChannelChrome::FinishSynthesizedResponse()
rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
mChannel->GetRequestHead(),
mSynthesizedResponseHead.ptr(), securityInfo);
mSynthesizedResponseHead.ref(), securityInfo);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
@ -156,10 +180,24 @@ InterceptedChannelChrome::FinishSynthesizedResponse()
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,
nsINetworkInterceptController* aController,
nsIStreamListener* aListener)
: InterceptedChannelBase(aController)
: InterceptedChannelBase(aController, aChannel->IsNavigation())
, mChannel(aChannel)
, mStreamListener(aListener)
{
@ -169,11 +207,18 @@ void
InterceptedChannelContent::NotifyController()
{
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
getter_AddRefs(mSynthesizedOutput),
getter_AddRefs(mResponseBody),
0, UINT32_MAX, true, true);
NS_ENSURE_SUCCESS_VOID(rv);
DoNotifyController(mSynthesizedOutput);
DoNotifyController();
}
NS_IMETHODIMP
InterceptedChannelContent::GetChannel(nsIChannel** aChannel)
{
NS_IF_ADDREF(*aChannel = mChannel);
return NS_OK;
}
NS_IMETHODIMP
@ -183,7 +228,7 @@ InterceptedChannelContent::ResetInterception()
return NS_ERROR_NOT_AVAILABLE;
}
mSynthesizedOutput = nullptr;
mResponseBody = nullptr;
mSynthesizedInput = nullptr;
mChannel->ResetInterception();
@ -194,7 +239,7 @@ InterceptedChannelContent::ResetInterception()
NS_IMETHODIMP
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
{
if (!mSynthesizedOutput) {
if (!mResponseBody) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -204,7 +249,7 @@ InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACS
NS_IMETHODIMP
InterceptedChannelContent::FinishSynthesizedResponse()
{
if (!mChannel) {
if (NS_WARN_IF(!mChannel)) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -212,19 +257,36 @@ InterceptedChannelContent::FinishSynthesizedResponse()
nsresult rv = nsInputStreamPump::Create(getter_AddRefs(mStoragePump), mSynthesizedInput,
int64_t(-1), int64_t(-1), 0, 0, true);
if (NS_FAILED(rv)) {
if (NS_WARN_IF(NS_FAILED(rv))) {
mSynthesizedInput->Close();
return rv;
}
mSynthesizedOutput = nullptr;
mResponseBody = nullptr;
rv = mStoragePump->AsyncRead(mStreamListener, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ptr(), mStoragePump);
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(), mStoragePump);
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;
}

Просмотреть файл

@ -30,22 +30,32 @@ protected:
// The interception controller to notify about the successful channel interception
nsCOMPtr<nsINetworkInterceptController> mController;
// The stream to write the body of the synthesized response
nsCOMPtr<nsIOutputStream> mResponseBody;
// 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 DoNotifyController(nsIOutputStream* aOut);
void DoNotifyController();
nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
virtual ~InterceptedChannelBase();
public:
explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
InterceptedChannelBase(nsINetworkInterceptController* aController,
bool aIsNavigation);
// Notify the interception controller that the channel has been intercepted
// and prepare the response body output stream.
virtual void NotifyController() = 0;
NS_DECL_ISUPPORTS
NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) MOZ_OVERRIDE;
NS_IMETHOD GetIsNavigation(bool* aIsNavigation) MOZ_OVERRIDE;
};
class InterceptedChannelChrome : public InterceptedChannelBase
@ -60,7 +70,11 @@ public:
nsINetworkInterceptController* aController,
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;
};
@ -70,8 +84,7 @@ class InterceptedChannelContent : public InterceptedChannelBase
// The actual channel being intercepted.
nsRefPtr<HttpChannelChild> mChannel;
// Writeable buffer for use when synthesizing a response in a child process
nsCOMPtr<nsIOutputStream> mSynthesizedOutput;
// Reader-side of the response body when synthesizing in a child proces
nsCOMPtr<nsIInputStream> mSynthesizedInput;
// Pump to read the synthesized body in child processes
@ -85,7 +98,11 @@ public:
nsINetworkInterceptController* aController,
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;
};

Просмотреть файл

@ -55,19 +55,19 @@ function make_channel(url, body, cb) {
this.numChecks++;
return true;
},
channelIntercepted: function(channel, stream) {
channelIntercepted: function(channel) {
channel.QueryInterface(Ci.nsIInterceptedChannel);
if (body) {
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
synthesized.data = body;
NetUtil.asyncCopy(synthesized, stream, function() {
NetUtil.asyncCopy(synthesized, channel.responseBody, function() {
channel.finishSynthesizedResponse();
});
}
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
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() {
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
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.finishSynthesizedResponse();
});
@ -169,12 +169,12 @@ add_test(function() {
// ensure that the intercepted channel supports suspend/resume
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"]
.createInstance(Ci.nsIStringInputStream);
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
// and cause the test to fail
intercepted.synthesizeHeader("Content-Type", "text/plain");
@ -185,6 +185,65 @@ add_test(function() {
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() {
httpServer.stop(run_next_test);
});