From 3fffe4ebdadaa2a73b1d7573c5a73392bd0606ef Mon Sep 17 00:00:00 2001 From: adeshkp Date: Mon, 8 Jun 2020 09:17:35 -0400 Subject: [PATCH 01/13] Issue #1542 - Fix a typo in wasm which was causing build failure on ARM --- js/src/wasm/WasmBaselineCompile.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 8dc5c104f4..7162e3338d 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -3391,7 +3391,7 @@ class BaseCompiler #ifdef JS_CODEGEN_ARM void loadI32(MemoryAccessDesc access, bool isSigned, RegI32 ptr, Register rt) { - if (access.byteSize() > 1 && IsUnaligned(ins->access())) { + if (access.byteSize() > 1 && IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(isSigned, access.byteSize(), ptr.reg, scratch, rt, 0); @@ -3405,7 +3405,7 @@ class BaseCompiler void storeI32(MemoryAccessDesc access, RegI32 ptr, Register rt) { - if (access.byteSize() > 1 && IsUnaligned(ins->access())) { + if (access.byteSize() > 1 && IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); masm.emitUnalignedStore(access.byteSize(), ptr.reg, rt, 0); } else { @@ -3419,7 +3419,7 @@ class BaseCompiler void loadI64(MemoryAccessDesc access, RegI32 ptr, RegI64 dest) { - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, dest.reg.low, @@ -3440,7 +3440,7 @@ class BaseCompiler void storeI64(MemoryAccessDesc access, RegI32 ptr, RegI64 src) { - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, src.reg.low, 0); masm.emitUnalignedStore(ByteSize(4), ptr.reg, src.reg.high, 4); @@ -3459,7 +3459,7 @@ class BaseCompiler void loadF32(MemoryAccessDesc access, RegI32 ptr, RegF32 dest, RegI32 tmp1) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp1.reg, 0); masm.ma_vxfer(tmp1.reg, dest.reg); @@ -3473,7 +3473,7 @@ class BaseCompiler void storeF32(MemoryAccessDesc access, RegI32 ptr, RegF32 src, RegI32 tmp1) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.ma_vxfer(src.reg, tmp1.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp1.reg, 0); } else { @@ -3486,7 +3486,7 @@ class BaseCompiler void loadF64(MemoryAccessDesc access, RegI32 ptr, RegF64 dest, RegI32 tmp1, RegI32 tmp2) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp1.reg, 0); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp2.reg, 4); @@ -3501,7 +3501,7 @@ class BaseCompiler void storeF64(MemoryAccessDesc access, RegI32 ptr, RegF64 src, RegI32 tmp1, RegI32 tmp2) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.ma_vxfer(src.reg, tmp1.reg, tmp2.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp1.reg, 0); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp2.reg, 4); From 21f87c85ec3dbca99d51d6ee85889d1243cd9307 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Thu, 11 Jun 2020 09:47:03 -0400 Subject: [PATCH 02/13] Issue #439 - Debug follow up to remove no longer existing headers Although the headers were removed, they are still referenced in WebIDL causing compiler to fail for debug builds. This removes TestFunctions.webidl and TestInterfaceJSMaplikeSetlikeIterable.webidl, with nit fix for existing incorrect whitespace. --- dom/webidl/moz.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 5261a3f7a5..7b8e7f0052 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -650,10 +650,10 @@ WEBIDL_FILES += [ # We only expose our prefable test interfaces in debug builds, just to be on # the safe side. if CONFIG['MOZ_DEBUG']: - WEBIDL_FILES += ['TestFunctions.webidl', - 'TestInterfaceJS.webidl', - 'TestInterfaceJSDictionaries.webidl', - 'TestInterfaceJSMaplikeSetlikeIterable.webidl'] + WEBIDL_FILES += [ + 'TestInterfaceJS.webidl', + 'TestInterfaceJSDictionaries.webidl', + ] if CONFIG['MOZ_SECUREELEMENT']: WEBIDL_FILES += [ From 24087592ad751f8b4ae332607252a3f8ecdaface Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:08:58 +0000 Subject: [PATCH 03/13] Issue #1587 - Part 1: Implement FetchController/FetchSignal interface --- dom/fetch/FetchController.cpp | 110 ++++++++++++++++++ dom/fetch/FetchController.h | 66 +++++++++++ dom/fetch/FetchSignal.cpp | 72 ++++++++++++ dom/fetch/FetchSignal.h | 47 ++++++++ dom/fetch/Request.h | 1 + dom/fetch/moz.build | 4 + .../fetch/file_fetch_controller.html | 40 +++++++ dom/tests/mochitest/fetch/mochitest.ini | 2 + .../fetch/test_fetch_controller.html | 40 +++++++ dom/webidl/FetchController.webidl | 15 +++ dom/webidl/FetchSignal.webidl | 13 +++ dom/webidl/Request.webidl | 3 + dom/webidl/moz.build | 2 + dom/workers/WorkerPrefs.h | 1 + 14 files changed, 416 insertions(+) create mode 100644 dom/fetch/FetchController.cpp create mode 100644 dom/fetch/FetchController.h create mode 100644 dom/fetch/FetchSignal.cpp create mode 100644 dom/fetch/FetchSignal.h create mode 100644 dom/tests/mochitest/fetch/file_fetch_controller.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_controller.html create mode 100644 dom/webidl/FetchController.webidl create mode 100644 dom/webidl/FetchSignal.webidl diff --git a/dom/fetch/FetchController.cpp b/dom/fetch/FetchController.cpp new file mode 100644 index 0000000000..b3d8a4d9c5 --- /dev/null +++ b/dom/fetch/FetchController.cpp @@ -0,0 +1,110 @@ +/* -*- 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 "FetchController.h" +#include "FetchSignal.h" +#include "mozilla/dom/FetchControllerBinding.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) +NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchController) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ bool +FetchController::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.fetchController.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->FetchControllerEnabled(); +} + +/* static */ already_AddRefed +FetchController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr fetchController = new FetchController(global); + return fetchController.forget(); +} + +FetchController::FetchController(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal) + , mAborted(false) +{} + +JSObject* +FetchController::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchControllerBinding::Wrap(aCx, this, aGivenProto); +} + +nsIGlobalObject* +FetchController::GetParentObject() const +{ + return mGlobal; +} + +FetchSignal* +FetchController::Signal() +{ + if (!mSignal) { + mSignal = new FetchSignal(this, mAborted); + } + + return mSignal; +} + +void +FetchController::Abort() +{ + if (mAborted) { + return; + } + + mAborted = true; + + if (mSignal) { + mSignal->Abort(); + } +} + +void +FetchController::Follow(FetchSignal& aSignal) +{ + // TODO +} + +void +FetchController::Unfollow(FetchSignal& aSignal) +{ + // TODO +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchController.h b/dom/fetch/FetchController.h new file mode 100644 index 0000000000..854c6f9743 --- /dev/null +++ b/dom/fetch/FetchController.h @@ -0,0 +1,66 @@ +/* -*- 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_FetchController_h +#define mozilla_dom_FetchController_h + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/ErrorResult.h" +#include "nsIGlobalObject.h" + +namespace mozilla { +namespace dom { + +class FetchSignal; + +class FetchController final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FetchController) + + static bool + IsEnabled(JSContext* aCx, JSObject* aGlobal); + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); + + explicit FetchController(nsIGlobalObject* aGlobal); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + nsIGlobalObject* + GetParentObject() const; + + FetchSignal* + Signal(); + + void + Abort(); + + void + Follow(FetchSignal& aSignal); + + void + Unfollow(FetchSignal& aSignal); + +private: + ~FetchController() = default; + + nsCOMPtr mGlobal; + RefPtr mSignal; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchController_h diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp new file mode 100644 index 0000000000..4395dbcf2f --- /dev/null +++ b/dom/fetch/FetchSignal.cpp @@ -0,0 +1,72 @@ +/* -*- 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 "FetchSignal.h" +#include "FetchController.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/FetchSignalBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(FetchSignal, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(FetchSignal, DOMEventTargetHelper) + +FetchSignal::FetchSignal(FetchController* aController, + bool aAborted) + : DOMEventTargetHelper(aController->GetParentObject()) + , mController(aController) + , mAborted(aAborted) +{} + +JSObject* +FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchSignalBinding::Wrap(aCx, this, aGivenProto); +} + +bool +FetchSignal::Aborted() const +{ + return mAborted; +} + +void +FetchSignal::Abort() +{ + MOZ_ASSERT(!mAborted); + mAborted = true; + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + // TODO which kind of event should we dispatch here? + + RefPtr event = + Event::Constructor(this, NS_LITERAL_STRING("abort"), init); + event->SetTrusted(true); + + DispatchDOMEvent(nullptr, event, nullptr, nullptr); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h new file mode 100644 index 0000000000..5bb16b8348 --- /dev/null +++ b/dom/fetch/FetchSignal.h @@ -0,0 +1,47 @@ +/* -*- 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_FetchSignal_h +#define mozilla_dom_FetchSignal_h + +#include "mozilla/DOMEventTargetHelper.h" + +namespace mozilla { +namespace dom { + +class FetchController; + +class FetchSignal final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) + + FetchSignal(FetchController* aController, bool aAborted); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + bool + Aborted() const; + + void + Abort(); + + IMPL_EVENT_HANDLER(abort); + +private: + ~FetchSignal() = default; + + RefPtr mController; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchSignal_h diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index d33c748122..56a75e5af5 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -12,6 +12,7 @@ #include "nsWrapperCache.h" #include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FetchSignal.h" #include "mozilla/dom/InternalRequest.h" // Required here due to certain WebIDL enums/classes being declared in both // files. diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index be820ab570..757f857f28 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -7,8 +7,10 @@ EXPORTS.mozilla.dom += [ 'ChannelInfo.h', 'Fetch.h', + 'FetchController.h', 'FetchDriver.h', 'FetchIPCTypes.h', + 'FetchSignal.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', @@ -27,7 +29,9 @@ UNIFIED_SOURCES += [ SOURCES += [ 'ChannelInfo.cpp', + 'FetchController.cpp', 'FetchDriver.cpp', + 'FetchSignal.cpp', 'FetchUtil.cpp', 'Headers.cpp', 'InternalHeaders.cpp', diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html new file mode 100644 index 0000000000..6efa2fe0ac --- /dev/null +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -0,0 +1,40 @@ + diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index cf4477463c..6dba08f982 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -1,6 +1,7 @@ [DEFAULT] support-files = fetch_test_framework.js + file_fetch_controller.html test_fetch_basic.js test_fetch_basic_http.js test_fetch_cors.js @@ -41,6 +42,7 @@ support-files = [test_fetch_basic_http.html] [test_fetch_basic_http_sw_reroute.html] [test_fetch_basic_http_sw_empty_reroute.html] +[test_fetch_controller.html] [test_fetch_cors.html] skip-if = toolkit == 'android' # Bug 1210282 [test_fetch_cors_sw_reroute.html] diff --git a/dom/tests/mochitest/fetch/test_fetch_controller.html b/dom/tests/mochitest/fetch/test_fetch_controller.html new file mode 100644 index 0000000000..812fb91616 --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_controller.html @@ -0,0 +1,40 @@ + + + + + Test FetchController + + + + + + + + diff --git a/dom/webidl/FetchController.webidl b/dom/webidl/FetchController.webidl new file mode 100644 index 0000000000..c5b1cc6da2 --- /dev/null +++ b/dom/webidl/FetchController.webidl @@ -0,0 +1,15 @@ +/* -*- 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="FetchController::IsEnabled"] +interface FetchController { + readonly attribute FetchSignal signal; + + void abort(); + void follow(FetchSignal signal); + void unfollow(FetchSignal signal); +}; diff --git a/dom/webidl/FetchSignal.webidl b/dom/webidl/FetchSignal.webidl new file mode 100644 index 0000000000..965355c203 --- /dev/null +++ b/dom/webidl/FetchSignal.webidl @@ -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="FetchController::IsEnabled"] +interface FetchSignal : EventTarget { + readonly attribute boolean aborted; + + attribute EventHandler onabort; +}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index e29c084d00..00497456ab 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -47,6 +47,9 @@ dictionary RequestInit { RequestCache cache; RequestRedirect redirect; DOMString integrity; + + [Func="FetchController::IsEnabled"] + FetchSignal signal; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 7b8e7f0052..fb5900bed6 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -141,7 +141,9 @@ WEBIDL_FILES = [ 'ExtendableMessageEvent.webidl', 'FakePluginTagInit.webidl', 'Fetch.webidl', + 'FetchController.webidl', 'FetchEvent.webidl', + 'FetchSignal.webidl', 'File.webidl', 'FileList.webidl', 'FileMode.webidl', diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index 9a1be4801f..d3e018b623 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -39,6 +39,7 @@ 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.fetchController.enabled", FetchControllerEnabled, FETCHCONTROLLER_ENABLED) WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged) WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) WORKER_PREF("general.appname.override", AppNameOverrideChanged) From 720779362220d263f59393cc369ee877f1379fc8 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:33:28 +0000 Subject: [PATCH 04/13] Issue #1587 - Part 2: Implement controller follow/unfollow --- dom/fetch/FetchController.cpp | 23 +++++- dom/fetch/FetchController.h | 11 ++- dom/fetch/FetchSignal.cpp | 77 ++++++++++++++++++- dom/fetch/FetchSignal.h | 31 ++++++++ .../fetch/file_fetch_controller.html | 68 ++++++++++++++++ 5 files changed, 202 insertions(+), 8 deletions(-) diff --git a/dom/fetch/FetchController.cpp b/dom/fetch/FetchController.cpp index b3d8a4d9c5..2eb40b9800 100644 --- a/dom/fetch/FetchController.cpp +++ b/dom/fetch/FetchController.cpp @@ -12,7 +12,8 @@ namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal, + mFollowingSignal) NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) @@ -97,13 +98,29 @@ FetchController::Abort() void FetchController::Follow(FetchSignal& aSignal) { - // TODO + FetchSignal::Follower::Follow(&aSignal); } void FetchController::Unfollow(FetchSignal& aSignal) { - // TODO + if (mFollowingSignal != &aSignal) { + return; + } + + FetchSignal::Follower::Unfollow(); +} + +FetchSignal* +FetchController::Following() const +{ + return mFollowingSignal; +} + +void +FetchController::Aborted() +{ + Abort(); } } // dom namespace diff --git a/dom/fetch/FetchController.h b/dom/fetch/FetchController.h index 854c6f9743..7a0132dca2 100644 --- a/dom/fetch/FetchController.h +++ b/dom/fetch/FetchController.h @@ -8,6 +8,7 @@ #define mozilla_dom_FetchController_h #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/FetchSignal.h" #include "nsCycleCollectionParticipant.h" #include "nsWrapperCache.h" #include "mozilla/ErrorResult.h" @@ -16,10 +17,9 @@ namespace mozilla { namespace dom { -class FetchSignal; - class FetchController final : public nsISupports , public nsWrapperCache + , public FetchSignal::Follower { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -51,6 +51,13 @@ public: void Unfollow(FetchSignal& aSignal); + FetchSignal* + Following() const; + + // FetchSignal::Follower + + void Aborted() override; + private: ~FetchController() = default; diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp index 4395dbcf2f..1924263e80 100644 --- a/dom/fetch/FetchSignal.cpp +++ b/dom/fetch/FetchSignal.cpp @@ -16,12 +16,12 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) @@ -55,6 +55,11 @@ FetchSignal::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; @@ -65,7 +70,73 @@ FetchSignal::Abort() Event::Constructor(this, NS_LITERAL_STRING("abort"), init); event->SetTrusted(true); - DispatchDOMEvent(nullptr, event, nullptr, nullptr); + bool dummy; + DispatchEvent(event, &dummy); +} + +void +FetchSignal::AddFollower(FetchSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + if (!mFollowers.Contains(aFollower)) { + mFollowers.AppendElement(aFollower); + } +} + +void +FetchSignal::RemoveFollower(FetchSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + mFollowers.RemoveElement(aFollower); +} + +bool +FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + + if (aFollower == mController) { + return false; + } + + FetchSignal* following = mController->Following(); + if (!following) { + return true; + } + + return following->CanAcceptFollower(aFollower); +} + +// FetchSignal::Follower +// ---------------------------------------------------------------------------- + +FetchSignal::Follower::~Follower() +{ + Unfollow(); +} + +void +FetchSignal::Follower::Follow(FetchSignal* aSignal) +{ + MOZ_DIAGNOSTIC_ASSERT(aSignal); + + if (!aSignal->CanAcceptFollower(this)) { + return; + } + + Unfollow(); + + mFollowingSignal = aSignal; + aSignal->AddFollower(this); +} + +void +FetchSignal::Follower::Unfollow() +{ + if (mFollowingSignal) { + mFollowingSignal->RemoveFollower(this); + mFollowingSignal = nullptr; + } } } // dom namespace diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h index 5bb16b8348..5d2f13c689 100644 --- a/dom/fetch/FetchSignal.h +++ b/dom/fetch/FetchSignal.h @@ -13,10 +13,29 @@ namespace mozilla { namespace dom { class FetchController; +class FetchSignal; class FetchSignal final : public DOMEventTargetHelper { public: + // This class must be implemented by objects who want to follow a FetchSignal. + class Follower + { + public: + virtual void Aborted() = 0; + + protected: + virtual ~Follower(); + + void + Follow(FetchSignal* aSignal); + + void + Unfollow(); + + RefPtr mFollowingSignal; + }; + NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) @@ -33,11 +52,23 @@ public: IMPL_EVENT_HANDLER(abort); + void + AddFollower(Follower* aFollower); + + void + RemoveFollower(Follower* aFollower); + + bool + CanAcceptFollower(Follower* aFollower) const; + private: ~FetchSignal() = default; RefPtr mController; + // Raw pointers. Follower unregisters itself in the DTOR. + nsTArray mFollowers; + bool mAborted; }; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 6efa2fe0ac..791d21b2b8 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -21,8 +21,76 @@ function testWebIDL() { 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 testFollowingOurself() { + // Let's follow ourself + var fc = new FetchController(); + fc.follow(fc.signal); + + fc.abort(); + is(fc.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testFollowingOther() { + // Let's follow another one + var fc1 = new FetchController(); + var fc2 = new FetchController(); + fc1.follow(fc2.signal); + + fc2.abort(); + + is(fc1.signal.aborted, true, "Signal is aborted"); + is(fc2.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testFollowingLoop() { + // fc1 -> fc2 -> fc3 -> fc1 + var fc1 = new FetchController(); + var fc2 = new FetchController(); + var fc3 = new FetchController(); + fc1.follow(fc2.signal); + fc2.follow(fc3.signal); + fc3.follow(fc1.signal); + + fc3.abort(); + + is(fc1.signal.aborted, true, "Signal is aborted"); + is(fc2.signal.aborted, true, "Signal is aborted"); + is(fc3.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(); +} + var steps = [ testWebIDL, + testUpdateData, + testFollowingOurself, + testFollowingOther, + testFollowingLoop, + testAbortEvent, ]; function next() { From 78ff97aaf3bb81a3f129932904357c546f5bf98a Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:51:44 +0000 Subject: [PATCH 05/13] Issue #1587 - Part 3: Hook FetchSignal up to the Fetch API --- dom/fetch/Fetch.cpp | 158 ++++++++++++++++-- dom/fetch/FetchDriver.cpp | 45 ++++- dom/fetch/FetchDriver.h | 29 +++- dom/fetch/FetchSignal.cpp | 8 + dom/fetch/FetchSignal.h | 1 + .../fetch/file_fetch_controller.html | 81 +++++++++ 6 files changed, 292 insertions(+), 30 deletions(-) diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index f944352e3b..11e93205ce 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -52,41 +52,137 @@ namespace dom { using namespace workers; +// This class helps the proxying of FetchSignal changes cross threads. +class FetchSignalProxy final : public FetchSignal::Follower +{ + // This is created and released on the main-thread. + RefPtr mSignalMainThread; + + // This value is used only for the creation of FetchSignal on the + // main-thread. They are not updated. + const bool mAborted; + + // This runnable propagates changes from the FetchSignal on workers to the + // FetchSignal on main-thread. + class FetchSignalProxyRunnable final : public Runnable + { + RefPtr mProxy; + + public: + explicit FetchSignalProxyRunnable(FetchSignalProxy* aProxy) + : mProxy(aProxy) + {} + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + FetchSignal* signal = mProxy->GetOrCreateSignalForMainThread(); + signal->Abort(); + return NS_OK; + } + }; + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchSignalProxy) + + explicit FetchSignalProxy(FetchSignal* aSignal) + : mAborted(aSignal->Aborted()) + { + Follow(aSignal); + } + + void + Aborted() override + { + RefPtr runnable = + new FetchSignalProxyRunnable(this); + NS_DispatchToMainThread(runnable); + } + + FetchSignal* + GetOrCreateSignalForMainThread() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!mSignalMainThread) { + mSignalMainThread = new FetchSignal(mAborted); + } + return mSignalMainThread; + } + + void + Shutdown() + { + Unfollow(); + } + +private: + ~FetchSignalProxy() + { + NS_ReleaseOnMainThread(mSignalMainThread.forget()); + } +}; + class WorkerFetchResolver final : public FetchDriverObserver { friend class MainThreadFetchRunnable; + friend class WorkerFetchResponseEndBase; friend class WorkerFetchResponseEndRunnable; friend class WorkerFetchResponseRunnable; RefPtr mPromiseProxy; + RefPtr mSignalProxy; + public: // Returns null if worker is shutting down. static already_AddRefed - Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise) + Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise, + FetchSignal* aSignal) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); - RefPtr proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); + RefPtr proxy = + PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); if (!proxy) { return nullptr; } - RefPtr r = new WorkerFetchResolver(proxy); + RefPtr signalProxy; + if (aSignal) { + signalProxy = new FetchSignalProxy(aSignal); + } + + RefPtr r = new WorkerFetchResolver(proxy, signalProxy); return r.forget(); } + FetchSignal* + GetFetchSignal() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mSignalProxy) { + return nullptr; + } + + return mSignalProxy->GetOrCreateSignalForMainThread(); + } + void OnResponseAvailableInternal(InternalResponse* aResponse) override; void - OnResponseEnd() override; + OnResponseEnd(FetchDriverObserver::EndReason eReason) override; private: - explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy) + WorkerFetchResolver(PromiseWorkerProxy* aProxy, + FetchSignalProxy* aSignalProxy) : mPromiseProxy(aProxy) + , mSignalProxy(aSignalProxy) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); + } ~WorkerFetchResolver() @@ -115,8 +211,12 @@ public: mDocument = aDocument; } - virtual void OnResponseEnd() override + void OnResponseEnd(FetchDriverObserver::EndReason aReason) override { + if (aReason == eAborted) { + mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + FlushConsoleReport(); } @@ -170,9 +270,11 @@ public: fetch->SetWorkerScript(spec); } + RefPtr signal = mResolver->GetFetchSignal(); + // ...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 +312,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr r = request->GetInternalRequest(); + RefPtr signal; + if (aInit.mSignal.WasPassed()) { + signal = &aInit.mSignal.Value(); + // Let's FetchDriver to deal with an already aborted signal. + } + if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr doc; @@ -240,7 +348,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr 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 +360,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, r->SetSkipServiceWorker(); } - RefPtr resolver = WorkerFetchResolver::Create(worker, p); + RefPtr resolver = WorkerFetchResolver::Create(worker, p, signal); if (!resolver) { NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker"); aRv.Throw(NS_ERROR_DOM_ABORT_ERR); @@ -306,6 +414,7 @@ public: , mResolver(aResolver) , mInternalResponse(aResponse) { + MOZ_ASSERT(mResolver); } bool @@ -332,9 +441,13 @@ public: class WorkerFetchResponseEndBase { RefPtr mPromiseProxy; + RefPtr mSignalProxy; + public: - explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : mPromiseProxy(aPromiseProxy) + , mSignalProxy(aSignalProxy) { MOZ_ASSERT(mPromiseProxy); } @@ -344,7 +457,16 @@ public: { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr promise = mPromiseProxy->WorkerPromise(); + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + mPromiseProxy->CleanUp(); + + if (mSignalProxy) { + mSignalProxy->Shutdown(); + mSignalProxy = nullptr; + } } }; @@ -352,9 +474,10 @@ class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { public: - explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy) + , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) { } @@ -379,9 +502,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr , public WorkerFetchResponseEndBase { public: - explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy) + , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) { } @@ -415,7 +539,7 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) } void -WorkerFetchResolver::OnResponseEnd() +WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); @@ -426,11 +550,11 @@ WorkerFetchResolver::OnResponseEnd() FlushConsoleReport(); RefPtr r = - new WorkerFetchResponseEndRunnable(mPromiseProxy); + new WorkerFetchResponseEndRunnable(mPromiseProxy, mSignalProxy); if (!r->Dispatch()) { RefPtr cr = - new WorkerFetchResponseEndControlRunnable(mPromiseProxy); + new WorkerFetchResponseEndControlRunnable(mPromiseProxy, mSignalProxy); // 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. diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 6294b0dc5c..448ec64cd5 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -67,7 +67,7 @@ FetchDriver::~FetchDriver() } nsresult -FetchDriver::Fetch(FetchDriverObserver* aObserver) +FetchDriver::Fetch(FetchSignal* 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 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 @@ -433,9 +443,11 @@ FetchDriver::FailWithNetworkError() #ifdef DEBUG mResponseAvailableCalled = true; #endif - mObserver->OnResponseEnd(); + mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking); mObserver = nullptr; } + + mChannel = nullptr; } namespace { @@ -777,10 +789,11 @@ FetchDriver::OnStopRequest(nsIRequest* aRequest, #endif } - mObserver->OnResponseEnd(); + mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking); mObserver = nullptr; } + mChannel = nullptr; return NS_OK; } @@ -921,5 +934,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 diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index f74298a48c..0ca9a34ee6 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -12,6 +12,7 @@ #include "nsIStreamListener.h" #include "nsIThreadRetargetableStreamListener.h" #include "mozilla/ConsoleReportCollector.h" +#include "mozilla/dom/FetchSignal.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,7 @@ public: } virtual void FlushConsoleReport() = 0; + protected: virtual ~FetchDriverObserver() { }; @@ -72,7 +81,8 @@ private: class FetchDriver final : public nsIStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, - public nsIThreadRetargetableStreamListener + public nsIThreadRetargetableStreamListener, + public FetchSignal::Follower { public: NS_DECL_ISUPPORTS @@ -82,9 +92,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(FetchSignal* aSignal, + FetchDriverObserver* aObserver); void SetDocument(nsIDocument* aDocument); @@ -96,6 +109,11 @@ public: mWorkerScript = aWorkerScirpt; } + // FetchSignal::Follower + + void + Aborted() override; + private: nsCOMPtr mPrincipal; nsCOMPtr mLoadGroup; @@ -104,6 +122,7 @@ private: nsCOMPtr mPipeOutputStream; RefPtr mObserver; nsCOMPtr mDocument; + nsCOMPtr mChannel; nsAutoPtr mSRIDataVerifier; SRIMetadata mSRIMetadata; nsCString mWorkerScript; diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp index 1924263e80..07ad6b53d2 100644 --- a/dom/fetch/FetchSignal.cpp +++ b/dom/fetch/FetchSignal.cpp @@ -37,6 +37,10 @@ FetchSignal::FetchSignal(FetchController* aController, , mAborted(aAborted) {} +FetchSignal::FetchSignal(bool aAborted) + : mAborted(aAborted) +{} + JSObject* FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { @@ -95,6 +99,10 @@ FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const { MOZ_DIAGNOSTIC_ASSERT(aFollower); + if (!mController) { + return true; + } + if (aFollower == mController) { return false; } diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h index 5d2f13c689..4970f03de4 100644 --- a/dom/fetch/FetchSignal.h +++ b/dom/fetch/FetchSignal.h @@ -40,6 +40,7 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) FetchSignal(FetchController* aController, bool aAborted); + explicit FetchSignal(bool aAborted); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 791d21b2b8..026ff16a87 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -84,13 +84,94 @@ function testAbortEvent() { fc.abort(); } +function testAbortedFetch() { + var fc = new FetchController(); + fc.abort(); + + fetch('data:,foo', { 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('data:,foo', { 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() { + function worker() { + var fc = new FetchController(); + fc.abort(); + + fetch('data:,foo', { signal: fc.signal }).then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); + } + + var str = worker.toString(); + var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); + var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); + var w = new Worker(url); + w.onmessage = function(e) { + ok(e.data, "Abort + Fetch works in workers"); + next(); + } +} + +function testWorkerFetchAndAbort() { + function worker() { + var fc = new FetchController(); + + var p = fetch('data:,foo', { signal: fc.signal }); + fc.abort(); + + p.then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); + } + + var str = worker.toString(); + var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); + var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); + var w = new Worker(url); + w.onmessage = function(e) { + ok(e.data, "Abort + Fetch works in workers"); + next(); + } +} + var steps = [ + // Simple stuff testWebIDL, testUpdateData, + + // Following algorithm testFollowingOurself, testFollowingOther, testFollowingLoop, + + // Event propagation testAbortEvent, + + // fetch + signaling + testAbortedFetch, + testFetchAndAbort, + testWorkerAbortedFetch, + testWorkerFetchAndAbort, ]; function next() { From 6722d7f4b4881b67a85749c56fca32f1adac5d9e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 11 Jun 2020 08:22:04 +0000 Subject: [PATCH 06/13] Issue #1587 - Part 4: Implement FetchObserver --- dom/base/nsGkAtomList.h | 2 + dom/fetch/FetchObserver.cpp | 66 +++++++++++++++++++ dom/fetch/FetchObserver.h | 46 +++++++++++++ dom/fetch/moz.build | 2 + .../mochitest/fetch/file_fetch_observer.html | 33 ++++++++++ dom/tests/mochitest/fetch/mochitest.ini | 2 + .../mochitest/fetch/test_fetch_observer.html | 40 +++++++++++ dom/webidl/FetchObserver.webidl | 27 ++++++++ dom/webidl/Request.webidl | 3 + dom/webidl/moz.build | 1 + dom/workers/WorkerPrefs.h | 1 + 11 files changed, 223 insertions(+) create mode 100644 dom/fetch/FetchObserver.cpp create mode 100644 dom/fetch/FetchObserver.h create mode 100644 dom/tests/mochitest/fetch/file_fetch_observer.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_observer.html create mode 100644 dom/webidl/FetchObserver.webidl diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 35963afd7b..371c9fca3a 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -910,7 +910,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") diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp new file mode 100644 index 0000000000..bc8c6fc2b5 --- /dev/null +++ b/dom/fetch/FetchObserver.cpp @@ -0,0 +1,66 @@ +/* -*- 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" + +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, + FetchState aState) + : DOMEventTargetHelper(aGlobal) + , mState(aState) +{} + +JSObject* +FetchObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchObserverBinding::Wrap(aCx, this, aGivenProto); +} + +FetchState +FetchObserver::State() const +{ + return mState; +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h new file mode 100644 index 0000000000..81f8e7b09b --- /dev/null +++ b/dom/fetch/FetchObserver.h @@ -0,0 +1,46 @@ +/* -*- 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" + +namespace mozilla { +namespace dom { + +class FetchObserver final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchObserver, DOMEventTargetHelper) + + static bool + IsEnabled(JSContext* aCx, JSObject* aGlobal); + + FetchObserver(nsIGlobalObject* aGlobal, FetchState aState); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + FetchState + State() const; + + IMPL_EVENT_HANDLER(statechange); + IMPL_EVENT_HANDLER(requestprogress); + IMPL_EVENT_HANDLER(responseprogress); + +private: + ~FetchObserver() = default; + + FetchState mState; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchObserver_h diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index 757f857f28..82fd991732 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -10,6 +10,7 @@ EXPORTS.mozilla.dom += [ 'FetchController.h', 'FetchDriver.h', 'FetchIPCTypes.h', + 'FetchObserver.h', 'FetchSignal.h', 'FetchUtil.h', 'Headers.h', @@ -31,6 +32,7 @@ SOURCES += [ 'ChannelInfo.cpp', 'FetchController.cpp', 'FetchDriver.cpp', + 'FetchObserver.cpp', 'FetchSignal.cpp', 'FetchUtil.cpp', 'Headers.cpp', diff --git a/dom/tests/mochitest/fetch/file_fetch_observer.html b/dom/tests/mochitest/fetch/file_fetch_observer.html new file mode 100644 index 0000000000..97af584ec0 --- /dev/null +++ b/dom/tests/mochitest/fetch/file_fetch_observer.html @@ -0,0 +1,33 @@ + diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 6dba08f982..60fa454f4d 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -5,6 +5,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 @@ -49,6 +50,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] diff --git a/dom/tests/mochitest/fetch/test_fetch_observer.html b/dom/tests/mochitest/fetch/test_fetch_observer.html new file mode 100644 index 0000000000..2b6c0362d2 --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_observer.html @@ -0,0 +1,40 @@ + + + + + Test FetchObserver + + + + + + + + diff --git a/dom/webidl/FetchObserver.webidl b/dom/webidl/FetchObserver.webidl new file mode 100644 index 0000000000..eecd67e668 --- /dev/null +++ b/dom/webidl/FetchObserver.webidl @@ -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; +}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index 00497456ab..8c6e33da33 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -50,6 +50,9 @@ dictionary RequestInit { [Func="FetchController::IsEnabled"] FetchSignal signal; + + [Func="FetchObserver::IsEnabled"] + ObserverCallback observe; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index fb5900bed6..b54086b716 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -143,6 +143,7 @@ WEBIDL_FILES = [ 'Fetch.webidl', 'FetchController.webidl', 'FetchEvent.webidl', + 'FetchObserver.webidl', 'FetchSignal.webidl', 'File.webidl', 'FileList.webidl', diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index d3e018b623..374f41ecf8 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -40,6 +40,7 @@ WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTC 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.fetchController.enabled", FetchControllerEnabled, FETCHCONTROLLER_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) From 3d2ca85eddb9f8ca15595a80c0498ff81b3a3a51 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 11 Jun 2020 08:51:07 +0000 Subject: [PATCH 07/13] Issue #1587 - Part 5: Hook FetchObserver up to the Fetch API --- dom/fetch/Fetch.cpp | 170 ++++++++++++++---- dom/fetch/FetchDriver.cpp | 37 ++++ dom/fetch/FetchDriver.h | 2 + dom/fetch/FetchObserver.cpp | 57 +++++- dom/fetch/FetchObserver.h | 10 +- .../fetch/file_fetch_controller.html | 40 +---- .../mochitest/fetch/file_fetch_observer.html | 115 +++++++++++- dom/tests/mochitest/fetch/mochitest.ini | 2 + dom/tests/mochitest/fetch/slow.sjs | 11 ++ .../mochitest/fetch/test_fetch_observer.html | 3 +- .../fetch/worker_fetch_controller.js | 27 +++ 11 files changed, 399 insertions(+), 75 deletions(-) create mode 100644 dom/tests/mochitest/fetch/slow.sjs create mode 100644 dom/tests/mochitest/fetch/worker_fetch_controller.js diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 11e93205ce..04aa7fd915 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -39,6 +39,7 @@ #include "mozilla/dom/URLSearchParams.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "FetchObserver.h" #include "InternalRequest.h" #include "InternalResponse.h" @@ -126,18 +127,20 @@ private: class WorkerFetchResolver final : public FetchDriverObserver { friend class MainThreadFetchRunnable; + friend class WorkerDataAvailableRunnable; friend class WorkerFetchResponseEndBase; friend class WorkerFetchResponseEndRunnable; friend class WorkerFetchResponseRunnable; RefPtr mPromiseProxy; RefPtr mSignalProxy; + RefPtr mFetchObserver; public: // Returns null if worker is shutting down. static already_AddRefed Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise, - FetchSignal* aSignal) + FetchSignal* aSignal, FetchObserver* aObserver) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); @@ -152,7 +155,8 @@ public: signalProxy = new FetchSignalProxy(aSignal); } - RefPtr r = new WorkerFetchResolver(proxy, signalProxy); + RefPtr r = + new WorkerFetchResolver(proxy, signalProxy, aObserver); return r.forget(); } @@ -174,15 +178,19 @@ public: void OnResponseEnd(FetchDriverObserver::EndReason eReason) override; + void + OnDataAvailable() override; + private: WorkerFetchResolver(PromiseWorkerProxy* aProxy, - FetchSignalProxy* aSignalProxy) + FetchSignalProxy* aSignalProxy, + FetchObserver* aObserver) : mPromiseProxy(aProxy) , mSignalProxy(aSignalProxy) + , mFetchObserver(aObserver) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); - } ~WorkerFetchResolver() @@ -196,12 +204,16 @@ class MainThreadFetchResolver final : public FetchDriverObserver { RefPtr mPromise; RefPtr mResponse; + RefPtr mFetchObserver; nsCOMPtr mDocument; NS_DECL_OWNINGTHREAD public: - explicit MainThreadFetchResolver(Promise* aPromise); + MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver) + : mPromise(aPromise) + , mFetchObserver(aObserver) + {} void OnResponseAvailableInternal(InternalResponse* aResponse) override; @@ -217,9 +229,14 @@ public: mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } + mFetchObserver = nullptr; + FlushConsoleReport(); } + void + OnDataAvailable() override; + private: ~MainThreadFetchResolver(); @@ -318,6 +335,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, // Let's FetchDriver to deal with an already aborted signal. } + RefPtr observer; + if (aInit.mObserve.WasPassed()) { + observer = new FetchObserver(aGlobal, signal); + aInit.mObserve.Value().HandleEvent(*observer); + } + if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr doc; @@ -344,7 +367,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, } } - RefPtr resolver = new MainThreadFetchResolver(p); + RefPtr resolver = + new MainThreadFetchResolver(p, observer); RefPtr fetch = new FetchDriver(r, principal, loadGroup); fetch->SetDocument(doc); resolver->SetDocument(doc); @@ -360,7 +384,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, r->SetSkipServiceWorker(); } - RefPtr resolver = WorkerFetchResolver::Create(worker, p, signal); + RefPtr 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); @@ -374,11 +399,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, return p.forget(); } -MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise) - : mPromise(aPromise) -{ -} - void MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { @@ -386,16 +406,39 @@ MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse AssertIsOnMainThread(); if (aResponse->Type() != ResponseType::Error) { + if (mFetchObserver) { + mFetchObserver->SetState(FetchState::Complete); + } + nsCOMPtr go = mPromise->GetParentObject(); mResponse = new Response(go, aResponse); mPromise->MaybeResolve(mResponse); } else { + if (mFetchObserver) { + mFetchObserver->SetState(FetchState::Errored); + } + ErrorResult result; result.ThrowTypeError(); 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); @@ -426,10 +469,18 @@ public: RefPtr promise = mResolver->mPromiseProxy->WorkerPromise(); if (mInternalResponse->Type() != ResponseType::Error) { + if (mResolver->mFetchObserver) { + mResolver->mFetchObserver->SetState(FetchState::Complete); + } + RefPtr global = aWorkerPrivate->GlobalScope(); RefPtr response = new Response(global, mInternalResponse); promise->MaybeResolve(response); } else { + if (mResolver->mFetchObserver) { + mResolver->mFetchObserver->SetState(FetchState::Errored); + } + ErrorResult result; result.ThrowTypeError(); promise->MaybeReject(result); @@ -438,18 +489,42 @@ public: } }; +class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable +{ + RefPtr 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 mPromiseProxy; - RefPtr mSignalProxy; +protected: + RefPtr mResolver; public: - WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : mPromiseProxy(aPromiseProxy) - , mSignalProxy(aSignalProxy) + explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver) + : mResolver(aResolver) { - MOZ_ASSERT(mPromiseProxy); + MOZ_ASSERT(aResolver); } void @@ -458,14 +533,13 @@ public: MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); - RefPtr promise = mPromiseProxy->WorkerPromise(); - promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + mResolver->mPromiseProxy->CleanUp(); - mPromiseProxy->CleanUp(); + mResolver->mFetchObserver = nullptr; - if (mSignalProxy) { - mSignalProxy->Shutdown(); - mSignalProxy = nullptr; + if (mResolver->mSignalProxy) { + mResolver->mSignalProxy->Shutdown(); + mResolver->mSignalProxy = nullptr; } } }; @@ -473,17 +547,26 @@ public: class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { + FetchDriverObserver::EndReason mReason; + public: - WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) + 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 = mResolver->mPromiseProxy->WorkerPromise(); + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + WorkerRunInternal(aWorkerPrivate); return true; } @@ -502,10 +585,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr , public WorkerFetchResponseEndBase { public: - WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) + WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate, + WorkerFetchResolver* aResolver) + : MainThreadWorkerControlRunnable(aWorkerPrivate) + , WorkerFetchResponseEndBase(aResolver) { } @@ -538,6 +621,21 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) } } +void +WorkerFetchResolver::OnDataAvailable() +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return; + } + + RefPtr r = + new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this); + Unused << r->Dispatch(); +} + void WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) { @@ -550,11 +648,13 @@ WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) FlushConsoleReport(); RefPtr r = - new WorkerFetchResponseEndRunnable(mPromiseProxy, mSignalProxy); + new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(), + this, aReason); if (!r->Dispatch()) { RefPtr cr = - new WorkerFetchResponseEndControlRunnable(mPromiseProxy, mSignalProxy); + 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. diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 448ec64cd5..e8d726ce17 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -667,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 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, @@ -678,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 = 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); diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index 0ca9a34ee6..e942aa2424 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -67,6 +67,8 @@ public: virtual void FlushConsoleReport() = 0; + virtual void OnDataAvailable() = 0; + protected: virtual ~FetchDriverObserver() { }; diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp index bc8c6fc2b5..982f0ad494 100644 --- a/dom/fetch/FetchObserver.cpp +++ b/dom/fetch/FetchObserver.cpp @@ -6,6 +6,7 @@ #include "FetchObserver.h" #include "WorkerPrivate.h" +#include "mozilla/dom/Event.h" namespace mozilla { namespace dom { @@ -45,10 +46,14 @@ FetchObserver::IsEnabled(JSContext* aCx, JSObject* aGlobal) } FetchObserver::FetchObserver(nsIGlobalObject* aGlobal, - FetchState aState) + FetchSignal* aSignal) : DOMEventTargetHelper(aGlobal) - , mState(aState) -{} + , mState(FetchState::Requesting) +{ + if (aSignal) { + Follow(aSignal); + } +} JSObject* FetchObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) @@ -62,5 +67,51 @@ 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::Constructor(this, NS_LITERAL_STRING("statechange"), init); + event->SetTrusted(true); + + bool dummy; + DispatchEvent(event, &dummy); +} + } // dom namespace } // mozilla namespace diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h index 81f8e7b09b..45adf2ba1e 100644 --- a/dom/fetch/FetchObserver.h +++ b/dom/fetch/FetchObserver.h @@ -9,11 +9,13 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/dom/FetchObserverBinding.h" +#include "mozilla/dom/FetchSignal.h" namespace mozilla { namespace dom { class FetchObserver final : public DOMEventTargetHelper + , public FetchSignal::Follower { public: NS_DECL_ISUPPORTS_INHERITED @@ -22,7 +24,7 @@ public: static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); - FetchObserver(nsIGlobalObject* aGlobal, FetchState aState); + FetchObserver(nsIGlobalObject* aGlobal, FetchSignal* aSignal); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -34,6 +36,12 @@ public: IMPL_EVENT_HANDLER(requestprogress); IMPL_EVENT_HANDLER(responseprogress); + void + Aborted() override; + + void + SetState(FetchState aState); + private: ~FetchObserver() = default; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 026ff16a87..e4137aac9e 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -88,7 +88,7 @@ function testAbortedFetch() { var fc = new FetchController(); fc.abort(); - fetch('data:,foo', { signal: fc.signal }).then(() => { + 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"); @@ -98,7 +98,7 @@ function testAbortedFetch() { function testFetchAndAbort() { var fc = new FetchController(); - var p = fetch('data:,foo', { signal: fc.signal }); + var p = fetch('slow.sjs', { signal: fc.signal }); fc.abort(); p.then(() => { @@ -109,49 +109,21 @@ function testFetchAndAbort() { } function testWorkerAbortedFetch() { - function worker() { - var fc = new FetchController(); - fc.abort(); - - fetch('data:,foo', { signal: fc.signal }).then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); - } - - var str = worker.toString(); - var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); - var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); - var w = new Worker(url); + 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() { - function worker() { - var fc = new FetchController(); - - var p = fetch('data:,foo', { signal: fc.signal }); - fc.abort(); - - p.then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); - } - - var str = worker.toString(); - var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); - var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); - var w = new Worker(url); + 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 = [ diff --git a/dom/tests/mochitest/fetch/file_fetch_observer.html b/dom/tests/mochitest/fetch/file_fetch_observer.html index 97af584ec0..a172a18dc1 100644 --- a/dom/tests/mochitest/fetch/file_fetch_observer.html +++ b/dom/tests/mochitest/fetch/file_fetch_observer.html @@ -10,12 +10,125 @@ function is(a, b, msg) { function testObserver() { ok("FetchObserver" in self, "We have a FetchObserver prototype"); - fetch('data:,foo', { observe: o => { + 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 FetchController(); + + 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 FetchController(); + + 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 FetchController(); + + 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 FetchController(); + + 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() { diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 60fa454f4d..7493ede505 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -17,11 +17,13 @@ support-files = reroute.html reroute.js reroute.js^headers^ + slow.sjs sw_reroute.js empty.js empty.js^headers^ worker_temporaryFileBlob.js common_temporaryFileBlob.js + worker_fetch_controller.js !/dom/xhr/tests/file_XHR_binary1.bin !/dom/xhr/tests/file_XHR_binary1.bin^headers^ !/dom/xhr/tests/file_XHR_binary2.bin diff --git a/dom/tests/mochitest/fetch/slow.sjs b/dom/tests/mochitest/fetch/slow.sjs new file mode 100644 index 0000000000..feab0f1fce --- /dev/null +++ b/dom/tests/mochitest/fetch/slow.sjs @@ -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); +} diff --git a/dom/tests/mochitest/fetch/test_fetch_observer.html b/dom/tests/mochitest/fetch/test_fetch_observer.html index 2b6c0362d2..2af86977c0 100644 --- a/dom/tests/mochitest/fetch/test_fetch_observer.html +++ b/dom/tests/mochitest/fetch/test_fetch_observer.html @@ -12,7 +12,8 @@