Implement abortController.

This commit is contained in:
Fedor 2020-07-16 03:27:14 +03:00
parent fc589eb50b
commit 764fb0f07f
30 changed files with 1381 additions and 43 deletions

View File

@ -0,0 +1,98 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AbortController.h"
#include "AbortSignal.h"
#include "mozilla/dom/AbortControllerBinding.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AbortController, mGlobal, mSignal)
NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortController)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortController)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortController)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/* static */ bool
AbortController::IsEnabled(JSContext* aCx, JSObject* aGlobal)
{
if (NS_IsMainThread()) {
return Preferences::GetBool("dom.abortController.enabled", false);
}
using namespace workers;
// Otherwise, check the pref via the WorkerPrivate
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (!workerPrivate) {
return false;
}
return workerPrivate->AbortControllerEnabled();
}
/* static */ already_AddRefed<AbortController>
AbortController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<AbortController> abortController = new AbortController(global);
return abortController.forget();
}
AbortController::AbortController(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal)
, mAborted(false)
{}
JSObject*
AbortController::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return AbortControllerBinding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject*
AbortController::GetParentObject() const
{
return mGlobal;
}
AbortSignal*
AbortController::Signal()
{
if (!mSignal) {
mSignal = new AbortSignal(this, mAborted);
}
return mSignal;
}
void
AbortController::Abort()
{
if (mAborted) {
return;
}
mAborted = true;
if (mSignal) {
mSignal->Abort();
}
}
} // dom namespace
} // mozilla namespace

View File

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_AbortController_h
#define mozilla_dom_AbortController_h
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/AbortSignal.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "mozilla/ErrorResult.h"
#include "nsIGlobalObject.h"
namespace mozilla {
namespace dom {
class AbortController final : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortController)
static bool
IsEnabled(JSContext* aCx, JSObject* aGlobal);
static already_AddRefed<AbortController>
Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
explicit AbortController(nsIGlobalObject* aGlobal);
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject*
GetParentObject() const;
AbortSignal*
Signal();
void
Abort();
private:
~AbortController() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<AbortSignal> mSignal;
bool mAborted;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_AbortController_h

124
dom/abort/AbortSignal.cpp Normal file
View File

@ -0,0 +1,124 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AbortSignal.h"
#include "AbortController.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/AbortSignalBinding.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortSignal)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper)
AbortSignal::AbortSignal(AbortController* aController,
bool aAborted)
: DOMEventTargetHelper(aController->GetParentObject())
, mController(aController)
, mAborted(aAborted)
{}
AbortSignal::AbortSignal(bool aAborted)
: mAborted(aAborted)
{}
JSObject*
AbortSignal::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return AbortSignalBinding::Wrap(aCx, this, aGivenProto);
}
bool
AbortSignal::Aborted() const
{
return mAborted;
}
void
AbortSignal::Abort()
{
MOZ_ASSERT(!mAborted);
mAborted = true;
// Let's inform the followers.
for (uint32_t i = 0; i < mFollowers.Length(); ++i) {
mFollowers[i]->Aborted();
}
EventInit init;
init.mBubbles = false;
init.mCancelable = false;
RefPtr<Event> event =
Event::Constructor(this, NS_LITERAL_STRING("abort"), init);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
}
void
AbortSignal::AddFollower(AbortSignal::Follower* aFollower)
{
MOZ_DIAGNOSTIC_ASSERT(aFollower);
if (!mFollowers.Contains(aFollower)) {
mFollowers.AppendElement(aFollower);
}
}
void
AbortSignal::RemoveFollower(AbortSignal::Follower* aFollower)
{
MOZ_DIAGNOSTIC_ASSERT(aFollower);
mFollowers.RemoveElement(aFollower);
}
// AbortSignal::Follower
// ----------------------------------------------------------------------------
AbortSignal::Follower::~Follower()
{
Unfollow();
}
void
AbortSignal::Follower::Follow(AbortSignal* aSignal)
{
MOZ_DIAGNOSTIC_ASSERT(aSignal);
Unfollow();
mFollowingSignal = aSignal;
aSignal->AddFollower(this);
}
void
AbortSignal::Follower::Unfollow()
{
if (mFollowingSignal) {
mFollowingSignal->RemoveFollower(this);
mFollowingSignal = nullptr;
}
}
} // dom namespace
} // mozilla namespace

76
dom/abort/AbortSignal.h Normal file
View File

@ -0,0 +1,76 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_AbortSignal_h
#define mozilla_dom_AbortSignal_h
#include "mozilla/DOMEventTargetHelper.h"
namespace mozilla {
namespace dom {
class AbortController;
class AbortSignal;
class AbortSignal final : public DOMEventTargetHelper
{
public:
// This class must be implemented by objects who want to follow a AbortSignal.
class Follower
{
public:
virtual void Aborted() = 0;
protected:
virtual ~Follower();
void
Follow(AbortSignal* aSignal);
void
Unfollow();
RefPtr<AbortSignal> mFollowingSignal;
};
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortSignal, DOMEventTargetHelper)
AbortSignal(AbortController* aController, bool aAborted);
explicit AbortSignal(bool aAborted);
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
bool
Aborted() const;
void
Abort();
IMPL_EVENT_HANDLER(abort);
void
AddFollower(Follower* aFollower);
void
RemoveFollower(Follower* aFollower);
private:
~AbortSignal() = default;
RefPtr<AbortController> mController;
// Raw pointers. Follower unregisters itself in the DTOR.
nsTArray<Follower*> mFollowers;
bool mAborted;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_AbortSignal_h

26
dom/abort/moz.build Normal file
View File

@ -0,0 +1,26 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM")
TEST_DIRS += ['tests']
EXPORTS.mozilla.dom += [
'AbortController.h',
'AbortSignal.h',
]
UNIFIED_SOURCES += [
'AbortController.cpp',
'AbortSignal.cpp',
]
LOCAL_INCLUDES += [
'../workers',
]
FINAL_LIBRARY = 'xul'

View File

@ -0,0 +1,113 @@
<script>
function ok(a, msg) {
parent.postMessage({ type: "check", status: !!a, message: msg }, "*");
}
function is(a, b, msg) {
ok(a === b, msg);
}
function testWebIDL() {
ok("FetchController" in self, "We have a FetchController prototype");
ok("FetchSignal" in self, "We have a FetchSignal prototype");
var fc = new FetchController();
ok(!!fc, "FetchController can be created");
ok(fc instanceof FetchController, "FetchController is a FetchController");
ok(!!fc.signal, "FetchController has a signal");
ok(fc.signal instanceof FetchSignal, "fetchSignal is a FetchSignal");
is(fc.signal.aborted, false, "By default FetchSignal.aborted is false");
next();
}
function testUpdateData() {
var fc = new FetchController();
is(fc.signal.aborted, false, "By default FetchSignal.aborted is false");
fc.abort();
is(fc.signal.aborted, true, "Signal is aborted");
next();
}
function testAbortEvent() {
var fc = new FetchController();
fc.signal.onabort = function(e) {
is(e.type, "abort", "Abort received");
next();
}
fc.abort();
}
function testAbortedFetch() {
var fc = new FetchController();
fc.abort();
fetch('slow.sjs', { signal: fc.signal }).then(() => {
ok(false, "Fetch should not return a resolved promise");
}, e => {
is(e.name, "AbortError", "We have an abort error");
}).then(next);
}
function testFetchAndAbort() {
var fc = new FetchController();
var p = fetch('slow.sjs', { signal: fc.signal });
fc.abort();
p.then(() => {
ok(false, "Fetch should not return a resolved promise");
}, e => {
is(e.name, "AbortError", "We have an abort error");
}).then(next);
}
function testWorkerAbortedFetch() {
var w = new Worker('worker_fetch_controller.js');
w.onmessage = function(e) {
ok(e.data, "Abort + Fetch works in workers");
next();
}
w.postMessage('testWorkerAbortedFetch');
}
function testWorkerFetchAndAbort() {
var w = new Worker('worker_fetch_controller.js');
w.onmessage = function(e) {
ok(e.data, "Abort + Fetch works in workers");
next();
}
w.postMessage('testWorkerFetchAndAbort');
}
var steps = [
// Simple stuff
testWebIDL,
testUpdateData,
// Event propagation
testAbortEvent,
// fetch + signaling
testAbortedFetch,
testFetchAndAbort,
testWorkerAbortedFetch,
testWorkerFetchAndAbort,
];
function next() {
if (!steps.length) {
parent.postMessage({ type: "finish" }, "*");
return;
}
var step = steps.shift();
step();
}
next();
</script>

View File

@ -0,0 +1,6 @@
[DEFAULT]
support-files =
file_abort_controller.html
worker_fetch_controller.js
[test_abort_controller.html]

View File

@ -0,0 +1,8 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_MANIFESTS += ['mochitest.ini']

View File

@ -0,0 +1,40 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test FetchController</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
SpecialPowers.pushPrefEnv({"set": [["dom.fetchController.enabled", true ]]}, () => {
let ifr = document.createElement('iframe');
ifr.src = "file_fetch_controller.html";
document.body.appendChild(ifr);
onmessage = function(e) {
if (e.data.type == "finish") {
SimpleTest.finish();
return;
}
if (e.data.type == "check") {
ok(e.data.status, e.data.message);
return;
}
ok(false, "Something when wrong.");
}
});
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -0,0 +1,27 @@
function testWorkerAbortedFetch() {
var fc = new AbortController();
fc.abort();
fetch('slow.sjs', { signal: fc.signal }).then(() => {
postMessage(false);
}, e => {
postMessage(e.name == "AbortError");
});
}
function testWorkerFetchAndAbort() {
var fc = new AbortController();
var p = fetch('slow.sjs', { signal: fc.signal });
fc.abort();
p.then(() => {
postMessage(false);
}, e => {
postMessage(e.name == "AbortError");
});
}
onmessage = function(e) {
self[e.data]();
}

View File

@ -909,7 +909,9 @@ GK_ATOM(onreadystatechange, "onreadystatechange")
GK_ATOM(onreceived, "onreceived")
GK_ATOM(onremoteheld, "onremoteheld")
GK_ATOM(onremoteresumed, "onremoteresumed")
GK_ATOM(onrequestprogress, "onrequestprogress")
GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
GK_ATOM(onresponseprogress, "onresponseprogress")
GK_ATOM(onretrieving, "onretrieving")
GK_ATOM(onRequest, "onRequest")
GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")

View File

@ -39,6 +39,7 @@
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "FetchObserver.h"
#include "InternalRequest.h"
#include "InternalResponse.h"
@ -52,38 +53,141 @@ namespace dom {
using namespace workers;
// This class helps the proxying of AbortSignal changes cross threads.
class AbortSignalProxy final : public AbortSignal::Follower
{
// This is created and released on the main-thread.
RefPtr<AbortSignal> mSignalMainThread;
// This value is used only for the creation of AbortSignal on the
// main-thread. They are not updated.
const bool mAborted;
// This runnable propagates changes from the AbortSignal on workers to the
// AbortSignal on main-thread.
class AbortSignalProxyRunnable final : public Runnable
{
RefPtr<AbortSignalProxy> mProxy;
public:
explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy)
: mProxy(aProxy)
{}
NS_IMETHOD
Run() override
{
MOZ_ASSERT(NS_IsMainThread());
AbortSignal* signal = mProxy->GetOrCreateSignalForMainThread();
signal->Abort();
return NS_OK;
}
};
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbortSignalProxy)
explicit AbortSignalProxy(AbortSignal* aSignal)
: mAborted(aSignal->Aborted())
{
Follow(aSignal);
}
void
Aborted() override
{
RefPtr<AbortSignalProxyRunnable> runnable =
new AbortSignalProxyRunnable(this);
NS_DispatchToMainThread(runnable);
}
AbortSignal*
GetOrCreateSignalForMainThread()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mSignalMainThread) {
mSignalMainThread = new AbortSignal(mAborted);
}
return mSignalMainThread;
}
void
Shutdown()
{
Unfollow();
}
private:
~AbortSignalProxy()
{
NS_ReleaseOnMainThread(mSignalMainThread.forget());
}
};
class WorkerFetchResolver final : public FetchDriverObserver
{
friend class MainThreadFetchRunnable;
friend class WorkerDataAvailableRunnable;
friend class WorkerFetchResponseEndBase;
friend class WorkerFetchResponseEndRunnable;
friend class WorkerFetchResponseRunnable;
RefPtr<PromiseWorkerProxy> mPromiseProxy;
RefPtr<AbortSignalProxy> mSignalProxy;
RefPtr<FetchObserver> mFetchObserver;
public:
// Returns null if worker is shutting down.
static already_AddRefed<WorkerFetchResolver>
Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise)
Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise,
AbortSignal* aSignal, FetchObserver* aObserver)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
RefPtr<PromiseWorkerProxy> proxy =
PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
if (!proxy) {
return nullptr;
}
RefPtr<WorkerFetchResolver> r = new WorkerFetchResolver(proxy);
RefPtr<AbortSignalProxy> signalProxy;
if (aSignal) {
signalProxy = new AbortSignalProxy(aSignal);
}
RefPtr<WorkerFetchResolver> r =
new WorkerFetchResolver(proxy, signalProxy, aObserver);
return r.forget();
}
AbortSignal*
GetAbortSignal()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mSignalProxy) {
return nullptr;
}
return mSignalProxy->GetOrCreateSignalForMainThread();
}
void
OnResponseAvailableInternal(InternalResponse* aResponse) override;
void
OnResponseEnd() override;
OnResponseEnd(FetchDriverObserver::EndReason eReason) override;
void
OnDataAvailable() override;
private:
explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy)
WorkerFetchResolver(PromiseWorkerProxy* aProxy,
AbortSignalProxy* aSignalProxy,
FetchObserver* aObserver)
: mPromiseProxy(aProxy)
, mSignalProxy(aSignalProxy)
, mFetchObserver(aObserver)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(mPromiseProxy);
@ -100,12 +204,16 @@ class MainThreadFetchResolver final : public FetchDriverObserver
{
RefPtr<Promise> mPromise;
RefPtr<Response> mResponse;
RefPtr<FetchObserver> mFetchObserver;
nsCOMPtr<nsIDocument> mDocument;
NS_DECL_OWNINGTHREAD
public:
explicit MainThreadFetchResolver(Promise* aPromise);
MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver)
: mPromise(aPromise)
, mFetchObserver(aObserver)
{}
void
OnResponseAvailableInternal(InternalResponse* aResponse) override;
@ -115,11 +223,20 @@ public:
mDocument = aDocument;
}
virtual void OnResponseEnd() override
void OnResponseEnd(FetchDriverObserver::EndReason aReason) override
{
if (aReason == eAborted) {
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
mFetchObserver = nullptr;
FlushConsoleReport();
}
void
OnDataAvailable() override;
private:
~MainThreadFetchResolver();
@ -170,9 +287,11 @@ public:
fetch->SetWorkerScript(spec);
}
RefPtr<AbortSignal> signal = mResolver->GetAbortSignal();
// ...but release it before calling Fetch, because mResolver's callback can
// be called synchronously and they want the mutex, too.
return fetch->Fetch(mResolver);
return fetch->Fetch(signal, mResolver);
}
};
@ -210,6 +329,23 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
RefPtr<InternalRequest> r = request->GetInternalRequest();
RefPtr<AbortSignal> signal;
if (aInit.mSignal.WasPassed()) {
signal = &aInit.mSignal.Value();
}
if (signal && signal->Aborted()) {
// An already aborted signal should reject immediately.
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
RefPtr<FetchObserver> observer;
if (aInit.mObserve.WasPassed()) {
observer = new FetchObserver(aGlobal, signal);
aInit.mObserve.Value().HandleEvent(*observer);
}
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
nsCOMPtr<nsIDocument> doc;
@ -236,11 +372,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
}
}
RefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(p);
RefPtr<MainThreadFetchResolver> resolver =
new MainThreadFetchResolver(p, observer);
RefPtr<FetchDriver> fetch = new FetchDriver(r, principal, loadGroup);
fetch->SetDocument(doc);
resolver->SetDocument(doc);
aRv = fetch->Fetch(resolver);
aRv = fetch->Fetch(signal, resolver);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -252,7 +389,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
r->SetSkipServiceWorker();
}
RefPtr<WorkerFetchResolver> resolver = WorkerFetchResolver::Create(worker, p);
RefPtr<WorkerFetchResolver> resolver =
WorkerFetchResolver::Create(worker, p, signal, observer);
if (!resolver) {
NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker");
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
@ -266,11 +404,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
return p.forget();
}
MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise)
: mPromise(aPromise)
{
}
void
MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse)
{
@ -278,16 +411,39 @@ MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse
AssertIsOnMainThread();
if (aResponse->Type() != ResponseType::Error) {
if (mFetchObserver) {
mFetchObserver->SetState(FetchState::Complete);
}
nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
mResponse = new Response(go, aResponse);
mPromise->MaybeResolve(mResponse);
} else {
if (mFetchObserver) {
mFetchObserver->SetState(FetchState::Errored);
}
ErrorResult result;
result.ThrowTypeError<MSG_FETCH_FAILED>();
mPromise->MaybeReject(result);
}
}
void
MainThreadFetchResolver::OnDataAvailable()
{
NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
AssertIsOnMainThread();
if (!mFetchObserver) {
return;
}
if (mFetchObserver->State() == FetchState::Requesting) {
mFetchObserver->SetState(FetchState::Responding);
}
}
MainThreadFetchResolver::~MainThreadFetchResolver()
{
NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
@ -306,6 +462,7 @@ public:
, mResolver(aResolver)
, mInternalResponse(aResponse)
{
MOZ_ASSERT(mResolver);
}
bool
@ -317,10 +474,18 @@ public:
RefPtr<Promise> promise = mResolver->mPromiseProxy->WorkerPromise();
if (mInternalResponse->Type() != ResponseType::Error) {
if (mResolver->mFetchObserver) {
mResolver->mFetchObserver->SetState(FetchState::Complete);
}
RefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
RefPtr<Response> response = new Response(global, mInternalResponse);
promise->MaybeResolve(response);
} else {
if (mResolver->mFetchObserver) {
mResolver->mFetchObserver->SetState(FetchState::Errored);
}
ErrorResult result;
result.ThrowTypeError<MSG_FETCH_FAILED>();
promise->MaybeReject(result);
@ -329,14 +494,42 @@ public:
}
};
class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable
{
RefPtr<WorkerFetchResolver> mResolver;
public:
WorkerDataAvailableRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver)
: MainThreadWorkerRunnable(aWorkerPrivate)
, mResolver(aResolver)
{
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
if (mResolver->mFetchObserver &&
mResolver->mFetchObserver->State() == FetchState::Requesting) {
mResolver->mFetchObserver->SetState(FetchState::Responding);
}
return true;
}
};
class WorkerFetchResponseEndBase
{
RefPtr<PromiseWorkerProxy> mPromiseProxy;
protected:
RefPtr<WorkerFetchResolver> mResolver;
public:
explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy)
: mPromiseProxy(aPromiseProxy)
explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver)
: mResolver(aResolver)
{
MOZ_ASSERT(mPromiseProxy);
MOZ_ASSERT(aResolver);
}
void
@ -344,23 +537,41 @@ public:
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
mPromiseProxy->CleanUp();
mResolver->mPromiseProxy->CleanUp();
mResolver->mFetchObserver = nullptr;
if (mResolver->mSignalProxy) {
mResolver->mSignalProxy->Shutdown();
mResolver->mSignalProxy = nullptr;
}
}
};
class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable
, public WorkerFetchResponseEndBase
{
FetchDriverObserver::EndReason mReason;
public:
explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy)
: MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate())
, WorkerFetchResponseEndBase(aPromiseProxy)
WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver,
FetchDriverObserver::EndReason aReason)
: MainThreadWorkerRunnable(aWorkerPrivate)
, WorkerFetchResponseEndBase(aResolver)
, mReason(aReason)
{
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
if (mReason == FetchDriverObserver::eAborted) {
RefPtr<Promise> promise = mResolver->mPromiseProxy->WorkerPromise();
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
WorkerRunInternal(aWorkerPrivate);
return true;
}
@ -379,9 +590,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr
, public WorkerFetchResponseEndBase
{
public:
explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy)
: MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate())
, WorkerFetchResponseEndBase(aPromiseProxy)
WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver)
: MainThreadWorkerControlRunnable(aWorkerPrivate)
, WorkerFetchResponseEndBase(aResolver)
{
}
@ -415,7 +627,22 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse)
}
void
WorkerFetchResolver::OnResponseEnd()
WorkerFetchResolver::OnDataAvailable()
{
AssertIsOnMainThread();
MutexAutoLock lock(mPromiseProxy->Lock());
if (mPromiseProxy->CleanedUp()) {
return;
}
RefPtr<WorkerDataAvailableRunnable> r =
new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this);
Unused << r->Dispatch();
}
void
WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason)
{
AssertIsOnMainThread();
MutexAutoLock lock(mPromiseProxy->Lock());
@ -426,11 +653,13 @@ WorkerFetchResolver::OnResponseEnd()
FlushConsoleReport();
RefPtr<WorkerFetchResponseEndRunnable> r =
new WorkerFetchResponseEndRunnable(mPromiseProxy);
new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(),
this, aReason);
if (!r->Dispatch()) {
RefPtr<WorkerFetchResponseEndControlRunnable> cr =
new WorkerFetchResponseEndControlRunnable(mPromiseProxy);
new WorkerFetchResponseEndControlRunnable(mPromiseProxy->GetWorkerPrivate(),
this);
// This can fail if the worker thread is canceled or killed causing
// the PromiseWorkerProxy to give up its WorkerHolder immediately,
// allowing the worker thread to become Dead.

View File

@ -67,7 +67,7 @@ FetchDriver::~FetchDriver()
}
nsresult
FetchDriver::Fetch(FetchDriverObserver* aObserver)
FetchDriver::Fetch(AbortSignal* aSignal, FetchDriverObserver* aObserver)
{
workers::AssertIsOnMainThread();
#ifdef DEBUG
@ -90,6 +90,18 @@ FetchDriver::Fetch(FetchDriverObserver* aObserver)
}
mRequest->SetPrincipalInfo(Move(principalInfo));
// If the signal is aborted, it's time to inform the observer and terminate
// the operation.
if (aSignal) {
if (aSignal->Aborted()) {
Aborted();
return NS_OK;
}
Follow(aSignal);
}
if (NS_FAILED(HttpFetch())) {
FailWithNetworkError();
}
@ -114,11 +126,7 @@ FetchDriver::HttpFetch()
nsAutoCString url;
mRequest->GetURL(url);
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri),
url,
nullptr,
nullptr,
ios);
rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr, ios);
NS_ENSURE_SUCCESS(rv, rv);
// Unsafe requests aren't allowed with when using no-core mode.
@ -380,6 +388,8 @@ FetchDriver::HttpFetch()
NS_ENSURE_SUCCESS(rv, rv);
// Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
mChannel = chan;
return NS_OK;
}
already_AddRefed<InternalResponse>
@ -433,9 +443,11 @@ FetchDriver::FailWithNetworkError()
#ifdef DEBUG
mResponseAvailableCalled = true;
#endif
mObserver->OnResponseEnd();
mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
mObserver = nullptr;
}
mChannel = nullptr;
}
namespace {
@ -655,6 +667,31 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
return NS_OK;
}
namespace {
// Runnable to call the observer OnDataAvailable on the main-thread.
class DataAvailableRunnable final : public Runnable
{
RefPtr<FetchDriverObserver> mObserver;
public:
explicit DataAvailableRunnable(FetchDriverObserver* aObserver)
: mObserver(aObserver)
{
MOZ_ASSERT(aObserver);
}
NS_IMETHOD
Run() override
{
mObserver->OnDataAvailable();
mObserver = nullptr;
return NS_OK;
}
};
} // anonymous namespace
NS_IMETHODIMP
FetchDriver::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
@ -666,6 +703,18 @@ FetchDriver::OnDataAvailable(nsIRequest* aRequest,
// called between OnStartRequest and OnStopRequest, so we don't need to worry
// about races.
if (mObserver) {
if (NS_IsMainThread()) {
mObserver->OnDataAvailable();
} else {
RefPtr<Runnable> runnable = new DataAvailableRunnable(mObserver);
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
uint32_t aRead;
MOZ_ASSERT(mResponse);
MOZ_ASSERT(mPipeOutputStream);
@ -777,10 +826,11 @@ FetchDriver::OnStopRequest(nsIRequest* aRequest,
#endif
}
mObserver->OnResponseEnd();
mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
mObserver = nullptr;
}
mChannel = nullptr;
return NS_OK;
}
@ -921,5 +971,21 @@ FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel) const
}
}
void FetchDriver::Aborted()
{
if (mObserver) {
#ifdef DEBUG
mResponseAvailableCalled = true;
#endif
mObserver->OnResponseEnd(FetchDriverObserver::eAborted);
mObserver = nullptr;
}
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
mChannel = nullptr;
}
}
} // namespace dom
} // namespace mozilla

View File

@ -12,6 +12,7 @@
#include "nsIStreamListener.h"
#include "nsIThreadRetargetableStreamListener.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/dom/AbortSignal.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/RefPtr.h"
@ -49,7 +50,14 @@ public:
mGotResponseAvailable = true;
OnResponseAvailableInternal(aResponse);
}
virtual void OnResponseEnd()
enum EndReason
{
eAborted,
eByNetworking,
};
virtual void OnResponseEnd(EndReason aReason)
{ };
nsIConsoleReportCollector* GetReporter() const
@ -58,6 +66,9 @@ public:
}
virtual void FlushConsoleReport() = 0;
virtual void OnDataAvailable() = 0;
protected:
virtual ~FetchDriverObserver()
{ };
@ -72,7 +83,8 @@ private:
class FetchDriver final : public nsIStreamListener,
public nsIChannelEventSink,
public nsIInterfaceRequestor,
public nsIThreadRetargetableStreamListener
public nsIThreadRetargetableStreamListener,
public AbortSignal::Follower
{
public:
NS_DECL_ISUPPORTS
@ -82,9 +94,12 @@ public:
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
explicit FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
nsILoadGroup* aLoadGroup);
NS_IMETHOD Fetch(FetchDriverObserver* aObserver);
FetchDriver(InternalRequest* aRequest,
nsIPrincipal* aPrincipal,
nsILoadGroup* aLoadGroup);
nsresult Fetch(AbortSignal* aSignal,
FetchDriverObserver* aObserver);
void
SetDocument(nsIDocument* aDocument);
@ -96,6 +111,11 @@ public:
mWorkerScript = aWorkerScirpt;
}
// AbortSignal::Follower
void
Aborted() override;
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@ -104,6 +124,7 @@ private:
nsCOMPtr<nsIOutputStream> mPipeOutputStream;
RefPtr<FetchDriverObserver> mObserver;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsIChannel> mChannel;
nsAutoPtr<SRICheckDataVerifier> mSRIDataVerifier;
SRIMetadata mSRIMetadata;
nsCString mWorkerScript;

117
dom/fetch/FetchObserver.cpp Normal file
View File

@ -0,0 +1,117 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FetchObserver.h"
#include "WorkerPrivate.h"
#include "mozilla/dom/Event.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(FetchObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchObserver,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchObserver)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(FetchObserver, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(FetchObserver, DOMEventTargetHelper)
/* static */ bool
FetchObserver::IsEnabled(JSContext* aCx, JSObject* aGlobal)
{
if (NS_IsMainThread()) {
return Preferences::GetBool("dom.fetchObserver.enabled", false);
}
using namespace workers;
// Otherwise, check the pref via the WorkerPrivate
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (!workerPrivate) {
return false;
}
return workerPrivate->FetchObserverEnabled();
}
FetchObserver::FetchObserver(nsIGlobalObject* aGlobal,
AbortSignal* aSignal)
: DOMEventTargetHelper(aGlobal)
, mState(FetchState::Requesting)
{
if (aSignal) {
Follow(aSignal);
}
}
JSObject*
FetchObserver::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return FetchObserverBinding::Wrap(aCx, this, aGivenProto);
}
FetchState
FetchObserver::State() const
{
return mState;
}
void
FetchObserver::Aborted()
{
SetState(FetchState::Aborted);
}
void
FetchObserver::SetState(FetchState aState)
{
MOZ_ASSERT(mState < aState);
if (mState == FetchState::Aborted ||
mState == FetchState::Errored ||
mState == FetchState::Complete) {
// We are already in a final state.
return;
}
// We cannot pass from Requesting to Complete directly.
if (mState == FetchState::Requesting &&
aState == FetchState::Complete) {
SetState(FetchState::Responding);
}
mState = aState;
if (mState == FetchState::Aborted ||
mState == FetchState::Errored ||
mState == FetchState::Complete) {
Unfollow();
}
EventInit init;
init.mBubbles = false;
init.mCancelable = false;
// TODO which kind of event should we dispatch here?
RefPtr<Event> event =
Event::Constructor(this, NS_LITERAL_STRING("statechange"), init);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
}
} // dom namespace
} // mozilla namespace

54
dom/fetch/FetchObserver.h Normal file
View File

@ -0,0 +1,54 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_FetchObserver_h
#define mozilla_dom_FetchObserver_h
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/FetchObserverBinding.h"
#include "mozilla/dom/AbortSignal.h"
namespace mozilla {
namespace dom {
class FetchObserver final : public DOMEventTargetHelper
, public AbortSignal::Follower
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchObserver, DOMEventTargetHelper)
static bool
IsEnabled(JSContext* aCx, JSObject* aGlobal);
FetchObserver(nsIGlobalObject* aGlobal, AbortSignal* aSignal);
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
FetchState
State() const;
IMPL_EVENT_HANDLER(statechange);
IMPL_EVENT_HANDLER(requestprogress);
IMPL_EVENT_HANDLER(responseprogress);
void
Aborted() override;
void
SetState(FetchState aState);
private:
~FetchObserver() = default;
FetchState mState;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_FetchObserver_h

View File

@ -12,6 +12,7 @@
#include "nsWrapperCache.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/AbortSignal.h"
#include "mozilla/dom/InternalRequest.h"
// Required here due to certain WebIDL enums/classes being declared in both
// files.

View File

@ -9,6 +9,7 @@ EXPORTS.mozilla.dom += [
'Fetch.h',
'FetchDriver.h',
'FetchIPCTypes.h',
'FetchObserver.h',
'FetchUtil.h',
'Headers.h',
'InternalHeaders.h',
@ -28,6 +29,7 @@ UNIFIED_SOURCES += [
SOURCES += [
'ChannelInfo.cpp',
'FetchDriver.cpp',
'FetchObserver.cpp',
'FetchUtil.cpp',
'Headers.cpp',
'InternalHeaders.cpp',

View File

@ -37,6 +37,7 @@ interfaces = [
DIRS += ['interfaces/' + i for i in interfaces]
DIRS += [
'abort',
'animation',
'apps',
'base',

View File

@ -0,0 +1,146 @@
<script>
function ok(a, msg) {
parent.postMessage({ type: "check", status: !!a, message: msg }, "*");
}
function is(a, b, msg) {
ok(a === b, msg);
}
function testObserver() {
ok("FetchObserver" in self, "We have a FetchObserver prototype");
fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { observe: o => {
ok(!!o, "We have an observer");
ok(o instanceof FetchObserver, "The correct object has been passed");
is(o.state, "requesting", "By default the state is requesting");
next();
}});
}
function testObserveAbort() {
var fc = new AbortController();
fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', {
signal: fc.signal,
observe: o => {
o.onstatechange = () => {
ok(true, "StateChange event dispatched");
if (o.state == "aborted") {
ok(true, "Aborted!");
next();
}
}
fc.abort();
}
});
}
function testObserveComplete() {
var fc = new AbortController();
fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', {
signal: fc.signal,
observe: o => {
o.onstatechange = () => {
ok(true, "StateChange event dispatched");
if (o.state == "complete") {
ok(true, "Operation completed");
next();
}
}
}
});
}
function testObserveErrored() {
var fc = new AbortController();
fetch('foo: bar', {
signal: fc.signal,
observe: o => {
o.onstatechange = () => {
ok(true, "StateChange event dispatched");
if (o.state == "errored") {
ok(true, "Operation completed");
next();
}
}
}
});
}
function testObserveResponding() {
var fc = new AbortController();
fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', {
signal: fc.signal,
observe: o => {
o.onstatechange = () => {
if (o.state == "responding") {
ok(true, "We have responding events");
next();
}
}
}
});
}
function workify(worker) {
function methods() {
function ok(a, msg) {
postMessage( { type: 'check', state: !!a, message: msg });
};
function is(a, b, msg) {
postMessage( { type: 'check', state: a === b, message: msg });
};
function next() {
postMessage( { type: 'finish' });
};
}
var str = methods.toString();
var methodsContent = str.substring(0, str.length - 1).split('\n').splice(1).join('\n');
str = worker.toString();
var workerContent = str.substring(0, str.length - 1).split('\n').splice(1).join('\n');
var content = methodsContent + workerContent;
var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" }));
var w = new Worker(url);
w.onmessage = e => {
if (e.data.type == 'check') {
ok(e.data.state, "WORKER: " + e.data.message);
} else if (e.data.type == 'finish') {
next();
} else {
ok(false, "Something went wrong");
}
}
}
var steps = [
testObserver,
testObserveAbort,
function() { workify(testObserveAbort); },
testObserveComplete,
function() { workify(testObserveComplete); },
testObserveErrored,
function() { workify(testObserveErrored); },
testObserveResponding,
function() { workify(testObserveResponding); },
];
function next() {
if (!steps.length) {
parent.postMessage({ type: "finish" }, "*");
return;
}
var step = steps.shift();
step();
}
next();
</script>

View File

@ -4,6 +4,7 @@ support-files =
test_fetch_basic.js
test_fetch_basic_http.js
test_fetch_cors.js
file_fetch_observer.html
test_formdataparsing.js
test_headers_common.js
test_request.js
@ -15,6 +16,7 @@ support-files =
reroute.html
reroute.js
reroute.js^headers^
slow.sjs
sw_reroute.js
empty.js
empty.js^headers^
@ -47,6 +49,7 @@ skip-if = toolkit == 'android' # Bug 1210282
skip-if = toolkit == 'android' # Bug 1210282
[test_fetch_cors_sw_empty_reroute.html]
skip-if = toolkit == 'android' # Bug 1210282
[test_fetch_observer.html]
[test_formdataparsing.html]
[test_formdataparsing_sw_reroute.html]
[test_request.html]

View File

@ -0,0 +1,11 @@
function handleRequest(request, response)
{
response.processAsync();
timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
timer.init(function() {
response.write("Here the content. But slowly.");
response.finish();
}, 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}

View File

@ -0,0 +1,41 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test FetchObserver</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
SpecialPowers.pushPrefEnv({"set": [["dom.fetchObserver.enabled", true ],
["dom.fetchController.enabled", true ]]}, () => {
let ifr = document.createElement('iframe');
ifr.src = "file_fetch_observer.html";
document.body.appendChild(ifr);
onmessage = function(e) {
if (e.data.type == "finish") {
SimpleTest.finish();
return;
}
if (e.data.type == "check") {
ok(e.data.status, e.data.message);
return;
}
ok(false, "Something when wrong.");
}
});
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -0,0 +1,13 @@
/* -*- 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/.
*/
[Constructor(), Exposed=(Window,Worker),
Func="AbortController::IsEnabled"]
interface AbortController {
readonly attribute AbortSignal signal;
void abort();
};

View File

@ -0,0 +1,13 @@
/* -*- 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/.
*/
[Exposed=(Window,Worker),
Func="AbortController::IsEnabled"]
interface AbortSignal : EventTarget {
readonly attribute boolean aborted;
attribute EventHandler onabort;
};

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/.
*/
callback interface ObserverCallback {
void handleEvent(FetchObserver observer);
};
enum FetchState {
// Pending states
"requesting", "responding",
// Final states
"aborted", "errored", "complete"
};
[Exposed=(Window,Worker),
Func="FetchObserver::IsEnabled"]
interface FetchObserver : EventTarget {
readonly attribute FetchState state;
// Events
attribute EventHandler onstatechange;
attribute EventHandler onrequestprogress;
attribute EventHandler onresponseprogress;
};

View File

@ -47,6 +47,12 @@ dictionary RequestInit {
RequestCache cache;
RequestRedirect redirect;
DOMString integrity;
[Func="AbortController::IsEnabled"]
AbortSignal signal;
[Func="FetchObserver::IsEnabled"]
ObserverCallback observe;
};
// Gecko currently does not ship RequestContext, so please don't use it in IDL

View File

@ -17,6 +17,8 @@ PREPROCESSED_WEBIDL_FILES = [
]
WEBIDL_FILES = [
'AbortController.webidl',
'AbortSignal.webidl',
'AbstractWorker.webidl',
'AnalyserNode.webidl',
'Animatable.webidl',
@ -142,6 +144,7 @@ WEBIDL_FILES = [
'FakePluginTagInit.webidl',
'Fetch.webidl',
'FetchEvent.webidl',
'FetchObserver.webidl',
'File.webidl',
'FileList.webidl',
'FileMode.webidl',

View File

@ -39,6 +39,8 @@ WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
WORKER_SIMPLE_PREF("dom.abortController.enabled", AbortControllerEnabled, ABORTCONTROLLER_ENABLED)
WORKER_SIMPLE_PREF("dom.fetchObserver.enabled", FetchObserverEnabled, FETCHOBSERVER_ENABLED)
WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged)
WORKER_PREF("intl.accept_languages", PrefLanguagesChanged)
WORKER_PREF("general.appname.override", AppNameOverrideChanged)

View File

@ -4757,6 +4757,9 @@ pref("dom.vibrator.max_vibrate_list_len", 128);
// Disabled by default to reduce private data exposure.
pref("dom.battery.enabled", false);
// Abort API
pref("dom.abortController.enabled", true);
// Push
pref("dom.push.enabled", false);