From 45d8ff90564b811c0e45d308ef93be70e8b7ce6a Mon Sep 17 00:00:00 2001 From: tester Date: Wed, 23 Mar 2022 20:34:21 +0000 Subject: [PATCH 01/43] Bug 1752326 - Prevent deadlock when stopping V4L2 capture. r=ng This is effectively a cherry-pick of the upstream commit https://webrtc.googlesource.com/src/+/974f6c64388614c332671c98dec8fffc86f35acf . The effected code was a bit different upstream, so there are minor differences. Differential Revision: https://phabricator.services.mozilla.com/D138175 --- .../linux/video_capture_linux.cc | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc b/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc index cdcd3b814e1c..0cab41c4f8df 100644 --- a/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_linux.cc @@ -386,18 +386,6 @@ bool VideoCaptureModuleV4L2::CaptureProcess() { rSet.revents = 0; retVal = poll(&rSet, 1, 1000); - if (retVal < 0 && errno != EINTR) // continue if interrupted - { - // poll failed - return false; - } else if (retVal == 0) { - // poll timed out - return true; - } else if (!(rSet.revents & POLLIN)) { - // not event on camera handle - return true; - } - { MutexLock lock(&capture_lock_); @@ -405,6 +393,18 @@ bool VideoCaptureModuleV4L2::CaptureProcess() { return false; } + if (retVal < 0 && errno != EINTR) // continue if interrupted + { + // poll failed + return false; + } else if (retVal == 0) { + // poll timed out + return true; + } else if (!(rSet.revents & POLLIN)) { + // not event on camera handle + return true; + } + if (_captureStarted) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); From 17e9dc73de14bdd6de56ff92c49656898d99c62b Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Mar 2022 20:58:49 +0000 Subject: [PATCH 02/43] Bug 1734886 - Part 5: Implement InitializeTransformStream r=mgaudet Differential Revision: https://phabricator.services.mozilla.com/D140377 --- dom/streams/ReadableStream.h | 6 + dom/streams/TransformStream.cpp | 262 +++++++++++++++++- dom/streams/TransformStream.h | 17 ++ dom/streams/UnderlyingSinkCallbackHelpers.cpp | 24 +- dom/streams/UnderlyingSinkCallbackHelpers.h | 44 ++- dom/streams/WritableStream.cpp | 29 ++ dom/streams/WritableStream.h | 6 + .../WritableStreamDefaultController.cpp | 10 +- dom/streams/WritableStreamDefaultController.h | 8 +- .../meta/streams/idlharness.any.js.ini | 96 ++++--- .../transform-streams/errors.any.js.ini | 24 +- .../transform-streams/general.any.js.ini | 30 +- .../transform-streams/properties.any.js.ini | 24 -- .../transform-streams/strategies.any.js.ini | 24 +- .../transform-streams/terminate.any.js.ini | 12 + 15 files changed, 495 insertions(+), 121 deletions(-) diff --git a/dom/streams/ReadableStream.h b/dom/streams/ReadableStream.h index 9515672d0646..abef7c521dda 100644 --- a/dom/streams/ReadableStream.h +++ b/dom/streams/ReadableStream.h @@ -189,6 +189,12 @@ MOZ_CAN_RUN_SCRIPT already_AddRefed ReadableStreamCancel( already_AddRefed AcquireReadableStreamDefaultReader(ReadableStream* aStream, ErrorResult& aRv); +MOZ_CAN_RUN_SCRIPT already_AddRefed CreateReadableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSourceAlgorithmsBase* aAlgorithms, + mozilla::Maybe aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, + ErrorResult& aRv); + bool ReadableStreamHasBYOBReader(ReadableStream* aStream); bool ReadableStreamHasDefaultReader(ReadableStream* aStream); diff --git a/dom/streams/TransformStream.cpp b/dom/streams/TransformStream.cpp index 149932a71035..ce907bdb4728 100644 --- a/dom/streams/TransformStream.cpp +++ b/dom/streams/TransformStream.cpp @@ -6,6 +6,9 @@ #include "mozilla/dom/TransformStream.h" +#include "UnderlyingSourceCallbackHelpers.h" +#include "js/TypeDecls.h" +#include "mozilla/dom/Promise.h" #include "mozilla/dom/WritableStream.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/RootedDictionary.h" @@ -16,7 +19,9 @@ namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, mController) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, + mBackpressureChangePromise, mController, + mReadable, mWritable) NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformStream) NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformStream) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStream) @@ -35,6 +40,216 @@ JSObject* TransformStream::WrapObject(JSContext* aCx, return TransformStream_Binding::Wrap(aCx, this, aGivenProto); } +// https://streams.spec.whatwg.org/#initialize-transform-stream +class TransformStreamUnderlyingSinkAlgorithms final + : public UnderlyingSinkAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + TransformStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) + + TransformStreamUnderlyingSinkAlgorithms(Promise* aStartPromise, + TransformStream* aStream) + : mStartPromise(aStartPromise), mStream(aStream) {} + + void StartCallback(JSContext* aCx, + WritableStreamDefaultController& aController, + JS::MutableHandle aRetVal, + ErrorResult& aRv) override { + // Step 1. Let startAlgorithm be an algorithm that returns startPromise. + // (Same as TransformStreamUnderlyingSourceAlgorithms::StartCallback) + aRetVal.setObject(*mStartPromise->PromiseObj()); + } + + already_AddRefed WriteCallback( + JSContext* aCx, JS::Handle aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) override { + // Step 2. Let writeAlgorithm be the following steps, taking a chunk + // argument: + // Step 1. Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, + // chunk). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed AbortCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) override { + // Step 3. Let abortAlgorithm be the following steps, taking a reason + // argument: + // Step 1. Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, + // reason). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed CloseCallback(JSContext* aCx, + ErrorResult& aRv) override { + // Step 4. Let closeAlgorithm be the following steps: + + // Step 1. Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + protected: + ~TransformStreamUnderlyingSinkAlgorithms() override = default; + + private: + RefPtr mStartPromise; + RefPtr mStream; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase, mStartPromise, + mStream) +NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamUnderlyingSinkAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSinkAlgorithms) + +// https://streams.spec.whatwg.org/#initialize-transform-stream +class TransformStreamUnderlyingSourceAlgorithms final + : public UnderlyingSourceAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + TransformStreamUnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase) + + TransformStreamUnderlyingSourceAlgorithms(Promise* aStartPromise, + TransformStream* aStream) + : mStartPromise(aStartPromise), mStream(aStream) {} + + void StartCallback(JSContext* aCx, ReadableStreamController& aController, + JS::MutableHandle aRetVal, + ErrorResult& aRv) override { + // Step 1. Let startAlgorithm be an algorithm that returns startPromise. + // (Same as TransformStreamUnderlyingSinkAlgorithms::StartCallback) + aRetVal.setObject(*mStartPromise->PromiseObj()); + } + + already_AddRefed PullCallback(JSContext* aCx, + ReadableStreamController& aController, + ErrorResult& aRv) override { + // Step 6. Let pullAlgorithm be the following steps: + // Step 1. Return ! TransformStreamDefaultSourcePullAlgorithm(stream). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed CancelCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) override { + // Step 7. Let cancelAlgorithm be the following steps, taking a reason + // argument: + // Step 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, + // reason). + // Step 2. Return a promise resolved with undefined. + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + void ErrorCallback() override {} + + protected: + ~TransformStreamUnderlyingSourceAlgorithms() override = default; + + private: + RefPtr mStartPromise; + RefPtr mStream; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase, + mStartPromise, mStream) +NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( + TransformStreamUnderlyingSourceAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSourceAlgorithms) + +// https://streams.spec.whatwg.org/#transform-stream-set-backpressure +static void TransformStreamSetBackpressure(TransformStream* aStream, + bool aBackpressure, + ErrorResult& aRv) { + // Step 1. Assert: stream.[[backpressure]] is not backpressure. + MOZ_ASSERT(aStream->Backpressure() != aBackpressure); + + // Step 2. If stream.[[backpressureChangePromise]] is not undefined, resolve + // stream.[[backpressureChangePromise]] with undefined. + if (Promise* promise = aStream->BackpressureChangePromise()) { + promise->MaybeResolveWithUndefined(); + } + + // Step 3. Set stream.[[backpressureChangePromise]] to a new promise. + RefPtr promise = Promise::Create(aStream->GetParentObject(), aRv); + if (aRv.Failed()) { + return; + } + aStream->SetBackpressureChangePromise(promise); + + // Step 4. Set stream.[[backpressure]] to backpressure. + aStream->SetBackpressure(aBackpressure); +} + +// https://streams.spec.whatwg.org/#initialize-transform-stream +void TransformStream::Initialize(JSContext* aCx, Promise* aStartPromise, + double aWritableHighWaterMark, + QueuingStrategySize* aWritableSizeAlgorithm, + double aReadableHighWaterMark, + QueuingStrategySize* aReadableSizeAlgorithm, + ErrorResult& aRv) { + // Step 1 - 4 + auto sinkAlgorithms = + MakeRefPtr(aStartPromise, this); + + // Step 5. Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, + // writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark, + // writableSizeAlgorithm). + mWritable = + CreateWritableStream(aCx, MOZ_KnownLive(mGlobal), sinkAlgorithms, + aWritableHighWaterMark, aWritableSizeAlgorithm, aRv); + if (aRv.Failed()) { + return; + } + + // Step 6 - 7 + auto sourceAlgorithms = MakeRefPtr( + aStartPromise, this); + + // Step 8. Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, + // pullAlgorithm, cancelAlgorithm, readableHighWaterMark, + // readableSizeAlgorithm). + mReadable = CreateReadableStream( + aCx, MOZ_KnownLive(mGlobal), sourceAlgorithms, + Some(aReadableHighWaterMark), aReadableSizeAlgorithm, aRv); + if (aRv.Failed()) { + return; + } + + // Step 9. Set stream.[[backpressure]] and + // stream.[[backpressureChangePromise]] to undefined. + mBackpressureChangePromise = nullptr; + + // Step 10. Perform ! TransformStreamSetBackpressure(stream, true). + TransformStreamSetBackpressure(this, true, aRv); + if (aRv.Failed()) { + return; + } + + // Step 11. Set stream.[[controller]] to undefined. + mController = nullptr; +} + // https://streams.spec.whatwg.org/#ts-constructor already_AddRefed TransformStream::Constructor( const GlobalObject& aGlobal, @@ -79,19 +294,35 @@ already_AddRefed TransformStream::Constructor( // Step 5. Let readableHighWaterMark be ? // ExtractHighWaterMark(readableStrategy, 0). - // TODO + double readableHighWaterMark = + ExtractHighWaterMark(aReadableStrategy, 0, aRv); + if (aRv.Failed()) { + return nullptr; + } // Step 6. Let readableSizeAlgorithm be ! // ExtractSizeAlgorithm(readableStrategy). - // TODO + // Note: Callers should recognize nullptr as a callback that returns 1. See + // also WritableStream::Constructor for this design decision. + RefPtr readableSizeAlgorithm = + aReadableStrategy.mSize.WasPassed() ? &aReadableStrategy.mSize.Value() + : nullptr; // Step 7. Let writableHighWaterMark be ? // ExtractHighWaterMark(writableStrategy, 1). - // TODO + double writableHighWaterMark = + ExtractHighWaterMark(aWritableStrategy, 1, aRv); + if (aRv.Failed()) { + return nullptr; + } // Step 8. Let writableSizeAlgorithm be ! // ExtractSizeAlgorithm(writableStrategy). - // TODO + // Note: Callers should recognize nullptr as a callback that returns 1. See + // also WritableStream::Constructor for this design decision. + RefPtr writableSizeAlgorithm = + aWritableStrategy.mSize.WasPassed() ? &aWritableStrategy.mSize.Value() + : nullptr; // Step 9. Let startPromise be a new promise. nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); @@ -104,7 +335,9 @@ already_AddRefed TransformStream::Constructor( // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, // readableSizeAlgorithm). RefPtr transformStream = new TransformStream(global); - // TODO: Init() + transformStream->Initialize( + aGlobal.Context(), startPromise, writableHighWaterMark, + writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm, aRv); if (aRv.Failed()) { return nullptr; } @@ -118,7 +351,22 @@ already_AddRefed TransformStream::Constructor( // Step 12. If transformerDict["start"] exists, then resolve startPromise with // the result of invoking transformerDict["start"] with argument list « // this.[[controller]] » and callback this value transformer. - // TODO + if (transformerDict.mStart.WasPassed()) { + RefPtr callback = transformerDict.mStart.Value(); + RefPtr controller = + transformStream->Controller(); + JS::Rooted retVal(aGlobal.Context()); + callback->Call(transformerObj, *controller, &retVal, aRv, + "Transformer.start", CallbackFunction::eRethrowExceptions); + if (aRv.Failed()) { + return nullptr; + } + + startPromise->MaybeResolve(retVal); + } else { + // Step 13. Otherwise, resolve startPromise with undefined. + startPromise->MaybeResolveWithUndefined(); + } return transformStream.forget(); } diff --git a/dom/streams/TransformStream.h b/dom/streams/TransformStream.h index 8ffd188762f5..2fc47fef947b 100644 --- a/dom/streams/TransformStream.h +++ b/dom/streams/TransformStream.h @@ -29,6 +29,13 @@ class TransformStream final : public nsISupports, public nsWrapperCache { NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransformStream) + // Internal slot accessors + bool Backpressure() const { return mBackpressure; } + void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; } + Promise* BackpressureChangePromise() { return mBackpressureChangePromise; } + void SetBackpressureChangePromise(Promise* aPromise) { + mBackpressureChangePromise = aPromise; + } TransformStreamDefaultController* Controller() { return mController; } void SetController(TransformStreamDefaultController* aController) { mController = aController; @@ -38,6 +45,12 @@ class TransformStream final : public nsISupports, public nsWrapperCache { ~TransformStream(); explicit TransformStream(nsIGlobalObject* aGlobal); + MOZ_CAN_RUN_SCRIPT void Initialize( + JSContext* aCx, Promise* aStartPromise, double aWritableHighWaterMark, + QueuingStrategySize* aWritableSizeAlgorithm, + double aReadableHighWaterMark, + QueuingStrategySize* aReadableSizeAlgorithm, ErrorResult& aRv); + public: nsIGlobalObject* GetParentObject() const { return mGlobal; } JSObject* WrapObject(JSContext* aCx, @@ -58,7 +71,11 @@ class TransformStream final : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; // Internal slots + bool mBackpressure; + RefPtr mBackpressureChangePromise; RefPtr mController; + RefPtr mReadable; + RefPtr mWritable; }; } // namespace mozilla::dom diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.cpp b/dom/streams/UnderlyingSinkCallbackHelpers.cpp index c886fb4895be..6d107692d980 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.cpp +++ b/dom/streams/UnderlyingSinkCallbackHelpers.cpp @@ -9,16 +9,24 @@ using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(UnderlyingSinkAlgorithms, - (mGlobal, mStartCallback, - mWriteCallback, mCloseCallback, - mAbortCallback), - (mUnderlyingSink)) -NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithms) -NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithms) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) +NS_IMPL_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( + UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase, + (mGlobal, mStartCallback, mWriteCallback, mCloseCallback, mAbortCallback), + (mUnderlyingSink)) +NS_IMPL_ADDREF_INHERITED(UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(UnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase) // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink void UnderlyingSinkAlgorithms::StartCallback( diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.h b/dom/streams/UnderlyingSinkCallbackHelpers.h index 3d409d7b503b..5402a2625599 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.h +++ b/dom/streams/UnderlyingSinkCallbackHelpers.h @@ -11,6 +11,7 @@ #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/UnderlyingSinkBinding.h" +#include "nsCycleCollectionParticipant.h" #include "nsISupports.h" #include "nsISupportsImpl.h" @@ -23,11 +24,36 @@ namespace mozilla::dom { class WritableStreamDefaultController; -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink -class UnderlyingSinkAlgorithms : public nsISupports { +class UnderlyingSinkAlgorithmsBase : public nsISupports { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithms) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithmsBase) + + MOZ_CAN_RUN_SCRIPT virtual void StartCallback( + JSContext* aCx, WritableStreamDefaultController& aController, + JS::MutableHandle aRetVal, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed WriteCallback( + JSContext* aCx, JS::Handle aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed CloseCallback( + JSContext* aCx, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed AbortCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) = 0; + + protected: + virtual ~UnderlyingSinkAlgorithmsBase() = default; +}; + +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink +class UnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) UnderlyingSinkAlgorithms(nsIGlobalObject* aGlobal, JS::HandleObject aUnderlyingSink, @@ -58,21 +84,21 @@ class UnderlyingSinkAlgorithms : public nsISupports { MOZ_CAN_RUN_SCRIPT void StartCallback( JSContext* aCx, WritableStreamDefaultController& aController, - JS::MutableHandle aRetVal, ErrorResult& aRv); + JS::MutableHandle aRetVal, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed WriteCallback( JSContext* aCx, JS::Handle aChunk, - WritableStreamDefaultController& aController, ErrorResult& aRv); + WritableStreamDefaultController& aController, ErrorResult& aRv) override; - MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback(JSContext* aCx, - ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback( + JSContext* aCx, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed AbortCallback( JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv); + ErrorResult& aRv) override; protected: - virtual ~UnderlyingSinkAlgorithms() { mozilla::DropJSObjects(this); }; + ~UnderlyingSinkAlgorithms() override { mozilla::DropJSObjects(this); } private: // Virtually const, but are cycle collected diff --git a/dom/streams/WritableStream.cpp b/dom/streams/WritableStream.cpp index 6ddef107c602..669e4cd4b4b4 100644 --- a/dom/streams/WritableStream.cpp +++ b/dom/streams/WritableStream.cpp @@ -699,6 +699,35 @@ AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv) { return writer.forget(); } +// https://streams.spec.whatwg.org/#create-writable-stream +already_AddRefed CreateWritableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, + QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { + // Step 1: Assert: ! IsNonNegativeNumber(highWaterMark) is true. + MOZ_ASSERT(IsNonNegativeNumber(aHighWaterMark)); + + // Step 2: Let stream be a new WritableStream. + // Step 3: Perform ! InitializeWritableStream(stream). + auto stream = MakeRefPtr(aGlobal); + + // Step 4: Let controller be a new WritableStreamDefaultController. + auto controller = + MakeRefPtr(aGlobal, *stream); + + // Step 5: Perform ? SetUpWritableStreamDefaultController(stream, controller, + // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, + // highWaterMark, sizeAlgorithm). + SetUpWritableStreamDefaultController(aCx, stream, controller, aAlgorithms, + aHighWaterMark, aSizeAlgorithm, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // Step 6: Return stream. + return stream.forget(); +} + already_AddRefed WritableStream::GetWriter( ErrorResult& aRv) { return AcquireWritableStreamDefaultWriter(this, aRv); diff --git a/dom/streams/WritableStream.h b/dom/streams/WritableStream.h index 7cba91159eef..4700a819f595 100644 --- a/dom/streams/WritableStream.h +++ b/dom/streams/WritableStream.h @@ -28,6 +28,7 @@ namespace mozilla::dom { class Promise; class WritableStreamDefaultController; class WritableStreamDefaultWriter; +class UnderlyingSinkAlgorithmsBase; class WritableStream : public nsISupports, public nsWrapperCache { public: @@ -180,6 +181,11 @@ class WritableStream : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; }; +MOZ_CAN_RUN_SCRIPT already_AddRefed CreateWritableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, + QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); + inline bool IsWritableStreamLocked(WritableStream* aStream) { return aStream->Locked(); } diff --git a/dom/streams/WritableStreamDefaultController.cpp b/dom/streams/WritableStreamDefaultController.cpp index 89f222660462..8df09749c53b 100644 --- a/dom/streams/WritableStreamDefaultController.cpp +++ b/dom/streams/WritableStreamDefaultController.cpp @@ -91,7 +91,7 @@ already_AddRefed WritableStreamDefaultController::AbortSteps( JSContext* aCx, JS::Handle aReason, ErrorResult& aRv) { // Step 1. Let result be the result of performing this.[[abortAlgorithm]], // passing reason. - RefPtr algorithms = mAlgorithms; + RefPtr algorithms = mAlgorithms; Optional> optionalReason(aCx, aReason); RefPtr abortPromise = algorithms->AbortCallback(aCx, optionalReason, aRv); @@ -126,7 +126,7 @@ WritableStreamDefaultControllerAdvanceQueueIfNeeded( void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithms* aAlgorithms, double aHighWaterMark, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { // Step 1. Assert: stream implements WritableStream. // Step 2. Assert: stream.[[controller]] is undefined. @@ -260,7 +260,8 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessClose( // Step 5. Let sinkClosePromise be the result of performing // controller.[[closeAlgorithm]]. - RefPtr algorithms = aController->GetAlgorithms(); + RefPtr algorithms = + aController->GetAlgorithms(); RefPtr sinkClosePromise = algorithms->CloseCallback(aCx, aRv); if (aRv.Failed()) { return; @@ -303,7 +304,8 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessWrite( // Step 3. Let sinkWritePromise be the result of performing // controller.[[writeAlgorithm]], passing in chunk. - RefPtr algorithms = aController->GetAlgorithms(); + RefPtr algorithms = + aController->GetAlgorithms(); RefPtr sinkWritePromise = algorithms->WriteCallback(aCx, aChunk, *aController, aRv); if (aRv.Failed()) { diff --git a/dom/streams/WritableStreamDefaultController.h b/dom/streams/WritableStreamDefaultController.h index 609c578d45c4..38fc0969b1a0 100644 --- a/dom/streams/WritableStreamDefaultController.h +++ b/dom/streams/WritableStreamDefaultController.h @@ -84,8 +84,8 @@ class WritableStreamDefaultController final : public nsISupports, mStrategySizeAlgorithm = aStrategySizeAlgorithm; } - UnderlyingSinkAlgorithms* GetAlgorithms() { return mAlgorithms; } - void SetAlgorithms(UnderlyingSinkAlgorithms* aAlgorithms) { + UnderlyingSinkAlgorithmsBase* GetAlgorithms() { return mAlgorithms; } + void SetAlgorithms(UnderlyingSinkAlgorithmsBase* aAlgorithms) { mAlgorithms = aAlgorithms; } @@ -128,14 +128,14 @@ class WritableStreamDefaultController final : public nsISupports, double mStrategyHWM = 0.0; RefPtr mStrategySizeAlgorithm; - RefPtr mAlgorithms; + RefPtr mAlgorithms; RefPtr mStream; }; MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithms* aSinkCallbacks, double aHighWaterMark, + UnderlyingSinkAlgorithmsBase* aSinkCallbacks, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultControllerFromUnderlyingSink( diff --git a/testing/web-platform/meta/streams/idlharness.any.js.ini b/testing/web-platform/meta/streams/idlharness.any.js.ini index d668edac00a8..0c924fd72632 100644 --- a/testing/web-platform/meta/streams/idlharness.any.js.ini +++ b/testing/web-platform/meta/streams/idlharness.any.js.ini @@ -12,7 +12,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -35,7 +36,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -118,7 +120,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -177,7 +180,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -236,7 +240,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -271,10 +276,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -289,7 +296,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -689,7 +697,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -712,7 +721,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -795,7 +805,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -854,7 +865,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -913,7 +925,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -948,10 +961,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -966,7 +981,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -1366,7 +1382,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -1389,7 +1406,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -1472,7 +1490,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -1531,7 +1550,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -1590,7 +1610,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -1625,10 +1646,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -1643,7 +1666,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -2043,7 +2067,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -2066,7 +2091,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -2149,7 +2175,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -2208,7 +2235,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -2267,7 +2295,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -2302,10 +2331,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -2320,7 +2351,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: diff --git a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini index b53d537b34eb..c4152600e888 100644 --- a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini @@ -11,9 +11,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -56,6 +53,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.serviceworker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -70,9 +70,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -115,6 +112,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.worker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -129,9 +129,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -174,6 +171,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -188,9 +188,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -232,3 +229,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + + [errored TransformStream should not enqueue new chunks] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini index d3face719e3b..0b163c08e560 100644 --- a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini @@ -50,10 +50,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -109,10 +112,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -168,10 +174,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -227,8 +236,11 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] - expected: FAIL - [Subclassing TransformStream should work] expected: FAIL + + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini index 1c40cb139f22..d79ccd83adc5 100644 --- a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini @@ -1,10 +1,4 @@ [properties.any.worker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -19,12 +13,6 @@ [properties.any.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -39,12 +27,6 @@ [properties.any.sharedworker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -59,12 +41,6 @@ [properties.any.serviceworker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini index 68ca3c853805..669dbfc0b022 100644 --- a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini @@ -14,9 +14,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -26,6 +23,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.serviceworker.html] [writableStrategy highWaterMark should work] @@ -43,9 +43,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -55,6 +52,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.sharedworker.html] [writableStrategy highWaterMark should work] @@ -72,9 +72,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -84,6 +81,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.worker.html] [writableStrategy highWaterMark should work] @@ -101,9 +101,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -112,3 +109,6 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini index cf1b68ddbf43..7536f29dea2b 100644 --- a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini @@ -14,6 +14,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.worker.html] [controller.terminate() should error pipeTo()] @@ -31,6 +34,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.serviceworker.html] [controller.terminate() should error pipeTo()] @@ -48,6 +54,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.html] [controller.terminate() should error pipeTo()] @@ -64,3 +73,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL From d83e9b2723eea155ad127b99d7f2e765c3d4cf8e Mon Sep 17 00:00:00 2001 From: Andrew Creskey Date: Wed, 23 Mar 2022 21:00:14 +0000 Subject: [PATCH 03/43] Bug 1759606 - Support no arguments to Snapshots.stripFragments(url) r=mossop The function Snapshots.stripFragments(url) should gracefully support no arguments (i.e. undefined for the url). Differential Revision: https://phabricator.services.mozilla.com/D140890 --- browser/components/places/Snapshots.jsm | 2 +- .../tests/unit/interactions/test_snapshots_fragments.js | 5 +++++ .../unit/interactions/test_snapshotselection_fragments.js | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/browser/components/places/Snapshots.jsm b/browser/components/places/Snapshots.jsm index 8f803e3c6046..625b3111edd1 100644 --- a/browser/components/places/Snapshots.jsm +++ b/browser/components/places/Snapshots.jsm @@ -295,7 +295,7 @@ const Snapshots = new (class Snapshots { * The url up until, but not including, any fragments */ stripFragments(url) { - return url.split("#")[0]; + return url?.split("#")[0]; } /** diff --git a/browser/components/places/tests/unit/interactions/test_snapshots_fragments.js b/browser/components/places/tests/unit/interactions/test_snapshots_fragments.js index 4745cc367642..6d4de46c5119 100644 --- a/browser/components/places/tests/unit/interactions/test_snapshots_fragments.js +++ b/browser/components/places/tests/unit/interactions/test_snapshots_fragments.js @@ -38,6 +38,11 @@ add_setup(async () => { }); add_task(async function test_stripFragments() { + Assert.equal( + Snapshots.stripFragments(), + undefined, + "stripFragments should handle undefined as an argument" + ); Assert.equal( Snapshots.stripFragments(TEST_FRAGMENT_URL1), TEST_URL1, diff --git a/browser/components/places/tests/unit/interactions/test_snapshotselection_fragments.js b/browser/components/places/tests/unit/interactions/test_snapshotselection_fragments.js index b74012c8844d..f149aea2f4df 100644 --- a/browser/components/places/tests/unit/interactions/test_snapshotselection_fragments.js +++ b/browser/components/places/tests/unit/interactions/test_snapshotselection_fragments.js @@ -2,8 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ /** - * Tests that we adult sites are correctly filtered from snapshot selections - * when requested. + * Test that we correctly handle urls with fragments in snapshot selection */ const TEST_URL1 = "https://example.com/"; From 9c09944c381002edaa940c4a4ed2c8406bdd16c3 Mon Sep 17 00:00:00 2001 From: kernp25 Date: Wed, 23 Mar 2022 21:20:31 +0000 Subject: [PATCH 04/43] Bug 1372100 - Support muted in tabs.create. r=mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D140677 --- .../components/extensions/parent/ext-tabs.js | 4 ++ .../components/extensions/schemas/tabs.json | 5 +++ .../test/browser/browser_ext_tabs_create.js | 45 ++++++++++++++++--- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/browser/components/extensions/parent/ext-tabs.js b/browser/components/extensions/parent/ext-tabs.js index c54a0fd3cf6d..84b8cc260a24 100644 --- a/browser/components/extensions/parent/ext-tabs.js +++ b/browser/components/extensions/parent/ext-tabs.js @@ -802,6 +802,10 @@ this.tabs = class extends ExtensionAPI { tabListener.initializingTabs.add(nativeTab); } + if (createProperties.muted) { + nativeTab.toggleMuteAudio(extension.id); + } + return tabManager.convert(nativeTab, currentTabSize); }); }, diff --git a/browser/components/extensions/schemas/tabs.json b/browser/components/extensions/schemas/tabs.json index e3b212e50ec2..862add6fb037 100644 --- a/browser/components/extensions/schemas/tabs.json +++ b/browser/components/extensions/schemas/tabs.json @@ -612,6 +612,11 @@ "type": "string", "optional": true, "description": "The title used for display if the tab is created in discarded mode." + }, + "muted": { + "type": "boolean", + "optional": true, + "description": "Whether the tab should be muted when created." } } }, diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_create.js b/browser/components/extensions/test/browser/browser_ext_tabs_create.js index a207868ae6d1..b463ba7ad4da 100644 --- a/browser/components/extensions/test/browser/browser_ext_tabs_create.js +++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js @@ -50,6 +50,11 @@ add_task(async function test_create_options() { // 'selected' is marked as unsupported in schema, so we've removed it. // For more details, see bug 1337509 selected: undefined, + mutedInfo: { + muted: false, + extensionId: undefined, + reason: undefined, + }, }; let tests = [ @@ -132,6 +137,26 @@ add_task(async function test_create_options() { )}`, }, }, + { + create: { muted: true }, + result: { + mutedInfo: { + muted: true, + extensionId: browser.runtime.id, + reason: "extension", + }, + }, + }, + { + create: { muted: false }, + result: { + mutedInfo: { + muted: false, + extensionId: undefined, + reason: undefined, + }, + }, + }, ]; async function nextTest() { @@ -182,11 +207,21 @@ add_task(async function test_create_options() { continue; } - browser.test.assertEq( - expected[key], - tab[key], - `Expected value for tab.${key}` - ); + if (key === "mutedInfo") { + for (let key of Object.keys(expected.mutedInfo)) { + browser.test.assertEq( + expected.mutedInfo[key], + tab.mutedInfo[key], + `Expected value for tab.mutedInfo.${key}` + ); + } + } else { + browser.test.assertEq( + expected[key], + tab[key], + `Expected value for tab.${key}` + ); + } } let updated = await updatedPromise; From 583e9dece983904c9ee95cd74e6c701d71aa8ba6 Mon Sep 17 00:00:00 2001 From: Joel Maher Date: Wed, 23 Mar 2022 21:40:23 +0000 Subject: [PATCH 05/43] Bug 1759035 - disable xpcshell tests that fail with socketprocess_networking and fission.autostart=true|false. r=kershaw,application-update-reviewers,nalexander,zombie Differential Revision: https://phabricator.services.mozilla.com/D140841 --- .../newtab/test/xpcshell/xpcshell.ini | 2 + dom/workers/test/xpcshell/xpcshell.ini | 2 + .../antitracking/test/xpcshell/xpcshell.ini | 15 ++++ .../test/xpcshell/xpcshell-content.ini | 30 +++++++- .../test/xpcshell/xpcshell-e10s.ini | 4 ++ .../test/xpcshell/xpcshell-remote.ini | 17 ++++- .../extensions/test/xpcshell/xpcshell.ini | 2 + .../search/tests/xpcshell/xpcshell.ini | 69 +++++++++++++++++++ .../update/tests/unit_aus_update/xpcshell.ini | 20 ++++++ 9 files changed, 157 insertions(+), 4 deletions(-) diff --git a/browser/components/newtab/test/xpcshell/xpcshell.ini b/browser/components/newtab/test/xpcshell/xpcshell.ini index 65e3ef0e3c63..18cb85af2a88 100644 --- a/browser/components/newtab/test/xpcshell/xpcshell.ini +++ b/browser/components/newtab/test/xpcshell/xpcshell.ini @@ -11,6 +11,8 @@ prefs = support-files = ds_layout.json topstories.json +skip-if = + socketprocess_networking # Bug 1759035 [test_AboutNewTab.js] [test_AboutWelcomeAttribution.js] diff --git a/dom/workers/test/xpcshell/xpcshell.ini b/dom/workers/test/xpcshell/xpcshell.ini index a352e4241915..8e1c6154cfa4 100644 --- a/dom/workers/test/xpcshell/xpcshell.ini +++ b/dom/workers/test/xpcshell/xpcshell.ini @@ -17,4 +17,6 @@ firefox-appdir = browser # Disable plugin loading to make it rr able to record and replay this test. prefs = plugin.disable=true +skip-if = + socketprocess_networking # Bug 1759035 diff --git a/toolkit/components/antitracking/test/xpcshell/xpcshell.ini b/toolkit/components/antitracking/test/xpcshell/xpcshell.ini index 13eb639b133f..cc04bd1a1f0f 100644 --- a/toolkit/components/antitracking/test/xpcshell/xpcshell.ini +++ b/toolkit/components/antitracking/test/xpcshell/xpcshell.ini @@ -5,12 +5,16 @@ prefs = [test_cookie_behavior.js] [test_getPartitionKeyFromURL.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_purge_trackers.js] skip-if = win10_2004 # Bug 1718292 [test_purge_trackers_telemetry.js] [test_tracking_db_service.js] skip-if = toolkit == "android" # Bug 1697936 [test_rejectForeignAllowList.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_staticPartition_clientAuthRemember.js] [test_staticPartition_font.js] support-files = @@ -19,9 +23,20 @@ skip-if = apple_silicon # bug 1729551 os == "mac" && bits == 64 && !debug # Bug 1652119 os == "win" && bits == 64 && !debug # Bug 1652119 + socketprocess_networking # Bug 1759035 [test_staticPartition_image.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_staticPartition_authhttp.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_staticPartition_prefetch.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_staticPartition_preload.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_ExceptionListService.js] [test_view_source.js] +skip-if = + socketprocess_networking # Bug 1759035 (not as common on win, perma on linux/osx) diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini index 9d338eb3ebda..0717374ba5e3 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini @@ -1,20 +1,46 @@ [test_ext_i18n.js] skip-if = os == "android" || (os == "win" && debug) || (os == "linux") [test_ext_i18n_css.js] +skip-if = + (socketprocess_networking || fission) && (os == "linux" && debug) # Bug 1759035 [test_ext_contentscript.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_ext_contentscript_about_blank_start.js] [test_ext_contentscript_canvas_tainting.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_contentscript_scriptCreated.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_contentscript_triggeringPrincipal.js] -skip-if = os == "android" || (os == "win" && debug) || tsan # Windows: Bug 1438796, tsan: bug 1612707, Android: Bug 1680132 +skip-if = + os == "android" # Bug 1680132 + (os == "win" && debug) # Bug 1438796 + tsan # Bug 1612707 + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_contentscript_xrays.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_contentScripts_register.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_contexts_gc.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_adoption_with_xrays.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_adoption_with_private_field_xrays.js] skip-if = !nightly_build + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_shadowdom.js] skip-if = ccov && os == 'linux' # bug 1607581 + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_web_accessible_resources.js] -skip-if = apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs +skip-if = + apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 [test_ext_web_accessible_resources_matches.js] +skip-if = + os == "linux" && socketprocess_networking && !fission && debug # Bug 1759035 diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-e10s.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-e10s.ini index 228492d00b07..3b95bdb66e34 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-e10s.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-e10s.ini @@ -19,7 +19,11 @@ prefs = services.settings.default_bucket=nonexistent-bucket-foo [include:xpcshell-common-e10s.ini] +skip-if = + socketprocess_networking # Bug 1759035 [include:xpcshell-content.ini] +skip-if = + socketprocess_networking && fission # Bug 1759035 # Tests that need to run with e10s only must NOT be placed here, # but in xpcshell-common-e10s.ini. diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini index 2ccd9232306a..c05d7730e735 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini @@ -2,7 +2,11 @@ head = head.js head_remote.js head_e10s.js head_telemetry.js head_sync.js head_storage.js tail = firefox-appdir = browser -skip-if = os == "android" +skip-if = + os == "android" + os == "win" && socketprocess_networking && fission # Bug 1759035 + os == "mac" && socketprocess_networking && fission # Bug 1759035 + # I would put linux here, but debug has too many chunks and only runs this manifest, so I need 1 test to pass dupe-manifest = support-files = data/** @@ -19,12 +23,21 @@ prefs = services.settings.default_bucket=nonexistent-bucket-foo [include:xpcshell-common.ini] +skip-if = + os == "linux" && socketprocess_networking # Bug 1759035 [include:xpcshell-common-e10s.ini] +skip-if = + os == "linux" && socketprocess_networking # Bug 1759035 [include:xpcshell-content.ini] - +skip-if = + os == "linux" && socketprocess_networking # Bug 1759035 [test_ext_contentscript_perf_observers.js] # Inexplicably, PerformanceObserver used in the test doesn't fire in non-e10s mode. skip-if = tsan + os == "linux" && socketprocess_networking # Bug 1759035 [test_ext_contentscript_xorigin_frame.js] +skip-if = + os == "linux" && socketprocess_networking # Bug 1759035 [test_WebExtensionContentScript.js] [test_ext_ipcBlob.js] skip-if = os == 'android' && processor == 'x86_64' + os == "linux" && socketprocess_networking # Bug 1759035 diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell.ini b/toolkit/components/extensions/test/xpcshell/xpcshell.ini index 22e5ffe42815..da85c16e799b 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini @@ -71,6 +71,8 @@ head = head.js head_schemas.js [test_ext_schemas_versioned.js] head = head.js head_schemas.js [test_ext_secfetch.js] +skip-if = + socketprocess_networking # Bug 1759035 [test_ext_shared_array_buffer.js] [test_ext_test_mock.js] [test_ext_test_wrapper.js] diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.ini b/toolkit/components/search/tests/xpcshell/xpcshell.ini index 678243bf97c6..837f8a768e38 100644 --- a/toolkit/components/search/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini @@ -70,7 +70,13 @@ support-files = method-extensions/post/manifest.json method-extensions/engines.json [test_defaultEngine_fallback.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_defaultEngine.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_defaultPrivateEngine.js] [test_engine_alias.js] [test_engine_multiple_alias.js] @@ -86,32 +92,65 @@ tag = remotesettings searchmain [test_getSubmission_encoding.js] [test_getSubmission_params.js] [test_identifiers.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_ignorelist_update.js] [test_ignorelist.js] [test_initialization.js] [test_initialization_with_region.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_list_json_locale.js] [test_list_json_no_private_default.js] [test_list_json_searchdefault.js] [test_list_json_searchorder.js] [test_maybereloadengine_order.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_migrateWebExtensionEngine.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_missing_engine.js] [test_multipleIcons.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_nodb_pluschanges.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_notifications.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_opensearch_icon.js] support-files = data/bigIcon.ico data/remoteIcon.ico data/svgIcon.svg +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_opensearch_icons_invalid.js] support-files = opensearch/chromeicon.xml opensearch/resourceicon.xml +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_opensearch_install_errors.js] support-files = opensearch/invalid.xml +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_opensearch_update.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_opensearch.js] support-files = opensearch/mozilla-ns.xml @@ -119,9 +158,15 @@ support-files = opensearch/simple.xml opensearch/suggestion.xml opensearch/suggestion-alternate.xml +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_originalDefaultEngine.js] [test_override_allowlist.js] [test_parseSubmissionURL.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_pref.js] [test_purpose.js] [test_region_params.js] @@ -131,11 +176,26 @@ support-files = [test_remove_engine_notification_box.js] [test_resultDomain.js] [test_save_sorted_engines.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_SearchStaticData.js] [test_searchSuggest_cookies.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_searchSuggest_private.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_searchSuggest.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_selectedEngine.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_sendSubmissionURL.js] [test_settings_broken.js] [test_settings_duplicate.js] @@ -143,6 +203,9 @@ support-files = [test_settings_ignorelist.js] support-files = data/search_ignorelist.json [test_settings_none.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_settings_obsolete.js] [test_settings_persist.js] [test_settings.js] @@ -152,9 +215,15 @@ support-files = data/search_ignorelist.json [test_validate_manifests.js] [test_webextensions_builtin_upgrade.js] [test_webextensions_install.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_webextensions_migrate_to.js] support-files = data/search-migration.json [test_webextensions_normandy_upgrade.js] +skip-if = + debug && socketprocess_networking # Bug 1759035 + (os == "mac" || os == "win") && socketprocess_networking # Bug 1759035 [test_webextensions_startup_remove.js] [test_webextensions_upgrade.js] [test_webextensions_valid.js] diff --git a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini index 83ccc418384a..df39bca8c9fe 100644 --- a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini +++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini @@ -19,8 +19,12 @@ support-files = [ausReadStrings.js] [canCheckForAndCanApplyUpdates.js] [urlConstruction.js] +skip-if = + socketprocess_networking # Bug 1759035 [updateManagerXML.js] [remoteUpdateXML.js] +skip-if = + socketprocess_networking # Bug 1759035 [cleanupDownloadingForOlderAppVersion.js] [cleanupDownloadingForDifferentChannel.js] [cleanupDownloadingForSameVersionAndBuildID.js] @@ -29,10 +33,18 @@ support-files = [cleanupSuccessLogMove.js] [cleanupSuccessLogsFIFO.js] [downloadInterruptedOffline.js] +skip-if = + socketprocess_networking # Bug 1759035 [downloadInterruptedNoRecovery.js] +skip-if = + socketprocess_networking # Bug 1759035 [downloadInterruptedRecovery.js] +skip-if = + socketprocess_networking # Bug 1759035 [downloadResumeForSameAppVersion.js] [languagePackUpdates.js] +skip-if = + socketprocess_networking # Bug 1759035 [updateSyncManager.js] [updateAutoPrefMigrate.js] skip-if = os != 'win' @@ -41,10 +53,18 @@ reason = Update pref migration is currently Windows only skip-if = os != 'win' reason = Update directory migration is currently Windows only [multiUpdate.js] +skip-if = + socketprocess_networking # Bug 1759035 [perInstallationPrefs.js] [onlyDownloadUpdatesThisSession.js] +skip-if = + socketprocess_networking # Bug 1759035 [disableBackgroundUpdatesBackgroundTask.js] +skip-if = + socketprocess_networking # Bug 1759035 [disableBackgroundUpdatesNonBackgroundTask.js] +skip-if = + socketprocess_networking # Bug 1759035 [ensureExperimentToRolloutTransitionPerformed.js] run-if = os == 'win' && appname == 'firefox' reason = Feature is Firefox-specific and Windows-specific. From 014927888b2eda8e2cbb4a288f6ba68c690c3bb3 Mon Sep 17 00:00:00 2001 From: Butkovits Atila Date: Thu, 24 Mar 2022 00:29:41 +0200 Subject: [PATCH 06/43] Backed out changeset 544d6892423d (bug 1734886) for causing failures attransform-stream.html. CLOSED TREE --- dom/streams/ReadableStream.h | 6 - dom/streams/TransformStream.cpp | 262 +----------------- dom/streams/TransformStream.h | 17 -- dom/streams/UnderlyingSinkCallbackHelpers.cpp | 24 +- dom/streams/UnderlyingSinkCallbackHelpers.h | 44 +-- dom/streams/WritableStream.cpp | 29 -- dom/streams/WritableStream.h | 6 - .../WritableStreamDefaultController.cpp | 10 +- dom/streams/WritableStreamDefaultController.h | 8 +- .../meta/streams/idlharness.any.js.ini | 96 +++---- .../transform-streams/errors.any.js.ini | 24 +- .../transform-streams/general.any.js.ini | 36 +-- .../transform-streams/properties.any.js.ini | 24 ++ .../transform-streams/strategies.any.js.ini | 24 +- .../transform-streams/terminate.any.js.ini | 12 - 15 files changed, 124 insertions(+), 498 deletions(-) diff --git a/dom/streams/ReadableStream.h b/dom/streams/ReadableStream.h index abef7c521dda..9515672d0646 100644 --- a/dom/streams/ReadableStream.h +++ b/dom/streams/ReadableStream.h @@ -189,12 +189,6 @@ MOZ_CAN_RUN_SCRIPT already_AddRefed ReadableStreamCancel( already_AddRefed AcquireReadableStreamDefaultReader(ReadableStream* aStream, ErrorResult& aRv); -MOZ_CAN_RUN_SCRIPT already_AddRefed CreateReadableStream( - JSContext* aCx, nsIGlobalObject* aGlobal, - UnderlyingSourceAlgorithmsBase* aAlgorithms, - mozilla::Maybe aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, - ErrorResult& aRv); - bool ReadableStreamHasBYOBReader(ReadableStream* aStream); bool ReadableStreamHasDefaultReader(ReadableStream* aStream); diff --git a/dom/streams/TransformStream.cpp b/dom/streams/TransformStream.cpp index ce907bdb4728..149932a71035 100644 --- a/dom/streams/TransformStream.cpp +++ b/dom/streams/TransformStream.cpp @@ -6,9 +6,6 @@ #include "mozilla/dom/TransformStream.h" -#include "UnderlyingSourceCallbackHelpers.h" -#include "js/TypeDecls.h" -#include "mozilla/dom/Promise.h" #include "mozilla/dom/WritableStream.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/RootedDictionary.h" @@ -19,9 +16,7 @@ namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, - mBackpressureChangePromise, mController, - mReadable, mWritable) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, mController) NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformStream) NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformStream) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStream) @@ -40,216 +35,6 @@ JSObject* TransformStream::WrapObject(JSContext* aCx, return TransformStream_Binding::Wrap(aCx, this, aGivenProto); } -// https://streams.spec.whatwg.org/#initialize-transform-stream -class TransformStreamUnderlyingSinkAlgorithms final - : public UnderlyingSinkAlgorithmsBase { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( - TransformStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) - - TransformStreamUnderlyingSinkAlgorithms(Promise* aStartPromise, - TransformStream* aStream) - : mStartPromise(aStartPromise), mStream(aStream) {} - - void StartCallback(JSContext* aCx, - WritableStreamDefaultController& aController, - JS::MutableHandle aRetVal, - ErrorResult& aRv) override { - // Step 1. Let startAlgorithm be an algorithm that returns startPromise. - // (Same as TransformStreamUnderlyingSourceAlgorithms::StartCallback) - aRetVal.setObject(*mStartPromise->PromiseObj()); - } - - already_AddRefed WriteCallback( - JSContext* aCx, JS::Handle aChunk, - WritableStreamDefaultController& aController, ErrorResult& aRv) override { - // Step 2. Let writeAlgorithm be the following steps, taking a chunk - // argument: - // Step 1. Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, - // chunk). - // TODO - return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), - aRv); - } - - already_AddRefed AbortCallback( - JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv) override { - // Step 3. Let abortAlgorithm be the following steps, taking a reason - // argument: - // Step 1. Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, - // reason). - // TODO - return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), - aRv); - } - - already_AddRefed CloseCallback(JSContext* aCx, - ErrorResult& aRv) override { - // Step 4. Let closeAlgorithm be the following steps: - - // Step 1. Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). - // TODO - return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), - aRv); - } - - protected: - ~TransformStreamUnderlyingSinkAlgorithms() override = default; - - private: - RefPtr mStartPromise; - RefPtr mStream; -}; - -NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSinkAlgorithms, - UnderlyingSinkAlgorithmsBase, mStartPromise, - mStream) -NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSinkAlgorithms, - UnderlyingSinkAlgorithmsBase) -NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSinkAlgorithms, - UnderlyingSinkAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamUnderlyingSinkAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSinkAlgorithms) - -// https://streams.spec.whatwg.org/#initialize-transform-stream -class TransformStreamUnderlyingSourceAlgorithms final - : public UnderlyingSourceAlgorithmsBase { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( - TransformStreamUnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase) - - TransformStreamUnderlyingSourceAlgorithms(Promise* aStartPromise, - TransformStream* aStream) - : mStartPromise(aStartPromise), mStream(aStream) {} - - void StartCallback(JSContext* aCx, ReadableStreamController& aController, - JS::MutableHandle aRetVal, - ErrorResult& aRv) override { - // Step 1. Let startAlgorithm be an algorithm that returns startPromise. - // (Same as TransformStreamUnderlyingSinkAlgorithms::StartCallback) - aRetVal.setObject(*mStartPromise->PromiseObj()); - } - - already_AddRefed PullCallback(JSContext* aCx, - ReadableStreamController& aController, - ErrorResult& aRv) override { - // Step 6. Let pullAlgorithm be the following steps: - // Step 1. Return ! TransformStreamDefaultSourcePullAlgorithm(stream). - // TODO - return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), - aRv); - } - - already_AddRefed CancelCallback( - JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv) override { - // Step 7. Let cancelAlgorithm be the following steps, taking a reason - // argument: - // Step 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, - // reason). - // Step 2. Return a promise resolved with undefined. - // TODO - return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), - aRv); - } - - void ErrorCallback() override {} - - protected: - ~TransformStreamUnderlyingSourceAlgorithms() override = default; - - private: - RefPtr mStartPromise; - RefPtr mStream; -}; - -NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSourceAlgorithms, - UnderlyingSourceAlgorithmsBase, - mStartPromise, mStream) -NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSourceAlgorithms, - UnderlyingSourceAlgorithmsBase) -NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSourceAlgorithms, - UnderlyingSourceAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( - TransformStreamUnderlyingSourceAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSourceAlgorithms) - -// https://streams.spec.whatwg.org/#transform-stream-set-backpressure -static void TransformStreamSetBackpressure(TransformStream* aStream, - bool aBackpressure, - ErrorResult& aRv) { - // Step 1. Assert: stream.[[backpressure]] is not backpressure. - MOZ_ASSERT(aStream->Backpressure() != aBackpressure); - - // Step 2. If stream.[[backpressureChangePromise]] is not undefined, resolve - // stream.[[backpressureChangePromise]] with undefined. - if (Promise* promise = aStream->BackpressureChangePromise()) { - promise->MaybeResolveWithUndefined(); - } - - // Step 3. Set stream.[[backpressureChangePromise]] to a new promise. - RefPtr promise = Promise::Create(aStream->GetParentObject(), aRv); - if (aRv.Failed()) { - return; - } - aStream->SetBackpressureChangePromise(promise); - - // Step 4. Set stream.[[backpressure]] to backpressure. - aStream->SetBackpressure(aBackpressure); -} - -// https://streams.spec.whatwg.org/#initialize-transform-stream -void TransformStream::Initialize(JSContext* aCx, Promise* aStartPromise, - double aWritableHighWaterMark, - QueuingStrategySize* aWritableSizeAlgorithm, - double aReadableHighWaterMark, - QueuingStrategySize* aReadableSizeAlgorithm, - ErrorResult& aRv) { - // Step 1 - 4 - auto sinkAlgorithms = - MakeRefPtr(aStartPromise, this); - - // Step 5. Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, - // writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark, - // writableSizeAlgorithm). - mWritable = - CreateWritableStream(aCx, MOZ_KnownLive(mGlobal), sinkAlgorithms, - aWritableHighWaterMark, aWritableSizeAlgorithm, aRv); - if (aRv.Failed()) { - return; - } - - // Step 6 - 7 - auto sourceAlgorithms = MakeRefPtr( - aStartPromise, this); - - // Step 8. Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, - // pullAlgorithm, cancelAlgorithm, readableHighWaterMark, - // readableSizeAlgorithm). - mReadable = CreateReadableStream( - aCx, MOZ_KnownLive(mGlobal), sourceAlgorithms, - Some(aReadableHighWaterMark), aReadableSizeAlgorithm, aRv); - if (aRv.Failed()) { - return; - } - - // Step 9. Set stream.[[backpressure]] and - // stream.[[backpressureChangePromise]] to undefined. - mBackpressureChangePromise = nullptr; - - // Step 10. Perform ! TransformStreamSetBackpressure(stream, true). - TransformStreamSetBackpressure(this, true, aRv); - if (aRv.Failed()) { - return; - } - - // Step 11. Set stream.[[controller]] to undefined. - mController = nullptr; -} - // https://streams.spec.whatwg.org/#ts-constructor already_AddRefed TransformStream::Constructor( const GlobalObject& aGlobal, @@ -294,35 +79,19 @@ already_AddRefed TransformStream::Constructor( // Step 5. Let readableHighWaterMark be ? // ExtractHighWaterMark(readableStrategy, 0). - double readableHighWaterMark = - ExtractHighWaterMark(aReadableStrategy, 0, aRv); - if (aRv.Failed()) { - return nullptr; - } + // TODO // Step 6. Let readableSizeAlgorithm be ! // ExtractSizeAlgorithm(readableStrategy). - // Note: Callers should recognize nullptr as a callback that returns 1. See - // also WritableStream::Constructor for this design decision. - RefPtr readableSizeAlgorithm = - aReadableStrategy.mSize.WasPassed() ? &aReadableStrategy.mSize.Value() - : nullptr; + // TODO // Step 7. Let writableHighWaterMark be ? // ExtractHighWaterMark(writableStrategy, 1). - double writableHighWaterMark = - ExtractHighWaterMark(aWritableStrategy, 1, aRv); - if (aRv.Failed()) { - return nullptr; - } + // TODO // Step 8. Let writableSizeAlgorithm be ! // ExtractSizeAlgorithm(writableStrategy). - // Note: Callers should recognize nullptr as a callback that returns 1. See - // also WritableStream::Constructor for this design decision. - RefPtr writableSizeAlgorithm = - aWritableStrategy.mSize.WasPassed() ? &aWritableStrategy.mSize.Value() - : nullptr; + // TODO // Step 9. Let startPromise be a new promise. nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); @@ -335,9 +104,7 @@ already_AddRefed TransformStream::Constructor( // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, // readableSizeAlgorithm). RefPtr transformStream = new TransformStream(global); - transformStream->Initialize( - aGlobal.Context(), startPromise, writableHighWaterMark, - writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm, aRv); + // TODO: Init() if (aRv.Failed()) { return nullptr; } @@ -351,22 +118,7 @@ already_AddRefed TransformStream::Constructor( // Step 12. If transformerDict["start"] exists, then resolve startPromise with // the result of invoking transformerDict["start"] with argument list « // this.[[controller]] » and callback this value transformer. - if (transformerDict.mStart.WasPassed()) { - RefPtr callback = transformerDict.mStart.Value(); - RefPtr controller = - transformStream->Controller(); - JS::Rooted retVal(aGlobal.Context()); - callback->Call(transformerObj, *controller, &retVal, aRv, - "Transformer.start", CallbackFunction::eRethrowExceptions); - if (aRv.Failed()) { - return nullptr; - } - - startPromise->MaybeResolve(retVal); - } else { - // Step 13. Otherwise, resolve startPromise with undefined. - startPromise->MaybeResolveWithUndefined(); - } + // TODO return transformStream.forget(); } diff --git a/dom/streams/TransformStream.h b/dom/streams/TransformStream.h index 2fc47fef947b..8ffd188762f5 100644 --- a/dom/streams/TransformStream.h +++ b/dom/streams/TransformStream.h @@ -29,13 +29,6 @@ class TransformStream final : public nsISupports, public nsWrapperCache { NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransformStream) - // Internal slot accessors - bool Backpressure() const { return mBackpressure; } - void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; } - Promise* BackpressureChangePromise() { return mBackpressureChangePromise; } - void SetBackpressureChangePromise(Promise* aPromise) { - mBackpressureChangePromise = aPromise; - } TransformStreamDefaultController* Controller() { return mController; } void SetController(TransformStreamDefaultController* aController) { mController = aController; @@ -45,12 +38,6 @@ class TransformStream final : public nsISupports, public nsWrapperCache { ~TransformStream(); explicit TransformStream(nsIGlobalObject* aGlobal); - MOZ_CAN_RUN_SCRIPT void Initialize( - JSContext* aCx, Promise* aStartPromise, double aWritableHighWaterMark, - QueuingStrategySize* aWritableSizeAlgorithm, - double aReadableHighWaterMark, - QueuingStrategySize* aReadableSizeAlgorithm, ErrorResult& aRv); - public: nsIGlobalObject* GetParentObject() const { return mGlobal; } JSObject* WrapObject(JSContext* aCx, @@ -71,11 +58,7 @@ class TransformStream final : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; // Internal slots - bool mBackpressure; - RefPtr mBackpressureChangePromise; RefPtr mController; - RefPtr mReadable; - RefPtr mWritable; }; } // namespace mozilla::dom diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.cpp b/dom/streams/UnderlyingSinkCallbackHelpers.cpp index 6d107692d980..c886fb4895be 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.cpp +++ b/dom/streams/UnderlyingSinkCallbackHelpers.cpp @@ -9,24 +9,16 @@ using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) -NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithmsBase) -NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(UnderlyingSinkAlgorithms, + (mGlobal, mStartCallback, + mWriteCallback, mCloseCallback, + mAbortCallback), + (mUnderlyingSink)) +NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithms) +NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithms) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(UnderlyingSinkAlgorithmsBase) -NS_IMPL_CYCLE_COLLECTION_TRACE_END - -NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( - UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase, - (mGlobal, mStartCallback, mWriteCallback, mCloseCallback, mAbortCallback), - (mUnderlyingSink)) -NS_IMPL_ADDREF_INHERITED(UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) -NS_IMPL_RELEASE_INHERITED(UnderlyingSinkAlgorithms, - UnderlyingSinkAlgorithmsBase) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) -NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase) // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink void UnderlyingSinkAlgorithms::StartCallback( diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.h b/dom/streams/UnderlyingSinkCallbackHelpers.h index 5402a2625599..3d409d7b503b 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.h +++ b/dom/streams/UnderlyingSinkCallbackHelpers.h @@ -11,7 +11,6 @@ #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/UnderlyingSinkBinding.h" -#include "nsCycleCollectionParticipant.h" #include "nsISupports.h" #include "nsISupportsImpl.h" @@ -24,36 +23,11 @@ namespace mozilla::dom { class WritableStreamDefaultController; -class UnderlyingSinkAlgorithmsBase : public nsISupports { +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink +class UnderlyingSinkAlgorithms : public nsISupports { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithmsBase) - - MOZ_CAN_RUN_SCRIPT virtual void StartCallback( - JSContext* aCx, WritableStreamDefaultController& aController, - JS::MutableHandle aRetVal, ErrorResult& aRv) = 0; - - MOZ_CAN_RUN_SCRIPT virtual already_AddRefed WriteCallback( - JSContext* aCx, JS::Handle aChunk, - WritableStreamDefaultController& aController, ErrorResult& aRv) = 0; - - MOZ_CAN_RUN_SCRIPT virtual already_AddRefed CloseCallback( - JSContext* aCx, ErrorResult& aRv) = 0; - - MOZ_CAN_RUN_SCRIPT virtual already_AddRefed AbortCallback( - JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv) = 0; - - protected: - virtual ~UnderlyingSinkAlgorithmsBase() = default; -}; - -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink -class UnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase { - public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( - UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithms) UnderlyingSinkAlgorithms(nsIGlobalObject* aGlobal, JS::HandleObject aUnderlyingSink, @@ -84,21 +58,21 @@ class UnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase { MOZ_CAN_RUN_SCRIPT void StartCallback( JSContext* aCx, WritableStreamDefaultController& aController, - JS::MutableHandle aRetVal, ErrorResult& aRv) override; + JS::MutableHandle aRetVal, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT already_AddRefed WriteCallback( JSContext* aCx, JS::Handle aChunk, - WritableStreamDefaultController& aController, ErrorResult& aRv) override; + WritableStreamDefaultController& aController, ErrorResult& aRv); - MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback( - JSContext* aCx, ErrorResult& aRv) override; + MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback(JSContext* aCx, + ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT already_AddRefed AbortCallback( JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv) override; + ErrorResult& aRv); protected: - ~UnderlyingSinkAlgorithms() override { mozilla::DropJSObjects(this); } + virtual ~UnderlyingSinkAlgorithms() { mozilla::DropJSObjects(this); }; private: // Virtually const, but are cycle collected diff --git a/dom/streams/WritableStream.cpp b/dom/streams/WritableStream.cpp index 669e4cd4b4b4..6ddef107c602 100644 --- a/dom/streams/WritableStream.cpp +++ b/dom/streams/WritableStream.cpp @@ -699,35 +699,6 @@ AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv) { return writer.forget(); } -// https://streams.spec.whatwg.org/#create-writable-stream -already_AddRefed CreateWritableStream( - JSContext* aCx, nsIGlobalObject* aGlobal, - UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, - QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { - // Step 1: Assert: ! IsNonNegativeNumber(highWaterMark) is true. - MOZ_ASSERT(IsNonNegativeNumber(aHighWaterMark)); - - // Step 2: Let stream be a new WritableStream. - // Step 3: Perform ! InitializeWritableStream(stream). - auto stream = MakeRefPtr(aGlobal); - - // Step 4: Let controller be a new WritableStreamDefaultController. - auto controller = - MakeRefPtr(aGlobal, *stream); - - // Step 5: Perform ? SetUpWritableStreamDefaultController(stream, controller, - // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, - // highWaterMark, sizeAlgorithm). - SetUpWritableStreamDefaultController(aCx, stream, controller, aAlgorithms, - aHighWaterMark, aSizeAlgorithm, aRv); - if (aRv.Failed()) { - return nullptr; - } - - // Step 6: Return stream. - return stream.forget(); -} - already_AddRefed WritableStream::GetWriter( ErrorResult& aRv) { return AcquireWritableStreamDefaultWriter(this, aRv); diff --git a/dom/streams/WritableStream.h b/dom/streams/WritableStream.h index 4700a819f595..7cba91159eef 100644 --- a/dom/streams/WritableStream.h +++ b/dom/streams/WritableStream.h @@ -28,7 +28,6 @@ namespace mozilla::dom { class Promise; class WritableStreamDefaultController; class WritableStreamDefaultWriter; -class UnderlyingSinkAlgorithmsBase; class WritableStream : public nsISupports, public nsWrapperCache { public: @@ -181,11 +180,6 @@ class WritableStream : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; }; -MOZ_CAN_RUN_SCRIPT already_AddRefed CreateWritableStream( - JSContext* aCx, nsIGlobalObject* aGlobal, - UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, - QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); - inline bool IsWritableStreamLocked(WritableStream* aStream) { return aStream->Locked(); } diff --git a/dom/streams/WritableStreamDefaultController.cpp b/dom/streams/WritableStreamDefaultController.cpp index 8df09749c53b..89f222660462 100644 --- a/dom/streams/WritableStreamDefaultController.cpp +++ b/dom/streams/WritableStreamDefaultController.cpp @@ -91,7 +91,7 @@ already_AddRefed WritableStreamDefaultController::AbortSteps( JSContext* aCx, JS::Handle aReason, ErrorResult& aRv) { // Step 1. Let result be the result of performing this.[[abortAlgorithm]], // passing reason. - RefPtr algorithms = mAlgorithms; + RefPtr algorithms = mAlgorithms; Optional> optionalReason(aCx, aReason); RefPtr abortPromise = algorithms->AbortCallback(aCx, optionalReason, aRv); @@ -126,7 +126,7 @@ WritableStreamDefaultControllerAdvanceQueueIfNeeded( void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, + UnderlyingSinkAlgorithms* aAlgorithms, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { // Step 1. Assert: stream implements WritableStream. // Step 2. Assert: stream.[[controller]] is undefined. @@ -260,8 +260,7 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessClose( // Step 5. Let sinkClosePromise be the result of performing // controller.[[closeAlgorithm]]. - RefPtr algorithms = - aController->GetAlgorithms(); + RefPtr algorithms = aController->GetAlgorithms(); RefPtr sinkClosePromise = algorithms->CloseCallback(aCx, aRv); if (aRv.Failed()) { return; @@ -304,8 +303,7 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessWrite( // Step 3. Let sinkWritePromise be the result of performing // controller.[[writeAlgorithm]], passing in chunk. - RefPtr algorithms = - aController->GetAlgorithms(); + RefPtr algorithms = aController->GetAlgorithms(); RefPtr sinkWritePromise = algorithms->WriteCallback(aCx, aChunk, *aController, aRv); if (aRv.Failed()) { diff --git a/dom/streams/WritableStreamDefaultController.h b/dom/streams/WritableStreamDefaultController.h index 38fc0969b1a0..609c578d45c4 100644 --- a/dom/streams/WritableStreamDefaultController.h +++ b/dom/streams/WritableStreamDefaultController.h @@ -84,8 +84,8 @@ class WritableStreamDefaultController final : public nsISupports, mStrategySizeAlgorithm = aStrategySizeAlgorithm; } - UnderlyingSinkAlgorithmsBase* GetAlgorithms() { return mAlgorithms; } - void SetAlgorithms(UnderlyingSinkAlgorithmsBase* aAlgorithms) { + UnderlyingSinkAlgorithms* GetAlgorithms() { return mAlgorithms; } + void SetAlgorithms(UnderlyingSinkAlgorithms* aAlgorithms) { mAlgorithms = aAlgorithms; } @@ -128,14 +128,14 @@ class WritableStreamDefaultController final : public nsISupports, double mStrategyHWM = 0.0; RefPtr mStrategySizeAlgorithm; - RefPtr mAlgorithms; + RefPtr mAlgorithms; RefPtr mStream; }; MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithmsBase* aSinkCallbacks, double aHighWaterMark, + UnderlyingSinkAlgorithms* aSinkCallbacks, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultControllerFromUnderlyingSink( diff --git a/testing/web-platform/meta/streams/idlharness.any.js.ini b/testing/web-platform/meta/streams/idlharness.any.js.ini index 0c924fd72632..d668edac00a8 100644 --- a/testing/web-platform/meta/streams/idlharness.any.js.ini +++ b/testing/web-platform/meta/streams/idlharness.any.js.ini @@ -12,8 +12,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -36,8 +35,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -120,8 +118,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -180,8 +177,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -240,8 +236,7 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -276,12 +271,10 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -296,8 +289,7 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -697,8 +689,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -721,8 +712,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -805,8 +795,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -865,8 +854,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -925,8 +913,7 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -961,12 +948,10 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -981,8 +966,7 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -1382,8 +1366,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -1406,8 +1389,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -1490,8 +1472,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -1550,8 +1531,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -1610,8 +1590,7 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -1646,12 +1625,10 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -1666,8 +1643,7 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -2067,8 +2043,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -2091,8 +2066,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -2175,8 +2149,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -2235,8 +2208,7 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -2295,8 +2267,7 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -2331,12 +2302,10 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -2351,8 +2320,7 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: - if not domstreams: FAIL + expected: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: diff --git a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini index c4152600e888..b53d537b34eb 100644 --- a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini @@ -11,6 +11,9 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL + [TransformStream constructor should throw when start does] + expected: FAIL + [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -53,9 +56,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.serviceworker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -70,6 +70,9 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL + [TransformStream constructor should throw when start does] + expected: FAIL + [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -112,9 +115,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.worker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -129,6 +129,9 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL + [TransformStream constructor should throw when start does] + expected: FAIL + [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -171,9 +174,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -188,6 +188,9 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL + [TransformStream constructor should throw when start does] + expected: FAIL + [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -229,6 +232,3 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - - [errored TransformStream should not enqueue new chunks] - expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini index 0b163c08e560..d3face719e3b 100644 --- a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini @@ -50,15 +50,12 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL + [start() should not be called twice] + expected: FAIL + [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.html] [TransformStream writable starts in the writable state] @@ -112,15 +109,12 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL + [start() should not be called twice] + expected: FAIL + [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.worker.html] [TransformStream writable starts in the writable state] @@ -174,15 +168,12 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL + [start() should not be called twice] + expected: FAIL + [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.serviceworker.html] [TransformStream writable starts in the writable state] @@ -236,11 +227,8 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL + [start() should not be called twice] + expected: FAIL + [Subclassing TransformStream should work] expected: FAIL - - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini index d79ccd83adc5..1c40cb139f22 100644 --- a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini @@ -1,4 +1,10 @@ [properties.any.worker.html] + [transformer method start should be called with the right number of arguments] + expected: FAIL + + [transformer method start should be called even when it's located on the prototype chain] + expected: FAIL + [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -13,6 +19,12 @@ [properties.any.html] + [transformer method start should be called with the right number of arguments] + expected: FAIL + + [transformer method start should be called even when it's located on the prototype chain] + expected: FAIL + [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -27,6 +39,12 @@ [properties.any.sharedworker.html] + [transformer method start should be called with the right number of arguments] + expected: FAIL + + [transformer method start should be called even when it's located on the prototype chain] + expected: FAIL + [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -41,6 +59,12 @@ [properties.any.serviceworker.html] + [transformer method start should be called with the right number of arguments] + expected: FAIL + + [transformer method start should be called even when it's located on the prototype chain] + expected: FAIL + [transformer method transform should be called with the right number of arguments] expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini index 669dbfc0b022..68ca3c853805 100644 --- a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini @@ -14,6 +14,9 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL + [a RangeError should be thrown for an invalid highWaterMark] + expected: FAIL + [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -23,9 +26,6 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL - [readableStrategy highWaterMark should be converted to a number] - expected: FAIL - [strategies.any.serviceworker.html] [writableStrategy highWaterMark should work] @@ -43,6 +43,9 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL + [a RangeError should be thrown for an invalid highWaterMark] + expected: FAIL + [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -52,9 +55,6 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL - [readableStrategy highWaterMark should be converted to a number] - expected: FAIL - [strategies.any.sharedworker.html] [writableStrategy highWaterMark should work] @@ -72,6 +72,9 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL + [a RangeError should be thrown for an invalid highWaterMark] + expected: FAIL + [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -81,9 +84,6 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL - [readableStrategy highWaterMark should be converted to a number] - expected: FAIL - [strategies.any.worker.html] [writableStrategy highWaterMark should work] @@ -101,6 +101,9 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL + [a RangeError should be thrown for an invalid highWaterMark] + expected: FAIL + [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -109,6 +112,3 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL - - [readableStrategy highWaterMark should be converted to a number] - expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini index 7536f29dea2b..cf1b68ddbf43 100644 --- a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini @@ -14,9 +14,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.worker.html] [controller.terminate() should error pipeTo()] @@ -34,9 +31,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.serviceworker.html] [controller.terminate() should error pipeTo()] @@ -54,9 +48,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.html] [controller.terminate() should error pipeTo()] @@ -73,6 +64,3 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL From 38d5d4aa3da248e238161bf85bac3ec8629132c6 Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:02 +0000 Subject: [PATCH 07/43] Bug 1738103 - Part 1: Remove the unused TailDispatchingTarget type, r=xpcom-reviewers,KrisWright Differential Revision: https://phabricator.services.mozilla.com/D129837 --- xpcom/threads/nsThreadUtils.cpp | 2 -- xpcom/threads/nsThreadUtils.h | 50 --------------------------------- 2 files changed, 52 deletions(-) diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp index 48c1ce5243c1..f29cbbafd1c7 100644 --- a/xpcom/threads/nsThreadUtils.cpp +++ b/xpcom/threads/nsThreadUtils.cpp @@ -41,8 +41,6 @@ static mozilla::LazyLogModule sEventDispatchAndRunLog("events"); using namespace mozilla; -NS_IMPL_ISUPPORTS(TailDispatchingTarget, nsIEventTarget, nsISerialEventTarget) - #ifndef XPCOM_GLUE_AVOID_NSPR NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod) diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h index 5f9b821d5d11..20f677556435 100644 --- a/xpcom/threads/nsThreadUtils.h +++ b/xpcom/threads/nsThreadUtils.h @@ -1800,56 +1800,6 @@ nsISerialEventTarget* GetCurrentSerialEventTarget(); nsISerialEventTarget* GetMainThreadSerialEventTarget(); -// Returns a wrapper around the current thread which routes normal dispatches -// through the tail dispatcher. -// This means that they will run at the end of the current task, rather than -// after all the subsequent tasks queued. This is useful to allow MozPromise -// callbacks returned by IPDL methods to avoid an extra trip through the event -// loop, and thus maintain correct ordering relative to other IPC events. The -// current thread implementation must support tail dispatch. -class TailDispatchingTarget : public nsISerialEventTarget { - public: - NS_DECL_THREADSAFE_ISUPPORTS - TailDispatchingTarget() -#if DEBUG - : mOwnerThread(AbstractThread::GetCurrent()) -#endif - { - MOZ_ASSERT(mOwnerThread, "Must be used with AbstractThreads"); - } - - NS_IMETHOD - Dispatch(already_AddRefed event, uint32_t flags) override { - MOZ_ASSERT(flags == DISPATCH_NORMAL); - MOZ_ASSERT( - AbstractThread::GetCurrent() == mOwnerThread, - "TailDispatchingTarget can only be used on the thread upon which it " - "was created - see the comment on the class declaration."); - AbstractThread::DispatchDirectTask(std::move(event)); - return NS_OK; - } - NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override { return true; } - NS_IMETHOD IsOnCurrentThread(bool* _retval) override { - *_retval = true; - return NS_OK; - } - NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override { - MOZ_ASSERT_UNREACHABLE("not implemented"); - return NS_ERROR_NOT_IMPLEMENTED; - } - NS_IMETHOD DelayedDispatch(already_AddRefed event, - uint32_t delay) override { - MOZ_ASSERT_UNREACHABLE("not implemented"); - return NS_ERROR_NOT_IMPLEMENTED; - } - - private: - virtual ~TailDispatchingTarget() = default; -#if DEBUG - const RefPtr mOwnerThread; -#endif -}; - // Returns the number of CPUs, like PR_GetNumberOfProcessors, except // that it can return a cached value on platforms where sandboxing // would prevent reading the current value (currently Linux). CPU From 2d44b3a38b825cb110f85d28bdd5929f3642ef3e Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:03 +0000 Subject: [PATCH 08/43] Bug 1738103 - Part 2: Accept messages for TaskQueue until queue is exhausted during shutdown, r=xpcom-reviewers,KrisWright This makes the behaviour of TaskQueue more in-line with nsThread, which may be useful when converting code to using taskqueue instead of threads. Differential Revision: https://phabricator.services.mozilla.com/D129838 --- xpcom/threads/TaskQueue.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp index 401f9d649941..37133eee041a 100644 --- a/xpcom/threads/TaskQueue.cpp +++ b/xpcom/threads/TaskQueue.cpp @@ -42,8 +42,11 @@ TaskDispatcher& TaskQueue::TailDispatcher() { nsresult TaskQueue::DispatchLocked(nsCOMPtr& aRunnable, uint32_t aFlags, DispatchReason aReason) { mQueueMonitor.AssertCurrentThreadOwns(); - if (mIsShutdown) { - return NS_ERROR_FAILURE; + + // Continue to allow dispatches after shutdown until the last message has been + // processed, at which point no more messages will be accepted. + if (mIsShutdown && !mIsRunning) { + return NS_ERROR_UNEXPECTED; } AbstractThread* currentThread; From dedb92d85bbd45dce874a74a68f2c1cc41b0d014 Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:03 +0000 Subject: [PATCH 09/43] Bug 1738103 - Part 3: DelayedRunnable should only be used with nsISerialEventTarget, r=necko-reviewers,valentin The existing behaviour of the code is not fully compatible with nsIEventTarget. Differential Revision: https://phabricator.services.mozilla.com/D129839 --- netwerk/base/nsStreamTransportService.cpp | 59 ++-------------------- netwerk/base/nsStreamTransportService.h | 12 +---- netwerk/system/mac/nsNetworkLinkService.h | 4 ++ netwerk/system/mac/nsNetworkLinkService.mm | 27 ++++++++-- xpcom/threads/DelayedRunnable.cpp | 2 +- xpcom/threads/DelayedRunnable.h | 4 +- 6 files changed, 34 insertions(+), 74 deletions(-) diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp index 3810ac8dc5fa..47a9dc0f32c3 100644 --- a/netwerk/base/nsStreamTransportService.cpp +++ b/netwerk/base/nsStreamTransportService.cpp @@ -16,7 +16,6 @@ #include "nsITransport.h" #include "nsIObserverService.h" #include "nsThreadPool.h" -#include "mozilla/DelayedRunnable.h" #include "mozilla/Services.h" namespace mozilla { @@ -242,9 +241,7 @@ nsInputStreamTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) { // nsStreamTransportService //----------------------------------------------------------------------------- -nsStreamTransportService::nsStreamTransportService() - : mScheduledDelayedRunnables( - "nsStreamTransportService.mScheduledDelayedRunnables") {} +nsStreamTransportService::nsStreamTransportService() = default; nsStreamTransportService::~nsStreamTransportService() { NS_ASSERTION(!mPool, "thread pool wasn't shutdown"); @@ -269,25 +266,8 @@ nsresult nsStreamTransportService::Init() { return NS_OK; } -void nsStreamTransportService::OnDelayedRunnableCreated( - DelayedRunnable* aRunnable) {} - -void nsStreamTransportService::OnDelayedRunnableScheduled( - DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - auto delayedRunnables = mScheduledDelayedRunnables.Lock(); - delayedRunnables->AppendElement(aRunnable); -} - -void nsStreamTransportService::OnDelayedRunnableRan( - DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - auto delayedRunnables = mScheduledDelayedRunnables.Lock(); - Unused << delayedRunnables->RemoveElement(aRunnable); -} - NS_IMPL_ISUPPORTS(nsStreamTransportService, nsIStreamTransportService, - nsIEventTarget, nsIDelayedRunnableObserver, nsIObserver) + nsIEventTarget, nsIObserver) NS_IMETHODIMP nsStreamTransportService::DispatchFromScript(nsIRunnable* task, @@ -315,15 +295,7 @@ nsStreamTransportService::Dispatch(already_AddRefed task, NS_IMETHODIMP nsStreamTransportService::DelayedDispatch(already_AddRefed aEvent, uint32_t aDelayMs) { - nsCOMPtr event = aEvent; - NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); - - RefPtr r = - new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs); - nsresult rv = r->Init(); - NS_ENSURE_SUCCESS(rv, rv); - - return Dispatch(r.forget(), NS_DISPATCH_NORMAL); + return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP_(bool) @@ -380,31 +352,6 @@ nsStreamTransportService::Observe(nsISupports* subject, const char* topic, pool->Shutdown(); } } - - // Because the DelayedRunnables are run by a thread pool, no guarantee is - // given to which thread they run or get released on. Releasing them on the - // thread pool or on the background target thus doesn't really matter. We are - // forced to do it on the background target after the thread pool has finished - // processing all events, since doing it on the thread pool would allow the - // shutdown task to race with scheduling new DelayedRunnables, possibly - // missing the cleanup of some of them. - nsTArray> delayedRunnables; - { - auto sdrs = mScheduledDelayedRunnables.Lock(); - std::swap(*sdrs, delayedRunnables); - MOZ_ASSERT(sdrs->IsEmpty()); - } - if (!delayedRunnables.IsEmpty()) { - NS_DispatchBackgroundTask( - NS_NewRunnableFunction( - "nsStreamTransportService::mScheduledDelayedRunnables Cancel", - [delayedRunnables = std::move(delayedRunnables)] { - for (const auto& r : delayedRunnables) { - r->CancelTimer(); - } - }), - NS_DISPATCH_SYNC); - } return NS_OK; } diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h index 67611dfb7174..0f86ef687388 100644 --- a/netwerk/base/nsStreamTransportService.h +++ b/netwerk/base/nsStreamTransportService.h @@ -14,19 +14,15 @@ #include "nsThreadUtils.h" #include "mozilla/Attributes.h" #include "mozilla/DataMutex.h" -#include "mozilla/DelayedRunnable.h" #include "mozilla/Mutex.h" class nsIThreadPool; namespace mozilla { -class DelayedRunnable; - namespace net { class nsStreamTransportService final : public nsIStreamTransportService, public nsIEventTarget, - public nsIDelayedRunnableObserver, public nsIObserver { public: NS_DECL_THREADSAFE_ISUPPORTS @@ -38,19 +34,13 @@ class nsStreamTransportService final : public nsIStreamTransportService, nsStreamTransportService(); - void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override; - private: ~nsStreamTransportService(); nsCOMPtr mPool GUARDED_BY(mShutdownLock); - DataMutex>> mScheduledDelayedRunnables; - mozilla::Mutex mShutdownLock{"nsStreamTransportService.mShutdownLock"}; - bool mIsShutdown GUARDED_BY(mShutdownLock) {false}; + bool mIsShutdown GUARDED_BY(mShutdownLock){false}; }; } // namespace net diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h index aa74d4b4b8c7..d3de10e6a5ed 100644 --- a/netwerk/system/mac/nsNetworkLinkService.h +++ b/netwerk/system/mac/nsNetworkLinkService.h @@ -77,6 +77,10 @@ class nsNetworkLinkService : public nsINetworkLinkService, // time to discover the gateway's MAC address. nsCOMPtr mNetworkIdTimer; + // Scheduled timers used to delay querying of the DNS suffix list when + // triggered by a network change. Guarded by mMutex. + nsTArray> mDNSConfigChangedTimers; + // IP address used to check the route for public traffic. struct in_addr mRouteCheckIPv4; }; diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm index a7b1f988910c..3c829986cc28 100644 --- a/netwerk/system/mac/nsNetworkLinkService.mm +++ b/netwerk/system/mac/nsNetworkLinkService.mm @@ -692,10 +692,19 @@ void nsNetworkLinkService::DNSConfigChanged(uint32_t aDelayMs) { return; } if (aDelayMs) { - MOZ_ALWAYS_SUCCEEDS(target->DelayedDispatch( - NS_NewRunnableFunction("nsNetworkLinkService::GetDnsSuffixListInternal", - [self = RefPtr{this}]() { self->GetDnsSuffixListInternal(); }), - aDelayMs)); + MutexAutoLock lock(mMutex); + nsCOMPtr timer; + MOZ_ALWAYS_SUCCEEDS(NS_NewTimerWithCallback( + getter_AddRefs(timer), + [self = RefPtr{this}](nsITimer* aTimer) { + self->GetDnsSuffixListInternal(); + + MutexAutoLock lock(self->mMutex); + self->mDNSConfigChangedTimers.RemoveElement(aTimer); + }, + TimeDuration::FromMilliseconds(aDelayMs), nsITimer::TYPE_ONE_SHOT, + "nsNetworkLinkService::GetDnsSuffixListInternal", target)); + mDNSConfigChangedTimers.AppendElement(timer); } else { MOZ_ALWAYS_SUCCEEDS(target->Dispatch( NS_NewRunnableFunction("nsNetworkLinkService::GetDnsSuffixListInternal", @@ -849,6 +858,16 @@ nsresult nsNetworkLinkService::Shutdown() { mNetworkIdTimer = nullptr; } + nsTArray> dnsConfigChangedTimers; + { + MutexAutoLock lock(mMutex); + dnsConfigChangedTimers = std::move(mDNSConfigChangedTimers); + mDNSConfigChangedTimers.Clear(); + } + for (const auto& timer : dnsConfigChangedTimers) { + timer->Cancel(); + } + return NS_OK; } diff --git a/xpcom/threads/DelayedRunnable.cpp b/xpcom/threads/DelayedRunnable.cpp index 067b08d4ee7d..42b09bf3780a 100644 --- a/xpcom/threads/DelayedRunnable.cpp +++ b/xpcom/threads/DelayedRunnable.cpp @@ -10,7 +10,7 @@ namespace mozilla { -DelayedRunnable::DelayedRunnable(already_AddRefed aTarget, +DelayedRunnable::DelayedRunnable(already_AddRefed aTarget, already_AddRefed aRunnable, uint32_t aDelay) : mozilla::Runnable("DelayedRunnable"), diff --git a/xpcom/threads/DelayedRunnable.h b/xpcom/threads/DelayedRunnable.h index 0bca644e42ff..90b505e433bd 100644 --- a/xpcom/threads/DelayedRunnable.h +++ b/xpcom/threads/DelayedRunnable.h @@ -18,7 +18,7 @@ namespace mozilla { class DelayedRunnable : public Runnable, public nsITimerCallback { public: - DelayedRunnable(already_AddRefed aTarget, + DelayedRunnable(already_AddRefed aTarget, already_AddRefed aRunnable, uint32_t aDelay); NS_DECL_ISUPPORTS_INHERITED @@ -37,7 +37,7 @@ class DelayedRunnable : public Runnable, public nsITimerCallback { ~DelayedRunnable() = default; nsresult DoRun(); - const nsCOMPtr mTarget; + const nsCOMPtr mTarget; const nsCOMPtr mObserver; nsCOMPtr mWrappedRunnable; nsCOMPtr mTimer; From 5b2ada26c5d8ae5e9b554757f1dfa73ebe09877e Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:04 +0000 Subject: [PATCH 10/43] Bug 1738103 - Part 4: Add XPCOM support for shutdown tasks, r=necko-reviewers,KrisWright Tasks registered with shutdown tasks are called when the target nsISerialEventTarget is about to be destroyed. Differential Revision: https://phabricator.services.mozilla.com/D129840 --- dom/base/DocGroup.cpp | 10 ++ dom/base/EventSource.cpp | 10 ++ dom/file/ipc/RemoteLazyInputStreamThread.cpp | 11 ++ dom/localstorage/LSObject.cpp | 10 ++ .../webrtc/libwebrtcglue/CallWorkerThread.h | 13 +++ dom/websocket/WebSocket.cpp | 10 ++ dom/workers/WorkerEventTarget.cpp | 10 ++ dom/workers/WorkerPrivate.cpp | 11 ++ ipc/chromium/src/base/message_loop.cc | 67 +++++++++-- ipc/chromium/src/base/message_loop.h | 3 +- ipc/chromium/src/base/message_pump.h | 4 +- ipc/glue/MessagePump.cpp | 6 +- ipc/glue/MessagePump.h | 16 +-- netwerk/base/nsSocketTransportService2.cpp | 12 ++ netwerk/base/nsStreamTransportService.cpp | 11 ++ netwerk/base/nsStreamTransportService.h | 1 - xpcom/build/XPCOMInit.cpp | 1 - xpcom/tests/gtest/TestThrottledEventQueue.cpp | 8 ++ xpcom/threads/AbstractThread.cpp | 28 ++--- xpcom/threads/DelayedRunnable.cpp | 109 ++++++++++++------ xpcom/threads/DelayedRunnable.h | 19 +-- xpcom/threads/LazyIdleThread.cpp | 10 ++ xpcom/threads/SharedThreadPool.cpp | 4 +- xpcom/threads/SharedThreadPool.h | 22 ++-- xpcom/threads/SynchronizedEventQueue.h | 9 ++ xpcom/threads/TaskQueue.cpp | 93 +++++++-------- xpcom/threads/TaskQueue.h | 33 ++---- xpcom/threads/ThreadEventQueue.cpp | 42 +++++++ xpcom/threads/ThreadEventQueue.h | 6 + xpcom/threads/ThreadEventTarget.cpp | 37 ++---- xpcom/threads/ThreadEventTarget.h | 17 +-- xpcom/threads/ThrottledEventQueue.cpp | 18 +++ xpcom/threads/moz.build | 2 +- xpcom/threads/nsIDelayedRunnableObserver.h | 50 -------- xpcom/threads/nsIEventTarget.idl | 42 +++++++ xpcom/threads/nsITargetShutdownTask.h | 37 ++++++ xpcom/threads/nsThread.cpp | 34 +++--- xpcom/threads/nsThread.h | 6 - xpcom/threads/nsThreadManager.cpp | 44 ++----- xpcom/threads/nsThreadManager.h | 8 -- xpcom/threads/nsThreadPool.cpp | 10 ++ 41 files changed, 559 insertions(+), 335 deletions(-) delete mode 100644 xpcom/threads/nsIDelayedRunnableObserver.h create mode 100644 xpcom/threads/nsITargetShutdownTask.h diff --git a/dom/base/DocGroup.cpp b/dom/base/DocGroup.cpp index 7347a80a0e99..30913c71e792 100644 --- a/dom/base/DocGroup.cpp +++ b/dom/base/DocGroup.cpp @@ -87,6 +87,16 @@ LabellingEventTarget::DelayedDispatch(already_AddRefed, uint32_t) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +LabellingEventTarget::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LabellingEventTarget::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP LabellingEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { *aIsOnCurrentThread = NS_IsMainThread(); diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp index 2948f61661a7..5ab25cf587fa 100644 --- a/dom/base/EventSource.cpp +++ b/dom/base/EventSource.cpp @@ -1927,6 +1927,16 @@ EventSourceImpl::DelayedDispatch(already_AddRefed aEvent, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + //----------------------------------------------------------------------------- // EventSourceImpl::nsIThreadRetargetableStreamListener //----------------------------------------------------------------------------- diff --git a/dom/file/ipc/RemoteLazyInputStreamThread.cpp b/dom/file/ipc/RemoteLazyInputStreamThread.cpp index 349844afb2d2..d666cb4a8ea7 100644 --- a/dom/file/ipc/RemoteLazyInputStreamThread.cpp +++ b/dom/file/ipc/RemoteLazyInputStreamThread.cpp @@ -6,6 +6,7 @@ #include "RemoteLazyInputStreamThread.h" +#include "ErrorList.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/StaticMutex.h" #include "mozilla/StaticPtr.h" @@ -242,6 +243,16 @@ RemoteLazyInputStreamThread::DelayedDispatch(already_AddRefed, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +RemoteLazyInputStreamThread::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + bool IsOnDOMFileThread() { StaticMutexAutoLock lock(gRemoteLazyThreadMutex); diff --git a/dom/localstorage/LSObject.cpp b/dom/localstorage/LSObject.cpp index 44a74ad7d7ae..b6d25b22dd3a 100644 --- a/dom/localstorage/LSObject.cpp +++ b/dom/localstorage/LSObject.cpp @@ -1195,6 +1195,16 @@ NestedEventTargetWrapper::DelayedDispatch(already_AddRefed aEvent, "NestedEventTargetWrapper"); } +NS_IMETHODIMP +NestedEventTargetWrapper::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +NestedEventTargetWrapper::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + nsresult RequestHelper::StartAndReturnResponse(LSRequestResponse& aResponse) { AssertIsOnOwningThread(); diff --git a/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h index 041de97d552a..b88fb2897f84 100644 --- a/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h +++ b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h @@ -37,6 +37,9 @@ class CallWorkerThread final : public AbstractThread, DelayedDispatch(already_AddRefed aEvent, uint32_t aDelayMs) override; + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override; + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override; + const UniquePtr> mWebrtcTaskQueue; @@ -79,6 +82,16 @@ CallWorkerThread::DelayedDispatch(already_AddRefed aEvent, mWebrtcTaskQueue->CreateTaskRunner(std::move(event)), aDelayMs); } +NS_IMETHODIMP CallWorkerThread::RegisterShutdownTask( + nsITargetShutdownTask* aTask) { + return mWebrtcTaskQueue->mTaskQueue->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP CallWorkerThread::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + return mWebrtcTaskQueue->mTaskQueue->UnregisterShutdownTask(aTask); +} + //----------------------------------------------------------------------------- // nsIDirectTaskDispatcher //----------------------------------------------------------------------------- diff --git a/dom/websocket/WebSocket.cpp b/dom/websocket/WebSocket.cpp index 37de8714978d..a916011f3e98 100644 --- a/dom/websocket/WebSocket.cpp +++ b/dom/websocket/WebSocket.cpp @@ -2756,6 +2756,16 @@ WebSocketImpl::DelayedDispatch(already_AddRefed, uint32_t) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP WebSocketImpl::IsOnCurrentThread(bool* aResult) { *aResult = IsTargetThread(); diff --git a/dom/workers/WorkerEventTarget.cpp b/dom/workers/WorkerEventTarget.cpp index 5cbe004a16b5..a1246d07436a 100644 --- a/dom/workers/WorkerEventTarget.cpp +++ b/dom/workers/WorkerEventTarget.cpp @@ -119,6 +119,16 @@ WorkerEventTarget::DelayedDispatch(already_AddRefed, uint32_t) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +WorkerEventTarget::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WorkerEventTarget::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP_(bool) WorkerEventTarget::IsOnCurrentThreadInfallible() { MutexAutoLock lock(mMutex); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index eff33ba065eb..c373d3210430 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -5653,6 +5653,17 @@ WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WorkerPrivate::EventTarget::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { // May be called on any thread! diff --git a/ipc/chromium/src/base/message_loop.cc b/ipc/chromium/src/base/message_loop.cc index e5da7b61adf8..425d6c4ebcd1 100644 --- a/ipc/chromium/src/base/message_loop.cc +++ b/ipc/chromium/src/base/message_loop.cc @@ -16,6 +16,8 @@ #include "mozilla/Atomics.h" #include "mozilla/Mutex.h" #include "mozilla/ProfilerRunnable.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" #include "nsThreadUtils.h" #if defined(OS_MACOSX) @@ -85,11 +87,29 @@ static LPTOP_LEVEL_EXCEPTION_FILTER GetTopSEHFilter() { //------------------------------------------------------------------------------ class MessageLoop::EventTarget : public nsISerialEventTarget, + public nsITargetShutdownTask, public MessageLoop::DestructionObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET_FULL + void TargetShutdown() override { + nsTArray> shutdownTasks; + { + mozilla::MutexAutoLock lock(mMutex); + if (mShutdownTasksRun) { + return; + } + mShutdownTasksRun = true; + shutdownTasks = std::move(mShutdownTasks); + mShutdownTasks.Clear(); + } + + for (auto& task : shutdownTasks) { + task->TargetShutdown(); + } + } + explicit EventTarget(MessageLoop* aLoop) : mMutex("MessageLoop::EventTarget"), mLoop(aLoop) { aLoop->AddDestructionObserver(this); @@ -100,18 +120,25 @@ class MessageLoop::EventTarget : public nsISerialEventTarget, if (mLoop) { mLoop->RemoveDestructionObserver(this); } + MOZ_ASSERT(mShutdownTasks.IsEmpty()); } void WillDestroyCurrentMessageLoop() override { - mozilla::MutexAutoLock lock(mMutex); - // The MessageLoop is being destroyed and we are called from its destructor - // There's no real need to remove ourselves from the destruction observer - // list. But it makes things look tidier. - mLoop->RemoveDestructionObserver(this); - mLoop = nullptr; + { + mozilla::MutexAutoLock lock(mMutex); + // The MessageLoop is being destroyed and we are called from its + // destructor There's no real need to remove ourselves from the + // destruction observer list. But it makes things look tidier. + mLoop->RemoveDestructionObserver(this); + mLoop = nullptr; + } + + TargetShutdown(); } mozilla::Mutex mMutex; + bool mShutdownTasksRun GUARDED_BY(mMutex) = false; + nsTArray> mShutdownTasks GUARDED_BY(mMutex); MessageLoop* mLoop GUARDED_BY(mMutex); }; @@ -165,6 +192,26 @@ MessageLoop::EventTarget::DelayedDispatch(already_AddRefed aEvent, return NS_OK; } +NS_IMETHODIMP +MessageLoop::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + mozilla::MutexAutoLock lock(mMutex); + if (!mLoop || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +NS_IMETHODIMP +MessageLoop::EventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + mozilla::MutexAutoLock lock(mMutex); + if (!mLoop || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + //------------------------------------------------------------------------------ // static @@ -175,7 +222,7 @@ void MessageLoop::set_current(MessageLoop* loop) { get_tls_ptr().Set(loop); } static mozilla::Atomic message_loop_id_seq(0); -MessageLoop::MessageLoop(Type type, nsIEventTarget* aEventTarget) +MessageLoop::MessageLoop(Type type, nsISerialEventTarget* aEventTarget) : type_(type), id_(++message_loop_id_seq), nestable_tasks_allowed_(true), @@ -257,7 +304,9 @@ MessageLoop::MessageLoop(Type type, nsIEventTarget* aEventTarget) // We want GetCurrentSerialEventTarget() to return the real nsThread if it // will be used to dispatch tasks. However, under all other cases; we'll want // it to return this MessageLoop's EventTarget. - if (!pump_->GetXPCOMThread()) { + if (nsISerialEventTarget* thread = pump_->GetXPCOMThread()) { + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(mEventTarget)); + } else { mozilla::SerialEventTargetGuard::Set(mEventTarget); } } @@ -379,7 +428,7 @@ void MessageLoop::PostIdleTask(already_AddRefed task) { // Possibly called on a background thread! void MessageLoop::PostTask_Helper(already_AddRefed task, int delay_ms) { - if (nsIEventTarget* target = pump_->GetXPCOMThread()) { + if (nsISerialEventTarget* target = pump_->GetXPCOMThread()) { nsresult rv; if (delay_ms) { rv = target->DelayedDispatch(std::move(task), delay_ms); diff --git a/ipc/chromium/src/base/message_loop.h b/ipc/chromium/src/base/message_loop.h index 7d2d3d4d0e09..33016a3d1952 100644 --- a/ipc/chromium/src/base/message_loop.h +++ b/ipc/chromium/src/base/message_loop.h @@ -30,7 +30,6 @@ #include "nsIRunnable.h" #include "nsThreadUtils.h" -class nsIEventTarget; class nsISerialEventTarget; namespace mozilla { @@ -200,7 +199,7 @@ class MessageLoop : public base::MessagePump::Delegate { // Normally, it is not necessary to instantiate a MessageLoop. Instead, it // is typical to make use of the current thread's MessageLoop instance. explicit MessageLoop(Type type = TYPE_DEFAULT, - nsIEventTarget* aEventTarget = nullptr); + nsISerialEventTarget* aEventTarget = nullptr); ~MessageLoop(); // Returns the type passed to the constructor. diff --git a/ipc/chromium/src/base/message_pump.h b/ipc/chromium/src/base/message_pump.h index 04d96c34a100..884c016d204d 100644 --- a/ipc/chromium/src/base/message_pump.h +++ b/ipc/chromium/src/base/message_pump.h @@ -9,7 +9,7 @@ #include "nsISupportsImpl.h" -class nsIEventTarget; +class nsISerialEventTarget; namespace base { @@ -129,7 +129,7 @@ class MessagePump { virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) = 0; // If returned, just use the nsThread. - virtual nsIEventTarget* GetXPCOMThread() { return nullptr; } + virtual nsISerialEventTarget* GetXPCOMThread() { return nullptr; } protected: virtual ~MessagePump(){}; diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp index 9da1aac81afc..1c2f377b2d71 100644 --- a/ipc/glue/MessagePump.cpp +++ b/ipc/glue/MessagePump.cpp @@ -58,7 +58,7 @@ class DoWorkRunnable final : public CancelableRunnable, } /* namespace ipc */ } /* namespace mozilla */ -MessagePump::MessagePump(nsIEventTarget* aEventTarget) +MessagePump::MessagePump(nsISerialEventTarget* aEventTarget) : mEventTarget(aEventTarget) { mDoWorkEvent = new DoWorkRunnable(this); } @@ -163,13 +163,13 @@ void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) { nsITimer::TYPE_ONE_SHOT); } -nsIEventTarget* MessagePump::GetXPCOMThread() { +nsISerialEventTarget* MessagePump::GetXPCOMThread() { if (mEventTarget) { return mEventTarget; } // Main thread - return GetMainThreadEventTarget(); + return GetMainThreadSerialEventTarget(); } void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) { diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h index 618f3faaf307..78269b7bd42f 100644 --- a/ipc/glue/MessagePump.h +++ b/ipc/glue/MessagePump.h @@ -30,7 +30,7 @@ class MessagePump : public base::MessagePumpDefault { friend class DoWorkRunnable; public: - explicit MessagePump(nsIEventTarget* aEventTarget); + explicit MessagePump(nsISerialEventTarget* aEventTarget); // From base::MessagePump. virtual void Run(base::MessagePump::Delegate* aDelegate) override; @@ -45,7 +45,7 @@ class MessagePump : public base::MessagePumpDefault { virtual void ScheduleDelayedWork( const base::TimeTicks& aDelayedWorkTime) override; - virtual nsIEventTarget* GetXPCOMThread() override; + virtual nsISerialEventTarget* GetXPCOMThread() override; protected: virtual ~MessagePump(); @@ -55,7 +55,7 @@ class MessagePump : public base::MessagePumpDefault { void DoDelayedWork(base::MessagePump::Delegate* aDelegate); protected: - nsIEventTarget* mEventTarget; + nsISerialEventTarget* mEventTarget; // mDelayedWorkTimer and mEventTarget are set in Run() by this class or its // subclasses. @@ -80,7 +80,7 @@ class MessagePumpForChildProcess final : public MessagePump { class MessagePumpForNonMainThreads final : public MessagePump { public: - explicit MessagePumpForNonMainThreads(nsIEventTarget* aEventTarget) + explicit MessagePumpForNonMainThreads(nsISerialEventTarget* aEventTarget) : MessagePump(aEventTarget) {} virtual void Run(base::MessagePump::Delegate* aDelegate) override; @@ -110,7 +110,7 @@ class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI, // The main run loop for this thread. virtual void DoRunLoop() override; - virtual nsIEventTarget* GetXPCOMThread() override { + virtual nsISerialEventTarget* GetXPCOMThread() override { return nullptr; // not sure what to do with this one } @@ -151,20 +151,20 @@ class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI, */ class MessagePumpForAndroidUI : public base::MessagePump { public: - explicit MessagePumpForAndroidUI(nsIEventTarget* aEventTarget) + explicit MessagePumpForAndroidUI(nsISerialEventTarget* aEventTarget) : mEventTarget(aEventTarget) {} virtual void Run(Delegate* delegate); virtual void Quit(); virtual void ScheduleWork(); virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time); - virtual nsIEventTarget* GetXPCOMThread() { return mEventTarget; } + virtual nsISerialEventTarget* GetXPCOMThread() { return mEventTarget; } private: ~MessagePumpForAndroidUI() {} MessagePumpForAndroidUI() {} - nsIEventTarget* mEventTarget; + nsISerialEventTarget* mEventTarget; }; #endif // defined(MOZ_WIDGET_ANDROID) diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp index d609a48c354c..5375a2d7c13e 100644 --- a/netwerk/base/nsSocketTransportService2.cpp +++ b/netwerk/base/nsSocketTransportService2.cpp @@ -298,6 +298,18 @@ nsSocketTransportService::DelayedDispatch(already_AddRefed, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsSocketTransportService::RegisterShutdownTask(nsITargetShutdownTask* task) { + nsCOMPtr thread = GetThreadSafely(); + return thread ? thread->RegisterShutdownTask(task) : NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsSocketTransportService::UnregisterShutdownTask(nsITargetShutdownTask* task) { + nsCOMPtr thread = GetThreadSafely(); + return thread ? thread->UnregisterShutdownTask(task) : NS_ERROR_UNEXPECTED; +} + NS_IMETHODIMP nsSocketTransportService::IsOnCurrentThread(bool* result) { *result = OnSocketThread(); diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp index 47a9dc0f32c3..57855d800000 100644 --- a/netwerk/base/nsStreamTransportService.cpp +++ b/netwerk/base/nsStreamTransportService.cpp @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsStreamTransportService.h" +#include "ErrorList.h" #include "nsXPCOMCIDInternal.h" #include "nsNetSegmentUtils.h" #include "nsTransportUtils.h" @@ -298,6 +299,16 @@ nsStreamTransportService::DelayedDispatch(already_AddRefed aEvent, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsStreamTransportService::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStreamTransportService::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP_(bool) nsStreamTransportService::IsOnCurrentThreadInfallible() { nsCOMPtr pool; diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h index 0f86ef687388..8e397277b3f5 100644 --- a/netwerk/base/nsStreamTransportService.h +++ b/netwerk/base/nsStreamTransportService.h @@ -5,7 +5,6 @@ #ifndef nsStreamTransportService_h__ #define nsStreamTransportService_h__ -#include "nsIDelayedRunnableObserver.h" #include "nsIStreamTransportService.h" #include "nsIEventTarget.h" #include "nsIObserver.h" diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 2b7934e7848d..b11ae9d40476 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -636,7 +636,6 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) { mozilla::AppShutdown::AdvanceShutdownPhase( mozilla::ShutdownPhase::XPCOMShutdownThreads); - nsThreadManager::get().CancelBackgroundDelayedRunnables(); gXPCOMThreadsShutDown = true; NS_ProcessPendingEvents(thread); diff --git a/xpcom/tests/gtest/TestThrottledEventQueue.cpp b/xpcom/tests/gtest/TestThrottledEventQueue.cpp index 834e6c081326..7dc7ba7adc8a 100644 --- a/xpcom/tests/gtest/TestThrottledEventQueue.cpp +++ b/xpcom/tests/gtest/TestThrottledEventQueue.cpp @@ -85,6 +85,14 @@ struct RunnableQueue : nsISerialEventTarget { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + // nsISupports methods NS_DECL_THREADSAFE_ISUPPORTS diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp index e523d814d681..40d6cdc3e1dd 100644 --- a/xpcom/threads/AbstractThread.cpp +++ b/xpcom/threads/AbstractThread.cpp @@ -34,14 +34,12 @@ MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS; class XPCOMThreadWrapper final : public AbstractThread, public nsIThreadObserver, - public nsIDirectTaskDispatcher, - public nsIDelayedRunnableObserver { + public nsIDirectTaskDispatcher { public: XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch, bool aOnThread) : AbstractThread(aRequireTailDispatch), mThread(aThread), - mDelayedRunnableObserver(do_QueryInterface(mThread)), mDirectTaskDispatcher(do_QueryInterface(aThread)), mOnThread(aOnThread) { MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher); @@ -97,6 +95,14 @@ class XPCOMThreadWrapper final : public AbstractThread, // Prevent a GCC warning about the other overload of Dispatch being hidden. using AbstractThread::Dispatch; + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->RegisterShutdownTask(aTask); + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->UnregisterShutdownTask(aTask); + } + bool IsCurrentThreadIn() const override { return mThread->IsOnCurrentThread(); } @@ -164,22 +170,8 @@ class XPCOMThreadWrapper final : public AbstractThread, return mDirectTaskDispatcher->HaveDirectTasks(aResult); } - //----------------------------------------------------------------------------- - // nsIDelayedRunnableObserver - //----------------------------------------------------------------------------- - void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override { - mDelayedRunnableObserver->OnDelayedRunnableCreated(aRunnable); - } - void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override { - mDelayedRunnableObserver->OnDelayedRunnableScheduled(aRunnable); - } - void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override { - mDelayedRunnableObserver->OnDelayedRunnableRan(aRunnable); - } - private: const RefPtr mThread; - const nsCOMPtr mDelayedRunnableObserver; const nsCOMPtr mDirectTaskDispatcher; std::unique_ptr mTailDispatcher; const bool mOnThread; @@ -240,8 +232,6 @@ class XPCOMThreadWrapper final : public AbstractThread, NS_INTERFACE_MAP_BEGIN(XPCOMThreadWrapper) NS_INTERFACE_MAP_ENTRY(nsIThreadObserver) NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher) - NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDelayedRunnableObserver, - mDelayedRunnableObserver) NS_INTERFACE_MAP_END_INHERITING(AbstractThread) NS_IMPL_ADDREF_INHERITED(XPCOMThreadWrapper, AbstractThread) diff --git a/xpcom/threads/DelayedRunnable.cpp b/xpcom/threads/DelayedRunnable.cpp index 42b09bf3780a..a9231442a7e7 100644 --- a/xpcom/threads/DelayedRunnable.cpp +++ b/xpcom/threads/DelayedRunnable.cpp @@ -15,62 +15,99 @@ DelayedRunnable::DelayedRunnable(already_AddRefed aTarget, uint32_t aDelay) : mozilla::Runnable("DelayedRunnable"), mTarget(aTarget), - mObserver(do_QueryInterface(mTarget)), - mWrappedRunnable(aRunnable), mDelayedFrom(TimeStamp::NowLoRes()), - mDelay(aDelay) { - MOZ_DIAGNOSTIC_ASSERT(mObserver, - "Target must implement nsIDelayedRunnableObserver"); -} + mDelay(aDelay), + mWrappedRunnable(aRunnable) {} nsresult DelayedRunnable::Init() { - mObserver->OnDelayedRunnableCreated(this); - return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay, - nsITimer::TYPE_ONE_SHOT, mTarget); -} + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + MOZ_ASSERT_UNREACHABLE(); + return NS_ERROR_INVALID_ARG; + } -void DelayedRunnable::CancelTimer() { - MOZ_ASSERT(mTarget->IsOnCurrentThread()); - mTimer->Cancel(); + nsresult rv = mTarget->RegisterShutdownTask(this); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT( + rv == NS_ERROR_UNEXPECTED, + "DelayedRunnable target must support RegisterShutdownTask"); + NS_WARNING("DelayedRunnable init after target is shutdown"); + return rv; + } + + rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay, + nsITimer::TYPE_ONE_SHOT, mTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + mTarget->UnregisterShutdownTask(this); + } + return rv; } NS_IMETHODIMP DelayedRunnable::Run() { - MOZ_ASSERT(mTimer, "DelayedRunnable without Init?"); + MOZ_ASSERT(mTarget->IsOnCurrentThread()); - // Already ran? - if (!mWrappedRunnable) { - return NS_OK; - } + nsCOMPtr runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); - // Are we too early? - if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < - mDelay) { - if (mObserver) { - mObserver->OnDelayedRunnableScheduled(this); + // Already ran? + if (!mWrappedRunnable) { + return NS_OK; } - return NS_OK; // Let the nsITimer run us. + + // Are we too early? + if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < + mDelay) { + return NS_OK; // Let the nsITimer run us. + } + + mTimer->Cancel(); + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); } - mTimer->Cancel(); - return DoRun(); + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); } NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) { - // If we already ran, the timer should have been canceled. - MOZ_ASSERT(mWrappedRunnable); + MOZ_ASSERT(mTarget->IsOnCurrentThread()); - if (mObserver) { - mObserver->OnDelayedRunnableRan(this); + nsCOMPtr runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); + + // We may have already run due to races + if (!mWrappedRunnable) { + return NS_OK; + } + + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); } - return DoRun(); + + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); } -nsresult DelayedRunnable::DoRun() { - nsCOMPtr r = std::move(mWrappedRunnable); - AUTO_PROFILE_FOLLOWING_RUNNABLE(r); - return r->Run(); +void DelayedRunnable::TargetShutdown() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + // Called at shutdown + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + return; + } + mWrappedRunnable = nullptr; + + if (mTimer) { + mTimer->Cancel(); + } } -NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback) +NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback, + nsITargetShutdownTask) } // namespace mozilla diff --git a/xpcom/threads/DelayedRunnable.h b/xpcom/threads/DelayedRunnable.h index 90b505e433bd..242278fa5b0f 100644 --- a/xpcom/threads/DelayedRunnable.h +++ b/xpcom/threads/DelayedRunnable.h @@ -9,14 +9,16 @@ #include "mozilla/TimeStamp.h" #include "nsCOMPtr.h" -#include "nsIDelayedRunnableObserver.h" #include "nsIRunnable.h" +#include "nsITargetShutdownTask.h" #include "nsITimer.h" #include "nsThreadUtils.h" namespace mozilla { -class DelayedRunnable : public Runnable, public nsITimerCallback { +class DelayedRunnable : public Runnable, + public nsITimerCallback, + public nsITargetShutdownTask { public: DelayedRunnable(already_AddRefed aTarget, already_AddRefed aRunnable, uint32_t aDelay); @@ -28,21 +30,22 @@ class DelayedRunnable : public Runnable, public nsITimerCallback { nsresult Init(); /** - * Cancels the underlying timer. Called when the target is going away, so the - * runnable can be released safely on the target thread. + * Called when the target is going away so the runnable can be released safely + * on the target thread. */ - void CancelTimer(); + void TargetShutdown() override; private: ~DelayedRunnable() = default; nsresult DoRun(); const nsCOMPtr mTarget; - const nsCOMPtr mObserver; + const TimeStamp mDelayedFrom; + const uint32_t mDelay; + + mozilla::Mutex mMutex{"DelayedRunnable"}; nsCOMPtr mWrappedRunnable; nsCOMPtr mTimer; - const TimeStamp mDelayedFrom; - uint32_t mDelay; }; } // namespace mozilla diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp index aaf8eac0d6f2..7824b74635c0 100644 --- a/xpcom/threads/LazyIdleThread.cpp +++ b/xpcom/threads/LazyIdleThread.cpp @@ -398,6 +398,16 @@ LazyIdleThread::DelayedDispatch(already_AddRefed, uint32_t) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +LazyIdleThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { if (mThread) { diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp index d359d85b5332..a2de1c44959e 100644 --- a/xpcom/threads/SharedThreadPool.cpp +++ b/xpcom/threads/SharedThreadPool.cpp @@ -173,9 +173,7 @@ NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) { NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool) - : mName(aName), mPool(aPool), mRefCnt(0) { - mEventTarget = aPool; -} + : mName(aName), mPool(aPool), mRefCnt(0) {} SharedThreadPool::~SharedThreadPool() = default; diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h index 936cac5cd8bb..5c29ba11f2e6 100644 --- a/xpcom/threads/SharedThreadPool.h +++ b/xpcom/threads/SharedThreadPool.h @@ -12,7 +12,6 @@ #include "mozilla/RefCountType.h" #include "nsCOMPtr.h" #include "nsID.h" -#include "nsIEventTarget.h" #include "nsIThreadPool.h" #include "nsString.h" #include "nscore.h" @@ -70,8 +69,8 @@ class SharedThreadPool : public nsIThreadPool { NS_IMETHOD Dispatch(already_AddRefed event, uint32_t flags = NS_DISPATCH_NORMAL) override { - return !mEventTarget ? NS_ERROR_NULL_POINTER - : mEventTarget->Dispatch(std::move(event), flags); + return !mPool ? NS_ERROR_NULL_POINTER + : mPool->Dispatch(std::move(event), flags); } NS_IMETHOD DelayedDispatch(already_AddRefed, uint32_t) override { @@ -80,13 +79,20 @@ class SharedThreadPool : public nsIThreadPool { using nsIEventTarget::Dispatch; + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD IsOnCurrentThread(bool* _retval) override { - return !mEventTarget ? NS_ERROR_NULL_POINTER - : mEventTarget->IsOnCurrentThread(_retval); + return !mPool ? NS_ERROR_NULL_POINTER : mPool->IsOnCurrentThread(_retval); } NS_IMETHOD_(bool) IsOnCurrentThreadInfallible() override { - return mEventTarget && mEventTarget->IsOnCurrentThread(); + return mPool && mPool->IsOnCurrentThread(); } // Creates necessary statics. Called once at startup. @@ -117,10 +123,6 @@ class SharedThreadPool : public nsIThreadPool { // Refcount. We implement custom ref counting so that the thread pool is // shutdown in a threadsafe manner and singletonness is preserved. nsrefcnt mRefCnt; - - // mPool QI'd to nsIEventTarget. We cache this, so that we can use - // NS_FORWARD_SAFE_NSIEVENTTARGET above. - nsCOMPtr mEventTarget; }; } // namespace mozilla diff --git a/xpcom/threads/SynchronizedEventQueue.h b/xpcom/threads/SynchronizedEventQueue.h index 161d1b72f619..e4cf1a62af1b 100644 --- a/xpcom/threads/SynchronizedEventQueue.h +++ b/xpcom/threads/SynchronizedEventQueue.h @@ -45,6 +45,9 @@ class ThreadTargetSink { // After this method is called, no more events can be posted. virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0; + virtual nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + virtual nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -110,6 +113,12 @@ class SynchronizedEventQueue : public ThreadTargetSink { */ virtual void PopEventQueue(nsIEventTarget* aTarget) = 0; + /** + * Flush the list of shutdown tasks which were previously registered. After + * this is called, new shutdown tasks cannot be registered. + */ + virtual void RunShutdownTasks() = 0; + protected: virtual ~SynchronizedEventQueue() = default; diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp index 37133eee041a..874f6ae31b65 100644 --- a/xpcom/threads/TaskQueue.cpp +++ b/xpcom/threads/TaskQueue.cpp @@ -8,6 +8,8 @@ #include "mozilla/DelayedRunnable.h" #include "mozilla/ProfilerRunnable.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" #include "nsThreadUtils.h" namespace mozilla { @@ -23,13 +25,12 @@ TaskQueue::TaskQueue(already_AddRefed aTarget, mName(aName) {} TaskQueue::~TaskQueue() { - // No one is referencing this TaskQueue anymore, meaning no tasks can be - // pending as all Runner hold a reference to this TaskQueue. - MOZ_ASSERT(mScheduledDelayedRunnables.IsEmpty()); + // We should never free the TaskQueue if it was destroyed abnormally, meaning + // that all cleanup tasks should be complete if we do. + MOZ_ASSERT(mShutdownTasks.IsEmpty()); } -NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher, - nsIDelayedRunnableObserver); +NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher); TaskDispatcher& TaskQueue::TailDispatcher() { MOZ_ASSERT(IsCurrentThreadIn()); @@ -75,6 +76,30 @@ nsresult TaskQueue::DispatchLocked(nsCOMPtr& aRunnable, return NS_OK; } +nsresult TaskQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + void TaskQueue::AwaitIdle() { MonitorAutoLock mon(mQueueMonitor); AwaitIdleLocked(); @@ -106,62 +131,24 @@ void TaskQueue::AwaitShutdownAndIdle() { } AwaitIdleLocked(); } - -void TaskQueue::OnDelayedRunnableCreated(DelayedRunnable* aRunnable) { -#ifdef DEBUG - MonitorAutoLock mon(mQueueMonitor); - MOZ_ASSERT(!mDelayedRunnablesCancelPromise); -#endif -} - -void TaskQueue::OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - mScheduledDelayedRunnables.AppendElement(aRunnable); -} - -void TaskQueue::OnDelayedRunnableRan(DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - Unused << mScheduledDelayedRunnables.RemoveElement(aRunnable); -} - -auto TaskQueue::CancelDelayedRunnables() -> RefPtr { - MonitorAutoLock mon(mQueueMonitor); - return CancelDelayedRunnablesLocked(); -} - -auto TaskQueue::CancelDelayedRunnablesLocked() -> RefPtr { - mQueueMonitor.AssertCurrentThreadOwns(); - if (mDelayedRunnablesCancelPromise) { - return mDelayedRunnablesCancelPromise; - } - mDelayedRunnablesCancelPromise = - mDelayedRunnablesCancelHolder.Ensure(__func__); - nsCOMPtr cancelRunnable = - NewRunnableMethod("TaskQueue::CancelDelayedRunnablesImpl", this, - &TaskQueue::CancelDelayedRunnablesImpl); - MOZ_ALWAYS_SUCCEEDS(DispatchLocked(/* passed by ref */ cancelRunnable, - NS_DISPATCH_NORMAL, TailDispatch)); - return mDelayedRunnablesCancelPromise; -} - -void TaskQueue::CancelDelayedRunnablesImpl() { - MOZ_ASSERT(IsOnCurrentThread()); - for (const auto& runnable : mScheduledDelayedRunnables) { - runnable->CancelTimer(); - } - mScheduledDelayedRunnables.Clear(); - mDelayedRunnablesCancelHolder.Resolve(true, __func__); -} - RefPtr TaskQueue::BeginShutdown() { // Dispatch any tasks for this queue waiting in the caller's tail dispatcher, // since this is the last opportunity to do so. if (AbstractThread* currentThread = AbstractThread::GetCurrent()) { currentThread->TailDispatchTasksFor(this); } + MonitorAutoLock mon(mQueueMonitor); - Unused << CancelDelayedRunnablesLocked(); + // Dispatch any cleanup tasks to the queue before we put it into full + // shutdown. + for (auto& task : mShutdownTasks) { + nsCOMPtr runnable{task->AsRunnable()}; + MOZ_ALWAYS_SUCCEEDS( + DispatchLocked(runnable, NS_DISPATCH_NORMAL, TailDispatch)); + } + mShutdownTasks.Clear(); mIsShutdown = true; + RefPtr p = mShutdownPromise.Ensure(__func__); MaybeResolveShutdown(); mon.NotifyAll(); diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h index 389221cf36e2..7ce146905483 100644 --- a/xpcom/threads/TaskQueue.h +++ b/xpcom/threads/TaskQueue.h @@ -14,7 +14,6 @@ #include "mozilla/Queue.h" #include "mozilla/RefPtr.h" #include "mozilla/TaskDispatcher.h" -#include "nsIDelayedRunnableObserver.h" #include "nsIDirectTaskDispatcher.h" #include "nsThreadUtils.h" @@ -47,9 +46,7 @@ typedef MozPromise ShutdownPromise; // A TaskQueue does not require explicit shutdown, however it provides a // BeginShutdown() method that places TaskQueue in a shut down state and returns // a promise that gets resolved once all pending tasks have completed -class TaskQueue : public AbstractThread, - public nsIDirectTaskDispatcher, - public nsIDelayedRunnableObserver { +class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher { class EventTargetWrapper; public: @@ -92,10 +89,8 @@ class TaskQueue : public AbstractThread, // So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags) using nsIEventTarget::Dispatch; - // nsIDelayedRunnableObserver - void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override; + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override; + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override; using CancelPromise = MozPromise; @@ -137,11 +132,6 @@ class TaskQueue : public AbstractThread, nsresult DispatchLocked(nsCOMPtr& aRunnable, uint32_t aFlags, DispatchReason aReason = NormalDispatch); - RefPtr CancelDelayedRunnablesLocked(); - - // Cancels any scheduled DelayedRunnables on this TaskQueue. - void CancelDelayedRunnablesImpl(); - void MaybeResolveShutdown() { mQueueMonitor.AssertCurrentThreadOwns(); if (mIsShutdown && !mIsRunning) { @@ -152,8 +142,8 @@ class TaskQueue : public AbstractThread, nsCOMPtr mTarget; - // Monitor that protects the queue, mIsRunning and - // mDelayedRunnablesCancelPromise; + // Monitor that protects the queue, mIsRunning, mIsShutdown and + // mShutdownTasks; Monitor mQueueMonitor MOZ_UNANNOTATED; typedef struct TaskStruct { @@ -164,17 +154,8 @@ class TaskQueue : public AbstractThread, // Queue of tasks to run. Queue mTasks; - // DelayedRunnables (from DelayedDispatch) that are managed by their - // respective timers, but have not yet run. Accessed only on this - // TaskQueue. - nsTArray> mScheduledDelayedRunnables; - - // Manages resolving mDelayedRunnablesCancelPromise. - MozPromiseHolder mDelayedRunnablesCancelHolder; - - // Set once the task to cancel all pending DelayedRunnables has been - // dispatched. Guarded by mQueueMonitor. - RefPtr mDelayedRunnablesCancelPromise; + // List of tasks to run during shutdown. + nsTArray> mShutdownTasks; // The thread currently running the task queue. We store a reference // to this so that IsCurrentThreadIn() can tell if the current thread diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp index 107b896b17ab..933af916fa67 100644 --- a/xpcom/threads/ThreadEventQueue.cpp +++ b/xpcom/threads/ThreadEventQueue.cpp @@ -9,6 +9,7 @@ #include "LeakRefPtr.h" #include "nsComponentManagerUtils.h" +#include "nsITargetShutdownTask.h" #include "nsIThreadInternal.h" #include "nsThreadUtils.h" #include "nsThread.h" @@ -31,6 +32,13 @@ class ThreadEventQueue::NestedSink : public ThreadTargetSink { void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; } + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { if (mQueue) { return mQueue->SizeOfIncludingThis(aMallocSizeOf); @@ -274,6 +282,40 @@ void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) { } } +nsresult ThreadEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult ThreadEventQueue::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void ThreadEventQueue::RunShutdownTasks() { + nsTArray> shutdownTasks; + { + MutexAutoLock lock(mLock); + shutdownTasks = std::move(mShutdownTasks); + mShutdownTasks.Clear(); + mShutdownTasksRun = true; + } + for (auto& task : shutdownTasks) { + task->TargetShutdown(); + } +} + ThreadEventQueue::NestedQueueItem::NestedQueueItem( UniquePtr aQueue, ThreadEventTarget* aEventTarget) : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {} diff --git a/xpcom/threads/ThreadEventQueue.h b/xpcom/threads/ThreadEventQueue.h index 09bc0f680242..7c27d348a895 100644 --- a/xpcom/threads/ThreadEventQueue.h +++ b/xpcom/threads/ThreadEventQueue.h @@ -43,6 +43,10 @@ class ThreadEventQueue final : public SynchronizedEventQueue { void Disconnect(const MutexAutoLock& aProofOfLock) final {} + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final; + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final; + void RunShutdownTasks() final; + already_AddRefed PushEventQueue() final; void PopEventQueue(nsIEventTarget* aTarget) final; @@ -79,6 +83,8 @@ class ThreadEventQueue final : public SynchronizedEventQueue { bool mEventsAreDoomed GUARDED_BY(mLock) = false; nsCOMPtr mObserver GUARDED_BY(mLock); + nsTArray> mShutdownTasks GUARDED_BY(mLock); + bool mShutdownTasksRun GUARDED_BY(mLock) = false; const bool mIsMainThread; }; diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp index 3e36b000289a..cb229669777c 100644 --- a/xpcom/threads/ThreadEventTarget.cpp +++ b/xpcom/threads/ThreadEventTarget.cpp @@ -27,9 +27,7 @@ ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink, mThread = PR_GetCurrentThread(); } -ThreadEventTarget::~ThreadEventTarget() { - MOZ_ASSERT(mScheduledDelayedRunnables.IsEmpty()); -} +ThreadEventTarget::~ThreadEventTarget() = default; void ThreadEventTarget::SetCurrentThread(PRThread* aThread) { mThread = aThread; @@ -37,8 +35,7 @@ void ThreadEventTarget::SetCurrentThread(PRThread* aThread) { void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; } -NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget, - nsIDelayedRunnableObserver) +NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget) NS_IMETHODIMP ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { @@ -116,6 +113,16 @@ ThreadEventTarget::DelayedDispatch(already_AddRefed aEvent, return Dispatch(r.forget(), NS_DISPATCH_NORMAL); } +NS_IMETHODIMP +ThreadEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThreadEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->UnregisterShutdownTask(aTask); +} + NS_IMETHODIMP ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { *aIsOnCurrentThread = IsOnCurrentThread(); @@ -129,23 +136,3 @@ ThreadEventTarget::IsOnCurrentThreadInfallible() { // we are called, we can never be on this thread. return false; } - -void ThreadEventTarget::OnDelayedRunnableCreated(DelayedRunnable* aRunnable) {} - -void ThreadEventTarget::OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - mScheduledDelayedRunnables.AppendElement(aRunnable); -} - -void ThreadEventTarget::OnDelayedRunnableRan(DelayedRunnable* aRunnable) { - MOZ_ASSERT(IsOnCurrentThread()); - Unused << mScheduledDelayedRunnables.RemoveElement(aRunnable); -} - -void ThreadEventTarget::NotifyShutdown() { - MOZ_ASSERT(IsOnCurrentThread()); - for (const auto& runnable : mScheduledDelayedRunnables) { - runnable->CancelTimer(); - } - mScheduledDelayedRunnables.Clear(); -} diff --git a/xpcom/threads/ThreadEventTarget.h b/xpcom/threads/ThreadEventTarget.h index cd766a7f0066..6ced779fb987 100644 --- a/xpcom/threads/ThreadEventTarget.h +++ b/xpcom/threads/ThreadEventTarget.h @@ -10,7 +10,6 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/Mutex.h" #include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink -#include "nsIDelayedRunnableObserver.h" #include "nsISerialEventTarget.h" namespace mozilla { @@ -18,23 +17,13 @@ class DelayedRunnable; // ThreadEventTarget handles the details of posting an event to a thread. It can // be used with any ThreadTargetSink implementation. -class ThreadEventTarget final : public nsISerialEventTarget, - public nsIDelayedRunnableObserver { +class ThreadEventTarget final : public nsISerialEventTarget { public: ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread); NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET_FULL - // nsIDelayedRunnableObserver - void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override; - void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override; - - // Notification from, and on, the owner thread that it is shutting down. - // Cancels any scheduled DelayedRunnables. - void NotifyShutdown(); - // Disconnects the target so that it can no longer post events. void Disconnect(const MutexAutoLock& aProofOfLock) { mSink->Disconnect(aProofOfLock); @@ -59,10 +48,6 @@ class ThreadEventTarget final : public nsISerialEventTarget, RefPtr mSink; bool mIsMainThread; - - // DelayedRunnables (from DelayedDispatch) that are managed by their - // respective timers, but have not yet run. Accessed only on this nsThread. - nsTArray> mScheduledDelayedRunnables; }; } // namespace mozilla diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp index 5584207d1328..b530ae16394e 100644 --- a/xpcom/threads/ThrottledEventQueue.cpp +++ b/xpcom/threads/ThrottledEventQueue.cpp @@ -362,6 +362,14 @@ class ThrottledEventQueue::Inner final : public nsISupports { return NS_ERROR_NOT_IMPLEMENTED; } + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->RegisterShutdownTask(aTask); + } + + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->UnregisterShutdownTask(aTask); + } + bool IsOnCurrentThread() { return mBaseTarget->IsOnCurrentThread(); } NS_DECL_THREADSAFE_ISUPPORTS @@ -425,6 +433,16 @@ ThrottledEventQueue::DelayedDispatch(already_AddRefed aEvent, return mInner->DelayedDispatch(std::move(aEvent), aFlags); } +NS_IMETHODIMP +ThrottledEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThrottledEventQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->UnregisterShutdownTask(aTask); +} + NS_IMETHODIMP ThrottledEventQueue::IsOnCurrentThread(bool* aResult) { *aResult = mInner->IsOnCurrentThread(); diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index 0ed81850fd04..3390ebf532df 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -31,9 +31,9 @@ XPCOM_MANIFESTS += [ EXPORTS += [ "MainThreadUtils.h", "nsICancelableRunnable.h", - "nsIDelayedRunnableObserver.h", "nsIDiscardableRunnable.h", "nsIIdleRunnable.h", + "nsITargetShutdownTask.h", "nsMemoryPressure.h", "nsProcess.h", "nsProxyRelease.h", diff --git a/xpcom/threads/nsIDelayedRunnableObserver.h b/xpcom/threads/nsIDelayedRunnableObserver.h deleted file mode 100644 index ad4e6de51aaa..000000000000 --- a/xpcom/threads/nsIDelayedRunnableObserver.h +++ /dev/null @@ -1,50 +0,0 @@ -/* -*- 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 XPCOM_THREADS_NSIDELAYEDRUNNABLEOBSERVER_H_ -#define XPCOM_THREADS_NSIDELAYEDRUNNABLEOBSERVER_H_ - -#include "nsISupports.h" - -namespace mozilla { -class DelayedRunnable; -} - -#define NS_IDELAYEDRUNNABLEOBSERVER_IID \ - { \ - 0xd226bade, 0xac13, 0x46fe, { \ - 0x9f, 0xcc, 0xde, 0xe7, 0x48, 0x35, 0xcd, 0x82 \ - } \ - } - -class NS_NO_VTABLE nsIDelayedRunnableObserver : public nsISupports { - public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDELAYEDRUNNABLEOBSERVER_IID) - - /** - * Called by the DelayedRunnable after being created, on the dispatching - * thread. This allows for various lifetime checks and gives assertions a - * chance to provide useful stack traces. - */ - virtual void OnDelayedRunnableCreated( - mozilla::DelayedRunnable* aRunnable) = 0; - /** - * Called by the DelayedRunnable on its target thread when delegating the - * responsibility for being run to its underlying timer. - */ - virtual void OnDelayedRunnableScheduled( - mozilla::DelayedRunnable* aRunnable) = 0; - /** - * Called by the DelayedRunnable on its target thread after having been run by - * its underlying timer. - */ - virtual void OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) = 0; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(nsIDelayedRunnableObserver, - NS_IDELAYEDRUNNABLEOBSERVER_IID) - -#endif diff --git a/xpcom/threads/nsIEventTarget.idl b/xpcom/threads/nsIEventTarget.idl index c0d7bc22726a..f36445bc851f 100644 --- a/xpcom/threads/nsIEventTarget.idl +++ b/xpcom/threads/nsIEventTarget.idl @@ -10,9 +10,12 @@ #include "nsCOMPtr.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Atomics.h" + +class nsITargetShutdownTask; %} native alreadyAddRefed_nsIRunnable(already_AddRefed); +[ptr] native nsITargetShutdownTask(nsITargetShutdownTask); [builtinclass, scriptable, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)] interface nsIEventTarget : nsISupports @@ -166,6 +169,45 @@ public: * that delay is zero. */ [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay); + + /** + * Register an task to be run on this event target when it begins shutting + * down. Shutdown tasks may be run in any order, and this function may be + * called from any thread. + * + * The event target may or may not continue accepting events during or after + * the shutdown task. The precise behaviour here depends on the event target. + * + * @param task + * The task to be registered to the target thread. + * NOTE that unlike `dispatch`, this will not leak the task if it fails. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events. + */ + [noscript] void registerShutdownTask(in nsITargetShutdownTask task); + + /** + * Unregisters an task previously registered with registerShutdownTask. This + * function may be called from any thread. + * + * @param task + * The task previously registered with registerShutdownTask + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events, or that the shutdown task cannot be found. + */ + [noscript] void unregisterShutdownTask(in nsITargetShutdownTask task); }; %{C++ diff --git a/xpcom/threads/nsITargetShutdownTask.h b/xpcom/threads/nsITargetShutdownTask.h new file mode 100644 index 000000000000..09ac3c5e5ff5 --- /dev/null +++ b/xpcom/threads/nsITargetShutdownTask.h @@ -0,0 +1,37 @@ +/* -*- 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 XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ +#define XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ + +#include "nsISupports.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +#define NS_ITARGETSHUTDOWNTASK_IID \ + { \ + 0xb08647aa, 0xcfb5, 0x4630, { \ + 0x8e, 0x26, 0x9a, 0xbe, 0xb3, 0x3f, 0x08, 0x40 \ + } \ + } + +class NS_NO_VTABLE nsITargetShutdownTask : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISHUTDOWNTASK_IID) + + virtual void TargetShutdown() = 0; + + already_AddRefed AsRunnable() { + // FIXME: Try QI to nsINamed if available? + return mozilla::NewRunnableMethod("nsITargetShutdownTask::TargetShutdown", + this, + &nsITargetShutdownTask::TargetShutdown); + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITargetShutdownTask, NS_ITARGETSHUTDOWNTASK_IID) + +#endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 3a54b8ffe13c..ec7202353644 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -182,7 +182,6 @@ NS_INTERFACE_MAP_BEGIN(nsThread) NS_INTERFACE_MAP_ENTRY(nsIEventTarget) NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget) NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) - NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDelayedRunnableObserver, mEventTarget) NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { @@ -229,9 +228,6 @@ class nsThreadShutdownEvent : public Runnable { // Creates a cycle between `mThread` and the shutdown context which will be // broken when the thread exits. mThread->mShutdownContext = mShutdownContext; - if (mThread->mEventTarget) { - mThread->mEventTarget->NotifyShutdown(); - } MessageLoop::current()->Quit(); return NS_OK; } @@ -388,6 +384,8 @@ void nsThread::ThreadFunc(void* aArg) { // Now, process incoming events... loop.Run(); + self->mEvents->RunShutdownTasks(); + BackgroundChild::CloseForCurrentThread(); // NB: The main thread does not shut down here! It shuts down via @@ -680,6 +678,22 @@ nsThread::DelayedDispatch(already_AddRefed aEvent, return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs); } +NS_IMETHODIMP +nsThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +nsThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->UnregisterShutdownTask(aTask); +} + NS_IMETHODIMP nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { if (mIsAPoolThreadFree && *mIsAPoolThreadFree) { @@ -1391,18 +1405,6 @@ nsIEventTarget* nsThread::EventTarget() { return this; } nsISerialEventTarget* nsThread::SerialEventTarget() { return this; } -void nsThread::OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) { - mEventTarget->OnDelayedRunnableCreated(aRunnable); -} - -void nsThread::OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) { - mEventTarget->OnDelayedRunnableScheduled(aRunnable); -} - -void nsThread::OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) { - mEventTarget->OnDelayedRunnableRan(aRunnable); -} - nsLocalExecutionRecord nsThread::EnterLocalExecution() { MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode); MOZ_ASSERT(IsOnCurrentThread()); diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index 639d9d42bc1a..821150e89d24 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -22,7 +22,6 @@ #include "mozilla/TaskDispatcher.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" -#include "nsIDelayedRunnableObserver.h" #include "nsIDirectTaskDispatcher.h" #include "nsIEventTarget.h" #include "nsISerialEventTarget.h" @@ -156,7 +155,6 @@ class PerformanceCounterState { // A native thread class nsThread : public nsIThreadInternal, public nsISupportsPriority, - public nsIDelayedRunnableObserver, public nsIDirectTaskDispatcher, private mozilla::LinkedListElement { friend mozilla::LinkedList; @@ -270,10 +268,6 @@ class nsThread : public nsIThreadInternal, mUseHangMonitor = aValue; } - void OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) override; - void OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) override; - void OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) override; - private: void DoMainThreadSpecificProcessing() const; diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp index 3c5c7b6c5855..2f074d84c108 100644 --- a/xpcom/threads/nsThreadManager.cpp +++ b/xpcom/threads/nsThreadManager.cpp @@ -54,9 +54,6 @@ class BackgroundEventTarget final : public nsIEventTarget { already_AddRefed CreateBackgroundTaskQueue( const char* aName); - using CancelPromise = TaskQueue::CancelPromise::AllPromiseType; - RefPtr CancelBackgroundDelayedRunnables(); - void BeginShutdown(nsTArray>&); void FinishShutdown(); @@ -68,7 +65,6 @@ class BackgroundEventTarget final : public nsIEventTarget { Mutex mMutex MOZ_UNANNOTATED; nsTArray> mTaskQueues; - bool mIsBackgroundDelayedRunnablesCanceled; }; NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget) @@ -183,6 +179,16 @@ BackgroundEventTarget::DelayedDispatch(already_AddRefed aRunnable, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + void BackgroundEventTarget::BeginShutdown( nsTArray>& promises) { for (auto& queue : mTaskQueues) { @@ -205,19 +211,6 @@ BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) { return queue.forget(); } -auto BackgroundEventTarget::CancelBackgroundDelayedRunnables() - -> RefPtr { - MOZ_ASSERT(NS_IsMainThread()); - MutexAutoLock lock(mMutex); - mIsBackgroundDelayedRunnablesCanceled = true; - nsTArray> promises; - for (const auto& tq : mTaskQueues) { - promises.AppendElement(tq->CancelDelayedRunnables()); - } - return TaskQueue::CancelPromise::All(GetMainThreadSerialEventTarget(), - promises); -} - extern "C" { // This uses the C language linkage because it's exposed to Rust // via the xpcom/rust/moz_task crate. @@ -372,6 +365,8 @@ void nsThreadManager::Shutdown() { // Empty the main thread event queue before we begin shutting down threads. NS_ProcessPendingEvents(mMainThread); + mMainThread->mEvents->RunShutdownTasks(); + nsTArray> promises; mBackgroundEventTarget->BeginShutdown(promises); @@ -424,8 +419,6 @@ void nsThreadManager::Shutdown() { // in-flight asynchronous thread shutdowns to complete. mMainThread->WaitForAllAsynchronousShutdowns(); - mMainThread->mEventTarget->NotifyShutdown(); - // In case there are any more events somehow... NS_ProcessPendingEvents(mMainThread); @@ -514,19 +507,6 @@ nsThreadManager::CreateBackgroundTaskQueue(const char* aName) { return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName); } -void nsThreadManager::CancelBackgroundDelayedRunnables() { - if (!mInitialized) { - return; - } - - bool canceled = false; - mBackgroundEventTarget->CancelBackgroundDelayedRunnables()->Then( - GetMainThreadSerialEventTarget(), __func__, [&] { canceled = true; }); - mozilla::SpinEventLoopUntil( - "nsThreadManager::CancelBackgroundDelayedRunnables"_ns, - [&]() { return canceled; }); -} - nsThread* nsThreadManager::GetCurrentThread() { // read thread local storage void* data = PR_GetThreadPrivate(mCurThreadIndex); diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h index a84d629069f1..a424a930e934 100644 --- a/xpcom/threads/nsThreadManager.h +++ b/xpcom/threads/nsThreadManager.h @@ -67,14 +67,6 @@ class nsThreadManager : public nsIThreadManager { already_AddRefed CreateBackgroundTaskQueue( const char* aName); - // For each background TaskQueue cancel pending DelayedRunnables, and prohibit - // creating future DelayedRunnables for them, since we'll soon be shutting - // them down. - // Pending DelayedRunnables are canceled on their respective TaskQueue. - // We block main thread until they are all done, but spin the eventloop in the - // meantime. - void CancelBackgroundDelayedRunnables(); - ~nsThreadManager(); void EnableMainThreadEventPrioritization(); diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp index ab7d341d231c..68fde2319c4f 100644 --- a/xpcom/threads/nsThreadPool.cpp +++ b/xpcom/threads/nsThreadPool.cpp @@ -369,6 +369,16 @@ nsThreadPool::DelayedDispatch(already_AddRefed, uint32_t) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsThreadPool::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP_(bool) nsThreadPool::IsOnCurrentThreadInfallible() { return gCurrentThreadPool.get() == this; From 3b47cbe61eb6261fd2355dbf93b45424927f9862 Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:04 +0000 Subject: [PATCH 11/43] Bug 1738103 - Part 5: Basic TargetShutdownTask tests, r=KrisWright Differential Revision: https://phabricator.services.mozilla.com/D141541 --- xpcom/tests/gtest/TestTaskQueue.cpp | 97 +++++++++++++++++++++++++++++ xpcom/tests/gtest/TestThreads.cpp | 97 +++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/xpcom/tests/gtest/TestTaskQueue.cpp b/xpcom/tests/gtest/TestTaskQueue.cpp index fe09567ed133..076c876f1c52 100644 --- a/xpcom/tests/gtest/TestTaskQueue.cpp +++ b/xpcom/tests/gtest/TestTaskQueue.cpp @@ -4,10 +4,13 @@ * 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 #include "gtest/gtest.h" #include "mozilla/SharedThreadPool.h" +#include "mozilla/SyncRunnable.h" #include "mozilla/TaskQueue.h" #include "mozilla/Unused.h" +#include "nsITargetShutdownTask.h" #include "VideoUtils.h" namespace TestTaskQueue { @@ -100,6 +103,100 @@ TEST(TaskQueue, GetCurrentSerialEventTarget) tq1->AwaitShutdownAndIdle(); } +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(TaskQueue, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared(); + auto runnableFromShutdownRun = std::make_shared(); + + RefPtr tq = new TaskQueue( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr dummyTask = new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + tq->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + nsCOMPtr dummyTask = + new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(TaskQueue, UnregisteredShutdownTask) +{ + RefPtr tq = new TaskQueue( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + MOZ_ALWAYS_SUCCEEDS(tq->UnregisterShutdownTask(shutdownTask)); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); +} + TEST(AbstractThread, GetCurrentSerialEventTarget) { RefPtr mainThread = AbstractThread::GetCurrent(); diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp index 91fc8709d5c8..62aa29d0a209 100644 --- a/xpcom/tests/gtest/TestThreads.cpp +++ b/xpcom/tests/gtest/TestThreads.cpp @@ -7,11 +7,14 @@ #include "nsThreadUtils.h" #include #include +#include #include "nspr.h" #include "nsCOMPtr.h" +#include "nsITargetShutdownTask.h" #include "nsIThread.h" #include "nsXPCOM.h" #include "mozilla/Monitor.h" +#include "mozilla/SyncRunnable.h" #include "gtest/gtest.h" using namespace mozilla; @@ -279,3 +282,97 @@ TEST(Threads, GetCurrentSerialEventTarget) MOZ_ALWAYS_SUCCEEDS(rv); thread->Shutdown(); } + +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(Threads, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared(); + auto runnableFromShutdownRun = std::make_shared(); + + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr dummyTask = new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + thread->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + nsCOMPtr dummyTask = + new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + thread->Shutdown(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(Threads, UnregisteredShutdownTask) +{ + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + MOZ_ALWAYS_SUCCEEDS(thread->UnregisterShutdownTask(shutdownTask)); + + thread->Shutdown(); +} From 737bb9fe3564ac0d431c6a11998455baf14bf835 Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Wed, 23 Mar 2022 22:22:05 +0000 Subject: [PATCH 12/43] Bug 1738104 - Shutdown IPC actors during EventTarget shutdown, r=ipc-reviewers,mccr8 Differential Revision: https://phabricator.services.mozilla.com/D129841 --- ipc/glue/MessageChannel.cpp | 52 ++++++++++++++++++++++++++++++++++--- ipc/glue/MessageChannel.h | 22 ++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index ecf2d8a9c187..331da2724ef6 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -501,6 +501,7 @@ MessageChannel::~MessageChannel() { MOZ_RELEASE_ASSERT(mPendingResponses.empty()); MOZ_RELEASE_ASSERT(!mChannelErrorTask); MOZ_RELEASE_ASSERT(mPending.isEmpty()); + MOZ_RELEASE_ASSERT(!mShutdownTask); } #ifdef DEBUG @@ -590,6 +591,12 @@ void MessageChannel::Clear() { // through this channel after it's Clear()'ed can cause this process to // crash. + if (mShutdownTask) { + mShutdownTask->Clear(); + mWorkerThread->UnregisterShutdownTask(mShutdownTask); + } + mShutdownTask = nullptr; + if (NS_IsMainThread() && gParentProcessBlocker == this) { gParentProcessBlocker = nullptr; } @@ -621,15 +628,34 @@ void MessageChannel::Clear() { bool MessageChannel::Open(ScopedPort aPort, Side aSide, nsISerialEventTarget* aEventTarget) { + nsCOMPtr eventTarget = + aEventTarget ? aEventTarget : GetCurrentSerialEventTarget(); + MOZ_RELEASE_ASSERT(eventTarget, + "Must open MessageChannel on a nsISerialEventTarget"); + MOZ_RELEASE_ASSERT(eventTarget->IsOnCurrentThread(), + "Must open MessageChannel from worker thread"); + + auto shutdownTask = MakeRefPtr(eventTarget, this); + nsresult rv = eventTarget->RegisterShutdownTask(shutdownTask); + MOZ_ASSERT(rv != NS_ERROR_NOT_IMPLEMENTED, + "target for MessageChannel must support shutdown tasks"); + if (rv == NS_ERROR_UNEXPECTED) { + // If shutdown tasks have already started running, dispatch our shutdown + // task manually. + NS_WARNING("Opening MessageChannel on EventTarget in shutdown"); + rv = eventTarget->Dispatch(shutdownTask->AsRunnable()); + } + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "error registering ShutdownTask for MessageChannel"); + { MonitorAutoLock lock(*mMonitor); MOZ_RELEASE_ASSERT(!mLink, "Open() called > once"); MOZ_RELEASE_ASSERT(ChannelClosed == mChannelState, "Not currently closed"); MOZ_ASSERT(mSide == UnknownSide); - mWorkerThread = aEventTarget ? aEventTarget : GetCurrentSerialEventTarget(); - MOZ_ASSERT(mWorkerThread, "We should always be on a nsISerialEventTarget"); - + mWorkerThread = eventTarget; + mShutdownTask = shutdownTask; mLink = MakeUnique(this, std::move(aPort)); mSide = aSide; } @@ -2300,5 +2326,25 @@ void MessageChannel::SetIsCrossProcess(bool aIsCrossProcess) { } } +NS_IMPL_ISUPPORTS(MessageChannel::WorkerTargetShutdownTask, + nsITargetShutdownTask) + +MessageChannel::WorkerTargetShutdownTask::WorkerTargetShutdownTask( + nsISerialEventTarget* aTarget, MessageChannel* aChannel) + : mTarget(aTarget), mChannel(aChannel) {} + +void MessageChannel::WorkerTargetShutdownTask::TargetShutdown() { + MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread()); + IPC_LOG("Closing channel due to event target shutdown"); + if (MessageChannel* channel = std::exchange(mChannel, nullptr)) { + channel->Close(); + } +} + +void MessageChannel::WorkerTargetShutdownTask::Clear() { + MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread()); + mChannel = nullptr; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h index d18be86849c0..62dbddaecdfb 100644 --- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -25,6 +25,7 @@ #include "MessageLink.h" // for HasResultCodes #include "mozilla/ipc/ScopedPort.h" +#include "nsITargetShutdownTask.h" class MessageLoop; @@ -545,6 +546,24 @@ class MessageChannel : HasResultCodes { void RunMessage(ActorLifecycleProxy* aProxy, MessageTask& aTask) REQUIRES(*mMonitor); + class WorkerTargetShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WorkerTargetShutdownTask(nsISerialEventTarget* aTarget, + MessageChannel* aChannel); + + void TargetShutdown() override; + void Clear(); + + private: + ~WorkerTargetShutdownTask() = default; + + const nsCOMPtr mTarget; + // Cleared by MessageChannel before it is destroyed. + MessageChannel* MOZ_NON_OWNING_REF mChannel; + }; + typedef LinkedList> MessageQueue; typedef std::map> CallbackMap; typedef IPC::Message::msgid_t msgid_t; @@ -576,6 +595,9 @@ class MessageChannel : HasResultCodes { // from multiple threads before Open(). nsCOMPtr mWorkerThread; + // Shutdown task to close the channel before mWorkerThread goes away. + RefPtr mShutdownTask GUARDED_BY(*mMonitor); + // Timeout periods are broken up in two to prevent system suspension from // triggering an abort. This method (called by WaitForEvent with a 'did // timeout' flag) decides if we should wait again for half of mTimeoutMs From 2c0c95670753bc1fcbe5c2969b047077adc0e8a3 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Wed, 23 Mar 2022 22:22:51 +0000 Subject: [PATCH 13/43] Bug 1207753 - Various xpcom thread-safety annotations r=nika Differential Revision: https://phabricator.services.mozilla.com/D130579 --- xpcom/base/nsConsoleService.cpp | 8 ++--- xpcom/base/nsConsoleService.h | 13 ++++---- xpcom/base/nsDumpUtils.h | 8 ++--- xpcom/build/SmallArrayLRUCache.h | 8 ++--- xpcom/ds/nsAtomTable.cpp | 10 +++---- xpcom/io/NonBlockingAsyncInputStream.h | 8 ++--- xpcom/io/nsMultiplexInputStream.cpp | 36 ++++++++++++----------- xpcom/tests/gtest/TestSynchronization.cpp | 1 + 8 files changed, 48 insertions(+), 44 deletions(-) diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp index 761c8f297095..80b1e692b68f 100644 --- a/xpcom/base/nsConsoleService.cpp +++ b/xpcom/base/nsConsoleService.cpp @@ -59,12 +59,12 @@ nsConsoleService::MessageElement::~MessageElement() = default; nsConsoleService::nsConsoleService() : mCurrentSize(0), + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mMaximumSize(250), mDeliveringMessage(false), mLock("nsConsoleService.mLock") { - // XXX grab this from a pref! - // hm, but worry about circularity, bc we want to be able to report - // prefs errs... - mMaximumSize = 250; #ifdef XP_WIN // This environment variable controls whether the console service diff --git a/xpcom/base/nsConsoleService.h b/xpcom/base/nsConsoleService.h index 7399138e8495..f4c3dca6bd67 100644 --- a/xpcom/base/nsConsoleService.h +++ b/xpcom/base/nsConsoleService.h @@ -87,26 +87,27 @@ class nsConsoleService final : public nsIConsoleService, public nsIObserver { nsresult MaybeForwardScriptError(nsIConsoleMessage* aMessage, bool* sent); void ClearMessagesForWindowID(const uint64_t innerID); - void ClearMessages(); + void ClearMessages() REQUIRES(mLock); - mozilla::LinkedList mMessages; + mozilla::LinkedList mMessages GUARDED_BY(mLock); // The current size of mMessages. - uint32_t mCurrentSize; + uint32_t mCurrentSize GUARDED_BY(mLock); // The maximum size of mMessages. - uint32_t mMaximumSize; + const uint32_t mMaximumSize; // Are we currently delivering a console message on the main thread? If // so, we suppress incoming messages on the main thread only, to avoid // infinite repitition. + // Only touched on MainThread bool mDeliveringMessage; // Listeners to notify whenever a new message is logged. - ListenerHash mListeners; + ListenerHash mListeners GUARDED_BY(mLock); // To serialize interesting methods. - mozilla::Mutex mLock MOZ_UNANNOTATED; + mozilla::Mutex mLock; }; #endif /* __nsconsoleservice_h__ */ diff --git a/xpcom/base/nsDumpUtils.h b/xpcom/base/nsDumpUtils.h index f18d46d6a38a..5d6913955e02 100644 --- a/xpcom/base/nsDumpUtils.h +++ b/xpcom/base/nsDumpUtils.h @@ -125,8 +125,8 @@ class FifoWatcher : public FdWatcher { explicit FifoWatcher(nsCString aPath) : mDirPath(aPath), mFifoInfoLock("FifoWatcher.mFifoInfoLock") {} - mozilla::Mutex mFifoInfoLock MOZ_UNANNOTATED; // protects mFifoInfo - FifoInfoArray mFifoInfo; + mozilla::Mutex mFifoInfoLock; // protects mFifoInfo + FifoInfoArray mFifoInfo GUARDED_BY(mFifoInfoLock); }; typedef void (*PipeCallback)(const uint8_t aRecvSig); @@ -159,8 +159,8 @@ class SignalPipeWatcher : public FdWatcher { MOZ_ASSERT(NS_IsMainThread()); } - mozilla::Mutex mSignalInfoLock MOZ_UNANNOTATED; // protects mSignalInfo - SignalInfoArray mSignalInfo; + mozilla::Mutex mSignalInfoLock; // protects mSignalInfo + SignalInfoArray mSignalInfo GUARDED_BY(mSignalInfoLock); }; #endif // XP_UNIX } diff --git a/xpcom/build/SmallArrayLRUCache.h b/xpcom/build/SmallArrayLRUCache.h index df4925c30993..d80819b4989c 100644 --- a/xpcom/build/SmallArrayLRUCache.h +++ b/xpcom/build/SmallArrayLRUCache.h @@ -166,7 +166,7 @@ class SmallArrayLRUCache { #endif // SMALLARRAYLRUCACHE_STATS private: - void Clear(const mozilla::OffTheBooksMutexAutoLock&) { + void Clear(const mozilla::OffTheBooksMutexAutoLock&) REQUIRES(mMutex) { for (KeyAndValue* item = &mLRUArray[0]; item != &mLRUArray[mSize]; ++item) { item->mValue = Value{}; } @@ -186,11 +186,11 @@ class SmallArrayLRUCache { constexpr static unsigned ShutdownSize = unsigned(-1); mozilla::OffTheBooksMutex mMutex{"LRU cache"}; - unsigned mSize = 0; - KeyAndValue mLRUArray[LRUCapacity]; + unsigned mSize GUARDED_BY(mMutex) = 0; + KeyAndValue mLRUArray[LRUCapacity] GUARDED_BY(mMutex); #ifdef SMALLARRAYLRUCACHE_STATS // Hit count for each position in the case. +1 for counting not-found cases. - unsigned mCacheFoundAt[LRUCapacity + 1] = {0u}; + unsigned mCacheFoundAt[LRUCapacity + 1] GUARDED_BY(mMutex) = {0u}; #endif // SMALLARRAYLRUCACHE_STATS }; diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp index 1a6c4bfabe86..090639c7b914 100644 --- a/xpcom/ds/nsAtomTable.cpp +++ b/xpcom/ds/nsAtomTable.cpp @@ -186,19 +186,19 @@ static AtomCache sRecentlyUsedMainThreadAtoms; // ConcurrentHashTable. class nsAtomSubTable { friend class nsAtomTable; - Mutex mLock MOZ_UNANNOTATED; + Mutex mLock; PLDHashTable mTable; nsAtomSubTable(); - void GCLocked(GCKind aKind); + void GCLocked(GCKind aKind) REQUIRES(mLock); void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf, - AtomsSizes& aSizes); + AtomsSizes& aSizes) REQUIRES(mLock); - AtomTableEntry* Search(AtomTableKey& aKey) const { + AtomTableEntry* Search(AtomTableKey& aKey) const REQUIRES(mLock) { mLock.AssertCurrentThreadOwns(); return static_cast(mTable.Search(&aKey)); } - AtomTableEntry* Add(AtomTableKey& aKey) { + AtomTableEntry* Add(AtomTableKey& aKey) REQUIRES(mLock) { mLock.AssertCurrentThreadOwns(); return static_cast(mTable.Add(&aKey)); // Infallible } diff --git a/xpcom/io/NonBlockingAsyncInputStream.h b/xpcom/io/NonBlockingAsyncInputStream.h index 1f9835397590..0d065e312adb 100644 --- a/xpcom/io/NonBlockingAsyncInputStream.h +++ b/xpcom/io/NonBlockingAsyncInputStream.h @@ -65,7 +65,7 @@ class NonBlockingAsyncInputStream final : public nsIAsyncInputStream, nsISeekableStream* MOZ_NON_OWNING_REF mWeakSeekableInputStream; nsITellableStream* MOZ_NON_OWNING_REF mWeakTellableInputStream; - Mutex mLock MOZ_UNANNOTATED; + Mutex mLock; struct WaitClosureOnly { WaitClosureOnly(AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget); @@ -77,13 +77,13 @@ class NonBlockingAsyncInputStream final : public nsIAsyncInputStream, // This is set when AsyncWait is called with a callback and with // WAIT_CLOSURE_ONLY as flag. // This is protected by mLock. - Maybe mWaitClosureOnly; + Maybe mWaitClosureOnly GUARDED_BY(mLock); // This is protected by mLock. - RefPtr mAsyncWaitCallback; + RefPtr mAsyncWaitCallback GUARDED_BY(mLock); // This is protected by mLock. - bool mClosed; + bool mClosed GUARDED_BY(mLock); }; } // namespace mozilla diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp index 66a4d695cb55..2f5750449be5 100644 --- a/xpcom/io/nsMultiplexInputStream.cpp +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -66,7 +66,8 @@ class nsMultiplexInputStream final : public nsIMultiplexInputStream, void AsyncWaitCompleted(); // This is used for nsIAsyncInputStreamLength::AsyncLengthWait - void AsyncWaitCompleted(int64_t aLength, const MutexAutoLock& aProofOfLock); + void AsyncWaitCompleted(int64_t aLength, const MutexAutoLock& aProofOfLock) + REQUIRES(mLock); struct StreamData { nsresult Initialize(nsIInputStream* aOriginalStream) { @@ -103,7 +104,7 @@ class nsMultiplexInputStream final : public nsIMultiplexInputStream, uint64_t mCurrentPos; }; - Mutex& GetLock() { return mLock; } + Mutex& GetLock() RETURN_CAPABILITY(mLock) { return mLock; } private: ~nsMultiplexInputStream() = default; @@ -112,7 +113,7 @@ class nsMultiplexInputStream final : public nsIMultiplexInputStream, // This method updates mSeekableStreams, mTellableStreams, // mIPCSerializableStreams and mCloneableStreams values. - void UpdateQIMap(StreamData& aStream); + void UpdateQIMap(StreamData& aStream) REQUIRES(mLock); struct MOZ_STACK_CLASS ReadSegmentsState { nsCOMPtr mThisStream; @@ -139,25 +140,26 @@ class nsMultiplexInputStream final : public nsIMultiplexInputStream, bool IsInputStreamLength() const; bool IsAsyncInputStreamLength() const; - Mutex mLock MOZ_UNANNOTATED; // Protects access to all data members. + Mutex mLock; // Protects access to all data members. - nsTArray mStreams; + nsTArray mStreams GUARDED_BY(mLock); - uint32_t mCurrentStream; - bool mStartedReadingCurrent; - nsresult mStatus; - nsCOMPtr mAsyncWaitCallback; - uint32_t mAsyncWaitFlags; - uint32_t mAsyncWaitRequestedCount; - nsCOMPtr mAsyncWaitEventTarget; - nsCOMPtr mAsyncWaitLengthCallback; + uint32_t mCurrentStream GUARDED_BY(mLock); + bool mStartedReadingCurrent GUARDED_BY(mLock); + nsresult mStatus GUARDED_BY(mLock); + nsCOMPtr mAsyncWaitCallback GUARDED_BY(mLock); + uint32_t mAsyncWaitFlags GUARDED_BY(mLock); + uint32_t mAsyncWaitRequestedCount GUARDED_BY(mLock); + nsCOMPtr mAsyncWaitEventTarget GUARDED_BY(mLock); + nsCOMPtr mAsyncWaitLengthCallback + GUARDED_BY(mLock); class AsyncWaitLengthHelper; - RefPtr mAsyncWaitLengthHelper; + RefPtr mAsyncWaitLengthHelper GUARDED_BY(mLock); - uint32_t mSeekableStreams; - uint32_t mIPCSerializableStreams; - uint32_t mCloneableStreams; + uint32_t mSeekableStreams GUARDED_BY(mLock); + uint32_t mIPCSerializableStreams GUARDED_BY(mLock); + uint32_t mCloneableStreams GUARDED_BY(mLock); // These are Atomics so that we can check them in QueryInterface without // taking a lock (to look at mStreams.Length() and the numbers above) diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp index 327f5bfe3fa3..ec45c7f78535 100644 --- a/xpcom/tests/gtest/TestSynchronization.cpp +++ b/xpcom/tests/gtest/TestSynchronization.cpp @@ -237,6 +237,7 @@ TEST(Synchronization, AutoLock) //----------------------------------------------------------------------------- // AutoTryLock tests // +// The thread owns assertions make mutex analysis throw spurious warnings TEST(Synchronization, AutoTryLock) NO_THREAD_SAFETY_ANALYSIS { Mutex l1 MOZ_UNANNOTATED("autotrylock"); From ced0d9770bcc6ec0efca88d388a2f6f76d754634 Mon Sep 17 00:00:00 2001 From: Eitan Isaacson Date: Wed, 23 Mar 2022 22:24:52 +0000 Subject: [PATCH 14/43] Bug 1760880 - Push document resolution in initial cache. r=morgan Testing this is hard because the point of failure would be if we recieved NotifyOfResolutionChange before the IPC doc is constructed. Differential Revision: https://phabricator.services.mozilla.com/D141904 --- accessible/generic/LocalAccessible.cpp | 9 +++++++++ accessible/ipc/RemoteAccessibleBase.cpp | 1 + 2 files changed, 10 insertions(+) diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp index 3a4bba8dccad..270ccb69927b 100644 --- a/accessible/generic/LocalAccessible.cpp +++ b/accessible/generic/LocalAccessible.cpp @@ -3366,6 +3366,15 @@ already_AddRefed LocalAccessible::BundleFieldsForCache( mStateFlags &= ~eOldFrameHasValidTransformStyle; } } + + if (IsDoc()) { + if (PresShell* presShell = AsDoc()->PresShellPtr()) { + // Send the initial resolution of the document. When this changes, we + // will ne notified via nsAS::NotifyOfResolutionChange + float resolution = presShell->GetResolution(); + fields->SetAttribute(nsGkAtoms::resolution, resolution); + } + } } return fields.forget(); diff --git a/accessible/ipc/RemoteAccessibleBase.cpp b/accessible/ipc/RemoteAccessibleBase.cpp index b930957a59c6..22ba6325cb89 100644 --- a/accessible/ipc/RemoteAccessibleBase.cpp +++ b/accessible/ipc/RemoteAccessibleBase.cpp @@ -423,6 +423,7 @@ LayoutDeviceIntRect RemoteAccessibleBase::Bounds() const { // be scaled relative to its parent doc. res = remoteAcc->AsDoc()->mCachedFields->GetAttribute( nsGkAtoms::resolution); + MOZ_ASSERT(res, "No cached document resolution found."); bounds.ScaleRoundOut(res.valueOr(1.0f)); } From 77603d5bca214d87f10f9747876913f2b778af2a Mon Sep 17 00:00:00 2001 From: Thomas Wisniewski Date: Wed, 23 Mar 2022 22:30:35 +0000 Subject: [PATCH 15/43] Bug 1739489 - Add a site intervention for draftjs.org and facebook.com which remaps textinput events to beforeinput, to fix 'crashes' with Mac OSX emoji picker; r=denschub,webcompat-reviewers Differential Revision: https://phabricator.services.mozilla.com/D139413 --- .../extensions/webcompat/data/injections.js | 14 ++++++++ .../js/bug1739489-draftjs-beforeinput.js | 35 +++++++++++++++++++ browser/extensions/webcompat/manifest.json | 2 +- browser/extensions/webcompat/moz.build | 1 + 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js diff --git a/browser/extensions/webcompat/data/injections.js b/browser/extensions/webcompat/data/injections.js index 9c33de32237c..57e5b7625197 100644 --- a/browser/extensions/webcompat/data/injections.js +++ b/browser/extensions/webcompat/data/injections.js @@ -609,6 +609,20 @@ const AVAILABLE_INJECTIONS = [ ], }, }, + { + id: "bug1739489", + platform: "desktop", + domain: "draft.js", + bug: "1739489", + contentScripts: { + matches: ["*://draftjs.org/*", "*://www.facebook.com/*"], + js: [ + { + file: "injections/js/bug1739489-draftjs-beforeinput.js", + }, + ], + }, + }, ]; module.exports = AVAILABLE_INJECTIONS; diff --git a/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js new file mode 100644 index 000000000000..39ce2edf2a6d --- /dev/null +++ b/browser/extensions/webcompat/injections/js/bug1739489-draftjs-beforeinput.js @@ -0,0 +1,35 @@ +"use strict"; + +/** + * Bug 1739489 - Entering an emoji using the MacOS IME "crashes" Draft.js editors. + */ + +/* globals exportFunction */ + +console.info( + "textInput event has been remapped to beforeinput for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1739489 for details." +); + +window.wrappedJSObject.TextEvent = window.wrappedJSObject.InputEvent; + +const { CustomEvent, Event, EventTarget } = window.wrappedJSObject; +var Remapped = [ + [CustomEvent, "constructor"], + [Event, "constructor"], + [Event, "initEvent"], + [EventTarget, "addEventListener"], + [EventTarget, "removeEventListener"], +]; + +for (const [obj, name] of Remapped) { + const { prototype } = obj; + const orig = prototype[name]; + Object.defineProperty(prototype, name, { + value: exportFunction(function(type, b, c, d) { + if (type?.toLowerCase() === "textinput") { + type = "beforeinput"; + } + return orig.call(this, type, b, c, d); + }, window), + }); +} diff --git a/browser/extensions/webcompat/manifest.json b/browser/extensions/webcompat/manifest.json index d8ee726cd792..df084dbde614 100644 --- a/browser/extensions/webcompat/manifest.json +++ b/browser/extensions/webcompat/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Web Compatibility Interventions", "description": "Urgent post-release fixes for web compatibility.", - "version": "31.0.0", + "version": "31.1.0", "applications": { "gecko": { "id": "webcompat@mozilla.org", diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build index f4d0508f3b8f..a71b6878a7d9 100644 --- a/browser/extensions/webcompat/moz.build +++ b/browser/extensions/webcompat/moz.build @@ -83,6 +83,7 @@ FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["js"] += [ "injections/js/bug1724764-amextravel.com-window-print.js", "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js", "injections/js/bug1731825-office365-email-handling-prompt-autohide.js", + "injections/js/bug1739489-draftjs-beforeinput.js", "injections/js/bug1756692-effectiveType-shim.js", ] From 99677756316960d661914ce19c32573be96049bf Mon Sep 17 00:00:00 2001 From: Gerald Squelart Date: Wed, 23 Mar 2022 22:41:07 +0000 Subject: [PATCH 16/43] Bug 1757528 - Don't run nativeallocations TV test on Android - r=canaltinova The regular xpcshell test don't appear to fail, so this additional patch ensures that only the TV (test verify) runs of test_feature_nativeallocations.js are skipped. Differential Revision: https://phabricator.services.mozilla.com/D141814 --- tools/profiler/tests/xpcshell/xpcshell.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/profiler/tests/xpcshell/xpcshell.ini b/tools/profiler/tests/xpcshell/xpcshell.ini index 06a4ee8518ea..c7f7bf0ad7be 100644 --- a/tools/profiler/tests/xpcshell/xpcshell.ini +++ b/tools/profiler/tests/xpcshell/xpcshell.ini @@ -35,7 +35,7 @@ skip-if = # and no allocation markers are gathered. Skip this test in that configuration. [test_feature_nativeallocations.js] skip-if = - toolkit == 'android' # bug 1757528 + os == "android" && verify # bug 1757528 asan tsan socketprocess_networking From 9cbad2f61f7d4eaeeb827f899e29ebcf5240cf33 Mon Sep 17 00:00:00 2001 From: Shane Caraveo Date: Wed, 23 Mar 2022 22:48:40 +0000 Subject: [PATCH 17/43] Bug 1748525 implement idle timeout for extension event pages r=rpl Differential Revision: https://phabricator.services.mozilla.com/D137942 --- .../components/extensions/ExtensionCommon.jsm | 9 ++- .../extensions/parent/ext-backgroundPage.js | 60 ++++++++++++++++ .../test/xpcshell/test_ext_contexts.js | 13 +++- .../test/xpcshell/test_ext_eventpage_idle.js | 69 +++++++++++++++++++ .../test/xpcshell/xpcshell-common.ini | 1 + 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js diff --git a/toolkit/components/extensions/ExtensionCommon.jsm b/toolkit/components/extensions/ExtensionCommon.jsm index e675dd0dac11..62187771cde7 100644 --- a/toolkit/components/extensions/ExtensionCommon.jsm +++ b/toolkit/components/extensions/ExtensionCommon.jsm @@ -2540,9 +2540,14 @@ class EventManager { return false; }; + let { extension } = this.context; + const resetIdle = () => extension?.emit("background-script-reset-idle"); + let fire = { + // Bug 1754866 fire.sync doesn't match documentation. sync: (...args) => { if (shouldFire()) { + resetIdle(); let result = this.context.applySafe(callback, args); this.context.logActivity("api_event", this.name, { args, result }); return result; @@ -2551,6 +2556,7 @@ class EventManager { async: (...args) => { return Promise.resolve().then(() => { if (shouldFire()) { + resetIdle(); let result = this.context.applySafe(callback, args); this.context.logActivity("api_event", this.name, { args, result }); return result; @@ -2561,6 +2567,7 @@ class EventManager { if (!shouldFire()) { throw new Error("Called raw() on unloaded/inactive context"); } + resetIdle(); let result = Reflect.apply(callback, null, args); this.context.logActivity("api_event", this.name, { args, result }); return result; @@ -2568,6 +2575,7 @@ class EventManager { asyncWithoutClone: (...args) => { return Promise.resolve().then(() => { if (shouldFire()) { + resetIdle(); let result = this.context.applySafeWithoutClone(callback, args); this.context.logActivity("api_event", this.name, { args, result }); return result; @@ -2576,7 +2584,6 @@ class EventManager { }, }; - let { extension } = this.context; let { module, event } = this; let unregister = null; diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js index a1e008bbf066..aa91f4636f35 100644 --- a/toolkit/components/extensions/parent/ext-backgroundPage.js +++ b/toolkit/components/extensions/parent/ext-backgroundPage.js @@ -30,6 +30,16 @@ XPCOMUtils.defineLazyGetter(this, "serviceWorkerManager", () => { ); }); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "backgroundIdleTimeout", + "extensions.background.idle.timeout", + 30000, + null, + // Minimum 100ms, max 5min + delay => Math.min(Math.max(delay, 100), 5 * 60 * 1000) +); + // Responsible for the background_page section of the manifest. class BackgroundPage extends HiddenExtensionPage { constructor(extension, options) { @@ -303,6 +313,26 @@ this.backgroundPage = class extends ExtensionAPI { return this.bgInstance.build(); } + observe(subject, topic, data) { + if (topic == "timer-callback") { + let { extension } = this; + this.clearIdleTimer(); + extension?.terminateBackground(); + } + } + + clearIdleTimer() { + this.backgroundTimer?.cancel(); + this.backgroundTimer = null; + } + + resetIdleTimer() { + this.clearIdleTimer(); + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(this, backgroundIdleTimeout, Ci.nsITimer.TYPE_ONE_SHOT); + this.backgroundTimer = timer; + } + async primeBackground(isInStartup = true) { let { extension } = this; @@ -344,7 +374,34 @@ this.backgroundPage = class extends ExtensionAPI { return bgStartupPromise; }; + let resetBackgroundIdle = () => { + this.clearIdleTimer(); + if (!this.extension || extension.persistentBackground) { + // Extension was already shut down or is persistent and + // does not idle timout. + return; + } + // TODO remove at an appropriate point in the future prior + // to general availability. There may be some racy conditions + // with idle timeout between an event starting and the event firing + // but we still want testing with an idle timeout. + if ( + !Services.prefs.getBoolPref("extensions.background.idle.enabled", true) + ) { + return; + } + + this.resetIdleTimer(); + }; + + // Listen for events from the EventManager + extension.on("background-script-reset-idle", resetBackgroundIdle); + // After the background is started, initiate the first timer + extension.once("background-script-started", resetBackgroundIdle); + extension.terminateBackground = async () => { + this.clearIdleTimer(); + extension.off("background-script-reset-idle", resetBackgroundIdle); await bgStartupPromise; this.onShutdown(false); EventManager.clearPrimedListeners(this.extension, false); @@ -398,6 +455,9 @@ this.backgroundPage = class extends ExtensionAPI { } onShutdown(isAppShutdown) { + // Ensure there is no backgroundTimer running + this.clearIdleTimer(); + if (this.bgInstance) { this.bgInstance.shutdown(isAppShutdown); this.bgInstance = null; diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js index 4162d4969257..028f5b563873 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js @@ -2,11 +2,18 @@ const global = this; -var { BaseContext, EventManager } = ExtensionCommon; +var { BaseContext, EventManager, EventEmitter } = ExtensionCommon; + +class FakeExtension extends EventEmitter { + constructor(id) { + super(); + this.id = id; + } +} class StubContext extends BaseContext { constructor() { - let fakeExtension = { id: "test@web.extension" }; + let fakeExtension = new FakeExtension("test@web.extension"); super("testEnv", fakeExtension); this.sandbox = Cu.Sandbox(global); } @@ -112,7 +119,7 @@ add_task(async function test_post_unload_listeners() { class Context extends BaseContext { constructor(principal) { - let fakeExtension = { id: "test@web.extension" }; + let fakeExtension = new FakeExtension("test@web.extension"); super("testEnv", fakeExtension); Object.defineProperty(this, "principal", { value: principal, diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js new file mode 100644 index 000000000000..1ce76c8aefac --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js @@ -0,0 +1,69 @@ +"use strict"; + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); + +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "42", + "42" +); + +Services.prefs.setBoolPref("extensions.eventPages.enabled", true); +// Set minimum idle timeout for testing +Services.prefs.setIntPref("extensions.background.idle.timeout", 0); + +function promiseExtensionEvent(extension, event) { + return new Promise(resolve => extension.extension.once(event, resolve)); +} + +add_setup(async () => { + await AddonTestUtils.promiseStartupManager(); +}); + +add_task(async function test_eventpage_idle() { + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + permissions: ["browserSettings"], + background: { persistent: false }, + }, + background() { + browser.browserSettings.homepageOverride.onChange.addListener(() => { + browser.test.sendMessage("homepageOverride"); + }); + }, + }); + await extension.startup(); + assertPersistentListeners(extension, "browserSettings", "homepageOverride", { + primed: false, + }); + + info(`test idle timeout after startup`); + await promiseExtensionEvent(extension, "shutdown-background-script"); + assertPersistentListeners(extension, "browserSettings", "homepageOverride", { + primed: true, + }); + Services.prefs.setStringPref( + "browser.startup.homepage", + "http://homepage.example.com" + ); + await extension.awaitMessage("homepageOverride"); + ok(true, "homepageOverride.onChange fired"); + + // again after the event is fired + info(`test idle timeout after wakeup`); + await promiseExtensionEvent(extension, "shutdown-background-script"); + assertPersistentListeners(extension, "browserSettings", "homepageOverride", { + primed: true, + }); + Services.prefs.setStringPref( + "browser.startup.homepage", + "http://test.example.com" + ); + await extension.awaitMessage("homepageOverride"); + ok(true, "homepageOverride.onChange fired"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini index 2eb6f51aeb74..4fb4837fd8fa 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini @@ -115,6 +115,7 @@ skip-if = os == "android" || tsan # tsan: bug 1612707 [test_ext_downloads_urlencoded.js] skip-if = os == "android" [test_ext_error_location.js] +[test_ext_eventpage_idle.js] [test_ext_eventpage_warning.js] [test_ext_eventpage_settings.js] [test_ext_experiments.js] From 7641e81228e038ad9d2434bd750aea055d587c81 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Wed, 23 Mar 2022 23:11:34 +0000 Subject: [PATCH 18/43] Bug 1760992 - [devtools] Prevent picking UA shadowRoot elements by default r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D141844 --- devtools/client/inspector/test/browser.ini | 1 + ...owser_inspector_picker-useragent-widget.js | 74 +++++++++++++++++++ .../server/actors/inspector/node-picker.js | 8 ++ 3 files changed, 83 insertions(+) create mode 100644 devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini index 1120be3f9eda..4d2167da6554 100644 --- a/devtools/client/inspector/test/browser.ini +++ b/devtools/client/inspector/test/browser.ini @@ -211,6 +211,7 @@ skip-if = (os == 'win' && processor == 'aarch64') # bug 1533492 [browser_inspector_picker-shift-key.js] [browser_inspector_picker-stop-on-eyedropper.js] [browser_inspector_picker-stop-on-tool-change.js] +[browser_inspector_picker-useragent-widget.js] [browser_inspector_portrait_mode.js] [browser_inspector_pseudoclass-lock.js] [browser_inspector_pseudoclass-menu.js] diff --git a/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js b/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js new file mode 100644 index 000000000000..7bc618c76ca1 --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js @@ -0,0 +1,74 @@ +/* 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/. */ + +"use strict"; + +const TEST_URI = `data:text/html;charset=utf-8, + + `; + +// Test that using the node picker on user agent widgets only selects shadow dom +// elements if `devtools.inspector.showAllAnonymousContent` is true. +// If not, we should only surface the host, in this case, the . +// +// For this test we use an , which is using shadow dom. See +// definition in toolkit/content/widgets/datetimebox.js +add_task(async function() { + // Run the test for both values for devtools.inspector.showAllAnonymousContent + await runUserAgentWidgetPickerTest({ enableAnonymousContent: false }); + await runUserAgentWidgetPickerTest({ enableAnonymousContent: true }); +}); + +async function runUserAgentWidgetPickerTest({ enableAnonymousContent }) { + await pushPref( + "devtools.inspector.showAllAnonymousContent", + enableAnonymousContent + ); + const { inspector, toolbox } = await openInspectorForURL(TEST_URI); + + info("Use the node picker inside the element"); + await startPicker(toolbox); + const onPickerStopped = toolbox.nodePicker.once("picker-stopped"); + await pickElement(inspector, "input", 5, 5); + await onPickerStopped; + + const selectedNode = inspector.selection.nodeFront; + if (enableAnonymousContent) { + // We do not assert specifically which node was selected, we just want to + // check the node was under the shadow DOM for the + const shadowHost = getShadowHost(selectedNode); + ok( + selectedNode.tagName.toLowerCase() !== "input", + "The selected node is not the " + ); + ok(shadowHost, "The selected node is in a shadow root"); + is(shadowHost.tagName.toLowerCase(), "input", "The shadowHost is "); + } else { + is( + selectedNode.tagName.toLowerCase(), + "input", + "The selected node is the " + ); + } +} + +/** + * Retrieve the nodeFront for the shadow host containing the provided nodeFront. + * Returns null if the nodeFront is not in a shadow DOM. + * + * @param {NodeFront} nodeFront + * The nodeFront for which we want to retrieve the shadow host. + * @return {NodeFront} The nodeFront corresponding to the shadow host, or null + * if the nodeFront is not in shadow DOM. + */ +function getShadowHost(nodeFront) { + let parent = nodeFront; + while (parent) { + if (parent.isShadowHost) { + return parent; + } + parent = parent.parentOrHost(); + } + return null; +} diff --git a/devtools/server/actors/inspector/node-picker.js b/devtools/server/actors/inspector/node-picker.js index cdddbdb6b47f..d339d39775c9 100644 --- a/devtools/server/actors/inspector/node-picker.js +++ b/devtools/server/actors/inspector/node-picker.js @@ -70,6 +70,14 @@ class NodePicker { node = this._findNodeAtMouseEventPosition(event) || node; } + // The node picker should only surface elements valid for the current walker + // configuration. UserAgent shadow DOM should only be visible if + // showAllAnonymousContent is set to true. Otherwise, fallback to the host. + const shadowRoot = node.containingShadowRoot; + if (shadowRoot?.isUAWidget() && !this._walker.showAllAnonymousContent) { + node = shadowRoot.host; + } + return this._walker.attachElement(node); } From 5a3bb6571974ef799d6a5c6bd80291876e58500b Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Wed, 23 Mar 2022 23:17:34 +0000 Subject: [PATCH 19/43] Bug 1756468 - Add locking to gfxFont, to allow worker threads to do text shaping. r=lsalzman We protect the shaped-word cache in each font with a RWLock, so that multiple threads can shape in parallel using cached data; only when a new entry needs to be cached will we need to take a write lock. (To improve clarity, this patch also constifys a bunch of methods that do not mutate the font instance.) Differential Revision: https://phabricator.services.mozilla.com/D141473 --- gfx/2d/2D.h | 6 +- gfx/thebes/gfxDWriteFonts.cpp | 82 +++++--- gfx/thebes/gfxDWriteFonts.h | 14 +- gfx/thebes/gfxFT2FontBase.cpp | 17 +- gfx/thebes/gfxFT2FontBase.h | 18 +- gfx/thebes/gfxFT2Fonts.cpp | 23 ++- gfx/thebes/gfxFT2Utils.h | 4 +- gfx/thebes/gfxFcPlatformFontList.cpp | 21 +- gfx/thebes/gfxFont.cpp | 281 +++++++++++++++++---------- gfx/thebes/gfxFont.h | 118 ++++++----- gfx/thebes/gfxFontEntry.cpp | 4 +- gfx/thebes/gfxFontEntry.h | 4 +- gfx/thebes/gfxGDIFont.cpp | 37 ++-- gfx/thebes/gfxGDIFont.h | 10 +- gfx/thebes/gfxHarfBuzzShaper.h | 4 + gfx/thebes/gfxMacFont.cpp | 35 ++-- gfx/thebes/gfxMacFont.h | 4 +- 17 files changed, 420 insertions(+), 262 deletions(-) diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index a796b70867e5..f982cae7dda8 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -980,7 +980,7 @@ class SharedFTFace : public external::AtomicRefCounted { * If no owner is given, then the user should avoid modifying any state on * the face so as not to invalidate the prior owner's modification. */ - bool Lock(void* aOwner = nullptr) CAPABILITY_ACQUIRE(mLock) { + bool Lock(const void* aOwner = nullptr) CAPABILITY_ACQUIRE(mLock) { mLock.Lock(); return !aOwner || mLastLockOwner.exchange(aOwner) == aOwner; } @@ -989,7 +989,7 @@ class SharedFTFace : public external::AtomicRefCounted { /** Should be called when a lock owner is destroyed so that we don't have * a dangling pointer to a destroyed owner. */ - void ForgetLockOwner(void* aOwner) { + void ForgetLockOwner(const void* aOwner) { if (aOwner) { mLastLockOwner.compareExchange(aOwner, nullptr); } @@ -1002,7 +1002,7 @@ class SharedFTFace : public external::AtomicRefCounted { // Remember the last owner of the lock, even after unlocking, to allow users // to avoid reinitializing state on the FT face if the last owner hasn't // changed by the next time it is locked with the same owner. - Atomic mLastLockOwner; + Atomic mLastLockOwner; }; #endif diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp index c57725b7cbb7..74e2b5acd2c8 100644 --- a/gfx/thebes/gfxDWriteFonts.cpp +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -107,7 +107,12 @@ gfxDWriteFont::gfxDWriteFont(const RefPtr& aUnscaledFont, ComputeMetrics(anAAOption); } -gfxDWriteFont::~gfxDWriteFont() { delete mMetrics; } +gfxDWriteFont::~gfxDWriteFont() { + if (auto* scaledFont = mAzureScaledFontGDI.exchange(nullptr)) { + scaledFont->Release(); + } + delete mMetrics; +} /* static */ bool gfxDWriteFont::InitDWriteSupport() { @@ -272,17 +277,12 @@ void gfxDWriteFont::UpdateClearTypeVars() { pixelGeometry, renderingModePref); } -UniquePtr gfxDWriteFont::CopyWithAntialiasOption( - AntialiasOption anAAOption) { +gfxFont* gfxDWriteFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) const { auto entry = static_cast(mFontEntry.get()); RefPtr unscaledFont = static_cast(mUnscaledFont.get()); - return MakeUnique(unscaledFont, entry, &mStyle, mFontFace, - anAAOption); -} - -const gfxFont::Metrics& gfxDWriteFont::GetHorizontalMetrics() { - return *mMetrics; + return new gfxDWriteFont(unscaledFont, entry, &mStyle, mFontFace, anAAOption); } bool gfxDWriteFont::GetFakeMetricsForArialBlack( @@ -743,7 +743,7 @@ gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) { } bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) { + bool aTight) const { DWRITE_GLYPH_METRICS m; HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE); if (FAILED(hr)) { @@ -780,32 +780,50 @@ void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, already_AddRefed gfxDWriteFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (mAzureScaledFontUsedClearType != UsingClearType()) { - mAzureScaledFont = nullptr; - mAzureScaledFontGDI = nullptr; + bool useClearType = UsingClearType(); + if (mAzureScaledFontUsedClearType != useClearType) { + if (auto* oldScaledFont = mAzureScaledFont.exchange(nullptr)) { + oldScaledFont->Release(); + } + if (auto* oldScaledFont = mAzureScaledFontGDI.exchange(nullptr)) { + oldScaledFont->Release(); + } } bool forceGDI = aRunParams.allowGDI && GetForceGDIClassic(); - RefPtr& azureScaledFont = - forceGDI ? mAzureScaledFontGDI : mAzureScaledFont; - if (!azureScaledFont) { - gfxDWriteFontEntry* fe = static_cast(mFontEntry.get()); - bool useEmbeddedBitmap = - (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT || - forceGDI) && - fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); - - const gfxFontStyle* fontStyle = GetStyle(); - azureScaledFont = Factory::CreateScaledFontForDWriteFont( - mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), - useEmbeddedBitmap, ApplySyntheticBold(), forceGDI); - if (!azureScaledFont) { - return nullptr; - } - InitializeScaledFont(azureScaledFont); - mAzureScaledFontUsedClearType = UsingClearType(); + ScaledFont* scaledFont = forceGDI ? mAzureScaledFontGDI : mAzureScaledFont; + if (scaledFont) { + return do_AddRef(scaledFont); } - return do_AddRef(azureScaledFont); + gfxDWriteFontEntry* fe = static_cast(mFontEntry.get()); + bool useEmbeddedBitmap = + (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT || + forceGDI) && + fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); + + const gfxFontStyle* fontStyle = GetStyle(); + RefPtr newScaledFont = Factory::CreateScaledFontForDWriteFont( + mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), + useEmbeddedBitmap, ApplySyntheticBold(), forceGDI); + if (!newScaledFont) { + return nullptr; + } + InitializeScaledFont(newScaledFont); + + if (forceGDI) { + if (mAzureScaledFontGDI.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + mAzureScaledFontUsedClearType = useClearType; + } + scaledFont = mAzureScaledFontGDI; + } else { + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + mAzureScaledFontUsedClearType = useClearType; + } + scaledFont = mAzureScaledFont; + } + return do_AddRef(scaledFont); } bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const { diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h index f5771dfff124..f846412e9460 100644 --- a/gfx/thebes/gfxDWriteFonts.h +++ b/gfx/thebes/gfxDWriteFonts.h @@ -39,10 +39,9 @@ class gfxDWriteFont final : public gfxFont { static void SystemTextQualityChanged(); - mozilla::UniquePtr CopyWithAntialiasOption( - AntialiasOption anAAOption) override; + gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; - bool AllowSubpixelAA() override { return mAllowManualShowGlyphs; } + bool AllowSubpixelAA() const override { return mAllowManualShowGlyphs; } bool IsValid() const; @@ -59,7 +58,8 @@ class gfxDWriteFont final : public gfxFont { int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) const override; void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const override; @@ -74,7 +74,7 @@ class gfxDWriteFont final : public gfxFont { bool ShouldRoundXOffset(cairo_t* aCairo) const override; protected: - const Metrics& GetHorizontalMetrics() override; + const Metrics& GetHorizontalMetrics() const override { return *mMetrics; } bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS* aFontMetrics); @@ -102,12 +102,12 @@ class gfxDWriteFont final : public gfxFont { // Used to record the sUseClearType setting at the time mAzureScaledFont // was set up, so we can tell if it's stale and needs to be re-created. - bool mAzureScaledFontUsedClearType; + mozilla::Atomic mAzureScaledFontUsedClearType; // Cache the GDI version of the ScaledFont so that font keys and other // meta-data can remain stable even if there is thrashing between GDI and // non-GDI usage. - RefPtr mAzureScaledFontGDI; + mozilla::Atomic mAzureScaledFontGDI; bool UsingClearType() { return mozilla::gfx::gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY; diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp index c52ffcc592cb..85937ed5eb87 100644 --- a/gfx/thebes/gfxFT2FontBase.cpp +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -41,7 +41,7 @@ gfxFT2FontBase::gfxFT2FontBase( gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } -FT_Face gfxFT2FontBase::LockFTFace() +FT_Face gfxFT2FontBase::LockFTFace() const CAPABILITY_ACQUIRE(mFTFace) NO_THREAD_SAFETY_ANALYSIS { if (!mFTFace->Lock(this)) { FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); @@ -52,7 +52,8 @@ FT_Face gfxFT2FontBase::LockFTFace() return mFTFace->GetFace(); } -void gfxFT2FontBase::UnlockFTFace() CAPABILITY_RELEASE(mFTFace) NO_THREAD_SAFETY_ANALYSIS { +void gfxFT2FontBase::UnlockFTFace() const + CAPABILITY_RELEASE(mFTFace) NO_THREAD_SAFETY_ANALYSIS { mFTFace->Unlock(); } @@ -552,10 +553,6 @@ void gfxFT2FontBase::InitMetrics() { #endif } -const gfxFont::Metrics& gfxFT2FontBase::GetHorizontalMetrics() { - return mMetrics; -} - uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, uint32_t variation_selector) { if (variation_selector) { @@ -596,7 +593,7 @@ bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { gfx_text_subpixel_position_force_enabled_AtStartup())); } -FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) { +FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const { FT_Vector strength = {0, 0}; if (!mEmbolden) { return strength; @@ -627,7 +624,7 @@ FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) { } bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, - IntRect* aBounds) { + IntRect* aBounds) const { gfxFT2LockedFace face(this); MOZ_ASSERT(face.get()); if (!face.get()) { @@ -714,7 +711,7 @@ bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, * FreeType for the glyph extents and initialize the glyph metrics. */ const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics( - uint16_t aGID, IntRect* aBounds) { + uint16_t aGID, IntRect* aBounds) const { if (!mGlyphMetrics) { mGlyphMetrics = mozilla::MakeUnique>(128); @@ -738,7 +735,7 @@ int32_t gfxFT2FontBase::GetGlyphWidth(uint16_t aGID) { } bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) { + bool aTight) const { IntRect bounds; const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); if (!metrics.HasValidBounds()) { diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h index 6e49a6c266a7..d219d397aee7 100644 --- a/gfx/thebes/gfxFT2FontBase.h +++ b/gfx/thebes/gfxFT2FontBase.h @@ -51,7 +51,8 @@ class gfxFT2FontBase : public gfxFont { uint32_t variation_selector) override; bool ProvidesGlyphWidths() const override { return true; } int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) const override; FontType GetType() const override { return FONT_TYPE_FT2; } @@ -61,8 +62,8 @@ class gfxFT2FontBase : public gfxFont { const nsTArray& aVariations, FT_Face aFTFace); - FT_Face LockFTFace(); - void UnlockFTFace(); + FT_Face LockFTFace() const; + void UnlockFTFace() const; private: uint32_t GetCharExtents(uint32_t aChar, gfxFloat* aWidth, @@ -71,12 +72,12 @@ class gfxFT2FontBase : public gfxFont { // Get advance (and optionally bounds) of a single glyph from FreeType, // and return true, or return false if we failed. bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth, - mozilla::gfx::IntRect* aBounds = nullptr); + mozilla::gfx::IntRect* aBounds = nullptr) const; protected: void InitMetrics(); - const Metrics& GetHorizontalMetrics() override; - FT_Vector GetEmboldenStrength(FT_Face aFace); + const Metrics& GetHorizontalMetrics() const override { return mMetrics; } + FT_Vector GetEmboldenStrength(FT_Face aFace) const; RefPtr mFTFace; @@ -133,9 +134,10 @@ class gfxFT2FontBase : public gfxFont { }; const GlyphMetrics& GetCachedGlyphMetrics( - uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr); + uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr) const; - mozilla::UniquePtr> mGlyphMetrics; + mutable mozilla::UniquePtr> + mGlyphMetrics; }; // Helper classes used for clearing out user font data when FT font diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index bb6a977cd8fb..9cc3a4bfd5f1 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -166,15 +166,24 @@ gfxFT2Font::~gfxFT2Font() {} already_AddRefed gfxFT2Font::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (!mAzureScaledFont) { - mAzureScaledFont = Factory::CreateScaledFontForFreeTypeFont( - GetUnscaledFont(), GetAdjustedSize(), mFTFace, - GetStyle()->NeedsSyntheticBold(GetFontEntry())); - InitializeScaledFont(); + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); } - RefPtr scaledFont(mAzureScaledFont); - return scaledFont.forget(); + RefPtr newScaledFont = Factory::CreateScaledFontForFreeTypeFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, + GetStyle()->NeedsSyntheticBold(GetFontEntry())); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); } bool gfxFT2Font::ShouldHintMetrics() const { diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h index 4defe373c7e4..37e930c74374 100644 --- a/gfx/thebes/gfxFT2Utils.h +++ b/gfx/thebes/gfxFT2Utils.h @@ -28,7 +28,7 @@ typedef struct FT_FaceRec_* FT_Face; */ class MOZ_STACK_CLASS gfxFT2LockedFace { public: - explicit gfxFT2LockedFace(gfxFT2FontBase* aFont) + explicit gfxFT2LockedFace(const gfxFT2FontBase* aFont) : mGfxFont(aFont), mFace(aFont->LockFTFace()) {} ~gfxFT2LockedFace() { if (mFace) { @@ -54,7 +54,7 @@ class MOZ_STACK_CLASS gfxFT2LockedFace { FT_ULong variantSelector); CharVariantFunction FindCharVariantFunction(); - gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller + const gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller FT_Face mFace; }; diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp index a4fef2159894..f6b46becf265 100644 --- a/gfx/thebes/gfxFcPlatformFontList.cpp +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -1272,14 +1272,23 @@ gfxFontconfigFont::~gfxFontconfigFont() = default; already_AddRefed gfxFontconfigFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (!mAzureScaledFont) { - mAzureScaledFont = Factory::CreateScaledFontForFontconfigFont( - GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); - InitializeScaledFont(); + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); } - RefPtr scaledFont(mAzureScaledFont); - return scaledFont.forget(); + RefPtr newScaledFont = Factory::CreateScaledFontForFontconfigFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); } bool gfxFontconfigFont::ShouldHintMetrics() const { diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index da4945244dfa..dd36c47efb28 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -13,6 +13,7 @@ #include "mozilla/intl/Segmenter.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/ScopeExit.h" #include "mozilla/SVGContextPaint.h" #include "mozilla/Logging.h" @@ -844,6 +845,7 @@ gfxFont::gfxFont(const RefPtr& aUnscaledFont, gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, AntialiasOption anAAOption) : mFontEntry(aFontEntry), + mLock("gfxFont lock"), mUnscaledFont(aUnscaledFont), mStyle(*aFontStyle), mAdjustedSize(-1.0), // negative to indicate "not yet initialized" @@ -873,6 +875,21 @@ gfxFont::gfxFont(const RefPtr& aUnscaledFont, gfxFont::~gfxFont() { mFontEntry->NotifyFontDestroyed(this); + // Delete objects owned through atomic pointers. (Some of these may be null, + // but that's OK.) + auto* verticalMetrics = mVerticalMetrics.exchange(nullptr); + delete verticalMetrics; + auto* hbShaper = mHarfBuzzShaper.exchange(nullptr); + delete hbShaper; + auto* grShaper = mGraphiteShaper.exchange(nullptr); + delete grShaper; + auto* mathTable = mMathTable.exchange(nullptr); + delete mathTable; + + if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) { + scaledFont->Release(); + } + if (mGlyphChangeObservers) { for (const auto& key : *mGlyphChangeObservers) { key->ForgetFont(); @@ -927,26 +944,37 @@ gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels( } } +gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() { + if (!mHarfBuzzShaper) { + auto* shaper = new gfxHarfBuzzShaper(this); + shaper->Initialize(); + if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) { + delete shaper; + } + } + gfxHarfBuzzShaper* shaper = mHarfBuzzShaper; + return shaper->IsInitialized() ? shaper : nullptr; +} + gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) { if (!aVertical && ProvidesGlyphWidths()) { return GetGlyphWidth(aGID) / 65536.0; } if (mFUnitsConvFactor < 0.0f) { - GetMetrics(nsFontMetrics::eHorizontal); + // Metrics haven't been initialized; lock while we do that. + AutoWriteLock lock(mLock); + if (mFUnitsConvFactor < 0.0f) { + GetMetrics(nsFontMetrics::eHorizontal); + } } NS_ASSERTION(mFUnitsConvFactor >= 0.0f, "missing font unit conversion factor"); - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + return (aVertical ? shaper->GetGlyphVAdvance(aGID) + : shaper->GetGlyphHAdvance(aGID)) / + 65536.0; } - gfxHarfBuzzShaper* shaper = - static_cast(mHarfBuzzShaper.get()); - if (!shaper->Initialize()) { - return 0; - } - return (aVertical ? shaper->GetGlyphVAdvance(aGID) - : shaper->GetGlyphHAdvance(aGID)) / - 65536.0; + return 0.0; } gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { @@ -954,15 +982,9 @@ gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { if (ProvidesGetGlyph()) { gid = GetGlyph(aUnicode, 0); } else { - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + gid = shaper->GetNominalGlyph(aUnicode); } - gfxHarfBuzzShaper* shaper = - static_cast(mHarfBuzzShaper.get()); - if (!shaper->Initialize()) { - return -1.0; - } - gid = shaper->GetNominalGlyph(aUnicode); } if (!gid) { return -1.0; @@ -1177,8 +1199,9 @@ static const hb_tag_t defaultFeatures[] = { HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'), HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')}; -void gfxFont::CheckForFeaturesInvolvingSpace() { - mFontEntry->mHasSpaceFeaturesInitialized = true; +void gfxFont::CheckForFeaturesInvolvingSpace() const { + auto setInitializedFlag = + MakeScopeExit([&]() { mFontEntry->mHasSpaceFeaturesInitialized = true; }); bool log = LOG_FONTINIT_ENABLED(); TimeStamp start; @@ -1305,7 +1328,7 @@ void gfxFont::CheckForFeaturesInvolvingSpace() { } } -bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) { +bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const { NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized, "need to initialize space lookup flags"); NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code"); @@ -1332,7 +1355,8 @@ bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) { return false; } -tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(Script aRunScript) { +tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping( + Script aRunScript) const { // avoid checking fonts known not to include default space-dependent features if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { if (!mKerningSet && mStyle.featureSettings.IsEmpty() && @@ -1468,16 +1492,14 @@ bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, } // xxx - for graphite, don't really know how to sniff lookups so bail - if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - return true; + { + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; + } } - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); - } - gfxHarfBuzzShaper* shaper = - static_cast(mHarfBuzzShaper.get()); - if (!shaper->Initialize()) { + gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); + if (!shaper) { return false; } @@ -1521,21 +1543,16 @@ bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, return true; } - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); - } - gfxHarfBuzzShaper* shaper = - static_cast(mHarfBuzzShaper.get()); - if (!shaper->Initialize()) { - return false; + if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); + + hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); + return hb_set_has(inputGlyphs, gid); } - // get the hbset containing input glyphs for the feature - const hb_set_t* inputGlyphs = - mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); - - hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); - return hb_set_has(inputGlyphs, gid); + return false; } bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) { @@ -2490,11 +2507,8 @@ bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { return true; } // Use harfbuzz shaper to look up the default glyph ID for the character. - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); - } - auto* shaper = static_cast(mHarfBuzzShaper.get()); - if (!shaper->Initialize()) { + auto* shaper = GetHarfBuzzShaper(); + if (!shaper) { return false; } uint32_t gid = 0; @@ -2559,15 +2573,22 @@ gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, // This is only used by MathML layout at present. if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && mAntialiasOption != kAntialiasNone) { - if (!mNonAAFont) { - mNonAAFont = CopyWithAntialiasOption(kAntialiasNone); + gfxFont* nonAA = mNonAAFont; + if (!nonAA) { + nonAA = CopyWithAntialiasOption(kAntialiasNone); + if (nonAA) { + if (!mNonAAFont.compareExchange(nullptr, nonAA)) { + delete nonAA; + nonAA = mNonAAFont; + } + } } // if font subclass doesn't implement CopyWithAntialiasOption(), // it will return null and we'll proceed to use the existing font - if (mNonAAFont) { - return mNonAAFont->Measure(aTextRun, aStart, aEnd, - TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, - aSpacing, aOrientation); + if (nonAA) { + return nonAA->Measure(aTextRun, aStart, aEnd, + TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, + aSpacing, aOrientation); } } @@ -2761,6 +2782,7 @@ gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, } bool gfxFont::AgeCachedWords() { + mozilla::AutoWriteLock lock(mLock); if (mWordCache) { for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) { CacheHashEntry* entry = it.Get(); @@ -2776,7 +2798,8 @@ bool gfxFont::AgeCachedWords() { return true; } -void gfxFont::NotifyGlyphsChanged() { +void gfxFont::NotifyGlyphsChanged() const { + AutoReadLock lock(const_cast(this)->mLock); uint32_t i, count = mGlyphExtentsArray.Length(); for (i = 0; i < count; ++i) { // Flush cached extents array @@ -2814,51 +2837,84 @@ gfxShapedWord* gfxFont::GetShapedWord( // if the cache is getting too big, flush it and start over uint32_t wordCacheMaxEntries = gfxPlatform::GetPlatform()->WordCacheMaxEntries(); + mLock.ReadLock(); if (mWordCache->Count() > wordCacheMaxEntries) { + // Flush the cache if it is getting overly big. + // There's a chance another thread could race with the lock-twiddling here, + // but that's OK; it's harmless if we end up calling ClearCacheWords twice + // from separate threads. Internally, it will hold the lock exclusively + // while doing its thing. + mLock.ReadUnlock(); NS_WARNING("flushing shaped-word cache"); ClearCachedWords(); + mLock.ReadLock(); + // Once we've reclaimed a read lock, we can proceed knowing the cache + // isn't growing uncontrollably. } // if there's a cached entry for this word, just return it CacheHashKey key(aText, aLength, aHash, aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags, aRounding); - CacheHashEntry* entry = mWordCache->PutEntry(key, fallible); - if (!entry) { - NS_WARNING("failed to create word cache entry - expect missing text"); - return nullptr; - } - gfxShapedWord* sw = entry->mShapedWord.get(); - - if (sw) { + CacheHashEntry* entry = mWordCache->GetEntry(key); + if (entry) { + gfxShapedWord* sw = entry->mShapedWord.get(); sw->ResetAge(); #ifndef RELEASE_OR_BETA if (aTextPerf) { + // XXX we should make sure this is atomic aTextPerf->current.wordCacheHit++; } #endif + mLock.ReadUnlock(); return sw; } + mLock.ReadUnlock(); -#ifndef RELEASE_OR_BETA - if (aTextPerf) { - aTextPerf->current.wordCacheMiss++; - } -#endif - - sw = gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, - aAppUnitsPerDevUnit, aFlags, aRounding); - entry->mShapedWord.reset(sw); + gfxShapedWord* sw = + gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); if (!sw) { NS_WARNING("failed to create gfxShapedWord - expect missing text"); return nullptr; } - DebugOnly ok = ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage, aVertical, aRounding, sw); - NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text"); + { + // We're going to cache the new shaped word, so lock for writing. + AutoWriteLock lock(mLock); + entry = mWordCache->PutEntry(key, fallible); + if (!entry) { + NS_WARNING("failed to create word cache entry - expect missing text"); + delete sw; + return nullptr; + } + + // It's unlikely, but maybe another thread got there before us... + if (entry->mShapedWord) { + // Just discard the newly-created word, and use the existing one. + delete sw; + sw = entry->mShapedWord.get(); + sw->ResetAge(); +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheHit++; + } +#endif + return sw; + } + + entry->mShapedWord.reset(sw); + +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheMiss++; + } +#endif + } + gfxFontCache::GetCache()->RunWordCacheExpirationTimer(); return sw; @@ -2929,13 +2985,18 @@ bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, // Vertical graphite support may be wanted as a future enhancement. if (FontCanSupportGraphite() && !aVertical) { if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - if (!mGraphiteShaper) { - mGraphiteShaper = MakeUnique(this); - Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); + gfxGraphiteShaper* shaper = mGraphiteShaper; + if (!shaper) { + shaper = new gfxGraphiteShaper(this); + if (mGraphiteShaper.compareExchange(nullptr, shaper)) { + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); + } else { + delete shaper; + shaper = mGraphiteShaper; + } } - if (mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, - aScript, aLanguage, aVertical, aRounding, - aShapedText)) { + if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, aShapedText); return true; @@ -2943,12 +3004,10 @@ bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, } } - if (!mHarfBuzzShaper) { - mHarfBuzzShaper = MakeUnique(this); - } - if (mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, - aLanguage, aVertical, aRounding, - aShapedText)) { + gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); + if (shaper && + shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, aShapedText)) { PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, aShapedText); if (GetFontEntry()->HasTrackingTable()) { @@ -3502,7 +3561,7 @@ bool gfxFont::InitFakeSmallCapsRun( aSyntheticUpper); } -gfxFont* gfxFont::GetSmallCapsFont() { +gfxFont* gfxFont::GetSmallCapsFont() const { gfxFontStyle style(*GetStyle()); style.size *= SMALL_CAPS_SCALE_FACTOR; style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; @@ -3510,7 +3569,7 @@ gfxFont* gfxFont::GetSmallCapsFont() { return fe->FindOrMakeFont(&style, mUnicodeRangeMap); } -gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) { +gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) const { gfxFontStyle style(*GetStyle()); style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); gfxFontEntry* fe = GetFontEntry(); @@ -3518,6 +3577,16 @@ gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) { } gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { + { + AutoReadLock lock(mLock); + uint32_t i, count = mGlyphExtentsArray.Length(); + for (i = 0; i < count; ++i) { + if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) + return mGlyphExtentsArray[i].get(); + } + } + AutoWriteLock lock(mLock); + // Re-check in case of race. uint32_t i, count = mGlyphExtentsArray.Length(); for (i = 0; i < count; ++i) { if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) @@ -3849,8 +3918,7 @@ void gfxFont::CreateVerticalMetrics() { const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); uint32_t len; - mVerticalMetrics = MakeUnique(); - auto* metrics = mVerticalMetrics.get(); + auto* metrics = new Metrics(); ::memset(metrics, 0, sizeof(Metrics)); // Some basic defaults, in case the font lacks any real metrics tables. @@ -3997,6 +4065,10 @@ void gfxFont::CreateVerticalMetrics() { metrics->maxHeight = metrics->maxAscent + metrics->maxDescent; metrics->xHeight = metrics->emHeight / 2; metrics->capHeight = metrics->maxAscent; + + if (!mVerticalMetrics.compareExchange(nullptr, metrics)) { + delete metrics; + } } gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { @@ -4037,6 +4109,7 @@ gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const { + AutoReadLock lock(const_cast(this)->mLock); for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { aSizes->mFontInstances += mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); @@ -4053,6 +4126,7 @@ void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, } void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { + AutoWriteLock lock(mLock); if (!mGlyphChangeObservers) { mGlyphChangeObservers = MakeUnique>(); } @@ -4060,6 +4134,7 @@ void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { } void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) { + AutoWriteLock lock(mLock); NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered"); @@ -4199,17 +4274,21 @@ void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) { } bool gfxFont::TryGetMathTable() { - if (!mMathInitialized) { - mMathInitialized = true; - - hb_face_t* face = GetFontEntry()->GetHBFace(); - if (face) { - if (hb_ot_math_has_data(face)) { - mMathTable = MakeUnique(face, GetAdjustedSize()); - } - hb_face_destroy(face); - } + if (mMathInitialized) { + return !!mMathTable; } + hb_face_t* face = GetFontEntry()->GetHBFace(); + if (face) { + if (hb_ot_math_has_data(face)) { + auto* mathTable = new gfxMathTable(face, GetAdjustedSize()); + if (!mMathTable.compareExchange(nullptr, mathTable)) { + delete mathTable; + } + } + hb_face_destroy(face); + } + mMathInitialized = true; + return !!mMathTable; } diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index d12b33ae5ff9..61704cc06312 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -60,6 +60,8 @@ #include "nsMathUtils.h" class gfxContext; +class gfxGraphiteShaper; +class gfxHarfBuzzShaper; class gfxGlyphExtents; class gfxMathTable; class gfxPattern; @@ -1417,7 +1419,8 @@ class gfxShapedWord final : public gfxShapedText { gfxFontShaper::RoundingFlags mRounding; - uint32_t mAgeCounter; + // With multithreaded shaping, this may be updated by any thread. + std::atomic mAgeCounter; // The mCharGlyphsStorage array is actually a variable-size member; // when the ShapedWord is created, its size will be increased as necessary @@ -1449,7 +1452,12 @@ class gfxFont { nsrefcnt AddRef(void) { MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); - if (mExpirationState.IsTracked()) { + nsExpirationState state; + { + mozilla::AutoReadLock lock(mLock); + state = mExpirationState; + } + if (state.IsTracked()) { gfxFontCache::GetCache()->RemoveObject(this); } ++mRefCnt; @@ -1460,15 +1468,15 @@ class gfxFont { MOZ_ASSERT(0 != mRefCnt, "dup release"); --mRefCnt; NS_LOG_RELEASE(this, mRefCnt, "gfxFont"); - if (mRefCnt == 0) { + nsrefcnt rval = mRefCnt; + if (!rval) { NotifyReleased(); // |this| may have been deleted. - return 0; } - return mRefCnt; + return rval; } - int32_t GetRefCount() { return mRefCnt; } + int32_t GetRefCount() { return int32_t(mRefCnt); } // options to specify the kind of AA to be used when creating a font typedef enum : uint8_t { @@ -1479,7 +1487,7 @@ class gfxFont { } AntialiasOption; protected: - nsAutoRefCnt mRefCnt; + mozilla::ThreadSafeAutoRefCnt mRefCnt; void NotifyReleased() { gfxFontCache* cache = gfxFontCache::GetCache(); @@ -1532,8 +1540,7 @@ class gfxFont { const nsCString& GetName() const { return mFontEntry->Name(); } const gfxFontStyle* GetStyle() const { return &mStyle; } - virtual mozilla::UniquePtr CopyWithAntialiasOption( - AntialiasOption anAAOption) { + virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const { // platforms where this actually matters should override return nullptr; } @@ -1557,15 +1564,17 @@ class gfxFont { } // check whether this is an sfnt we can potentially use with harfbuzz - bool FontCanSupportHarfBuzz() { return mFontEntry->HasCmapTable(); } + bool FontCanSupportHarfBuzz() const { return mFontEntry->HasCmapTable(); } // check whether this is an sfnt we can potentially use with Graphite - bool FontCanSupportGraphite() { return mFontEntry->HasGraphiteTables(); } + bool FontCanSupportGraphite() const { + return mFontEntry->HasGraphiteTables(); + } // Whether this is a font that may be doing full-color rendering, // and therefore needs us to use a mask for text-shadow even when // we're not actually blurring. - bool AlwaysNeedsMaskForShadow() { + bool AlwaysNeedsMaskForShadow() const { return mFontEntry->TryGetColorGlyphs() || mFontEntry->TryGetSVGData(this) || mFontEntry->HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) || mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); @@ -1621,6 +1630,10 @@ class gfxFont { virtual bool ShouldHintMetrics() const { return true; } virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; } + // Return the font's owned harfbuzz shaper, creating and initializing it if + // necessary; returns null if shaper initialization has failed. + gfxHarfBuzzShaper* GetHarfBuzzShaper(); + // Font metrics struct Metrics { gfxFloat capHeight; @@ -1786,14 +1799,14 @@ class gfxFont { nsExpirationState* GetExpirationState() { return &mExpirationState; } // Get the glyphID of a space - uint16_t GetSpaceGlyph() { return mSpaceGlyph; } + uint16_t GetSpaceGlyph() const { return mSpaceGlyph; } gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit); void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, bool aNeedTight, gfxGlyphExtents* aExtents); - virtual bool AllowSubpixelAA() { return true; } + virtual bool AllowSubpixelAA() const { return true; } bool ApplySyntheticBold() const { return mApplySyntheticBold; } @@ -1804,7 +1817,7 @@ class gfxFont { // For size S up to a threshold size T, we use (0.25 + 3S / 4T), // so that the result ranges from 0.25 to 1.0; thereafter, // simply use (S / T). - gfxFloat GetSyntheticBoldOffset() { + gfxFloat GetSyntheticBoldOffset() const { gfxFloat size = GetAdjustedSize(); const gfxFloat threshold = 48.0; return size < threshold ? (0.25 + 0.75 * size / threshold) @@ -1812,7 +1825,7 @@ class gfxFont { } gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); } - bool HasCharacter(uint32_t ch) { + bool HasCharacter(uint32_t ch) const { if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) { return false; } @@ -1827,7 +1840,7 @@ class gfxFont { mUnicodeRangeMap = aUnicodeRangeMap; } - uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) { + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) const { if (!mIsValid) { return 0; } @@ -1867,8 +1880,15 @@ class gfxFont { // Ensure the ShapedWord cache is initialized. This MUST be called before // any attempt to use GetShapedWord(). void InitWordCache() { + mLock.ReadLock(); if (!mWordCache) { - mWordCache = mozilla::MakeUnique>(); + mLock.ReadUnlock(); + mozilla::AutoWriteLock lock(mLock); + if (!mWordCache) { + mWordCache = mozilla::MakeUnique>(); + } + } else { + mLock.ReadUnlock(); } } @@ -1879,13 +1899,14 @@ class gfxFont { // Discard all cached word records; called on memory-pressure notification. void ClearCachedWords() { + mozilla::AutoWriteLock lock(mLock); if (mWordCache) { mWordCache->Clear(); } } // Glyph rendering/geometry has changed, so invalidate data as necessary. - void NotifyGlyphsChanged(); + void NotifyGlyphsChanged() const; virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const; @@ -1915,12 +1936,11 @@ class gfxFont { // gfxFont implementations may cache ScaledFont versions other than the // default, so InitializeScaledFont must support explicitly specifying - // other ScaledFonts than the default to initialize. + // which ScaledFonts to initialize. void InitializeScaledFont( const RefPtr& aScaledFont); - void InitializeScaledFont() { InitializeScaledFont(mAzureScaledFont); } - bool KerningDisabled() { return mKerningSet && !mKerningEnabled; } + bool KerningDisabled() const { return mKerningSet && !mKerningEnabled; } /** * Subclass this object to be notified of glyph changes. Delete the object @@ -1947,7 +1967,7 @@ class gfxFont { }; friend class GlyphChangeObserver; - bool GlyphsMayChange() { + bool GlyphsMayChange() const { // Currently only fonts with SVG glyphs can have animated glyphs return mFontEntry->TryGetSVGData(this); } @@ -1961,21 +1981,21 @@ class gfxFont { // If (and ONLY if) TryGetMathTable() has returned true, the MathTable() // method may be called to access the gfxMathTable data. bool TryGetMathTable(); - gfxMathTable* MathTable() { + gfxMathTable* MathTable() const { MOZ_RELEASE_ASSERT(mMathTable, "A successful call to TryGetMathTable() must be " "performed before calling this function"); - return mMathTable.get(); + return mMathTable; } // Return a cloned font resized and offset to simulate sub/superscript // glyphs. This does not add a reference to the returned font. - gfxFont* GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel); + gfxFont* GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) const; bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh); protected: - virtual const Metrics& GetHorizontalMetrics() = 0; + virtual const Metrics& GetHorizontalMetrics() const = 0; void CreateVerticalMetrics(); @@ -2021,7 +2041,7 @@ class gfxFont { // Return a font that is a "clone" of this one, but reduced to 80% size // (and with variantCaps set to normal). This does not add a reference to // the returned font. - gfxFont* GetSmallCapsFont(); + gfxFont* GetSmallCapsFont() const; // subclasses may provide (possibly hinted) glyph widths (in font units); // if they do not override this, harfbuzz will use unhinted widths @@ -2033,7 +2053,7 @@ class gfxFont { virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; } virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight = false) { + bool aTight = false) const { return false; } @@ -2044,13 +2064,13 @@ class gfxFont { void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver); // whether font contains substitution lookups containing spaces - bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript); + bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const; // do spaces participate in shaping rules? if so, can't used word cache // Note that this function uses HasGraphiteSpaceContextuals, so it can only // return a "hint" to the correct answer. The calling code must ensure it // performs safe actions independent of the value returned. - tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript); + tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript) const; // For 8-bit text, expand to 16-bit and then call the following method. bool ShapeText(DrawTarget* aContext, const uint8_t* aText, @@ -2100,7 +2120,7 @@ class gfxFont { bool aVertical, RoundingFlags aRounding, gfxTextRun* aTextRun); - void CheckForFeaturesInvolvingSpace(); + void CheckForFeaturesInvolvingSpace() const; // whether a given feature is included in feature settings from both the // font and the style. aFeatureOn set if resolved feature value is non-zero @@ -2111,6 +2131,7 @@ class gfxFont { static nsTHashSet* sDefaultFeatures; RefPtr mFontEntry; + mozilla::RWLock mLock; struct CacheHashKey { union { @@ -2197,35 +2218,40 @@ class gfxFont { mozilla::UniquePtr mShapedWord; }; - mozilla::UniquePtr> mWordCache; + mozilla::UniquePtr> mWordCache GUARDED_BY(mLock); static const uint32_t kShapedWordCacheMaxAge = 3; - nsTArray> mGlyphExtentsArray; - mozilla::UniquePtr> mGlyphChangeObservers; + nsTArray> mGlyphExtentsArray + GUARDED_BY(mLock); + mozilla::UniquePtr> mGlyphChangeObservers + GUARDED_BY(mLock); // a copy of the font without antialiasing, if needed for separate // measurement by mathml code - mozilla::UniquePtr mNonAAFont; + mozilla::Atomic mNonAAFont; // we create either or both of these shapers when needed, depending // whether the font has graphite tables, and whether graphite shaping // is actually enabled - mozilla::UniquePtr mHarfBuzzShaper; - mozilla::UniquePtr mGraphiteShaper; + mozilla::Atomic mHarfBuzzShaper; + mozilla::Atomic mGraphiteShaper; - // if a userfont with unicode-range specified, contains map of *possible* - // ranges supported by font + // If a userfont with unicode-range specified, contains map of *possible* + // ranges supported by font. This is set during user-font initialization, + // before the font is available to other threads, and thereafter is inert + // so no guard is needed. RefPtr mUnicodeRangeMap; - RefPtr mUnscaledFont; - RefPtr mAzureScaledFont; + RefPtr mUnscaledFont GUARDED_BY(mLock); + + mozilla::Atomic mAzureScaledFont; // For vertical metrics, created on demand. - mozilla::UniquePtr mVerticalMetrics; + mozilla::Atomic mVerticalMetrics; // Table used for MathML layout. - mozilla::UniquePtr mMathTable; + mozilla::Atomic mMathTable; gfxFontStyle mStyle; mutable gfxFloat mAdjustedSize; @@ -2235,7 +2261,7 @@ class gfxFont { // This is OK because we only multiply by this factor, never divide. float mFUnitsConvFactor; - nsExpirationState mExpirationState; + nsExpirationState mExpirationState GUARDED_BY(mLock); // Glyph ID of the font's glyph, zero if missing uint16_t mSpaceGlyph = 0; @@ -2252,7 +2278,7 @@ class gfxFont { bool mKerningSet; // kerning explicitly set? bool mKerningEnabled; // if set, on or off? - bool mMathInitialized; // TryGetMathTable() called? + mozilla::Atomic mMathInitialized; // TryGetMathTable() called? // Helper for subclasses that want to initialize standard metrics from the // tables of sfnt (TrueType/OpenType) fonts. diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp index b2abace1e568..50c1620ac229 100644 --- a/gfx/thebes/gfxFontEntry.cpp +++ b/gfx/thebes/gfxFontEntry.cpp @@ -339,7 +339,7 @@ void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, mSVGGlyphs->RenderGlyph(aContext, aGlyphId, aContextPaint); } -bool gfxFontEntry::TryGetSVGData(gfxFont* aFont) { +bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) { if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { return false; } @@ -377,7 +377,7 @@ void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) { void gfxFontEntry::NotifyGlyphsChanged() { for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { - gfxFont* font = mFontsUsingSVGGlyphs[i]; + const gfxFont* font = mFontsUsingSVGGlyphs[i]; font->NotifyGlyphsChanged(); } } diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h index 8d29fc38cfc3..ae6393ef3481 100644 --- a/gfx/thebes/gfxFontEntry.h +++ b/gfx/thebes/gfxFontEntry.h @@ -251,7 +251,7 @@ class gfxFontEntry { // can be safely dereferenced. virtual nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr); - bool TryGetSVGData(gfxFont* aFont); + bool TryGetSVGData(const gfxFont* aFont); bool HasSVGGlyph(uint32_t aGlyphId); bool GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId, gfxFloat aSize, gfxRect* aResult); @@ -472,7 +472,7 @@ class gfxFontEntry { mozilla::UniquePtr mUserFontData; mozilla::UniquePtr mSVGGlyphs; // list of gfxFonts that are using SVG glyphs - nsTArray mFontsUsingSVGGlyphs; + nsTArray mFontsUsingSVGGlyphs; nsTArray mFeatureSettings; nsTArray mVariationSettings; mozilla::UniquePtr> mSupportedFeatures; diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp index aa7ec382b0f3..dad92e12a151 100644 --- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -51,10 +51,9 @@ gfxGDIFont::~gfxGDIFont() { delete mMetrics; } -UniquePtr gfxGDIFont::CopyWithAntialiasOption( - AntialiasOption anAAOption) { +gfxFont* gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption) const { auto entry = static_cast(mFontEntry.get()); - return MakeUnique(entry, &mStyle, anAAOption); + return new gfxGDIFont(entry, &mStyle, anAAOption); } bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, @@ -71,21 +70,28 @@ bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, aLanguage, aVertical, aRounding, aShapedText); } -const gfxFont::Metrics& gfxGDIFont::GetHorizontalMetrics() { return *mMetrics; } - already_AddRefed gfxGDIFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (!mAzureScaledFont) { - LOGFONT lf; - GetObject(GetHFONT(), sizeof(LOGFONT), &lf); - - mAzureScaledFont = Factory::CreateScaledFontForGDIFont( - &lf, GetUnscaledFont(), GetAdjustedSize()); - InitializeScaledFont(); + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); } - RefPtr scaledFont(mAzureScaledFont); - return scaledFont.forget(); + LOGFONT lf; + GetObject(GetHFONT(), sizeof(LOGFONT), &lf); + + RefPtr newScaledFont = Factory::CreateScaledFontForGDIFont( + &lf, GetUnscaledFont(), GetAdjustedSize()); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); } gfxFont::RunMetrics gfxGDIFont::Measure(const gfxTextRun* aTextRun, @@ -485,7 +491,8 @@ int32_t gfxGDIFont::GetGlyphWidth(uint16_t aGID) { }); } -bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { +bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) const { DCForMetrics dc; AutoSelectFont fs(dc, GetHFONT()); diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h index 874c206b4a73..a731e06efa0b 100644 --- a/gfx/thebes/gfxGDIFont.h +++ b/gfx/thebes/gfxGDIFont.h @@ -22,7 +22,7 @@ class gfxGDIFont : public gfxFont { virtual ~gfxGDIFont(); - HFONT GetHFONT() { return mFont; } + HFONT GetHFONT() const { return mFont; } already_AddRefed GetScaledFont( const TextRunDrawParams& aRunParams) override; @@ -35,8 +35,7 @@ class gfxGDIFont : public gfxFont { mozilla::gfx::ShapedTextFlags aOrientation) override; /* required for MathML to suppress effects of ClearType "padding" */ - mozilla::UniquePtr CopyWithAntialiasOption( - AntialiasOption anAAOption) override; + gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; // If the font has a cmap table, we handle it purely with harfbuzz; // but if not (e.g. .fon fonts), we'll use a GDI callback to get glyphs. @@ -49,7 +48,8 @@ class gfxGDIFont : public gfxFont { // get hinted glyph width in pixels as 16.16 fixed-point value int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) const override; void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const; @@ -59,7 +59,7 @@ class gfxGDIFont : public gfxFont { FontType GetType() const override { return FONT_TYPE_GDI; } protected: - const Metrics& GetHorizontalMetrics() override; + const Metrics& GetHorizontalMetrics() const override { return *mMetrics; } bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, Script aScript, diff --git a/gfx/thebes/gfxHarfBuzzShaper.h b/gfx/thebes/gfxHarfBuzzShaper.h index aa1ffa076182..c4647820ff62 100644 --- a/gfx/thebes/gfxHarfBuzzShaper.h +++ b/gfx/thebes/gfxHarfBuzzShaper.h @@ -25,8 +25,12 @@ class gfxHarfBuzzShaper : public gfxFontShaper { gfxHarfBuzzShaper* mShaper; }; + // Initializes the shaper and returns whether this was successful. bool Initialize(); + // Returns whether the shaper has been successfully initialized. + bool IsInitialized() const { return mHBFont != nullptr; } + bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, Script aScript, nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp index 3cd6d316d4d3..122014ffa547 100644 --- a/gfx/thebes/gfxMacFont.cpp +++ b/gfx/thebes/gfxMacFont.cpp @@ -518,7 +518,8 @@ int32_t gfxMacFont::GetGlyphWidth(uint16_t aGID) { return advance.width * 0x10000; } -bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { +bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) const { CGRect bb; if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) { return false; @@ -581,21 +582,27 @@ void gfxMacFont::InitMetricsFromPlatform() { already_AddRefed gfxMacFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (!mAzureScaledFont) { - gfxFontEntry* fe = GetFontEntry(); - bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs(); - mAzureScaledFont = Factory::CreateScaledFontForMacFont( - GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), - ToDeviceColor(mFontSmoothingBackgroundColor), - !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs); - if (!mAzureScaledFont) { - return nullptr; - } - InitializeScaledFont(); + if (ScaledFont* scaledFont = mAzureScaledFont) { + return do_AddRef(scaledFont); } - RefPtr scaledFont(mAzureScaledFont); - return scaledFont.forget(); + gfxFontEntry* fe = GetFontEntry(); + bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs(); + RefPtr newScaledFont = Factory::CreateScaledFontForMacFont( + GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), + ToDeviceColor(mFontSmoothingBackgroundColor), + !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs); + if (!newScaledFont) { + return nullptr; + } + + InitializeScaledFont(newScaledFont); + + if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { + Unused << newScaledFont.forget(); + } + ScaledFont* scaledFont = mAzureScaledFont; + return do_AddRef(scaledFont); } bool gfxMacFont::ShouldRoundXOffset(cairo_t* aCairo) const { diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h index 2bd10c74a501..7c5dd2a436a8 100644 --- a/gfx/thebes/gfxMacFont.h +++ b/gfx/thebes/gfxMacFont.h @@ -37,7 +37,7 @@ class gfxMacFont : public gfxFont { int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) const override; already_AddRefed GetScaledFont( const TextRunDrawParams& aRunParams) override; @@ -59,7 +59,7 @@ class gfxMacFont : public gfxFont { CTFontDescriptorRef aFontDesc = nullptr); protected: - const Metrics& GetHorizontalMetrics() override { return mMetrics; } + const Metrics& GetHorizontalMetrics() const override { return mMetrics; } // override to prefer CoreText shaping with fonts that depend on AAT bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, From c57248694710e5d75d2368732cf80474b5ad2e62 Mon Sep 17 00:00:00 2001 From: alwu Date: Wed, 23 Mar 2022 23:29:43 +0000 Subject: [PATCH 20/43] Bug 1760464 - block certain Intel graphic cards to prevent crashes while using Intel MFT for HW VP8 decoding. r=media-playback-reviewers,gfx-reviewers,bryce,aosmond Differential Revision: https://phabricator.services.mozilla.com/D141810 --- dom/media/platforms/wmf/WMFDecoderModule.cpp | 3 ++- widget/GfxDriverInfo.cpp | 5 +++++ widget/GfxDriverInfo.h | 1 + widget/windows/GfxInfo.cpp | 9 +++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dom/media/platforms/wmf/WMFDecoderModule.cpp b/dom/media/platforms/wmf/WMFDecoderModule.cpp index 5d1f532dc391..e9cad72d23c1 100644 --- a/dom/media/platforms/wmf/WMFDecoderModule.cpp +++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp @@ -272,7 +272,8 @@ bool WMFDecoderModule::HasH264() { /* static */ bool WMFDecoderModule::HasVP8() { - return sUsableVPXMFT && + // Some Intel HW MFTs would crash on VP8 decoding. + return sUsableVPXMFT && gfx::gfxVars::UseVP8HwDecode() && CanCreateWMFDecoder(); } diff --git a/widget/GfxDriverInfo.cpp b/widget/GfxDriverInfo.cpp index b394cd35843e..eed7d716623d 100644 --- a/widget/GfxDriverInfo.cpp +++ b/widget/GfxDriverInfo.cpp @@ -433,6 +433,10 @@ const GfxDeviceFamily* GfxDriverInfo::GetDeviceFamily(DeviceFamily id) { APPEND_DEVICE(0xa011); APPEND_DEVICE(0xa012); break; + case DeviceFamily::Bug1760464: + APPEND_DEVICE(0x0a16); // Intel HD Graphics Family on Haswell Ultrabooks + APPEND_DEVICE(0x041e); // Intel HD Graphics 4400 + break; case DeviceFamily::AmdR600: // AMD R600 generation GPUs // R600 @@ -950,6 +954,7 @@ const nsAString& GfxDriverInfo::GetDeviceVendor(DeviceFamily id) { case DeviceFamily::Bug1116812: case DeviceFamily::Bug1155608: case DeviceFamily::Bug1207665: + case DeviceFamily::Bug1760464: vendor = DeviceVendor::Intel; break; case DeviceFamily::NvidiaAll: diff --git a/widget/GfxDriverInfo.h b/widget/GfxDriverInfo.h index 9b41a7ec150e..02b3704467c0 100644 --- a/widget/GfxDriverInfo.h +++ b/widget/GfxDriverInfo.h @@ -197,6 +197,7 @@ enum class DeviceFamily : uint8_t { Bug1155608, Bug1207665, Bug1447141, + Bug1760464, AmdR600, NvidiaRolloutWebRender, IntelRolloutWebRender, diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index e1faaab86ecb..ad6ebd397c6e 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1555,6 +1555,15 @@ const nsTArray& GfxInfo::GetGfxDriverInfo() { nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4578, "FEATURE_FAILURE_BUG_1432610"); + /** + * Disable VP8 HW decoding on Windows 8.1 on Intel Haswel for some devices. + * See bug 1760464 comment 6. + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows8_1, DeviceFamily::Bug1760464, + nsIGfxInfo::FEATURE_VP8_HW_DECODE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_BUG_1760464"); /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302 * See bug 806786 From c87ce3dd2f27799c7d08de926f22fec1d8420c7e Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 23 Mar 2022 23:30:47 +0000 Subject: [PATCH 21/43] Bug 1734886 - Part 5: Implement InitializeTransformStream r=mgaudet Differential Revision: https://phabricator.services.mozilla.com/D140377 --- dom/streams/ReadableStream.h | 6 + dom/streams/TransformStream.cpp | 262 +++++++++++++++++- dom/streams/TransformStream.h | 17 ++ dom/streams/UnderlyingSinkCallbackHelpers.cpp | 24 +- dom/streams/UnderlyingSinkCallbackHelpers.h | 44 ++- dom/streams/WritableStream.cpp | 29 ++ dom/streams/WritableStream.h | 6 + .../WritableStreamDefaultController.cpp | 10 +- dom/streams/WritableStreamDefaultController.h | 8 +- .../meta/streams/idlharness.any.js.ini | 96 ++++--- .../transform-streams/errors.any.js.ini | 24 +- .../transform-streams/general.any.js.ini | 30 +- .../transform-streams/properties.any.js.ini | 24 -- .../transform-streams/strategies.any.js.ini | 24 +- .../transform-streams/terminate.any.js.ini | 12 + 15 files changed, 495 insertions(+), 121 deletions(-) diff --git a/dom/streams/ReadableStream.h b/dom/streams/ReadableStream.h index 9515672d0646..abef7c521dda 100644 --- a/dom/streams/ReadableStream.h +++ b/dom/streams/ReadableStream.h @@ -189,6 +189,12 @@ MOZ_CAN_RUN_SCRIPT already_AddRefed ReadableStreamCancel( already_AddRefed AcquireReadableStreamDefaultReader(ReadableStream* aStream, ErrorResult& aRv); +MOZ_CAN_RUN_SCRIPT already_AddRefed CreateReadableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSourceAlgorithmsBase* aAlgorithms, + mozilla::Maybe aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, + ErrorResult& aRv); + bool ReadableStreamHasBYOBReader(ReadableStream* aStream); bool ReadableStreamHasDefaultReader(ReadableStream* aStream); diff --git a/dom/streams/TransformStream.cpp b/dom/streams/TransformStream.cpp index 149932a71035..7270f63a7c3f 100644 --- a/dom/streams/TransformStream.cpp +++ b/dom/streams/TransformStream.cpp @@ -6,6 +6,9 @@ #include "mozilla/dom/TransformStream.h" +#include "UnderlyingSourceCallbackHelpers.h" +#include "js/TypeDecls.h" +#include "mozilla/dom/Promise.h" #include "mozilla/dom/WritableStream.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/RootedDictionary.h" @@ -16,7 +19,9 @@ namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, mController) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal, + mBackpressureChangePromise, mController, + mReadable, mWritable) NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformStream) NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformStream) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStream) @@ -35,6 +40,216 @@ JSObject* TransformStream::WrapObject(JSContext* aCx, return TransformStream_Binding::Wrap(aCx, this, aGivenProto); } +// https://streams.spec.whatwg.org/#initialize-transform-stream +class TransformStreamUnderlyingSinkAlgorithms final + : public UnderlyingSinkAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + TransformStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) + + TransformStreamUnderlyingSinkAlgorithms(Promise* aStartPromise, + TransformStream* aStream) + : mStartPromise(aStartPromise), mStream(aStream) {} + + void StartCallback(JSContext* aCx, + WritableStreamDefaultController& aController, + JS::MutableHandle aRetVal, + ErrorResult& aRv) override { + // Step 1. Let startAlgorithm be an algorithm that returns startPromise. + // (Same as TransformStreamUnderlyingSourceAlgorithms::StartCallback) + aRetVal.setObject(*mStartPromise->PromiseObj()); + } + + already_AddRefed WriteCallback( + JSContext* aCx, JS::Handle aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) override { + // Step 2. Let writeAlgorithm be the following steps, taking a chunk + // argument: + // Step 1. Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, + // chunk). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed AbortCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) override { + // Step 3. Let abortAlgorithm be the following steps, taking a reason + // argument: + // Step 1. Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, + // reason). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed CloseCallback(JSContext* aCx, + ErrorResult& aRv) override { + // Step 4. Let closeAlgorithm be the following steps: + + // Step 1. Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + protected: + ~TransformStreamUnderlyingSinkAlgorithms() override = default; + + private: + RefPtr mStartPromise; + RefPtr mStream; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase, mStartPromise, + mStream) +NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamUnderlyingSinkAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSinkAlgorithms) + +// https://streams.spec.whatwg.org/#initialize-transform-stream +class TransformStreamUnderlyingSourceAlgorithms final + : public UnderlyingSourceAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + TransformStreamUnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase) + + TransformStreamUnderlyingSourceAlgorithms(Promise* aStartPromise, + TransformStream* aStream) + : mStartPromise(aStartPromise), mStream(aStream) {} + + void StartCallback(JSContext* aCx, ReadableStreamController& aController, + JS::MutableHandle aRetVal, + ErrorResult& aRv) override { + // Step 1. Let startAlgorithm be an algorithm that returns startPromise. + // (Same as TransformStreamUnderlyingSinkAlgorithms::StartCallback) + aRetVal.setObject(*mStartPromise->PromiseObj()); + } + + already_AddRefed PullCallback(JSContext* aCx, + ReadableStreamController& aController, + ErrorResult& aRv) override { + // Step 6. Let pullAlgorithm be the following steps: + // Step 1. Return ! TransformStreamDefaultSourcePullAlgorithm(stream). + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + already_AddRefed CancelCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) override { + // Step 7. Let cancelAlgorithm be the following steps, taking a reason + // argument: + // Step 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, + // reason). + // Step 2. Return a promise resolved with undefined. + // TODO + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), + aRv); + } + + void ErrorCallback() override {} + + protected: + ~TransformStreamUnderlyingSourceAlgorithms() override = default; + + private: + RefPtr mStartPromise; + RefPtr mStream; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase, + mStartPromise, mStream) +NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSourceAlgorithms, + UnderlyingSourceAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( + TransformStreamUnderlyingSourceAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSourceAlgorithms) + +// https://streams.spec.whatwg.org/#transform-stream-set-backpressure +static void TransformStreamSetBackpressure(TransformStream* aStream, + bool aBackpressure, + ErrorResult& aRv) { + // Step 1. Assert: stream.[[backpressure]] is not backpressure. + MOZ_ASSERT(aStream->Backpressure() != aBackpressure); + + // Step 2. If stream.[[backpressureChangePromise]] is not undefined, resolve + // stream.[[backpressureChangePromise]] with undefined. + if (Promise* promise = aStream->BackpressureChangePromise()) { + promise->MaybeResolveWithUndefined(); + } + + // Step 3. Set stream.[[backpressureChangePromise]] to a new promise. + RefPtr promise = Promise::Create(aStream->GetParentObject(), aRv); + if (aRv.Failed()) { + return; + } + aStream->SetBackpressureChangePromise(promise); + + // Step 4. Set stream.[[backpressure]] to backpressure. + aStream->SetBackpressure(aBackpressure); +} + +// https://streams.spec.whatwg.org/#initialize-transform-stream +void TransformStream::Initialize(JSContext* aCx, Promise* aStartPromise, + double aWritableHighWaterMark, + QueuingStrategySize* aWritableSizeAlgorithm, + double aReadableHighWaterMark, + QueuingStrategySize* aReadableSizeAlgorithm, + ErrorResult& aRv) { + // Step 1 - 4 + auto sinkAlgorithms = + MakeRefPtr(aStartPromise, this); + + // Step 5. Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, + // writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark, + // writableSizeAlgorithm). + mWritable = + CreateWritableStream(aCx, MOZ_KnownLive(mGlobal), sinkAlgorithms, + aWritableHighWaterMark, aWritableSizeAlgorithm, aRv); + if (aRv.Failed()) { + return; + } + + // Step 6 - 7 + auto sourceAlgorithms = MakeRefPtr( + aStartPromise, this); + + // Step 8. Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, + // pullAlgorithm, cancelAlgorithm, readableHighWaterMark, + // readableSizeAlgorithm). + mReadable = CreateReadableStream( + aCx, MOZ_KnownLive(mGlobal), sourceAlgorithms, + Some(aReadableHighWaterMark), aReadableSizeAlgorithm, aRv); + if (aRv.Failed()) { + return; + } + + // Step 9. Set stream.[[backpressure]] and + // stream.[[backpressureChangePromise]] to undefined. + mBackpressureChangePromise = nullptr; + + // Step 10. Perform ! TransformStreamSetBackpressure(stream, true). + TransformStreamSetBackpressure(this, true, aRv); + if (aRv.Failed()) { + return; + } + + // Step 11. Set stream.[[controller]] to undefined. + mController = nullptr; +} + // https://streams.spec.whatwg.org/#ts-constructor already_AddRefed TransformStream::Constructor( const GlobalObject& aGlobal, @@ -79,19 +294,35 @@ already_AddRefed TransformStream::Constructor( // Step 5. Let readableHighWaterMark be ? // ExtractHighWaterMark(readableStrategy, 0). - // TODO + double readableHighWaterMark = + ExtractHighWaterMark(aReadableStrategy, 0, aRv); + if (aRv.Failed()) { + return nullptr; + } // Step 6. Let readableSizeAlgorithm be ! // ExtractSizeAlgorithm(readableStrategy). - // TODO + // Note: Callers should recognize nullptr as a callback that returns 1. See + // also ReadableStream::Constructor for this design decision. + RefPtr readableSizeAlgorithm = + aReadableStrategy.mSize.WasPassed() ? &aReadableStrategy.mSize.Value() + : nullptr; // Step 7. Let writableHighWaterMark be ? // ExtractHighWaterMark(writableStrategy, 1). - // TODO + double writableHighWaterMark = + ExtractHighWaterMark(aWritableStrategy, 1, aRv); + if (aRv.Failed()) { + return nullptr; + } // Step 8. Let writableSizeAlgorithm be ! // ExtractSizeAlgorithm(writableStrategy). - // TODO + // Note: Callers should recognize nullptr as a callback that returns 1. See + // also WritableStream::Constructor for this design decision. + RefPtr writableSizeAlgorithm = + aWritableStrategy.mSize.WasPassed() ? &aWritableStrategy.mSize.Value() + : nullptr; // Step 9. Let startPromise be a new promise. nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); @@ -104,7 +335,9 @@ already_AddRefed TransformStream::Constructor( // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, // readableSizeAlgorithm). RefPtr transformStream = new TransformStream(global); - // TODO: Init() + transformStream->Initialize( + aGlobal.Context(), startPromise, writableHighWaterMark, + writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm, aRv); if (aRv.Failed()) { return nullptr; } @@ -118,7 +351,22 @@ already_AddRefed TransformStream::Constructor( // Step 12. If transformerDict["start"] exists, then resolve startPromise with // the result of invoking transformerDict["start"] with argument list « // this.[[controller]] » and callback this value transformer. - // TODO + if (transformerDict.mStart.WasPassed()) { + RefPtr callback = transformerDict.mStart.Value(); + RefPtr controller = + transformStream->Controller(); + JS::Rooted retVal(aGlobal.Context()); + callback->Call(transformerObj, *controller, &retVal, aRv, + "Transformer.start", CallbackFunction::eRethrowExceptions); + if (aRv.Failed()) { + return nullptr; + } + + startPromise->MaybeResolve(retVal); + } else { + // Step 13. Otherwise, resolve startPromise with undefined. + startPromise->MaybeResolveWithUndefined(); + } return transformStream.forget(); } diff --git a/dom/streams/TransformStream.h b/dom/streams/TransformStream.h index 8ffd188762f5..83bdf446f322 100644 --- a/dom/streams/TransformStream.h +++ b/dom/streams/TransformStream.h @@ -29,6 +29,13 @@ class TransformStream final : public nsISupports, public nsWrapperCache { NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransformStream) + // Internal slot accessors + bool Backpressure() const { return mBackpressure; } + void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; } + Promise* BackpressureChangePromise() { return mBackpressureChangePromise; } + void SetBackpressureChangePromise(Promise* aPromise) { + mBackpressureChangePromise = aPromise; + } TransformStreamDefaultController* Controller() { return mController; } void SetController(TransformStreamDefaultController* aController) { mController = aController; @@ -38,6 +45,12 @@ class TransformStream final : public nsISupports, public nsWrapperCache { ~TransformStream(); explicit TransformStream(nsIGlobalObject* aGlobal); + MOZ_CAN_RUN_SCRIPT void Initialize( + JSContext* aCx, Promise* aStartPromise, double aWritableHighWaterMark, + QueuingStrategySize* aWritableSizeAlgorithm, + double aReadableHighWaterMark, + QueuingStrategySize* aReadableSizeAlgorithm, ErrorResult& aRv); + public: nsIGlobalObject* GetParentObject() const { return mGlobal; } JSObject* WrapObject(JSContext* aCx, @@ -58,7 +71,11 @@ class TransformStream final : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; // Internal slots + bool mBackpressure = false; + RefPtr mBackpressureChangePromise; RefPtr mController; + RefPtr mReadable; + RefPtr mWritable; }; } // namespace mozilla::dom diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.cpp b/dom/streams/UnderlyingSinkCallbackHelpers.cpp index c886fb4895be..6d107692d980 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.cpp +++ b/dom/streams/UnderlyingSinkCallbackHelpers.cpp @@ -9,16 +9,24 @@ using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(UnderlyingSinkAlgorithms, - (mGlobal, mStartCallback, - mWriteCallback, mCloseCallback, - mAbortCallback), - (mUnderlyingSink)) -NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithms) -NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithms) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) +NS_IMPL_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( + UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase, + (mGlobal, mStartCallback, mWriteCallback, mCloseCallback, mAbortCallback), + (mUnderlyingSink)) +NS_IMPL_ADDREF_INHERITED(UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) +NS_IMPL_RELEASE_INHERITED(UnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase) // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink void UnderlyingSinkAlgorithms::StartCallback( diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.h b/dom/streams/UnderlyingSinkCallbackHelpers.h index 3d409d7b503b..5402a2625599 100644 --- a/dom/streams/UnderlyingSinkCallbackHelpers.h +++ b/dom/streams/UnderlyingSinkCallbackHelpers.h @@ -11,6 +11,7 @@ #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/UnderlyingSinkBinding.h" +#include "nsCycleCollectionParticipant.h" #include "nsISupports.h" #include "nsISupportsImpl.h" @@ -23,11 +24,36 @@ namespace mozilla::dom { class WritableStreamDefaultController; -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink -class UnderlyingSinkAlgorithms : public nsISupports { +class UnderlyingSinkAlgorithmsBase : public nsISupports { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithms) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UnderlyingSinkAlgorithmsBase) + + MOZ_CAN_RUN_SCRIPT virtual void StartCallback( + JSContext* aCx, WritableStreamDefaultController& aController, + JS::MutableHandle aRetVal, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed WriteCallback( + JSContext* aCx, JS::Handle aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed CloseCallback( + JSContext* aCx, ErrorResult& aRv) = 0; + + MOZ_CAN_RUN_SCRIPT virtual already_AddRefed AbortCallback( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) = 0; + + protected: + virtual ~UnderlyingSinkAlgorithmsBase() = default; +}; + +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink +class UnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) UnderlyingSinkAlgorithms(nsIGlobalObject* aGlobal, JS::HandleObject aUnderlyingSink, @@ -58,21 +84,21 @@ class UnderlyingSinkAlgorithms : public nsISupports { MOZ_CAN_RUN_SCRIPT void StartCallback( JSContext* aCx, WritableStreamDefaultController& aController, - JS::MutableHandle aRetVal, ErrorResult& aRv); + JS::MutableHandle aRetVal, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed WriteCallback( JSContext* aCx, JS::Handle aChunk, - WritableStreamDefaultController& aController, ErrorResult& aRv); + WritableStreamDefaultController& aController, ErrorResult& aRv) override; - MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback(JSContext* aCx, - ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT already_AddRefed CloseCallback( + JSContext* aCx, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed AbortCallback( JSContext* aCx, const Optional>& aReason, - ErrorResult& aRv); + ErrorResult& aRv) override; protected: - virtual ~UnderlyingSinkAlgorithms() { mozilla::DropJSObjects(this); }; + ~UnderlyingSinkAlgorithms() override { mozilla::DropJSObjects(this); } private: // Virtually const, but are cycle collected diff --git a/dom/streams/WritableStream.cpp b/dom/streams/WritableStream.cpp index 6ddef107c602..669e4cd4b4b4 100644 --- a/dom/streams/WritableStream.cpp +++ b/dom/streams/WritableStream.cpp @@ -699,6 +699,35 @@ AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv) { return writer.forget(); } +// https://streams.spec.whatwg.org/#create-writable-stream +already_AddRefed CreateWritableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, + QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { + // Step 1: Assert: ! IsNonNegativeNumber(highWaterMark) is true. + MOZ_ASSERT(IsNonNegativeNumber(aHighWaterMark)); + + // Step 2: Let stream be a new WritableStream. + // Step 3: Perform ! InitializeWritableStream(stream). + auto stream = MakeRefPtr(aGlobal); + + // Step 4: Let controller be a new WritableStreamDefaultController. + auto controller = + MakeRefPtr(aGlobal, *stream); + + // Step 5: Perform ? SetUpWritableStreamDefaultController(stream, controller, + // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, + // highWaterMark, sizeAlgorithm). + SetUpWritableStreamDefaultController(aCx, stream, controller, aAlgorithms, + aHighWaterMark, aSizeAlgorithm, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // Step 6: Return stream. + return stream.forget(); +} + already_AddRefed WritableStream::GetWriter( ErrorResult& aRv) { return AcquireWritableStreamDefaultWriter(this, aRv); diff --git a/dom/streams/WritableStream.h b/dom/streams/WritableStream.h index 7cba91159eef..4700a819f595 100644 --- a/dom/streams/WritableStream.h +++ b/dom/streams/WritableStream.h @@ -28,6 +28,7 @@ namespace mozilla::dom { class Promise; class WritableStreamDefaultController; class WritableStreamDefaultWriter; +class UnderlyingSinkAlgorithmsBase; class WritableStream : public nsISupports, public nsWrapperCache { public: @@ -180,6 +181,11 @@ class WritableStream : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; }; +MOZ_CAN_RUN_SCRIPT already_AddRefed CreateWritableStream( + JSContext* aCx, nsIGlobalObject* aGlobal, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, + QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); + inline bool IsWritableStreamLocked(WritableStream* aStream) { return aStream->Locked(); } diff --git a/dom/streams/WritableStreamDefaultController.cpp b/dom/streams/WritableStreamDefaultController.cpp index 89f222660462..8df09749c53b 100644 --- a/dom/streams/WritableStreamDefaultController.cpp +++ b/dom/streams/WritableStreamDefaultController.cpp @@ -91,7 +91,7 @@ already_AddRefed WritableStreamDefaultController::AbortSteps( JSContext* aCx, JS::Handle aReason, ErrorResult& aRv) { // Step 1. Let result be the result of performing this.[[abortAlgorithm]], // passing reason. - RefPtr algorithms = mAlgorithms; + RefPtr algorithms = mAlgorithms; Optional> optionalReason(aCx, aReason); RefPtr abortPromise = algorithms->AbortCallback(aCx, optionalReason, aRv); @@ -126,7 +126,7 @@ WritableStreamDefaultControllerAdvanceQueueIfNeeded( void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithms* aAlgorithms, double aHighWaterMark, + UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) { // Step 1. Assert: stream implements WritableStream. // Step 2. Assert: stream.[[controller]] is undefined. @@ -260,7 +260,8 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessClose( // Step 5. Let sinkClosePromise be the result of performing // controller.[[closeAlgorithm]]. - RefPtr algorithms = aController->GetAlgorithms(); + RefPtr algorithms = + aController->GetAlgorithms(); RefPtr sinkClosePromise = algorithms->CloseCallback(aCx, aRv); if (aRv.Failed()) { return; @@ -303,7 +304,8 @@ MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessWrite( // Step 3. Let sinkWritePromise be the result of performing // controller.[[writeAlgorithm]], passing in chunk. - RefPtr algorithms = aController->GetAlgorithms(); + RefPtr algorithms = + aController->GetAlgorithms(); RefPtr sinkWritePromise = algorithms->WriteCallback(aCx, aChunk, *aController, aRv); if (aRv.Failed()) { diff --git a/dom/streams/WritableStreamDefaultController.h b/dom/streams/WritableStreamDefaultController.h index 609c578d45c4..38fc0969b1a0 100644 --- a/dom/streams/WritableStreamDefaultController.h +++ b/dom/streams/WritableStreamDefaultController.h @@ -84,8 +84,8 @@ class WritableStreamDefaultController final : public nsISupports, mStrategySizeAlgorithm = aStrategySizeAlgorithm; } - UnderlyingSinkAlgorithms* GetAlgorithms() { return mAlgorithms; } - void SetAlgorithms(UnderlyingSinkAlgorithms* aAlgorithms) { + UnderlyingSinkAlgorithmsBase* GetAlgorithms() { return mAlgorithms; } + void SetAlgorithms(UnderlyingSinkAlgorithmsBase* aAlgorithms) { mAlgorithms = aAlgorithms; } @@ -128,14 +128,14 @@ class WritableStreamDefaultController final : public nsISupports, double mStrategyHWM = 0.0; RefPtr mStrategySizeAlgorithm; - RefPtr mAlgorithms; + RefPtr mAlgorithms; RefPtr mStream; }; MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultController( JSContext* aCx, WritableStream* aStream, WritableStreamDefaultController* aController, - UnderlyingSinkAlgorithms* aSinkCallbacks, double aHighWaterMark, + UnderlyingSinkAlgorithmsBase* aSinkCallbacks, double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultControllerFromUnderlyingSink( diff --git a/testing/web-platform/meta/streams/idlharness.any.js.ini b/testing/web-platform/meta/streams/idlharness.any.js.ini index d668edac00a8..0c924fd72632 100644 --- a/testing/web-platform/meta/streams/idlharness.any.js.ini +++ b/testing/web-platform/meta/streams/idlharness.any.js.ini @@ -12,7 +12,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -35,7 +36,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -118,7 +120,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -177,7 +180,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -236,7 +240,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -271,10 +276,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -289,7 +296,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -689,7 +697,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -712,7 +721,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -795,7 +805,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -854,7 +865,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -913,7 +925,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -948,10 +961,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -966,7 +981,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -1366,7 +1382,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -1389,7 +1406,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -1472,7 +1490,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -1531,7 +1550,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -1590,7 +1610,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -1625,10 +1646,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -1643,7 +1666,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: @@ -2043,7 +2067,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "terminate()" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: new WritableStream() must inherit property "locked" with the proper type] expected: @@ -2066,7 +2091,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "desiredSize" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface object length] expected: @@ -2149,7 +2175,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling error(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [WritableStream interface: existence and properties of interface prototype object's @@unscopables property] expected: @@ -2208,7 +2235,8 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: calling enqueue(optional any) on self.transformStreamDefaultController with too few arguments must throw TypeError] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamBYOBReader interface: existence and properties of interface prototype object] expected: @@ -2267,7 +2295,8 @@ if not domstreams: FAIL [Stringification of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableStreamDefaultReader interface: operation read()] expected: @@ -2302,10 +2331,12 @@ if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "enqueue(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [TransformStreamDefaultController interface: self.transformStreamDefaultController must inherit property "error(optional any)" with the proper type] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController interface: existence and properties of interface prototype object's "constructor" property] expected: @@ -2320,7 +2351,8 @@ if not domstreams: FAIL [TransformStreamDefaultController must be primary interface of self.transformStreamDefaultController] - expected: FAIL + expected: + if not domstreams: FAIL [ReadableByteStreamController must be primary interface of self.readableByteStreamController] expected: diff --git a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini index b53d537b34eb..c4152600e888 100644 --- a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini @@ -11,9 +11,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -56,6 +53,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.serviceworker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -70,9 +70,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -115,6 +112,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.worker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -129,9 +129,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -174,6 +171,9 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + [errored TransformStream should not enqueue new chunks] + expected: FAIL + [errors.any.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -188,9 +188,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [TransformStream constructor should throw when start does] - expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] expected: FAIL @@ -232,3 +229,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL + + [errored TransformStream should not enqueue new chunks] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini index d3face719e3b..0b163c08e560 100644 --- a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini @@ -50,10 +50,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -109,10 +112,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -168,10 +174,13 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] + [Subclassing TransformStream should work] expected: FAIL - [Subclassing TransformStream should work] + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] expected: FAIL @@ -227,8 +236,11 @@ [terminate() should do nothing after readable.cancel()] expected: FAIL - [start() should not be called twice] - expected: FAIL - [Subclassing TransformStream should work] expected: FAIL + + [enqueue() should throw after controller.terminate()] + expected: FAIL + + [controller.terminate() should do nothing the second time it is called] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini index 1c40cb139f22..d79ccd83adc5 100644 --- a/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/properties.any.js.ini @@ -1,10 +1,4 @@ [properties.any.worker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -19,12 +13,6 @@ [properties.any.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -39,12 +27,6 @@ [properties.any.sharedworker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL @@ -59,12 +41,6 @@ [properties.any.serviceworker.html] - [transformer method start should be called with the right number of arguments] - expected: FAIL - - [transformer method start should be called even when it's located on the prototype chain] - expected: FAIL - [transformer method transform should be called with the right number of arguments] expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini index 68ca3c853805..669dbfc0b022 100644 --- a/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/strategies.any.js.ini @@ -14,9 +14,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -26,6 +23,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.serviceworker.html] [writableStrategy highWaterMark should work] @@ -43,9 +43,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -55,6 +52,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.sharedworker.html] [writableStrategy highWaterMark should work] @@ -72,9 +72,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -84,6 +81,9 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL + [strategies.any.worker.html] [writableStrategy highWaterMark should work] @@ -101,9 +101,6 @@ [default readable strategy should be equivalent to { highWaterMark: 0 }] expected: FAIL - [a RangeError should be thrown for an invalid highWaterMark] - expected: FAIL - [writableStrategy highWaterMark should be converted to a number] expected: FAIL @@ -112,3 +109,6 @@ [a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception] expected: FAIL + + [readableStrategy highWaterMark should be converted to a number] + expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini index cf1b68ddbf43..7536f29dea2b 100644 --- a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini @@ -14,6 +14,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.worker.html] [controller.terminate() should error pipeTo()] @@ -31,6 +34,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.serviceworker.html] [controller.terminate() should error pipeTo()] @@ -48,6 +54,9 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL + [terminate.any.html] [controller.terminate() should error pipeTo()] @@ -64,3 +73,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL + + [controller.enqueue() should throw after controller.terminate()] + expected: FAIL From 7ec8ae196345e28c440bdd8b69694c7251a2afcb Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Wed, 23 Mar 2022 23:53:39 +0000 Subject: [PATCH 22/43] Bug 1761001 - Update wpt expectation because we use Animation.timeline setter in seamlessly-updating-the-playback-rate-of-an-animation.html. r=birtles Animation.timeline setter is only supported on Nightly, so we have to update the wpt expectation for this file. Differential Revision: https://phabricator.services.mozilla.com/D141889 --- ...lessly-updating-the-playback-rate-of-an-animation.html.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini diff --git a/testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini b/testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini new file mode 100644 index 000000000000..3e33dcc86124 --- /dev/null +++ b/testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini @@ -0,0 +1,4 @@ +[seamlessly-updating-the-playback-rate-of-an-animation.html] + [Updating the negative playback rate with the unresolved current time and a positive infinite associated effect end should not throw an exception] + expected: + if release_or_beta: FAIL From 84d4ef5c4206d7100eed46634ad97c2c5e943541 Mon Sep 17 00:00:00 2001 From: Butkovits Atila Date: Thu, 24 Mar 2022 02:23:00 +0200 Subject: [PATCH 23/43] Backed out changeset aaf04c8ba59a (bug 1760992) for causing failures at browser_inspector_picker-useragent-widget.js.CLOSED TREE --- devtools/client/inspector/test/browser.ini | 1 - ...owser_inspector_picker-useragent-widget.js | 74 ------------------- .../server/actors/inspector/node-picker.js | 8 -- 3 files changed, 83 deletions(-) delete mode 100644 devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini index 4d2167da6554..1120be3f9eda 100644 --- a/devtools/client/inspector/test/browser.ini +++ b/devtools/client/inspector/test/browser.ini @@ -211,7 +211,6 @@ skip-if = (os == 'win' && processor == 'aarch64') # bug 1533492 [browser_inspector_picker-shift-key.js] [browser_inspector_picker-stop-on-eyedropper.js] [browser_inspector_picker-stop-on-tool-change.js] -[browser_inspector_picker-useragent-widget.js] [browser_inspector_portrait_mode.js] [browser_inspector_pseudoclass-lock.js] [browser_inspector_pseudoclass-menu.js] diff --git a/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js b/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js deleted file mode 100644 index 7bc618c76ca1..000000000000 --- a/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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/. */ - -"use strict"; - -const TEST_URI = `data:text/html;charset=utf-8, - - `; - -// Test that using the node picker on user agent widgets only selects shadow dom -// elements if `devtools.inspector.showAllAnonymousContent` is true. -// If not, we should only surface the host, in this case, the . -// -// For this test we use an , which is using shadow dom. See -// definition in toolkit/content/widgets/datetimebox.js -add_task(async function() { - // Run the test for both values for devtools.inspector.showAllAnonymousContent - await runUserAgentWidgetPickerTest({ enableAnonymousContent: false }); - await runUserAgentWidgetPickerTest({ enableAnonymousContent: true }); -}); - -async function runUserAgentWidgetPickerTest({ enableAnonymousContent }) { - await pushPref( - "devtools.inspector.showAllAnonymousContent", - enableAnonymousContent - ); - const { inspector, toolbox } = await openInspectorForURL(TEST_URI); - - info("Use the node picker inside the element"); - await startPicker(toolbox); - const onPickerStopped = toolbox.nodePicker.once("picker-stopped"); - await pickElement(inspector, "input", 5, 5); - await onPickerStopped; - - const selectedNode = inspector.selection.nodeFront; - if (enableAnonymousContent) { - // We do not assert specifically which node was selected, we just want to - // check the node was under the shadow DOM for the - const shadowHost = getShadowHost(selectedNode); - ok( - selectedNode.tagName.toLowerCase() !== "input", - "The selected node is not the " - ); - ok(shadowHost, "The selected node is in a shadow root"); - is(shadowHost.tagName.toLowerCase(), "input", "The shadowHost is "); - } else { - is( - selectedNode.tagName.toLowerCase(), - "input", - "The selected node is the " - ); - } -} - -/** - * Retrieve the nodeFront for the shadow host containing the provided nodeFront. - * Returns null if the nodeFront is not in a shadow DOM. - * - * @param {NodeFront} nodeFront - * The nodeFront for which we want to retrieve the shadow host. - * @return {NodeFront} The nodeFront corresponding to the shadow host, or null - * if the nodeFront is not in shadow DOM. - */ -function getShadowHost(nodeFront) { - let parent = nodeFront; - while (parent) { - if (parent.isShadowHost) { - return parent; - } - parent = parent.parentOrHost(); - } - return null; -} diff --git a/devtools/server/actors/inspector/node-picker.js b/devtools/server/actors/inspector/node-picker.js index d339d39775c9..cdddbdb6b47f 100644 --- a/devtools/server/actors/inspector/node-picker.js +++ b/devtools/server/actors/inspector/node-picker.js @@ -70,14 +70,6 @@ class NodePicker { node = this._findNodeAtMouseEventPosition(event) || node; } - // The node picker should only surface elements valid for the current walker - // configuration. UserAgent shadow DOM should only be visible if - // showAllAnonymousContent is set to true. Otherwise, fallback to the host. - const shadowRoot = node.containingShadowRoot; - if (shadowRoot?.isUAWidget() && !this._walker.showAllAnonymousContent) { - node = shadowRoot.host; - } - return this._walker.attachElement(node); } From 7b522cebcc16ba64fd6dd68d4e5bb0ad8f093e4d Mon Sep 17 00:00:00 2001 From: Timothy Nikkel Date: Wed, 23 Mar 2022 23:58:23 +0000 Subject: [PATCH 24/43] Bug 1695598. Allow apz to update the main thread scroll offset if an APZ scroll animation is pending on the main thread or has been sent from the main thread. r=botond Apz can already update the main thread scroll offset if apz has informed the main thread that it has received a scroll animation and is running it. This situation can happen when the user holds down an arrow key, or in a test situation if one sends a key event every frame via requestAnimateFrame. The problem happens in APZCCallbackHelper because aFrame->IsScrollAnimating(IncludeApzAnimation::No) returns true so we don't update the main thread scroll offset from the repaint request. aFrame->IsScrollAnimating(IncludeApzAnimation::No) returns true because there is a smooth scroll request either pending on the main thread, or sent in the last transaction, but not yet acknowledged by apz back to the main thread. (Scroll animations that are acknowledged by apz back to the main thread are ignored in IsScrollAnimating because of the IncludeApzAnimation::No argument.) So we need to change IsScrollAnimating. However there are two other callers of it: 1) ScrollFrameHelper::SaveState (passes IncludeApzAnimation::No) 2) everything else (passes IncludeApzAnimation::Yes) We can leave 2) alone, but 1) needs the current behaviour of IncludeApzAnimation::No. This is because there are tests that check that we save mDestination (the destination of the smooth scroll) and restore it. However we do not want to save mDestination if IsApzAnimationInProgress because mDestination has already been overwritten with the current scroll position from apz (via APZCCallbackHelper->ScrollToCSSPixelsApproximate). So we change IncludeApzAnimation::No to ignore all apz animations (what APZCCallbackHelper needs) and introduce a new value of the enum for what SaveState needs. Differential Revision: https://phabricator.services.mozilla.com/D106757 --- layout/generic/nsGfxScrollFrame.cpp | 10 ++++++---- layout/generic/nsIScrollableFrame.h | 22 +++++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 6e2170f5b828..5ee153d0b959 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2448,7 +2448,7 @@ void ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition, // Bug 1740164: We will apply it for cases there's no animation in APZ. if (mCurrentAPZScrollAnimationType == APZScrollAnimationType::TriggeredByUserInput && - !IsScrollAnimating(IncludeApzAnimation::No)) { + !IsScrollAnimating(IncludeApzAnimation::PendingAndRequestedOnly)) { CSSIntPoint delta = aScrollPosition - currentCSSPixels; ScrollByCSSPixels(delta, aMode); return; @@ -7330,10 +7330,11 @@ bool ScrollFrameHelper::IsScrollAnimating( if (aIncludeApz == IncludeApzAnimation::Yes && IsApzAnimationInProgress()) { return true; } - if (IsLastScrollUpdateAnimating()) { + if (aIncludeApz != IncludeApzAnimation::No && IsLastScrollUpdateAnimating()) { return true; } - return mApzAnimationRequested || mAsyncScroll || mAsyncSmoothMSDScroll; + return (aIncludeApz != IncludeApzAnimation::No && mApzAnimationRequested) || + mAsyncScroll || mAsyncSmoothMSDScroll; } void ScrollFrameHelper::ResetScrollInfoIfNeeded( @@ -7360,7 +7361,8 @@ UniquePtr ScrollFrameHelper::SaveState() const { // Don't store a scroll state if we never have been scrolled or restored // a previous scroll state, and we're not in the middle of a smooth scroll. - bool isScrollAnimating = IsScrollAnimating(IncludeApzAnimation::No); + bool isScrollAnimating = + IsScrollAnimating(IncludeApzAnimation::PendingAndRequestedOnly); if (!mHasBeenScrolled && !mDidHistoryRestore && !isScrollAnimating) { return nullptr; } diff --git a/layout/generic/nsIScrollableFrame.h b/layout/generic/nsIScrollableFrame.h index 030ad726a57c..aa39e9a2486a 100644 --- a/layout/generic/nsIScrollableFrame.h +++ b/layout/generic/nsIScrollableFrame.h @@ -413,12 +413,24 @@ class nsIScrollableFrame : public nsIScrollbarMediator { /** * Returns whether there's an async scroll going on. * - * The argument allows a subtle distinction that's needed for APZ. When - * `IncludeApzAnimation::No` is given, ongoing APZ animations that have - * already been synced to the main thread are not included, which is needed so - * that APZ can keep syncing the scroll offset properly. + * The argument allows a subtle distinction that's needed for APZ. APZ scroll + * animations that are requested from the main thread go through three states: + * 1) pending, when the main thread has recorded that it wants apz to do a + * scroll animation, 2) requested, when the main thread has sent the request + * to the compositor (but it hasn't necessarily arrived yet), and 3) in + * progress, after apz has responded to the main thread that is got the + * request. When `IncludeApzAnimation::No` is given, APZ animations in any of + * the three states are not included, which is needed so that APZ can keep + * syncing the scroll offset properly. When + * `IncludeApzAnimation::PendingAndRequestedOnly` is given, only APZ + * animations in the pending or requested state are included, this is needed + * when saving the state of a scroll frame (we want to save the mDestination + * of the animation, but the repaint request that informs the main thread that + * the animation is in progress will also overwrite mDestination with the + * current scroll position). When `IncludeApzAnimation::Yes` is given, APZ + * animations in any state are included, this is what all other callers use. */ - enum class IncludeApzAnimation : bool { No, Yes }; + enum class IncludeApzAnimation : uint8_t { No, PendingAndRequestedOnly, Yes }; virtual bool IsScrollAnimating( IncludeApzAnimation = IncludeApzAnimation::Yes) = 0; From c2e46664d6103540367a6e9e1816c1fa7b65b330 Mon Sep 17 00:00:00 2001 From: Timothy Nikkel Date: Wed, 23 Mar 2022 23:58:24 +0000 Subject: [PATCH 25/43] Bug 1695598. Add test. r=tnikkel Differential Revision: https://phabricator.services.mozilla.com/D107043 --- .../apz/test/mochitest/apz_test_utils.js | 14 +- .../apz/test/mochitest/helper_bug1695598.html | 124 ++++++++++++++++++ .../test/mochitest/test_group_keyboard.html | 1 + 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1695598.html diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js index abfbd73315d8..2b3954288e84 100644 --- a/gfx/layers/apz/test/mochitest/apz_test_utils.js +++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js @@ -287,7 +287,7 @@ function promiseAfterPaint() { // APZ handler on the main thread, the repaints themselves may not have // occurred by the the returned promise resolves. If you want to wait // for those repaints, consider using promiseApzFlushedRepaints instead. -function promiseOnlyApzControllerFlushed(aWindow = window) { +function promiseOnlyApzControllerFlushedWithoutSetTimeout(aWindow = window) { return new Promise(function(resolve, reject) { var repaintDone = function() { dump("PromiseApzRepaintsFlushed: APZ flush done\n"); @@ -295,7 +295,7 @@ function promiseOnlyApzControllerFlushed(aWindow = window) { repaintDone, "apz-repaints-flushed" ); - setTimeout(resolve, 0); + resolve(); }; SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed"); if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) { @@ -311,6 +311,16 @@ function promiseOnlyApzControllerFlushed(aWindow = window) { }); } +// Another variant of the above promiseOnlyApzControllerFlushedWithoutSetTimeout +// but with a setTimeout(0) callback. +function promiseOnlyApzControllerFlushed(aWindow = window) { + return new Promise(resolve => { + promiseOnlyApzControllerFlushedWithoutSetTimeout(aWindow).then(() => { + setTimeout(resolve, 0); + }); + }); +} + // Flush repaints, APZ pending repaints, and any repaints resulting from that // flush. This is particularly useful if the test needs to reach some sort of // "idle" state in terms of repaints. Usually just waiting for all paints diff --git a/gfx/layers/apz/test/mochitest/helper_bug1695598.html b/gfx/layers/apz/test/mochitest/helper_bug1695598.html new file mode 100644 index 000000000000..6000355e23b3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1695598.html @@ -0,0 +1,124 @@ + + + Test for bug 1695598 + + + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_group_keyboard.html b/gfx/layers/apz/test/mochitest/test_group_keyboard.html index 9469be8cf9bf..a7ce5e01b929 100644 --- a/gfx/layers/apz/test/mochitest/test_group_keyboard.html +++ b/gfx/layers/apz/test/mochitest/test_group_keyboard.html @@ -45,6 +45,7 @@ var subtests = [ prefs: smoothness_prefs }, {"file": "helper_relative_scroll_smoothness.html?input-type=native-key&scroll-method=scrollTop", prefs: smoothness_prefs }, + {"file": "helper_bug1695598.html"}, // Run helper_bug1756529.html twice, first exercising the main-thread keyboard // scrolling codepaths (e.g. PresShell::ScrollPage()), and once exercising the // APZ keyboard scrolling codepaths. From cc758fd688490b11cb3caf388f472a393b9d9ec0 Mon Sep 17 00:00:00 2001 From: James Teh Date: Thu, 24 Mar 2022 00:06:15 +0000 Subject: [PATCH 26/43] Bug 1550644: nsTextEquivUtils: Add space for block elements when we encounter them. r=eeejay,adw Previously, when we were about to add text from a text node, we looked at the parent to determine if it was a block element and added space based on that. This caused problems if there were multiple text nodes inside a block because we ended up adding space between those text nodes, even if there was no intervening block. Instead, since we're walking the tree, we now add space whenever we encounter a block, both before and after its descendants. If there are multiple adjacent blocks, this does mean we append multiple spaces, but we compress space befor returning to the caller anyway. This fixes highlights in address bar suggestions being separated from the rest of the suggestion. Differential Revision: https://phabricator.services.mozilla.com/D141722 --- accessible/base/nsTextEquivUtils.cpp | 50 +++++++++---------- .../tests/mochitest/name/test_general.html | 27 ++++++++-- .../browser_autocomplete_a11y_label.js | 11 ++-- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/accessible/base/nsTextEquivUtils.cpp b/accessible/base/nsTextEquivUtils.cpp index e693bb349f49..d2985c940841 100644 --- a/accessible/base/nsTextEquivUtils.cpp +++ b/accessible/base/nsTextEquivUtils.cpp @@ -105,26 +105,6 @@ nsresult nsTextEquivUtils::AppendTextEquivFromContent( nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent, nsAString* aString) { if (aContent->IsText()) { - bool isHTMLBlock = false; - - nsIContent* parentContent = aContent->GetFlattenedTreeParent(); - if (parentContent) { - nsIFrame* frame = parentContent->GetPrimaryFrame(); - if (frame) { - // If this text is inside a block level frame (as opposed to span - // level), we need to add spaces around that block's text, so we don't - // get words jammed together in final name. - const nsStyleDisplay* display = frame->StyleDisplay(); - if (display->IsBlockOutsideStyle() || - display->mDisplay == StyleDisplay::TableCell) { - isHTMLBlock = true; - if (!aString->IsEmpty()) { - aString->Append(char16_t(' ')); - } - } - } - } - if (aContent->TextLength() > 0) { nsIFrame* frame = aContent->GetPrimaryFrame(); if (frame) { @@ -136,9 +116,6 @@ nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent, // If aContent is an object that is display: none, we have no a frame. aContent->GetAsText()->AppendTextTo(*aString); } - if (isHTMLBlock && !aString->IsEmpty()) { - aString->Append(char16_t(' ')); - } } return NS_OK; @@ -184,10 +161,27 @@ nsresult nsTextEquivUtils::AppendFromAccessibleChildren( nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible, nsAString* aString) { // XXX: is it necessary to care the accessible is not a document? + bool isHTMLBlock = false; if (aAccessible->IsLocal() && aAccessible->AsLocal()->IsContent()) { - nsresult rv = AppendTextEquivFromTextContent( - aAccessible->AsLocal()->GetContent(), aString); + nsIContent* content = aAccessible->AsLocal()->GetContent(); + nsresult rv = AppendTextEquivFromTextContent(content, aString); if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv; + if (!content->IsText()) { + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame) { + // If this is a block level frame (as opposed to span level), we need to + // add spaces around that block's text, so we don't get words jammed + // together in final name. + const nsStyleDisplay* display = frame->StyleDisplay(); + if (display->IsBlockOutsideStyle() || + display->mDisplay == StyleDisplay::TableCell) { + isHTMLBlock = true; + if (!aString->IsEmpty()) { + aString->Append(char16_t(' ')); + } + } + } + } } bool isEmptyTextEquiv = true; @@ -220,9 +214,15 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible, // Implementation of h. step if (isEmptyTextEquiv && !text.IsEmpty()) { AppendString(aString, text); + if (isHTMLBlock) { + aString->Append(char16_t(' ')); + } return NS_OK; } + if (!isEmptyTextEquiv && isHTMLBlock) { + aString->Append(char16_t(' ')); + } return rv; } diff --git a/accessible/tests/mochitest/name/test_general.html b/accessible/tests/mochitest/name/test_general.html index 552929893625..89314712fb0b 100644 --- a/accessible/tests/mochitest/name/test_general.html +++ b/accessible/tests/mochitest/name/test_general.html @@ -58,10 +58,9 @@ // spaces) testName("btn_labelledby_mixed_block", "text more text"); - // Gets the name from text nodes contained by html:td (every text node - // value in the name should be devided by spaces). - // XXX: this case is rather a feature than strong wanted behaviour. - testName("btn_labelledby_mixed_table", "text space text"); + // Gets the name from text nodes contained by html:td. The text nodes + // should not be divided by spaces. + testName("btn_labelledby_mixed_table", "textTEXTtext"); // Gets the name from image accessible. testName("btn_labelledby_mixed_img", "text image"); @@ -251,6 +250,17 @@ // Name from subtree of grouping labelled by an ancestor. testName("grouping_labelledby_ancestor", "label"); + // Name from subtree of a container containing text nodes and inline + // elements. There should be no spaces because everyhing is inline. + testName("container_text_inline", "abc"); + // Name from subtree of a container containing text nodes and block + // elements. There should be a space on both sides of the block. + testName("container_text_block", "a b c"); + // Name from subtree of a container containing text nodes and empty + // block elements. There should be space on either side of the blocks, but + // not a double space. + testName("container_text_emptyblock", "a b"); + SimpleTest.finish(); } @@ -387,7 +397,7 @@ - +
textspacetexttextTEXTtext
@@ -711,5 +721,12 @@ This content should not be included in the grouping's label. + + +
abc
+ +
a

b

c
+ +
a

b
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js index c92773ebf16d..ee2747e9989e 100644 --- a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js +++ b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js @@ -78,7 +78,7 @@ add_task(async function switchToTab() { // before the result. await getResultText( element, - "Firefox Suggest about: robots— Switch to Tab", + "Firefox Suggest about:robots — Switch to Tab", "Result a11y text is correct" ); @@ -116,9 +116,8 @@ add_task(async function searchSuggestions() { ); // The first expected search is the search term itself since the heuristic // result will come before the search suggestions. - // The extra spaces are here due to bug 1550644. - let searchTerm = "foo "; - let expectedSearches = [searchTerm, "foo foo", "foo bar"]; + let searchTerm = "foo"; + let expectedSearches = [searchTerm, "foofoo", "foobar"]; for (let i = 0; i < length; i++) { let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i); if (result.type === UrlbarUtils.RESULT_TYPE.SEARCH) { @@ -140,7 +139,7 @@ add_task(async function searchSuggestions() { if (result.searchParams.inPrivateWindow) { await getResultText( element, - searchTerm + "— Search in a Private Window", + searchTerm + " — Search in a Private Window", "Check result label" ); } else { @@ -148,7 +147,7 @@ add_task(async function searchSuggestions() { await getResultText( element, suggestion + - "— Search with browser_searchSuggestionEngine searchSuggestionEngine.xml", + " — Search with browser_searchSuggestionEngine searchSuggestionEngine.xml", "Check result label" ); } From a75b92ee74e292bc34b65f6f0127816b2dfc1c75 Mon Sep 17 00:00:00 2001 From: sotaro Date: Thu, 24 Mar 2022 00:18:33 +0000 Subject: [PATCH 27/43] Bug 1760618 - Call NotifyChildCreated() after WebRenderBridgeParent::UpdateWebRender() in CompositorBridgeParent::RecvAdoptChild() r=gfx-reviewers,aosmond In CompositorBridgeParent::RecvAdoptChild(), NotifyChildCreated() is called before WebRenderBridgeParent::UpdateWebRender(). The NotifyChildCreated() updates sIndirectLayerTrees[child].mParent. It affects to WebRenderBridgeParent::GetRootCompositorBridgeParent(). In the UpdateWebRender(), WebRenderBridgeParent::ClearAnimationResources() is called. But in ClearAnimationResources(), OMTASampler is got from new CompositorBridgeParent. In this case, OMTASampler needs to be got from old CompositorBridgeParent. Differential Revision: https://phabricator.services.mozilla.com/D141819 --- gfx/layers/ipc/CompositorBridgeParent.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp index d8a6dadde06f..5bdb554f3cf4 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.cpp +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -1133,7 +1133,6 @@ mozilla::ipc::IPCResult CompositorBridgeParent::RecvAdoptChild( } oldApzUpdater = sIndirectLayerTrees[child].mParent->mApzUpdater; } - NotifyChildCreated(child); if (mWrBridge) { childWrBridge = sIndirectLayerTrees[child].mWrBridge; } @@ -1154,6 +1153,13 @@ mozilla::ipc::IPCResult CompositorBridgeParent::RecvAdoptChild( now, now, now); } + { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + // Update sIndirectLayerTrees[child].mParent after + // WebRenderBridgeParent::UpdateWebRender(). + NotifyChildCreated(child); + } + if (oldApzUpdater) { // If we are moving a child from an APZ-enabled window to an APZ-disabled // window (which can happen if e.g. a WebExtension moves a tab into a From 5f6fb83e59e0b9f7c594990f5368ff5f41d731d1 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Thu, 24 Mar 2022 00:27:40 +0000 Subject: [PATCH 28/43] Bug 1207753 - dom/promises thread-safety annotations r=smaug Differential Revision: https://phabricator.services.mozilla.com/D130947 --- dom/promise/Promise.cpp | 4 ++++ dom/promise/PromiseWorkerProxy.h | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index c11cbf9c37ab..2e5602f2b171 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -762,6 +762,10 @@ WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const { return mWorkerRef->Private(); } +bool PromiseWorkerProxy::OnWritingThread() const { + return IsCurrentThreadRunningWorker(); +} + Promise* PromiseWorkerProxy::WorkerPromise() const { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mWorkerPromise); diff --git a/dom/promise/PromiseWorkerProxy.h b/dom/promise/PromiseWorkerProxy.h index 75098dd1ab27..21cf663f8071 100644 --- a/dom/promise/PromiseWorkerProxy.h +++ b/dom/promise/PromiseWorkerProxy.h @@ -117,7 +117,8 @@ class WorkerPrivate; // references to it are dropped. class PromiseWorkerProxy : public PromiseNativeHandler, - public StructuredCloneHolderBase { + public StructuredCloneHolderBase, + public SingleWriterLockOwner { friend class PromiseWorkerProxyRunnable; NS_DECL_THREADSAFE_ISUPPORTS @@ -132,6 +133,8 @@ class PromiseWorkerProxy : public PromiseNativeHandler, PromiseWorkerProxy* aProxy, JS::HandleObject aObj); + bool OnWritingThread() const override; + struct PromiseWorkerProxyStructuredCloneCallbacks { ReadCallbackOp Read; WriteCallbackOp Write; @@ -144,7 +147,7 @@ class PromiseWorkerProxy : public PromiseNativeHandler, // Main thread callers must hold Lock() and check CleanUp() before calling // this. Worker thread callers, this will assert that the proxy has not been // cleaned up. - WorkerPrivate* GetWorkerPrivate() const; + WorkerPrivate* GetWorkerPrivate() const NO_THREAD_SAFETY_ANALYSIS; // This should only be used within WorkerRunnable::WorkerRun() running on the // worker thread! Do not call this after calling CleanUp(). @@ -156,9 +159,9 @@ class PromiseWorkerProxy : public PromiseNativeHandler, // 2. WorkerPromise() will crash! void CleanUp(); - Mutex& Lock() { return mCleanUpLock; } + Mutex& Lock() RETURN_CAPABILITY(mCleanUpLock) { return mCleanUpLock; } - bool CleanedUp() const { + bool CleanedUp() const REQUIRES(mCleanUpLock) { mCleanUpLock.AssertCurrentThreadOwns(); return mCleanedUp; } @@ -202,12 +205,15 @@ class PromiseWorkerProxy : public PromiseNativeHandler, // Modified on the worker thread. // It is ok to *read* this without a lock on the worker. // Main thread must always acquire a lock. - bool mCleanedUp; // To specify if the cleanUp() has been done. + bool mCleanedUp + GUARDED_BY(mCleanUpLock); // To specify if the cleanUp() has been done. const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks; // Ensure the worker and the main thread won't race to access |mCleanedUp|. - Mutex mCleanUpLock MOZ_UNANNOTATED; + // Should be a MutexSingleWriter, but that causes a lot of issues when you + // expose the lock via Lock(). + Mutex mCleanUpLock; }; } // namespace dom } // namespace mozilla From 131dd31758c9fe761abbd00933a0f308fcc9dcdb Mon Sep 17 00:00:00 2001 From: Butkovits Atila Date: Thu, 24 Mar 2022 03:28:12 +0200 Subject: [PATCH 29/43] Backed out changeset f26f836c1575 (bug 1756468) for causing leakes. CLOSED TREE --- gfx/2d/2D.h | 6 +- gfx/thebes/gfxDWriteFonts.cpp | 80 +++----- gfx/thebes/gfxDWriteFonts.h | 14 +- gfx/thebes/gfxFT2FontBase.cpp | 16 +- gfx/thebes/gfxFT2FontBase.h | 18 +- gfx/thebes/gfxFT2Fonts.cpp | 23 +-- gfx/thebes/gfxFT2Utils.h | 4 +- gfx/thebes/gfxFcPlatformFontList.cpp | 21 +- gfx/thebes/gfxFont.cpp | 275 ++++++++++----------------- gfx/thebes/gfxFont.h | 118 +++++------- gfx/thebes/gfxFontEntry.cpp | 4 +- gfx/thebes/gfxFontEntry.h | 4 +- gfx/thebes/gfxGDIFont.cpp | 37 ++-- gfx/thebes/gfxGDIFont.h | 10 +- gfx/thebes/gfxHarfBuzzShaper.h | 4 - gfx/thebes/gfxMacFont.cpp | 35 ++-- gfx/thebes/gfxMacFont.h | 4 +- 17 files changed, 258 insertions(+), 415 deletions(-) diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index f982cae7dda8..a796b70867e5 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -980,7 +980,7 @@ class SharedFTFace : public external::AtomicRefCounted { * If no owner is given, then the user should avoid modifying any state on * the face so as not to invalidate the prior owner's modification. */ - bool Lock(const void* aOwner = nullptr) CAPABILITY_ACQUIRE(mLock) { + bool Lock(void* aOwner = nullptr) CAPABILITY_ACQUIRE(mLock) { mLock.Lock(); return !aOwner || mLastLockOwner.exchange(aOwner) == aOwner; } @@ -989,7 +989,7 @@ class SharedFTFace : public external::AtomicRefCounted { /** Should be called when a lock owner is destroyed so that we don't have * a dangling pointer to a destroyed owner. */ - void ForgetLockOwner(const void* aOwner) { + void ForgetLockOwner(void* aOwner) { if (aOwner) { mLastLockOwner.compareExchange(aOwner, nullptr); } @@ -1002,7 +1002,7 @@ class SharedFTFace : public external::AtomicRefCounted { // Remember the last owner of the lock, even after unlocking, to allow users // to avoid reinitializing state on the FT face if the last owner hasn't // changed by the next time it is locked with the same owner. - Atomic mLastLockOwner; + Atomic mLastLockOwner; }; #endif diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp index 74e2b5acd2c8..c57725b7cbb7 100644 --- a/gfx/thebes/gfxDWriteFonts.cpp +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -107,12 +107,7 @@ gfxDWriteFont::gfxDWriteFont(const RefPtr& aUnscaledFont, ComputeMetrics(anAAOption); } -gfxDWriteFont::~gfxDWriteFont() { - if (auto* scaledFont = mAzureScaledFontGDI.exchange(nullptr)) { - scaledFont->Release(); - } - delete mMetrics; -} +gfxDWriteFont::~gfxDWriteFont() { delete mMetrics; } /* static */ bool gfxDWriteFont::InitDWriteSupport() { @@ -277,12 +272,17 @@ void gfxDWriteFont::UpdateClearTypeVars() { pixelGeometry, renderingModePref); } -gfxFont* gfxDWriteFont::CopyWithAntialiasOption( - AntialiasOption anAAOption) const { +UniquePtr gfxDWriteFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) { auto entry = static_cast(mFontEntry.get()); RefPtr unscaledFont = static_cast(mUnscaledFont.get()); - return new gfxDWriteFont(unscaledFont, entry, &mStyle, mFontFace, anAAOption); + return MakeUnique(unscaledFont, entry, &mStyle, mFontFace, + anAAOption); +} + +const gfxFont::Metrics& gfxDWriteFont::GetHorizontalMetrics() { + return *mMetrics; } bool gfxDWriteFont::GetFakeMetricsForArialBlack( @@ -743,7 +743,7 @@ gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) { } bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const { + bool aTight) { DWRITE_GLYPH_METRICS m; HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE); if (FAILED(hr)) { @@ -780,50 +780,32 @@ void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, already_AddRefed gfxDWriteFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - bool useClearType = UsingClearType(); - if (mAzureScaledFontUsedClearType != useClearType) { - if (auto* oldScaledFont = mAzureScaledFont.exchange(nullptr)) { - oldScaledFont->Release(); - } - if (auto* oldScaledFont = mAzureScaledFontGDI.exchange(nullptr)) { - oldScaledFont->Release(); - } + if (mAzureScaledFontUsedClearType != UsingClearType()) { + mAzureScaledFont = nullptr; + mAzureScaledFontGDI = nullptr; } bool forceGDI = aRunParams.allowGDI && GetForceGDIClassic(); - ScaledFont* scaledFont = forceGDI ? mAzureScaledFontGDI : mAzureScaledFont; - if (scaledFont) { - return do_AddRef(scaledFont); - } + RefPtr& azureScaledFont = + forceGDI ? mAzureScaledFontGDI : mAzureScaledFont; + if (!azureScaledFont) { + gfxDWriteFontEntry* fe = static_cast(mFontEntry.get()); + bool useEmbeddedBitmap = + (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT || + forceGDI) && + fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); - gfxDWriteFontEntry* fe = static_cast(mFontEntry.get()); - bool useEmbeddedBitmap = - (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT || - forceGDI) && - fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)); - - const gfxFontStyle* fontStyle = GetStyle(); - RefPtr newScaledFont = Factory::CreateScaledFontForDWriteFont( - mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), - useEmbeddedBitmap, ApplySyntheticBold(), forceGDI); - if (!newScaledFont) { - return nullptr; - } - InitializeScaledFont(newScaledFont); - - if (forceGDI) { - if (mAzureScaledFontGDI.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - mAzureScaledFontUsedClearType = useClearType; + const gfxFontStyle* fontStyle = GetStyle(); + azureScaledFont = Factory::CreateScaledFontForDWriteFont( + mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(), + useEmbeddedBitmap, ApplySyntheticBold(), forceGDI); + if (!azureScaledFont) { + return nullptr; } - scaledFont = mAzureScaledFontGDI; - } else { - if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - mAzureScaledFontUsedClearType = useClearType; - } - scaledFont = mAzureScaledFont; + InitializeScaledFont(azureScaledFont); + mAzureScaledFontUsedClearType = UsingClearType(); } - return do_AddRef(scaledFont); + + return do_AddRef(azureScaledFont); } bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const { diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h index f846412e9460..f5771dfff124 100644 --- a/gfx/thebes/gfxDWriteFonts.h +++ b/gfx/thebes/gfxDWriteFonts.h @@ -39,9 +39,10 @@ class gfxDWriteFont final : public gfxFont { static void SystemTextQualityChanged(); - gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; + mozilla::UniquePtr CopyWithAntialiasOption( + AntialiasOption anAAOption) override; - bool AllowSubpixelAA() const override { return mAllowManualShowGlyphs; } + bool AllowSubpixelAA() override { return mAllowManualShowGlyphs; } bool IsValid() const; @@ -58,8 +59,7 @@ class gfxDWriteFont final : public gfxFont { int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const override; @@ -74,7 +74,7 @@ class gfxDWriteFont final : public gfxFont { bool ShouldRoundXOffset(cairo_t* aCairo) const override; protected: - const Metrics& GetHorizontalMetrics() const override { return *mMetrics; } + const Metrics& GetHorizontalMetrics() override; bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS* aFontMetrics); @@ -102,12 +102,12 @@ class gfxDWriteFont final : public gfxFont { // Used to record the sUseClearType setting at the time mAzureScaledFont // was set up, so we can tell if it's stale and needs to be re-created. - mozilla::Atomic mAzureScaledFontUsedClearType; + bool mAzureScaledFontUsedClearType; // Cache the GDI version of the ScaledFont so that font keys and other // meta-data can remain stable even if there is thrashing between GDI and // non-GDI usage. - mozilla::Atomic mAzureScaledFontGDI; + RefPtr mAzureScaledFontGDI; bool UsingClearType() { return mozilla::gfx::gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY; diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp index 85937ed5eb87..a3a216ccc760 100644 --- a/gfx/thebes/gfxFT2FontBase.cpp +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -41,7 +41,7 @@ gfxFT2FontBase::gfxFT2FontBase( gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } -FT_Face gfxFT2FontBase::LockFTFace() const +FT_Face gfxFT2FontBase::LockFTFace() CAPABILITY_ACQUIRE(mFTFace) NO_THREAD_SAFETY_ANALYSIS { if (!mFTFace->Lock(this)) { FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); @@ -52,7 +52,7 @@ FT_Face gfxFT2FontBase::LockFTFace() const return mFTFace->GetFace(); } -void gfxFT2FontBase::UnlockFTFace() const +void gfxFT2FontBase::UnlockFTFace() CAPABILITY_RELEASE(mFTFace) NO_THREAD_SAFETY_ANALYSIS { mFTFace->Unlock(); } @@ -553,6 +553,10 @@ void gfxFT2FontBase::InitMetrics() { #endif } +const gfxFont::Metrics& gfxFT2FontBase::GetHorizontalMetrics() { + return mMetrics; +} + uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, uint32_t variation_selector) { if (variation_selector) { @@ -593,7 +597,7 @@ bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { gfx_text_subpixel_position_force_enabled_AtStartup())); } -FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const { +FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) { FT_Vector strength = {0, 0}; if (!mEmbolden) { return strength; @@ -624,7 +628,7 @@ FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const { } bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, - IntRect* aBounds) const { + IntRect* aBounds) { gfxFT2LockedFace face(this); MOZ_ASSERT(face.get()); if (!face.get()) { @@ -711,7 +715,7 @@ bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, * FreeType for the glyph extents and initialize the glyph metrics. */ const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics( - uint16_t aGID, IntRect* aBounds) const { + uint16_t aGID, IntRect* aBounds) { if (!mGlyphMetrics) { mGlyphMetrics = mozilla::MakeUnique>(128); @@ -735,7 +739,7 @@ int32_t gfxFT2FontBase::GetGlyphWidth(uint16_t aGID) { } bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const { + bool aTight) { IntRect bounds; const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); if (!metrics.HasValidBounds()) { diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h index d219d397aee7..6e49a6c266a7 100644 --- a/gfx/thebes/gfxFT2FontBase.h +++ b/gfx/thebes/gfxFT2FontBase.h @@ -51,8 +51,7 @@ class gfxFT2FontBase : public gfxFont { uint32_t variation_selector) override; bool ProvidesGlyphWidths() const override { return true; } int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; FontType GetType() const override { return FONT_TYPE_FT2; } @@ -62,8 +61,8 @@ class gfxFT2FontBase : public gfxFont { const nsTArray& aVariations, FT_Face aFTFace); - FT_Face LockFTFace() const; - void UnlockFTFace() const; + FT_Face LockFTFace(); + void UnlockFTFace(); private: uint32_t GetCharExtents(uint32_t aChar, gfxFloat* aWidth, @@ -72,12 +71,12 @@ class gfxFT2FontBase : public gfxFont { // Get advance (and optionally bounds) of a single glyph from FreeType, // and return true, or return false if we failed. bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth, - mozilla::gfx::IntRect* aBounds = nullptr) const; + mozilla::gfx::IntRect* aBounds = nullptr); protected: void InitMetrics(); - const Metrics& GetHorizontalMetrics() const override { return mMetrics; } - FT_Vector GetEmboldenStrength(FT_Face aFace) const; + const Metrics& GetHorizontalMetrics() override; + FT_Vector GetEmboldenStrength(FT_Face aFace); RefPtr mFTFace; @@ -134,10 +133,9 @@ class gfxFT2FontBase : public gfxFont { }; const GlyphMetrics& GetCachedGlyphMetrics( - uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr) const; + uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr); - mutable mozilla::UniquePtr> - mGlyphMetrics; + mozilla::UniquePtr> mGlyphMetrics; }; // Helper classes used for clearing out user font data when FT font diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index 9cc3a4bfd5f1..bb6a977cd8fb 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -166,24 +166,15 @@ gfxFT2Font::~gfxFT2Font() {} already_AddRefed gfxFT2Font::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (ScaledFont* scaledFont = mAzureScaledFont) { - return do_AddRef(scaledFont); + if (!mAzureScaledFont) { + mAzureScaledFont = Factory::CreateScaledFontForFreeTypeFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, + GetStyle()->NeedsSyntheticBold(GetFontEntry())); + InitializeScaledFont(); } - RefPtr newScaledFont = Factory::CreateScaledFontForFreeTypeFont( - GetUnscaledFont(), GetAdjustedSize(), mFTFace, - GetStyle()->NeedsSyntheticBold(GetFontEntry())); - if (!newScaledFont) { - return nullptr; - } - - InitializeScaledFont(newScaledFont); - - if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - } - ScaledFont* scaledFont = mAzureScaledFont; - return do_AddRef(scaledFont); + RefPtr scaledFont(mAzureScaledFont); + return scaledFont.forget(); } bool gfxFT2Font::ShouldHintMetrics() const { diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h index 37e930c74374..4defe373c7e4 100644 --- a/gfx/thebes/gfxFT2Utils.h +++ b/gfx/thebes/gfxFT2Utils.h @@ -28,7 +28,7 @@ typedef struct FT_FaceRec_* FT_Face; */ class MOZ_STACK_CLASS gfxFT2LockedFace { public: - explicit gfxFT2LockedFace(const gfxFT2FontBase* aFont) + explicit gfxFT2LockedFace(gfxFT2FontBase* aFont) : mGfxFont(aFont), mFace(aFont->LockFTFace()) {} ~gfxFT2LockedFace() { if (mFace) { @@ -54,7 +54,7 @@ class MOZ_STACK_CLASS gfxFT2LockedFace { FT_ULong variantSelector); CharVariantFunction FindCharVariantFunction(); - const gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller + gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller FT_Face mFace; }; diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp index f6b46becf265..a4fef2159894 100644 --- a/gfx/thebes/gfxFcPlatformFontList.cpp +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -1272,23 +1272,14 @@ gfxFontconfigFont::~gfxFontconfigFont() = default; already_AddRefed gfxFontconfigFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (ScaledFont* scaledFont = mAzureScaledFont) { - return do_AddRef(scaledFont); + if (!mAzureScaledFont) { + mAzureScaledFont = Factory::CreateScaledFontForFontconfigFont( + GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); + InitializeScaledFont(); } - RefPtr newScaledFont = Factory::CreateScaledFontForFontconfigFont( - GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern()); - if (!newScaledFont) { - return nullptr; - } - - InitializeScaledFont(newScaledFont); - - if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - } - ScaledFont* scaledFont = mAzureScaledFont; - return do_AddRef(scaledFont); + RefPtr scaledFont(mAzureScaledFont); + return scaledFont.forget(); } bool gfxFontconfigFont::ShouldHintMetrics() const { diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index dd36c47efb28..da4945244dfa 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -13,7 +13,6 @@ #include "mozilla/intl/Segmenter.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/StaticPrefs_gfx.h" -#include "mozilla/ScopeExit.h" #include "mozilla/SVGContextPaint.h" #include "mozilla/Logging.h" @@ -845,7 +844,6 @@ gfxFont::gfxFont(const RefPtr& aUnscaledFont, gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, AntialiasOption anAAOption) : mFontEntry(aFontEntry), - mLock("gfxFont lock"), mUnscaledFont(aUnscaledFont), mStyle(*aFontStyle), mAdjustedSize(-1.0), // negative to indicate "not yet initialized" @@ -875,21 +873,6 @@ gfxFont::gfxFont(const RefPtr& aUnscaledFont, gfxFont::~gfxFont() { mFontEntry->NotifyFontDestroyed(this); - // Delete objects owned through atomic pointers. (Some of these may be null, - // but that's OK.) - auto* verticalMetrics = mVerticalMetrics.exchange(nullptr); - delete verticalMetrics; - auto* hbShaper = mHarfBuzzShaper.exchange(nullptr); - delete hbShaper; - auto* grShaper = mGraphiteShaper.exchange(nullptr); - delete grShaper; - auto* mathTable = mMathTable.exchange(nullptr); - delete mathTable; - - if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) { - scaledFont->Release(); - } - if (mGlyphChangeObservers) { for (const auto& key : *mGlyphChangeObservers) { key->ForgetFont(); @@ -944,37 +927,26 @@ gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels( } } -gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() { - if (!mHarfBuzzShaper) { - auto* shaper = new gfxHarfBuzzShaper(this); - shaper->Initialize(); - if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) { - delete shaper; - } - } - gfxHarfBuzzShaper* shaper = mHarfBuzzShaper; - return shaper->IsInitialized() ? shaper : nullptr; -} - gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) { if (!aVertical && ProvidesGlyphWidths()) { return GetGlyphWidth(aGID) / 65536.0; } if (mFUnitsConvFactor < 0.0f) { - // Metrics haven't been initialized; lock while we do that. - AutoWriteLock lock(mLock); - if (mFUnitsConvFactor < 0.0f) { - GetMetrics(nsFontMetrics::eHorizontal); - } + GetMetrics(nsFontMetrics::eHorizontal); } NS_ASSERTION(mFUnitsConvFactor >= 0.0f, "missing font unit conversion factor"); - if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { - return (aVertical ? shaper->GetGlyphVAdvance(aGID) - : shaper->GetGlyphHAdvance(aGID)) / - 65536.0; + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); } - return 0.0; + gfxHarfBuzzShaper* shaper = + static_cast(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return 0; + } + return (aVertical ? shaper->GetGlyphVAdvance(aGID) + : shaper->GetGlyphHAdvance(aGID)) / + 65536.0; } gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { @@ -982,9 +954,15 @@ gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { if (ProvidesGetGlyph()) { gid = GetGlyph(aUnicode, 0); } else { - if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { - gid = shaper->GetNominalGlyph(aUnicode); + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); } + gfxHarfBuzzShaper* shaper = + static_cast(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return -1.0; + } + gid = shaper->GetNominalGlyph(aUnicode); } if (!gid) { return -1.0; @@ -1199,9 +1177,8 @@ static const hb_tag_t defaultFeatures[] = { HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'), HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')}; -void gfxFont::CheckForFeaturesInvolvingSpace() const { - auto setInitializedFlag = - MakeScopeExit([&]() { mFontEntry->mHasSpaceFeaturesInitialized = true; }); +void gfxFont::CheckForFeaturesInvolvingSpace() { + mFontEntry->mHasSpaceFeaturesInitialized = true; bool log = LOG_FONTINIT_ENABLED(); TimeStamp start; @@ -1328,7 +1305,7 @@ void gfxFont::CheckForFeaturesInvolvingSpace() const { } } -bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const { +bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) { NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized, "need to initialize space lookup flags"); NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code"); @@ -1355,8 +1332,7 @@ bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const { return false; } -tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping( - Script aRunScript) const { +tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(Script aRunScript) { // avoid checking fonts known not to include default space-dependent features if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { if (!mKerningSet && mStyle.featureSettings.IsEmpty() && @@ -1492,14 +1468,16 @@ bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, } // xxx - for graphite, don't really know how to sniff lookups so bail - { - if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - return true; - } + if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { + return true; } - gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); - if (!shaper) { + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); + } + gfxHarfBuzzShaper* shaper = + static_cast(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { return false; } @@ -1543,16 +1521,21 @@ bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, return true; } - if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { - // get the hbset containing input glyphs for the feature - const hb_set_t* inputGlyphs = - mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); - - hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); - return hb_set_has(inputGlyphs, gid); + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); + } + gfxHarfBuzzShaper* shaper = + static_cast(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { + return false; } - return false; + // get the hbset containing input glyphs for the feature + const hb_set_t* inputGlyphs = + mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); + + hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); + return hb_set_has(inputGlyphs, gid); } bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) { @@ -2507,8 +2490,11 @@ bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { return true; } // Use harfbuzz shaper to look up the default glyph ID for the character. - auto* shaper = GetHarfBuzzShaper(); - if (!shaper) { + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); + } + auto* shaper = static_cast(mHarfBuzzShaper.get()); + if (!shaper->Initialize()) { return false; } uint32_t gid = 0; @@ -2573,22 +2559,15 @@ gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, // This is only used by MathML layout at present. if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && mAntialiasOption != kAntialiasNone) { - gfxFont* nonAA = mNonAAFont; - if (!nonAA) { - nonAA = CopyWithAntialiasOption(kAntialiasNone); - if (nonAA) { - if (!mNonAAFont.compareExchange(nullptr, nonAA)) { - delete nonAA; - nonAA = mNonAAFont; - } - } + if (!mNonAAFont) { + mNonAAFont = CopyWithAntialiasOption(kAntialiasNone); } // if font subclass doesn't implement CopyWithAntialiasOption(), // it will return null and we'll proceed to use the existing font - if (nonAA) { - return nonAA->Measure(aTextRun, aStart, aEnd, - TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, - aSpacing, aOrientation); + if (mNonAAFont) { + return mNonAAFont->Measure(aTextRun, aStart, aEnd, + TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, + aSpacing, aOrientation); } } @@ -2782,7 +2761,6 @@ gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, } bool gfxFont::AgeCachedWords() { - mozilla::AutoWriteLock lock(mLock); if (mWordCache) { for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) { CacheHashEntry* entry = it.Get(); @@ -2798,8 +2776,7 @@ bool gfxFont::AgeCachedWords() { return true; } -void gfxFont::NotifyGlyphsChanged() const { - AutoReadLock lock(const_cast(this)->mLock); +void gfxFont::NotifyGlyphsChanged() { uint32_t i, count = mGlyphExtentsArray.Length(); for (i = 0; i < count; ++i) { // Flush cached extents array @@ -2837,84 +2814,51 @@ gfxShapedWord* gfxFont::GetShapedWord( // if the cache is getting too big, flush it and start over uint32_t wordCacheMaxEntries = gfxPlatform::GetPlatform()->WordCacheMaxEntries(); - mLock.ReadLock(); if (mWordCache->Count() > wordCacheMaxEntries) { - // Flush the cache if it is getting overly big. - // There's a chance another thread could race with the lock-twiddling here, - // but that's OK; it's harmless if we end up calling ClearCacheWords twice - // from separate threads. Internally, it will hold the lock exclusively - // while doing its thing. - mLock.ReadUnlock(); NS_WARNING("flushing shaped-word cache"); ClearCachedWords(); - mLock.ReadLock(); - // Once we've reclaimed a read lock, we can proceed knowing the cache - // isn't growing uncontrollably. } // if there's a cached entry for this word, just return it CacheHashKey key(aText, aLength, aHash, aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags, aRounding); - CacheHashEntry* entry = mWordCache->GetEntry(key); - if (entry) { - gfxShapedWord* sw = entry->mShapedWord.get(); + CacheHashEntry* entry = mWordCache->PutEntry(key, fallible); + if (!entry) { + NS_WARNING("failed to create word cache entry - expect missing text"); + return nullptr; + } + gfxShapedWord* sw = entry->mShapedWord.get(); + + if (sw) { sw->ResetAge(); #ifndef RELEASE_OR_BETA if (aTextPerf) { - // XXX we should make sure this is atomic aTextPerf->current.wordCacheHit++; } #endif - mLock.ReadUnlock(); return sw; } - mLock.ReadUnlock(); - gfxShapedWord* sw = - gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, - aAppUnitsPerDevUnit, aFlags, aRounding); +#ifndef RELEASE_OR_BETA + if (aTextPerf) { + aTextPerf->current.wordCacheMiss++; + } +#endif + + sw = gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, + aAppUnitsPerDevUnit, aFlags, aRounding); + entry->mShapedWord.reset(sw); if (!sw) { NS_WARNING("failed to create gfxShapedWord - expect missing text"); return nullptr; } + DebugOnly ok = ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage, aVertical, aRounding, sw); + NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text"); - { - // We're going to cache the new shaped word, so lock for writing. - AutoWriteLock lock(mLock); - entry = mWordCache->PutEntry(key, fallible); - if (!entry) { - NS_WARNING("failed to create word cache entry - expect missing text"); - delete sw; - return nullptr; - } - - // It's unlikely, but maybe another thread got there before us... - if (entry->mShapedWord) { - // Just discard the newly-created word, and use the existing one. - delete sw; - sw = entry->mShapedWord.get(); - sw->ResetAge(); -#ifndef RELEASE_OR_BETA - if (aTextPerf) { - aTextPerf->current.wordCacheHit++; - } -#endif - return sw; - } - - entry->mShapedWord.reset(sw); - -#ifndef RELEASE_OR_BETA - if (aTextPerf) { - aTextPerf->current.wordCacheMiss++; - } -#endif - } - gfxFontCache::GetCache()->RunWordCacheExpirationTimer(); return sw; @@ -2985,18 +2929,13 @@ bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, // Vertical graphite support may be wanted as a future enhancement. if (FontCanSupportGraphite() && !aVertical) { if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - gfxGraphiteShaper* shaper = mGraphiteShaper; - if (!shaper) { - shaper = new gfxGraphiteShaper(this); - if (mGraphiteShaper.compareExchange(nullptr, shaper)) { - Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); - } else { - delete shaper; - shaper = mGraphiteShaper; - } + if (!mGraphiteShaper) { + mGraphiteShaper = MakeUnique(this); + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1); } - if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, - aLanguage, aVertical, aRounding, aShapedText)) { + if (mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, + aScript, aLanguage, aVertical, aRounding, + aShapedText)) { PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, aShapedText); return true; @@ -3004,10 +2943,12 @@ bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, } } - gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); - if (shaper && - shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, - aLanguage, aVertical, aRounding, aShapedText)) { + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(this); + } + if (mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aLanguage, aVertical, aRounding, + aShapedText)) { PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, aShapedText); if (GetFontEntry()->HasTrackingTable()) { @@ -3561,7 +3502,7 @@ bool gfxFont::InitFakeSmallCapsRun( aSyntheticUpper); } -gfxFont* gfxFont::GetSmallCapsFont() const { +gfxFont* gfxFont::GetSmallCapsFont() { gfxFontStyle style(*GetStyle()); style.size *= SMALL_CAPS_SCALE_FACTOR; style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; @@ -3569,7 +3510,7 @@ gfxFont* gfxFont::GetSmallCapsFont() const { return fe->FindOrMakeFont(&style, mUnicodeRangeMap); } -gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) const { +gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) { gfxFontStyle style(*GetStyle()); style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); gfxFontEntry* fe = GetFontEntry(); @@ -3577,16 +3518,6 @@ gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) const { } gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { - { - AutoReadLock lock(mLock); - uint32_t i, count = mGlyphExtentsArray.Length(); - for (i = 0; i < count; ++i) { - if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) - return mGlyphExtentsArray[i].get(); - } - } - AutoWriteLock lock(mLock); - // Re-check in case of race. uint32_t i, count = mGlyphExtentsArray.Length(); for (i = 0; i < count; ++i) { if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) @@ -3918,7 +3849,8 @@ void gfxFont::CreateVerticalMetrics() { const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); uint32_t len; - auto* metrics = new Metrics(); + mVerticalMetrics = MakeUnique(); + auto* metrics = mVerticalMetrics.get(); ::memset(metrics, 0, sizeof(Metrics)); // Some basic defaults, in case the font lacks any real metrics tables. @@ -4065,10 +3997,6 @@ void gfxFont::CreateVerticalMetrics() { metrics->maxHeight = metrics->maxAscent + metrics->maxDescent; metrics->xHeight = metrics->emHeight / 2; metrics->capHeight = metrics->maxAscent; - - if (!mVerticalMetrics.compareExchange(nullptr, metrics)) { - delete metrics; - } } gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { @@ -4109,7 +4037,6 @@ gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const { - AutoReadLock lock(const_cast(this)->mLock); for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { aSizes->mFontInstances += mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); @@ -4126,7 +4053,6 @@ void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, } void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { - AutoWriteLock lock(mLock); if (!mGlyphChangeObservers) { mGlyphChangeObservers = MakeUnique>(); } @@ -4134,7 +4060,6 @@ void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { } void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) { - AutoWriteLock lock(mLock); NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered"); @@ -4274,21 +4199,17 @@ void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) { } bool gfxFont::TryGetMathTable() { - if (mMathInitialized) { - return !!mMathTable; - } + if (!mMathInitialized) { + mMathInitialized = true; - hb_face_t* face = GetFontEntry()->GetHBFace(); - if (face) { - if (hb_ot_math_has_data(face)) { - auto* mathTable = new gfxMathTable(face, GetAdjustedSize()); - if (!mMathTable.compareExchange(nullptr, mathTable)) { - delete mathTable; + hb_face_t* face = GetFontEntry()->GetHBFace(); + if (face) { + if (hb_ot_math_has_data(face)) { + mMathTable = MakeUnique(face, GetAdjustedSize()); } + hb_face_destroy(face); } - hb_face_destroy(face); } - mMathInitialized = true; return !!mMathTable; } diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index 61704cc06312..d12b33ae5ff9 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -60,8 +60,6 @@ #include "nsMathUtils.h" class gfxContext; -class gfxGraphiteShaper; -class gfxHarfBuzzShaper; class gfxGlyphExtents; class gfxMathTable; class gfxPattern; @@ -1419,8 +1417,7 @@ class gfxShapedWord final : public gfxShapedText { gfxFontShaper::RoundingFlags mRounding; - // With multithreaded shaping, this may be updated by any thread. - std::atomic mAgeCounter; + uint32_t mAgeCounter; // The mCharGlyphsStorage array is actually a variable-size member; // when the ShapedWord is created, its size will be increased as necessary @@ -1452,12 +1449,7 @@ class gfxFont { nsrefcnt AddRef(void) { MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); - nsExpirationState state; - { - mozilla::AutoReadLock lock(mLock); - state = mExpirationState; - } - if (state.IsTracked()) { + if (mExpirationState.IsTracked()) { gfxFontCache::GetCache()->RemoveObject(this); } ++mRefCnt; @@ -1468,15 +1460,15 @@ class gfxFont { MOZ_ASSERT(0 != mRefCnt, "dup release"); --mRefCnt; NS_LOG_RELEASE(this, mRefCnt, "gfxFont"); - nsrefcnt rval = mRefCnt; - if (!rval) { + if (mRefCnt == 0) { NotifyReleased(); // |this| may have been deleted. + return 0; } - return rval; + return mRefCnt; } - int32_t GetRefCount() { return int32_t(mRefCnt); } + int32_t GetRefCount() { return mRefCnt; } // options to specify the kind of AA to be used when creating a font typedef enum : uint8_t { @@ -1487,7 +1479,7 @@ class gfxFont { } AntialiasOption; protected: - mozilla::ThreadSafeAutoRefCnt mRefCnt; + nsAutoRefCnt mRefCnt; void NotifyReleased() { gfxFontCache* cache = gfxFontCache::GetCache(); @@ -1540,7 +1532,8 @@ class gfxFont { const nsCString& GetName() const { return mFontEntry->Name(); } const gfxFontStyle* GetStyle() const { return &mStyle; } - virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const { + virtual mozilla::UniquePtr CopyWithAntialiasOption( + AntialiasOption anAAOption) { // platforms where this actually matters should override return nullptr; } @@ -1564,17 +1557,15 @@ class gfxFont { } // check whether this is an sfnt we can potentially use with harfbuzz - bool FontCanSupportHarfBuzz() const { return mFontEntry->HasCmapTable(); } + bool FontCanSupportHarfBuzz() { return mFontEntry->HasCmapTable(); } // check whether this is an sfnt we can potentially use with Graphite - bool FontCanSupportGraphite() const { - return mFontEntry->HasGraphiteTables(); - } + bool FontCanSupportGraphite() { return mFontEntry->HasGraphiteTables(); } // Whether this is a font that may be doing full-color rendering, // and therefore needs us to use a mask for text-shadow even when // we're not actually blurring. - bool AlwaysNeedsMaskForShadow() const { + bool AlwaysNeedsMaskForShadow() { return mFontEntry->TryGetColorGlyphs() || mFontEntry->TryGetSVGData(this) || mFontEntry->HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) || mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x')); @@ -1630,10 +1621,6 @@ class gfxFont { virtual bool ShouldHintMetrics() const { return true; } virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; } - // Return the font's owned harfbuzz shaper, creating and initializing it if - // necessary; returns null if shaper initialization has failed. - gfxHarfBuzzShaper* GetHarfBuzzShaper(); - // Font metrics struct Metrics { gfxFloat capHeight; @@ -1799,14 +1786,14 @@ class gfxFont { nsExpirationState* GetExpirationState() { return &mExpirationState; } // Get the glyphID of a space - uint16_t GetSpaceGlyph() const { return mSpaceGlyph; } + uint16_t GetSpaceGlyph() { return mSpaceGlyph; } gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit); void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, bool aNeedTight, gfxGlyphExtents* aExtents); - virtual bool AllowSubpixelAA() const { return true; } + virtual bool AllowSubpixelAA() { return true; } bool ApplySyntheticBold() const { return mApplySyntheticBold; } @@ -1817,7 +1804,7 @@ class gfxFont { // For size S up to a threshold size T, we use (0.25 + 3S / 4T), // so that the result ranges from 0.25 to 1.0; thereafter, // simply use (S / T). - gfxFloat GetSyntheticBoldOffset() const { + gfxFloat GetSyntheticBoldOffset() { gfxFloat size = GetAdjustedSize(); const gfxFloat threshold = 48.0; return size < threshold ? (0.25 + 0.75 * size / threshold) @@ -1825,7 +1812,7 @@ class gfxFont { } gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); } - bool HasCharacter(uint32_t ch) const { + bool HasCharacter(uint32_t ch) { if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) { return false; } @@ -1840,7 +1827,7 @@ class gfxFont { mUnicodeRangeMap = aUnicodeRangeMap; } - uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) const { + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) { if (!mIsValid) { return 0; } @@ -1880,15 +1867,8 @@ class gfxFont { // Ensure the ShapedWord cache is initialized. This MUST be called before // any attempt to use GetShapedWord(). void InitWordCache() { - mLock.ReadLock(); if (!mWordCache) { - mLock.ReadUnlock(); - mozilla::AutoWriteLock lock(mLock); - if (!mWordCache) { - mWordCache = mozilla::MakeUnique>(); - } - } else { - mLock.ReadUnlock(); + mWordCache = mozilla::MakeUnique>(); } } @@ -1899,14 +1879,13 @@ class gfxFont { // Discard all cached word records; called on memory-pressure notification. void ClearCachedWords() { - mozilla::AutoWriteLock lock(mLock); if (mWordCache) { mWordCache->Clear(); } } // Glyph rendering/geometry has changed, so invalidate data as necessary. - void NotifyGlyphsChanged() const; + void NotifyGlyphsChanged(); virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const; @@ -1936,11 +1915,12 @@ class gfxFont { // gfxFont implementations may cache ScaledFont versions other than the // default, so InitializeScaledFont must support explicitly specifying - // which ScaledFonts to initialize. + // other ScaledFonts than the default to initialize. void InitializeScaledFont( const RefPtr& aScaledFont); + void InitializeScaledFont() { InitializeScaledFont(mAzureScaledFont); } - bool KerningDisabled() const { return mKerningSet && !mKerningEnabled; } + bool KerningDisabled() { return mKerningSet && !mKerningEnabled; } /** * Subclass this object to be notified of glyph changes. Delete the object @@ -1967,7 +1947,7 @@ class gfxFont { }; friend class GlyphChangeObserver; - bool GlyphsMayChange() const { + bool GlyphsMayChange() { // Currently only fonts with SVG glyphs can have animated glyphs return mFontEntry->TryGetSVGData(this); } @@ -1981,21 +1961,21 @@ class gfxFont { // If (and ONLY if) TryGetMathTable() has returned true, the MathTable() // method may be called to access the gfxMathTable data. bool TryGetMathTable(); - gfxMathTable* MathTable() const { + gfxMathTable* MathTable() { MOZ_RELEASE_ASSERT(mMathTable, "A successful call to TryGetMathTable() must be " "performed before calling this function"); - return mMathTable; + return mMathTable.get(); } // Return a cloned font resized and offset to simulate sub/superscript // glyphs. This does not add a reference to the returned font. - gfxFont* GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) const; + gfxFont* GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel); bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh); protected: - virtual const Metrics& GetHorizontalMetrics() const = 0; + virtual const Metrics& GetHorizontalMetrics() = 0; void CreateVerticalMetrics(); @@ -2041,7 +2021,7 @@ class gfxFont { // Return a font that is a "clone" of this one, but reduced to 80% size // (and with variantCaps set to normal). This does not add a reference to // the returned font. - gfxFont* GetSmallCapsFont() const; + gfxFont* GetSmallCapsFont(); // subclasses may provide (possibly hinted) glyph widths (in font units); // if they do not override this, harfbuzz will use unhinted widths @@ -2053,7 +2033,7 @@ class gfxFont { virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; } virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight = false) const { + bool aTight = false) { return false; } @@ -2064,13 +2044,13 @@ class gfxFont { void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver); // whether font contains substitution lookups containing spaces - bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const; + bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript); // do spaces participate in shaping rules? if so, can't used word cache // Note that this function uses HasGraphiteSpaceContextuals, so it can only // return a "hint" to the correct answer. The calling code must ensure it // performs safe actions independent of the value returned. - tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript) const; + tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript); // For 8-bit text, expand to 16-bit and then call the following method. bool ShapeText(DrawTarget* aContext, const uint8_t* aText, @@ -2120,7 +2100,7 @@ class gfxFont { bool aVertical, RoundingFlags aRounding, gfxTextRun* aTextRun); - void CheckForFeaturesInvolvingSpace() const; + void CheckForFeaturesInvolvingSpace(); // whether a given feature is included in feature settings from both the // font and the style. aFeatureOn set if resolved feature value is non-zero @@ -2131,7 +2111,6 @@ class gfxFont { static nsTHashSet* sDefaultFeatures; RefPtr mFontEntry; - mozilla::RWLock mLock; struct CacheHashKey { union { @@ -2218,40 +2197,35 @@ class gfxFont { mozilla::UniquePtr mShapedWord; }; - mozilla::UniquePtr> mWordCache GUARDED_BY(mLock); + mozilla::UniquePtr> mWordCache; static const uint32_t kShapedWordCacheMaxAge = 3; - nsTArray> mGlyphExtentsArray - GUARDED_BY(mLock); - mozilla::UniquePtr> mGlyphChangeObservers - GUARDED_BY(mLock); + nsTArray> mGlyphExtentsArray; + mozilla::UniquePtr> mGlyphChangeObservers; // a copy of the font without antialiasing, if needed for separate // measurement by mathml code - mozilla::Atomic mNonAAFont; + mozilla::UniquePtr mNonAAFont; // we create either or both of these shapers when needed, depending // whether the font has graphite tables, and whether graphite shaping // is actually enabled - mozilla::Atomic mHarfBuzzShaper; - mozilla::Atomic mGraphiteShaper; + mozilla::UniquePtr mHarfBuzzShaper; + mozilla::UniquePtr mGraphiteShaper; - // If a userfont with unicode-range specified, contains map of *possible* - // ranges supported by font. This is set during user-font initialization, - // before the font is available to other threads, and thereafter is inert - // so no guard is needed. + // if a userfont with unicode-range specified, contains map of *possible* + // ranges supported by font RefPtr mUnicodeRangeMap; - RefPtr mUnscaledFont GUARDED_BY(mLock); - - mozilla::Atomic mAzureScaledFont; + RefPtr mUnscaledFont; + RefPtr mAzureScaledFont; // For vertical metrics, created on demand. - mozilla::Atomic mVerticalMetrics; + mozilla::UniquePtr mVerticalMetrics; // Table used for MathML layout. - mozilla::Atomic mMathTable; + mozilla::UniquePtr mMathTable; gfxFontStyle mStyle; mutable gfxFloat mAdjustedSize; @@ -2261,7 +2235,7 @@ class gfxFont { // This is OK because we only multiply by this factor, never divide. float mFUnitsConvFactor; - nsExpirationState mExpirationState GUARDED_BY(mLock); + nsExpirationState mExpirationState; // Glyph ID of the font's glyph, zero if missing uint16_t mSpaceGlyph = 0; @@ -2278,7 +2252,7 @@ class gfxFont { bool mKerningSet; // kerning explicitly set? bool mKerningEnabled; // if set, on or off? - mozilla::Atomic mMathInitialized; // TryGetMathTable() called? + bool mMathInitialized; // TryGetMathTable() called? // Helper for subclasses that want to initialize standard metrics from the // tables of sfnt (TrueType/OpenType) fonts. diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp index 50c1620ac229..b2abace1e568 100644 --- a/gfx/thebes/gfxFontEntry.cpp +++ b/gfx/thebes/gfxFontEntry.cpp @@ -339,7 +339,7 @@ void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId, mSVGGlyphs->RenderGlyph(aContext, aGlyphId, aContextPaint); } -bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) { +bool gfxFontEntry::TryGetSVGData(gfxFont* aFont) { if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { return false; } @@ -377,7 +377,7 @@ void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) { void gfxFontEntry::NotifyGlyphsChanged() { for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { - const gfxFont* font = mFontsUsingSVGGlyphs[i]; + gfxFont* font = mFontsUsingSVGGlyphs[i]; font->NotifyGlyphsChanged(); } } diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h index ae6393ef3481..8d29fc38cfc3 100644 --- a/gfx/thebes/gfxFontEntry.h +++ b/gfx/thebes/gfxFontEntry.h @@ -251,7 +251,7 @@ class gfxFontEntry { // can be safely dereferenced. virtual nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr); - bool TryGetSVGData(const gfxFont* aFont); + bool TryGetSVGData(gfxFont* aFont); bool HasSVGGlyph(uint32_t aGlyphId); bool GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId, gfxFloat aSize, gfxRect* aResult); @@ -472,7 +472,7 @@ class gfxFontEntry { mozilla::UniquePtr mUserFontData; mozilla::UniquePtr mSVGGlyphs; // list of gfxFonts that are using SVG glyphs - nsTArray mFontsUsingSVGGlyphs; + nsTArray mFontsUsingSVGGlyphs; nsTArray mFeatureSettings; nsTArray mVariationSettings; mozilla::UniquePtr> mSupportedFeatures; diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp index dad92e12a151..aa7ec382b0f3 100644 --- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -51,9 +51,10 @@ gfxGDIFont::~gfxGDIFont() { delete mMetrics; } -gfxFont* gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption) const { +UniquePtr gfxGDIFont::CopyWithAntialiasOption( + AntialiasOption anAAOption) { auto entry = static_cast(mFontEntry.get()); - return new gfxGDIFont(entry, &mStyle, anAAOption); + return MakeUnique(entry, &mStyle, anAAOption); } bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, @@ -70,28 +71,21 @@ bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, aLanguage, aVertical, aRounding, aShapedText); } +const gfxFont::Metrics& gfxGDIFont::GetHorizontalMetrics() { return *mMetrics; } + already_AddRefed gfxGDIFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (ScaledFont* scaledFont = mAzureScaledFont) { - return do_AddRef(scaledFont); + if (!mAzureScaledFont) { + LOGFONT lf; + GetObject(GetHFONT(), sizeof(LOGFONT), &lf); + + mAzureScaledFont = Factory::CreateScaledFontForGDIFont( + &lf, GetUnscaledFont(), GetAdjustedSize()); + InitializeScaledFont(); } - LOGFONT lf; - GetObject(GetHFONT(), sizeof(LOGFONT), &lf); - - RefPtr newScaledFont = Factory::CreateScaledFontForGDIFont( - &lf, GetUnscaledFont(), GetAdjustedSize()); - if (!newScaledFont) { - return nullptr; - } - - InitializeScaledFont(newScaledFont); - - if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - } - ScaledFont* scaledFont = mAzureScaledFont; - return do_AddRef(scaledFont); + RefPtr scaledFont(mAzureScaledFont); + return scaledFont.forget(); } gfxFont::RunMetrics gfxGDIFont::Measure(const gfxTextRun* aTextRun, @@ -491,8 +485,7 @@ int32_t gfxGDIFont::GetGlyphWidth(uint16_t aGID) { }); } -bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const { +bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { DCForMetrics dc; AutoSelectFont fs(dc, GetHFONT()); diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h index a731e06efa0b..874c206b4a73 100644 --- a/gfx/thebes/gfxGDIFont.h +++ b/gfx/thebes/gfxGDIFont.h @@ -22,7 +22,7 @@ class gfxGDIFont : public gfxFont { virtual ~gfxGDIFont(); - HFONT GetHFONT() const { return mFont; } + HFONT GetHFONT() { return mFont; } already_AddRefed GetScaledFont( const TextRunDrawParams& aRunParams) override; @@ -35,7 +35,8 @@ class gfxGDIFont : public gfxFont { mozilla::gfx::ShapedTextFlags aOrientation) override; /* required for MathML to suppress effects of ClearType "padding" */ - gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override; + mozilla::UniquePtr CopyWithAntialiasOption( + AntialiasOption anAAOption) override; // If the font has a cmap table, we handle it purely with harfbuzz; // but if not (e.g. .fon fonts), we'll use a GDI callback to get glyphs. @@ -48,8 +49,7 @@ class gfxGDIFont : public gfxFont { // get hinted glyph width in pixels as 16.16 fixed-point value int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, FontCacheSizes* aSizes) const; @@ -59,7 +59,7 @@ class gfxGDIFont : public gfxFont { FontType GetType() const override { return FONT_TYPE_GDI; } protected: - const Metrics& GetHorizontalMetrics() const override { return *mMetrics; } + const Metrics& GetHorizontalMetrics() override; bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, Script aScript, diff --git a/gfx/thebes/gfxHarfBuzzShaper.h b/gfx/thebes/gfxHarfBuzzShaper.h index c4647820ff62..aa1ffa076182 100644 --- a/gfx/thebes/gfxHarfBuzzShaper.h +++ b/gfx/thebes/gfxHarfBuzzShaper.h @@ -25,12 +25,8 @@ class gfxHarfBuzzShaper : public gfxFontShaper { gfxHarfBuzzShaper* mShaper; }; - // Initializes the shaper and returns whether this was successful. bool Initialize(); - // Returns whether the shaper has been successfully initialized. - bool IsInitialized() const { return mHBFont != nullptr; } - bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, Script aScript, nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding, diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp index 122014ffa547..3cd6d316d4d3 100644 --- a/gfx/thebes/gfxMacFont.cpp +++ b/gfx/thebes/gfxMacFont.cpp @@ -518,8 +518,7 @@ int32_t gfxMacFont::GetGlyphWidth(uint16_t aGID) { return advance.width * 0x10000; } -bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, - bool aTight) const { +bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) { CGRect bb; if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) { return false; @@ -582,27 +581,21 @@ void gfxMacFont::InitMetricsFromPlatform() { already_AddRefed gfxMacFont::GetScaledFont( const TextRunDrawParams& aRunParams) { - if (ScaledFont* scaledFont = mAzureScaledFont) { - return do_AddRef(scaledFont); + if (!mAzureScaledFont) { + gfxFontEntry* fe = GetFontEntry(); + bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs(); + mAzureScaledFont = Factory::CreateScaledFontForMacFont( + GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), + ToDeviceColor(mFontSmoothingBackgroundColor), + !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs); + if (!mAzureScaledFont) { + return nullptr; + } + InitializeScaledFont(); } - gfxFontEntry* fe = GetFontEntry(); - bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs(); - RefPtr newScaledFont = Factory::CreateScaledFontForMacFont( - GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(), - ToDeviceColor(mFontSmoothingBackgroundColor), - !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs); - if (!newScaledFont) { - return nullptr; - } - - InitializeScaledFont(newScaledFont); - - if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) { - Unused << newScaledFont.forget(); - } - ScaledFont* scaledFont = mAzureScaledFont; - return do_AddRef(scaledFont); + RefPtr scaledFont(mAzureScaledFont); + return scaledFont.forget(); } bool gfxMacFont::ShouldRoundXOffset(cairo_t* aCairo) const { diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h index 7c5dd2a436a8..2bd10c74a501 100644 --- a/gfx/thebes/gfxMacFont.h +++ b/gfx/thebes/gfxMacFont.h @@ -37,7 +37,7 @@ class gfxMacFont : public gfxFont { int32_t GetGlyphWidth(uint16_t aGID) override; - bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) const override; + bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override; already_AddRefed GetScaledFont( const TextRunDrawParams& aRunParams) override; @@ -59,7 +59,7 @@ class gfxMacFont : public gfxFont { CTFontDescriptorRef aFontDesc = nullptr); protected: - const Metrics& GetHorizontalMetrics() const override { return mMetrics; } + const Metrics& GetHorizontalMetrics() override { return mMetrics; } // override to prefer CoreText shaping with fonts that depend on AAT bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength, From cfc887ddbab46f5ff6cee7b03df4be626226e505 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Thu, 24 Mar 2022 01:31:10 +0000 Subject: [PATCH 30/43] Bug 1760992 - [devtools] Prevent picking UA shadowRoot elements by default r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D141844 --- devtools/client/inspector/test/browser.ini | 1 + ...owser_inspector_picker-useragent-widget.js | 73 +++++++++++++++++++ .../server/actors/inspector/node-picker.js | 8 ++ 3 files changed, 82 insertions(+) create mode 100644 devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js diff --git a/devtools/client/inspector/test/browser.ini b/devtools/client/inspector/test/browser.ini index 1120be3f9eda..4d2167da6554 100644 --- a/devtools/client/inspector/test/browser.ini +++ b/devtools/client/inspector/test/browser.ini @@ -211,6 +211,7 @@ skip-if = (os == 'win' && processor == 'aarch64') # bug 1533492 [browser_inspector_picker-shift-key.js] [browser_inspector_picker-stop-on-eyedropper.js] [browser_inspector_picker-stop-on-tool-change.js] +[browser_inspector_picker-useragent-widget.js] [browser_inspector_portrait_mode.js] [browser_inspector_pseudoclass-lock.js] [browser_inspector_pseudoclass-menu.js] diff --git a/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js b/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js new file mode 100644 index 000000000000..1bdd63ebb60f --- /dev/null +++ b/devtools/client/inspector/test/browser_inspector_picker-useragent-widget.js @@ -0,0 +1,73 @@ +/* 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/. */ + +"use strict"; + +const TEST_URI = `data:text/html;charset=utf-8, + + `; + +// Test that using the node picker on user agent widgets only selects shadow dom +// elements if `devtools.inspector.showAllAnonymousContent` is true. +// If not, we should only surface the host, in this case, the