diff options
Diffstat (limited to 'dom')
1267 files changed, 18562 insertions, 12694 deletions
diff --git a/dom/animation/DocumentTimeline.cpp b/dom/animation/DocumentTimeline.cpp index 85e08ca8b0..804ae66160 100644 --- a/dom/animation/DocumentTimeline.cpp +++ b/dom/animation/DocumentTimeline.cpp @@ -55,9 +55,9 @@ DocumentTimeline::DocumentTimeline(Document* aDocument, } DocumentTimeline::~DocumentTimeline() { - MOZ_ASSERT(!mIsObservingRefreshDriver, - "Timeline should have disassociated" - " from the refresh driver before being destroyed"); + MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver, + "Timeline should have disassociated" + " from the refresh driver before being destroyed"); if (isInList()) { remove(); } @@ -206,7 +206,8 @@ void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime) { } void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver) { - MOZ_ASSERT(!mIsObservingRefreshDriver); + MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver, + "shouldn't register as an observer more than once"); // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver // since it might end up calling NotifyTimerAdjusted which calls // MostRecentRefreshTimeUpdated which has an assertion for @@ -218,9 +219,10 @@ void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver) { } void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver) { - MOZ_ASSERT(!mIsObservingRefreshDriver, - "Timeline should not be observing the refresh driver before" - " it is created"); + MOZ_RELEASE_ASSERT( + !mIsObservingRefreshDriver, + "Timeline should not be observing the refresh driver before" + " it is created"); if (!mAnimationOrder.isEmpty()) { MOZ_ASSERT(isInList(), diff --git a/dom/animation/EffectCompositor.h b/dom/animation/EffectCompositor.h index 34c2688486..78ec63f595 100644 --- a/dom/animation/EffectCompositor.h +++ b/dom/animation/EffectCompositor.h @@ -244,8 +244,8 @@ class EffectCompositor { // animations that can be throttled, we will add an entry to the hashtable to // indicate that the style rule on the element is out of date but without // posting a restyle to update it. - EnumeratedArray<CascadeLevel, CascadeLevel(kCascadeLevelCount), - nsTHashMap<PseudoElementHashEntry, bool>> + EnumeratedArray<CascadeLevel, nsTHashMap<PseudoElementHashEntry, bool>, + kCascadeLevelCount> mElementsToRestyle; bool mIsInPreTraverse = false; diff --git a/dom/base/BodyUtil.cpp b/dom/base/BodyUtil.cpp index e5d86a3c36..e8de3d18ec 100644 --- a/dom/base/BodyUtil.cpp +++ b/dom/base/BodyUtil.cpp @@ -267,7 +267,7 @@ class MOZ_STACK_CLASS FormDataParser { } // Determine boundary from mimetype. - UniquePtr<CMimeType> parsed = CMimeType::Parse(mMixedCaseMimeType); + RefPtr<CMimeType> parsed = CMimeType::Parse(mMixedCaseMimeType); if (!parsed) { return false; } @@ -422,7 +422,7 @@ already_AddRefed<FormData> BodyUtil::ConsumeFormData( if (isValidUrlEncodedMimeType) { RefPtr<FormData> fd = new FormData(aParent); DebugOnly<bool> status = URLParams::Parse( - aStr, [&fd](const nsAString& aName, const nsAString& aValue) { + aStr, true, [&fd](const nsAString& aName, const nsAString& aValue) { ErrorResult rv; fd->Append(aName, aValue, rv); MOZ_ASSERT(!rv.Failed()); diff --git a/dom/base/CharacterData.cpp b/dom/base/CharacterData.cpp index b4809a0293..eccf9fe4d9 100644 --- a/dom/base/CharacterData.cpp +++ b/dom/base/CharacterData.cpp @@ -11,32 +11,22 @@ #include "mozilla/dom/CharacterData.h" -#include "mozilla/DebugOnly.h" - #include "mozilla/AsyncEventDispatcher.h" -#include "mozilla/MemoryReporting.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/Element.h" -#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/MutationObservers.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/UnbindContext.h" #include "nsReadableUtils.h" #include "mozilla/InternalMutationEvent.h" -#include "nsCOMPtr.h" -#include "nsDOMString.h" -#include "nsChangeHint.h" -#include "nsCOMArray.h" #include "mozilla/dom/DirectionalityUtils.h" -#include "nsCCUncollectableMarker.h" #include "mozAutoDocUpdate.h" #include "nsIContentInlines.h" #include "nsTextNode.h" #include "nsBidiUtils.h" -#include "PLDHashTable.h" #include "mozilla/Sprintf.h" #include "nsWindowSizes.h" -#include "nsWrapperCacheInlines.h" #if defined(ACCESSIBILITY) && defined(DEBUG) # include "nsAccessibilityService.h" @@ -478,13 +468,14 @@ nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void CharacterData::UnbindFromTree(bool aNullParent) { +void CharacterData::UnbindFromTree(UnbindContext& aContext) { // Unset frame flags; if we need them again later, they'll get set again. UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE); - HandleShadowDOMRelatedRemovalSteps(aNullParent); + const bool nullParent = aContext.IsUnbindRoot(this); + HandleShadowDOMRelatedRemovalSteps(nullParent); - if (aNullParent) { + if (nullParent) { if (GetParent()) { NS_RELEASE(mParent); } else { @@ -495,15 +486,13 @@ void CharacterData::UnbindFromTree(bool aNullParent) { ClearInDocument(); SetIsConnected(false); - if (aNullParent || !mParent->IsInShadowTree()) { + if (nullParent || !mParent->IsInShadowTree()) { UnsetFlags(NODE_IS_IN_SHADOW_TREE); // Begin keeping track of our subtree root. - SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot()); - } + SetSubtreeRootPointer(nullParent ? this : mParent->SubtreeRoot()); - if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) { - if (aNullParent || !mParent->IsInShadowTree()) { + if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) { slots->mContainingShadow = nullptr; } } diff --git a/dom/base/CharacterData.h b/dom/base/CharacterData.h index 8e008b134b..50ff159eef 100644 --- a/dom/base/CharacterData.h +++ b/dom/base/CharacterData.h @@ -106,7 +106,7 @@ class CharacterData : public nsIContent { // Implementation for nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; const nsTextFragment* GetText() override { return &mText; } uint32_t TextLength() const final { return TextDataLength(); } diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index be06bb083a..0df1cd3c9b 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -602,23 +602,6 @@ void ChromeUtils::Import(const GlobalObject& aGlobal, aRetval.set(exports); } -static mozJSModuleLoader* GetContextualESLoader( - const Optional<bool>& aLoadInDevToolsLoader, JSObject* aGlobal) { - RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader(); - // We should load the module in the DevTools loader if: - // - ChromeUtils.importESModule's `loadInDevToolsLoader` option is true, or, - // - if the callsite is from a module loaded in the DevTools loader and - // `loadInDevToolsLoader` isn't an explicit false. - bool shouldUseDevToolsLoader = - (aLoadInDevToolsLoader.WasPassed() && aLoadInDevToolsLoader.Value()) || - (devToolsModuleloader && !aLoadInDevToolsLoader.WasPassed() && - devToolsModuleloader->IsLoaderGlobal(aGlobal)); - if (shouldUseDevToolsLoader) { - return mozJSModuleLoader::GetOrCreateDevToolsLoader(); - } - return mozJSModuleLoader::Get(); -} - static mozJSModuleLoader* GetModuleLoaderForCurrentGlobal( JSContext* aCx, const GlobalObject& aGlobal, Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>& @@ -629,7 +612,7 @@ static mozJSModuleLoader* GetModuleLoaderForCurrentGlobal( return mozJSModuleLoader::Get(); } if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global)) { - return mozJSModuleLoader::GetOrCreateDevToolsLoader(); + return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx); } if (loader::NonSharedGlobalSyncModuleLoaderScope::IsActive()) { @@ -681,7 +664,7 @@ static mozJSModuleLoader* GetModuleLoaderForOptions( Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>& aMaybeSyncLoaderScope) { if (!aOptions.mGlobal.WasPassed()) { - return GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get()); + return mozJSModuleLoader::Get(); } switch (aOptions.mGlobal.Value()) { @@ -689,7 +672,7 @@ static mozJSModuleLoader* GetModuleLoaderForOptions( return mozJSModuleLoader::Get(); case ImportESModuleTargetGlobal::Devtools: - return mozJSModuleLoader::GetOrCreateDevToolsLoader(); + return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx); case ImportESModuleTargetGlobal::Contextual: { if (!NS_IsMainThread()) { @@ -700,7 +683,7 @@ static mozJSModuleLoader* GetModuleLoaderForOptions( RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader(); if (devToolsModuleloader && devToolsModuleloader->IsLoaderGlobal(aGlobal.Get())) { - return mozJSModuleLoader::GetOrCreateDevToolsLoader(); + return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx); } return mozJSModuleLoader::Get(); } @@ -715,7 +698,8 @@ static mozJSModuleLoader* GetModuleLoaderForOptions( } static bool ValidateImportOptions( - JSContext* aCx, const ImportESModuleOptionsDictionary& aOptions) { + JSContext* aCx, const GlobalObject& aGlobal, + const ImportESModuleOptionsDictionary& aOptions) { if (!NS_IsMainThread() && (!aOptions.mGlobal.WasPassed() || (aOptions.mGlobal.Value() != ImportESModuleTargetGlobal::Current && @@ -727,12 +711,17 @@ static bool ValidateImportOptions( return false; } - if (aOptions.mGlobal.WasPassed() && - aOptions.mLoadInDevToolsLoader.WasPassed()) { - JS_ReportErrorASCII(aCx, - "global option and loadInDevToolsLoader option " - "cannot be used at the same time"); - return false; + if (NS_IsMainThread()) { + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(aGlobal.GetAsSupports()); + + if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global) && + !aOptions.mGlobal.WasPassed()) { + JS_ReportErrorASCII(aCx, + "ChromeUtils.importESModule: global option is " + "required in DevTools distinct global"); + return false; + } } return true; @@ -745,7 +734,7 @@ void ChromeUtils::ImportESModule( JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) { JSContext* cx = aGlobal.Context(); - if (!ValidateImportOptions(cx, aOptions)) { + if (!ValidateImportOptions(cx, aGlobal, aOptions)) { aRv.Throw(NS_ERROR_FAILURE); return; } @@ -789,21 +778,11 @@ void ChromeUtils::ImportESModule( class EncodedOptions { public: explicit EncodedOptions(const ImportESModuleOptionsDictionary& aOptions) { - uint32_t globalFlag = 0; if (aOptions.mGlobal.WasPassed()) { - globalFlag = uint32_t(aOptions.mGlobal.Value()) + 1; - } - - uint32_t devtoolsFlag = 0; - if (aOptions.mLoadInDevToolsLoader.WasPassed()) { - if (aOptions.mLoadInDevToolsLoader.Value()) { - devtoolsFlag = DevToolsFlag_True; - } else { - devtoolsFlag = DevToolsFlag_False; - } + mValue = uint32_t(aOptions.mGlobal.Value()) + 1; + } else { + mValue = 0; } - - mValue = globalFlag | devtoolsFlag; } explicit EncodedOptions(uint32_t aValue) : mValue(aValue) {} @@ -811,37 +790,14 @@ class EncodedOptions { int32_t toInt32() const { return int32_t(mValue); } void DecodeInto(ImportESModuleOptionsDictionary& aOptions) { - uint32_t globalFlag = mValue & GlobalFlag_Mask; - if (globalFlag == 0) { + if (mValue == 0) { aOptions.mGlobal.Reset(); } else { - aOptions.mGlobal.Construct(ImportESModuleTargetGlobal(globalFlag - 1)); - } - - uint32_t devtoolsFlag = mValue & DevToolsFlag_Mask; - switch (devtoolsFlag) { - case DevToolsFlag_NotPassed: - aOptions.mLoadInDevToolsLoader.Reset(); - break; - case DevToolsFlag_False: - aOptions.mLoadInDevToolsLoader.Construct(false); - break; - case DevToolsFlag_True: - aOptions.mLoadInDevToolsLoader.Construct(true); - break; - default: - MOZ_CRASH("Unknown DevToolsFlag"); + aOptions.mGlobal.Construct(ImportESModuleTargetGlobal(mValue - 1)); } } private: - static constexpr uint32_t GlobalFlag_Mask = 0xF; - - static constexpr uint32_t DevToolsFlag_NotPassed = 0x00; - static constexpr uint32_t DevToolsFlag_False = 0x10; - static constexpr uint32_t DevToolsFlag_True = 0x20; - static constexpr uint32_t DevToolsFlag_Mask = 0x0F0; - uint32_t mValue = 0; }; @@ -1017,10 +973,6 @@ static bool ModuleGetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp, ImportESModuleOptionsDictionary options; encodedOptions.DecodeInto(options); - if (!ValidateImportOptions(aCx, options)) { - return false; - } - GlobalObject global(aCx, callee); Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope; @@ -1198,6 +1150,11 @@ void ChromeUtils::DefineESModuleGetters( return; } + if (!ValidateImportOptions(cx, global, aOptions)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + EncodedOptions encodedOptions(aOptions); JS::Rooted<JS::PropertyKey> prop(cx); @@ -1411,7 +1368,8 @@ void ChromeUtils::ClearStyleSheetCache(GlobalObject&) { static WebIDLProcType ProcTypeToWebIDL(mozilla::ProcType aType) { // Max is the value of the last enum, not the length, so add one. static_assert( - WebIDLProcTypeValues::Count == static_cast<size_t>(ProcType::Max) + 1, + static_cast<size_t>(MaxContiguousEnumValue<WebIDLProcType>::value) == + static_cast<size_t>(ProcType::Max), "In order for this static cast to be okay, " "WebIDLProcType must match ProcType exactly"); @@ -2118,9 +2076,9 @@ unsigned ChromeUtils::AliveUtilityProcesses(const GlobalObject&) { void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal, nsTArray<nsCString>& aNames) { aNames.Clear(); - for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) { - auto idlName = static_cast<UtilityActorName>(i); - aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName)); + for (UtilityActorName idlName : + MakeWebIDLEnumeratedRange<WebIDLUtilityActorName>()) { + aNames.AppendElement(GetEnumString(idlName)); } } diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index 804fc44e4a..4c2d043ecb 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -129,8 +129,7 @@ class ChromeUtils { static bool IsOriginAttributesEqualIgnoringFPD( const dom::OriginAttributesDictionary& aA, const dom::OriginAttributesDictionary& aB) { - return aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser && - aA.mUserContextId == aB.mUserContextId && + return aA.mUserContextId == aB.mUserContextId && aA.mPrivateBrowsingId == aB.mPrivateBrowsingId; } diff --git a/dom/base/ContentProcessMessageManager.cpp b/dom/base/ContentProcessMessageManager.cpp index 7fe9f4c2c7..7661d1036f 100644 --- a/dom/base/ContentProcessMessageManager.cpp +++ b/dom/base/ContentProcessMessageManager.cpp @@ -6,7 +6,6 @@ #include "ContentProcessMessageManager.h" -#include "nsContentCID.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/MessageManagerBinding.h" #include "mozilla/dom/ParentProcessMessageManager.h" @@ -31,7 +30,7 @@ ContentProcessMessageManager::~ContentProcessMessageManager() { ContentProcessMessageManager* ContentProcessMessageManager::Get() { nsCOMPtr<nsIMessageSender> service = - do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID); + do_GetService("@mozilla.org/childprocessmessagemanager;1"); if (!service) { return nullptr; } diff --git a/dom/base/DOMParser.cpp b/dom/base/DOMParser.cpp index 87bac20093..b3cf6ba04b 100644 --- a/dom/base/DOMParser.cpp +++ b/dom/base/DOMParser.cpp @@ -181,12 +181,10 @@ already_AddRefed<Document> DOMParser::ParseFromStream(nsIInputStream* aStream, // Create a fake channel nsCOMPtr<nsIChannel> parserChannel; - NS_NewInputStreamChannel( - getter_AddRefs(parserChannel), mDocumentURI, - nullptr, // aStream - mPrincipal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, - nsIContentPolicy::TYPE_OTHER, - nsDependentCSubstring(SupportedTypeValues::GetString(aType))); + NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, + nullptr, // aStream + mPrincipal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OTHER, GetEnumString(aType)); if (NS_WARN_IF(!parserChannel)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp deleted file mode 100644 index 93c1d75d89..0000000000 --- a/dom/base/DOMRequest.cpp +++ /dev/null @@ -1,256 +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/. */ - -#include "DOMRequest.h" - -#include "DOMException.h" -#include "nsThreadUtils.h" -#include "mozilla/HoldDropJSObjects.h" -#include "mozilla/ErrorResult.h" -#include "mozilla/dom/Event.h" -#include "mozilla/dom/Promise.h" -#include "mozilla/dom/ScriptSettings.h" -#include "jsfriendapi.h" -#include "nsContentUtils.h" - -using mozilla::dom::AnyCallback; -using mozilla::dom::AutoJSAPI; -using mozilla::dom::DOMException; -using mozilla::dom::DOMRequest; -using mozilla::dom::DOMRequestService; -using mozilla::dom::Promise; -using mozilla::dom::RootingCx; - -DOMRequest::DOMRequest(nsPIDOMWindowInner* aWindow) - : DOMEventTargetHelper(aWindow), - mResult(JS::UndefinedValue()), - mDone(false) {} - -DOMRequest::DOMRequest(nsIGlobalObject* aGlobal) - : DOMEventTargetHelper(aGlobal), - mResult(JS::UndefinedValue()), - mDone(false) {} - -DOMRequest::~DOMRequest() { mozilla::DropJSObjects(this); } - -NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(DOMRequest, - DOMEventTargetHelper, - (mError, mPromise), - (mResult)) - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMRequest) -NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) - -NS_IMPL_ADDREF_INHERITED(DOMRequest, DOMEventTargetHelper) -NS_IMPL_RELEASE_INHERITED(DOMRequest, DOMEventTargetHelper) - -/* virtual */ -JSObject* DOMRequest::WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) { - return DOMRequest_Binding::Wrap(aCx, this, aGivenProto); -} - -void DOMRequest::FireSuccess(JS::Handle<JS::Value> aResult) { - NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); - NS_ASSERTION(!mError, "mError shouldn't have been set!"); - NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); - - mDone = true; - if (aResult.isGCThing()) { - RootResultVal(); - } - mResult = aResult; - - FireEvent(u"success"_ns, false, false); - - if (mPromise) { - mPromise->MaybeResolve(mResult); - } -} - -void DOMRequest::FireError(const nsAString& aError) { - NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); - NS_ASSERTION(!mError, "mError shouldn't have been set!"); - NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); - - mDone = true; - // XXX Error code chosen arbitrarily - mError = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR, - NS_ConvertUTF16toUTF8(aError)); - - FireEvent(u"error"_ns, true, true); - - if (mPromise) { - mPromise->MaybeRejectBrokenly(mError); - } -} - -void DOMRequest::FireError(nsresult aError) { - NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); - NS_ASSERTION(!mError, "mError shouldn't have been set!"); - NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); - - mDone = true; - mError = DOMException::Create(aError); - - FireEvent(u"error"_ns, true, true); - - if (mPromise) { - mPromise->MaybeRejectBrokenly(mError); - } -} - -void DOMRequest::FireDetailedError(DOMException& aError) { - NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); - NS_ASSERTION(!mError, "mError shouldn't have been set!"); - NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); - - mDone = true; - mError = &aError; - - FireEvent(u"error"_ns, true, true); - - if (mPromise) { - mPromise->MaybeRejectBrokenly(mError); - } -} - -void DOMRequest::FireEvent(const nsAString& aType, bool aBubble, - bool aCancelable) { - if (NS_FAILED(CheckCurrentGlobalCorrectness())) { - return; - } - - RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); - event->InitEvent(aType, aBubble, aCancelable); - event->SetTrusted(true); - - DispatchEvent(*event); -} - -void DOMRequest::RootResultVal() { mozilla::HoldJSObjects(this); } - -void DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback, - AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - mozilla::ErrorResult& aRv) { - if (!mPromise) { - mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv); - if (aRv.Failed()) { - return; - } - if (mDone) { - // Since we create mPromise lazily, it's possible that the DOMRequest - // object has already fired its success/error event. In that case we - // should manually resolve/reject mPromise here. mPromise will take care - // of calling the callbacks on |promise| as needed. - if (mError) { - mPromise->MaybeRejectBrokenly(mError); - } else { - mPromise->MaybeResolve(mResult); - } - } - } - - // Just use the global of the Promise itself as the callee global. - JS::Rooted<JSObject*> global(aCx, mPromise->PromiseObj()); - global = JS::GetNonCCWObjectGlobal(global); - mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv); -} - -NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService) - -NS_IMETHODIMP -DOMRequestService::CreateRequest(mozIDOMWindow* aWindow, - DOMRequest** aRequest) { - MOZ_ASSERT(NS_IsMainThread()); - NS_ENSURE_STATE(aWindow); - auto* win = nsPIDOMWindowInner::From(aWindow); - RefPtr<DOMRequest> req = new DOMRequest(win); - req.forget(aRequest); - - return NS_OK; -} - -NS_IMETHODIMP -DOMRequestService::FireSuccess(DOMRequest* aRequest, - JS::Handle<JS::Value> aResult) { - NS_ENSURE_STATE(aRequest); - aRequest->FireSuccess(aResult); - - return NS_OK; -} - -NS_IMETHODIMP -DOMRequestService::FireError(DOMRequest* aRequest, const nsAString& aError) { - NS_ENSURE_STATE(aRequest); - aRequest->FireError(aError); - - return NS_OK; -} - -class FireSuccessAsyncTask : public mozilla::Runnable { - FireSuccessAsyncTask(DOMRequest* aRequest, const JS::Value& aResult) - : mozilla::Runnable("FireSuccessAsyncTask"), - mReq(aRequest), - mResult(RootingCx(), aResult) {} - - public: - // Due to the fact that initialization can fail during shutdown (since we - // can't fetch a js context), set up an initiatization function to make sure - // we can return the failure appropriately - static nsresult Dispatch(DOMRequest* aRequest, const JS::Value& aResult) { - RefPtr<FireSuccessAsyncTask> asyncTask = - new FireSuccessAsyncTask(aRequest, aResult); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask)); - return NS_OK; - } - - NS_IMETHOD - Run() override { - mReq->FireSuccess( - JS::Handle<JS::Value>::fromMarkedLocation(mResult.address())); - return NS_OK; - } - - private: - RefPtr<DOMRequest> mReq; - JS::PersistentRooted<JS::Value> mResult; -}; - -class FireErrorAsyncTask : public mozilla::Runnable { - public: - FireErrorAsyncTask(DOMRequest* aRequest, const nsAString& aError) - : mozilla::Runnable("FireErrorAsyncTask"), - mReq(aRequest), - mError(aError) {} - - NS_IMETHOD - Run() override { - mReq->FireError(mError); - return NS_OK; - } - - private: - RefPtr<DOMRequest> mReq; - nsString mError; -}; - -NS_IMETHODIMP -DOMRequestService::FireSuccessAsync(DOMRequest* aRequest, - JS::Handle<JS::Value> aResult) { - NS_ENSURE_STATE(aRequest); - return FireSuccessAsyncTask::Dispatch(aRequest, aResult); -} - -NS_IMETHODIMP -DOMRequestService::FireErrorAsync(DOMRequest* aRequest, - const nsAString& aError) { - NS_ENSURE_STATE(aRequest); - nsCOMPtr<nsIRunnable> asyncTask = new FireErrorAsyncTask(aRequest, aError); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask)); - return NS_OK; -} diff --git a/dom/base/DOMRequest.h b/dom/base/DOMRequest.h deleted file mode 100644 index b0e7c23112..0000000000 --- a/dom/base/DOMRequest.h +++ /dev/null @@ -1,103 +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 mozilla_dom_domrequest_h__ -#define mozilla_dom_domrequest_h__ - -#include "nsIDOMRequestService.h" -#include "mozilla/Attributes.h" -#include "mozilla/DOMEventTargetHelper.h" -#include "mozilla/dom/DOMException.h" -#include "mozilla/dom/DOMRequestBinding.h" - -#include "nsCOMPtr.h" - -namespace mozilla { - -class ErrorResult; - -namespace dom { - -class AnyCallback; -class Promise; - -class DOMRequest : public DOMEventTargetHelper { - protected: - JS::Heap<JS::Value> mResult; - RefPtr<DOMException> mError; - RefPtr<Promise> mPromise; - bool mDone; - - public: - NS_DECL_ISUPPORTS_INHERITED - - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DOMRequest, - DOMEventTargetHelper) - - // WrapperCache - nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); } - - virtual JSObject* WrapObject(JSContext* aCx, - JS::Handle<JSObject*> aGivenProto) override; - - // WebIDL Interface - DOMRequestReadyState ReadyState() const { - return mDone ? DOMRequestReadyState::Done : DOMRequestReadyState::Pending; - } - - void GetResult(JSContext*, JS::MutableHandle<JS::Value> aRetval) const { - NS_ASSERTION(mDone || mResult.isUndefined(), - "Result should be undefined when pending"); - aRetval.set(mResult); - } - - DOMException* GetError() const { - NS_ASSERTION(mDone || !mError, "Error should be null when pending"); - return mError; - } - - IMPL_EVENT_HANDLER(success) - IMPL_EVENT_HANDLER(error) - - void Then(JSContext* aCx, AnyCallback* aResolveCallback, - AnyCallback* aRejectCallback, JS::MutableHandle<JS::Value> aRetval, - mozilla::ErrorResult& aRv); - - void FireSuccess(JS::Handle<JS::Value> aResult); - void FireError(const nsAString& aError); - void FireError(nsresult aError); - void FireDetailedError(DOMException& aError); - - explicit DOMRequest(nsPIDOMWindowInner* aWindow); - explicit DOMRequest(nsIGlobalObject* aGlobal); - - protected: - virtual ~DOMRequest(); - - void FireEvent(const nsAString& aType, bool aBubble, bool aCancelable); - - void RootResultVal(); -}; - -class DOMRequestService final : public nsIDOMRequestService { - ~DOMRequestService() = default; - - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIDOMREQUESTSERVICE - - // No one should call this but the factory. - static already_AddRefed<DOMRequestService> FactoryCreate() { - return MakeAndAddRef<DOMRequestService>(); - } -}; - -} // namespace dom -} // namespace mozilla - -#define DOMREQUEST_SERVICE_CONTRACTID "@mozilla.org/dom/dom-request-service;1" - -#endif // mozilla_dom_domrequest_h__ diff --git a/dom/base/DOMRequestHelper.sys.mjs b/dom/base/DOMRequestHelper.sys.mjs deleted file mode 100644 index 832c06c4de..0000000000 --- a/dom/base/DOMRequestHelper.sys.mjs +++ /dev/null @@ -1,335 +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/. */ - -/** - * Helper object for APIs that deal with DOMRequests and Promises. - * It allows objects inheriting from it to create and keep track of DOMRequests - * and Promises objects in the common scenario where requests are created in - * the child, handed out to content and delivered to the parent within an async - * message (containing the identifiers of these requests). The parent may send - * messages back as answers to different requests and the child will use this - * helper to get the right request object. This helper also takes care of - * releasing the requests objects when the window goes out of scope. - * - * DOMRequestIPCHelper also deals with message listeners, allowing to add them - * to the child side of frame and process message manager and removing them - * when needed. - */ -export function DOMRequestIpcHelper() { - // _listeners keeps a list of messages for which we added a listener and the - // kind of listener that we added (strong or weak). It's an object of this - // form: - // { - // "message1": true, - // "messagen": false - // } - // - // where each property is the name of the message and its value is a boolean - // that indicates if the listener is weak or not. - this._listeners = null; - this._requests = null; - this._window = null; -} - -DOMRequestIpcHelper.prototype = { - /** - * An object which "inherits" from DOMRequestIpcHelper and declares its own - * queryInterface method MUST implement Ci.nsISupportsWeakReference. - */ - QueryInterface: ChromeUtils.generateQI([ - "nsISupportsWeakReference", - "nsIObserver", - ]), - - /** - * 'aMessages' is expected to be an array of either: - * - objects of this form: - * { - * name: "messageName", - * weakRef: false - * } - * where 'name' is the message identifier and 'weakRef' a boolean - * indicating if the listener should be a weak referred one or not. - * - * - or only strings containing the message name, in which case the listener - * will be added as a strong reference by default. - */ - addMessageListeners(aMessages) { - if (!aMessages) { - return; - } - - if (!this._listeners) { - this._listeners = {}; - } - - if (!Array.isArray(aMessages)) { - aMessages = [aMessages]; - } - - aMessages.forEach(aMsg => { - let name = aMsg.name || aMsg; - // If the listener is already set and it is of the same type we just - // increase the count and bail out. If it is not of the same type, - // we throw an exception. - if (this._listeners[name] != undefined) { - if (!!aMsg.weakRef == this._listeners[name].weakRef) { - this._listeners[name].count++; - return; - } - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - - aMsg.weakRef - ? Services.cpmm.addWeakMessageListener(name, this) - : Services.cpmm.addMessageListener(name, this); - this._listeners[name] = { - weakRef: !!aMsg.weakRef, - count: 1, - }; - }); - }, - - /** - * 'aMessages' is expected to be a string or an array of strings containing - * the message names of the listeners to be removed. - */ - removeMessageListeners(aMessages) { - if (!this._listeners || !aMessages) { - return; - } - - if (!Array.isArray(aMessages)) { - aMessages = [aMessages]; - } - - aMessages.forEach(aName => { - if (this._listeners[aName] == undefined) { - return; - } - - // Only remove the listener really when we don't have anybody that could - // be waiting on a message. - if (!--this._listeners[aName].count) { - this._listeners[aName].weakRef - ? Services.cpmm.removeWeakMessageListener(aName, this) - : Services.cpmm.removeMessageListener(aName, this); - delete this._listeners[aName]; - } - }); - }, - - /** - * Initialize the helper adding the corresponding listeners to the messages - * provided as the second parameter. - * - * 'aMessages' is expected to be an array of either: - * - * - objects of this form: - * { - * name: 'messageName', - * weakRef: false - * } - * where 'name' is the message identifier and 'weakRef' a boolean - * indicating if the listener should be a weak referred one or not. - * - * - or only strings containing the message name, in which case the listener - * will be added as a strong referred one by default. - */ - initDOMRequestHelper(aWindow, aMessages) { - // Query our required interfaces to force a fast fail if they are not - // provided. These calls will throw if the interface is not available. - this.QueryInterface(Ci.nsISupportsWeakReference); - this.QueryInterface(Ci.nsIObserver); - - if (aMessages) { - this.addMessageListeners(aMessages); - } - - this._id = this._getRandomId(); - - this._window = aWindow; - if (this._window) { - // We don't use this.innerWindowID, but other classes rely on it. - this.innerWindowID = this._window.windowGlobalChild.innerWindowId; - } - - this._destroyed = false; - - Services.obs.addObserver( - this, - "inner-window-destroyed", - /* weak-ref */ true - ); - }, - - destroyDOMRequestHelper() { - if (this._destroyed) { - return; - } - - this._destroyed = true; - - Services.obs.removeObserver(this, "inner-window-destroyed"); - - if (this._listeners) { - Object.keys(this._listeners).forEach(aName => { - this._listeners[aName].weakRef - ? Services.cpmm.removeWeakMessageListener(aName, this) - : Services.cpmm.removeMessageListener(aName, this); - }); - } - - this._listeners = null; - this._requests = null; - - // Objects inheriting from DOMRequestIPCHelper may have an uninit function. - if (this.uninit) { - this.uninit(); - } - - this._window = null; - }, - - observe(aSubject, aTopic, aData) { - if (aTopic !== "inner-window-destroyed") { - return; - } - - let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (wId != this.innerWindowID) { - return; - } - - this.destroyDOMRequestHelper(); - }, - - getRequestId(aRequest) { - if (!this._requests) { - this._requests = {}; - } - - let id = "id" + this._getRandomId(); - this._requests[id] = aRequest; - return id; - }, - - getPromiseResolverId(aPromiseResolver) { - // Delegates to getRequest() since the lookup table is agnostic about - // storage. - return this.getRequestId(aPromiseResolver); - }, - - getRequest(aId) { - if (this._requests && this._requests[aId]) { - return this._requests[aId]; - } - return undefined; - }, - - getPromiseResolver(aId) { - // Delegates to getRequest() since the lookup table is agnostic about - // storage. - return this.getRequest(aId); - }, - - removeRequest(aId) { - if (this._requests && this._requests[aId]) { - delete this._requests[aId]; - } - }, - - removePromiseResolver(aId) { - // Delegates to getRequest() since the lookup table is agnostic about - // storage. - this.removeRequest(aId); - }, - - takeRequest(aId) { - if (!this._requests || !this._requests[aId]) { - return null; - } - let request = this._requests[aId]; - delete this._requests[aId]; - return request; - }, - - takePromiseResolver(aId) { - // Delegates to getRequest() since the lookup table is agnostic about - // storage. - return this.takeRequest(aId); - }, - - _getRandomId() { - return Services.uuid.generateUUID().toString(); - }, - - createRequest() { - // If we don't have a valid window object, throw. - if (!this._window) { - console.error( - "DOMRequestHelper trying to create a DOMRequest without a valid window, failing." - ); - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - return Services.DOMRequest.createRequest(this._window); - }, - - /** - * createPromise() creates a new Promise, with `aPromiseInit` as the - * PromiseInit callback. The promise constructor is obtained from the - * reference to window owned by this DOMRequestIPCHelper. - */ - createPromise(aPromiseInit) { - // If we don't have a valid window object, throw. - if (!this._window) { - console.error( - "DOMRequestHelper trying to create a Promise without a valid window, failing." - ); - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - return new this._window.Promise(aPromiseInit); - }, - - /** - * createPromiseWithId() creates a new Promise, accepting a callback - * which is immediately called with the generated resolverId. - */ - createPromiseWithId(aCallback) { - return this.createPromise((aResolve, aReject) => { - let resolverId = this.getPromiseResolverId({ - resolve: aResolve, - reject: aReject, - }); - aCallback(resolverId); - }); - }, - - forEachRequest(aCallback) { - if (!this._requests) { - return; - } - - Object.keys(this._requests).forEach(aKey => { - if (this._window.DOMRequest.isInstance(this.getRequest(aKey))) { - aCallback(aKey); - } - }); - }, - - forEachPromiseResolver(aCallback) { - if (!this._requests) { - return; - } - - Object.keys(this._requests).forEach(aKey => { - if ( - "resolve" in this.getPromiseResolver(aKey) && - "reject" in this.getPromiseResolver(aKey) - ) { - aCallback(aKey); - } - }); - }, -}; diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp index 46229cfbd3..dd427c61b1 100644 --- a/dom/base/DirectionalityUtils.cpp +++ b/dom/base/DirectionalityUtils.cpp @@ -5,8 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - Implementation description from https://etherpad.mozilla.org/dir-auto - Static case =========== When we see a new content node with @dir=auto from the parser, we set the @@ -45,23 +43,8 @@ I will call this algorithm "upward propagation". - Each text node should maintain a list of elements which have their - directionality determined by the first strong character of that text node. - This is useful to make dynamic changes more efficient. One way to implement - this is to have a per-document hash table mapping a text node to a set of - elements. I'll call this data structure TextNodeDirectionalityMap. The - algorithm for appending a new text node above needs to update this data - structure. - - *IMPLEMENTATION NOTE* - In practice, the implementation uses two per-node properties: - - dirAutoSetBy, which is set on a node with auto-directionality, and points to - the textnode that contains the strong character which determines the - directionality of the node. - - textNodeDirectionalityMap, which is set on a text node and points to a hash - table listing the nodes whose directionality is determined by the text node. + Each text node keeps a flag if it might determine the directionality of any + ancestor. This is useful to make dynamic changes more efficient. Handling dynamic changes ======================== @@ -90,16 +73,15 @@ (I'll call this the "downward propagation algorithm".) by walking the child subtree in tree order. Note that an element with @dir=auto should not affect other elements in its document with @dir=auto. So there is no need to walk up - the parent chain in this case. TextNodeDirectionalityMap needs to be updated - as appropriate. + the parent chain in this case. 3a. When the dir attribute is set to any valid value on an element that didn't have a valid dir attribute before, this means that any descendant of that element will not affect the directionality of any of its ancestors. So we need - to check whether any text node descendants of the element are listed in - TextNodeDirectionalityMap, and whether the elements whose direction they set - are ancestors of the element. If so, we need to rerun the downward propagation - algorithm for those ancestors. + to check whether any text node descendants of the element can set the dir of + any ancestor, and whether the elements whose direction they set are ancestors + of the element. If so, we need to rerun the downward propagation algorithm for + those ancestors. That's done by OnSetDirAttr. 4. When the dir attribute is changed from auto to something else (including the case where it gets removed) on a textarea or an input element with @@ -118,80 +100,29 @@ should still retain the same flag.) * We resolve the directionality of the element based on the value of the @dir attribute on the element itself or its parent element. - TextNodeDirectionalityMap needs to be updated as appropriate. 5a. When the dir attribute is removed or set to an invalid value on any element (except a bdi element) with the NodeAncestorHasDirAuto flag which previously had a valid dir attribute, it might have a text node descendant that did not previously affect the directionality of any of its ancestors but - should now begin to affect them. We run the following algorithm: - * Walk up the parent chain from the element. - * For any element that appears in the TextNodeDirectionalityMap, remove the - element from the map and rerun the downward propagation algorithm - (see section 3). - * If we reach an element without either of the NodeHasDirAuto or - NodeAncestorHasDirAuto flags, abort the parent chain walk. + should now begin to affect them. We run OnSetDirAttr. 6. When an element with @dir=auto is added to the document, we should handle it similar to the case 2/3 above. 7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is removed from the document, we should handle it similar to the case 4/5 above, - except that we don't need to handle anything in the child subtree. We should - also remove all of the occurrences of that node and its descendants from - TextNodeDirectionalityMap. (This is the conceptual description of what needs - to happen but in the implementation UnbindFromTree is going to be called on - all of the descendants so we don't need to descend into the child subtree). + except that we don't need to handle anything in the child subtree. 8. When the contents of a text node is changed either from script or by the - user, we need to run the following algorithm: - * If the change has happened after the first character with strong - directionality in the text node, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node belongs to a textarea with NodeHasDirAuto, we need to - update the directionality of the textarea. - * Grab a list of elements affected by this text node from - TextNodeDirectionalityMap and re-resolve the directionality of each one of - them based on the new contents of the text node. - * If the text node does not exist in TextNodeDirectionalityMap, and it has the - NodeAncestorHasDirAuto flag set, this could potentially be a text node - which is going to start affecting the directionality of its parent @dir=auto - elements. In this case, we need to fall back to the (potentially expensive) - "upward propagation algorithm". The TextNodeDirectionalityMap data structure - needs to be update during this algorithm. - * If the new contents of the text node do not have any strong characters, and - the old contents used to, and the text node used to exist in - TextNodeDirectionalityMap and it has the NodeAncestorHasDirAuto flag set, - the elements associated with this text node inside TextNodeDirectionalityMap - will now get their directionality from another text node. In this case, for - each element in the list retrieved from TextNodeDirectionalityMap, run the - downward propagation algorithm (section 3), and remove the text node from - TextNodeDirectionalityMap. - - 9. When a new text node is injected into a document, we need to run the - following algorithm: - * If the contents of the text node do not have any characters with strong - direction, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node is appended to a textarea element with NodeHasDirAuto, we - need to update the directionality of the textarea. - * If the text node has NodeAncestorHasDirAuto, we need to run the "upward - propagation algorithm". The TextNodeDirectionalityMap data structure needs to - be update during this algorithm. - - 10. When a text node is removed from a document, we need to run the following - algorithm: - * If the contents of the text node do not have any characters with strong - direction, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node is removed from a textarea element with NodeHasDirAuto, - set the directionality to "ltr". (This is what the spec currently says, but - I'm filing a spec bug to get it fixed -- the directionality should depend on - the parent element here.) - * If the text node has NodeAncestorHasDirAuto, we need to look at the list - of elements being affected by this text node from TextNodeDirectionalityMap, - run the "downward propagation algorithm" (section 3) for each one of them, - while updating TextNodeDirectionalityMap along the way. + user, we need to run TextNode{WillChange,Changed}Direction, see inline docs + for details. + + 9. When a new text node is injected into a document, we need to run + SetDirectionFromNewTextNode. + + 10. When a text node is removed from a document, we need to run + ResetDirectionSetByTextNode. 11. If the value of the @dir attribute on a bdi element is changed to an invalid value (or if it's removed), determine the new directionality similar @@ -210,18 +141,16 @@ #include "nsIContent.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" -#include "mozilla/AutoRestore.h" -#include "mozilla/DebugOnly.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/UnbindContext.h" #include "mozilla/intl/UnicodeProperties.h" #include "nsUnicodeProperties.h" #include "nsTextFragment.h" #include "nsAttrValue.h" #include "nsTextNode.h" -#include "nsCheapSets.h" namespace mozilla { @@ -230,75 +159,53 @@ using mozilla::dom::HTMLInputElement; using mozilla::dom::HTMLSlotElement; using mozilla::dom::ShadowRoot; -static nsIContent* GetParentOrHostOrSlot( - const nsIContent* aContent, bool* aCrossedShadowBoundary = nullptr) { +static nsIContent* GetParentOrHostOrSlot(const nsIContent* aContent) { if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) { - if (aCrossedShadowBoundary) { - *aCrossedShadowBoundary = true; - } return slot; } - - nsIContent* parent = aContent->GetParent(); - if (parent) { + if (nsIContent* parent = aContent->GetParent()) { return parent; } - - const ShadowRoot* sr = ShadowRoot::FromNode(aContent); - if (sr) { - if (aCrossedShadowBoundary) { - *aCrossedShadowBoundary = true; - } - return sr->Host(); + if (const ShadowRoot* sr = ShadowRoot::FromNode(aContent)) { + return sr->GetHost(); } - return nullptr; } -static bool AncestorChainCrossesShadowBoundary(nsIContent* aDescendant, - nsIContent* aAncestor) { - bool crossedShadowBoundary = false; - nsIContent* content = aDescendant; - while (content && content != aAncestor) { - content = GetParentOrHostOrSlot(content, &crossedShadowBoundary); - if (crossedShadowBoundary) { - return true; - } - } - - return false; -} - /** - * Returns true if aElement is one of the elements whose text content should not - * affect its own direction, nor the direction of ancestors with dir=auto. + * Returns true if aElement is one of the elements whose text content should + * affect its own direction, or the direction of ancestors with dir=auto. * * Note that this does not include <bdi>, whose content does affect its own * direction when it has dir=auto (which it has by default), so one needs to - * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors. + * test for it separately, e.g. with AffectsDirectionOfAncestors. * It *does* include textarea, because even if a textarea has dir=auto, it has * unicode-bidi: plaintext and is handled automatically in bidi resolution. * It also includes `input`, because it takes the `dir` value from its value * attribute, instead of the child nodes. */ -static bool DoesNotParticipateInAutoDirection(const nsIContent* aContent) { - mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo(); - return ((!aContent->IsHTMLElement() || nodeInfo->Equals(nsGkAtoms::script) || - nodeInfo->Equals(nsGkAtoms::style) || - nodeInfo->Equals(nsGkAtoms::input) || - nodeInfo->Equals(nsGkAtoms::textarea) || - aContent->IsInNativeAnonymousSubtree())) && - !aContent->IsShadowRoot(); +static bool ParticipatesInAutoDirection(const nsIContent* aContent) { + if (aContent->IsInNativeAnonymousSubtree()) { + return false; + } + if (aContent->IsShadowRoot()) { + return true; + } + dom::NodeInfo* ni = aContent->NodeInfo(); + return ni->NamespaceID() == kNameSpaceID_XHTML && + !ni->Equals(nsGkAtoms::script) && !ni->Equals(nsGkAtoms::style) && + !ni->Equals(nsGkAtoms::input) && !ni->Equals(nsGkAtoms::textarea); } /** - * Returns true if aElement is one of the element whose text content should not - * affect the direction of ancestors with dir=auto (though it may affect its own - * direction, e.g. <bdi>) + * Returns true if aElement is one of the element whose text should affect the + * direction of ancestors with dir=auto (though note that even if it returns + * false it may affect its own direction, e.g. <bdi> or dir=auto itself) */ -static bool DoesNotAffectDirectionOfAncestors(const Element* aElement) { - return (DoesNotParticipateInAutoDirection(aElement) || - aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir()); +static bool AffectsDirectionOfAncestors(const Element* aElement) { + return ParticipatesInAutoDirection(aElement) && + !aElement->IsHTMLElement(nsGkAtoms::bdi) && !aElement->HasFixedDir() && + !aElement->HasDirAuto(); } /** @@ -318,11 +225,15 @@ static Directionality GetDirectionFromChar(uint32_t ch) { } } -inline static bool NodeAffectsDirAutoAncestor(nsIContent* aTextNode) { +inline static bool TextChildrenAffectDirAutoAncestor(nsIContent* aContent) { + return ParticipatesInAutoDirection(aContent) && + aContent->NodeOrAncestorHasDirAuto(); +} + +inline static bool NodeAffectsDirAutoAncestor(nsTextNode* aTextNode) { nsIContent* parent = GetParentOrHostOrSlot(aTextNode); - return (parent && !DoesNotParticipateInAutoDirection(parent) && - parent->NodeOrAncestorHasDirAuto() && - !aTextNode->IsInNativeAnonymousSubtree()); + return parent && TextChildrenAffectDirAutoAncestor(parent) && + !aTextNode->IsInNativeAnonymousSubtree(); } Directionality GetDirectionFromText(const char16_t* aText, @@ -394,11 +305,11 @@ static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode, } static nsTextNode* WalkDescendantsAndGetDirectionFromText( - nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) { + nsINode* aRoot, Directionality* aDirectionality) { nsIContent* child = aRoot->GetFirstChild(); while (child) { if ((child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) || + !AffectsDirectionOfAncestors(child->AsElement())) || child->GetAssignedSlot()) { child = child->GetNextNonChildNode(aRoot); continue; @@ -409,19 +320,16 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( for (uint32_t i = 0; i < assignedNodes.Length(); ++i) { nsIContent* assignedNode = assignedNodes[i]->AsContent(); if (assignedNode->NodeType() == nsINode::TEXT_NODE) { - auto text = static_cast<nsTextNode*>(assignedNode); - if (assignedNode != aSkip) { - Directionality textNodeDir = GetDirectionFromText(text); - if (textNodeDir != Directionality::Unset) { - *aDirectionality = textNodeDir; - return text; - } + auto* text = static_cast<nsTextNode*>(assignedNode); + Directionality textNodeDir = GetDirectionFromText(text); + if (textNodeDir != Directionality::Unset) { + *aDirectionality = textNodeDir; + return text; } } else if (assignedNode->IsElement() && - !DoesNotAffectDirectionOfAncestors( - assignedNode->AsElement())) { + AffectsDirectionOfAncestors(assignedNode->AsElement())) { nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - assignedNode, aSkip, aDirectionality); + assignedNode, aDirectionality); if (text) { return text; } @@ -429,8 +337,8 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( } } - if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) { - auto text = static_cast<nsTextNode*>(child); + if (child->NodeType() == nsINode::TEXT_NODE) { + auto* text = static_cast<nsTextNode*>(child); Directionality textNodeDir = GetDirectionFromText(text); if (textNodeDir != Directionality::Unset) { *aDirectionality = textNodeDir; @@ -447,17 +355,14 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( * Set the directionality of a node with dir=auto as defined in * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality * - * @param[in] changedNode If we call this method because the content of a text - * node is about to change, pass in the changed node, so that we - * know not to return it * @return the text node containing the character that determined the direction */ -static nsTextNode* WalkDescendantsSetDirectionFromText( - Element* aElement, bool aNotify, nsINode* aChangedNode = nullptr) { +static nsTextNode* WalkDescendantsSetDirectionFromText(Element* aElement, + bool aNotify) { MOZ_ASSERT(aElement, "Must have an element"); MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto"); - if (DoesNotParticipateInAutoDirection(aElement)) { + if (!ParticipatesInAutoDirection(aElement)) { return nullptr; } @@ -465,8 +370,8 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( // Check the text in Shadow DOM. if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) { - nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - shadowRoot, aChangedNode, &textNodeDir); + nsTextNode* text = + WalkDescendantsAndGetDirectionFromText(shadowRoot, &textNodeDir); if (text) { aElement->SetDirectionality(textNodeDir, aNotify); return text; @@ -474,8 +379,8 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( } // Check the text in light DOM. - nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - aElement, aChangedNode, &textNodeDir); + nsTextNode* text = + WalkDescendantsAndGetDirectionFromText(aElement, &textNodeDir); if (text) { aElement->SetDirectionality(textNodeDir, aNotify); return text; @@ -487,194 +392,6 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( return nullptr; } -class nsTextNodeDirectionalityMap { - static void nsTextNodeDirectionalityMapDtor(void* aObject, - nsAtom* aPropertyName, - void* aPropertyValue, - void* aData) { - nsINode* textNode = static_cast<nsINode*>(aObject); - textNode->ClearHasTextNodeDirectionalityMap(); - - nsTextNodeDirectionalityMap* map = - reinterpret_cast<nsTextNodeDirectionalityMap*>(aPropertyValue); - map->EnsureMapIsClear(); - delete map; - } - - public: - explicit nsTextNodeDirectionalityMap(nsINode* aTextNode) - : mElementToBeRemoved(nullptr) { - MOZ_ASSERT(aTextNode, "Null text node"); - MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap); - aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this, - nsTextNodeDirectionalityMapDtor); - aTextNode->SetHasTextNodeDirectionalityMap(); - } - - MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap) - - static void nsTextNodeDirectionalityMapPropertyDestructor( - void* aObject, nsAtom* aProperty, void* aPropertyValue, void* aData) { - nsTextNode* textNode = static_cast<nsTextNode*>(aPropertyValue); - nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode); - if (map) { - map->RemoveEntryForProperty(static_cast<Element*>(aObject)); - } - NS_RELEASE(textNode); - } - - void AddEntry(nsTextNode* aTextNode, Element* aElement) { - if (!mElements.Contains(aElement)) { - mElements.Put(aElement); - NS_ADDREF(aTextNode); - aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode, - nsTextNodeDirectionalityMapPropertyDestructor); - aElement->SetHasDirAutoSet(); - } - } - - void RemoveEntry(nsTextNode* aTextNode, Element* aElement) { - NS_ASSERTION(mElements.Contains(aElement), - "element already removed from map"); - - mElements.Remove(aElement); - aElement->ClearHasDirAutoSet(); - aElement->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - - void RemoveEntryForProperty(Element* aElement) { - if (mElementToBeRemoved != aElement) { - mElements.Remove(aElement); - } - aElement->ClearHasDirAutoSet(); - } - - private: - nsCheapSet<nsPtrHashKey<Element>> mElements; - // Only used for comparison. - Element* mElementToBeRemoved; - - static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode) { - MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, - "Must be a text node"); - nsTextNodeDirectionalityMap* map = nullptr; - - if (aTextNode->HasTextNodeDirectionalityMap()) { - map = static_cast<nsTextNodeDirectionalityMap*>( - aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap)); - } - - return map; - } - - static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, - void* aDir) { - aEntry->GetKey()->SetDirectionality( - *reinterpret_cast<Directionality*>(aDir), true); - return OpNext; - } - - struct nsTextNodeDirectionalityMapAndElement { - nsTextNodeDirectionalityMap* mMap; - nsCOMPtr<nsINode> mNode; - }; - - static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, - void* aData) { - // run the downward propagation algorithm - // and remove the text node from the map - nsTextNodeDirectionalityMapAndElement* data = - static_cast<nsTextNodeDirectionalityMapAndElement*>(aData); - nsINode* oldTextNode = data->mNode; - Element* rootNode = aEntry->GetKey(); - nsTextNode* newTextNode = nullptr; - if (rootNode->GetParentNode() && rootNode->HasDirAuto()) { - newTextNode = - WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode); - } - - AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved); - data->mMap->mElementToBeRemoved = rootNode; - if (newTextNode) { - nsINode* oldDirAutoSetBy = static_cast<nsTextNode*>( - rootNode->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (oldDirAutoSetBy == newTextNode) { - // We're already registered. - return OpNext; - } - nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode); - } else { - rootNode->ClearHasDirAutoSet(); - rootNode->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - return OpRemove; - } - - static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry, - void* aData) { - AutoTArray<Element*, 8>* entries = - static_cast<AutoTArray<Element*, 8>*>(aData); - entries->AppendElement(aEntry->GetKey()); - return OpRemove; - } - - public: - uint32_t UpdateAutoDirection(Directionality aDir) { - return mElements.EnumerateEntries(SetNodeDirection, &aDir); - } - - void ResetAutoDirection(nsINode* aTextNode) { - nsTextNodeDirectionalityMapAndElement data = {this, aTextNode}; - mElements.EnumerateEntries(ResetNodeDirection, &data); - } - - void EnsureMapIsClear() { - AutoRestore<Element*> restore(mElementToBeRemoved); - AutoTArray<Element*, 8> entries; - mElements.EnumerateEntries(TakeEntries, &entries); - for (Element* el : entries) { - el->ClearHasDirAutoSet(); - el->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - } - - static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement) { - if (aTextNode->HasTextNodeDirectionalityMap()) { - GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement); - } - } - - static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement) { - nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode); - if (!map) { - map = new nsTextNodeDirectionalityMap(aTextNode); - } - - map->AddEntry(aTextNode, aElement); - } - - static uint32_t UpdateTextNodeDirection(nsINode* aTextNode, - Directionality aDir) { - MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(), - "Map missing in UpdateTextNodeDirection"); - return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir); - } - - static void ResetTextNodeDirection(nsTextNode* aTextNode, - nsTextNode* aChangedTextNode) { - MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(), - "Map missing in ResetTextNodeDirection"); - RefPtr<nsTextNode> textNode = aTextNode; - GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode); - } - - static void EnsureMapIsClearFor(nsINode* aTextNode) { - if (aTextNode->HasTextNodeDirectionalityMap()) { - GetDirectionalityMap(aTextNode)->EnsureMapIsClear(); - } - } -}; - Directionality GetParentDirectionality(const Element* aElement) { if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) { if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) { @@ -729,7 +446,7 @@ static inline bool IsBoundary(const Element& aElement) { static void SetDirectionalityOnDescendantsInternal(nsINode* aNode, Directionality aDir, bool aNotify) { - if (Element* element = Element::FromNode(aNode)) { + if (auto* element = Element::FromNode(aNode)) { if (ShadowRoot* shadow = element->GetShadowRoot()) { SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify); } @@ -778,66 +495,37 @@ void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir, } static void ResetAutoDirection(Element* aElement, bool aNotify) { - if (aElement->HasDirAutoSet()) { - // If the parent has the DirAutoSet flag, its direction is determined by - // some text node descendant. - // Remove it from the map and reset its direction by the downward - // propagation algorithm - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (setByNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } - } - - if (aElement->HasDirAuto()) { - nsTextNode* setByNode = - WalkDescendantsSetDirectionFromText(aElement, aNotify); - if (setByNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement); - } - SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(), - aNotify); + MOZ_ASSERT(aElement->HasDirAuto()); + nsTextNode* setByNode = + WalkDescendantsSetDirectionFromText(aElement, aNotify); + if (setByNode) { + setByNode->SetMaySetDirAuto(); } + SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(), + aNotify); } /** - * Walk the parent chain of a text node whose dir attribute has been removed and - * reset the direction of any of its ancestors which have dir=auto and whose - * directionality is determined by a text node descendant. + * Walk the parent chain of a text node whose dir attribute has been removed or + * added and reset the direction of any of its ancestors which have dir=auto and + * whose directionality is determined by a text node descendant. */ void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) { - nsTextNode* setByNode; - nsIContent* parent = GetParentOrHostOrSlot(aElement); - while (parent && parent->NodeOrAncestorHasDirAuto()) { - if (!parent->IsElement()) { - parent = GetParentOrHostOrSlot(parent); + for (nsIContent* parent = GetParentOrHostOrSlot(aElement); + parent && parent->NodeOrAncestorHasDirAuto(); + parent = GetParentOrHostOrSlot(parent)) { + auto* parentElement = Element::FromNode(*parent); + if (!parentElement || !parentElement->HasDirAuto()) { continue; } - - Element* parentElement = parent->AsElement(); - if (parent->HasDirAutoSet()) { - // If the parent has the DirAutoSet flag, its direction is determined by - // some text node descendant. - // Remove it from the map and reset its direction by the downward - // propagation algorithm - setByNode = static_cast<nsTextNode*>( - parent->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (setByNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, - parentElement); - } - } - if (parentElement->HasDirAuto()) { - setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify); - if (setByNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement); - } - SetDirectionalityOnDescendants( - parentElement, parentElement->GetDirectionality(), aNotify); - break; + nsTextNode* setByNode = + WalkDescendantsSetDirectionFromText(parentElement, aNotify); + if (setByNode) { + setByNode->SetMaySetDirAuto(); } - parent = GetParentOrHostOrSlot(parent); + SetDirectionalityOnDescendants(parentElement, + parentElement->GetDirectionality(), aNotify); + break; } } @@ -901,26 +589,6 @@ void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) { } } -void WalkDescendantsResetAutoDirection(Element* aElement) { - nsIContent* child = aElement->GetFirstChild(); - while (child) { - if (child->IsElement() && child->AsElement()->HasDirAuto()) { - child = child->GetNextNonChildNode(aElement); - continue; - } - - if (child->NodeType() == nsINode::TEXT_NODE && - child->HasTextNodeDirectionalityMap()) { - nsTextNodeDirectionalityMap::ResetTextNodeDirection( - static_cast<nsTextNode*>(child), nullptr); - // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child) - // since ResetTextNodeDirection may have kept elements in child's - // DirectionalityMap. - } - child = child->GetNextNode(aElement); - } -} - static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot); static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) { @@ -938,7 +606,7 @@ static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) { nsIContent* child = aRoot->GetFirstChild(); while (child) { if (child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) { + !AffectsDirectionOfAncestors(child->AsElement())) { child = child->GetNextNonChildNode(aRoot); continue; } @@ -961,20 +629,20 @@ static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) { } void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) { - // Only test for DoesNotParticipateInAutoDirection -- in other words, if - // aElement is a <bdi> which is having its dir attribute set to auto (or + // Only test for ParticipatesInAutoDirection -- in other words, if aElement is + // a <bdi> which is having its dir attribute set to auto (or // removed or set to an invalid value, which are equivalent to dir=auto for // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi> // being bound to an existing node with dir=auto. - if (!DoesNotParticipateInAutoDirection(aElement) && + if (ParticipatesInAutoDirection(aElement) && !aElement->AncestorHasDirAuto()) { SetAncestorHasDirAutoOnDescendants(aElement); } nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify); if (textNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement); + textNode->SetMaySetDirAuto(); } } @@ -1022,98 +690,67 @@ void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) { } } -void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir, - bool aNotify = true) { - MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, - "Must be a text node"); +struct DirAutoElementResult { + Element* mElement = nullptr; + // This is false when we hit the top of the ancestor chain without finding a + // dir=auto element or an element with a fixed direction. This is useful when + // processing node removals, since we might need to look at the subtree we're + // removing from. + bool mAnswerIsDefinitive = false; +}; - bool crossedShadowBoundary = false; - nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary); - while (parent && parent->NodeOrAncestorHasDirAuto()) { - if (!parent->IsElement()) { - parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary); +static DirAutoElementResult FindDirAutoElementFrom(nsIContent* aContent) { + for (nsIContent* parent = aContent; + parent && parent->NodeOrAncestorHasDirAuto(); + parent = GetParentOrHostOrSlot(parent)) { + auto* parentElement = Element::FromNode(*parent); + if (!parentElement) { continue; } - - Element* parentElement = parent->AsElement(); - if (DoesNotParticipateInAutoDirection(parentElement) || + if (!ParticipatesInAutoDirection(parentElement) || parentElement->HasFixedDir()) { - break; + return {nullptr, true}; } - if (parentElement->HasDirAuto()) { - bool resetDirection = false; - nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>( - parent->GetProperty(nsGkAtoms::dirAutoSetBy)); - - if (!parent->HasDirAutoSet()) { - // Fast path if parent's direction is not yet set by any descendant - MOZ_ASSERT(!directionWasSetByTextNode, - "dirAutoSetBy property should be null"); - resetDirection = true; - } else { - // If parent's direction is already set, we need to know if - // aTextNode is before or after the text node that had set it. - // We will walk parent's descendants in tree order starting from - // aTextNode to optimize for the most common case where text nodes are - // being appended to tree. - if (!directionWasSetByTextNode) { - resetDirection = true; - } else if (directionWasSetByTextNode != aTextNode) { - if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary( - directionWasSetByTextNode, parent)) { - // Need to take the slow path when the path from either the old or - // new text node to the dir=auto element crosses shadow boundary. - ResetAutoDirection(parentElement, aNotify); - return; - } - - nsIContent* child = aTextNode->GetNextNode(parent); - while (child) { - if (child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) { - child = child->GetNextNonChildNode(parent); - continue; - } - - if (child == directionWasSetByTextNode) { - // we found the node that set the element's direction after our - // text node, so we need to reset the direction - resetDirection = true; - break; - } - - child = child->GetNextNode(parent); - } - } - } - - if (resetDirection) { - if (directionWasSetByTextNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap( - directionWasSetByTextNode, parentElement); - } - parentElement->SetDirectionality(aDir, aNotify); - nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement); - SetDirectionalityOnDescendants(parentElement, aDir, aNotify); - } + return {parentElement, true}; + } + } + return {nullptr, false}; +} - // Since we found an element with dir=auto, we can stop walking the - // parent chain: none of its ancestors will have their direction set by - // any of its descendants. - return; +static DirAutoElementResult FindDirAutoElementForText(nsTextNode* aTextNode) { + MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, + "Must be a text node"); + return FindDirAutoElementFrom(GetParentOrHostOrSlot(aTextNode)); +} + +static DirAutoElementResult SetAncestorDirectionIfAuto(nsTextNode* aTextNode, + Directionality aDir, + bool aNotify = true) { + auto result = FindDirAutoElementForText(aTextNode); + if (Element* parentElement = result.mElement) { + if (parentElement->GetDirectionality() == aDir) { + // If we know that the directionality is already correct, we don't need to + // reset it. But we might be responsible for the directionality of + // parentElement. + MOZ_ASSERT(aDir != Directionality::Unset); + aTextNode->SetMaySetDirAuto(); + } else { + // Otherwise recompute the directionality of parentElement. + ResetAutoDirection(parentElement, aNotify); } - parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary); } + return result; } bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, uint32_t aOffset) { if (!NodeAffectsDirAutoAncestor(aTextNode)) { - nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode); return false; } + // If the change has happened after the first character with strong + // directionality in the text node, do nothing. uint32_t firstStrong; *aOldDir = GetDirectionFromText(aTextNode, &firstStrong); return (aOffset <= firstStrong); @@ -1121,29 +758,17 @@ bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir, bool aNotify) { + MOZ_ASSERT(NodeAffectsDirAutoAncestor(aTextNode), "Caller should check"); Directionality newDir = GetDirectionFromText(aTextNode); - if (newDir == Directionality::Unset) { - if (aOldDir != Directionality::Unset && - aTextNode->HasTextNodeDirectionalityMap()) { - // This node used to have a strong directional character but no - // longer does. ResetTextNodeDirection() will re-resolve the - // directionality of any elements whose directionality was - // determined by this node. - nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode); - } - } else { - // This node has a strong directional character. If it has a - // TextNodeDirectionalityMap property, it already determines the - // directionality of some element(s), so call UpdateTextNodeDirection to - // reresolve their directionality. If it has no map, or if - // UpdateTextNodeDirection returns zero, indicating that the map is - // empty, call SetAncestorDirectionIfAuto to find ancestor elements - // which should have their directionality determined by this node. - if (aTextNode->HasTextNodeDirectionalityMap() && - nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, - newDir)) { - return; - } + if (newDir == aOldDir) { + return; + } + // If the old directionality is Unset, we might determine now dir=auto + // ancestor direction now, even if we don't have the MaySetDirAuto flag. + // + // Otherwise we used to have a strong directionality and either no longer + // does, or it changed. We might need to reset the direction. + if (aOldDir == Directionality::Unset || aTextNode->MaySetDirAuto()) { SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify); } } @@ -1164,17 +789,39 @@ void SetDirectionFromNewTextNode(nsTextNode* aTextNode) { } } -void ResetDirectionSetByTextNode(nsTextNode* aTextNode) { - if (!NodeAffectsDirAutoAncestor(aTextNode)) { - nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode); +void ResetDirectionSetByTextNode(nsTextNode* aTextNode, + dom::UnbindContext& aContext) { + MOZ_ASSERT(!aTextNode->IsInComposedDoc(), "Should be disconnected already"); + if (!aTextNode->MaySetDirAuto()) { + return; + } + auto result = FindDirAutoElementForText(aTextNode); + if (result.mAnswerIsDefinitive) { + // The dir=auto element is in our (now detached) subtree. We're done, as + // nothing really changed for our purposes. + return; + } + MOZ_ASSERT(!result.mElement); + // The dir=auto element might have been on the element we're unbinding from. + // In any case, this text node is clearly no longer what determines its + // directionality. + aTextNode->ClearMaySetDirAuto(); + auto* unboundFrom = + nsIContent::FromNodeOrNull(aContext.GetOriginalSubtreeParent()); + if (!unboundFrom || !TextChildrenAffectDirAutoAncestor(unboundFrom)) { return; } Directionality dir = GetDirectionFromText(aTextNode); - if (dir != Directionality::Unset && - aTextNode->HasTextNodeDirectionalityMap()) { - nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode); + if (dir == Directionality::Unset) { + return; + } + + result = FindDirAutoElementFrom(unboundFrom); + if (!result.mElement || result.mElement->GetDirectionality() != dir) { + return; } + ResetAutoDirection(result.mElement, /* aNotify = */ true); } void SetDirectionalityFromValue(Element* aElement, const nsAString& value, @@ -1192,26 +839,15 @@ void SetDirectionalityFromValue(Element* aElement, const nsAString& value, void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, bool hadValidDir, bool hadDirAuto, bool aNotify) { - if (aElement->IsHTMLElement(nsGkAtoms::input) || - aElement->IsHTMLElement(nsGkAtoms::textarea)) { + if (aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) { return; } if (aElement->AncestorHasDirAuto()) { - if (!hadValidDir) { - // The element is a descendant of an element with dir = auto, is - // having its dir attribute set, and previously didn't have a valid dir - // attribute. - // Check whether any of its text node descendants determine the - // direction of any of its ancestors, and redetermine their direction - WalkDescendantsResetAutoDirection(aElement); - } else if (!aElement->HasValidDir()) { - // The element is a descendant of an element with dir = auto and is - // having its dir attribute removed or set to an invalid value. - // Reset the direction of any of its ancestors whose direction is - // determined by a text node descendant - WalkAncestorsResetAutoDirection(aElement, aNotify); - } + // The element is a descendant of an element with dir = auto, is having its + // dir attribute changed. Reset the direction of any of its ancestors whose + // direction might be determined by a text node descendant + WalkAncestorsResetAutoDirection(aElement, aNotify); } else if (hadDirAuto && !aElement->HasDirAuto()) { // The element isn't a descendant of an element with dir = auto, and is // having its dir attribute set to something other than auto. @@ -1231,11 +867,6 @@ void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, if (aElement->HasDirAuto()) { WalkDescendantsSetDirAuto(aElement, aNotify); } else { - if (aElement->HasDirAutoSet()) { - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } SetDirectionalityOnDescendants( aElement, RecomputeDirectionality(aElement, aNotify), aNotify); } @@ -1244,7 +875,7 @@ void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, void SetDirOnBind(Element* aElement, nsIContent* aParent) { // Set the AncestorHasDirAuto flag, unless this element shouldn't affect // ancestors that have dir=auto - if (!DoesNotParticipateInAutoDirection(aElement) && + if (ParticipatesInAutoDirection(aElement) && !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent && aParent->NodeOrAncestorHasDirAuto()) { aElement->SetAncestorHasDirAuto(); @@ -1265,12 +896,6 @@ void SetDirOnBind(Element* aElement, nsIContent* aParent) { } void ResetDir(Element* aElement) { - if (aElement->HasDirAutoSet()) { - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } - if (!aElement->HasDirAuto()) { RecomputeDirectionality(aElement, false); } diff --git a/dom/base/DirectionalityUtils.h b/dom/base/DirectionalityUtils.h index 17cec80485..0012a1d4be 100644 --- a/dom/base/DirectionalityUtils.h +++ b/dom/base/DirectionalityUtils.h @@ -18,6 +18,7 @@ class nsTextNode; namespace mozilla::dom { class Element; class HTMLSlotElement; +struct UnbindContext; } // namespace mozilla::dom namespace mozilla { @@ -133,10 +134,8 @@ void SetDirectionFromNewTextNode(nsTextNode* aTextNode); /** * When a text node is removed from a document, find any ancestors whose * directionality it determined and redetermine their directionality - * - * @param aTextNode the text node */ -void ResetDirectionSetByTextNode(nsTextNode* aTextNode); +void ResetDirectionSetByTextNode(nsTextNode*, dom::UnbindContext&); /** * Set the directionality of an element according to the directionality of the diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 819cd8c11d..4e9286a91e 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -104,6 +104,7 @@ #include "mozilla/SMILTimeContainer.h" #include "mozilla/ScopeExit.h" #include "mozilla/Components.h" +#include "mozilla/SVGUtils.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/ServoTypes.h" #include "mozilla/SizeOfState.h" @@ -1399,6 +1400,7 @@ Document::Document(const char* aContentType) mShouldResistFingerprinting(false), mCloningForSVGUse(false), mAllowDeclarativeShadowRoots(false), + mSuspendDOMNotifications(false), mXMLDeclarationBits(0), mOnloadBlockCount(0), mWriteLevel(0), @@ -1430,7 +1432,6 @@ Document::Document(const char* aContentType) mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED), mViewportType(Unknown), mViewportFit(ViewportFitType::Auto), - mSubDocuments(nullptr), mHeaderData(nullptr), mServoRestyleRootDirtyBits(0), mThrowOnDynamicMarkupInsertionCounter(0), @@ -2333,7 +2334,6 @@ Document::~Document() { // Kill the subdocument map, doing this will release its strong // references, if any. - delete mSubDocuments; mSubDocuments = nullptr; nsAutoScriptBlocker scriptBlocker; @@ -2505,7 +2505,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise) @@ -2627,7 +2627,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize); NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) @@ -2687,7 +2687,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) tmp->mStyleSheetSetList = nullptr; } - delete tmp->mSubDocuments; tmp->mSubDocuments = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager) @@ -2848,7 +2847,6 @@ void Document::DisconnectNodeTree() { // Delete references to sub-documents and kill the subdocument map, // if any. This is not strictly needed, but makes the node tree // teardown a bit faster. - delete mSubDocuments; mSubDocuments = nullptr; bool oldVal = mInUnlinkOrDeletion; @@ -6319,9 +6317,11 @@ void Document::DeferredContentEditableCountChange(Element* aElement) { if (aElement) { if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) { nsCOMPtr<nsIInlineSpellChecker> spellChecker; - rv = htmlEditor->GetInlineSpellChecker(false, - getter_AddRefs(spellChecker)); - NS_ENSURE_SUCCESS_VOID(rv); + DebugOnly<nsresult> rvIgnored = htmlEditor->GetInlineSpellChecker( + false, getter_AddRefs(spellChecker)); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "EditorBase::GetInlineSpellChecker() failed, but ignored"); if (spellChecker && aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) { @@ -7205,7 +7205,8 @@ nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) { PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry}; - mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry)); + mSubDocuments = + MakeUnique<PLDHashTable>(&hash_table_ops, sizeof(SubDocMapEntry)); } // Add a mapping to the hash table @@ -12394,7 +12395,8 @@ already_AddRefed<nsIURI> Document::ResolvePreloadImage( void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr, ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet, - bool aLinkPreload, uint64_t aEarlyHintPreloaderId) { + bool aLinkPreload, uint64_t aEarlyHintPreloaderId, + const nsAString& aFetchPriority) { nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsContentUtils::CORSModeToLoadImageFlags( Element::StringToCORSMode(aCrossOriginAttr)); @@ -12415,7 +12417,8 @@ void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr, nsresult rv = nsContentUtils::LoadImage( aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo, nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request), - policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId); + policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId, + nsGenericHTMLElement::ToFetchPriority(aFetchPriority)); // Pin image-reference to avoid evicting it from the img-cache before // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and @@ -12428,7 +12431,8 @@ void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr, void Document::MaybePreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr, ReferrerPolicyEnum aReferrerPolicy, - bool aIsImgSet, bool aLinkPreload) { + bool aIsImgSet, bool aLinkPreload, + const nsAString& aFetchPriority) { const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr); if (aLinkPreload) { // Check if the image was already preloaded in this document to avoid @@ -12437,7 +12441,7 @@ void Document::MaybePreLoadImage(nsIURI* aUri, PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode); if (!mPreloadService.PreloadExists(key)) { PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, - aLinkPreload, 0); + aLinkPreload, 0, aFetchPriority); } return; } @@ -12451,7 +12455,7 @@ void Document::MaybePreLoadImage(nsIURI* aUri, // Image not in cache - trigger preload PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload, - 0); + 0, aFetchPriority); } void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) { @@ -14751,6 +14755,10 @@ void Document::TopLayerPush(Element& aElement) { const bool modal = aElement.State().HasState(ElementState::MODAL); TopLayerPop(aElement); + if (nsIFrame* f = aElement.GetPrimaryFrame()) { + f->MarkNeedsDisplayItemRebuild(); + } + mTopLayer.AppendElement(do_GetWeakReference(&aElement)); NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match"); @@ -14804,6 +14812,9 @@ Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) { nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i])); if (element && aPredicate(element)) { removedElement = element; + if (nsIFrame* f = element->GetPrimaryFrame()) { + f->MarkNeedsDisplayItemRebuild(); + } mTopLayer.RemoveElementAt(i); break; } @@ -14818,6 +14829,12 @@ Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) { while (!mTopLayer.IsEmpty()) { Element* element = GetTopLayerTop(); if (!element || element->GetComposedDoc() != this) { + if (element) { + if (nsIFrame* f = element->GetPrimaryFrame()) { + f->MarkNeedsDisplayItemRebuild(); + } + } + mTopLayer.RemoveLastElement(); } else { // The top element of the stack is now an in-doc element. Return here. @@ -16432,27 +16449,79 @@ DOMIntersectionObserver& Document::EnsureLazyLoadObserver() { return *mLazyLoadObserver; } -ResizeObserver& Document::EnsureLastRememberedSizeObserver() { - if (!mLastRememberedSizeObserver) { - mLastRememberedSizeObserver = - ResizeObserver::CreateLastRememberedSizeObserver(*this); - } - return *mLastRememberedSizeObserver; -} - void Document::ObserveForLastRememberedSize(Element& aElement) { if (NS_WARN_IF(!IsActive())) { return; } - // Options are initialized with ResizeObserverBoxOptions::Content_box by - // default, which is what we want. - static ResizeObserverOptions options; - EnsureLastRememberedSizeObserver().Observe(aElement, options); + mElementsObservedForLastRememberedSize.Insert(&aElement); } void Document::UnobserveForLastRememberedSize(Element& aElement) { - if (mLastRememberedSizeObserver) { - mLastRememberedSizeObserver->Unobserve(aElement); + mElementsObservedForLastRememberedSize.Remove(&aElement); +} + +void Document::UpdateLastRememberedSizes() { + auto shouldRemoveElement = [&](auto* element) { + if (element->GetComposedDoc() != this) { + element->RemoveLastRememberedBSize(); + element->RemoveLastRememberedISize(); + return true; + } + return !element->GetPrimaryFrame(); + }; + + for (auto it = mElementsObservedForLastRememberedSize.begin(), + end = mElementsObservedForLastRememberedSize.end(); + it != end; ++it) { + if (shouldRemoveElement(*it)) { + mElementsObservedForLastRememberedSize.Remove(it); + continue; + } + const auto element = *it; + MOZ_ASSERT(element->GetComposedDoc() == this); + nsIFrame* frame = element->GetPrimaryFrame(); + MOZ_ASSERT(frame); + + // As for ResizeObserver, skip nodes hidden `content-visibility`. + if (frame->IsHiddenByContentVisibilityOnAnyAncestor()) { + continue; + } + + MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(), + "Should have unobserved non-replaced inline."); + MOZ_ASSERT(!frame->HidesContent(), + "Should have unobserved element skipping its contents."); + const nsStylePosition* stylePos = frame->StylePosition(); + const WritingMode wm = frame->GetWritingMode(); + bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); + bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); + MOZ_ASSERT(canUpdateBSize || !element->HasLastRememberedBSize(), + "Should have removed the last remembered block size."); + MOZ_ASSERT(canUpdateISize || !element->HasLastRememberedISize(), + "Should have removed the last remembered inline size."); + MOZ_ASSERT(canUpdateBSize || canUpdateISize, + "Should have unobserved if we can't update any size."); + + AutoTArray<LogicalPixelSize, 1> contentSizeList = + ResizeObserver::CalculateBoxSize(element, + ResizeObserverBoxOptions::Content_box, + /* aForceFragmentHandling */ true); + MOZ_ASSERT(!contentSizeList.IsEmpty()); + + if (canUpdateBSize) { + float bSize = 0; + for (const auto& current : contentSizeList) { + bSize += current.BSize(); + } + element->SetLastRememberedBSize(bSize); + } + if (canUpdateISize) { + float iSize = 0; + for (const auto& current : contentSizeList) { + iSize = std::max(iSize, current.ISize()); + } + element->SetLastRememberedISize(iSize); + } } } @@ -17103,13 +17172,7 @@ bool Document::IsExtensionPage() const { void Document::AddResizeObserver(ResizeObserver& aObserver) { MOZ_ASSERT(!mResizeObservers.Contains(&aObserver)); - // Insert internal ResizeObservers before scripted ones, since they may have - // observable side-effects and we don't want to expose the insertion time. - if (aObserver.HasNativeCallback()) { - mResizeObservers.InsertElementAt(0, &aObserver); - } else { - mResizeObservers.AppendElement(&aObserver); - } + mResizeObservers.AppendElement(&aObserver); } void Document::RemoveResizeObserver(ResizeObserver& aObserver) { @@ -17164,6 +17227,18 @@ void Document::DetermineProximityToViewportAndNotifyResizeObservers() { // sub-documents or ancestors, so flushing layout for the whole browsing // context tree makes sure we don't miss anyone. FlushLayoutForWholeBrowsingContextTree(*this); + + // Last remembered sizes are recorded "at the time that ResizeObserver + // events are determined and delivered". + // https://drafts.csswg.org/css-sizing-4/#last-remembered + // + // We do it right after layout to make sure sizes are up-to-date. If we do + // it after determining the proximities to viewport of + // 'content-visibility: auto' nodes, and if one of such node ever becomes + // relevant to the user, then we would be incorrectly recording the size + // of its rendering when it was skipping its content. + UpdateLastRememberedSizes(); + if (PresShell* presShell = GetPresShell()) { auto result = presShell->DetermineProximityToViewport(); if (result.mHadInitialDetermination) { @@ -18627,7 +18702,7 @@ nsIPrincipal* Document::EffectiveStoragePrincipal() const { } // Calling StorageAllowedForDocument will notify the ContentBlockLog. This - // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us + // loads TrackingDBService.sys.mjs, making us potentially // fail // browser/base/content/test/performance/browser_startup.js. To avoid // that, we short-circuit the check here by allowing storage access to system // and addon principles, avoiding the test-failure. diff --git a/dom/base/Document.h b/dom/base/Document.h index 6a2bd55a9c..a52c61addf 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -57,7 +57,6 @@ #include "mozilla/dom/LargestContentfulPaint.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WakeLockBinding.h" -#include "mozilla/glean/GleanMetrics.h" #include "nsAtom.h" #include "nsCOMArray.h" #include "nsCOMPtr.h" @@ -320,6 +319,9 @@ enum BFCacheStatus { }; } // namespace dom +namespace glean::perf { +struct PageLoadExtra; +} } // namespace mozilla namespace mozilla::net { @@ -2930,10 +2932,11 @@ class Document : public nsINode, */ void MaybePreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr, ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet, - bool aLinkPreload); + bool aLinkPreload, const nsAString& aFetchPriority); void PreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr, ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet, - bool aLinkPreload, uint64_t aEarlyHintPreloaderId); + bool aLinkPreload, uint64_t aEarlyHintPreloaderId, + const nsAString& aFetchPriority); /** * Called by images to forget an image preload when they start doing @@ -3722,12 +3725,12 @@ class Document : public nsINode, DOMIntersectionObserver* GetLazyLoadObserver() { return mLazyLoadObserver; } DOMIntersectionObserver& EnsureLazyLoadObserver(); - ResizeObserver* GetLastRememberedSizeObserver() { - return mLastRememberedSizeObserver; + bool HasElementsWithLastRememberedSize() const { + return !mElementsObservedForLastRememberedSize.IsEmpty(); } - ResizeObserver& EnsureLastRememberedSizeObserver(); void ObserveForLastRememberedSize(Element&); void UnobserveForLastRememberedSize(Element&); + void UpdateLastRememberedSizes(); // Dispatch a runnable related to the document. nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const; @@ -3875,6 +3878,17 @@ class Document : public nsINode, void SetAllowDeclarativeShadowRoots(bool aAllowDeclarativeShadowRoots); bool AllowsDeclarativeShadowRoots() const; + void SuspendDOMNotifications() { + MOZ_ASSERT(IsHTMLDocument(), + "Currently suspending DOM notifications is supported only on " + "HTML documents."); + mSuspendDOMNotifications = true; + } + + void ResumeDOMNotifications() { mSuspendDOMNotifications = false; } + + bool DOMNotificationsSuspended() const { return mSuspendDOMNotifications; } + protected: RefPtr<DocumentL10n> mDocumentL10n; @@ -4866,6 +4880,8 @@ class Document : public nsINode, bool mAllowDeclarativeShadowRoots : 1; + bool mSuspendDOMNotifications : 1; + // The fingerprinting protections overrides for this document. The value will // override the default enabled fingerprinting protections for this document. // This will only get populated if these is one that comes from the local @@ -5125,7 +5141,11 @@ class Document : public nsINode, // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor ViewportFitType mViewportFit; - PLDHashTable* mSubDocuments; + // XXXdholbert This should really be modernized to a nsTHashMap or similar, + // though note that the modernization will need to take care to also convert + // the special hash_table_ops logic (e.g. how SubDocClearEntry clears the + // parent document as part of cleaning up an entry in this table). + UniquePtr<PLDHashTable> mSubDocuments; class HeaderData; UniquePtr<HeaderData> mHeaderData; @@ -5162,9 +5182,9 @@ class Document : public nsINode, RefPtr<DOMIntersectionObserver> mLazyLoadObserver; - // ResizeObserver for storing and removing the last remembered size. + // Elements observed for a last remembered size. // @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered} - RefPtr<ResizeObserver> mLastRememberedSizeObserver; + nsTHashSet<RefPtr<Element>> mElementsObservedForLastRememberedSize; // Stack of top layer elements. nsTArray<nsWeakPtr> mTopLayer; diff --git a/dom/base/DocumentFragment.h b/dom/base/DocumentFragment.h index 4b1c11c480..9b8cc69673 100644 --- a/dom/base/DocumentFragment.h +++ b/dom/base/DocumentFragment.h @@ -66,7 +66,7 @@ class DocumentFragment : public FragmentOrElement { return NS_ERROR_NOT_IMPLEMENTED; } - virtual void UnbindFromTree(bool aNullParent) override { + virtual void UnbindFromTree(UnbindContext&) override { NS_ASSERTION(false, "Trying to unbind a fragment from a tree"); } diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index f653363f48..be31000278 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -54,7 +54,6 @@ #include "mozilla/PresShellForwards.h" #include "mozilla/ReflowOutput.h" #include "mozilla/RelativeTo.h" -#include "mozilla/ScrollOrigin.h" #include "mozilla/ScrollTypes.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/ServoStyleConstsInlines.h" @@ -106,6 +105,7 @@ #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/Text.h" +#include "mozilla/dom/UnbindContext.h" #include "mozilla/dom/WindowBinding.h" #include "mozilla/dom/XULCommandEvent.h" #include "mozilla/dom/nsCSPContext.h" @@ -113,6 +113,7 @@ #include "mozilla/gfx/BaseRect.h" #include "mozilla/gfx/BaseSize.h" #include "mozilla/gfx/Matrix.h" +#include "mozilla/widget/Screen.h" #include "nsAtom.h" #include "nsAttrName.h" #include "nsAttrValueInlines.h" @@ -161,7 +162,6 @@ #include "nsIInterfaceRequestor.h" #include "nsIMemoryReporter.h" #include "nsIPrincipal.h" -#include "nsIScreenManager.h" #include "nsIScriptError.h" #include "nsIScrollableFrame.h" #include "nsISpeculativeConnect.h" @@ -780,8 +780,6 @@ void Element::ScrollIntoView(const ScrollIntoViewOptions& aOptions) { return WhereToScroll::Center; case ScrollLogicalPosition::End: return WhereToScroll::End; - case ScrollLogicalPosition::EndGuard_: - MOZ_FALLTHROUGH_ASSERT("Unexpected block direction value"); case ScrollLogicalPosition::Nearest: break; } @@ -1048,20 +1046,13 @@ int32_t Element::ScreenY() { } already_AddRefed<nsIScreen> Element::GetScreen() { - nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); - if (!frame) { - return nullptr; + // Flush layout to guarantee that frames are created if needed, and preserve + // behavior. + Unused << GetPrimaryFrame(FlushType::Frames); + if (nsIWidget* widget = nsContentUtils::WidgetForContent(this)) { + return widget->GetWidgetScreen(); } - nsCOMPtr<nsIScreenManager> screenMgr = - do_GetService("@mozilla.org/gfx/screenmanager;1"); - if (!screenMgr) { - return nullptr; - } - nsPresContext* pc = frame->PresContext(); - const CSSIntRect rect = frame->GetScreenRect(); - DesktopRect desktopRect = rect * pc->CSSToDevPixelScale() / - pc->DeviceContext()->GetDesktopToDeviceScale(); - return screenMgr->ScreenForRect(DesktopIntRect::Round(desktopRect)); + return nullptr; } already_AddRefed<DOMRect> Element::GetBoundingClientRect() { @@ -1722,8 +1713,7 @@ already_AddRefed<nsIHTMLCollection> Element::GetElementsByClassName( } Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const { - const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); - if (slots) { + if (const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) { nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElements.Get(aAttr); if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) { // If reflectedTarget's explicitly set attr-element |attrEl| is @@ -1774,16 +1764,56 @@ void Element::ClearExplicitlySetAttrElement(nsAtom* aAttr) { } void Element::ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement) { +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = GetAccService(); +#endif + // Accessibility requires that no other attribute changes occur between + // AttrElementWillChange and AttrElementChanged. Scripts could cause + // this, so don't let them run here. We do this even if accessibility isn't + // running so that the JS behavior is consistent regardless of accessibility. + // Otherwise, JS might be able to use this difference to determine whether + // accessibility is running, which would be a privacy concern. + nsAutoScriptBlocker scriptBlocker; if (aElement) { +#ifdef ACCESSIBILITY + if (accService) { + accService->NotifyAttrElementWillChange(this, aAttr); + } +#endif SetAttr(aAttr, EmptyString(), IgnoreErrors()); nsExtendedDOMSlots* slots = ExtendedDOMSlots(); slots->mExplicitlySetAttrElements.InsertOrUpdate( aAttr, do_GetWeakReference(aElement)); +#ifdef ACCESSIBILITY + if (accService) { + accService->NotifyAttrElementChanged(this, aAttr); + } +#endif return; } +#ifdef ACCESSIBILITY + if (accService) { + accService->NotifyAttrElementWillChange(this, aAttr); + } +#endif ClearExplicitlySetAttrElement(aAttr); UnsetAttr(aAttr, IgnoreErrors()); +#ifdef ACCESSIBILITY + if (accService) { + accService->NotifyAttrElementChanged(this, aAttr); + } +#endif +} + +Element* Element::GetExplicitlySetAttrElement(nsAtom* aAttr) const { + if (const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) { + nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElements.Get(aAttr); + if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) { + return attrEl; + } + } + return nullptr; } void Element::GetElementsWithGrid(nsTArray<RefPtr<Element>>& aElements) { @@ -1958,7 +1988,8 @@ nsresult Element::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -bool WillDetachFromShadowOnUnbind(const Element& aElement, bool aNullParent) { +static bool WillDetachFromShadowOnUnbind(const Element& aElement, + bool aNullParent) { // If our parent still is in a shadow tree by now, and we're not removing // ourselves from it, then we're still going to be in a shadow tree after // this. @@ -1966,12 +1997,14 @@ bool WillDetachFromShadowOnUnbind(const Element& aElement, bool aNullParent) { (aNullParent || !aElement.GetParent()->IsInShadowTree()); } -void Element::UnbindFromTree(bool aNullParent) { - HandleShadowDOMRelatedRemovalSteps(aNullParent); +void Element::UnbindFromTree(UnbindContext& aContext) { + const bool nullParent = aContext.IsUnbindRoot(this); + + HandleShadowDOMRelatedRemovalSteps(nullParent); if (HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) && !IsHTMLElement(nsGkAtoms::datalist)) { - if (aNullParent) { + if (nullParent) { UnsetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR); } else { nsIContent* parent = GetParent(); @@ -1983,7 +2016,7 @@ void Element::UnbindFromTree(bool aNullParent) { } const bool detachingFromShadow = - WillDetachFromShadowOnUnbind(*this, aNullParent); + WillDetachFromShadowOnUnbind(*this, nullParent); // Make sure to only remove from the ID table if our subtree root is actually // changing. if (IsInUncomposedDoc() || detachingFromShadow) { @@ -2033,7 +2066,7 @@ void Element::UnbindFromTree(bool aNullParent) { data->ClearAllAnimationCollections(); } - if (aNullParent) { + if (nullParent) { if (GetParent()) { RefPtr<nsINode> p; p.swap(mParent); @@ -2071,15 +2104,13 @@ void Element::UnbindFromTree(bool aNullParent) { ClearElementCreatedFromPrototypeAndHasUnmodifiedL10n(); } - if (aNullParent || !mParent->IsInShadowTree()) { + if (nullParent || !mParent->IsInShadowTree()) { UnsetFlags(NODE_IS_IN_SHADOW_TREE); // Begin keeping track of our subtree root. - SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot()); - } + SetSubtreeRootPointer(nullParent ? this : mParent->SubtreeRoot()); - if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) { - if (aNullParent || !mParent->IsInShadowTree()) { + if (nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) { slots->mContainingShadow = nullptr; } } @@ -2103,11 +2134,10 @@ void Element::UnbindFromTree(bool aNullParent) { } if (HasLastRememberedBSize() || HasLastRememberedISize()) { - // Need to remove the last remembered size at the next ResizeObserver - // opportunity, so observe the element. But if already observed, we still - // want the callback to be invoked even if the size was already 0x0, so - // unobserve it first. - document->UnobserveForLastRememberedSize(*this); + // Make sure the element is observed so that remembered sizes are kept + // until the next time "ResizeObserver events are determined and + // delivered". See "Disconnected element" tests from + // css/css-sizing/contain-intrinsic-size/auto-006.html document->ObserveForLastRememberedSize(*this); } } @@ -2121,9 +2151,7 @@ void Element::UnbindFromTree(bool aNullParent) { for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { - // Note that we pass false for aNullParent here, since we don't want - // the kids to forget us. - child->UnbindFromTree(false); + child->UnbindFromTree(aContext); } MutationObservers::NotifyParentChainChanged(this); @@ -2751,6 +2779,12 @@ bool Element::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, return true; } + if (aAttribute == nsGkAtoms::aria_activedescendant) { + // String in aria-activedescendant is an id, so store as an atom. + aResult.ParseAtom(aValue); + return true; + } + if (aAttribute == nsGkAtoms::id) { // Store id as an atom. id="" means that the element has no id, // not that it has an emptystring as the id. @@ -2805,6 +2839,8 @@ void Element::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, if (ShadowRoot* shadow = GetParent()->GetShadowRoot()) { shadow->MaybeReassignContent(*this); } + } else if (aName == nsGkAtoms::aria_activedescendant) { + ClearExplicitlySetAttrElement(aName); } } } @@ -2856,6 +2892,11 @@ void Element::OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName, ElementCallbackType::eAttributeChanged, this, args, definition); } } + + if (aNamespaceID == kNameSpaceID_None && + aName == nsGkAtoms::aria_activedescendant) { + ClearExplicitlySetAttrElement(aName); + } } EventListenerManager* Element::GetEventListenerManagerForAttr(nsAtom* aAttrName, @@ -3397,14 +3438,6 @@ nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) { void Element::GetLinkTarget(nsAString& aTarget) { aTarget.Truncate(); } -static nsStaticAtom* const sPropertiesToTraverseAndUnlink[] = { - nsGkAtoms::dirAutoSetBy, nullptr}; - -// static -nsStaticAtom* const* Element::HTMLSVGPropertiesToTraverseAndUnlink() { - return sPropertiesToTraverseAndUnlink; -} - nsresult Element::CopyInnerTo(Element* aDst, ReparseAttributes aReparse) { nsresult rv = aDst->mAttrs.EnsureCapacityToClone(mAttrs); NS_ENSURE_SUCCESS(rv, rv); @@ -5048,4 +5081,14 @@ void Element::SetHTMLUnsafe(const nsAString& aHTML) { nsContentUtils::SetHTMLUnsafe(this, this, aHTML); } +bool Element::BlockingContainsRender() const { + const nsAttrValue* attrValue = GetParsedAttr(nsGkAtoms::blocking); + if (!attrValue || !StaticPrefs::dom_element_blocking_enabled()) { + return false; + } + MOZ_ASSERT(attrValue->Type() == nsAttrValue::eAtomArray, + "Checking blocking attribute on element that doesn't parse it?"); + return attrValue->Contains(nsGkAtoms::render, eIgnoreCase); +} + } // namespace mozilla::dom diff --git a/dom/base/Element.h b/dom/base/Element.h index 6c717bfc88..40a8052aef 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -90,7 +90,6 @@ class nsIDOMXULSelectControlElement; class nsIDOMXULSelectControlItemElement; class nsIFrame; class nsIHTMLCollection; -class nsIMozBrowserFrame; class nsIPrincipal; class nsIScreen; class nsIScrollableFrame; @@ -244,6 +243,15 @@ class Grid; SetOrRemoveNullableStringAttr(nsGkAtoms::attr, aValue, aRv); \ } +#define REFLECT_NULLABLE_ELEMENT_ATTR(method, attr) \ + Element* Get##method() const { \ + return GetAttrAssociatedElement(nsGkAtoms::attr); \ + } \ + \ + void Set##method(Element* aElement) { \ + ExplicitlySetAttrElement(nsGkAtoms::attr, aElement); \ + } + class Element : public FragmentOrElement { public: #ifdef MOZILLA_INTERNAL_API @@ -459,22 +467,13 @@ class Element : public FragmentOrElement { virtual bool IsInteractiveHTMLContent() const; /** - * Returns |this| as an nsIMozBrowserFrame* if the element is a frame or - * iframe element. - * - * We have this method, rather than using QI, so that we can use it during - * the servo traversal, where we can't QI DOM nodes because of non-thread-safe - * refcounts. - */ - virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() { return nullptr; } - - /** * Is the attribute named aAttribute a mapped attribute? */ NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; + using nsIContent::UnbindFromTree; virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const; static void MapNoAttributesInto(mozilla::MappedDeclarationsBuilder&); @@ -658,8 +657,13 @@ class Element : public FragmentOrElement { REFLECT_NULLABLE_DOMSTRING_ATTR(Role, role) // AriaAttributes + REFLECT_NULLABLE_ELEMENT_ATTR(AriaActiveDescendantElement, + aria_activedescendant) REFLECT_NULLABLE_DOMSTRING_ATTR(AriaAtomic, aria_atomic) REFLECT_NULLABLE_DOMSTRING_ATTR(AriaAutoComplete, aria_autocomplete) + REFLECT_NULLABLE_DOMSTRING_ATTR(AriaBrailleLabel, aria_braillelabel) + REFLECT_NULLABLE_DOMSTRING_ATTR(AriaBrailleRoleDescription, + aria_brailleroledescription) REFLECT_NULLABLE_DOMSTRING_ATTR(AriaBusy, aria_busy) REFLECT_NULLABLE_DOMSTRING_ATTR(AriaChecked, aria_checked) REFLECT_NULLABLE_DOMSTRING_ATTR(AriaColCount, aria_colcount) @@ -1107,8 +1111,6 @@ class Element : public FragmentOrElement { return FindAttributeDependence(aAttribute, aMaps, N); } - static nsStaticAtom* const* HTMLSVGPropertiesToTraverseAndUnlink(); - MOZ_CAN_RUN_SCRIPT virtual void HandleInvokeInternal(nsAtom* aAction, ErrorResult& aRv) {} @@ -1245,6 +1247,16 @@ class Element : public FragmentOrElement { void ClearExplicitlySetAttrElement(nsAtom*); + /** + * Gets the attribute element for the given attribute. + * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element + * Unlike GetAttrAssociatedElement, this returns the target even if it isn't + * a descendant of any of this element's shadow-including ancestors. It also + * doesn't attempt to retrieve an element using a string id set in the content + * attribute. + */ + Element* GetExplicitlySetAttrElement(nsAtom* aAttr) const; + PseudoStyleType GetPseudoElementType() const { nsresult rv = NS_OK; auto raw = GetProperty(nsGkAtoms::pseudoProperty, &rv); @@ -1332,6 +1344,10 @@ class Element : public FragmentOrElement { void GetLoading(nsAString& aValue) const; bool ParseLoadingAttribute(const nsAString& aValue, nsAttrValue& aResult); + // https://html.spec.whatwg.org/#potentially-render-blocking + virtual bool IsPotentiallyRenderBlocking() { return false; } + bool BlockingContainsRender() const; + // Shadow DOM v1 enum class ShadowRootDeclarative : bool { No, Yes }; diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp index 52cb13f097..f70db487dd 100644 --- a/dom/base/EventSource.cpp +++ b/dom/base/EventSource.cpp @@ -849,7 +849,8 @@ EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { aStatusCode != NS_ERROR_NET_PARTIAL_TRANSFER && aStatusCode != NS_ERROR_NET_TIMEOUT_EXTERNAL && aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED && - aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) { + aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL && + aStatusCode != NS_ERROR_INVALID_CONTENT_ENCODING) { DispatchFailConnection(); return NS_ERROR_ABORT; } diff --git a/dom/base/FlushType.h b/dom/base/FlushType.h index 04a253c534..c90cbbd16f 100644 --- a/dom/base/FlushType.h +++ b/dom/base/FlushType.h @@ -40,7 +40,7 @@ enum class FlushType : uint8_t { // Flush type strings that will be displayed in the profiler // clang-format off -const EnumeratedArray<FlushType, FlushType::Count, const char*> +const EnumeratedArray<FlushType, const char*, size_t(FlushType::Count)> kFlushTypeNames = { "", "Event", diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 9811ce5ace..87fd81bfa3 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -16,11 +16,11 @@ #include "mozilla/dom/FragmentOrElement.h" #include "DOMIntersectionObserver.h" #include "mozilla/AsyncEventDispatcher.h" -#include "mozilla/DeclarationBlock.h" #include "mozilla/EffectSet.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/ElementAnimationData.h" +#include "mozilla/DeclarationBlock.h" #include "mozilla/HTMLEditor.h" #include "mozilla/mozInlineSpellChecker.h" #include "mozilla/PresShell.h" @@ -30,39 +30,31 @@ #include "mozilla/URLExtraData.h" #include "mozilla/dom/Attr.h" #include "mozilla/dom/RadioGroupContainer.h" +#include "mozilla/dom/UnbindContext.h" #include "nsDOMAttributeMap.h" #include "nsAtom.h" #include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/ScriptLoader.h" -#include "mozilla/dom/TouchEvent.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "nsIControllers.h" #include "nsIDocumentEncoder.h" #include "nsFocusManager.h" -#include "nsIScriptGlobalObject.h" #include "nsNetUtil.h" #include "nsIFrame.h" #include "nsIAnonymousContentCreator.h" #include "nsPresContext.h" -#include "nsStyleConsts.h" #include "nsString.h" -#include "nsUnicharUtils.h" -#include "nsDOMCID.h" #include "nsDOMCSSAttrDeclaration.h" #include "nsNameSpaceManager.h" #include "nsContentList.h" #include "nsDOMTokenList.h" #include "nsError.h" -#include "nsDOMString.h" #include "nsXULElement.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/MouseEvents.h" -#include "nsAttrValueOrString.h" -#include "nsQueryObject.h" -#include "nsFrameSelection.h" #ifdef DEBUG # include "nsRange.h" #endif @@ -73,7 +65,6 @@ #include "nsGkAtoms.h" #include "nsContentUtils.h" #include "nsTextFragment.h" -#include "nsContentCID.h" #include "nsWindowSizes.h" #include "nsIWidget.h" @@ -82,12 +73,10 @@ #include "nsGenericHTMLElement.h" #include "nsContentCreatorFunctions.h" #include "nsView.h" -#include "nsViewManager.h" #include "nsIScrollableFrame.h" #include "ChildIterator.h" -#include "nsTextNode.h" #include "mozilla/dom/NodeListBinding.h" - +#include "mozilla/dom/MutationObservers.h" #include "nsCCUncollectableMarker.h" #include "mozAutoDocUpdate.h" @@ -97,16 +86,12 @@ #include "nsWrapperCacheInlines.h" #include "nsCycleCollector.h" #include "xpcpublic.h" -#include "mozilla/Telemetry.h" - -#include "mozilla/CORSMode.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/SVGUseElement.h" -#include "nsStyledElement.h" #include "nsIContentInlines.h" #include "nsChildContentList.h" #include "mozilla/BloomFilter.h" @@ -169,6 +154,11 @@ nsIContent* nsIContent::FindFirstNonChromeOnlyAccessContent() const { return nullptr; } +void nsIContent::UnbindFromTree() { + UnbindContext context(*this); + UnbindFromTree(context); +} + // https://dom.spec.whatwg.org/#dom-slotable-assignedslot HTMLSlotElement* nsIContent::GetAssignedSlotByMode() const { /** @@ -1337,14 +1327,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement) Element* elem = tmp->AsElement(); elem->UnlinkIntersectionObservers(); } - - if (tmp->IsHTMLElement() || tmp->IsSVGElement()) { - nsStaticAtom* const* props = - Element::HTMLSVGPropertiesToTraverseAndUnlink(); - for (uint32_t i = 0; props[i]; ++i) { - tmp->RemoveProperty(props[i]); - } - } } // Unlink child content (and unbind our subtree). @@ -1815,15 +1797,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement) } } } - if (tmp->IsHTMLElement() || tmp->IsSVGElement()) { - nsStaticAtom* const* props = - Element::HTMLSVGPropertiesToTraverseAndUnlink(); - for (uint32_t i = 0; props[i]; ++i) { - nsISupports* property = - static_cast<nsISupports*>(tmp->GetProperty(props[i])); - cb.NoteXPCOMChild(property); - } - } } if (tmp->IsElement()) { Element* element = tmp->AsElement(); @@ -2029,6 +2002,7 @@ void FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, } if (doc->IsHTMLDocument()) { + doc->SuspendDOMNotifications(); nsAtom* contextLocalName = parseContext->NodeInfo()->NameAtom(); int32_t contextNameSpaceID = parseContext->GetNameSpaceID(); @@ -2036,6 +2010,10 @@ void FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, aError = nsContentUtils::ParseFragmentHTML( aInnerHTML, target, contextLocalName, contextNameSpaceID, doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true); + doc->ResumeDOMNotifications(); + if (target->GetFirstChild()) { + MutationObservers::NotifyContentAppended(target, target->GetFirstChild()); + } mb.NodesAdded(); // HTML5 parser has notified, but not fired mutation events. nsContentUtils::FireMutationEventsForDirectParsing(doc, target, diff --git a/dom/base/IDTracker.cpp b/dom/base/IDTracker.cpp index 4db592534d..813aa44e11 100644 --- a/dom/base/IDTracker.cpp +++ b/dom/base/IDTracker.cpp @@ -132,13 +132,30 @@ void IDTracker::ResetWithLocalRef(Element& aFrom, const nsAString& aLocalRef, bool aWatch) { MOZ_ASSERT(nsContentUtils::IsLocalRefURL(aLocalRef)); - nsAutoCString ref; - if (!AppendUTF16toUTF8(Substring(aLocalRef, 1), ref, mozilla::fallible)) { + auto ref = Substring(aLocalRef, 1); + if (ref.IsEmpty()) { Unlink(); return; } - NS_UnescapeURL(ref); - RefPtr<nsAtom> idAtom = NS_Atomize(ref); + + nsAutoCString utf8Ref; + if (!AppendUTF16toUTF8(ref, utf8Ref, mozilla::fallible)) { + Unlink(); + return; + } + + // Only unescape ASCII characters; if we were to unescape arbitrary bytes, + // we'd potentially end up with invalid UTF-8. + nsAutoCString unescaped; + bool appended; + if (NS_FAILED(NS_UnescapeURL(utf8Ref.BeginReading(), utf8Ref.Length(), + esc_OnlyASCII | esc_AlwaysCopy, unescaped, + appended, mozilla::fallible))) { + Unlink(); + return; + } + + RefPtr<nsAtom> idAtom = NS_Atomize(unescaped); ResetWithID(aFrom, idAtom, aWatch); } diff --git a/dom/base/InProcessBrowserChildMessageManager.cpp b/dom/base/InProcessBrowserChildMessageManager.cpp index 191ec9f7b5..830c57591b 100644 --- a/dom/base/InProcessBrowserChildMessageManager.cpp +++ b/dom/base/InProcessBrowserChildMessageManager.cpp @@ -13,7 +13,6 @@ #include "nsFrameLoaderOwner.h" #include "nsQueryObject.h" #include "xpcpublic.h" -#include "nsIMozBrowserFrame.h" #include "mozilla/EventDispatcher.h" #include "mozilla/dom/ChromeMessageSender.h" #include "mozilla/dom/Document.h" @@ -100,15 +99,6 @@ InProcessBrowserChildMessageManager::InProcessBrowserChildMessageManager( mOwner(aOwner), mChromeMessageManager(aChrome) { mozilla::HoldJSObjects(this); - - // If owner corresponds to an <iframe mozbrowser>, we'll have to tweak our - // GetEventTargetParent implementation. - nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner); - if (browserFrame) { - mIsBrowserFrame = browserFrame->GetReallyIsBrowser(); - } else { - mIsBrowserFrame = false; - } } InProcessBrowserChildMessageManager::~InProcessBrowserChildMessageManager() { @@ -236,19 +226,7 @@ void InProcessBrowserChildMessageManager::GetEventTargetParent( return; } - if (mIsBrowserFrame && - (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) { - if (mOwner) { - if (nsPIDOMWindowInner* innerWindow = - mOwner->OwnerDoc()->GetInnerWindow()) { - // 'this' is already a "chrome handler", so we consider window's - // parent target to be part of that same part of the event path. - aVisitor.SetParentTarget(innerWindow->GetParentTarget(), false); - } - } - } else { - aVisitor.SetParentTarget(mOwner, false); - } + aVisitor.SetParentTarget(mOwner, false); } class nsAsyncScriptLoad : public Runnable { diff --git a/dom/base/InProcessBrowserChildMessageManager.h b/dom/base/InProcessBrowserChildMessageManager.h index b12e84290f..4064a6d65b 100644 --- a/dom/base/InProcessBrowserChildMessageManager.h +++ b/dom/base/InProcessBrowserChildMessageManager.h @@ -108,9 +108,6 @@ class InProcessBrowserChildMessageManager final RefPtr<nsDocShell> mDocShell; bool mLoadingScript; - // Is this the message manager for an in-process <iframe mozbrowser>? This - // affects where events get sent, so it affects GetEventTargetParent. - bool mIsBrowserFrame; bool mPreventEventsEscaping; // We keep a strong reference to the frameloader after we've started diff --git a/dom/base/IndexedDBHelper.sys.mjs b/dom/base/IndexedDBHelper.sys.mjs index a36b211c0a..a9a6b39786 100644 --- a/dom/base/IndexedDBHelper.sys.mjs +++ b/dom/base/IndexedDBHelper.sys.mjs @@ -9,7 +9,7 @@ if (DEBUG) { dump("-*- IndexedDBHelper: " + s + "\n"); }; } else { - debug = function (s) {}; + debug = function () {}; } function getErrorName(err) { @@ -70,7 +70,7 @@ IndexedDBHelper.prototype = { debug("Opened database:" + self.dbName + " " + self.dbVersion); } self._db = event.target.result; - self._db.onversionchange = function (event) { + self._db.onversionchange = function () { if (DEBUG) { debug("WARNING: DB modified from a different window."); } @@ -106,7 +106,7 @@ IndexedDBHelper.prototype = { } invokeCallbacks(getErrorName(aEvent.target.error)); }; - req.onblocked = function (aEvent) { + req.onblocked = function () { if (DEBUG) { debug("Opening database request is blocked."); } diff --git a/dom/base/MimeType.cpp b/dom/base/MimeType.cpp index c5869b1d8a..da61489c1f 100644 --- a/dom/base/MimeType.cpp +++ b/dom/base/MimeType.cpp @@ -9,8 +9,8 @@ #include "nsUnicharUtils.h" template <typename char_type> -/* static */ mozilla::UniquePtr<TMimeType<char_type>> -TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) { +/* static */ RefPtr<TMimeType<char_type>> TMimeType<char_type>::Parse( + const nsTSubstring<char_type>& aMimeType) { // See https://mimesniff.spec.whatwg.org/#parsing-a-mime-type // Steps 1-2 @@ -85,8 +85,8 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) { for (const char_type* c = subtypeStart; c < subtypeEnd; ++c) { subtype.Append(ToLowerCaseASCII(*c)); } - mozilla::UniquePtr<TMimeType<char_type>> mimeType( - mozilla::MakeUnique<TMimeType<char_type>>(type, subtype)); + RefPtr<TMimeType<char_type>> mimeType = + new TMimeType<char_type>(type, subtype); // Step 11 while (pos < end) { @@ -274,7 +274,7 @@ template <typename char_type> static char_type kCHARSET[] = {'c', 'h', 'a', 'r', 's', 'e', 't'}; static nsTDependentSubstring<char_type> kCharset(kCHARSET, 7); - mozilla::UniquePtr<TMimeType<char_type>> parsed; + RefPtr<TMimeType<char_type>> parsed; nsTAutoString<char_type> prevContentType; nsTAutoString<char_type> prevCharset; @@ -398,9 +398,9 @@ void TMimeType<char_type>::SetParameterValue( }); } -template mozilla::UniquePtr<TMimeType<char16_t>> TMimeType<char16_t>::Parse( +template RefPtr<TMimeType<char16_t>> TMimeType<char16_t>::Parse( const nsTSubstring<char16_t>& aMimeType); -template mozilla::UniquePtr<TMimeType<char>> TMimeType<char>::Parse( +template RefPtr<TMimeType<char>> TMimeType<char>::Parse( const nsTSubstring<char>& aMimeType); template bool TMimeType<char16_t>::Parse( const nsTSubstring<char16_t>& aMimeType, diff --git a/dom/base/MimeType.h b/dom/base/MimeType.h index 8c980c7f63..a8b23dfa9b 100644 --- a/dom/base/MimeType.h +++ b/dom/base/MimeType.h @@ -8,7 +8,6 @@ #define mozilla_dom_MimeType_h #include "mozilla/TextUtils.h" -#include "mozilla/UniquePtr.h" #include "nsTHashMap.h" #include "nsTArray.h" @@ -26,6 +25,8 @@ struct HashKeyType<char> { template <typename char_type> class TMimeType final { private: + ~TMimeType() = default; + class ParameterValue : public nsTString<char_type> { public: bool mRequiresQuoting; @@ -48,8 +49,8 @@ class TMimeType final { const nsTSubstring<char_type>& aSubtype) : mType(aType), mSubtype(aSubtype) {} - static mozilla::UniquePtr<TMimeType<char_type>> Parse( - const nsTSubstring<char_type>& aStr); + static RefPtr<TMimeType<char_type>> Parse( + const nsTSubstring<char_type>& aMimeType); // @param aMimeType - the mimetype string // @param aOutEssence - will hold the value of the content-type @@ -84,6 +85,8 @@ class TMimeType final { // @param aValue - the value of the parameter void SetParameterValue(const nsTSubstring<char_type>& aName, const nsTSubstring<char_type>& aValue); + + NS_INLINE_DECL_REFCOUNTING(TMimeType) }; using MimeType = TMimeType<char16_t>; diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index d4b04f8092..14a00b8ed8 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -171,6 +171,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharePromise) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXRSystem) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClipboard) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END void Navigator::Invalidate() { @@ -254,6 +255,8 @@ void Navigator::Invalidate() { mSharePromise = nullptr; mWakeLock = nullptr; + + mClipboard = nullptr; } void Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType, diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 998328cfc6..e559dc4d6a 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -39,7 +39,6 @@ class MediaDevices; struct MediaStreamConstraints; class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; class ServiceWorkerContainer; -class DOMRequest; class CredentialsContainer; class Clipboard; class LockManager; diff --git a/dom/base/RadioGroupContainer.cpp b/dom/base/RadioGroupContainer.cpp index 1d5abb78c6..3bb4d7da20 100644 --- a/dom/base/RadioGroupContainer.cpp +++ b/dom/base/RadioGroupContainer.cpp @@ -131,6 +131,17 @@ nsresult RadioGroupContainer::GetNextRadioButton( return NS_OK; } +HTMLInputElement* RadioGroupContainer::GetFirstRadioButton( + const nsAString& aName) { + nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); + for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsList()) { + if (!radio->Disabled()) { + return radio; + } + } + return nullptr; +} + void RadioGroupContainer::AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio, nsIContent* aAncestor) { diff --git a/dom/base/RadioGroupContainer.h b/dom/base/RadioGroupContainer.h index 0c920c1c09..6331c71dd9 100644 --- a/dom/base/RadioGroupContainer.h +++ b/dom/base/RadioGroupContainer.h @@ -32,6 +32,7 @@ class RadioGroupContainer final { nsresult GetNextRadioButton(const nsAString& aName, const bool aPrevious, HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut); + HTMLInputElement* GetFirstRadioButton(const nsAString& aName); void AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio, nsIContent* aAncestor); void RemoveFromRadioGroup(const nsAString& aName, HTMLInputElement* aRadio); diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp index 77472179c8..4a8ffd4c76 100644 --- a/dom/base/ResizeObserver.cpp +++ b/dom/base/ResizeObserver.cpp @@ -66,16 +66,9 @@ static nsSize GetContentRectSize(const nsIFrame& aFrame) { return aFrame.GetContentRectRelativeToSelf().Size(); } -/** - * Returns |aTarget|'s size in the form of gfx::Size (in pixels). - * If the target is an SVG that does not participate in CSS layout, - * its width and height are determined from bounding box. - * - * https://www.w3.org/TR/resize-observer-1/#calculate-box-size - */ -static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize( +AutoTArray<LogicalPixelSize, 1> ResizeObserver::CalculateBoxSize( Element* aTarget, ResizeObserverBoxOptions aBox, - const ResizeObserver& aObserver) { + bool aForceFragmentHandling) { nsIFrame* frame = aTarget->GetPrimaryFrame(); if (!frame) { @@ -158,7 +151,7 @@ static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize( return CSSPixel::FromAppUnits(GetContentRectSize(*aFrame)).ToUnknownSize(); }; if (!StaticPrefs::dom_resize_observer_support_fragments() && - !aObserver.HasNativeCallback()) { + !aForceFragmentHandling) { return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))}; } AutoTArray<LogicalPixelSize, 1> size; @@ -209,7 +202,7 @@ bool ResizeObservation::IsActive() const { } return mLastReportedSize != - CalculateBoxSize(mTarget, mObservedBox, *mObserver); + ResizeObserver::CalculateBoxSize(mTarget, mObservedBox); } void ResizeObservation::UpdateLastReportedSize( @@ -221,23 +214,14 @@ void ResizeObservation::UpdateLastReportedSize( NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ResizeObserver) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner, mDocument, mActiveTargets, - mObservationMap); - if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) { - ImplCycleCollectionTraverse( - cb, tmp->mCallback.as<RefPtr<ResizeObserverCallback>>(), "mCallback", - 0); - } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner, mDocument, mCallback, + mActiveTargets, mObservationMap); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ResizeObserver) tmp->Disconnect(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner, mDocument, mActiveTargets, + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner, mDocument, mCallback, mActiveTargets, mObservationMap); - if (tmp->mCallback.is<RefPtr<ResizeObserverCallback>>()) { - ImplCycleCollectionUnlink( - tmp->mCallback.as<RefPtr<ResizeObserverCallback>>()); - } NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -248,14 +232,6 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -ResizeObserver::ResizeObserver(Document& aDocument, NativeCallback aCallback) - : mOwner(aDocument.GetInnerWindow()), - mDocument(&aDocument), - mCallback(aCallback) { - MOZ_ASSERT(mOwner, "Need a non-null owner window"); - MOZ_ASSERT(mDocument == mOwner->GetExtantDoc()); -} - already_AddRefed<ResizeObserver> ResizeObserver::Constructor( const GlobalObject& aGlobal, ResizeObserverCallback& aCb, ErrorResult& aRv) { @@ -361,12 +337,7 @@ void ResizeObserver::GatherActiveObservations(uint32_t aDepth) { if (targetDepth > aDepth) { mActiveTargets.AppendElement(observation); } else { - // This boolean is only used to indicate we will deliver resize loop error - // notification later on. However, we don't want to do that for our - // internal observers. - if (!HasNativeCallback()) { - mHasSkippedTargets = true; - } + mHasSkippedTargets = true; } } } @@ -383,12 +354,12 @@ uint32_t ResizeObserver::BroadcastActiveObservations() { for (auto& observation : mActiveTargets) { Element* target = observation->Target(); - auto borderBoxSize = - CalculateBoxSize(target, ResizeObserverBoxOptions::Border_box, *this); - auto contentBoxSize = - CalculateBoxSize(target, ResizeObserverBoxOptions::Content_box, *this); - auto devicePixelContentBoxSize = CalculateBoxSize( - target, ResizeObserverBoxOptions::Device_pixel_content_box, *this); + auto borderBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Border_box); + auto contentBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Content_box); + auto devicePixelContentBoxSize = ResizeObserver::CalculateBoxSize( + target, ResizeObserverBoxOptions::Device_pixel_content_box); RefPtr<ResizeObserverEntry> entry = new ResizeObserverEntry(mOwner, *target, borderBoxSize, contentBoxSize, devicePixelContentBoxSize); @@ -419,13 +390,8 @@ uint32_t ResizeObserver::BroadcastActiveObservations() { } } - if (mCallback.is<RefPtr<ResizeObserverCallback>>()) { - RefPtr<ResizeObserverCallback> callback( - mCallback.as<RefPtr<ResizeObserverCallback>>()); - callback->Call(this, entries, *this); - } else { - mCallback.as<NativeCallback>()(entries, *this); - } + RefPtr<ResizeObserverCallback> callback(mCallback); + callback->Call(this, entries, *this); mActiveTargets.Clear(); mHasSkippedTargets = false; @@ -524,61 +490,6 @@ void ResizeObserverEntry::SetDevicePixelContentSize( } } -static void LastRememberedSizeCallback( - const Sequence<OwningNonNull<ResizeObserverEntry>>& aEntries, - ResizeObserver& aObserver) { - for (const auto& entry : aEntries) { - Element* target = entry->Target(); - if (!target->IsInComposedDoc()) { - aObserver.Unobserve(*target); - target->RemoveLastRememberedBSize(); - target->RemoveLastRememberedISize(); - continue; - } - nsIFrame* frame = target->GetPrimaryFrame(); - if (!frame) { - aObserver.Unobserve(*target); - continue; - } - MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(), - "Should have unobserved non-replaced inline."); - MOZ_ASSERT(!frame->HidesContent(), - "Should have unobserved element skipping its contents."); - const nsStylePosition* stylePos = frame->StylePosition(); - const WritingMode wm = frame->GetWritingMode(); - bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); - bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); - MOZ_ASSERT(canUpdateBSize || !target->HasLastRememberedBSize(), - "Should have removed the last remembered block size."); - MOZ_ASSERT(canUpdateISize || !target->HasLastRememberedISize(), - "Should have removed the last remembered inline size."); - MOZ_ASSERT(canUpdateBSize || canUpdateISize, - "Should have unobserved if we can't update any size."); - AutoTArray<RefPtr<ResizeObserverSize>, 1> contentSizeList; - entry->GetContentBoxSize(contentSizeList); - MOZ_ASSERT(!contentSizeList.IsEmpty()); - if (canUpdateBSize) { - float bSize = 0; - for (const auto& current : contentSizeList) { - bSize += current->BlockSize(); - } - target->SetLastRememberedBSize(bSize); - } - if (canUpdateISize) { - float iSize = 0; - for (const auto& current : contentSizeList) { - iSize = std::max(iSize, current->InlineSize()); - } - target->SetLastRememberedISize(iSize); - } - } -} - -/* static */ already_AddRefed<ResizeObserver> -ResizeObserver::CreateLastRememberedSizeObserver(Document& aDocument) { - return do_AddRef(new ResizeObserver(aDocument, LastRememberedSizeCallback)); -} - NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverSize, mOwner) NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverSize) NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverSize) diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h index 6f1fc5b6cd..eaa0e1726d 100644 --- a/dom/base/ResizeObserver.h +++ b/dom/base/ResizeObserver.h @@ -121,9 +121,6 @@ class ResizeObservation final : public LinkedListElement<ResizeObservation> { * https://drafts.csswg.org/resize-observer/#api */ class ResizeObserver final : public nsISupports, public nsWrapperCache { - using NativeCallback = void (*)( - const Sequence<OwningNonNull<ResizeObserverEntry>>&, ResizeObserver&); - ResizeObserver(Document& aDocument, NativeCallback aCallback); public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -131,9 +128,7 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache { ResizeObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner, Document* aDocument, ResizeObserverCallback& aCb) - : mOwner(std::move(aOwner)), - mDocument(aDocument), - mCallback(RefPtr<ResizeObserverCallback>(&aCb)) { + : mOwner(std::move(aOwner)), mDocument(aDocument), mCallback(&aCb) { MOZ_ASSERT(mOwner, "Need a non-null owner window"); MOZ_ASSERT(mDocument, "Need a non-null doc"); MOZ_ASSERT(mDocument == mOwner->GetExtantDoc()); @@ -177,11 +172,6 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache { bool HasSkippedObservations() const { return mHasSkippedTargets; } /** - * Returns whether this is an internal ResizeObserver with a native callback. - */ - bool HasNativeCallback() const { return mCallback.is<NativeCallback>(); } - - /** * Invoke the callback function in JavaScript for all active observations * and pass the sequence of ResizeObserverEntry so JavaScript can access them. * The active observations' mLastReportedSize fields will be updated, and @@ -191,8 +181,21 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache { */ MOZ_CAN_RUN_SCRIPT uint32_t BroadcastActiveObservations(); - static already_AddRefed<ResizeObserver> CreateLastRememberedSizeObserver( - Document&); + /** + * Returns |aTarget|'s size in the form of gfx::Size (in pixels). + * If the target is an SVG that does not participate in CSS layout, + * its width and height are determined from bounding box. Otherwise, the + * relevant box is determined according to the |aBox| parameter. + * + * If dom.resize_observer.support_fragments is enabled, or if + * |aForceFragmentHandling| is true then the function reports the size of all + * fragments, and not just the first one. + * + * https://www.w3.org/TR/resize-observer-1/#calculate-box-size + */ + static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize( + Element* aTarget, ResizeObserverBoxOptions aBox, + bool aForceFragmentHandling = false); protected: ~ResizeObserver() { Disconnect(); } @@ -200,7 +203,7 @@ class ResizeObserver final : public nsISupports, public nsWrapperCache { nsCOMPtr<nsPIDOMWindowInner> mOwner; // The window's document at the time of ResizeObserver creation. RefPtr<Document> mDocument; - Variant<RefPtr<ResizeObserverCallback>, NativeCallback> mCallback; + RefPtr<ResizeObserverCallback> mCallback; nsTArray<RefPtr<ResizeObservation>> mActiveTargets; // The spec uses a list to store the skipped targets. However, it seems what // we want is to check if there are any skipped targets (i.e. existence). diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp index a56e460cbf..69986e6b78 100644 --- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -46,7 +46,6 @@ #include "nsString.h" #include "nsFrameSelection.h" #include "nsISelectionListener.h" -#include "nsContentCID.h" #include "nsDeviceContext.h" #include "nsIContent.h" #include "nsIContentInlines.h" @@ -817,7 +816,8 @@ void Selection::SetAnchorFocusRange(size_t aIndex) { static int32_t CompareToRangeStart(const nsINode& aCompareNode, uint32_t aCompareOffset, - const AbstractRange& aRange) { + const AbstractRange& aRange, + nsContentUtils::NodeIndexCache* aCache) { MOZ_ASSERT(aRange.GetStartContainer()); nsINode* start = aRange.GetStartContainer(); // If the nodes that we're comparing are not in the same document, assume that @@ -831,7 +831,13 @@ static int32_t CompareToRangeStart(const nsINode& aCompareNode, // The points are in the same subtree, hence there has to be an order. return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, start, - aRange.StartOffset()); + aRange.StartOffset(), aCache); +} + +static int32_t CompareToRangeStart(const nsINode& aCompareNode, + uint32_t aCompareOffset, + const AbstractRange& aRange) { + return CompareToRangeStart(aCompareNode, aCompareOffset, aRange, nullptr); } static int32_t CompareToRangeEnd(const nsINode& aCompareNode, @@ -1463,10 +1469,49 @@ void Selection::StyledRanges::ReorderRangesIfNecessary() { mInvalidStaticRanges.AppendElements(std::move(invalidStaticRanges)); } if (domMutationHasHappened || mRangesMightHaveChanged) { - mRanges.Sort([](const StyledRange& a, const StyledRange& b) -> int { - return CompareToRangeStart(*a.mRange->GetStartContainer(), - a.mRange->StartOffset(), *b.mRange); - }); + // This is hot code. Proceed with caution. + // This path uses a cache that keep the last 100 node/index combinations + // in a stack-allocated array to save up on expensive calls to + // nsINode::ComputeIndexOf() (which happen in + // nsContentUtils::ComparePoints()). + // The second expensive call here is the sort() below, which should be + // avoided if possible. Sorting can be avoided if the ranges are still in + // order. Checking the order is cheap compared to sorting (also, it fills up + // the cache, which is reused by the sort call). + nsContentUtils::NodeIndexCache cache; + bool rangeOrderHasChanged = false; + const nsINode* prevStartContainer = nullptr; + uint32_t prevStartOffset = 0; + for (const StyledRange& range : mRanges) { + const nsINode* startContainer = range.mRange->GetStartContainer(); + uint32_t startOffset = range.mRange->StartOffset(); + if (!prevStartContainer) { + prevStartContainer = startContainer; + prevStartOffset = startOffset; + continue; + } + // Calling ComparePoints here saves one call of + // AbstractRange::StartOffset() per iteration (which is surprisingly + // expensive). + const Maybe<int32_t> compareResult = nsContentUtils::ComparePoints( + startContainer, startOffset, prevStartContainer, prevStartOffset, + &cache); + // If the nodes are in different subtrees, the Maybe is empty. + // Since CompareToRangeStart pretends ranges to be ordered, this aligns + // to that behavior. + if (compareResult.valueOr(1) != 1) { + rangeOrderHasChanged = true; + break; + } + prevStartContainer = startContainer; + prevStartOffset = startOffset; + } + if (rangeOrderHasChanged) { + mRanges.Sort([&cache](const StyledRange& a, const StyledRange& b) -> int { + return CompareToRangeStart(*a.mRange->GetStartContainer(), + a.mRange->StartOffset(), *b.mRange, &cache); + }); + } mDocumentGeneration = currentDocumentGeneration; mRangesMightHaveChanged = false; } diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index 64a1614aee..94db7bad85 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -4,7 +4,6 @@ * 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 "mozilla/Preferences.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/DocumentFragment.h" @@ -17,8 +16,10 @@ #include "mozilla/dom/HTMLDetailsElement.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSummaryElement.h" +#include "mozilla/dom/MutationObservers.h" #include "mozilla/dom/Text.h" #include "mozilla/dom/TreeOrderedArrayInlines.h" +#include "mozilla/dom/UnbindContext.h" #include "mozilla/EventDispatcher.h" #include "mozilla/IdentifierMapEntry.h" #include "mozilla/PresShell.h" @@ -26,7 +27,6 @@ #include "mozilla/ScopeExit.h" #include "mozilla/ServoStyleRuleMap.h" #include "mozilla/StyleSheet.h" -#include "mozilla/StyleSheetInlines.h" #include "mozilla/dom/StyleSheetList.h" using namespace mozilla; @@ -184,10 +184,13 @@ void ShadowRoot::Unbind() { OwnerDoc()->RemoveComposedDocShadowRoot(*this); } + UnbindContext context(*this); for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { - child->UnbindFromTree(false); + child->UnbindFromTree(context); } + + MutationObservers::NotifyParentChainChanged(this); } void ShadowRoot::Unattach() { diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index bf3945b1d7..f3e2bea46b 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -34,6 +34,8 @@ #include "mozilla/dom/DOMTypes.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/EncodedAudioChunk.h" +#include "mozilla/dom/EncodedAudioChunkBinding.h" #include "mozilla/dom/EncodedVideoChunk.h" #include "mozilla/dom/EncodedVideoChunkBinding.h" #include "mozilla/dom/File.h" @@ -58,6 +60,7 @@ #include "mozilla/dom/TransformStream.h" #include "mozilla/dom/TransformStreamBinding.h" #include "mozilla/dom/VideoFrame.h" +#include "mozilla/dom/AudioData.h" #include "mozilla/dom/VideoFrameBinding.h" #include "mozilla/dom/WebIDLSerializable.h" #include "mozilla/dom/WritableStream.h" @@ -399,6 +402,7 @@ void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx, mClonedSurfaces.Clear(); mInputStreamArray.Clear(); mVideoFrames.Clear(); + mEncodedAudioChunks.Clear(); mEncodedVideoChunks.Clear(); Clear(); } @@ -1127,6 +1131,28 @@ JSObject* StructuredCloneHolder::CustomReadHandler( } } + if (StaticPrefs::dom_media_webcodecs_enabled() && + aTag == SCTAG_DOM_AUDIODATA && + CloneScope() == StructuredCloneScope::SameProcess && + aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) { + JS::Rooted<JSObject*> global(aCx, mGlobal->GetGlobalJSObject()); + if (AudioData_Binding::ConstructorEnabled(aCx, global)) { + return AudioData::ReadStructuredClone(aCx, mGlobal, aReader, + AudioData()[aIndex]); + } + } + + if (StaticPrefs::dom_media_webcodecs_enabled() && + aTag == SCTAG_DOM_ENCODEDAUDIOCHUNK && + CloneScope() == StructuredCloneScope::SameProcess && + aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) { + JS::Rooted<JSObject*> global(aCx, mGlobal->GetGlobalJSObject()); + if (EncodedAudioChunk_Binding::ConstructorEnabled(aCx, global)) { + return EncodedAudioChunk::ReadStructuredClone( + aCx, mGlobal, aReader, EncodedAudioChunks()[aIndex]); + } + } + return ReadFullySerializableObjects(aCx, aReader, aTag, false); } @@ -1246,6 +1272,29 @@ bool StructuredCloneHolder::CustomWriteHandler( } } + // See if this is an AudioData object. + if (StaticPrefs::dom_media_webcodecs_enabled()) { + mozilla::dom::AudioData* audioData = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(AudioData, &obj, audioData))) { + SameProcessScopeRequired(aSameProcessScopeRequired); + return CloneScope() == StructuredCloneScope::SameProcess + ? audioData->WriteStructuredClone(aWriter, this) + : false; + } + } + + // See if this is a EncodedAudioChunk object. + if (StaticPrefs::dom_media_webcodecs_enabled()) { + EncodedAudioChunk* encodedAudioChunk = nullptr; + if (NS_SUCCEEDED( + UNWRAP_OBJECT(EncodedAudioChunk, &obj, encodedAudioChunk))) { + SameProcessScopeRequired(aSameProcessScopeRequired); + return CloneScope() == StructuredCloneScope::SameProcess + ? encodedAudioChunk->WriteStructuredClone(aWriter, this) + : false; + } + } + { // We only care about streams, so ReflectorToISupportsStatic is fine. nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(aObj); @@ -1429,6 +1478,39 @@ StructuredCloneHolder::CustomReadTransferHandler( return true; } + if (StaticPrefs::dom_media_webcodecs_enabled() && + aTag == SCTAG_DOM_AUDIODATA && + CloneScope() == StructuredCloneScope::SameProcess && + aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) { + MOZ_ASSERT(aContent); + + JS::Rooted<JSObject*> globalObj(aCx, mGlobal->GetGlobalJSObject()); + // aContent will be released in CustomFreeTransferHandler. + if (!AudioData_Binding::ConstructorEnabled(aCx, globalObj)) { + return false; + } + + AudioData::TransferredData* data = + static_cast<AudioData::TransferredData*>(aContent); + nsCOMPtr<nsIGlobalObject> global = mGlobal; + RefPtr<mozilla::dom::AudioData> audioData = + AudioData::FromTransferred(global.get(), data); + // aContent will be released in CustomFreeTransferHandler if frame is null. + if (!audioData) { + return false; + } + delete data; + aContent = nullptr; + + JS::Rooted<JS::Value> value(aCx); + if (!GetOrCreateDOMReflector(aCx, audioData, &value)) { + JS_ClearPendingException(aCx); + return false; + } + aReturnObject.set(&value.toObject()); + return true; + } + return false; } @@ -1530,6 +1612,26 @@ StructuredCloneHolder::CustomWriteTransferHandler( return true; } } + if (StaticPrefs::dom_media_webcodecs_enabled()) { + mozilla::dom::AudioData* audioData = nullptr; + rv = UNWRAP_OBJECT(AudioData, &obj, audioData); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(audioData); + + *aExtraData = 0; + *aTag = SCTAG_DOM_AUDIODATA; + *aContent = nullptr; + + UniquePtr<AudioData::TransferredData> data = audioData->Transfer(); + if (!data) { + return false; + } + *aContent = data.release(); + MOZ_ASSERT(*aContent); + *aOwnership = JS::SCTAG_TMO_CUSTOM; + return true; + } + } } { @@ -1667,6 +1769,16 @@ void StructuredCloneHolder::CustomFreeTransferHandler( } return; } + if (StaticPrefs::dom_media_webcodecs_enabled() && + aTag == SCTAG_DOM_AUDIODATA && + CloneScope() == StructuredCloneScope::SameProcess) { + if (aContent) { + AudioData::TransferredData* data = + static_cast<AudioData::TransferredData*>(aContent); + delete data; + } + return; + } } bool StructuredCloneHolder::CustomCanTransferHandler( @@ -1750,6 +1862,15 @@ bool StructuredCloneHolder::CustomCanTransferHandler( } } + if (StaticPrefs::dom_media_webcodecs_enabled()) { + mozilla::dom::AudioData* audioData = nullptr; + nsresult rv = UNWRAP_OBJECT(AudioData, &obj, audioData); + if (NS_SUCCEEDED(rv)) { + SameProcessScopeRequired(aSameProcessScopeRequired); + return CloneScope() == StructuredCloneScope::SameProcess; + } + } + return false; } diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h index 206c3d3a25..d1d7cf7806 100644 --- a/dom/base/StructuredCloneHolder.h +++ b/dom/base/StructuredCloneHolder.h @@ -165,10 +165,12 @@ class StructuredCloneHolderBase { }; class BlobImpl; +class EncodedAudioChunkData; class EncodedVideoChunkData; class MessagePort; class MessagePortIdentifier; struct VideoFrameSerializedData; +struct AudioDataSerializedData; class StructuredCloneHolder : public StructuredCloneHolderBase { public: @@ -270,10 +272,16 @@ class StructuredCloneHolder : public StructuredCloneHolderBase { nsTArray<VideoFrameSerializedData>& VideoFrames() { return mVideoFrames; } + nsTArray<AudioDataSerializedData>& AudioData() { return mAudioData; } + nsTArray<EncodedVideoChunkData>& EncodedVideoChunks() { return mEncodedVideoChunks; } + nsTArray<EncodedAudioChunkData>& EncodedAudioChunks() { + return mEncodedAudioChunks; + } + // Implementations of the virtual methods to allow cloning of objects which // JS engine itself doesn't clone. @@ -379,9 +387,15 @@ class StructuredCloneHolder : public StructuredCloneHolderBase { // Used for cloning VideoFrame in the structured cloning algorithm. nsTArray<VideoFrameSerializedData> mVideoFrames; + // Used for cloning AudioData in the structured cloning algorithm. + nsTArray<AudioDataSerializedData> mAudioData; + // Used for cloning EncodedVideoChunk in the structured cloning algorithm. nsTArray<EncodedVideoChunkData> mEncodedVideoChunks; + // Used for cloning EncodedAudioChunk in the structured cloning algorithm. + nsTArray<EncodedAudioChunkData> mEncodedAudioChunks; + // This raw pointer is only set within ::Read() and is unset by the end. nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal; diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 95233c62d7..bbc50d9f9d 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -157,6 +157,10 @@ enum StructuredCloneTags : uint32_t { SCTAG_DOM_ENCODEDVIDEOCHUNK, + SCTAG_DOM_AUDIODATA, + + SCTAG_DOM_ENCODEDAUDIOCHUNK, + // IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the // "less stable" tags! }; diff --git a/dom/base/UnbindContext.h b/dom/base/UnbindContext.h new file mode 100644 index 0000000000..77505ba582 --- /dev/null +++ b/dom/base/UnbindContext.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +/* State that is passed down to UnbindToTree. */ + +#ifndef mozilla_dom_UnbindContext_h__ +#define mozilla_dom_UnbindContext_h__ + +#include "mozilla/Attributes.h" +#include "nsINode.h" + +namespace mozilla::dom { + +struct MOZ_STACK_CLASS UnbindContext final { + // The root of the subtree being unbound. + nsINode& Root() const { return mRoot; } + // Whether we're the root of the subtree being unbound. + bool IsUnbindRoot(const nsINode* aNode) const { return &mRoot == aNode; } + // The parent node of the subtree we're unbinding from. + nsINode* GetOriginalSubtreeParent() const { return mOriginalParent; } + + explicit UnbindContext(nsINode& aRoot) + : mRoot(aRoot), mOriginalParent(aRoot.GetParentNode()) {} + + private: + nsINode& mRoot; + nsINode* const mOriginalParent; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/base/UseCounters.conf b/dom/base/UseCounters.conf index 67aa988176..86d782b476 100644 --- a/dom/base/UseCounters.conf +++ b/dom/base/UseCounters.conf @@ -74,7 +74,6 @@ custom onunderflow sets an element onunderflow event listener // JavaScript feature usage custom JS_asmjs uses asm.js custom JS_wasm uses WebAssembly -custom JS_late_weekday parses a Date with day of week in an unexpected position custom JS_wasm_legacy_exceptions uses WebAssembly legacy exception-handling // Console API diff --git a/dom/base/crashtests/crashtests.list b/dom/base/crashtests/crashtests.list index 7260a18878..864538ddf5 100644 --- a/dom/base/crashtests/crashtests.list +++ b/dom/base/crashtests/crashtests.list @@ -220,7 +220,7 @@ load 1406109-1.html load 1411473.html load 1413815.html load 1419799.html -skip-if(!browserIsRemote) skip-if(geckoview) skip-if(geckoview&&isDebugBuild) skip-if(AddressSanitizer) skip-if(ThreadSanitizer) pref(dom.disable_open_during_load,false) load 1419902.html # skip on non e10s loads, Bug 1419902. Bug 1563013 for GV+WR. Bug 1524493 GV+debug. Bug 1573281 asan +skip-if(geckoview) skip-if(geckoview&&isDebugBuild) skip-if(AddressSanitizer) skip-if(ThreadSanitizer) pref(dom.disable_open_during_load,false) load 1419902.html # skip Bug 1419902. Bug 1563013 for GV+WR. Bug 1524493 GV+debug. Bug 1573281 asan load 1422883.html load 1428053.html load 1441029.html @@ -250,7 +250,7 @@ load 1555786.html load 1577191.html load eventSource_invalid_scheme_worker_shutdown.html load 1291535.html -skip-if(!isDebugBuild||xulRuntime.OS!="Linux") load 1611853.html +skip-if(!isDebugBuild||!gtkWidget) load 1611853.html load 1619322.html asserts(0-2) load 1623918.html # May hit an assertion if the <input> element's anonymous tree hasn't been flushed when IMEContentObserver handles focus load 1656925.html diff --git a/dom/base/moz.build b/dom/base/moz.build index 14c9f9dd96..ef1780f161 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -22,7 +22,6 @@ include("/tools/fuzzing/libfuzzer-config.mozbuild") XPIDL_SOURCES += [ "mozIDOMWindow.idl", "nsIContentPolicy.idl", - "nsIDOMRequestService.idl", "nsIDroppedLinkHandler.idl", "nsIEventSourceEventService.idl", "nsIImageLoadingContent.idl", @@ -51,7 +50,6 @@ EXPORTS += [ "nsAttrValueInlines.h", "nsCaseTreatment.h", "nsChildContentList.h", - "nsContentCID.h", "nsContentCreatorFunctions.h", "nsContentList.h", "nsContentListDeclarations.h", @@ -187,7 +185,6 @@ EXPORTS.mozilla.dom += [ "DOMPoint.h", "DOMQuad.h", "DOMRect.h", - "DOMRequest.h", "DOMStringList.h", "DOMTokenListSupportedTokens.h", "Element.h", @@ -283,6 +280,7 @@ EXPORTS.mozilla.dom += [ "TreeOrderedArrayInlines.h", "TreeWalker.h", "UIDirectionManager.h", + "UnbindContext.h", "UseCounterMetrics.h", "UserActivation.h", "ViewportMetaData.h", @@ -345,7 +343,6 @@ UNIFIED_SOURCES += [ "DOMPoint.cpp", "DOMQuad.cpp", "DOMRect.cpp", - "DOMRequest.cpp", "DOMStringList.cpp", "Element.cpp", "EventSource.cpp", @@ -545,7 +542,6 @@ if CONFIG["TARGET_CPU"].startswith("ppc"): EXTRA_JS_MODULES += [ "ContentAreaDropListener.sys.mjs", - "DOMRequestHelper.sys.mjs", "IndexedDBHelper.sys.mjs", "LocationHelper.sys.mjs", "ProcessSelector.sys.mjs", diff --git a/dom/base/nsAttrValue.cpp b/dom/base/nsAttrValue.cpp index ea7923fa0f..01efef2eb9 100644 --- a/dom/base/nsAttrValue.cpp +++ b/dom/base/nsAttrValue.cpp @@ -15,11 +15,10 @@ #include "nsAttrValue.h" #include "nsAttrValueInlines.h" -#include "nsAtomHashKeys.h" #include "nsUnicharUtils.h" #include "mozilla/AttributeStyles.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/BloomFilter.h" -#include "mozilla/CORSMode.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/MemoryReporting.h" #include "mozilla/ServoBindingTypes.h" @@ -27,7 +26,6 @@ #include "mozilla/ShadowParts.h" #include "mozilla/SVGAttrValueWrapper.h" #include "mozilla/URLExtraData.h" -#include "mozilla/dom/CSSRuleBinding.h" #include "mozilla/dom/Document.h" #include "nsContentUtils.h" #include "nsReadableUtils.h" diff --git a/dom/base/nsContentCID.h b/dom/base/nsContentCID.h deleted file mode 100644 index cda1cb4075..0000000000 --- a/dom/base/nsContentCID.h +++ /dev/null @@ -1,195 +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 nsContentCID_h__ -#define nsContentCID_h__ - -// {972D8D8F-F0DA-11d4-9885-00C04FA0CF4B} -#define NS_CONTENT_VIEWER_CID \ - { \ - 0x972d8d8f, 0xf0da, 0x11d4, { \ - 0x98, 0x85, 0x0, 0xc0, 0x4f, 0xa0, 0xcf, 0x4b \ - } \ - } - -// {FC886801-E768-11d4-9885-00C04FA0CF4B} -#define NS_CONTENT_DOCUMENT_LOADER_FACTORY_CID \ - { \ - 0xfc886801, 0xe768, 0x11d4, { \ - 0x98, 0x85, 0x0, 0xc0, 0x4f, 0xa0, 0xcf, 0x4b \ - } \ - } - -#define NS_NAMESPACEMANAGER_CID \ - { /* d9783472-8fe9-11d2-9d3c-0060088f9ff7 */ \ - 0xd9783472, 0x8fe9, 0x11d2, { \ - 0x9d, 0x3c, 0x00, 0x60, 0x08, 0x8f, 0x9f, 0xf7 \ - } \ - } - -// {09F689E0-B4DA-11d2-A68B-00104BDE6048} -#define NS_EVENTLISTENERMANAGER_CID \ - { \ - 0x9f689e0, 0xb4da, 0x11d2, { \ - 0xa6, 0x8b, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 \ - } \ - } - -// {64F300A1-C88C-11d3-97FB-00400553EEF0} -#define NS_XBLSERVICE_CID \ - { \ - 0x64f300a1, 0xc88c, 0x11d3, { \ - 0x97, 0xfb, 0x0, 0x40, 0x5, 0x53, 0xee, 0xf0 \ - } \ - } - -// {4aef38b7-6364-4e23-a5e7-12f837fbbd9c} -#define NS_XMLCONTENTSERIALIZER_CID \ - { \ - 0x4aef38b7, 0x6364, 0x4e23, { \ - 0xa5, 0xe7, 0x12, 0xf8, 0x37, 0xfb, 0xbd, 0x9c \ - } \ - } - -// {e7c2aaf5-c11a-4954-9dbf-e28edec1fd91} -#define NS_XHTMLCONTENTSERIALIZER_CID \ - { \ - 0xe7c2aaf5, 0xc11a, 0x4954, { \ - 0x9d, 0xbf, 0xe2, 0x8e, 0xde, 0xc1, 0xfd, 0x91 \ - } \ - } - -// {9d3f70da-86e9-11d4-95ec-00b0d03e37b7} -#define NS_HTMLCONTENTSERIALIZER_CID \ - { \ - 0x9d3f70da, 0x86e9, 0x11d4, { \ - 0x95, 0xec, 0x00, 0xb0, 0xd0, 0x3e, 0x37, 0xb7 \ - } \ - } - -// {feca3c34-205e-4ae5-bd1c-03c686ff012b} -#define MOZ_SANITIZINGHTMLSERIALIZER_CID \ - { \ - 0xfeca3c34, 0x205e, 0x4ae5, { \ - 0xbd, 0x1c, 0x03, 0xc6, 0x86, 0xff, 0x01, 0x2b \ - } \ - } - -// {6030f7ef-32ed-46a7-9a63-6a5d3f90445f} -#define NS_PLAINTEXTSERIALIZER_CID \ - { \ - 0x6030f7ef, 0x32ed, 0x46a7, { \ - 0x9a, 0x63, 0x6a, 0x5d, 0x3f, 0x90, 0x44, 0x5f \ - } \ - } - -// {d4f2b600-b5c1-11d6-b483-cc97c63e567c} -#define NS_HTMLFRAGMENTSINK_CID \ - { \ - 0xd4f2b600, 0xb5c1, 0x11d6, { \ - 0xb4, 0x83, 0xcc, 0x97, 0xc6, 0x3e, 0x56, 0x7c \ - } \ - } - -// {13111d00-ce81-11d6-8082-ecf3665af67c} -#define NS_HTMLFRAGMENTSINK2_CID \ - { \ - 0x13111d00, 0xce81, 0x11d6, { \ - 0x80, 0x82, 0xec, 0xf3, 0x66, 0x5a, 0xf6, 0x7c \ - } \ - } - -// {4B664E54-72A2-4bbf-A5C2-66D4DC3066A0} -#define NS_XMLFRAGMENTSINK_CID \ - { \ - 0x4b664e54, 0x72a2, 0x4bbf, { \ - 0xa5, 0xc2, 0x66, 0xd4, 0xdc, 0x30, 0x66, 0xa0 \ - } \ - } - -// {4DC30689-929D-425e-A709-082C6294E542} -#define NS_XMLFRAGMENTSINK2_CID \ - { \ - 0x4dc30689, 0x929d, 0x425e, { \ - 0xa7, 0x9, 0x8, 0x2c, 0x62, 0x94, 0xe5, 0x42 \ - } \ - } - -// {3986B301-097C-11d3-BF87-00105A1B0627} -#define NS_XULPOPUPLISTENER_CID \ - { \ - 0x3986b301, 0x97c, 0x11d3, { \ - 0xbf, 0x87, 0x0, 0x10, 0x5a, 0x1b, 0x6, 0x27 \ - } \ - } - -// {3D262D00-8B5A-11d2-8EB0-00805F29F370} -#define NS_XULTEMPLATEBUILDER_CID \ - { \ - 0x3d262d00, 0x8b5a, 0x11d2, { \ - 0x8e, 0xb0, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 \ - } \ - } - -// {1abdcc96-1dd2-11b2-b520-f8f59cdd67bc} -#define NS_XULTREEBUILDER_CID \ - { \ - 0x1abdcc96, 0x1dd2, 0x11b2, { \ - 0xb5, 0x20, 0xf8, 0xf5, 0x9c, 0xdd, 0x67, 0xbc \ - } \ - } - -#define NS_EVENTLISTENERSERVICE_CID \ - { /* baa34652-f1f1-4185-b224-244ee82a413a */ \ - 0xbaa34652, 0xf1f1, 0x4185, { \ - 0xb2, 0x24, 0x24, 0x4e, 0xe8, 0x2a, 0x41, 0x3a \ - } \ - } -#define NS_EVENTLISTENERSERVICE_CONTRACTID "@mozilla.org/eventlistenerservice;1" - -#define NS_GLOBALMESSAGEMANAGER_CID \ - { /* 130b016f-fad7-4526-bc7f-827dabf79265 */ \ - 0x130b016f, 0xfad7, 0x4526, { \ - 0xbc, 0x7f, 0x82, 0x7d, 0xab, 0xf7, 0x92, 0x65 \ - } \ - } -#define NS_GLOBALMESSAGEMANAGER_CONTRACTID "@mozilla.org/globalmessagemanager;1" - -#define NS_PARENTPROCESSMESSAGEMANAGER_CID \ - { /* 2a058404-fb85-44ec-8cfd-e8cbdc988dc1 */ \ - 0x2a058404, 0xfb85, 0x44ec, { \ - 0x8c, 0xfd, 0xe8, 0xcb, 0xdc, 0x98, 0x8d, 0xc1 \ - } \ - } -#define NS_PARENTPROCESSMESSAGEMANAGER_CONTRACTID \ - "@mozilla.org/parentprocessmessagemanager;1" - -#define NS_CHILDPROCESSMESSAGEMANAGER_CID \ - { /* fe0ff7c3-8e97-448b-9a8a-86afdb9fbbb6 */ \ - 0xfe0ff7c3, 0x8e97, 0x448b, { \ - 0x9a, 0x8a, 0x86, 0xaf, 0xdb, 0x9f, 0xbb, 0xb6 \ - } \ - } -#define NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID \ - "@mozilla.org/childprocessmessagemanager;1" - -// {08c6cc8b-cfb0-421d-b1f7-683ff2989681} -#define THIRDPARTYUTIL_CID \ - { \ - 0x08c6cc8b, 0xcfb0, 0x421d, { \ - 0xb1, 0xf7, 0x68, 0x3f, 0xf2, 0x98, 0x96, 0x81 \ - } \ - } - -// {7B121F7E-EBE4-43AB-9410-DC9087A1DBA6} -#define GECKO_MEDIA_PLUGIN_SERVICE_CID \ - { \ - 0x7B121F7E, 0xEBE4, 0x43AB, { \ - 0x94, 0x10, 0xDC, 0x90, 0x87, 0xA1, 0xDB, 0xA6 \ - } \ - } - -#endif /* nsContentCID_h__ */ diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index d849b13927..c6f1687f73 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3027,13 +3027,15 @@ bool nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2, } /* static */ -Maybe<int32_t> nsContentUtils::ComparePoints( - const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, - uint32_t aOffset2, ComparePointsCache* aParent1Cache) { +Maybe<int32_t> nsContentUtils::ComparePoints(const nsINode* aParent1, + uint32_t aOffset1, + const nsINode* aParent2, + uint32_t aOffset2, + NodeIndexCache* aIndexCache) { bool disconnected{false}; const int32_t order = ComparePoints_Deprecated( - aParent1, aOffset1, aParent2, aOffset2, &disconnected, aParent1Cache); + aParent1, aOffset1, aParent2, aOffset2, &disconnected, aIndexCache); if (disconnected) { return Nothing(); } @@ -3044,7 +3046,7 @@ Maybe<int32_t> nsContentUtils::ComparePoints( /* static */ int32_t nsContentUtils::ComparePoints_Deprecated( const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, - uint32_t aOffset2, bool* aDisconnected, ComparePointsCache* aParent1Cache) { + uint32_t aOffset2, bool* aDisconnected, NodeIndexCache* aIndexCache) { if (aParent1 == aParent2) { return aOffset1 < aOffset2 ? -1 : aOffset1 > aOffset2 ? 1 : 0; } @@ -3089,10 +3091,15 @@ int32_t nsContentUtils::ComparePoints_Deprecated( if (MOZ_UNLIKELY(child2->IsShadowRoot())) { return 1; } - const Maybe<uint32_t> child1Index = - aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1) - : parent->ComputeIndexOf(child1); - const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2); + Maybe<uint32_t> child1Index; + Maybe<uint32_t> child2Index; + if (aIndexCache) { + aIndexCache->ComputeIndicesOf(parent, child1, child2, child1Index, + child2Index); + } else { + child1Index = parent->ComputeIndexOf(child1); + child2Index = parent->ComputeIndexOf(child2); + } if (MOZ_LIKELY(child1Index.isSome() && child2Index.isSome())) { return *child1Index < *child2Index ? -1 : 1; } @@ -3110,7 +3117,9 @@ int32_t nsContentUtils::ComparePoints_Deprecated( if (!pos1) { const nsINode* child2 = parents2.ElementAt(--pos2); - const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2); + const Maybe<uint32_t> child2Index = + aIndexCache ? aIndexCache->ComputeIndexOf(parent, child2) + : parent->ComputeIndexOf(child2); if (MOZ_UNLIKELY(NS_WARN_IF(child2Index.isNothing()))) { return 1; } @@ -3119,8 +3128,8 @@ int32_t nsContentUtils::ComparePoints_Deprecated( const nsINode* child1 = parents1.ElementAt(--pos1); const Maybe<uint32_t> child1Index = - aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1) - : parent->ComputeIndexOf(child1); + aIndexCache ? aIndexCache->ComputeIndexOf(parent, child1) + : parent->ComputeIndexOf(child1); if (MOZ_UNLIKELY(NS_WARN_IF(child1Index.isNothing()))) { return -1; } @@ -4004,7 +4013,8 @@ nsresult nsContentUtils::LoadImage( int32_t aLoadFlags, const nsAString& initiatorType, imgRequestProxy** aRequest, nsContentPolicyType aContentPolicyType, bool aUseUrgentStartForChannel, bool aLinkPreload, - uint64_t aEarlyHintPreloaderId) { + uint64_t aEarlyHintPreloaderId, + mozilla::dom::FetchPriority aFetchPriority) { MOZ_ASSERT(aURI, "Must have a URI"); MOZ_ASSERT(aContext, "Must have a context"); MOZ_ASSERT(aLoadingDocument, "Must have a document"); @@ -4041,7 +4051,7 @@ nsresult nsContentUtils::LoadImage( initiatorType, /* the load initiator */ aUseUrgentStartForChannel, /* urgent-start flag */ aLinkPreload, /* <link preload> initiator */ - aEarlyHintPreloaderId, aRequest); + aEarlyHintPreloaderId, aFetchPriority, aRequest); } // static @@ -11426,6 +11436,7 @@ int32_t nsContentUtils::CompareTreePosition(const nsINode* aNode1, nsIContent* nsContentUtils::AttachDeclarativeShadowRoot(nsIContent* aHost, ShadowRootMode aMode, + bool aIsClonable, bool aDelegatesFocus) { RefPtr<Element> host = mozilla::dom::Element::FromNodeOrNull(aHost); if (!host) { @@ -11436,7 +11447,7 @@ nsIContent* nsContentUtils::AttachDeclarativeShadowRoot(nsIContent* aHost, init.mMode = aMode; init.mDelegatesFocus = aDelegatesFocus; init.mSlotAssignment = SlotAssignmentMode::Named; - init.mClonable = true; + init.mClonable = aIsClonable; RefPtr shadowRoot = host->AttachShadow(init, IgnoreErrors(), Element::ShadowRootDeclarative::Yes); diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 95744fe831..338fc097de 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -39,6 +39,7 @@ #include "mozilla/UniquePtr.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/FromParser.h" +#include "mozilla/dom/FetchPriority.h" #include "mozilla/fallible.h" #include "mozilla/gfx/Point.h" #include "nsCOMPtr.h" @@ -545,26 +546,115 @@ class nsContentUtils { mozilla::Maybe<uint32_t>* aNode1Index = nullptr, mozilla::Maybe<uint32_t>* aNode2Index = nullptr); - struct ComparePointsCache { + /** + * Cache implementation for ComparePoints(). + * + * This cache keeps the last cache_size child/index combinations + * in a stack-allocated array for fast lookup. + * If the cache is full, the entries are overridden, + * starting from the oldest entry. + * + * Note: This cache does not observe invalidation. As soon as script has + * run, this cache must not be used anymore. + * Also, this cache uses raw pointers. Beware! + */ + template <size_t cache_size> + struct ResizableNodeIndexCache { + /** + * Looks up or computes two indices in one loop. + */ + void ComputeIndicesOf(const nsINode* aParent, const nsINode* aChild1, + const nsINode* aChild2, + mozilla::Maybe<uint32_t>& aChild1Index, + mozilla::Maybe<uint32_t>& aChild2Index) { + bool foundChild1 = false; + bool foundChild2 = false; + for (size_t cacheIndex = 0; cacheIndex < cache_size; ++cacheIndex) { + if (foundChild1 && foundChild2) { + return; + } + const nsINode* node = mNodes[cacheIndex]; + if (!node) { + // reached the end of not-fully-populated cache. + break; + } + if (!foundChild1 && node == aChild1) { + aChild1Index = mIndices[cacheIndex]; + foundChild1 = true; + continue; + } + if (!foundChild2 && node == aChild2) { + aChild2Index = mIndices[cacheIndex]; + foundChild2 = true; + continue; + } + } + if (!foundChild1) { + aChild1Index = ComputeAndInsertIndexIntoCache(aParent, aChild1); + } + if (!foundChild2) { + aChild2Index = ComputeAndInsertIndexIntoCache(aParent, aChild2); + } + } + /** + * Looks up or computes child index. + */ mozilla::Maybe<uint32_t> ComputeIndexOf(const nsINode* aParent, const nsINode* aChild) { - if (aParent == mParent && aChild == mChild) { - return mIndex; + for (size_t cacheIndex = 0; cacheIndex < cache_size; ++cacheIndex) { + const nsINode* node = mNodes[cacheIndex]; + if (!node) { + break; + } + if (node == aChild) { + return mIndices[cacheIndex]; + } } - - mIndex = aParent->ComputeIndexOf(aChild); - mParent = aParent; - mChild = aChild; - return mIndex; + return ComputeAndInsertIndexIntoCache(aParent, aChild); } private: - const nsINode* mParent = nullptr; - const nsINode* mChild = nullptr; - mozilla::Maybe<uint32_t> mIndex; + /** + * Computes the index of aChild in aParent, inserts the index into the + * cache, and returns the index. + */ + mozilla::Maybe<uint32_t> ComputeAndInsertIndexIntoCache( + const nsINode* aParent, const nsINode* aChild) { + mozilla::Maybe<uint32_t> childIndex = aParent->ComputeIndexOf(aChild); + + mNodes[mNext] = aChild; + mIndices[mNext] = childIndex; + + ++mNext; + if (mNext == cache_size) { + // the last element of the cache has been reached. + // set mNext to 0 to start overriding the oldest cache entries. + mNext = 0; + } + return childIndex; + } + + /// Node storage. The array is initialized to null + /// by the empty initializer list. + const nsINode* mNodes[cache_size]{}; + + mozilla::Maybe<uint32_t> mIndices[cache_size]; + + /// The next element in the cache that will be written to. + /// If the cache is full (mNext == cache_size), + /// the oldest entries in the cache will be overridden, + /// ie. mNext will be set to 0. + size_t mNext{0}; }; /** + * Typedef with a reasonable default cache size. + * If Caches of different sizes are needed, + * ComparePoints would need to become templated. + */ + using NodeIndexCache = ResizableNodeIndexCache<100>; + + /** * Utility routine to compare two "points", where a point is a node/offset * pair. * Pass a cache object as aParent1Cache if you expect to repeatedly @@ -577,7 +667,7 @@ class nsContentUtils { */ static mozilla::Maybe<int32_t> ComparePoints( const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, - uint32_t aOffset2, ComparePointsCache* aParent1Cache = nullptr); + uint32_t aOffset2, NodeIndexCache* aIndexCache = nullptr); template <typename FPT, typename FRT, typename SPT, typename SRT> static mozilla::Maybe<int32_t> ComparePoints( const mozilla::RangeBoundaryBase<FPT, FRT>& aFirstBoundary, @@ -592,13 +682,16 @@ class nsContentUtils { * the result is 1, and the optional aDisconnected parameter * is set to true. * - * Pass a cache object as aParent1Cache if you expect to repeatedly - * call this function with the same value as aParent1. + * Pass a cache object as aIndexCache if you expect to repeatedly + * call this function. + * ComparePointsCache will store the last X (currently 100) node/index + * combinations in a stack-allocated array and does a lookup there + * before going into the expensive ComputeIndexOf() method. */ static int32_t ComparePoints_Deprecated( const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2, uint32_t aOffset2, bool* aDisconnected = nullptr, - ComparePointsCache* aParent1Cache = nullptr); + NodeIndexCache* aIndexCache = nullptr); template <typename FPT, typename FRT, typename SPT, typename SRT> static int32_t ComparePoints_Deprecated( const mozilla::RangeBoundaryBase<FPT, FRT>& aFirstBoundary, @@ -1026,7 +1119,9 @@ class nsContentUtils { nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE, bool aUseUrgentStartForChannel = false, bool aLinkPreload = false, - uint64_t aEarlyHintPreloaderId = 0); + uint64_t aEarlyHintPreloaderId = 0, + mozilla::dom::FetchPriority aFetchPriority = + mozilla::dom::FetchPriority::Auto); /** * Obtain an image loader that respects the given document/channel's privacy @@ -3438,7 +3533,7 @@ class nsContentUtils { MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsIContent* AttachDeclarativeShadowRoot( - nsIContent* aHost, mozilla::dom::ShadowRootMode aMode, + nsIContent* aHost, mozilla::dom::ShadowRootMode aMode, bool aIsClonable, bool aDelegatesFocus); private: diff --git a/dom/base/nsCopySupport.cpp b/dom/base/nsCopySupport.cpp index 4dc183d664..15c0cf4cf0 100644 --- a/dom/base/nsCopySupport.cpp +++ b/dom/base/nsCopySupport.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsCopySupport.h" +#include "nsGlobalWindowInner.h" #include "nsIDocumentEncoder.h" #include "nsISupports.h" #include "nsIContent.h" @@ -38,7 +39,6 @@ #include "nsIImageLoadingContent.h" #include "nsIInterfaceRequestorUtils.h" #include "nsContentUtils.h" -#include "nsContentCID.h" #ifdef XP_WIN # include "mozilla/StaticPrefs_clipboard.h" @@ -714,6 +714,33 @@ static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) { return nullptr; } +/** + * This class is used while processing clipboard paste event. + */ +class MOZ_RAII AutoHandlingPasteEvent final { + public: + explicit AutoHandlingPasteEvent(nsGlobalWindowInner* aWindow, + DataTransfer* aDataTransfer, + const EventMessage& aEventMessage, + const int32_t& aClipboardType) { + MOZ_ASSERT(aDataTransfer); + if (aWindow && aEventMessage == ePaste && + aClipboardType == nsIClipboard::kGlobalClipboard) { + aWindow->SetCurrentPasteDataTransfer(aDataTransfer); + mInnerWindow = aWindow; + } + } + + ~AutoHandlingPasteEvent() { + if (mInnerWindow) { + mInnerWindow->SetCurrentPasteDataTransfer(nullptr); + } + } + + private: + RefPtr<nsGlobalWindowInner> mInnerWindow; +}; + bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage, int32_t aClipboardType, PresShell* aPresShell, @@ -791,9 +818,16 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage, InternalClipboardEvent evt(true, originalEventMessage); evt.mClipboardData = clipboardData; - RefPtr<nsPresContext> presContext = presShell->GetPresContext(); - EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr, - &status); + { + AutoHandlingPasteEvent autoHandlingPasteEvent( + nsGlobalWindowInner::Cast(doc->GetInnerWindow()), clipboardData, + aEventMessage, aClipboardType); + + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr, + &status); + } + // If the event was cancelled, don't do the clipboard operation doDefault = (status != nsEventStatus_eConsumeNoDefault); } diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp index ec70d2b1ac..c174153531 100644 --- a/dom/base/nsDOMNavigationTiming.cpp +++ b/dom/base/nsDOMNavigationTiming.cpp @@ -7,12 +7,11 @@ #include "nsDOMNavigationTiming.h" #include "GeckoProfiler.h" -#include "ipc/IPCMessageUtilsSpecializations.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/dom/Document.h" -#include "mozilla/dom/PerformanceNavigation.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "mozilla/ipc/URIUtils.h" #include "nsCOMPtr.h" diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 692b94e9bd..9bc8340b90 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -897,6 +897,7 @@ nsresult nsDOMWindowUtils::SendTouchEventCommon( return NS_ERROR_UNEXPECTED; } WidgetTouchEvent event(true, msg, widget); + event.mFlags.mIsSynthesizedForTests = true; event.mModifiers = nsContentUtils::GetWidgetModifiers(aModifiers); nsPresContext* presContext = GetPresContext(); diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index 08a2641333..5a4cf78d65 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -3358,7 +3358,7 @@ nsresult nsFocusManager::DetermineElementToMoveFocus( } return GetNextTabbableContent(presShell, startContent, nullptr, startContent, true, 1, false, false, - aNavigateByKey, false, aNextContent); + aNavigateByKey, false, false, aNextContent); } if (aType == MOVEFOCUS_LAST) { if (!aStartContent) { @@ -3366,7 +3366,7 @@ nsresult nsFocusManager::DetermineElementToMoveFocus( } return GetNextTabbableContent(presShell, startContent, nullptr, startContent, false, 0, false, false, - aNavigateByKey, false, aNextContent); + aNavigateByKey, false, false, aNextContent); } bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_FORWARDDOC || @@ -3537,7 +3537,7 @@ nsresult nsFocusManager::DetermineElementToMoveFocus( MOZ_KnownLive(skipOriginalContentCheck ? nullptr : originalStartContent.get()), startContent, forward, tabIndex, ignoreTabIndex, - forDocumentNavigation, aNavigateByKey, false, + forDocumentNavigation, aNavigateByKey, false, false, getter_AddRefs(nextFocus)); NS_ENSURE_SUCCESS(rv, rv); if (rv == NS_SUCCESS_DOM_NO_OPERATION) { @@ -3660,15 +3660,16 @@ nsresult nsFocusManager::DetermineElementToMoveFocus( // If the focus started in this window outside a popup however, we should // continue by looping around to the end again. if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) { - // HTML content documents can have their root element focused (a focus - // ring appears around the entire content area frame). This root - // appears in the tab order before all of the elements in the document. - // Chrome documents however cannot be focused directly, so instead we - // focus the first focusable element within the window. + // HTML content documents can have their root element focused by + // pressing F6(a focus ring appears around the entire content area + // frame). This root appears in the tab order before all of the elements + // in the document. Chrome documents however cannot be focused directly, + // so instead we focus the first focusable element within the window. // For example, the urlbar. RefPtr<Element> rootElementForFocus = GetRootForFocus(piWindow, doc, true, true); - return FocusFirst(rootElementForFocus, aNextContent); + return FocusFirst(rootElementForFocus, aNextContent, + true /* aReachedToEndForDocumentNavigation */); } // Once we have hit the top-level and have iterated to the end again, we @@ -3889,7 +3890,7 @@ nsIContent* nsFocusManager::GetNextTabbableContentInScope( nsIContent* aOwner, nsIContent* aStartContent, nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, - bool aSkipOwner) { + bool aSkipOwner, bool aReachedToEndForDocumentNavigation) { MOZ_ASSERT( IsHostOrSlot(aOwner) || IsOpenPopoverWithInvoker(aOwner), "Scope owner should be host, slot or an open popover with invoker set."); @@ -3972,6 +3973,7 @@ nsIContent* nsFocusManager::GetNextTabbableContentInScope( if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent, aForward, aForDocumentNavigation, aNavigateByKey, + aReachedToEndForDocumentNavigation, getter_AddRefs(elementInFrame))) { return elementInFrame; } @@ -3984,7 +3986,8 @@ nsIContent* nsFocusManager::GetNextTabbableContentInScope( nsIContent* contentToFocus = GetNextTabbableContentInScope( iterContent, iterContent, aOriginalStartContent, aForward, aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation, - aNavigateByKey, false /* aSkipOwner */); + aNavigateByKey, false /* aSkipOwner */, + aReachedToEndForDocumentNavigation); if (contentToFocus) { return contentToFocus; } @@ -4024,7 +4027,8 @@ nsIContent* nsFocusManager::GetNextTabbableContentInScope( nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes( nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */, nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex, - bool* aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey) { + bool* aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, + bool aReachedToEndForDocumentNavigation) { MOZ_ASSERT(aStartOwner == FindScopeOwner(aStartContent), "aStartOWner should be the scope owner of aStartContent"); MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot"); @@ -4043,7 +4047,7 @@ nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes( nsIContent* contentToFocus = GetNextTabbableContentInScope( owner, startContent, aOriginalStartContent, aForward, tabIndex, tabIndex < 0, aForDocumentNavigation, aNavigateByKey, - false /* aSkipOwner */); + false /* aSkipOwner */, aReachedToEndForDocumentNavigation); if (contentToFocus) { return contentToFocus; } @@ -4089,7 +4093,8 @@ nsresult nsFocusManager::GetNextTabbableContent( PresShell* aPresShell, nsIContent* aRootContent, nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, - bool aNavigateByKey, bool aSkipPopover, nsIContent** aResultContent) { + bool aNavigateByKey, bool aSkipPopover, + bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent) { *aResultContent = nullptr; if (!aStartContent) { @@ -4109,7 +4114,7 @@ nsresult nsFocusManager::GetNextTabbableContent( nsIContent* contentToFocus = GetNextTabbableContentInScope( startContent, startContent, aOriginalStartContent, aForward, 1, aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, - true /* aSkipOwner */); + true /* aSkipOwner */, aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); return NS_OK; @@ -4125,7 +4130,7 @@ nsresult nsFocusManager::GetNextTabbableContent( nsIContent* contentToFocus = GetNextTabbableContentInScope( popover, popover, aOriginalStartContent, aForward, 1, aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, - true /* aSkipOwner */); + true /* aSkipOwner */, aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); return NS_OK; @@ -4140,7 +4145,7 @@ nsresult nsFocusManager::GetNextTabbableContent( nsIContent* contentToFocus = GetNextTabbableContentInAncestorScopes( owner, startContent /* inout */, aOriginalStartContent, aForward, &aCurrentTabIndex, &aIgnoreTabIndex, aForDocumentNavigation, - aNavigateByKey); + aNavigateByKey, aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); return NS_OK; @@ -4185,7 +4190,8 @@ nsresult nsFocusManager::GetNextTabbableContent( nsIContent* contentToFocus = GetNextTabbableContentInScope( iterStartContent, iterStartContent, aOriginalStartContent, aForward, aForward ? 1 : 0, aIgnoreTabIndex, - aForDocumentNavigation, aNavigateByKey, true /* aSkipOwner */); + aForDocumentNavigation, aNavigateByKey, true /* aSkipOwner */, + aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); return NS_OK; @@ -4276,7 +4282,8 @@ nsresult nsFocusManager::GetNextTabbableContent( (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { nsresult rv = GetNextTabbableContent( aPresShell, rootElement, nullptr, invokerContent, true, - tabIndex, false, false, aNavigateByKey, true, aResultContent); + tabIndex, false, false, aNavigateByKey, true, + aReachedToEndForDocumentNavigation, aResultContent); if (NS_SUCCEEDED(rv) && *aResultContent) { return rv; } @@ -4298,7 +4305,7 @@ nsresult nsFocusManager::GetNextTabbableContent( nsIContent* contentToFocus = GetNextTabbableContentInScope( popover, popover, aOriginalStartContent, aForward, 0, aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, - true /* aSkipOwner */); + true /* aSkipOwner */, aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); @@ -4340,7 +4347,8 @@ nsresult nsFocusManager::GetNextTabbableContent( // want to locate the first content, not the first document. nsresult rv = GetNextTabbableContent( aPresShell, currentContent, nullptr, currentContent, true, 1, - false, false, aNavigateByKey, false, aResultContent); + false, false, aNavigateByKey, false, + aReachedToEndForDocumentNavigation, aResultContent); if (NS_SUCCEEDED(rv) && *aResultContent) { return rv; } @@ -4368,7 +4376,7 @@ nsresult nsFocusManager::GetNextTabbableContent( currentTopLevelScopeOwner, currentTopLevelScopeOwner, aOriginalStartContent, aForward, aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, - true /* aSkipOwner */); + true /* aSkipOwner */, aReachedToEndForDocumentNavigation); if (contentToFocus) { NS_ADDREF(*aResultContent = contentToFocus); return NS_OK; @@ -4469,7 +4477,8 @@ nsresult nsFocusManager::GetNextTabbableContent( // frame. If so, navigate into the child frame instead. if (TryToMoveFocusToSubDocument( currentContent, aOriginalStartContent, aForward, - aForDocumentNavigation, aNavigateByKey, aResultContent)) { + aForDocumentNavigation, aNavigateByKey, + aReachedToEndForDocumentNavigation, aResultContent)) { MOZ_ASSERT(*aResultContent); return NS_OK; } @@ -4485,6 +4494,24 @@ nsresult nsFocusManager::GetNextTabbableContent( NS_ADDREF(*aResultContent = currentContent); return NS_OK; } + } else if (currentContent && aReachedToEndForDocumentNavigation && + StaticPrefs::dom_disable_tab_focus_to_root_element() && + nsContentUtils::IsChromeDoc( + currentContent->GetComposedDoc())) { + // aReachedToEndForDocumentNavigation is true means + // 1. This is a document navigation (VK_F6) + // 2. This is the top-level document (Note that we may start from + // a subdocument) + // 3. We've searched through the this top-level document already + if (!GetRootForChildDocument(currentContent)) { + // We'd like to focus the first focusable element of this + // top-level chrome document. + if (currentContent == aRootContent || + currentContent != startContent) { + NS_ADDREF(*aResultContent = currentContent); + return NS_OK; + } + } } } } else if (aOriginalStartContent && @@ -4522,13 +4549,14 @@ nsresult nsFocusManager::GetNextTabbableContent( if (aCurrentTabIndex == (aForward ? 0 : 1)) { // if going backwards, the canvas should be focused once the beginning // has been reached, so get the root element. - if (!aForward) { + if (!aForward && !StaticPrefs::dom_disable_tab_focus_to_root_element()) { nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent); NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); RefPtr<Element> docRoot = GetRootForFocus( window, aRootContent->GetComposedDoc(), false, true); - FocusFirst(docRoot, aResultContent); + FocusFirst(docRoot, aResultContent, + false /* aReachedToEndForDocumentNavigation */); } break; } @@ -4556,7 +4584,8 @@ bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent, // the frameset's frames and locate the first focusable frame. if (!rootElementForChildDocument->IsHTMLElement(nsGkAtoms::frameset)) { *aCheckSubDocument = false; - Unused << FocusFirst(rootElementForChildDocument, aResultContent); + Unused << FocusFirst(rootElementForChildDocument, aResultContent, + false /* aReachedToEndForDocumentNavigation */); return *aResultContent != nullptr; } } else { @@ -4571,12 +4600,12 @@ bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent, bool nsFocusManager::TryToMoveFocusToSubDocument( nsIContent* aCurrentContent, nsIContent* aOriginalStartContent, bool aForward, bool aForDocumentNavigation, bool aNavigateByKey, - nsIContent** aResultContent) { + bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent) { Document* doc = aCurrentContent->GetComposedDoc(); NS_ASSERTION(doc, "content not in document"); Document* subdoc = doc->GetSubDocumentFor(aCurrentContent); if (subdoc && !subdoc->EventHandlingSuppressed()) { - if (aForward) { + if (aForward && !StaticPrefs::dom_disable_tab_focus_to_root_element()) { // When tabbing forward into a frame, return the root // frame so that the canvas becomes focused. if (nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow()) { @@ -4592,11 +4621,19 @@ bool nsFocusManager::TryToMoveFocusToSubDocument( nsresult rv = GetNextTabbableContent( subPresShell, rootElement, aOriginalStartContent, rootElement, aForward, (aForward ? 1 : 0), false, aForDocumentNavigation, - aNavigateByKey, false, aResultContent); + aNavigateByKey, false, aReachedToEndForDocumentNavigation, + aResultContent); NS_ENSURE_SUCCESS(rv, false); if (*aResultContent) { return true; } + if (rootElement->IsEditable() && + StaticPrefs::dom_disable_tab_focus_to_root_element()) { + // Only move to the root element with a valid reason + *aResultContent = rootElement; + NS_ADDREF(*aResultContent); + return true; + } } } } @@ -4711,7 +4748,8 @@ int32_t nsFocusManager::GetNextTabIndex(nsIContent* aParent, } nsresult nsFocusManager::FocusFirst(Element* aRootElement, - nsIContent** aNextContent) { + nsIContent** aNextContent, + bool aReachedToEndForDocumentNavigation) { if (!aRootElement) { return NS_OK; } @@ -4741,9 +4779,12 @@ nsresult nsFocusManager::FocusFirst(Element* aRootElement, // tabbable item so that the first item is focused. Note that we // always go forward and not back here. if (RefPtr<PresShell> presShell = doc->GetPresShell()) { - return GetNextTabbableContent(presShell, aRootElement, nullptr, - aRootElement, true, 1, false, false, true, - false, aNextContent); + return GetNextTabbableContent( + presShell, aRootElement, nullptr, aRootElement, true, 1, false, + StaticPrefs::dom_disable_tab_focus_to_root_element() + ? aReachedToEndForDocumentNavigation + : false, + true, false, aReachedToEndForDocumentNavigation, aNextContent); } } } diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h index 6e5c761f4c..4fb9d05e1c 100644 --- a/dom/base/nsFocusManager.h +++ b/dom/base/nsFocusManager.h @@ -578,6 +578,10 @@ class nsFocusManager final : public nsIFocusManager, * aSkipOwner to skip owner while searching. The flag is set when caller is * |GetNextTabbableContent| in order to let caller handle owner. * + * aReachedToEndForDocumentNavigation is true when this is a document + * navigation and the focus algorithm has reached to the end of the top-level + * document. + * * NOTE: * Consider the method searches downwards in flattened subtree * rooted at aOwner. @@ -586,7 +590,8 @@ class nsFocusManager final : public nsIFocusManager, nsIContent* aOwner, nsIContent* aStartContent, nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, - bool aForDocumentNavigation, bool aNavigateByKey, bool aSkipOwner); + bool aForDocumentNavigation, bool aNavigateByKey, bool aSkipOwner, + bool aReachedToEndForDocumentNavigation); /** * Retrieve the next tabbable element in scope including aStartContent @@ -619,6 +624,10 @@ class nsFocusManager final : public nsIFocusManager, * aNavigateByKey to move focus by keyboard as a side effect of computing the * next target. * + * aReachedToEndForDocumentNavigation is true when this is a document + * navigation and the focus algorithm has reached to the end of the top-level + * document. + * * NOTE: * Consider the method searches upwards in all shadow host- or slot-rooted * flattened subtrees that contains aStartContent as non-root, except @@ -628,7 +637,8 @@ class nsFocusManager final : public nsIFocusManager, nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */, nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex, bool* aIgnoreTabIndex, - bool aForDocumentNavigation, bool aNavigateByKey); + bool aForDocumentNavigation, bool aNavigateByKey, + bool aReachedToEndForDocumentNavigation); /** * Retrieve the next tabbable element within a document, using focusability @@ -663,13 +673,17 @@ class nsFocusManager final : public nsIFocusManager, * * aNavigateByKey to move focus by keyboard as a side effect of computing the * next target. + * + * aReachedToEndForDocumentNavigation is true when this is a document + * navigation and the focus algorithm has reached to the end of the top-level + * document. */ MOZ_CAN_RUN_SCRIPT nsresult GetNextTabbableContent( mozilla::PresShell* aPresShell, nsIContent* aRootContent, nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, bool aSkipPopover, - nsIContent** aResultContent); + bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent); /** * Get the next tabbable image map area and returns it. @@ -699,9 +713,13 @@ class nsFocusManager final : public nsIFocusManager, * Focus the first focusable content within the document with a root node of * aRootContent. For content documents, this will be aRootContent itself, but * for chrome documents, this will locate the next focusable content. + * + * aReachedToEndForDocumentNavigation is true when the focus algorithm has + * reached to the end of the top-level document. */ - MOZ_CAN_RUN_SCRIPT nsresult FocusFirst(mozilla::dom::Element* aRootContent, - nsIContent** aNextContent); + MOZ_CAN_RUN_SCRIPT nsresult + FocusFirst(mozilla::dom::Element* aRootContent, nsIContent** aNextContent, + bool aReachedToEndForDocumentNavigation); /** * Retrieves and returns the root node from aDocument to be focused. Will @@ -761,7 +779,7 @@ class nsFocusManager final : public nsIFocusManager, MOZ_CAN_RUN_SCRIPT bool TryToMoveFocusToSubDocument( nsIContent* aCurrentContent, nsIContent* aOriginalStartContent, bool aForward, bool aForDocumentNavigation, bool aNavigateByKey, - nsIContent** aResultContent); + bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent); // Sets the focused BrowsingContext and, if appropriate, syncs it to // other processes. diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index a40bc427dd..eca528f258 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -39,7 +39,6 @@ #include "nsSubDocumentFrame.h" #include "nsError.h" #include "nsIAppWindow.h" -#include "nsIMozBrowserFrame.h" #include "nsIScriptError.h" #include "nsGlobalWindowInner.h" #include "nsGlobalWindowOuter.h" @@ -262,13 +261,6 @@ static bool IsTopContent(BrowsingContext* aParent, Element* aOwner) { return false; } - // If we have a (deprecated) mozbrowser element, we want to start a new - // BrowsingContext tree regardless of whether the parent is chrome or content. - nsCOMPtr<nsIMozBrowserFrame> mozbrowser = aOwner->GetAsMozBrowserFrame(); - if (mozbrowser && mozbrowser->GetReallyIsBrowser()) { - return true; - } - if (aParent->IsContent()) { // If we're already in content, we may still want to create a new // BrowsingContext tree if our element is a xul browser element with a @@ -365,17 +357,8 @@ static bool InitialLoadIsRemote(Element* aOwner) { return false; } - // If we're an <iframe mozbrowser> and we don't have a "remote" attribute, - // fall back to the default. - nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aOwner); - bool isMozBrowserFrame = browserFrame && browserFrame->GetReallyIsBrowser(); - if (isMozBrowserFrame && !aOwner->HasAttr(nsGkAtoms::remote)) { - return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false); - } - - // Otherwise, we're remote if we have "remote=true" and we're either a - // browser frame or a XUL element. - return (isMozBrowserFrame || aOwner->GetNameSpaceID() == kNameSpaceID_XUL) && + // Otherwise, we're remote if we have "remote=true" and we're a XUL element. + return (aOwner->GetNameSpaceID() == kNameSpaceID_XUL) && aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote, nsGkAtoms::_true, eCaseMatters); } @@ -706,12 +689,6 @@ nsresult nsFrameLoader::ReallyStartLoadingInternal() { // Default flags: int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE; - - // Flags for browser frame: - if (OwnerIsMozBrowserFrame()) { - flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | - nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; - } loadState->SetLoadFlags(flags); loadState->SetFirstParty(false); @@ -875,14 +852,6 @@ static bool CheckDocShellType(mozilla::dom::Element* aOwnerContent, bool isContent = aOwnerContent->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::content, eIgnoreCase); - if (!isContent) { - nsCOMPtr<nsIMozBrowserFrame> mozbrowser = - aOwnerContent->GetAsMozBrowserFrame(); - if (mozbrowser) { - mozbrowser->GetMozbrowser(&isContent); - } - } - if (isContent) { return aDocShell->ItemType() == nsIDocShellTreeItem::typeContent; } @@ -1156,7 +1125,6 @@ bool nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size, if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) { os->NotifyObservers(ToSupports(this), "remote-browser-shown", nullptr); } - ProcessPriorityManager::RemoteBrowserFrameShown(this); } } else { nsIntRect dimensions; @@ -1329,14 +1297,6 @@ nsresult nsFrameLoader::SwapWithOtherRemoteLoader( return NS_ERROR_NOT_IMPLEMENTED; } - // Destroy browser frame scripts for content leaving a frame with browser API - if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) { - DestroyBrowserFrameScripts(); - } - if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) { - aOther->DestroyBrowserFrameScripts(); - } - otherBrowserParent->SetBrowserDOMWindow(browserDOMWindow); browserParent->SetBrowserDOMWindow(otherBrowserDOMWindow); @@ -1405,10 +1365,6 @@ nsresult nsFrameLoader::SwapWithOtherRemoteLoader( ourPresShell->BackingScaleFactorChanged(); otherPresShell->BackingScaleFactorChanged(); - // Initialize browser API if needed now that owner content has changed. - InitializeBrowserAPI(); - aOther->InitializeBrowserAPI(); - mInSwap = aOther->mInSwap = false; // Send an updated tab context since owner content type may have changed. @@ -1536,13 +1492,8 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, return NS_ERROR_NOT_IMPLEMENTED; } - bool ourFullscreenAllowed = ourContent->IsXULElement() || - (OwnerIsMozBrowserFrame() && - ourContent->HasAttr(nsGkAtoms::allowfullscreen)); - bool otherFullscreenAllowed = - otherContent->IsXULElement() || - (aOther->OwnerIsMozBrowserFrame() && - otherContent->HasAttr(nsGkAtoms::allowfullscreen)); + bool ourFullscreenAllowed = ourContent->IsXULElement(); + bool otherFullscreenAllowed = otherContent->IsXULElement(); if (ourFullscreenAllowed != otherFullscreenAllowed) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -1732,14 +1683,6 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, return rv; } - // Destroy browser frame scripts for content leaving a frame with browser API - if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) { - DestroyBrowserFrameScripts(); - } - if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) { - aOther->DestroyBrowserFrameScripts(); - } - // Now move the docshells to the right docshell trees. Note that this // resets their treeowners to null. ourParentItem->RemoveChild(ourDocshell); @@ -1837,10 +1780,6 @@ nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, ourFrame->PresShell()->BackingScaleFactorChanged(); otherFrame->PresShell()->BackingScaleFactorChanged(); - // Initialize browser API if needed now that owner content has changed - InitializeBrowserAPI(); - aOther->InitializeBrowserAPI(); - return NS_OK; } @@ -2174,11 +2113,6 @@ void nsFrameLoader::SetOwnerContent(Element* aContent) { } } -bool nsFrameLoader::OwnerIsMozBrowserFrame() { - nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent); - return browserFrame ? browserFrame->GetReallyIsBrowser() : false; -} - nsIContent* nsFrameLoader::GetParentObject() const { return mOwnerContent; } void nsFrameLoader::AssertSafeToInit() { @@ -2338,16 +2272,7 @@ nsresult nsFrameLoader::MaybeCreateDocShell() { MOZ_ALWAYS_SUCCEEDS(mPendingBrowsingContext->SetInitialSandboxFlags( mPendingBrowsingContext->GetSandboxFlags())); - if (OwnerIsMozBrowserFrame()) { - // For inproc frames, set the docshell properties. - nsAutoString name; - if (mOwnerContent->GetAttr(nsGkAtoms::name, name)) { - docShell->SetName(name); - } - } - ReallyLoadFrameScripts(); - InitializeBrowserAPI(); // Previously we would forcibly create the initial about:blank document for // in-process content frames from a frame script which eagerly loaded in @@ -2588,11 +2513,8 @@ bool nsFrameLoader::TryRemoteBrowserInternal() { // Graphics initialization code relies on having a frame for the // remote browser case, as we can be inside a popup, which is a different // widget. - // - // FIXME: Ideally this should be unconditional, but we skip if for <iframe - // mozbrowser> because the old RDM ui depends on current behavior, and the - // mozbrowser frame code is scheduled for deletion, see bug 1574886. - if (!OwnerIsMozBrowserFrame() && !mOwnerContent->GetPrimaryFrame()) { + + if (!mOwnerContent->GetPrimaryFrame()) { doc->FlushPendingNotifications(FlushType::Frames); } @@ -2647,12 +2569,11 @@ bool nsFrameLoader::TryRemoteBrowserInternal() { mPendingBrowsingContext->InitSessionHistory(); } - // <iframe mozbrowser> gets to skip these checks. // iframes for JS plugins also get to skip these checks. We control the URL // that gets loaded, but the load is triggered from the document containing // the plugin. // out of process iframes also get to skip this check. - if (!OwnerIsMozBrowserFrame() && !XRE_IsContentProcess()) { + if (!XRE_IsContentProcess()) { if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) { // Allow three exceptions to this rule : // - about:addons so it can load remote extension options pages @@ -2817,7 +2738,6 @@ bool nsFrameLoader::TryRemoteBrowserInternal() { } ReallyLoadFrameScripts(); - InitializeBrowserAPI(); return true; } @@ -3053,7 +2973,7 @@ nsresult nsFrameLoader::EnsureMessageManager() { return NS_OK; } - if (!mIsTopLevelContent && !OwnerIsMozBrowserFrame() && !IsRemoteFrame() && + if (!mIsTopLevelContent && !IsRemoteFrame() && !(mOwnerContent->IsXULElement() && mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::forcemessagemanager, @@ -3096,7 +3016,7 @@ nsresult nsFrameLoader::EnsureMessageManager() { NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED); // Set up session store - if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + if (SessionStorePlatformCollection()) { if (XRE_IsParentProcess() && mIsTopLevelContent) { mSessionStoreChild = SessionStoreChild::GetOrCreate( GetExtantBrowsingContext(), mOwnerContent); @@ -3538,36 +3458,6 @@ BrowsingContext* nsFrameLoader::GetExtantBrowsingContext() { return mPendingBrowsingContext; } -void nsFrameLoader::InitializeBrowserAPI() { - if (!OwnerIsMozBrowserFrame()) { - return; - } - - nsresult rv = EnsureMessageManager(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - mMessageManager->LoadFrameScript( - u"chrome://global/content/BrowserElementChild.js"_ns, - /* allowDelayedLoad = */ true, - /* aRunInGlobalScope */ true, IgnoreErrors()); - - nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent); - if (browserFrame) { - browserFrame->InitializeBrowserAPI(); - } -} - -void nsFrameLoader::DestroyBrowserFrameScripts() { - if (!OwnerIsMozBrowserFrame()) { - return; - } - nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent); - if (browserFrame) { - browserFrame->DestroyBrowserFrameScripts(); - } -} - void nsFrameLoader::StartPersistence( BrowsingContext* aContext, nsIWebBrowserPersistDocumentReceiver* aRecv, ErrorResult& aRv) { @@ -3673,9 +3563,9 @@ nsresult nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext, nsresult nsFrameLoader::PopulateOriginContextIdsFromAttributes( OriginAttributes& aAttr) { - // Only XUL or mozbrowser frames are allowed to set context IDs + // Only XUL are allowed to set context IDs uint32_t namespaceID = mOwnerContent->GetNameSpaceID(); - if (namespaceID != kNameSpaceID_XUL && !OwnerIsMozBrowserFrame()) { + if (namespaceID != kNameSpaceID_XUL) { return NS_OK; } @@ -3693,7 +3583,7 @@ nsresult nsFrameLoader::PopulateOriginContextIdsFromAttributes( mOwnerContent->GetAttr(nsGkAtoms::geckoViewSessionContextId, attributeValue) && !attributeValue.IsEmpty()) { - // XXX: Should we check the format from `GeckoViewNavigation.jsm` here? + // XXX: Should we check the format from `GeckoViewNavigation.sys.mjs` here? aAttr.mGeckoViewSessionContextId = attributeValue; } @@ -3899,8 +3789,7 @@ bool nsFrameLoader::EnsureBrowsingContextAttached() { // Inherit the `mFirstPartyDomain` flag from our parent document's result // principal, if it was set. if (parentContext->IsContent() && - !parentDoc->NodePrincipal()->IsSystemPrincipal() && - !OwnerIsMozBrowserFrame()) { + !parentDoc->NodePrincipal()->IsSystemPrincipal()) { OriginAttributes docAttrs = parentDoc->NodePrincipal()->OriginAttributesRef(); // We only want to inherit firstPartyDomain here, other attributes should @@ -3918,15 +3807,6 @@ bool nsFrameLoader::EnsureBrowsingContextAttached() { if (NS_WARN_IF(NS_FAILED(rv))) { return false; } - - // <iframe mozbrowser> is allowed to set `mozprivatebrowsing` to - // force-enable private browsing. - if (OwnerIsMozBrowserFrame()) { - if (mOwnerContent->HasAttr(nsGkAtoms::mozprivatebrowsing)) { - attrs.SyncAttributesWithPrivateBrowsing(true); - usePrivateBrowsing = true; - } - } } // If we've already been attached, return. diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h index 159e3865a6..33c8000868 100644 --- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -264,12 +264,6 @@ class nsFrameLoader final : public nsStubMutationObserver, bool IsNetworkCreated() const { return mNetworkCreated; } - /** - * Is this a frame loader for a bona fide <iframe mozbrowser>? - * <xul:browser> is not a mozbrowser, so this is false for that case. - */ - bool OwnerIsMozBrowserFrame(); - nsIContent* GetParentObject() const; /** @@ -481,9 +475,6 @@ class nsFrameLoader final : public nsStubMutationObserver, void AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner); - void InitializeBrowserAPI(); - void DestroyBrowserFrameScripts(); - nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext, nsIURI* aURI = nullptr); diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index 8b69389790..5337e1588f 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -167,6 +167,7 @@ #include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/TrustedTypePolicyFactory.h" #include "mozilla/dom/VRDisplay.h" #include "mozilla/dom/VRDisplayEvent.h" #include "mozilla/dom/VRDisplayEventBinding.h" @@ -224,6 +225,7 @@ #include "nsIBrowserChild.h" #include "nsICancelableRunnable.h" #include "nsIChannel.h" +#include "nsIClipboard.h" #include "nsIContentSecurityPolicy.h" #include "nsIControllers.h" #include "nsICookieJarSettings.h" @@ -1283,6 +1285,8 @@ void nsGlobalWindowInner::FreeInnerObjects() { mWebTaskScheduler = nullptr; } + mTrustedTypePolicyFactory = nullptr; + mSharedWorkers.Clear(); #ifdef MOZ_WEBSPEECH @@ -1378,6 +1382,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrustedTypePolicyFactory) + #ifdef MOZ_WEBSPEECH NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis) #endif @@ -1446,6 +1452,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualViewport) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPasteDataTransfer) tmp->TraverseObjectsInGlobal(cb); @@ -1481,6 +1488,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler) } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrustedTypePolicyFactory) + #ifdef MOZ_WEBSPEECH NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) #endif @@ -1554,6 +1563,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner) NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils) NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualViewport) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentPasteDataTransfer) tmp->UnlinkObjectsInGlobal(); @@ -5082,7 +5092,7 @@ nsGlobalWindowInner::ShowSlowScriptDialog(JSContext* aCx, } // Reached only on non-e10s - once per slow script dialog. - // On e10s - we probe once at ProcessHangsMonitor.jsm + // On e10s - we probe once at ProcessHangsMonitor.sys.mjs Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTICE_COUNT, 1); // Get the nsIPrompt interface from the docshell @@ -7603,6 +7613,27 @@ JS::loader::ModuleLoaderBase* nsGlobalWindowInner::GetModuleLoader( return loader->GetModuleLoader(); } +void nsGlobalWindowInner::SetCurrentPasteDataTransfer( + DataTransfer* aDataTransfer) { + MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->GetEventMessage() == ePaste); + MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->ClipboardType() == + nsIClipboard::kGlobalClipboard); + MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->GetAsyncGetClipboardData()); + mCurrentPasteDataTransfer = aDataTransfer; +} + +DataTransfer* nsGlobalWindowInner::GetCurrentPasteDataTransfer() const { + return mCurrentPasteDataTransfer; +} + +TrustedTypePolicyFactory* nsGlobalWindowInner::TrustedTypes() { + if (!mTrustedTypePolicyFactory) { + mTrustedTypePolicyFactory = MakeRefPtr<TrustedTypePolicyFactory>(this); + } + + return mTrustedTypePolicyFactory; +} + nsIURI* nsPIDOMWindowInner::GetDocumentURI() const { return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get(); } diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h index 100dbb9699..15fbc4259f 100644 --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -104,6 +104,7 @@ class ClientSource; class Console; class Crypto; class CustomElementRegistry; +class DataTransfer; class DocGroup; class External; class Function; @@ -127,6 +128,7 @@ class WebTaskScheduler; class WebTaskSchedulerMainThread; class SpeechSynthesis; class Timeout; +class TrustedTypePolicyFactory; class VisualViewport; class VRDisplay; enum class VRDisplayEventReason : uint8_t; @@ -1254,11 +1256,18 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, virtual JS::loader::ModuleLoaderBase* GetModuleLoader( JSContext* aCx) override; + mozilla::dom::TrustedTypePolicyFactory* TrustedTypes(); + + void SetCurrentPasteDataTransfer(mozilla::dom::DataTransfer* aDataTransfer); + mozilla::dom::DataTransfer* GetCurrentPasteDataTransfer() const; + private: RefPtr<mozilla::dom::ContentMediaController> mContentMediaController; RefPtr<mozilla::dom::WebTaskSchedulerMainThread> mWebTaskScheduler; + RefPtr<mozilla::dom::TrustedTypePolicyFactory> mTrustedTypePolicyFactory; + protected: // Whether we need to care about orientation changes. bool mHasOrientationChangeListeners : 1; @@ -1460,6 +1469,10 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, mGroupMessageManagers{1}; } mChromeFields; + // Cache the DataTransfer created for a paste event, this will be reset after + // the event is dispatched. + RefPtr<mozilla::dom::DataTransfer> mCurrentPasteDataTransfer; + // These fields are used by the inner and outer windows to prevent // programatically moving the window while the mouse is down. static bool sMouseDown; diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 87874d49be..e28dcdb092 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -99,7 +99,6 @@ // Other Classes #include "mozilla/dom/BarProps.h" -#include "nsContentCID.h" #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/WorkerCommon.h" diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h index de00012a01..700855370f 100644 --- a/dom/base/nsIContent.h +++ b/dom/base/nsIContent.h @@ -21,6 +21,7 @@ class HTMLEditor; struct URLExtraData; namespace dom { struct BindContext; +struct UnbindContext; class ShadowRoot; class HTMLSlotElement; } // namespace dom @@ -58,6 +59,7 @@ class nsIContent : public nsINode { using IMEEnabled = mozilla::widget::IMEEnabled; using IMEState = mozilla::widget::IMEState; using BindContext = mozilla::dom::BindContext; + using UnbindContext = mozilla::dom::UnbindContext; void ConstructUbiNode(void* storage) override; @@ -111,15 +113,10 @@ class nsIContent : public nsINode { * from a parent, this will be called after it has been removed from the * parent's child list and after the nsIDocumentObserver notifications for * the removal have been dispatched. - * @param aDeep Whether to recursively unbind the entire subtree rooted at - * this node. The only time false should be passed is when the - * parent node of the content is being destroyed. - * @param aNullParent Whether to null out the parent pointer as well. This - * is usually desirable. This argument should only be false while - * recursively calling UnbindFromTree when a subtree is detached. * @note This method is safe to call on nodes that are not bound to a tree. */ - virtual void UnbindFromTree(bool aNullParent = true) = 0; + virtual void UnbindFromTree(UnbindContext&) = 0; + void UnbindFromTree(); enum { /** diff --git a/dom/base/nsIDOMRequestService.idl b/dom/base/nsIDOMRequestService.idl deleted file mode 100644 index 4f6fcd5784..0000000000 --- a/dom/base/nsIDOMRequestService.idl +++ /dev/null @@ -1,21 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface mozIDOMWindow; -webidl DOMRequest; - -[scriptable, builtinclass, uuid(9a57e5de-ce93-45fa-8145-755722834f7c)] -interface nsIDOMRequestService : nsISupports -{ - DOMRequest createRequest(in mozIDOMWindow window); - - void fireSuccess(in DOMRequest request, in jsval result); - void fireError(in DOMRequest request, in AString error); - void fireSuccessAsync(in DOMRequest request, in jsval result); - void fireErrorAsync(in DOMRequest request, in AString error); -}; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 3a9fe23899..d5455e5596 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -16,6 +16,7 @@ #include "js/JSON.h" // JS_ParseJSON #include "mozAutoDocUpdate.h" #include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/CORSMode.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" @@ -304,7 +305,7 @@ class IsItemInRangeComparator { // @param aStartOffset has to be less or equal to aEndOffset. IsItemInRangeComparator(const nsINode& aNode, const uint32_t aStartOffset, const uint32_t aEndOffset, - nsContentUtils::ComparePointsCache* aCache) + nsContentUtils::NodeIndexCache* aCache) : mNode(aNode), mStartOffset(aStartOffset), mEndOffset(aEndOffset), @@ -332,7 +333,7 @@ class IsItemInRangeComparator { const nsINode& mNode; const uint32_t mStartOffset; const uint32_t mEndOffset; - nsContentUtils::ComparePointsCache* mCache; + nsContentUtils::NodeIndexCache* mCache; }; bool nsINode::IsSelected(const uint32_t aStartOffset, @@ -367,7 +368,7 @@ bool nsINode::IsSelected(const uint32_t aStartOffset, } } - nsContentUtils::ComparePointsCache cache; + nsContentUtils::NodeIndexCache cache; IsItemInRangeComparator comparator{*this, aStartOffset, aEndOffset, &cache}; for (Selection* selection : ancestorSelections) { // Binary search the sorted ranges in this selection. @@ -1810,6 +1811,10 @@ Maybe<uint32_t> nsINode::ComputeIndexOf(const nsINode* aPossibleChild) const { return Nothing(); } + if (aPossibleChild == GetFirstChild()) { + return Some(0); + } + if (aPossibleChild == GetLastChild()) { MOZ_ASSERT(GetChildCount()); return Some(GetChildCount() - 1); @@ -3672,15 +3677,13 @@ already_AddRefed<nsINode> nsINode::CloneAndAdopt( } newShadowRoot->SetIsDeclarative(originalShadowRoot->IsDeclarative()); - if (aDeep) { - for (nsIContent* origChild = originalShadowRoot->GetFirstChild(); - origChild; origChild = origChild->GetNextSibling()) { - nsCOMPtr<nsINode> child = - CloneAndAdopt(origChild, aClone, aDeep, nodeInfoManager, - aReparentScope, newShadowRoot, aError); - if (NS_WARN_IF(aError.Failed())) { - return nullptr; - } + for (nsIContent* origChild = originalShadowRoot->GetFirstChild(); + origChild; origChild = origChild->GetNextSibling()) { + nsCOMPtr<nsINode> child = + CloneAndAdopt(origChild, aClone, true, nodeInfoManager, + aReparentScope, newShadowRoot, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; } } } diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index ce9fbd55be..3a47992cc8 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -1895,13 +1895,9 @@ class nsINode : public mozilla::dom::EventTarget { // flags, because we can't use those to distinguish // <bdi dir="some-invalid-value"> and <bdi dir="auto">. NodeHasValidDirAttribute, - // Set if the node has dir=auto and has a property pointing to the text - // node that determines its direction - NodeHasDirAutoSet, - // Set if the node is a text node descendant of a node with dir=auto - // and has a TextNodeDirectionalityMap property listing the elements whose - // direction it determines. - NodeHasTextNodeDirectionalityMap, + // Set if this node, which must be a text node, might be responsible for + // setting the directionality of a dir="auto" ancestor. + NodeMaySetDirAuto, // Set if a node in the node's parent chain has dir=auto. NodeAncestorHasDirAuto, // Set if the node is handling a click. @@ -2028,31 +2024,19 @@ class nsINode : public mozilla::dom::EventTarget { void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); } void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); } bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); } - void SetHasDirAutoSet() { - MOZ_ASSERT(NodeType() != TEXT_NODE, "SetHasDirAutoSet on text node"); - SetBoolFlag(NodeHasDirAutoSet); + void SetMaySetDirAuto() { + // FIXME(bug 1881225): dir=auto should probably work on CDATA too. + MOZ_ASSERT(NodeType() == TEXT_NODE); + SetBoolFlag(NodeMaySetDirAuto); } - void ClearHasDirAutoSet() { - MOZ_ASSERT(NodeType() != TEXT_NODE, "ClearHasDirAutoSet on text node"); - ClearBoolFlag(NodeHasDirAutoSet); + bool MaySetDirAuto() const { + MOZ_ASSERT(NodeType() == TEXT_NODE); + return GetBoolFlag(NodeMaySetDirAuto); } - bool HasDirAutoSet() const { return GetBoolFlag(NodeHasDirAutoSet); } - void SetHasTextNodeDirectionalityMap() { - MOZ_ASSERT(NodeType() == TEXT_NODE, - "SetHasTextNodeDirectionalityMap on non-text node"); - SetBoolFlag(NodeHasTextNodeDirectionalityMap); + void ClearMaySetDirAuto() { + MOZ_ASSERT(NodeType() == TEXT_NODE); + ClearBoolFlag(NodeMaySetDirAuto); } - void ClearHasTextNodeDirectionalityMap() { - MOZ_ASSERT(NodeType() == TEXT_NODE, - "ClearHasTextNodeDirectionalityMap on non-text node"); - ClearBoolFlag(NodeHasTextNodeDirectionalityMap); - } - bool HasTextNodeDirectionalityMap() const { - MOZ_ASSERT(NodeType() == TEXT_NODE, - "HasTextNodeDirectionalityMap on non-text node"); - return GetBoolFlag(NodeHasTextNodeDirectionalityMap); - } - void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); } void ClearAncestorHasDirAuto() { ClearBoolFlag(NodeAncestorHasDirAuto); } bool AncestorHasDirAuto() const { diff --git a/dom/base/nsIScriptableContentIterator.idl b/dom/base/nsIScriptableContentIterator.idl index caf689f550..370cd8c8a7 100644 --- a/dom/base/nsIScriptableContentIterator.idl +++ b/dom/base/nsIScriptableContentIterator.idl @@ -64,11 +64,3 @@ interface nsIScriptableContentIterator : nsISupports // See ContentIteratorBase::PositionAt(nsINode*) void positionAt(in Node aNode); }; - -%{C++ -#define SCRIPTABLE_CONTENT_ITERATOR_CID \ - { 0xf68037ec, 0x2790, 0x44c5, \ - { 0x8e, 0x5f, 0xdf, 0x5d, 0xa5, 0x8b, 0x93, 0xa7 } } -#define SCRIPTABLE_CONTENT_ITERATOR_CONTRACTID \ - "@mozilla.org/scriptable-content-iterator;1" -%} diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index c1320a3472..fa1798ce35 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -47,6 +47,7 @@ #include "mozilla/dom/BindContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/FetchPriority.h" #include "mozilla/dom/PContent.h" // For TextRecognitionResult #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/ImageTextBinding.h" @@ -1143,7 +1144,8 @@ nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce, nsresult rv = nsContentUtils::LoadImage( aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo, this, loadFlags, element->LocalName(), getter_AddRefs(req), policyType, - mUseUrgentStartForChannel); + mUseUrgentStartForChannel, /* aLinkPreload */ false, + /* aEarlyHintPreloaderId */ 0, GetFetchPriorityForImage()); // Reset the flag to avoid loading from XPCOM or somewhere again else without // initiated by user interaction. @@ -1639,10 +1641,12 @@ void nsImageLoadingContent::BindToTree(BindContext& aContext, } } -void nsImageLoadingContent::UnbindFromTree(bool aNullParent) { +void nsImageLoadingContent::UnbindFromTree() { // We may be leaving the document, so if our image is tracked, untrack it. nsCOMPtr<Document> doc = GetOurCurrentDoc(); - if (!doc) return; + if (!doc) { + return; + } UntrackImage(mCurrentRequest); UntrackImage(mPendingRequest); @@ -1860,3 +1864,7 @@ nsLoadFlags nsImageLoadingContent::LoadFlags() { } return nsIRequest::LOAD_NORMAL; } + +FetchPriority nsImageLoadingContent::GetFetchPriorityForImage() const { + return FetchPriority::Auto; +} diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h index 6929f20a23..2b6dac53fb 100644 --- a/dom/base/nsImageLoadingContent.h +++ b/dom/base/nsImageLoadingContent.h @@ -40,6 +40,7 @@ namespace dom { struct BindContext; class Document; class Element; +enum class FetchPriority : uint8_t; } // namespace dom } // namespace mozilla @@ -221,7 +222,7 @@ class nsImageLoadingContent : public nsIImageLoadingContent { // Subclasses are *required* to call BindToTree/UnbindFromTree. void BindToTree(mozilla::dom::BindContext&, nsINode& aParent); - void UnbindFromTree(bool aNullParent); + void UnbindFromTree(); void OnLoadComplete(imgIRequest* aRequest, nsresult aStatus); void OnUnlockedDraw(); @@ -236,6 +237,8 @@ class nsImageLoadingContent : public nsIImageLoadingContent { // want a non-const nsIContent. virtual nsIContent* AsContent() = 0; + virtual mozilla::dom::FetchPriority GetFetchPriorityForImage() const; + /** * Get width and height of the current request, using given image request if * attributes are unset. diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 1d2eb16885..1397bd25b5 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2227,6 +2227,21 @@ void nsJSContext::EnsureStatics() { "javascript.options.mem.gc_max_helper_threads", (void*)JSGC_MAX_HELPER_THREADS); + Preferences::RegisterCallbackAndCall( + SetMemoryPrefChangedCallbackInt, + "javascript.options.mem.nursery_eager_collection_threshold_kb", + (void*)JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_KB); + + Preferences::RegisterCallbackAndCall( + SetMemoryPrefChangedCallbackInt, + "javascript.options.mem.nursery_eager_collection_threshold_percent", + (void*)JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_PERCENT); + + Preferences::RegisterCallbackAndCall( + SetMemoryPrefChangedCallbackInt, + "javascript.options.mem.nursery_eager_collection_timeout_ms", + (void*)JSGC_NURSERY_EAGER_COLLECTION_TIMEOUT_MS); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); if (!obs) { MOZ_CRASH(); diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index 0a2ea3e62d..ab87c58e87 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -223,7 +223,7 @@ already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell( return docShell.forget(); } -void nsObjectLoadingContent::UnbindFromTree(bool aNullParent) { +void nsObjectLoadingContent::UnbindFromTree() { // Reset state and clear pending events /// XXX(johns): The implementation for GenericFrame notes that ideally we /// would keep the docshell around, but trash the frameloader @@ -1249,7 +1249,11 @@ nsresult nsObjectLoadingContent::LoadObject(bool aNotify, bool aForceLoad, break; } - rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req, + uint32_t uriLoaderFlags = nsDocShell::ComputeURILoaderFlags( + docShell->GetBrowsingContext(), LOAD_NORMAL, + /* aIsDocumentLoad */ false); + + rv = uriLoader->OpenChannel(mChannel, uriLoaderFlags, req, getter_AddRefs(finalListener)); // finalListener will receive OnStartRequest either below, or if // `mChannel` is a `DocumentChannel`, it will be received after diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h index 563ad4df3f..c679a7cc5d 100644 --- a/dom/base/nsObjectLoadingContent.h +++ b/dom/base/nsObjectLoadingContent.h @@ -183,7 +183,7 @@ class nsObjectLoadingContent : public nsIStreamListener, void CreateStaticClone(nsObjectLoadingContent* aDest) const; - void UnbindFromTree(bool aNullParent = true); + void UnbindFromTree(); /** * Return the content policy type used for loading the element. diff --git a/dom/base/nsTextNode.cpp b/dom/base/nsTextNode.cpp index df08c547be..e34b581c40 100644 --- a/dom/base/nsTextNode.cpp +++ b/dom/base/nsTextNode.cpp @@ -43,13 +43,13 @@ class nsAttributeTextNode final : public nsTextNode, NS_ASSERTION(mAttrName, "Must have attr name"); } - virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(UnbindContext&) override; NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED - virtual already_AddRefed<CharacterData> CloneDataNode( + already_AddRefed<CharacterData> CloneDataNode( mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override { RefPtr<nsAttributeTextNode> it = new (aNodeInfo->NodeInfoManager()) nsAttributeTextNode( @@ -123,10 +123,9 @@ nsresult nsTextNode::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void nsTextNode::UnbindFromTree(bool aNullParent) { - ResetDirectionSetByTextNode(this); - - CharacterData::UnbindFromTree(aNullParent); +void nsTextNode::UnbindFromTree(UnbindContext& aContext) { + CharacterData::UnbindFromTree(aContext); + ResetDirectionSetByTextNode(this, aContext); } #ifdef MOZ_DOM_LIST @@ -209,16 +208,16 @@ nsresult nsAttributeTextNode::BindToTree(BindContext& aContext, return NS_OK; } -void nsAttributeTextNode::UnbindFromTree(bool aNullParent) { +void nsAttributeTextNode::UnbindFromTree(UnbindContext& aContext) { // UnbindFromTree can be called anytime so we have to be safe. if (mGrandparent) { - // aNullParent might not be true here, but we want to remove the + // aContext might not be true here, but we want to remove the // mutation observer anyway since we only need it while we're // in the document. mGrandparent->RemoveMutationObserver(this); mGrandparent = nullptr; } - nsTextNode::UnbindFromTree(aNullParent); + nsTextNode::UnbindFromTree(aContext); } void nsAttributeTextNode::AttributeChanged(Element* aElement, diff --git a/dom/base/nsTextNode.h b/dom/base/nsTextNode.h index 23d7aff8a8..b39497a3b1 100644 --- a/dom/base/nsTextNode.h +++ b/dom/base/nsTextNode.h @@ -45,7 +45,7 @@ class nsTextNode : public mozilla::dom::Text { mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; nsresult AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength, bool aNotify, nsIContent* aNextSibling); diff --git a/dom/base/nsWindowRoot.cpp b/dom/base/nsWindowRoot.cpp index 1ee2368f1e..1874898b68 100644 --- a/dom/base/nsWindowRoot.cpp +++ b/dom/base/nsWindowRoot.cpp @@ -13,8 +13,6 @@ #include "nsWindowRoot.h" #include "nsPIDOMWindow.h" #include "nsPresContext.h" -#include "nsLayoutCID.h" -#include "nsContentCID.h" #include "nsString.h" #include "nsFrameLoaderOwner.h" #include "nsFrameLoader.h" diff --git a/dom/base/test/browser.toml b/dom/base/test/browser.toml index 1deeb88d89..a68bd2e873 100644 --- a/dom/base/test/browser.toml +++ b/dom/base/test/browser.toml @@ -109,6 +109,16 @@ skip-if = [ ] support-files = ["browser_multiple_popups.html"] +["browser_object_attachment.js"] +support-files = [ + "file_img_object_attachment.html", + "file_img_attachment.jpg", + "file_img_attachment.jpg^headers^", + "file_pdf_object_attachment.html", + "file_pdf_attachment.pdf", + "file_pdf_attachment.pdf^headers^", +] + ["browser_outline_refocus.js"] ["browser_page_load_event_telemetry.js"] diff --git a/dom/base/test/browser_object_attachment.js b/dom/base/test/browser_object_attachment.js new file mode 100644 index 0000000000..b4432862f0 --- /dev/null +++ b/dom/base/test/browser_object_attachment.js @@ -0,0 +1,168 @@ +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", +}); + +const httpsTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +add_task(async function test_pdf_object_attachment() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.navigation.object_embed.allow_retargeting", false]], + }); + + await BrowserTestUtils.withNewTab( + `${httpsTestRoot}/file_pdf_object_attachment.html`, + async browser => { + is( + browser.browsingContext.children.length, + 1, + "Should have a child frame" + ); + await SpecialPowers.spawn(browser, [], async () => { + let obj = content.document.querySelector("object"); + is( + obj.displayedType, + Ci.nsIObjectLoadingContent.TYPE_DOCUMENT, + "should be displaying TYPE_DOCUMENT" + ); + }); + } + ); +}); + +add_task(async function test_img_object_attachment() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.navigation.object_embed.allow_retargeting", false]], + }); + + await BrowserTestUtils.withNewTab( + `${httpsTestRoot}/file_img_object_attachment.html`, + async browser => { + is( + browser.browsingContext.children.length, + 1, + "Should have a child frame" + ); + await SpecialPowers.spawn(browser, [], async () => { + let obj = content.document.querySelector("object"); + is( + obj.displayedType, + Ci.nsIObjectLoadingContent.TYPE_DOCUMENT, + "should be displaying TYPE_DOCUMENT" + ); + }); + } + ); +}); + +async function waitForDownload() { + // Get the downloads list and add a view to listen for a download to be added. + let downloadList = await Downloads.getList(Downloads.ALL); + + // Wait for a single download + let downloadView; + let finishedAllDownloads = new Promise(resolve => { + downloadView = { + onDownloadAdded(aDownload) { + info("download added"); + resolve(aDownload); + }, + }; + }); + await downloadList.addView(downloadView); + let download = await finishedAllDownloads; + await downloadList.removeView(downloadView); + + // Clean up the download from the list. + await downloadList.remove(download); + await download.finalize(true); + + // Return the download + return download; +} + +add_task(async function test_pdf_object_attachment_download() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.navigation.object_embed.allow_retargeting", true]], + }); + + // Set the behaviour to save pdfs to disk and not handle internally, so we + // don't end up with extra tabs after the test. + var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService( + Ci.nsIHandlerService + ); + const mimeInfo = gMimeSvc.getFromTypeAndExtension("application/pdf", "pdf"); + let previousAction = mimeInfo.preferredAction; + mimeInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk; + gHandlerSvc.store(mimeInfo); + registerCleanupFunction(() => { + mimeInfo.preferredAction = previousAction; + gHandlerSvc.store(mimeInfo); + }); + + // Start listening for the download before opening the new tab. + let downloadPromise = waitForDownload(); + await BrowserTestUtils.withNewTab( + `${httpsTestRoot}/file_pdf_object_attachment.html`, + async browser => { + let download = await downloadPromise; + is( + download.source.url, + `${httpsTestRoot}/file_pdf_attachment.pdf`, + "download should be the pdf" + ); + + await SpecialPowers.spawn(browser, [], async () => { + let obj = content.document.querySelector("object"); + is( + obj.displayedType, + Ci.nsIObjectLoadingContent.TYPE_FALLBACK, + "should be displaying TYPE_FALLBACK" + ); + }); + } + ); +}); + +add_task(async function test_img_object_attachment_download() { + // NOTE: This is testing our current behaviour here as of bug 1868001 (which + // is to download an image with `Content-Disposition: attachment` embedded + // within an object or embed element). + // + // Other browsers ignore the `Content-Disposition: attachment` header when + // loading images within object or embed element as-of december 2023, as + // we did prior to the changes in bug 1595491. + // + // If this turns out to be a web-compat issue, we may want to introduce + // special handling to ignore content-disposition when loading images within + // an object or embed element. + await SpecialPowers.pushPrefEnv({ + set: [["dom.navigation.object_embed.allow_retargeting", true]], + }); + + // Start listening for the download before opening the new tab. + let downloadPromise = waitForDownload(); + await BrowserTestUtils.withNewTab( + `${httpsTestRoot}/file_img_object_attachment.html`, + async browser => { + let download = await downloadPromise; + is( + download.source.url, + `${httpsTestRoot}/file_img_attachment.jpg`, + "download should be the jpg" + ); + + await SpecialPowers.spawn(browser, [], async () => { + let obj = content.document.querySelector("object"); + is( + obj.displayedType, + Ci.nsIObjectLoadingContent.TYPE_FALLBACK, + "should be displaying TYPE_FALLBACK" + ); + }); + } + ); +}); diff --git a/dom/base/test/chrome.toml b/dom/base/test/chrome.toml index 687d778cac..ec736a086c 100644 --- a/dom/base/test/chrome.toml +++ b/dom/base/test/chrome.toml @@ -1,6 +1,5 @@ [DEFAULT] skip-if = ["os == 'android'"] -prefs = ["dom.domrequest.enabled=true"] support-files = [ "file_empty.html", "file_blocking_image.html", @@ -38,8 +37,6 @@ support-files = [ ["test_bug1120222.html"] -["test_domrequesthelper.xhtml"] - ["test_fragment_sanitization.xhtml"] ["test_getLastOverWindowPointerLocationInCSSPixels.html"] diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js index 7c39df0c13..e7e3c63b5c 100644 --- a/dom/base/test/chrome/bug418986-1.js +++ b/dom/base/test/chrome/bug418986-1.js @@ -1,4 +1,7 @@ /* globals chromeWindow */ + +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // The main test function. var test = function (isContent) { SimpleTest.waitForExplicitFinish(); diff --git a/dom/base/test/chrome/chrome.toml b/dom/base/test/chrome/chrome.toml index b8439a2d2e..08265bcb97 100644 --- a/dom/base/test/chrome/chrome.toml +++ b/dom/base/test/chrome/chrome.toml @@ -18,7 +18,6 @@ support-files = [ "custom_element_ep.js", "window_nsITextInputProcessor.xhtml", "title_window.xhtml", - "window_swapFrameLoaders.xhtml", ] prefs = ["gfx.font_rendering.fallback.async=false"] @@ -126,9 +125,6 @@ support-files = ["../dummy.html"] ["test_range_getClientRectsAndTexts.html"] -["test_swapFrameLoaders.xhtml"] -skip-if = ["os == 'mac'"] # bug 1674413 - ["test_title.xhtml"] support-files = ["file_title.xhtml"] diff --git a/dom/base/test/chrome/file_bug549682.xhtml b/dom/base/test/chrome/file_bug549682.xhtml index 8ae05d38d8..0bb3080507 100644 --- a/dom/base/test/chrome/file_bug549682.xhtml +++ b/dom/base/test/chrome/file_bug549682.xhtml @@ -28,11 +28,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=549682 } var asyncPPML = false; - function ppmASL(m) { + function ppmASL() { asyncPPML = true; } var syncPPML = false; - function ppmSL(m) { + function ppmSL() { syncPPML = true; } ppm.addMessageListener("processmessageAsync", ppmASL); @@ -42,7 +42,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=549682 cpm.sendSyncMessage("processmessageSync", ""); var asyncCPML = false; - function cpmASL(m) { + function cpmASL() { asyncCPML = true; } cpm.addMessageListener("childprocessmessage", cpmASL); @@ -93,7 +93,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=549682 var weakListener = { QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), - receiveMessage(msg) { + receiveMessage() { if (weakMessageReceived) { ok(false, 'Weak listener fired twice.'); return; @@ -109,7 +109,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=549682 var weakListener2 = { QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), - receiveMessage(msg) { + receiveMessage() { ok(false, 'Should not have received a message.'); } }; diff --git a/dom/base/test/chrome/file_bug616841.xhtml b/dom/base/test/chrome/file_bug616841.xhtml index b0512d162c..3651a00226 100644 --- a/dom/base/test/chrome/file_bug616841.xhtml +++ b/dom/base/test/chrome/file_bug616841.xhtml @@ -27,7 +27,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=616841 [ "D", "\u010C" ] ]; var nCmps = 0; - function recvContentReady(m) { + function recvContentReady() { for (var i = 0; i < toCompare.length; ++i) { var pair = toCompare[i]; messageManager.broadcastAsyncMessage("cmp", diff --git a/dom/base/test/chrome/test_bug1339722.html b/dom/base/test/chrome/test_bug1339722.html index d8d95f1faa..7655ff95fa 100644 --- a/dom/base/test/chrome/test_bug1339722.html +++ b/dom/base/test/chrome/test_bug1339722.html @@ -29,7 +29,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1339722 // behave similarly. const TOPIC = "document-on-modify-request"; let win; - const observe = (subject, topic, data) => { + const observe = (subject, topic) => { info("Got " + topic); Services.obs.removeObserver(observe, TOPIC); @@ -58,7 +58,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1339722 // Remove the iframe to cause frameloader destroy. iframe.remove(); - setTimeout($ => { + setTimeout(() => { ok(!document.getElementById("testFrame"), "verify iframe removed"); SimpleTest.finish(); }, 0); diff --git a/dom/base/test/chrome/test_bug339494.xhtml b/dom/base/test/chrome/test_bug339494.xhtml index 203f6e644d..afab41b65c 100644 --- a/dom/base/test/chrome/test_bug339494.xhtml +++ b/dom/base/test/chrome/test_bug339494.xhtml @@ -55,7 +55,7 @@ SimpleTest.waitForExplicitFinish(); s.setAttribute("ggg", "testvalue"); await promiseFlushingMutationObserver(); - const observer = new MutationObserver((aMutationList, aObserver) => { + const observer = new MutationObserver(() => { ok(s.hasAttribute("ggg"), "Value check 3. There should be a value"); isnot(s.getAttribute("ggg"), "testvalue", "Value check 4"); is(s.getAttribute("ggg"), "othervalue", "Value check 5"); diff --git a/dom/base/test/chrome/test_bug429785.xhtml b/dom/base/test/chrome/test_bug429785.xhtml index fb51634fab..10c9977ccb 100644 --- a/dom/base/test/chrome/test_bug429785.xhtml +++ b/dom/base/test/chrome/test_bug429785.xhtml @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429785 var errorLogged = false; var listener = { QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]), - observe(msg) { errorLogged = true; } + observe() { errorLogged = true; } }; function step2() { diff --git a/dom/base/test/chrome/test_bug430050.xhtml b/dom/base/test/chrome/test_bug430050.xhtml index d7d6cf656c..dfe1e3c8ee 100644 --- a/dom/base/test/chrome/test_bug430050.xhtml +++ b/dom/base/test/chrome/test_bug430050.xhtml @@ -27,7 +27,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=430050 } function startTest() { - const observer = new MutationObserver((aMutationList, aObserver) => { + const observer = new MutationObserver(() => { document.getElementById('b').setAttribute("src", "data:text/plain,failed"); const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); diff --git a/dom/base/test/chrome/test_chromeOuterWindowID.xhtml b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml index 1feb7c7c74..3aa482b636 100644 --- a/dom/base/test/chrome/test_chromeOuterWindowID.xhtml +++ b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml @@ -42,7 +42,7 @@ windows. "Both browsers should belong to the same document."); let winID = getOuterWindowID(browser1.ownerGlobal); - let getChildRootOuterId = browser => { + let getChildRootOuterId = () => { try { return docShell.browserChild?.chromeOuterWindowID; } catch(ex) { } diff --git a/dom/base/test/chrome/test_swapFrameLoaders.xhtml b/dom/base/test/chrome/test_swapFrameLoaders.xhtml deleted file mode 100644 index 4ea11a1a62..0000000000 --- a/dom/base/test/chrome/test_swapFrameLoaders.xhtml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0"?> -<?xml-stylesheet type="text/css" href="chrome://global/skin"?> -<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=1242644 -Test swapFrameLoaders with different frame types and remoteness ---> -<window title="Mozilla Bug 1242644" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> - - <!-- test results are displayed in the html:body --> - <body xmlns="http://www.w3.org/1999/xhtml"> - <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644" - target="_blank">Mozilla Bug 1242644</a> - </body> - - <!-- test code goes here --> - <script type="application/javascript"><![CDATA[ - SimpleTest.waitForExplicitFinish(); - - window.openDialog("window_swapFrameLoaders.xhtml", "bug1242644", - "chrome,width=600,height=600,noopener", window); - ]]></script> -</window> diff --git a/dom/base/test/chrome/title_window.xhtml b/dom/base/test/chrome/title_window.xhtml index f48cdaaaf1..5f9840c36c 100644 --- a/dom/base/test/chrome/title_window.xhtml +++ b/dom/base/test/chrome/title_window.xhtml @@ -63,10 +63,10 @@ } } - function listener2(ev) { + function listener2() { inProgressDoc[description] = false; } - function listener3(ev) { + function listener3() { inProgressWin[description] = false; } frame.addEventListener("DOMTitleChanged", listener); diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xhtml b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml index c8ce6ee5e7..c62ba2ce47 100644 --- a/dom/base/test/chrome/window_nsITextInputProcessor.xhtml +++ b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml @@ -4120,7 +4120,7 @@ function runUnloadTests1() let oldSrc = iframe.src; let parentWindow = window; - iframe.addEventListener("load", function (aEvent) { + iframe.addEventListener("load", function () { ok(true, description + "dummy page is loaded"); childWindow = iframe.contentWindow; textareaInFrame = null; @@ -4181,7 +4181,7 @@ function runUnloadTests2() let oldSrc = iframe.src; - iframe.addEventListener("load", function (aEvent) { + iframe.addEventListener("load", function () { ok(true, description + "dummy page is loaded"); childWindow = iframe.contentWindow; textareaInFrame = null; diff --git a/dom/base/test/chrome/window_swapFrameLoaders.xhtml b/dom/base/test/chrome/window_swapFrameLoaders.xhtml deleted file mode 100644 index 4a38bcc1fc..0000000000 --- a/dom/base/test/chrome/window_swapFrameLoaders.xhtml +++ /dev/null @@ -1,223 +0,0 @@ -<?xml version="1.0"?> -<?xml-stylesheet type="text/css" href="chrome://global/skin"?> -<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=1242644 -Test swapFrameLoaders with different frame types and remoteness ---> -<window title="Mozilla Bug 1242644" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript"><![CDATA[ - ["SimpleTest", "SpecialPowers", "info", "is", "ok", "add_task"].forEach(key => { - window[key] = window.arguments[0][key]; - }) - - const NS = { - xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", - html: "http://www.w3.org/1999/xhtml", - } - - const TAG = { - xul: "browser", - html: "iframe", // mozbrowser - } - - const SCENARIOS = [ - ["xul", "xul"], - ["xul", "html"], - ["html", "xul"], - ["html", "html"], - ["xul", "xul", { remote: true }], - ["xul", "html", { remote: true }], - ["html", "xul", { remote: true }], - ["html", "html", { remote: true }], - ["xul", "html", { userContextId: 2 }], - ["xul", "html", { userContextId: 2, remote: true }], - ]; - - const HEIGHTS = [ - 200, - 400 - ]; - - function frameScript() { - /* eslint-env mozilla/frame-script */ - addEventListener("load", function onLoad() { - sendAsyncMessage("test:load"); - }, true); - } - - // Watch for loads in new frames - window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true); - - function once(target, eventName, useCapture = false) { - info("Waiting for event: '" + eventName + "' on " + target + "."); - - return new Promise(resolve => { - for (let [add, remove] of [ - ["addEventListener", "removeEventListener"], - ["addMessageListener", "removeMessageListener"], - ]) { - if ((add in target) && (remove in target)) { - target[add](eventName, function onEvent(...aArgs) { - info("Got event: '" + eventName + "' on " + target + "."); - target[remove](eventName, onEvent, useCapture); - resolve(aArgs); - }, useCapture); - break; - } - } - }); - } - - async function addFrame(type, options, height) { - let remote = options && options.remote; - let userContextId = options && options.userContextId; - let frame = document.createElementNS(NS[type], TAG[type]); - frame.setAttribute("remote", remote); - if (remote && type == "xul") { - frame.setAttribute("style", "-moz-binding: none;"); - } - if (userContextId) { - frame.setAttribute("usercontextid", userContextId); - } - if (type == "html") { - frame.setAttribute("mozbrowser", "true"); - frame.setAttribute("noisolation", "true"); - frame.setAttribute("allowfullscreen", "true"); - } else if (type == "xul") { - frame.setAttribute("type", "content"); - } - let src = `data:text/html,<!doctype html>` + - `<body style="height:${height}px"/>`; - frame.setAttribute("src", src); - document.documentElement.appendChild(frame); - let mm = frame.frameLoader.messageManager; - await once(mm, "test:load"); - return frame; - } - - add_task(async function() { - for (let scenario of SCENARIOS) { - let [ typeA, typeB, options ] = scenario; - let heightA = HEIGHTS[0]; - info(`Adding frame A, type ${typeA}, options ${JSON.stringify(options)}, height ${heightA}`); - let frameA = await addFrame(typeA, options, heightA); - - let heightB = HEIGHTS[1]; - info(`Adding frame B, type ${typeB}, options ${JSON.stringify(options)}, height ${heightB}`); - let frameB = await addFrame(typeB, options, heightB); - - let frameScriptFactory = function(name) { - /* eslint-env mozilla/frame-script */ - return `function() { - addMessageListener("ping", function() { - sendAsyncMessage("pong", "${name}"); - }); - addMessageListener("check-browser-api", function() { - let exists = "api" in this; - sendAsyncMessage("check-browser-api", { - exists, - running: exists && !this.api._shuttingDown, - }); - }); - addEventListener("pagehide", function({ inFrameSwap }) { - sendAsyncMessage("pagehide", inFrameSwap); - }, {mozSystemGroup: true}); - }`; - } - - // Load frame script into each frame - { - let mmA = frameA.frameLoader.messageManager; - let mmB = frameB.frameLoader.messageManager; - - mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false); - mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false); - } - - // Ping before swap - { - let mmA = frameA.frameLoader.messageManager; - let mmB = frameB.frameLoader.messageManager; - - let inflightA = once(mmA, "pong"); - let inflightB = once(mmB, "pong"); - - info("Ping message manager for frame A"); - mmA.sendAsyncMessage("ping"); - let [ { data: pongA } ] = await inflightA; - is(pongA, "A", "Frame A message manager gets reply A before swap"); - - info("Ping message manager for frame B"); - mmB.sendAsyncMessage("ping"); - let [ { data: pongB } ] = await inflightB; - is(pongB, "B", "Frame B message manager gets reply B before swap"); - } - - // Ping after swap using message managers acquired before - { - let mmA = frameA.frameLoader.messageManager; - let mmB = frameB.frameLoader.messageManager; - - let pagehideA = once(mmA, "pagehide"); - let pagehideB = once(mmB, "pagehide"); - - info("swapFrameLoaders"); - frameA.swapFrameLoaders(frameB); - - let [ { data: inFrameSwapA } ] = await pagehideA; - ok(inFrameSwapA, "Frame A got pagehide with inFrameSwap: true"); - let [ { data: inFrameSwapB } ] = await pagehideB; - ok(inFrameSwapB, "Frame B got pagehide with inFrameSwap: true"); - - let inflightA = once(mmA, "pong"); - let inflightB = once(mmB, "pong"); - - info("Ping message manager for frame A"); - mmA.sendAsyncMessage("ping"); - let [ { data: pongA } ] = await inflightA; - is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap"); - - info("Ping message manager for frame B"); - mmB.sendAsyncMessage("ping"); - let [ { data: pongB } ] = await inflightB; - is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap"); - } - - // Check height after swap - if (frameA.getContentDimensions) { - let { height } = await frameA.getContentDimensions(); - is(height, heightB, "Frame A's content height is 400px after swap"); - } - if (frameB.getContentDimensions) { - let { height } = await frameB.getContentDimensions(); - is(height, heightA, "Frame B's content height is 200px after swap"); - } - - // Ping after swap using message managers acquired after - { - let mmA = frameA.frameLoader.messageManager; - let mmB = frameB.frameLoader.messageManager; - - let inflightA = once(mmA, "pong"); - let inflightB = once(mmB, "pong"); - - info("Ping message manager for frame A"); - mmA.sendAsyncMessage("ping"); - let [ { data: pongA } ] = await inflightA; - is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap"); - - info("Ping message manager for frame B"); - mmB.sendAsyncMessage("ping"); - let [ { data: pongB } ] = await inflightB; - is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap"); - } - - frameA.remove(); - frameB.remove(); - } - }); - ]]></script> -</window> diff --git a/dom/base/test/common_postMessages.js b/dom/base/test/common_postMessages.js index c4836fdd77..044f3e7f34 100644 --- a/dom/base/test/common_postMessages.js +++ b/dom/base/test/common_postMessages.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function getType(a) { if (a === null || a === undefined) { return "null"; diff --git a/dom/base/test/file_bug1008126_worker.js b/dom/base/test/file_bug1008126_worker.js index aaba278de5..4e418d777e 100644 --- a/dom/base/test/file_bug1008126_worker.js +++ b/dom/base/test/file_bug1008126_worker.js @@ -3,6 +3,8 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + var gEntry1 = "data_1.txt"; var gEntry2 = "data_2.txt"; var gEntry3 = "data_big.txt"; diff --git a/dom/base/test/file_bug945152_worker.js b/dom/base/test/file_bug945152_worker.js index 9664045b6d..c0feebf0d6 100644 --- a/dom/base/test/file_bug945152_worker.js +++ b/dom/base/test/file_bug945152_worker.js @@ -1,3 +1,4 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ var gData1 = "TEST_DATA_1:ABCDEFGHIJKLMNOPQRSTUVWXYZ"; var gData2 = "TEST_DATA_2:1234567890"; var gPaddingChar = "."; diff --git a/dom/base/test/file_focus_shadow_dom.html b/dom/base/test/file_focus_shadow_dom.html index 6fa9d1b88e..7a0a0da729 100644 --- a/dom/base/test/file_focus_shadow_dom.html +++ b/dom/base/test/file_focus_shadow_dom.html @@ -79,10 +79,16 @@ opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)"); - synthesizeKey("KEY_Tab"); - opener.is(shadowIframe.contentDocument.activeElement, - shadowIframe.contentDocument.documentElement, - "Should have focused document element in shadow iframe. (3)"); + + let canTabMoveFocusToRootElement = + !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element"); + if (canTabMoveFocusToRootElement) { + synthesizeKey("KEY_Tab"); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.documentElement, + "Should have focused document element in shadow iframe. (3)"); + } + synthesizeKey("KEY_Tab"); opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.body.firstChild, @@ -99,10 +105,12 @@ opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.body.firstChild, "Should have focused input element in shadow iframe. (4)"); - synthesizeKey("KEY_Tab", {shiftKey: true}); - opener.is(shadowIframe.contentDocument.activeElement, - shadowIframe.contentDocument.documentElement, - "Should have focused document element in shadow iframe. (4)"); + if (canTabMoveFocusToRootElement) { + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.documentElement, + "Should have focused document element in shadow iframe. (4)"); + } synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); diff --git a/dom/base/test/file_img_attachment.jpg b/dom/base/test/file_img_attachment.jpg Binary files differnew file mode 100644 index 0000000000..dcd99b9670 --- /dev/null +++ b/dom/base/test/file_img_attachment.jpg diff --git a/dom/base/test/file_img_attachment.jpg^headers^ b/dom/base/test/file_img_attachment.jpg^headers^ new file mode 100644 index 0000000000..71ed8e7805 --- /dev/null +++ b/dom/base/test/file_img_attachment.jpg^headers^ @@ -0,0 +1 @@ +Content-Disposition: attachment diff --git a/dom/base/test/file_img_object_attachment.html b/dom/base/test/file_img_object_attachment.html new file mode 100644 index 0000000000..502894041e --- /dev/null +++ b/dom/base/test/file_img_object_attachment.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <body> + <object data="file_img_attachment.jpg" type="image/jpeg" width="100%" height="600">fallback</object> + </body> +</html> diff --git a/dom/base/test/file_pdf_attachment.pdf b/dom/base/test/file_pdf_attachment.pdf Binary files differnew file mode 100644 index 0000000000..89066463f1 --- /dev/null +++ b/dom/base/test/file_pdf_attachment.pdf diff --git a/dom/base/test/file_pdf_attachment.pdf^headers^ b/dom/base/test/file_pdf_attachment.pdf^headers^ new file mode 100644 index 0000000000..562009a8de --- /dev/null +++ b/dom/base/test/file_pdf_attachment.pdf^headers^ @@ -0,0 +1,2 @@ +Content-Type: application/octet-stream +Content-Disposition: attachment diff --git a/dom/base/test/file_pdf_object_attachment.html b/dom/base/test/file_pdf_object_attachment.html new file mode 100644 index 0000000000..d87eff9923 --- /dev/null +++ b/dom/base/test/file_pdf_object_attachment.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <body> + <object data="file_pdf_attachment.pdf" type="application/pdf" width="100%" height="600">fallback</object> + </body> +</html> diff --git a/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml index 93f00311e7..bc92abf3e0 100644 --- a/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml +++ b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml @@ -79,7 +79,7 @@ function thirdEntry(event) { gOuterDoc.exitFullscreen(); } -function earlyExit(event) { +function earlyExit() { ok(false, "MozDOMFullscreen:Exited should only be triggered after cancel all fullscreen"); } diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js index 2ea2b9ee40..49a48c3177 100644 --- a/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js +++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js @@ -82,7 +82,7 @@ function preventBFCache(aBrowsingContext, aPrevent) { let target = content.document.getElementById("div"); target.addEventListener( "mousedown", - function (e) { + function () { content.window.history.back(); }, { once: true } diff --git a/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js index 10d10a0b0f..1338c4a550 100644 --- a/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js +++ b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js @@ -70,7 +70,7 @@ async function WaitRemoveDocumentAndCloseTab(aBrowser, aBrowsingContext) { return new Promise(resolve => { content.document.addEventListener( "fullscreenchange", - e => { + () => { resolve(); }, { once: true } diff --git a/dom/base/test/fullscreen/file_fullscreen-api.html b/dom/base/test/fullscreen/file_fullscreen-api.html index 645e6ece46..9661b10de9 100644 --- a/dom/base/test/fullscreen/file_fullscreen-api.html +++ b/dom/base/test/fullscreen/file_fullscreen-api.html @@ -112,7 +112,7 @@ function enter2(event) { promise = document.exitFullscreen(); } -function exit2(event) { +function exit2() { is(document.fullscreenElement, null, "Full-screen element should have rolled back."); is(iframe.contentDocument.fullscreenElement, null, @@ -156,7 +156,7 @@ function exit3(event) { promise = outOfDocElement.requestFullscreen(); } -function error1(event) { +function error1() { ok(!document.fullscreenElement, "Requests for full-screen from not-in-doc elements should fail."); assertPromiseRejected(promise, "in error1"); @@ -181,7 +181,7 @@ function enter4(event) { "Should not have a full-screen element again."); } -async function exit_to_arg_test_1(event) { +async function exit_to_arg_test_1() { ok(!document.fullscreenElement, "Should have left full-screen mode (third time)."); addFullscreenChangeContinuation("enter", enter_from_arg_test_1); @@ -196,14 +196,14 @@ async function exit_to_arg_test_1(event) { ok(!threw, "requestFullscreen with bogus arg (123) shouldn't throw exception"); } -function enter_from_arg_test_1(event) { +function enter_from_arg_test_1() { ok(document.fullscreenElement, "Should have entered full-screen after calling with bogus (ignored) argument (fourth time)"); addFullscreenChangeContinuation("exit", exit_to_arg_test_2); document.exitFullscreen(); } -async function exit_to_arg_test_2(event) { +async function exit_to_arg_test_2() { ok(!document.fullscreenElement, "Should have left full-screen mode (fourth time)."); addFullscreenChangeContinuation("enter", enter_from_arg_test_2); @@ -218,14 +218,14 @@ async function exit_to_arg_test_2(event) { ok(!threw, "requestFullscreen with { vrDisplay: null } shouldn't throw exception"); } -function enter_from_arg_test_2(event) { +function enter_from_arg_test_2() { ok(document.fullscreenElement, "Should have entered full-screen after calling with vrDisplay null argument (fifth time)"); addFullscreenChangeContinuation("exit", exit4); document.exitFullscreen(); } -function exit4(event) { +function exit4() { ok(!document.fullscreenElement, "Should be back in non-full-screen mode (fifth time)"); SpecialPowers.pushPrefEnv({"set":[["full-screen-api.allow-trusted-requests-only", true]]}, function() { @@ -234,7 +234,7 @@ function exit4(event) { }); } -function error2(event) { +function error2() { ok(!document.fullscreenElement, "Should still be in normal mode, because calling context isn't trusted."); button = document.createElement("button"); @@ -246,13 +246,13 @@ function error2(event) { sendMouseClick(button); } -function enter5(event) { +function enter5() { ok(document.fullscreenElement, "Moved to full-screen after mouse click"); addFullscreenChangeContinuation("exit", exit5); document.exitFullscreen(); } -function exit5(event) { +function exit5() { ok(!document.fullscreenElement, "Should have left full-screen mode (last time)."); SpecialPowers.pushPrefEnv({ @@ -264,7 +264,7 @@ function exit5(event) { }); } -function error3(event) { +function error3() { ok(!document.fullscreenElement, "Should still be in normal mode, because pref is not enabled."); diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html index 61db80c228..48f2dea09d 100644 --- a/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html +++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html @@ -2,7 +2,7 @@ <button>Launch</button> <script> let button = document.querySelector("button"); -button.addEventListener("click", function(e) { +button.addEventListener("click", function() { let newWindow = window.open("", "", "newWindow"); newWindow.document.write(`<!DOCTYPE HTML> <button>click me!</button> diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html index 7490f12936..beafd79661 100644 --- a/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html +++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html @@ -2,7 +2,7 @@ <button>click me!</button> <script> let button = document.querySelector("button"); -button.addEventListener("click", function(e) { +button.addEventListener("click", function() { document.documentElement.requestFullscreen(); setTimeout(() => { while(true) { diff --git a/dom/base/test/fullscreen/file_fullscreen-denied.html b/dom/base/test/fullscreen/file_fullscreen-denied.html index db9a69e71a..fe4244ec7f 100644 --- a/dom/base/test/fullscreen/file_fullscreen-denied.html +++ b/dom/base/test/fullscreen/file_fullscreen-denied.html @@ -137,7 +137,7 @@ function requestFullscreenMouseBtn(event, button) { synthesizeMouseAtCenter(clickEl, { button }); } -async function testFullscreenMouseBtn(event, button, next) { +async function testFullscreenMouseBtn() { await SpecialPowers.pushPrefEnv({ "set": [["full-screen-api.mouse-event-allow-left-button-only", true]] }); diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html index d7d8a90aaf..210b2af1b6 100644 --- a/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html +++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html @@ -33,7 +33,7 @@ function is(a, b, msg) { var escKeyReceived = false; var escKeySent = false; -function keyHandler(event) { +function keyHandler() { if (escKeyReceived == KeyboardEvent.DOM_VK_ESC) { escKeyReceived = true; } diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html index f65f930b3f..1e2252f1ab 100644 --- a/dom/base/test/fullscreen/file_fullscreen-esc-exit.html +++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html @@ -34,14 +34,14 @@ function finish() { opener.nextTest(); } -function fullscreenchange1(event) { +function fullscreenchange1() { is(document.fullscreenElement, document.body, "FSE should be doc"); addFullscreenChangeContinuation("exit", fullscreenchange2); ok(!document.getElementById("subdoc").contentWindow.escKeySent, "Should not yet have sent ESC key press."); document.getElementById("subdoc").contentWindow.startTest(); } -function fullscreenchange2(event) { +function fullscreenchange2() { ok(document.getElementById("subdoc").contentWindow.escKeySent, "Should have sent ESC key press."); ok(!document.getElementById("subdoc").contentWindow.escKeyReceived, "ESC key press to exit should not be delivered."); ok(!document.fullscreenElement, "Should have left full-screen mode on ESC key press"); diff --git a/dom/base/test/fullscreen/file_fullscreen-shadowdom.html b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html index 348e08ae87..1972e1457e 100644 --- a/dom/base/test/fullscreen/file_fullscreen-shadowdom.html +++ b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html @@ -29,7 +29,7 @@ var elem = shadowRoot.firstChild; var gotFullscreenEvent = false; - document.addEventListener("fullscreenchange", function (e) { + document.addEventListener("fullscreenchange", function () { if (document.fullscreenElement === host) { is(shadowRoot.fullscreenElement, elem, "Expected element entered fullsceen"); diff --git a/dom/base/test/fullscreen/file_fullscreen-svg-element.html b/dom/base/test/fullscreen/file_fullscreen-svg-element.html index 1dfc78aa1c..d03a13ee84 100644 --- a/dom/base/test/fullscreen/file_fullscreen-svg-element.html +++ b/dom/base/test/fullscreen/file_fullscreen-svg-element.html @@ -32,7 +32,7 @@ var elem = document.getElementById("svg-elem") , elemWasLocked = false; - document.addEventListener("fullscreenchange", function (e) { + document.addEventListener("fullscreenchange", function () { if (document.fullscreenElement === elem) { elemWasLocked = true; document.exitFullscreen(); diff --git a/dom/base/test/fullscreen/fullscreen.xhtml b/dom/base/test/fullscreen/fullscreen.xhtml index 2cc95642b6..76986db540 100644 --- a/dom/base/test/fullscreen/fullscreen.xhtml +++ b/dom/base/test/fullscreen/fullscreen.xhtml @@ -11,7 +11,7 @@ window.addEventListener("fullscreen", onFullScreen, true); -function onFullScreen(event) +function onFullScreen() { window.arguments[0].done(window.fullScreen); } diff --git a/dom/base/test/fullscreen/fullscreen_helpers.js b/dom/base/test/fullscreen/fullscreen_helpers.js index 6e78015cd8..f097ae316e 100644 --- a/dom/base/test/fullscreen/fullscreen_helpers.js +++ b/dom/base/test/fullscreen/fullscreen_helpers.js @@ -83,7 +83,7 @@ function waitWidgetFullscreenEvent( aIsInFullscreen, aWaitUntil = false ) { - return BrowserTestUtils.waitForEvent(aWindow, "fullscreen", false, aEvent => { + return BrowserTestUtils.waitForEvent(aWindow, "fullscreen", false, () => { if ( aWaitUntil && aIsInFullscreen != @@ -106,7 +106,7 @@ function waitForFullScreenObserver( aIsInFullscreen, aWaitUntil = false ) { - return TestUtils.topicObserved("fullscreen-painted", (subject, data) => { + return TestUtils.topicObserved("fullscreen-painted", () => { if ( aWaitUntil && aIsInFullscreen != diff --git a/dom/base/test/fullscreen/test_fullscreen-api.html b/dom/base/test/fullscreen/test_fullscreen-api.html index 2a59d6eeb0..ef07a5f97f 100644 --- a/dom/base/test/fullscreen/test_fullscreen-api.html +++ b/dom/base/test/fullscreen/test_fullscreen-api.html @@ -111,7 +111,7 @@ function runNextTest() { // otherwise, the fullscreen request might be denied. if (testWindow.document.hidden) { info("Waiting for document to unhide"); - waitForEvent(testWindow.document, "visibilitychange", (event) => { + waitForEvent(testWindow.document, "visibilitychange", () => { return !testWindow.document.hidden; }, testWindow.begin); return; diff --git a/dom/base/test/fullscreen/test_fullscreen_modal.html b/dom/base/test/fullscreen/test_fullscreen_modal.html index 78e70d9052..fef4e3867d 100644 --- a/dom/base/test/fullscreen/test_fullscreen_modal.html +++ b/dom/base/test/fullscreen/test_fullscreen_modal.html @@ -21,7 +21,7 @@ const button = document.querySelector("button"); let clickCount = 0; let lastFullscreenPromise = null; let shouldEnterFullscreen = false; -button.addEventListener("click", function(e) { +button.addEventListener("click", function() { clickCount++; if (shouldEnterFullscreen) { const fullscreenElement = document.getElementById("fullscreen"); @@ -58,6 +58,15 @@ async function testFullscreenIsModal(modal) { ok(document.fullscreenElement.matches(":fullscreen"), "Fullscreen element matches :fullscreen"); is(document.fullscreenElement.matches(":modal"), modal, "Fullscreen element matches :modal"); + // Before exiting fullscreen, wait on the document becoming active, + // which signifies that the fullscreen request has been completely + // processed. It is important that the fullscreen request is all + // the way done, or the exit fullscreen request could be dropped. + let documentWrapper = SpecialPowers.wrap(document); + await SimpleTest.promiseWaitForCondition(() => documentWrapper.isActive(), + "Timed out waiting for document to become active."); + ok(documentWrapper.isActive(), "Document is active."); + await document.exitFullscreen(); clickButton(/* expectEvent = */ true); } diff --git a/dom/base/test/gtest/TestMimeType.cpp b/dom/base/test/gtest/TestMimeType.cpp index fecb3f8678..40916130a8 100644 --- a/dom/base/test/gtest/TestMimeType.cpp +++ b/dom/base/test/gtest/TestMimeType.cpp @@ -9,12 +9,10 @@ #include "MimeType.h" #include "nsString.h" -using mozilla::UniquePtr; - TEST(MimeType, EmptyString) { const auto in = u""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Empty string"; } @@ -22,7 +20,7 @@ TEST(MimeType, EmptyString) TEST(MimeType, JustWhitespace) { const auto in = u" \t\r\n "_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Just whitespace"; } @@ -30,7 +28,7 @@ TEST(MimeType, JustWhitespace) TEST(MimeType, JustBackslash) { const auto in = u"\\"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Just backslash"; } @@ -38,7 +36,7 @@ TEST(MimeType, JustBackslash) TEST(MimeType, JustForwardslash) { const auto in = u"/"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Just forward slash"; } @@ -46,7 +44,7 @@ TEST(MimeType, JustForwardslash) TEST(MimeType, MissingType1) { const auto in = u"/bogus"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing type #1"; } @@ -54,7 +52,7 @@ TEST(MimeType, MissingType1) TEST(MimeType, MissingType2) { const auto in = u" \r\n\t/bogus"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing type #2"; } @@ -62,7 +60,7 @@ TEST(MimeType, MissingType2) TEST(MimeType, MissingSubtype1) { const auto in = u"bogus"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing subtype #1"; } @@ -70,7 +68,7 @@ TEST(MimeType, MissingSubtype1) TEST(MimeType, MissingSubType2) { const auto in = u"bogus/"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing subtype #2"; } @@ -78,7 +76,7 @@ TEST(MimeType, MissingSubType2) TEST(MimeType, MissingSubType3) { const auto in = u"bogus;"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing subtype #3"; } @@ -86,7 +84,7 @@ TEST(MimeType, MissingSubType3) TEST(MimeType, MissingSubType4) { const auto in = u"bogus; \r\n\t"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Missing subtype #3"; } @@ -94,7 +92,7 @@ TEST(MimeType, MissingSubType4) TEST(MimeType, ExtraForwardSlash) { const auto in = u"bogus/bogus/;"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Extra forward slash"; } @@ -102,7 +100,7 @@ TEST(MimeType, ExtraForwardSlash) TEST(MimeType, WhitespaceInType) { const auto in = u"t\re\nx\tt /html"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Type with whitespace"; } @@ -110,7 +108,7 @@ TEST(MimeType, WhitespaceInType) TEST(MimeType, WhitespaceInSubtype) { const auto in = u"text/ h\rt\nm\tl"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Subtype with whitespace"; } @@ -118,7 +116,7 @@ TEST(MimeType, WhitespaceInSubtype) TEST(MimeType, NonAlphanumericMediaType1) { const auto in = u"</>"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #1"; } @@ -126,7 +124,7 @@ TEST(MimeType, NonAlphanumericMediaType1) TEST(MimeType, NonAlphanumericMediaType2) { const auto in = u"(/)"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #2"; } @@ -134,7 +132,7 @@ TEST(MimeType, NonAlphanumericMediaType2) TEST(MimeType, NonAlphanumericMediaType3) { const auto in = u"{/}"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #3"; } @@ -142,7 +140,7 @@ TEST(MimeType, NonAlphanumericMediaType3) TEST(MimeType, NonAlphanumericMediaType4) { const auto in = u"\"/\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #4"; } @@ -150,7 +148,7 @@ TEST(MimeType, NonAlphanumericMediaType4) TEST(MimeType, NonAlphanumericMediaType5) { const auto in = u"\0/\0"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #5"; } @@ -158,7 +156,7 @@ TEST(MimeType, NonAlphanumericMediaType5) TEST(MimeType, NonAlphanumericMediaType6) { const auto in = u"text/html(;doesnot=matter"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-alphanumeric media type #6"; } @@ -166,7 +164,7 @@ TEST(MimeType, NonAlphanumericMediaType6) TEST(MimeType, NonLatin1MediaType1) { const auto in = u"ÿ/ÿ"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-latin1 media type #1"; } @@ -174,7 +172,7 @@ TEST(MimeType, NonLatin1MediaType1) TEST(MimeType, NonLatin1MediaType2) { const auto in = u"\x0100/\x0100"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_FALSE(parsed) << "Non-latin1 media type #2"; } @@ -182,7 +180,7 @@ TEST(MimeType, NonLatin1MediaType2) TEST(MimeType, MultipleParameters) { const auto in = u"text/html;charset=gbk;no=1;charset_=gbk_;yes=2"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsString out; @@ -194,7 +192,7 @@ TEST(MimeType, MultipleParameters) TEST(MimeType, DuplicateParameter1) { const auto in = u"text/html;charset=gbk;charset=windows-1255"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsString out; @@ -206,7 +204,7 @@ TEST(MimeType, DuplicateParameter1) TEST(MimeType, DuplicateParameter2) { const auto in = u"text/html;charset=();charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsString out; @@ -218,7 +216,7 @@ TEST(MimeType, DuplicateParameter2) TEST(MimeType, CString) { const auto in = "text/html;charset=();charset=GBK"_ns; - UniquePtr<CMimeType> parsed = CMimeType::Parse(in); + RefPtr<CMimeType> parsed = CMimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsCString out; @@ -234,7 +232,7 @@ TEST(MimeType, CString) TEST(MimeType, NonAlphanumericParametersAreQuoted) { const auto in = u"text/html;test=\x00FF\\;charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsString out; @@ -249,7 +247,7 @@ TEST(MimeType, NonAlphanumericParametersAreQuoted) TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace1) { const auto in = u"text/html;charset= g\\\"bk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -261,7 +259,7 @@ TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace1) TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace2) { const auto in = u"text/html;charset= \"g\\bk\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -273,7 +271,7 @@ TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace2) TEST(MimeType, ParameterQuotedIfHasInternalWhitespace) { const auto in = u"text/html;charset=g \\b\"k"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -285,7 +283,7 @@ TEST(MimeType, ParameterQuotedIfHasInternalWhitespace) TEST(MimeType, ImproperlyQuotedParameter1) { const auto in = u"x/x;test=\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -297,7 +295,7 @@ TEST(MimeType, ImproperlyQuotedParameter1) TEST(MimeType, ImproperlyQuotedParameter2) { const auto in = u"x/x;test=\"\\"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -309,7 +307,7 @@ TEST(MimeType, ImproperlyQuotedParameter2) TEST(MimeType, NonLatin1ParameterIgnored) { const auto in = u"x/x;test=\xFFFD;x=x"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -321,7 +319,7 @@ TEST(MimeType, NonLatin1ParameterIgnored) TEST(MimeType, ParameterIgnoredIfWhitespaceInName1) { const auto in = u"text/html;charset =gbk;charset=123"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -333,7 +331,7 @@ TEST(MimeType, ParameterIgnoredIfWhitespaceInName1) TEST(MimeType, ParameterIgnoredIfWhitespaceInName2) { const auto in = u"text/html;cha rset =gbk;charset=123"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -345,7 +343,7 @@ TEST(MimeType, ParameterIgnoredIfWhitespaceInName2) TEST(MimeType, WhitespaceTrimmed) { const auto in = u"\n\r\t text/plain\n\r\t ;\n\r\t charset=123\n\r\t "_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -357,7 +355,7 @@ TEST(MimeType, WhitespaceTrimmed) TEST(MimeType, WhitespaceOnlyParameterIgnored) { const auto in = u"x/x;x= \r\n\t"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -369,7 +367,7 @@ TEST(MimeType, WhitespaceOnlyParameterIgnored) TEST(MimeType, IncompleteParameterIgnored1) { const auto in = u"x/x;test"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -381,7 +379,7 @@ TEST(MimeType, IncompleteParameterIgnored1) TEST(MimeType, IncompleteParameterIgnored2) { const auto in = u"x/x;test="_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -393,7 +391,7 @@ TEST(MimeType, IncompleteParameterIgnored2) TEST(MimeType, IncompleteParameterIgnored3) { const auto in = u"x/x;test= \r\n\t"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -405,7 +403,7 @@ TEST(MimeType, IncompleteParameterIgnored3) TEST(MimeType, IncompleteParameterIgnored4) { const auto in = u"text/html;test;charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -417,7 +415,7 @@ TEST(MimeType, IncompleteParameterIgnored4) TEST(MimeType, IncompleteParameterIgnored5) { const auto in = u"text/html;test=;charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -429,7 +427,7 @@ TEST(MimeType, IncompleteParameterIgnored5) TEST(MimeType, EmptyParameterIgnored1) { const auto in = u"text/html ; ; charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -441,7 +439,7 @@ TEST(MimeType, EmptyParameterIgnored1) TEST(MimeType, EmptyParameterIgnored2) { const auto in = u"text/html;;;;charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -453,7 +451,7 @@ TEST(MimeType, EmptyParameterIgnored2) TEST(MimeType, InvalidParameterIgnored1) { const auto in = u"text/html;';charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -465,7 +463,7 @@ TEST(MimeType, InvalidParameterIgnored1) TEST(MimeType, InvalidParameterIgnored2) { const auto in = u"text/html;\";charset=gbk;=123; =321"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -477,7 +475,7 @@ TEST(MimeType, InvalidParameterIgnored2) TEST(MimeType, InvalidParameterIgnored3) { const auto in = u"text/html;charset= \"\u007F;charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -490,7 +488,7 @@ TEST(MimeType, InvalidParameterIgnored4) { const auto in = nsLiteralString( u"text/html;charset=\"\u007F;charset=foo\";charset=GBK;charset="); - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -502,7 +500,7 @@ TEST(MimeType, InvalidParameterIgnored4) TEST(MimeType, SingleQuotes1) { const auto in = u"text/html;charset='gbk'"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -514,7 +512,7 @@ TEST(MimeType, SingleQuotes1) TEST(MimeType, SingleQuotes2) { const auto in = u"text/html;charset='gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -526,7 +524,7 @@ TEST(MimeType, SingleQuotes2) TEST(MimeType, SingleQuotes3) { const auto in = u"text/html;charset=gbk'"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -538,7 +536,7 @@ TEST(MimeType, SingleQuotes3) TEST(MimeType, SingleQuotes4) { const auto in = u"text/html;charset=';charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -550,7 +548,7 @@ TEST(MimeType, SingleQuotes4) TEST(MimeType, SingleQuotes5) { const auto in = u"text/html;charset=''';charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -562,7 +560,7 @@ TEST(MimeType, SingleQuotes5) TEST(MimeType, DoubleQuotes1) { const auto in = u"text/html;charset=\"gbk\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -574,7 +572,7 @@ TEST(MimeType, DoubleQuotes1) TEST(MimeType, DoubleQuotes2) { const auto in = u"text/html;charset=\"gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -586,7 +584,7 @@ TEST(MimeType, DoubleQuotes2) TEST(MimeType, DoubleQuotes3) { const auto in = u"text/html;charset=gbk\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -598,7 +596,7 @@ TEST(MimeType, DoubleQuotes3) TEST(MimeType, DoubleQuotes4) { const auto in = u"text/html;charset=\" gbk\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -610,7 +608,7 @@ TEST(MimeType, DoubleQuotes4) TEST(MimeType, DoubleQuotes5) { const auto in = u"text/html;charset=\"gbk \""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -622,7 +620,7 @@ TEST(MimeType, DoubleQuotes5) TEST(MimeType, DoubleQuotes6) { const auto in = u"text/html;charset=\"\\ gbk\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -634,7 +632,7 @@ TEST(MimeType, DoubleQuotes6) TEST(MimeType, DoubleQuotes7) { const auto in = u"text/html;charset=\"\\g\\b\\k\""_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -646,7 +644,7 @@ TEST(MimeType, DoubleQuotes7) TEST(MimeType, DoubleQuotes8) { const auto in = u"text/html;charset=\"gbk\"x"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -658,7 +656,7 @@ TEST(MimeType, DoubleQuotes8) TEST(MimeType, DoubleQuotes9) { const auto in = u"text/html;charset=\"\";charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -670,7 +668,7 @@ TEST(MimeType, DoubleQuotes9) TEST(MimeType, DoubleQuotes10) { const auto in = u"text/html;charset=\";charset=GBK"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -682,7 +680,7 @@ TEST(MimeType, DoubleQuotes10) TEST(MimeType, UnexpectedCodePoints) { const auto in = u"text/html;charset={gbk}"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -699,7 +697,7 @@ TEST(MimeType, LongTypesSubtypesAccepted) "2345678901234567890123456789012345678901234567890123456789/" "012345678901234567890123456789012345678901234567890123456789012345678901" "2345678901234567890123456789012345678901234567890123456789"); - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -716,7 +714,7 @@ TEST(MimeType, LongParametersAccepted) "012345678901234567890123456789012345678901234567890123456789012345678901" "2345678901234567890123456789012345678901234567890123456789=x;charset=" "gbk"); - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -744,7 +742,7 @@ TEST(MimeType, AllValidCharactersAccepted1) u"\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED" u"\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8" u"\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\""); - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -756,7 +754,7 @@ TEST(MimeType, AllValidCharactersAccepted1) TEST(MimeType, CaseNormalization1) { const auto in = u"TEXT/PLAIN;CHARSET=TEST"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -775,7 +773,7 @@ TEST(MimeType, CaseNormalization2) ".^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$" "%&'*+-.^_`|~" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -794,7 +792,7 @@ TEST(MimeType, CaseNormalization2) TEST(MimeType, LegacyCommentSyntax1) { const auto in = u"text/html;charset=gbk("_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; @@ -806,7 +804,7 @@ TEST(MimeType, LegacyCommentSyntax1) TEST(MimeType, LegacyCommentSyntax2) { const auto in = u"text/html;x=(;charset=gbk"_ns; - UniquePtr<MimeType> parsed = MimeType::Parse(in); + RefPtr<MimeType> parsed = MimeType::Parse(in); ASSERT_TRUE(parsed) << "Parsing succeeded"; nsAutoString out; diff --git a/dom/base/test/meta_viewport/viewport_helpers.js b/dom/base/test/meta_viewport/viewport_helpers.js index d4d346b5d0..cad6613549 100644 --- a/dom/base/test/meta_viewport/viewport_helpers.js +++ b/dom/base/test/meta_viewport/viewport_helpers.js @@ -40,5 +40,6 @@ function getViewportInfo(aDisplayWidth, aDisplayHeight) { } function fuzzeq(a, b, msg) { + // eslint-disable-next-line mozilla/no-comparison-or-assignment-inside-ok ok(Math.abs(a - b) < 1e-6, msg); } diff --git a/dom/base/test/mochitest.toml b/dom/base/test/mochitest.toml index 6415c4b33b..fb6724e497 100644 --- a/dom/base/test/mochitest.toml +++ b/dom/base/test/mochitest.toml @@ -3,7 +3,6 @@ tags = "condprof" prefs = [ "formhelper.autozoom.force-disable.test-only=true", "plugins.rewrite_youtube_embeds=true", - "dom.domrequest.enabled=true", ] support-files = [ "audio.ogg", @@ -1232,8 +1231,6 @@ skip-if = [ ["test_domparsing.html"] -["test_domrequest.html"] - ["test_domwindowutils.html"] skip-if = ["os == 'android'"] # Bug 1525959 @@ -1301,6 +1298,8 @@ skip-if = [ "http2", ] +["test_focus_radio.html"] + ["test_focus_scroll_padding_tab.html"] ["test_focus_scrollable_fieldset.html"] diff --git a/dom/base/test/test_domrequest.html b/dom/base/test/test_domrequest.html deleted file mode 100644 index 1aea26f657..0000000000 --- a/dom/base/test/test_domrequest.html +++ /dev/null @@ -1,233 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <title>Test for DOMRequest</title> - <script src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<p id="display"></p> -<div id="content" style="display: none"> - -</div> -<pre id="test"> -<script class="testbody" type="application/javascript"> -"use strict"; - -var reqserv = SpecialPowers.getDOMRequestService(); -ok("createRequest" in reqserv, "appears to be a service"); - -function testBasics() { - // create a request - var req = reqserv.createRequest(window); - ok("result" in req, "request has result"); - ok("error" in req, "request has error"); - ok("onsuccess" in req, "request has onsuccess"); - ok("onerror" in req, "request has onerror"); - ok("readyState" in req, "request has readyState"); - ok("then" in req, "request has then"); - - is(req.readyState, "pending", "readyState is pending"); - is(req.result, undefined, "result is undefined"); - is(req.onsuccess, null, "onsuccess is null"); - is(req.onerror, null, "onerror is null"); - - runTest(); -} - -function testSuccess() { - // fire success - var req = reqserv.createRequest(window); - var ev = null; - req.onsuccess = function(e) { - ev = e; - } - var result = null; - var promise = req.then(function(r) { - is(r, "my result", "correct result when resolving the promise"); - result = r; - runTest(); - }, function(e) { - ok(false, "promise should not be rejected"); - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - reqserv.fireSuccess(req, "my result"); - ok(ev, "got success event"); - is(ev.type, "success", "correct type during success"); - is(ev.target, req, "correct target during success"); - is(req.readyState, "done", "correct readyState after success"); - is(req.error, null, "correct error after success"); - is(req.result, "my result", "correct result after success"); - is(result, null, "Promise should not be resolved synchronously"); -} - -function testError() { - // fire error - var req = reqserv.createRequest(window); - var ev = null; - req.onerror = function(e) { - ev = e; - } - var error = null; - var promise = req.then(function(r) { - ok(false, "promise should not be resolved"); - runTest(); - }, function(e) { - ok(e instanceof DOMException, "got error rejection"); - ok(e === req.error, "got correct error when rejecting the promise"); - error = e; - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - reqserv.fireError(req, "OhMyError"); - ok(ev, "got error event"); - is(ev.type, "error", "correct type during error"); - is(ev.target, req, "correct target during error"); - is(req.readyState, "done", "correct readyState after error"); - is(req.error.name, "UnknownError", "correct error type after error"); - is(req.error.message, "OhMyError", "correct error message after error"); - is(req.result, undefined, "correct result after error"); - is(error, null, "Promise should not be rejected synchronously"); -} - -function testDetailedError() { - // fire detailed error - var req = reqserv.createRequest(window); - var ev = null; - req.onerror = function(e) { - ev = e; - }; - var error = null; - var promise = req.then(function(r) { - ok(false, "promise should not be resolved"); - runTest(); - }, function(e) { - ok(e instanceof DOMException, "got error rejection"); - ok(e === req.error, "got correct error when rejecting the promise"); - error = e; - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - SpecialPowers.wrwp(req).fireDetailedError(new DOMException("detailedError")); - ok(ev, "got error event"); - is(ev.type, "error", "correct type during error"); - is(ev.target, req, "correct target during error"); - is(req.readyState, "done", "correct readyState after error"); - is(req.error.name, "UnknownError", "correct error type after error"); - is(req.error.message, "detailedError", "correct error message after error"); - is(req.result, undefined, "correct result after error"); - is(error, null, "Promise should not be rejected synchronously"); -} - -function testThenAfterSuccess() { - // fire success - var req = reqserv.createRequest(window); - var ev = null; - req.onsuccess = function(e) { - ev = e; - } - reqserv.fireSuccess(req, "my result"); - ok(ev, "got success event"); - is(ev.type, "success", "correct type during success"); - is(ev.target, req, "correct target during success"); - is(req.readyState, "done", "correct readyState after success"); - is(req.error, null, "correct error after success"); - is(req.result, "my result", "correct result after success"); - var result = null; - var promise = req.then(function(r) { - is(r, "my result", "correct result when resolving the promise"); - result = r; - runTest(); - }, function(e) { - ok(false, "promise should not be rejected"); - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - is(result, null, "Promise should not be resolved synchronously"); -} - -function testThenAfterError() { - // fire error - var req = reqserv.createRequest(window); - var ev = null; - req.onerror = function(e) { - ev = e; - } - reqserv.fireError(req, "OhMyError"); - ok(ev, "got error event"); - is(ev.type, "error", "correct type during error"); - is(ev.target, req, "correct target during error"); - is(req.readyState, "done", "correct readyState after error"); - is(req.error.name, "UnknownError", "correct error type after error"); - is(req.error.message, "OhMyError", "correct error message after error"); - is(req.result, undefined, "correct result after error"); - var error = null; - var promise = req.then(function(r) { - ok(false, "promise should not be resolved"); - runTest(); - }, function(e) { - ok(e instanceof DOMException, "got error rejection"); - ok(e === req.error, "got correct error when rejecting the promise"); - error = e; - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - is(error, null, "Promise should not be rejected synchronously"); -} - -function testDetailedError() { - // fire detailed error - var req = reqserv.createRequest(window); - var ev = null; - req.onerror = function(e) { - ev = e; - }; - var error = null; - var promise = req.then(function(r) { - ok(false, "promise should not be resolved"); - runTest(); - }, function(e) { - ok(e instanceof DOMException, "got error rejection"); - ok(e === req.error, "got correct error when rejecting the promise"); - error = e; - runTest(); - }); - ok(promise instanceof Promise, "then() should return a Promise"); - SpecialPowers.wrap(req).fireDetailedError(new DOMException("detailedError")); - ok(ev, "got error event"); - is(ev.type, "error", "correct type during error"); - is(ev.target, req, "correct target during error"); - is(req.readyState, "done", "correct readyState after error"); - is(req.error.name, "Error", "correct error type after error"); - is(req.error.message, "detailedError", "correct error message after error"); - is(req.result, undefined, "correct result after error"); - is(error, null, "Promise should not be rejected synchronously"); -} - -var tests = [ - testBasics, - testSuccess, - testError, - testDetailedError, - testThenAfterSuccess, - testThenAfterError, -]; - -function runTest() { - if (!tests.length) { - SimpleTest.finish(); - return; - } - - var test = tests.shift(); - test(); -} - -SimpleTest.waitForExplicitFinish(); -runTest(); - -</script> -</pre> -</body> -</html> diff --git a/dom/base/test/test_domrequesthelper.xhtml b/dom/base/test/test_domrequesthelper.xhtml deleted file mode 100644 index 6b9061b8a2..0000000000 --- a/dom/base/test/test_domrequesthelper.xhtml +++ /dev/null @@ -1,549 +0,0 @@ -<?xml version="1.0"?> -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<?xml-stylesheet href="chrome://global/skin" type="text/css"?> -<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> - -<window title="DOMRequestHelper Test" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - onload="start();"> - - <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> - - <script type="application/javascript"> - <![CDATA[ - const {DOMRequestIpcHelper} = ChromeUtils.importESModule( - "resource://gre/modules/DOMRequestHelper.sys.mjs" - ); - let obs = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(); - - function DummyHelperSubclass() { - this.onuninit = null; - } - DummyHelperSubclass.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - uninit() { - if (typeof this.onuninit === "function") { - this.onuninit(); - } - this.onuninit = null; - } - }; - - var dummy = new DummyHelperSubclass(); - var isDOMRequestHelperDestroyed = false; - - /** - * Init & destroy. - */ - function initDOMRequestHelperTest(aMessages) { - // If we haven't initialized the DOMRequestHelper object, its private - // properties will be undefined, but once destroyDOMRequestHelper is - // called, they're set to null. - var expectedPrivatePropertyValues = - isDOMRequestHelperDestroyed ? null : undefined; - - is(dummy._requests, expectedPrivatePropertyValues, "Request is expected"); - is(dummy._messages, undefined, "Messages is undefined"); - is(dummy._window, expectedPrivatePropertyValues, "Window is expected"); - - dummy.initDOMRequestHelper(window, aMessages); - - ok(dummy._window, "Window exists"); - is(dummy._window, window, "Correct window"); - if (aMessages) { - is(typeof dummy._listeners, "object", "Listeners is an object"); - } - } - - function destroyDOMRequestHelperTest() { - dummy.destroyDOMRequestHelper(); - isDOMRequestHelperDestroyed = true; - - is(dummy._requests, null, "Request is null"); - is(dummy._messages, undefined, "Messages is undefined"); - is(dummy._window, null, "Window is null"); - } - - /** - * Message listeners. - */ - function checkMessageListeners(aExpectedListeners, aCount) { - info("Checking message listeners\n" + "Expected listeners " + - JSON.stringify(aExpectedListeners) + " \nExpected count " + aCount); - let count = 0; - Object.keys(dummy._listeners).forEach(function(name) { - count++; - is(aExpectedListeners[name].weakRef, dummy._listeners[name].weakRef, - "Message found " + name + " - Same weakRef"); - is(aExpectedListeners[name].count, dummy._listeners[name].count, - "Message found " + name + " - Same count"); - }); - is(aCount, count, "Correct number of listeners"); - } - - function addMessageListenersTest(aMessages, aExpectedListeners, aCount) { - dummy.addMessageListeners(aMessages); - info(JSON.stringify(dummy._listeners)); - checkMessageListeners(aExpectedListeners, aCount); - } - - function removeMessageListenersTest(aMessages, aExpectedListeners, aCount) { - dummy.removeMessageListeners(aMessages); - checkMessageListeners(aExpectedListeners, aCount); - } - - /** - * Utility function to test window destruction behavior. In general this - * function does the following: - * - * 1) Create a new iframe - * 2) Create a new DOMRequestHelper - * 3) initDOMRequestHelper(), optionally with weak or strong listeners - * 4) Optionally force a garbage collection to reap weak references - * 5) Destroy the iframe triggering an inner-window-destroyed event - * 6) Callback with a boolean indicating if DOMRequestHelper.uninit() was - * called. - * - * Example usage: - * - * checkWindowDestruction({ messages: ["foo"], gc: true }, - * function(uninitCalled) { - * // expect uninitCalled === false since GC with only weak refs - * }); - */ - const TOPIC = "inner-window-destroyed"; - function checkWindowDestruction(aOptions, aCallback) { - aOptions = aOptions || {}; - aOptions.messages = aOptions.messages || []; - aOptions.gc = !!aOptions.gc; - - if (typeof aCallback !== "function") { - aCallback = function() { }; - } - - let uninitCalled = false; - - // Use a secondary observer so we know when to expect the uninit(). We - // can then reasonably expect uninitCalled to be set properly on the - // next tick. - let observer = { - observe(aSubject, aTopic, aData) { - if (aTopic !== TOPIC) { - return; - } - obs.removeObserver(observer, TOPIC); - setTimeout(function() { - aCallback(uninitCalled); - }); - } - }; - - let frame = document.createXULElement("iframe"); - frame.onload = function() { - obs.addObserver(observer, TOPIC); - // Create dummy DOMRequestHelper specific to checkWindowDestruction() - let cwdDummy = new DummyHelperSubclass(); - cwdDummy.onuninit = function() { - uninitCalled = true; - - if (!aOptions.messages || !aOptions.messages.length) { - return; - } - - // If all message listeners are removed, cwdDummy.receiveMessage - // should never be called. - ppmm.broadcastAsyncMessage(aOptions.messages[0].name); - }; - - // Test if we still receive messages from ppmm. - cwdDummy.receiveMessage = function(aMessage) { - ok(false, "cwdDummy.receiveMessage should NOT be called: " + aMessage.name); - }; - - cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages); - // Make sure to clear our strong ref here so that we can test our - // weak reference listeners and observer. - cwdDummy = null; - if (aOptions.gc) { - Cu.schedulePreciseGC(function() { - SpecialPowers.DOMWindowUtils.cycleCollect(); - SpecialPowers.DOMWindowUtils.garbageCollect(); - SpecialPowers.DOMWindowUtils.garbageCollect(); - document.documentElement.removeChild(frame); - }); - return; - } - document.documentElement.removeChild(frame); - }; - document.documentElement.appendChild(frame); - } - - /** - * Test steps. - */ - var tests = [ - function() { - info("== InitDOMRequestHelper no messages"); - initDOMRequestHelperTest(null); - next(); - }, - function() { - info("== DestroyDOMRequestHelper"); - destroyDOMRequestHelperTest(); - next(); - }, - function() { - info("== InitDOMRequestHelper empty array"); - initDOMRequestHelperTest([]); - checkMessageListeners({}, 0); - next(); - }, - function() { - info("== DestroyDOMRequestHelper"); - destroyDOMRequestHelperTest(); - next(); - }, - function() { - info("== InitDOMRequestHelper with strings array"); - initDOMRequestHelperTest(["name1", "nameN"]); - checkMessageListeners({"name1": {weakRef: false, count: 1}, - "nameN": {weakRef: false, count: 1}}, 2); - next(); - }, - function() { - info("== DestroyDOMRequestHelper"); - destroyDOMRequestHelperTest(); - next(); - }, - function() { - info("== InitDOMRequestHelper with objects array"); - initDOMRequestHelperTest([{ - name: "name1", - weakRef: false - }, { - name: "nameN", - weakRef: true - }]); - checkMessageListeners({ - "name1": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 2); - next(); - }, - function() { - info("== AddMessageListeners empty array"); - addMessageListenersTest([], { - "name1": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 2); - next(); - }, - function() { - info("== AddMessageListeners null"); - addMessageListenersTest(null, { - "name1": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 2); - next(); - }, - function() { - info("== AddMessageListeners new listener, string only"); - addMessageListenersTest("name2", { - "name1": {weakRef: false, count: 1}, - "name2": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 3); - next(); - }, - function() { - info("== AddMessageListeners new listeners, strings array"); - addMessageListenersTest(["name3", "name4"], { - "name1": {weakRef: false, count: 1}, - "name2": {weakRef: false, count: 1}, - "name3": {weakRef: false, count: 1}, - "name4": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 5); - next(); - }, - function() { - info("== AddMessageListeners new listeners, objects array"); - addMessageListenersTest([{ - name: "name5", - weakRef: true - }, { - name: "name6", - weakRef: false - }], { - "name1": {weakRef: false, count: 1}, - "name2": {weakRef: false, count: 1}, - "name3": {weakRef: false, count: 1}, - "name4": {weakRef: false, count: 1}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 7); - next(); - }, - function() { - info("== RemoveMessageListeners, null"); - removeMessageListenersTest(null, { - "name1": {weakRef: false, count: 1}, - "name2": {weakRef: false, count: 1}, - "name3": {weakRef: false, count: 1}, - "name4": {weakRef: false, count: 1}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 7); - next(); - }, - function() { - info("== RemoveMessageListeners, one message"); - removeMessageListenersTest("name1", { - "name2": {weakRef: false, count: 1}, - "name3": {weakRef: false, count: 1}, - "name4": {weakRef: false, count: 1}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 6); - next(); - }, - function() { - info("== RemoveMessageListeners, array of messages"); - removeMessageListenersTest(["name2", "name3"], { - "name4": {weakRef: false, count: 1}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 4); - next(); - }, - function() { - info("== RemoveMessageListeners, unknown message"); - removeMessageListenersTest("unknown", { - "name4": {weakRef: false, count: 1}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 4); - next(); - }, - function() { - try { - info("== AddMessageListeners, same message, same kind"); - addMessageListenersTest("name4", { - "name4": {weakRef: false, count: 2}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 4); - next(); - } catch (ex) { - ok(false, "Unexpected exception " + ex); - } - }, - function() { - info("== AddMessageListeners, same message, different kind"); - try { - addMessageListenersTest({name: "name4", weakRef: true}, { - "name4": {weakRef: false, count: 2}, - "name5": {weakRef: true, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 4); - ok(false, "Should have thrown an exception"); - } catch (ex) { - ok(true, "Expected exception"); - next(); - } - }, - function() { - info("== RemoveMessageListeners, message with two listeners"); - try { - removeMessageListenersTest(["name4", "name5"], { - "name4": {weakRef: false, count: 1}, - "name6": {weakRef: false, count: 1}, - "nameN": {weakRef: true, count: 1} - }, 3); - next(); - } catch (ex) { - ok(false, "Unexpected exception " + ex); - } - }, - function() { - info("== Test createRequest()"); - ok(DOMRequest, "DOMRequest object exists"); - var req = dummy.createRequest(); - ok(DOMRequest.isInstance(req), "Returned a DOMRequest"); - next(); - }, - function() { - info("== Test getRequestId(), removeRequest() and getRequest()"); - var req = dummy.createRequest(); - var id = dummy.getRequestId(req); - is(typeof id, "string", "id is a string"); - var req_ = dummy.getRequest(id); - is(req, req_, "Got correct request"); - dummy.removeRequest(id); - req = dummy.getRequest(id); - is(req, undefined, "No request"); - next(); - }, - function() { - info("== Test createPromise()"); - ok(Promise, "Promise object exists"); - var promise = dummy.createPromise(function(resolve, reject) { - resolve(true); - }); - ok(promise instanceof Promise, "Returned a Promise"); - promise.then(next); - }, - function() { - info("== Test createPromiseWithId()"); - var _resolverId; - var promise = dummy.createPromiseWithId(function(resolverId) { - _resolverId = resolverId; - }); - var resolver = dummy.getPromiseResolver(_resolverId); - ok(promise instanceof Promise, "Returned a Promise"); - ok(typeof _resolverId === "string", "resolverId is a string"); - ok(resolver != null, "resolverId is a valid id"); - next(); - }, - function() { - info("== Test getResolver()"); - var id; - var resolver; - var promise = dummy.createPromise(function(resolve, reject) { - var r = { resolve, reject }; - id = dummy.getPromiseResolverId(r); - resolver = r; - ok(typeof id === "string", "id is a string"); - r.resolve(true); - }).then(function(unused) { - var r = dummy.getPromiseResolver(id); - ok(resolver === r, "Get succeeded"); - next(); - }); - }, - function() { - info("== Test removeResolver"); - var id; - var promise = dummy.createPromise(function(resolve, reject) { - var r = { resolve, reject }; - id = dummy.getPromiseResolverId(r); - ok(typeof id === "string", "id is a string"); - - var resolver = dummy.getPromiseResolver(id); - info("Got resolver " + JSON.stringify(resolver)); - ok(resolver === r, "Resolver get succeeded"); - - r.resolve(true); - }).then(function(unused) { - dummy.removePromiseResolver(id); - var resolver = dummy.getPromiseResolver(id); - ok(resolver === undefined, "removeResolver: get failed"); - next(); - }); - }, - function() { - info("== Test takeResolver"); - var id; - var resolver; - var promise = dummy.createPromise(function(resolve, reject) { - var r = { resolve, reject }; - id = dummy.getPromiseResolverId(r); - resolver = r; - ok(typeof id === "string", "id is a string"); - - var gotR = dummy.getPromiseResolver(id); - ok(gotR === r, "resolver get succeeded"); - - r.resolve(true); - }).then(function(unused) { - var r = dummy.takePromiseResolver(id); - ok(resolver === r, "take should succeed"); - - r = dummy.getPromiseResolver(id); - ok(r === undefined, "takeResolver: get failed"); - next(); - }); - }, - function() { - info("== Test window destroyed without messages and without GC"); - checkWindowDestruction({ gc: false }, function(uninitCalled) { - ok(uninitCalled, "uninit() should have been called"); - next(); - }); - }, - function() { - info("== Test window destroyed without messages and with GC"); - checkWindowDestruction({ gc: true }, function(uninitCalled) { - ok(!uninitCalled, "uninit() should NOT have been called"); - next(); - }); - }, - function() { - info("== Test window destroyed with weak messages and without GC"); - checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }], - gc: false }, function(uninitCalled) { - ok(uninitCalled, "uninit() should have been called"); - next(); - }); - }, - function() { - info("== Test window destroyed with weak messages and with GC"); - checkWindowDestruction({ messages: [{ name: "foo", weakRef: true }], - gc: true }, function(uninitCalled) { - ok(!uninitCalled, "uninit() should NOT have been called"); - next(); - }); - }, - function() { - info("== Test window destroyed with strong messages and without GC"); - checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }], - gc: false }, function(uninitCalled) { - ok(uninitCalled, "uninit() should have been called"); - next(); - }); - }, - function() { - info("== Test window destroyed with strong messages and with GC"); - checkWindowDestruction({ messages: [{ name: "foo", weakRef: false }], - gc: true }, function(uninitCalled) { - ok(uninitCalled, "uninit() should have been called"); - next(); - }); - } - ]; - - function next() { - if (!tests.length) { - SimpleTest.finish(); - return; - } - - var test = tests.shift(); - test(); - } - - function start() { - SimpleTest.waitForExplicitFinish(); - next(); - } - ]]> - </script> - - <body xmlns="http://www.w3.org/1999/xhtml"> - <p id="display"></p> - <div id="content" style="display: none"></div> - <pre id="test"></pre> - </body> -</window> diff --git a/dom/base/test/test_focus_radio.html b/dom/base/test/test_focus_radio.html new file mode 100644 index 0000000000..8e97012745 --- /dev/null +++ b/dom/base/test/test_focus_radio.html @@ -0,0 +1,90 @@ +<!doctype html> +<title>Test for input radio focus</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> + +<button id="before">before</button> +<fieldset> + <legend>a</legend> + <label><input id="a1" type="radio" name="a" checked>a1</label> + <label><input id="a2" type="radio" name="a">a2</label> +</fieldset> +<fieldset> + <legend>b</legend> + <label><input id="b1" type="radio" name="b">b1</label> + <label><input id="b2" type="radio" name="b" checked>b2</label> +</fieldset> +<fieldset> + <legend>c</legend> + <label><input id="c1" type="radio" name="c">c1</label> + <label><input id="c2" type="radio" name="c">c2</label> +</fieldset> +<fieldset> + <legend>d</legend> + <label><input id="d1" type="radio" name="d" disabled>d1</label> + <label><input id="d2" type="radio" name="d">d2</label> + <label><input id="d3" type="radio" name="d" disabled>d3</label> + <label><input id="d4" type="radio" name="d">d4</label> +</fieldset> +<button id="after">after</button> + +<script> + SimpleTest.waitForExplicitFinish(); + + function expectFocusAfterKey(key, id) { + const res = key.match(/(Shift\+)?(.*)/); + const shiftKey = Boolean(res[1]); + const rawKey = res[2]; + synthesizeKey(`KEY_${rawKey}`, { shiftKey }); + is(document.activeElement.id, id, `${id} is focused after ${key}`); + } + + SimpleTest.waitForFocus(async function() { + await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}); + + expectFocusAfterKey("Tab", "before"); + // a1 is checked. + expectFocusAfterKey("Tab", "a1"); + // b2 is checked. + expectFocusAfterKey("Tab", "b2"); + // Nothing is checked in group c, so c1 should get focus. + expectFocusAfterKey("Tab", "c1"); + // d1 is disabled, so d2 should get focus. + expectFocusAfterKey("Tab", "d2"); + expectFocusAfterKey("Tab", "after"); + + expectFocusAfterKey("Shift+Tab", "d2"); + expectFocusAfterKey("Shift+Tab", "c1"); + expectFocusAfterKey("Shift+Tab", "b2"); + expectFocusAfterKey("Shift+Tab", "a1"); + expectFocusAfterKey("Shift+Tab", "before"); + + expectFocusAfterKey("Tab", "a1"); + expectFocusAfterKey("ArrowDown", "a2"); + expectFocusAfterKey("Tab", "b2"); + // a2 is now checked, so shift+tab should focus it. + expectFocusAfterKey("Shift+Tab", "a2"); + + expectFocusAfterKey("Tab", "b2"); + expectFocusAfterKey("ArrowUp", "b1"); + expectFocusAfterKey("Shift+Tab", "a2"); + expectFocusAfterKey("Tab", "b1"); + + expectFocusAfterKey("Tab", "c1"); + expectFocusAfterKey("ArrowDown", "c2"); + expectFocusAfterKey("Tab", "d2"); + expectFocusAfterKey("Shift+Tab", "c2"); + + expectFocusAfterKey("Tab", "d2"); + // d3 is disabled, so down arrow should focus d4. + expectFocusAfterKey("ArrowDown", "d4"); + expectFocusAfterKey("ArrowUp", "d2"); + expectFocusAfterKey("ArrowDown", "d4"); + // Down arrow should wrap at the bottom, skipping disabled. + expectFocusAfterKey("ArrowDown", "d2"); + // Up arrow should wrap at the top. + expectFocusAfterKey("ArrowUp", "d4"); + + SimpleTest.finish(); + }); +</script> diff --git a/dom/base/test/unit/test_error_codes.js b/dom/base/test/unit/test_error_codes.js index 73c893c512..5c26331d11 100644 --- a/dom/base/test/unit/test_error_codes.js +++ b/dom/base/test/unit/test_error_codes.js @@ -10,7 +10,7 @@ function asyncXHR(expectedStatus, nextTestFunc) { xhr.open("GET", "http://localhost:4444/test_error_code.xml", true); var sawError = false; - xhr.addEventListener("loadend", function doAsyncRequest_onLoad(event) { + xhr.addEventListener("loadend", function doAsyncRequest_onLoad() { Assert.ok(sawError, "Should have received an error"); nextTestFunc(); }); diff --git a/dom/base/test/useractivation/file_clipboard_common.js b/dom/base/test/useractivation/file_clipboard_common.js index fe172e52c8..3925404350 100644 --- a/dom/base/test/useractivation/file_clipboard_common.js +++ b/dom/base/test/useractivation/file_clipboard_common.js @@ -373,10 +373,10 @@ function allMechanisms(aCb, aClipOverride, aNegateAll) { case 0: // Keyboard issued cutCopyAll( - function docut(aSucc) { + function docut() { synthesizeKey("x", { accelKey: true }); }, - function docopy(aSucc) { + function docopy() { synthesizeKey("c", { accelKey: true }); }, function done() { @@ -411,14 +411,14 @@ function allMechanisms(aCb, aClipOverride, aNegateAll) { case 2: // Not triggered by user gesture cutCopyAll( - function doCut(aSucc) { + function doCut() { is( false, document.execCommand("cut"), "Can't directly execCommand not in a user callback" ); }, - function doCopy(aSucc) { + function doCopy() { is( false, document.execCommand("copy"), diff --git a/dom/base/test/useractivation/test_popup_blocker_async_callback.html b/dom/base/test/useractivation/test_popup_blocker_async_callback.html index dc53596531..4667229116 100644 --- a/dom/base/test/useractivation/test_popup_blocker_async_callback.html +++ b/dom/base/test/useractivation/test_popup_blocker_async_callback.html @@ -16,7 +16,7 @@ SimpleTest.requestFlakyTimeout("Need to test setTimeout"); function startTest(aTestAsyncFun, aAllowPopup = true) { return new Promise(aResolve => { let target = document.getElementById("target"); - target.addEventListener("click", (e) => { + target.addEventListener("click", () => { aTestAsyncFun(() => { let w = window.open("about:blank"); is(!!w, aAllowPopup, `Should ${aAllowPopup ? "allow" : "block"} popup`); diff --git a/dom/base/test/useractivation/test_useractivation_scrollbar.html b/dom/base/test/useractivation/test_useractivation_scrollbar.html index 778d6d0898..197cdc49fb 100644 --- a/dom/base/test/useractivation/test_useractivation_scrollbar.html +++ b/dom/base/test/useractivation/test_useractivation_scrollbar.html @@ -78,7 +78,7 @@ if (!opener) { SimpleTest.waitForFocus(async function() { function waitForEvent(aTarget, aEvent) { return new Promise((aResolve) => { - aTarget.addEventListener(aEvent, function listener(event) { + aTarget.addEventListener(aEvent, function listener() { aResolve(); }, { once: true }); }); diff --git a/dom/base/use_counter_metrics.yaml b/dom/base/use_counter_metrics.yaml index f94029689c..211d3e1344 100644 --- a/dom/base/use_counter_metrics.yaml +++ b/dom/base/use_counter_metrics.yaml @@ -20150,6 +20150,23 @@ use.counter.css.page: send_in_pings: - use-counters + css_transition_behavior: + type: counter + description: > + Whether a page used the CSS property transition-behavior. + Compare against `use.counter.top_level_content_documents_destroyed` + to calculate the rate. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + notification_emails: + - dom-core@mozilla.com + - emilio@mozilla.com + expires: never + send_in_pings: + - use-counters + css_transition_delay: type: counter description: > @@ -31984,6 +32001,23 @@ use.counter.css.doc: send_in_pings: - use-counters + css_transition_behavior: + type: counter + description: > + Whether a document used the CSS property transition-behavior. + Compare against `use.counter.content_documents_destroyed` + to calculate the rate. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + notification_emails: + - dom-core@mozilla.com + - emilio@mozilla.com + expires: never + send_in_pings: + - use-counters + css_transition_delay: type: counter description: > diff --git a/dom/bindings/BindingDeclarations.h b/dom/bindings/BindingDeclarations.h index 24385c9fa5..c723af0021 100644 --- a/dom/bindings/BindingDeclarations.h +++ b/dom/bindings/BindingDeclarations.h @@ -131,11 +131,6 @@ template <class T> constexpr bool is_dom_union_with_typedarray_members = std::is_base_of_v<UnionWithTypedArraysBase, T>; -struct EnumEntry { - const char* value; - size_t length; -}; - enum class CallerType : uint32_t; class MOZ_STACK_CLASS GlobalObject { @@ -562,6 +557,13 @@ JS::Handle<JSObject*> GetPerInterfaceObjectHandle( JSContext* aCx, size_t aSlotId, CreateInterfaceObjectsMethod aCreator, bool aDefineOnGlobal); +namespace binding_detail { + +template <typename Enum> +struct EnumStrings; + +} // namespace binding_detail + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/BindingIPCUtils.h b/dom/bindings/BindingIPCUtils.h index 5598e5896b..5cf6edb7a7 100644 --- a/dom/bindings/BindingIPCUtils.h +++ b/dom/bindings/BindingIPCUtils.h @@ -6,9 +6,18 @@ #ifndef _mozilla_dom_BindingIPCUtils_h #define _mozilla_dom_BindingIPCUtils_h +#include "mozilla/EnumTypeTraits.h" #include "mozilla/dom/BindingDeclarations.h" #include "ipc/EnumSerializer.h" +namespace mozilla::dom { + +template <class Enum> +using WebIDLEnumSerializer = IPC::ContiguousEnumSerializerInclusive< + Enum, ContiguousEnumValues<Enum>::min, ContiguousEnumValues<Enum>::max>; + +} // namespace mozilla::dom + namespace IPC { template <> struct ParamTraits<mozilla::dom::CallerType> diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index df3c509ab7..dd7a42b670 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -24,6 +24,8 @@ #include "mozilla/Array.h" #include "mozilla/Assertions.h" #include "mozilla/DeferredFinalize.h" +#include "mozilla/EnumTypeTraits.h" +#include "mozilla/EnumeratedRange.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/BindingCallContext.h" #include "mozilla/dom/BindingDeclarations.h" @@ -1343,26 +1345,27 @@ inline bool EnumValueNotFound<true>(BindingCallContext& cx, deflated.get(), type); } +namespace binding_detail { + template <typename CharT> inline int FindEnumStringIndexImpl(const CharT* chars, size_t length, - const EnumEntry* values) { - int i = 0; - for (const EnumEntry* value = values; value->value; ++value, ++i) { - if (length != value->length) { + const Span<const nsLiteralCString>& values) { + for (size_t i = 0; i < values.Length(); ++i) { + const nsLiteralCString& value = values[i]; + if (length != value.Length()) { continue; } bool equal = true; - const char* val = value->value; for (size_t j = 0; j != length; ++j) { - if (unsigned(val[j]) != unsigned(chars[j])) { + if (unsigned(value.CharAt(j)) != unsigned(chars[j])) { equal = false; break; } } if (equal) { - return i; + return (int)i; } } @@ -1371,8 +1374,9 @@ inline int FindEnumStringIndexImpl(const CharT* chars, size_t length, template <bool InvalidValueFatal> inline bool FindEnumStringIndex(BindingCallContext& cx, JS::Handle<JS::Value> v, - const EnumEntry* values, const char* type, - const char* sourceDescription, int* index) { + const Span<const nsLiteralCString>& values, + const char* type, const char* sourceDescription, + int* index) { // JS_StringEqualsAscii is slow as molasses, so don't use it here. JS::Rooted<JSString*> str(cx, JS::ToString(cx, v)); if (!str) { @@ -1405,6 +1409,31 @@ inline bool FindEnumStringIndex(BindingCallContext& cx, JS::Handle<JS::Value> v, return EnumValueNotFound<InvalidValueFatal>(cx, str, type, sourceDescription); } +} // namespace binding_detail + +template <typename Enum, class StringT> +inline Maybe<Enum> StringToEnum(const StringT& aString) { + int index = binding_detail::FindEnumStringIndexImpl( + aString.BeginReading(), aString.Length(), + binding_detail::EnumStrings<Enum>::Values); + return index >= 0 ? Some(static_cast<Enum>(index)) : Nothing(); +} + +template <typename Enum> +inline const nsCString& GetEnumString(Enum stringId) { + MOZ_RELEASE_ASSERT( + static_cast<size_t>(stringId) < + mozilla::ArrayLength(binding_detail::EnumStrings<Enum>::Values)); + return binding_detail::EnumStrings<Enum>::Values[static_cast<size_t>( + stringId)]; +} + +template <typename Enum> +constexpr mozilla::detail::EnumeratedRange<Enum> MakeWebIDLEnumeratedRange() { + return MakeInclusiveEnumeratedRange(ContiguousEnumValues<Enum>::min, + ContiguousEnumValues<Enum>::max); +} + inline nsWrapperCache* GetWrapperCache(const ParentObject& aParentObject) { return aParentObject.mWrapperCache; } @@ -3255,6 +3284,7 @@ already_AddRefed<Promise> CreateRejectedPromiseFromThrownException( } // namespace binding_detail } // namespace dom + } // namespace mozilla #endif /* mozilla_dom_BindingUtils_h__ */ diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index bf736e9ac2..7eed2593aa 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1006,6 +1006,18 @@ DOMInterfaces = { 'wrapperCache': False, }, +'TrustedHTML': { + 'wrapperCache': False, +}, + +'TrustedScript': { + 'wrapperCache': False, +}, + +'TrustedScriptURL': { + 'wrapperCache': False, +}, + 'UserInteraction': { 'nativeType': 'mozilla::telemetry::UserInteractionStopwatch', 'headerFile': 'mozilla/telemetry/Stopwatch.h', @@ -1527,6 +1539,10 @@ DOMInterfaces = { 'nativeType': 'mozilla::glean::GleanText', 'headerFile': 'mozilla/glean/bindings/Text.h', }, +'GleanObject': { + 'nativeType': 'mozilla::glean::GleanObject', + 'headerFile': 'mozilla/glean/bindings/Object.h', +}, 'Window': { 'nativeType': 'nsGlobalWindowInner', @@ -1962,6 +1978,11 @@ if buildconfig.substs.get("ENABLE_TESTS", False): 'register': False, }, + 'TestCallbackDictUnionOverload' : { + 'headerFile': 'TestBindingHeader.h', + 'register': False, + }, + }) if buildconfig.substs.get("MOZ_DEBUG", False): diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 80efa81ec5..7a6a28bffc 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -11,7 +11,6 @@ import re import string import textwrap -import six from Configuration import ( Descriptor, MemberIsLegacyUnforgeable, @@ -51,7 +50,6 @@ LEGACYCALLER_HOOK_NAME = "_legacycaller" RESOLVE_HOOK_NAME = "_resolve" MAY_RESOLVE_HOOK_NAME = "_mayResolve" NEW_ENUMERATE_HOOK_NAME = "_newEnumerate" -ENUM_ENTRY_VARIABLE_NAME = "strings" INSTANCE_RESERVED_SLOTS = 1 # This size is arbitrary. It is a power of 2 to make using it as a modulo @@ -247,12 +245,6 @@ def wantsGetWrapperCache(desc): ) -# We'll want to insert the indent at the beginnings of lines, but we -# don't want to indent empty lines. So only indent lines that have a -# non-newline character on them. -lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE) - - def indent(s, indentLevel=2): """ Indent C++ code. @@ -260,9 +252,16 @@ def indent(s, indentLevel=2): Weird secret feature: this doesn't indent lines that start with # (such as #include lines or #ifdef/#endif). """ - if s == "": - return s - return re.sub(lineStartDetector, indentLevel * " ", s) + + # We'll want to insert the indent at the beginnings of lines, but we + # don't want to indent empty lines. + padding = indentLevel * " " + return "\n".join( + [ + (padding + line) if line and line[0] != "#" else line + for line in s.split("\n") + ] + ) # dedent() and fill() are often called on the same string multiple @@ -1600,7 +1599,7 @@ class CGHeaders(CGWrapper): def getDeclarationFilename(decl): # Use our local version of the header, not the exported one, so that # test bindings, which don't export, will work correctly. - basename = os.path.basename(decl.filename()) + basename = os.path.basename(decl.filename) return basename.replace(".webidl", "Binding.h") @staticmethod @@ -7046,7 +7045,10 @@ def getJSToNativeConversionInfo( """ { int index; - if (!FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, ${values}, "${enumtype}", "${sourceDescription}", &index)) { + if (!binding_detail::FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, + binding_detail::EnumStrings<${enumtype}>::Values, + "${enumtype}", "${sourceDescription}", + &index)) { $*{exceptionCode} } $*{handleInvalidEnumValueCode} @@ -7054,7 +7056,6 @@ def getJSToNativeConversionInfo( } """, enumtype=enumName, - values=enumName + "Values::" + ENUM_ENTRY_VARIABLE_NAME, invalidEnumValueFatal=toStringBool(invalidEnumValueFatal), handleInvalidEnumValueCode=handleInvalidEnumValueCode, exceptionCode=exceptionCode, @@ -12330,20 +12331,13 @@ def getEnumValueName(value): raise SyntaxError('"_empty" is not an IDL enum value we support yet') if value == "": return "_empty" - nativeName = MakeNativeName(value) - if nativeName == "EndGuard_": - raise SyntaxError( - 'Enum value "' + value + '" cannot be used because it' - " collides with our internal EndGuard_ value. Please" - " rename our internal EndGuard_ to something else" - ) - return nativeName + return MakeNativeName(value) class CGEnumToJSValue(CGAbstractMethod): def __init__(self, enum): enumType = enum.identifier.name - self.stringsArray = enumType + "Values::" + ENUM_ENTRY_VARIABLE_NAME + self.stringsArray = "binding_detail::EnumStrings<" + enumType + ">::Values" CGAbstractMethod.__init__( self, None, @@ -12361,8 +12355,8 @@ class CGEnumToJSValue(CGAbstractMethod): """ MOZ_ASSERT(uint32_t(aArgument) < ArrayLength(${strings})); JSString* resultStr = - JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].value, - ${strings}[uint32_t(aArgument)].length); + JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].BeginReading(), + ${strings}[uint32_t(aArgument)].Length()); if (!resultStr) { return false; } @@ -12377,80 +12371,54 @@ class CGEnum(CGThing): def __init__(self, enum): CGThing.__init__(self) self.enum = enum - entryDecl = fill( - """ - extern const EnumEntry ${entry_array}[${entry_count}]; - - static constexpr size_t Count = ${real_entry_count}; - - // Our "${entry_array}" contains an extra entry with a null string. - static_assert(mozilla::ArrayLength(${entry_array}) - 1 == Count, - "Mismatch between enum strings and enum count"); - - static_assert(static_cast<size_t>(${name}::EndGuard_) == Count, - "Mismatch between enum value and enum count"); - - inline auto GetString(${name} stringId) { - MOZ_ASSERT(static_cast<${type}>(stringId) < Count); - const EnumEntry& entry = ${entry_array}[static_cast<${type}>(stringId)]; - return Span<const char>{entry.value, entry.length}; - } - """, - entry_array=ENUM_ENTRY_VARIABLE_NAME, - entry_count=self.nEnumStrings(), - # -1 because nEnumStrings() includes a string for EndGuard_ - real_entry_count=self.nEnumStrings() - 1, - name=self.enum.identifier.name, - type=self.underlyingType(), - ) strings = CGNamespace( - self.stringsNamespace(), + "binding_detail", CGGeneric( - declare=entryDecl, + declare=fill( + """ + template <> struct EnumStrings<${name}> { + static const nsLiteralCString Values[${count}]; + }; + """, + name=self.enum.identifier.name, + count=self.nEnumStrings(), + ), define=fill( """ - extern const EnumEntry ${name}[${count}] = { - $*{entries} - { nullptr, 0 } - }; - """, - name=ENUM_ENTRY_VARIABLE_NAME, + const nsLiteralCString EnumStrings<${name}>::Values[${count}] = { + $*{entries} + }; + """, + name=self.enum.identifier.name, count=self.nEnumStrings(), - entries="".join( - '{"%s", %d},\n' % (val, len(val)) for val in self.enum.values() - ), + entries="".join('"%s"_ns,\n' % val for val in self.enum.values()), ), ), ) toJSValue = CGEnumToJSValue(enum) self.cgThings = CGList([strings, toJSValue], "\n") - def stringsNamespace(self): - return self.enum.identifier.name + "Values" - def nEnumStrings(self): - return len(self.enum.values()) + 1 + return len(self.enum.values()) - def underlyingType(self): - count = self.nEnumStrings() + @staticmethod + def underlyingType(enum): + count = len(enum.values()) if count <= 256: return "uint8_t" if count <= 65536: return "uint16_t" - raise ValueError( - "Enum " + self.enum.identifier.name + " has more than 65536 values" - ) + raise ValueError("Enum " + enum.identifier.name + " has more than 65536 values") def declare(self): decl = fill( """ enum class ${name} : ${ty} { $*{enums} - EndGuard_ }; """, name=self.enum.identifier.name, - ty=self.underlyingType(), + ty=CGEnum.underlyingType(self.enum), enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n", ) @@ -12463,6 +12431,39 @@ class CGEnum(CGThing): return self.enum.getDeps() +class CGMaxContiguousEnumValue(CGThing): + def __init__(self, enum): + CGThing.__init__(self) + self.enum = enum + + def declare(self): + enumValues = self.enum.values() + return fill( + """ + template <> + struct MaxContiguousEnumValue<dom::${name}> + { + static constexpr dom::${name} value = dom::${name}::${maxValue}; + + static_assert(static_cast<${ty}>(dom::${name}::${minValue}) == 0, + "We rely on this in ContiguousEnumValues"); + static_assert(mozilla::ArrayLength(dom::binding_detail::EnumStrings<dom::${name}>::Values) - 1 == UnderlyingValue(value), + "Mismatch between enum strings and enum count"); + }; + """, + name=self.enum.identifier.name, + ty=CGEnum.underlyingType(self.enum), + maxValue=getEnumValueName(enumValues[-1]), + minValue=getEnumValueName(enumValues[0]), + ) + + def define(self): + return "" + + def deps(self): + return self.enum.getDeps() + + def getUnionAccessorSignatureType(type, descriptorProvider): """ Returns the types that are used in the getter and setter signatures for @@ -14867,7 +14868,7 @@ class CGCountMaybeMissingProperty(CGAbstractMethod): generate nested switches) or strings to use for case bodies. """ cases = [] - for label, body in sorted(six.iteritems(switchDecriptor["cases"])): + for label, body in sorted(switchDecriptor["cases"].items()): if isinstance(body, dict): body = self.gen_switch(body) cases.append( @@ -18412,7 +18413,7 @@ class CGGlobalNames(CGGeneric): ), ) - entries.append((name, nativeEntry)) + entries.append((name.encode(), nativeEntry)) # Unfortunately, when running tests, we may have no entries. # PerfectHash will assert if we give it an empty set of entries, so we @@ -18569,7 +18570,7 @@ class ForwardDeclarationBuilder: ] ) ) - for namespace, child in sorted(six.iteritems(self.children)): + for namespace, child in sorted(self.children.items()): decls.append(CGNamespace(namespace, child._build(atTopLevel=False))) cg = CGList(decls, "\n") @@ -19073,9 +19074,11 @@ class CGBindingRoot(CGThing): # Do codegen for all the enums enums = config.getEnums(webIDLFile) cgthings.extend(CGEnum(e) for e in enums) + maxEnumValues = CGList([CGMaxContiguousEnumValue(e) for e in enums], "\n") bindingDeclareHeaders["mozilla/Span.h"] = enums bindingDeclareHeaders["mozilla/ArrayUtils.h"] = enums + bindingDeclareHeaders["mozilla/EnumTypeTraits.h"] = enums hasCode = descriptors or callbackDescriptors or dictionaries or callbacks bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode @@ -19175,7 +19178,13 @@ class CGBindingRoot(CGThing): curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") # Wrap all of that in our namespaces. - curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, pre="\n")) + + if len(maxEnumValues) > 0: + curr = CGNamespace("dom", CGWrapper(curr, pre="\n")) + curr = CGWrapper(CGList([curr, maxEnumValues], "\n\n"), post="\n\n") + curr = CGNamespace("mozilla", CGWrapper(curr, pre="\n")) + else: + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, pre="\n")) curr = CGList( [ @@ -19194,12 +19203,10 @@ class CGBindingRoot(CGThing): # Add header includes. bindingHeaders = [ - header for header, include in six.iteritems(bindingHeaders) if include + header for header, include in bindingHeaders.items() if include ] bindingDeclareHeaders = [ - header - for header, include in six.iteritems(bindingDeclareHeaders) - if include + header for header, include in bindingDeclareHeaders.items() if include ] curr = CGHeaders( @@ -24681,7 +24688,7 @@ class CGEventRoot(CGThing): self.root, pre=( AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT - % os.path.basename(descriptor.interface.filename()) + % os.path.basename(descriptor.interface.filename) ), ) diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index 266a8db34a..354a450de6 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -7,7 +7,6 @@ import itertools import os from collections import defaultdict -import six from WebIDL import IDLIncludesStatement autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n" @@ -98,7 +97,7 @@ class Configuration(DescriptorProvider): # different .webidl file than their LHS interface. Make sure we # don't have any of those. See similar block below for partial # interfaces! - if thing.interface.filename() != thing.filename(): + if thing.interface.filename != thing.filename: raise TypeError( "The binding build system doesn't really support " "'includes' statements which don't appear in the " @@ -124,7 +123,7 @@ class Configuration(DescriptorProvider): # statements! if not thing.isExternal(): for partial in thing.getPartials(): - if partial.filename() != thing.filename(): + if partial.filename != thing.filename: raise TypeError( "The binding build system doesn't really support " "partial interfaces/namespaces/mixins which don't " @@ -146,7 +145,7 @@ class Configuration(DescriptorProvider): or iface.getExtendedAttribute("Func") == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"] or not iface.hasInterfaceObject() - or isInWebIDLRoot(iface.filename()) + or isInWebIDLRoot(iface.filename) ): raise TypeError( "Interfaces which are exposed to the web may only be " @@ -187,7 +186,7 @@ class Configuration(DescriptorProvider): self.descriptorsByFile = {} for d in self.descriptors: - self.descriptorsByFile.setdefault(d.interface.filename(), []).append(d) + self.descriptorsByFile.setdefault(d.interface.filename, []).append(d) self.enums = [e for e in parseData if e.isEnum()] @@ -212,19 +211,19 @@ class Configuration(DescriptorProvider): def addUnion(t): filenamesForUnion = self.filenamesPerUnion[t.name] - if t.filename() not in filenamesForUnion: + if t.filename not in filenamesForUnion: # We have a to be a bit careful: some of our built-in # typedefs are for unions, and those unions end up with # "<unknown>" as the filename. If that happens, we don't # want to try associating this union with one particular # filename, since there isn't one to associate it with, # really. - if t.filename() == "<unknown>": + if t.filename == "<unknown>": uniqueFilenameForUnion = None elif len(filenamesForUnion) == 0: # This is the first file that we found a union with this # name in, record the union as part of the file. - uniqueFilenameForUnion = t.filename() + uniqueFilenameForUnion = t.filename else: # We already found a file that contains a union with # this name. @@ -246,7 +245,7 @@ class Configuration(DescriptorProvider): # the filename as None, so that we can detect that. uniqueFilenameForUnion = None self.unionsPerFilename[uniqueFilenameForUnion].append(t) - filenamesForUnion.add(t.filename()) + filenamesForUnion.add(t.filename) def addUnions(t): t = findInnermostType(t) @@ -498,7 +497,7 @@ class Configuration(DescriptorProvider): # Collect up our filters, because we may have a webIDLFile filter that # we always want to apply first. tofilter = [(lambda x: x.interface.isExternal(), False)] - for key, val in six.iteritems(filters): + for key, val in filters.items(): if key == "webIDLFile": # Special-case this part to make it fast, since most of our # getDescriptors calls are conditioned on a webIDLFile. We may @@ -571,13 +570,13 @@ class Configuration(DescriptorProvider): return curr def getEnums(self, webIDLFile): - return [e for e in self.enums if e.filename() == webIDLFile] + return [e for e in self.enums if e.filename == webIDLFile] def getDictionaries(self, webIDLFile): - return [d for d in self.dictionaries if d.filename() == webIDLFile] + return [d for d in self.dictionaries if d.filename == webIDLFile] def getCallbacks(self, webIDLFile): - return [c for c in self.callbacks if c.filename() == webIDLFile] + return [c for c in self.callbacks if c.filename == webIDLFile] def getDescriptor(self, interfaceName): """ @@ -704,7 +703,7 @@ class Descriptor(DescriptorProvider): # import it here, sadly. # Use our local version of the header, not the exported one, so that # test bindings, which don't export, will work correctly. - basename = os.path.basename(self.interface.filename()) + basename = os.path.basename(self.interface.filename) headerDefault = basename.replace(".webidl", "Binding.h") else: if not self.interface.isExternal() and self.interface.getExtendedAttribute( diff --git a/dom/bindings/mozwebidlcodegen/__init__.py b/dom/bindings/mozwebidlcodegen/__init__.py index 8b6e62f345..3e8c6aa420 100644 --- a/dom/bindings/mozwebidlcodegen/__init__.py +++ b/dom/bindings/mozwebidlcodegen/__init__.py @@ -14,7 +14,6 @@ import os from copy import deepcopy import mozpack.path as mozpath -import six from mach.mixin.logging import LoggingMixin from mozbuild.makeutil import Makefile from mozbuild.pythonutil import iter_modules_in_path @@ -339,10 +338,8 @@ class WebIDLCodegenManager(LoggingMixin): if self._make_deps_path: mk = Makefile() codegen_rule = mk.create_rule([self._make_deps_target]) - codegen_rule.add_dependencies( - six.ensure_text(s) for s in global_hashes.keys() - ) - codegen_rule.add_dependencies(six.ensure_text(p) for p in self._input_paths) + codegen_rule.add_dependencies(global_hashes.keys()) + codegen_rule.add_dependencies(self._input_paths) with FileAvoidWrite(self._make_deps_path) as fh: mk.dump(fh) @@ -359,7 +356,12 @@ class WebIDLCodegenManager(LoggingMixin): example_paths = self._example_paths(interface) for path in example_paths: - print("Generating {}".format(path)) + self.log( + logging.INFO, + "webidl_generate_example_files", + {"filename": path}, + "Generating WebIDL example files derived from {filename}", + ) return self._maybe_write_codegen(root, *example_paths) @@ -380,7 +382,7 @@ class WebIDLCodegenManager(LoggingMixin): for path in sorted(self._input_paths): with io.open(path, "r", encoding="utf-8") as fh: data = fh.read() - hashes[path] = hashlib.sha1(six.ensure_binary(data)).hexdigest() + hashes[path] = hashlib.sha1(data.encode()).hexdigest() parser.parse(data, path) # Only these directories may contain WebIDL files with interfaces @@ -494,7 +496,7 @@ class WebIDLCodegenManager(LoggingMixin): for name in changedDictionaryNames: d = self._config.getDictionaryIfExists(name) if d: - changed_inputs.add(d.filename()) + changed_inputs.add(d.filename) # Only use paths that are known to our current state. # This filters out files that were deleted or changed type (e.g. from diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 9101e14cf2..43f9ec12f1 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -43,25 +43,22 @@ def parseInt(literal): return value * sign -def enum(*names, **kw): - class Foo(object): - attrs = OrderedDict() +# This is surprisingly faster than using the enum.IntEnum type (which doesn't +# support 'base' anyway) +def enum(*names, base=None): + if base is not None: + names = base.attrs + names - def __init__(self, names): - for v, k in enumerate(names): - self.attrs[k] = v - - def __getattr__(self, attr): - if attr in self.attrs: - return self.attrs[attr] - raise AttributeError + class CustomEnumType(object): + attrs = names def __setattr__(self, name, value): # this makes it read-only raise NotImplementedError - if "base" not in kw: - return Foo(names) - return Foo(chain(kw["base"].attrs.keys(), names)) + for v, k in enumerate(names): + setattr(CustomEnumType, k, v) + + return CustomEnumType() class WebIDLError(Exception): @@ -85,13 +82,10 @@ class Location(object): self._lineno = lineno self._lexpos = lexpos self._lexdata = lexer.lexdata - self._file = filename if filename else "<unknown>" + self.filename = filename if filename else "<unknown>" def __eq__(self, other): - return self._lexpos == other._lexpos and self._file == other._file - - def filename(self): - return self._file + return self._lexpos == other._lexpos and self.filename == other.filename def resolve(self): if self._line: @@ -110,7 +104,7 @@ class Location(object): def get(self): self.resolve() - return "%s line %s:%s" % (self._file, self._lineno, self._colno) + return "%s line %s:%s" % (self.filename, self._lineno, self._colno) def _pointerline(self): return " " * self._colno + "^" @@ -118,7 +112,7 @@ class Location(object): def __str__(self): self.resolve() return "%s line %s:%s\n%s\n%s" % ( - self._file, + self.filename, self._lineno, self._colno, self._line, @@ -129,13 +123,11 @@ class Location(object): class BuiltinLocation(object): def __init__(self, text): self.msg = text + "\n" + self.filename = "<builtin>" def __eq__(self, other): return isinstance(other, BuiltinLocation) and self.msg == other.msg - def filename(self): - return "<builtin>" - def resolve(self): pass @@ -153,9 +145,7 @@ class IDLObject(object): def __init__(self, location): self.location = location self.userData = dict() - - def filename(self): - return self.location.filename() + self.filename = location and location.filename def isInterface(self): return False @@ -220,8 +210,8 @@ class IDLObject(object): visited.add(self) deps = set() - if self.filename() != "<builtin>": - deps.add(self.filename()) + if self.filename != "<builtin>": + deps.add(self.filename) for d in self._getDependentObjects(): deps.update(d.getDeps(visited)) @@ -3032,6 +3022,9 @@ class IDLRecordType(IDLParametrizedType): if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) + if other.isCallback(): + # Let other determine if it's a LegacyTreatNonObjectAsNull callback + return other.isDistinguishableFrom(self) return ( other.isPrimitive() or other.isString() @@ -3490,7 +3483,7 @@ class IDLWrapperType(IDLType): or other.isSequence() or other.isRecord() ) - if self.isDictionary() and (other.nullable() or other.isUndefined()): + if self.isDictionary() and other.nullable(): return False if ( other.isPrimitive() @@ -3499,28 +3492,51 @@ class IDLWrapperType(IDLType): or other.isSequence() ): return True - if self.isDictionary(): + + # If this needs to handle other dictionary-like types we probably need + # some additional checks first. + assert self.isDictionaryLike() == ( + self.isDictionary() or self.isCallbackInterface() + ) + if self.isDictionaryLike(): + if other.isCallback(): + # Let other determine if it's a LegacyTreatNonObjectAsNull callback + return other.isDistinguishableFrom(self) + + assert ( + other.isNonCallbackInterface() + or other.isAny() + or other.isUndefined() + or other.isObject() + or other.isDictionaryLike() + ) + # At this point, dictionary-like (for 'self') and interface-like + # (for 'other') are the only two that are distinguishable. + # any is the union of all non-union types, so it's not distinguishable + # from other unions (because it is a union itself), or from all + # non-union types (because it has all of them as its members). return other.isNonCallbackInterface() - assert self.isInterface() - if other.isInterface(): + assert self.isNonCallbackInterface() + + if other.isUndefined() or other.isDictionaryLike() or other.isCallback(): + return True + + if other.isNonCallbackInterface(): if other.isSpiderMonkeyInterface(): # Just let |other| handle things return other.isDistinguishableFrom(self) + assert self.isGeckoInterface() and other.isGeckoInterface() if self.inner.isExternal() or other.unroll().inner.isExternal(): return self != other - return len( - self.inner.interfacesBasedOnSelf - & other.unroll().inner.interfacesBasedOnSelf - ) == 0 and (self.isNonCallbackInterface() or other.isNonCallbackInterface()) - if ( - other.isUndefined() - or other.isDictionary() - or other.isCallback() - or other.isRecord() - ): - return self.isNonCallbackInterface() + return ( + len( + self.inner.interfacesBasedOnSelf + & other.unroll().inner.interfacesBasedOnSelf + ) + == 0 + ) # Not much else |other| can be. # any is the union of all non-union types, so it's not distinguishable @@ -6067,6 +6083,9 @@ class IDLCallbackType(IDLType): if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) + # Callbacks without `LegacyTreatNonObjectAsNull` are distinguishable from Dictionary likes + if other.isDictionaryLike(): + return not self.callback._treatNonObjectAsNull return ( other.isUndefined() or other.isPrimitive() @@ -6943,7 +6962,7 @@ class IDLExtendedAttribute(IDLObject): class Tokenizer(object): - tokens = ["INTEGER", "FLOATLITERAL", "IDENTIFIER", "STRING", "WHITESPACE", "OTHER"] + tokens = ["INTEGER", "FLOATLITERAL", "IDENTIFIER", "STRING", "COMMENTS", "OTHER"] def t_FLOATLITERAL(self, t): r"(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN" @@ -6979,17 +6998,19 @@ class Tokenizer(object): t.value = t.value[1:-1] return t - def t_WHITESPACE(self, t): - r"[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+" + t_ignore = "\t\n\r " + + def t_COMMENTS(self, t): + r"//[^\n]*|/\*(?s:.)*?\*/" pass def t_ELLIPSIS(self, t): r"\.\.\." - t.type = self.keywords.get(t.value) + t.type = "ELLIPSIS" return t def t_OTHER(self, t): - r"[^\t\n\r 0-9A-Z_a-z]" + r"[^0-9A-Z_a-z]" t.type = self.keywords.get(t.value, "OTHER") return t @@ -7086,14 +7107,14 @@ class Tokenizer(object): if lexer: self.lexer = lexer else: - self.lexer = lex.lex(object=self, reflags=re.DOTALL) + self.lexer = lex.lex(object=self) class SqueakyCleanLogger(object): errorWhitelist = [ - # Web IDL defines the WHITESPACE token, but doesn't actually + # Web IDL defines the COMMENTS token, but doesn't actually # use it ... so far. - "Token 'WHITESPACE' defined, but not used", + "Token 'COMMENTS' defined, but not used", # And that means we have an unused token "There is 1 unused token", # Web IDL defines a OtherOrComma rule that's only used in @@ -9097,13 +9118,8 @@ class Parser(Tokenizer): production.validate() # De-duplicate self._productions, without modifying its order. - seen = set() - result = [] - for p in self._productions: - if p not in seen: - seen.add(p) - result.append(p) - return result + result = dict.fromkeys(self._productions) + return list(result.keys()) def reset(self): return Parser(lexer=self.lexer) diff --git a/dom/bindings/parser/tests/test_builtin_filename.py b/dom/bindings/parser/tests/test_builtin_filename.py index 25ed32befc..97e8cb061f 100644 --- a/dom/bindings/parser/tests/test_builtin_filename.py +++ b/dom/bindings/parser/tests/test_builtin_filename.py @@ -8,4 +8,4 @@ def WebIDLTest(parser, harness): ) attr = parser.finish()[0].members[0] - harness.check(attr.type.filename(), "<builtin>", "Filename on builtin type") + harness.check(attr.type.filename, "<builtin>", "Filename on builtin type") diff --git a/dom/bindings/parser/tests/test_distinguishability.py b/dom/bindings/parser/tests/test_distinguishability.py index 8497490b1f..caf726c16b 100644 --- a/dom/bindings/parser/tests/test_distinguishability.py +++ b/dom/bindings/parser/tests/test_distinguishability.py @@ -228,7 +228,9 @@ def WebIDLTest(parser, harness): and (a != "any" and a != "Promise<any>" and a != "Promise<any>?") ] - unions = ["(long or Callback)", "(long or Dict)"] + unionsWithCallback = ["(long or Callback)"] + unionsNoCallback = ["(long or Dict)"] + unions = unionsWithCallback + unionsNoCallback numerics = ["long", "short", "long?", "short?"] booleans = ["boolean", "boolean?"] undefineds = ["undefined", "undefined?"] @@ -265,7 +267,7 @@ def WebIDLTest(parser, harness): "Date?", "any", "Promise<any>?", - ] + allBut(unions, ["(long or Callback)"]) + ] + unionsNoCallback sequences = ["sequence<long>", "sequence<short>"] nonUserObjects = nonObjects + interfaces + sequences otherObjects = allBut(argTypes, nonUserObjects + ["object"]) @@ -282,17 +284,14 @@ def WebIDLTest(parser, harness): "record<ByteString, long>", "record<UTF8String, long>", ] # JSString not supported in records - dictionaryLike = ( - [ - "Dict", - "Dict2", - "CallbackInterface", - "CallbackInterface?", - "CallbackInterface2", - ] - + records - + allBut(unions, ["(long or Callback)"]) - ) + dicts = ["Dict", "Dict2"] + callbacks = ["Callback", "Callback2"] + callbackInterfaces = [ + "CallbackInterface", + "CallbackInterface?", + "CallbackInterface2", + ] + dictionaryLike = dicts + callbackInterfaces + records + unionsNoCallback # Build a representation of the distinguishability table as a dict # of dicts, holding True values where needed, holes elsewhere. @@ -327,23 +326,60 @@ def WebIDLTest(parser, harness): setDistinguishable( "UnrelatedInterface", allBut(argTypes, ["object", "UnrelatedInterface"]) ) - setDistinguishable("CallbackInterface", allBut(nonUserObjects, undefineds)) setDistinguishable( - "CallbackInterface?", allBut(nonUserObjects, nullables + undefineds) + "CallbackInterface", + allBut(nonUserObjects + callbacks + unionsWithCallback, undefineds), + ) + setDistinguishable( + "CallbackInterface?", + allBut(nonUserObjects + callbacks + unionsWithCallback, nullables + undefineds), + ) + setDistinguishable( + "CallbackInterface2", + allBut(nonUserObjects + callbacks + unionsWithCallback, undefineds), ) - setDistinguishable("CallbackInterface2", allBut(nonUserObjects, undefineds)) setDistinguishable("object", nonObjects) - setDistinguishable("Callback", nonUserObjects) - setDistinguishable("Callback2", nonUserObjects) - setDistinguishable("Dict", allBut(nonUserObjects, nullables + undefineds)) - setDistinguishable("Dict2", allBut(nonUserObjects, nullables + undefineds)) - setDistinguishable("sequence<long>", allBut(argTypes, sequences + ["object"])) - setDistinguishable("sequence<short>", allBut(argTypes, sequences + ["object"])) - setDistinguishable("record<DOMString, object>", allBut(nonUserObjects, undefineds)) - setDistinguishable("record<USVString, Dict>", allBut(nonUserObjects, undefineds)) + setDistinguishable( + "Callback", + nonUserObjects + unionsNoCallback + dicts + records + callbackInterfaces, + ) + setDistinguishable( + "Callback2", + nonUserObjects + unionsNoCallback + dicts + records + callbackInterfaces, + ) + setDistinguishable( + "Dict", + allBut(nonUserObjects + unionsWithCallback + callbacks, nullables + undefineds), + ) + setDistinguishable( + "Dict2", + allBut(nonUserObjects + unionsWithCallback + callbacks, nullables + undefineds), + ) + setDistinguishable( + "sequence<long>", + allBut(argTypes, sequences + ["object"]), + ) + setDistinguishable( + "sequence<short>", + allBut(argTypes, sequences + ["object"]), + ) + setDistinguishable( + "record<DOMString, object>", + allBut(nonUserObjects + unionsWithCallback + callbacks, undefineds), + ) + setDistinguishable( + "record<USVString, Dict>", + allBut(nonUserObjects + unionsWithCallback + callbacks, undefineds), + ) # JSString not supported in records - setDistinguishable("record<ByteString, long>", allBut(nonUserObjects, undefineds)) - setDistinguishable("record<UTF8String, long>", allBut(nonUserObjects, undefineds)) + setDistinguishable( + "record<ByteString, long>", + allBut(nonUserObjects + unionsWithCallback + callbacks, undefineds), + ) + setDistinguishable( + "record<UTF8String, long>", + allBut(nonUserObjects + unionsWithCallback + callbacks, undefineds), + ) setDistinguishable("any", []) setDistinguishable("Promise<any>", []) setDistinguishable("Promise<any>?", []) @@ -358,9 +394,13 @@ def WebIDLTest(parser, harness): setDistinguishable( "Uint16Array", allBut(argTypes, ["ArrayBufferView", "Uint16Array", "object"]) ) - setDistinguishable("(long or Callback)", allBut(nonUserObjects, numerics)) setDistinguishable( - "(long or Dict)", allBut(nonUserObjects, numerics + nullables + undefineds) + "(long or Callback)", + allBut(nonUserObjects + dicts + records + callbackInterfaces, numerics), + ) + setDistinguishable( + "(long or Dict)", + allBut(nonUserObjects + callbacks, numerics + nullables + undefineds), ) def areDistinguishable(type1, type2): @@ -377,6 +417,7 @@ def WebIDLTest(parser, harness): callback interface CallbackInterface2 {}; callback Callback = any(); callback Callback2 = long(short arg); + [LegacyTreatNonObjectAsNull] callback LegacyCallback1 = any(); // Give our dictionaries required members so we don't need to // mess with optional and default values. dictionary Dict { required long member; }; diff --git a/dom/bindings/parser/tests/test_legacyTreatNonObjectAsNull.py b/dom/bindings/parser/tests/test_legacyTreatNonObjectAsNull.py new file mode 100644 index 0000000000..380ccdc4e7 --- /dev/null +++ b/dom/bindings/parser/tests/test_legacyTreatNonObjectAsNull.py @@ -0,0 +1,11 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + [LegacyTreatNonObjectAsNull] callback Function = any(any... arguments); + """ + ) + + results = parser.finish() + + callback = results[0] + harness.check(callback._treatNonObjectAsNull, True, "Got the expected value") diff --git a/dom/bindings/parser/tests/test_union_callback_dict.py b/dom/bindings/parser/tests/test_union_callback_dict.py new file mode 100644 index 0000000000..3e87e16ad4 --- /dev/null +++ b/dom/bindings/parser/tests/test_union_callback_dict.py @@ -0,0 +1,132 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary TestDict { + DOMString member; + }; + [LegacyTreatNonObjectAsNull] callback TestCallback = undefined (); + typedef (TestCallback or TestDict) TestUnionCallbackDict; + """ + ) + results = parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should not allow Dict/Callback union where callback is [LegacyTreatNonObjectAsNull]", + ) + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + dictionary TestDict { + DOMString member; + }; + [LegacyTreatNonObjectAsNull] callback TestCallback = undefined (); + typedef (TestDict or TestCallback) TestUnionCallbackDict; + """ + ) + results = parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should not allow Dict/Callback union where callback is [LegacyTreatNonObjectAsNull]", + ) + + parser = parser.reset() + + parser.parse( + """ + dictionary TestDict { + DOMString member; + }; + callback TestCallback = undefined (); + typedef (TestCallback or TestDict) TestUnionCallbackDict; + """ + ) + results = parser.finish() + + harness.ok(True, "TestUnionCallbackDict interface parsed without error") + harness.check(len(results), 3, "Document should have 3 types") + + myDict = results[0] + harness.ok(isinstance(myDict, WebIDL.IDLDictionary), "Expect an IDLDictionary") + + myCallback = results[1] + harness.ok(isinstance(myCallback, WebIDL.IDLCallback), "Expect an IDLCallback") + + myUnion = results[2] + harness.ok(isinstance(myUnion, WebIDL.IDLTypedef), "Expect a IDLTypedef") + harness.ok( + isinstance(myUnion.innerType, WebIDL.IDLUnionType), "Expect a IDLUnionType" + ) + harness.ok( + isinstance(myUnion.innerType.memberTypes[0], WebIDL.IDLCallbackType), + "Expect a IDLCallbackType", + ) + harness.ok( + isinstance(myUnion.innerType.memberTypes[1], WebIDL.IDLWrapperType), + "Expect a IDLDictionary", + ) + harness.ok( + (myUnion.innerType.memberTypes[0].callback == myCallback), + "Expect left Union member to be MyCallback", + ) + harness.ok( + (myUnion.innerType.memberTypes[1].inner == myDict), + "Expect right Union member to be MyDict", + ) + + parser = parser.reset() + + parser.parse( + """ + dictionary TestDict { + DOMString member; + }; + callback TestCallback = undefined (); + typedef (TestDict or TestCallback) TestUnionCallbackDict; + """ + ) + results = parser.finish() + + harness.ok(True, "TestUnionCallbackDict interface parsed without error") + harness.check(len(results), 3, "Document should have 3 types") + + myDict = results[0] + harness.ok(isinstance(myDict, WebIDL.IDLDictionary), "Expect an IDLDictionary") + + myCallback = results[1] + harness.ok(isinstance(myCallback, WebIDL.IDLCallback), "Expect an IDLCallback") + + myUnion = results[2] + harness.ok(isinstance(myUnion, WebIDL.IDLTypedef), "Expect a IDLTypedef") + harness.ok( + isinstance(myUnion.innerType, WebIDL.IDLUnionType), "Expect a IDLUnionType" + ) + harness.ok( + isinstance(myUnion.innerType.memberTypes[0], WebIDL.IDLWrapperType), + "Expect a IDLDictionary", + ) + harness.ok( + isinstance(myUnion.innerType.memberTypes[1], WebIDL.IDLCallbackType), + "Expect a IDLCallbackType", + ) + harness.ok( + (myUnion.innerType.memberTypes[0].inner == myDict), + "Expect right Union member to be MyDict", + ) + harness.ok( + (myUnion.innerType.memberTypes[1].callback == myCallback), + "Expect left Union member to be MyCallback", + ) diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 4a7ea91ee8..77053d9ba6 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -1798,6 +1798,20 @@ class TestPrefChromeOnlySCFuncConstructorForInterface : public nsISupports, Constructor(const GlobalObject&); }; +class TestCallbackDictUnionOverload : public nsISupports, + public nsWrapperCache { + public: + NS_DECL_ISUPPORTS + virtual nsISupports* GetParentObject(); + + void Overload1(bool); + void Overload1(TestCallback&); + void Overload1(const GrandparentDict&); + void Overload2(bool); + void Overload2(const GrandparentDict&); + void Overload2(TestCallback&); +}; + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 577be05920..827d9c35a7 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -1535,3 +1535,16 @@ interface TestPrefChromeOnlySCFuncConstructorForInterface { // in the generated constructor. constructor(); }; + +typedef (TestCallback or GrandparentDict) TestCallbackDictUnion; +typedef (GrandparentDict or TestCallback) TestDictCallbackUnion; + +[Exposed=Window] +interface TestCallbackDictUnionOverload { + undefined overload1(boolean arg); + undefined overload1(TestCallback arg); + undefined overload1(optional GrandparentDict arg = {}); + undefined overload2(boolean arg); + undefined overload2(optional GrandparentDict arg = {}); + undefined overload2(TestCallback arg); +}; diff --git a/dom/bindings/test/TestInterfaceJS.sys.mjs b/dom/bindings/test/TestInterfaceJS.sys.mjs index 53ce9d107d..ea229eda49 100644 --- a/dom/bindings/test/TestInterfaceJS.sys.mjs +++ b/dom/bindings/test/TestInterfaceJS.sys.mjs @@ -93,12 +93,12 @@ TestInterfaceJS.prototype = { pingPongNullableUnion(x) { return x; }, - returnBadUnion(x) { + returnBadUnion() { return 3; }, - testSequenceOverload(arg) {}, - testSequenceUnion(arg) {}, + testSequenceOverload() {}, + testSequenceUnion() {}, testThrowError() { throw new this._win.Error("We are an Error"); @@ -121,7 +121,7 @@ TestInterfaceJS.prototype = { throw Cr.NS_BINDING_ABORTED; }, - testThrowNsresultFromNative(x) { + testThrowNsresultFromNative() { // We want to throw an exception that we generate from an nsresult thrown // by a C++ component. Services.io.notImplemented(); diff --git a/dom/bindings/test/test_async_iterable.html b/dom/bindings/test/test_async_iterable.html index 8f1f04aea7..ed47ef4c9d 100644 --- a/dom/bindings/test/test_async_iterable.html +++ b/dom/bindings/test/test_async_iterable.html @@ -88,7 +88,7 @@ async function test_data_single() { let blockingPromises = []; for (let i = 0; i < 10; ++i) { let unblocker; - let promise = new Promise((resolve, reject) => { + let promise = new Promise((resolve) => { unblocker = resolve; }); unblockers.push(unblocker); diff --git a/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml index 724c40cbd3..be188b8fbe 100644 --- a/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml +++ b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml @@ -50,9 +50,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1123516 ok(true, "Calling iterators via xrays should fail"); } - setlike.forEach((v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); + setlike.forEach((v) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); TestInterfaceSetlikeNode.prototype.forEach.call(setlike, - (v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); + (v) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); is(TestInterfaceSetlikeNode.prototype.delete.call(setlike, win.document.documentElement), true, "Cross-compartment unwrapping/comparison delete works"); /* eslint-disable-next-line no-shadow */ diff --git a/dom/bindings/test/test_observablearray.html b/dom/bindings/test/test_observablearray.html index 313fb67622..56e35eeca5 100644 --- a/dom/bindings/test/test_observablearray.html +++ b/dom/bindings/test/test_observablearray.html @@ -21,7 +21,7 @@ add_task(function testObservableArray_length() { let deleteCallbackTests = null; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, deleteBooleanCallback(value, index) { @@ -82,10 +82,10 @@ add_task(function testObservableArray_length_callback_throw() { let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback(value) { deleteCallbackCount++; if (value) { throw new Error("deleteBooleanCallback"); @@ -268,7 +268,7 @@ add_task(function testObservableArray_setter_callback_throw() { throw new Error("setBooleanCallback"); } }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback(value) { deleteCallbackCount++; if (value) { throw new Error("deleteBooleanCallback"); @@ -437,7 +437,7 @@ add_task(function testObservableArray_indexed_setter_callback_throw() { let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback(value) { setCallbackCount++; if (value) { throw new Error("setBooleanCallback"); @@ -501,7 +501,7 @@ add_task(function testObservableArray_object() { is(index, callbackIndex++, "setCallbackTests: test index argument"); isDeeply(values[index], value, "setCallbackTests: test value argument"); }, - deleteObjectCallback(value, index) { + deleteObjectCallback() { deleteCallbackCount++; }, }); @@ -521,10 +521,10 @@ add_task(function testObservableArray_object() { add_task(function testObservableArray_xrays() { let m = new TestInterfaceObservableArray({ - setObjectCallback(value, index) { + setObjectCallback() { ok(false, "Shouldn't reach setObjectCallback"); }, - deleteObjectCallback(value, index) { + deleteObjectCallback() { ok(false, "Shouldn't reach deleteObjectCallback"); }, }); diff --git a/dom/bindings/test/test_observablearray_helper.html b/dom/bindings/test/test_observablearray_helper.html index d2b4897cac..e2f3e42c45 100644 --- a/dom/bindings/test/test_observablearray_helper.html +++ b/dom/bindings/test/test_observablearray_helper.html @@ -110,7 +110,7 @@ add_task(function testObservableArray_helper_replaceElementAt_callback_throw() { let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback(value) { setCallbackCount++; if (value) { throw new Error("setBooleanCallback"); @@ -170,7 +170,7 @@ add_task(function testObservableArray_helper_appendElement() { setCallbackTests(value, index); } }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback() { deleteCallbackCount++; }, }); @@ -211,13 +211,13 @@ add_task(function testObservableArray_helper_appendElement_callback_throw() { let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback(value) { setCallbackCount++; if (value) { throw new Error("setBooleanCallback"); } }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback() { deleteCallbackCount++; }, }); @@ -257,7 +257,7 @@ add_task(function testObservableArray_helper_removeLastElement() { let deleteCallbackTests = null; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, deleteBooleanCallback(value, index) { @@ -316,10 +316,10 @@ add_task(function testObservableArray_helper_removeLastElement_callback_throw() let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback(value) { deleteCallbackCount++; if (value) { throw new Error("deleteBooleanCallback"); diff --git a/dom/bindings/test/test_observablearray_proxyhandler.html b/dom/bindings/test/test_observablearray_proxyhandler.html index d7d8810981..00ef7c26b4 100644 --- a/dom/bindings/test/test_observablearray_proxyhandler.html +++ b/dom/bindings/test/test_observablearray_proxyhandler.html @@ -184,7 +184,7 @@ add_task(function testObservableArrayExoticObjects_defineProperty_callback_throw const minLen = 3; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback(value) { setCallbackCount++; if (value) { throw new Error("setBooleanCallback"); @@ -300,7 +300,7 @@ add_task(function testObservableArrayExoticObjects_deleteProperty() { let deleteCallbackTests = null; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, deleteBooleanCallback(value, index) { @@ -384,10 +384,10 @@ add_task(function testObservableArrayExoticObjects_deleteProperty_callback_throw let deleteCallbackCount = 0; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback() { setCallbackCount++; }, - deleteBooleanCallback(value, index) { + deleteBooleanCallback(value) { deleteCallbackCount++; if (value) { throw new Error("deleteBooleanCallback"); @@ -731,7 +731,7 @@ add_task(function testObservableArrayExoticObjects_set_callback_throw() { const minLen = 3; let m = new TestInterfaceObservableArray({ - setBooleanCallback(value, index) { + setBooleanCallback(value) { setCallbackCount++; if (value) { throw new Error("setBooleanCallback"); diff --git a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html index ab7e15dc57..aba129dc3e 100644 --- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html +++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html @@ -31,7 +31,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592 is(exn.stack, stack, "Should have the right stack in test " + testNumber); } - function ensurePromiseFail(testNumber, value) { + function ensurePromiseFail(testNumber) { ok(false, "Test " + testNumber + " should not have a fulfilled promise"); } diff --git a/dom/bindings/test/test_sequence_wrapping.html b/dom/bindings/test/test_sequence_wrapping.html index 34d901fb66..2b1d657b6c 100644 --- a/dom/bindings/test/test_sequence_wrapping.html +++ b/dom/bindings/test/test_sequence_wrapping.html @@ -37,7 +37,7 @@ function doTest() { win.Object.defineProperty(win.Array.prototype, "0", { - set(val) { + set() { setterCalled = true; }, }); diff --git a/dom/broadcastchannel/tests/browser_private_browsing.js b/dom/broadcastchannel/tests/browser_private_browsing.js index e61e8ae58e..0cf88c0237 100644 --- a/dom/broadcastchannel/tests/browser_private_browsing.js +++ b/dom/broadcastchannel/tests/browser_private_browsing.js @@ -37,7 +37,7 @@ add_task(async function () { await BrowserTestUtils.browserLoaded(win2.gBrowser.getBrowserForTab(tab2)); var browser2 = gBrowser.getBrowserForTab(tab2); - var p1 = SpecialPowers.spawn(browser1, [], function (opts) { + var p1 = SpecialPowers.spawn(browser1, [], function () { return new content.window.Promise(resolve => { content.window.bc = new content.window.BroadcastChannel("foobar"); content.window.bc.onmessage = function (e) { @@ -46,7 +46,7 @@ add_task(async function () { }); }); - var p2 = SpecialPowers.spawn(browser2, [], function (opts) { + var p2 = SpecialPowers.spawn(browser2, [], function () { return new content.window.Promise(resolve => { content.window.bc = new content.window.BroadcastChannel("foobar"); content.window.bc.onmessage = function (e) { @@ -55,7 +55,7 @@ add_task(async function () { }); }); - await SpecialPowers.spawn(browser1, [], function (opts) { + await SpecialPowers.spawn(browser1, [], function () { return new content.window.Promise(resolve => { var bc = new content.window.BroadcastChannel("foobar"); bc.postMessage("hello world from private browsing"); @@ -63,7 +63,7 @@ add_task(async function () { }); }); - await SpecialPowers.spawn(browser2, [], function (opts) { + await SpecialPowers.spawn(browser2, [], function () { return new content.window.Promise(resolve => { var bc = new content.window.BroadcastChannel("foobar"); bc.postMessage("hello world from non private browsing"); diff --git a/dom/broadcastchannel/tests/file_mozbrowser.html b/dom/broadcastchannel/tests/file_mozbrowser.html deleted file mode 100644 index 6370500d98..0000000000 --- a/dom/broadcastchannel/tests/file_mozbrowser.html +++ /dev/null @@ -1,20 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>MozBrowser iframe</title> -</head> -<body> -<div id="container"></div> - <script type="application/javascript"> - - var ifr = document.createElement("iframe"); - ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser.html"; - ifr.onload = function() { alert("DONE"); }; - - var domParent = document.getElementById("container"); - domParent.appendChild(ifr); - - </script> -</body> -</html> diff --git a/dom/broadcastchannel/tests/file_mozbrowser2.html b/dom/broadcastchannel/tests/file_mozbrowser2.html deleted file mode 100644 index 2e5f394eb8..0000000000 --- a/dom/broadcastchannel/tests/file_mozbrowser2.html +++ /dev/null @@ -1,21 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>MozBrowser iframe</title> -</head> -<body> -<div id="container"></div> - <script type="application/javascript"> - - var ifr = document.createElement("iframe"); - ifr.setAttribute("mozbrowser", true); - ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser2.html"; - ifr.onload = function() { alert("DONE"); }; - - var domParent = document.getElementById("container"); - domParent.appendChild(ifr); - - </script> -</body> -</html> diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser.html b/dom/broadcastchannel/tests/iframe_mozbrowser.html deleted file mode 100644 index adbb77a061..0000000000 --- a/dom/broadcastchannel/tests/iframe_mozbrowser.html +++ /dev/null @@ -1,15 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>MozBrowser iframe</title> -</head> -<body> - <script type="application/javascript"> - -var bc = new BroadcastChannel("foobar"); -bc.postMessage("This is wrong!"); - - </script> -</body> -</html> diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser2.html b/dom/broadcastchannel/tests/iframe_mozbrowser2.html deleted file mode 100644 index adbb77a061..0000000000 --- a/dom/broadcastchannel/tests/iframe_mozbrowser2.html +++ /dev/null @@ -1,15 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>MozBrowser iframe</title> -</head> -<body> - <script type="application/javascript"> - -var bc = new BroadcastChannel("foobar"); -bc.postMessage("This is wrong!"); - - </script> -</body> -</html> diff --git a/dom/broadcastchannel/tests/mochitest.toml b/dom/broadcastchannel/tests/mochitest.toml index 40264f7a3d..5c061277b6 100644 --- a/dom/broadcastchannel/tests/mochitest.toml +++ b/dom/broadcastchannel/tests/mochitest.toml @@ -4,10 +4,6 @@ support-files = [ "broadcastchannel_sharedWorker.js", "broadcastchannel_worker_alive.js", "!/dom/events/test/event_leak_utils.js", - "file_mozbrowser.html", - "file_mozbrowser2.html", - "iframe_mozbrowser.html", - "iframe_mozbrowser2.html", "testUrl1_bfcache.html", "testUrl2_bfcache.html", ] diff --git a/dom/broadcastchannel/tests/test_bfcache.html b/dom/broadcastchannel/tests/test_bfcache.html index 0197f343e2..1c6155e6f1 100644 --- a/dom/broadcastchannel/tests/test_bfcache.html +++ b/dom/broadcastchannel/tests/test_bfcache.html @@ -52,7 +52,7 @@ counter++; } - function page2Shown(e) { + function page2Shown() { if (!expectedPersisted) { SimpleTest.executeSoon(function() { info("Posting a message."); diff --git a/dom/broadcastchannel/tests/test_event_listener_leaks.html b/dom/broadcastchannel/tests/test_event_listener_leaks.html index 33568913fc..a284489526 100644 --- a/dom/broadcastchannel/tests/test_event_listener_leaks.html +++ b/dom/broadcastchannel/tests/test_event_listener_leaks.html @@ -28,7 +28,7 @@ async function useBroadcastChannel(contentWindow) { outer.postMessage("foo"); await new Promise(resolve => { - bc.onmessage = e => { + bc.onmessage = () => { contentWindow.messageCount += 1; resolve(); }; diff --git a/dom/broadcastchannel/tests/test_message_after_close.html b/dom/broadcastchannel/tests/test_message_after_close.html index 1ef8a018ea..a14b590651 100644 --- a/dom/broadcastchannel/tests/test_message_after_close.html +++ b/dom/broadcastchannel/tests/test_message_after_close.html @@ -13,7 +13,7 @@ let a = new BroadcastChannel('a'); let b = new BroadcastChannel('a'); let count = 0; -a.onmessage = function(e) { +a.onmessage = function() { ++count; a.close(); diff --git a/dom/browser-element/BrowserElementChild.js b/dom/browser-element/BrowserElementChild.js deleted file mode 100644 index 762957bcd4..0000000000 --- a/dom/browser-element/BrowserElementChild.js +++ /dev/null @@ -1,42 +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/. */ - -/* eslint-env mozilla/frame-script */ -/* global api, CopyPasteAssistent */ - -"use strict"; - -function debug(msg) { - // dump("BrowserElementChild - " + msg + "\n"); -} - -var BrowserElementIsReady; - -debug(`Might load BE scripts: BEIR: ${BrowserElementIsReady}`); -if (!BrowserElementIsReady) { - debug("Loading BE scripts"); - if (!("BrowserElementIsPreloaded" in this)) { - Services.scriptloader.loadSubScript( - "chrome://global/content/BrowserElementChildPreload.js", - this - ); - } - - function onDestroy() { - removeMessageListener("browser-element-api:destroy", onDestroy); - - if (api) { - api.destroy(); - } - - BrowserElementIsReady = false; - } - addMessageListener("browser-element-api:destroy", onDestroy); - - BrowserElementIsReady = true; -} else { - debug("BE already loaded, abort"); -} - -sendAsyncMessage("browser-element-api:call", { msg_name: "hello" }); diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js deleted file mode 100644 index 1bbcf9ff05..0000000000 --- a/dom/browser-element/BrowserElementChildPreload.js +++ /dev/null @@ -1,290 +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"; - -/* eslint-env mozilla/frame-script */ - -function debug(msg) { - // dump("BrowserElementChildPreload - " + msg + "\n"); -} - -debug("loaded"); - -var BrowserElementIsReady; - -var { BrowserElementPromptService } = ChromeUtils.import( - "resource://gre/modules/BrowserElementPromptService.jsm" -); - -function sendAsyncMsg(msg, data) { - // Ensure that we don't send any messages before BrowserElementChild.js - // finishes loading. - if (!BrowserElementIsReady) { - return; - } - - if (!data) { - data = {}; - } - - data.msg_name = msg; - sendAsyncMessage("browser-element-api:call", data); -} - -var LISTENED_EVENTS = [ - // This listens to unload events from our message manager, but /not/ from - // the |content| window. That's because the window's unload event doesn't - // bubble, and we're not using a capturing listener. If we'd used - // useCapture == true, we /would/ hear unload events from the window, which - // is not what we want! - { type: "unload", useCapture: false, wantsUntrusted: false }, -]; - -/** - * The BrowserElementChild implements one half of <iframe mozbrowser>. - * (The other half is, unsurprisingly, BrowserElementParent.) - * - * This script is injected into an <iframe mozbrowser> via - * nsIMessageManager::LoadFrameScript(). - * - * Our job here is to listen for events within this frame and bubble them up to - * the parent process. - */ - -var global = this; - -function BrowserElementChild() { - // Maps outer window id --> weak ref to window. Used by modal dialog code. - this._windowIDDict = {}; - - this._init(); -} - -BrowserElementChild.prototype = { - _init() { - debug("Starting up."); - - BrowserElementPromptService.mapWindowToBrowserElementChild(content, this); - - this._shuttingDown = false; - - LISTENED_EVENTS.forEach(event => { - addEventListener( - event.type, - this, - event.useCapture, - event.wantsUntrusted - ); - }); - - addMessageListener("browser-element-api:call", this); - }, - - /** - * Shut down the frame's side of the browser API. This is called when: - * - our BrowserChildGlobal starts to die - * - the content is moved to frame without the browser API - * This is not called when the page inside |content| unloads. - */ - destroy() { - debug("Destroying"); - this._shuttingDown = true; - - BrowserElementPromptService.unmapWindowToBrowserElementChild(content); - - LISTENED_EVENTS.forEach(event => { - removeEventListener( - event.type, - this, - event.useCapture, - event.wantsUntrusted - ); - }); - - removeMessageListener("browser-element-api:call", this); - }, - - handleEvent(event) { - switch (event.type) { - case "unload": - this.destroy(event); - break; - } - }, - - receiveMessage(message) { - let self = this; - - let mmCalls = { - "unblock-modal-prompt": this._recvStopWaiting, - }; - - if (message.data.msg_name in mmCalls) { - return mmCalls[message.data.msg_name].apply(self, arguments); - } - return undefined; - }, - - get _windowUtils() { - return content.document.defaultView.windowUtils; - }, - - _tryGetInnerWindowID(win) { - try { - return win.windowGlobalChild.innerWindowId; - } catch (e) { - return null; - } - }, - - /** - * Show a modal prompt. Called by BrowserElementPromptService. - */ - showModalPrompt(win, args) { - args.windowID = { - outer: win.docShell.outerWindowID, - inner: this._tryGetInnerWindowID(win), - }; - sendAsyncMsg("showmodalprompt", args); - - let returnValue = this._waitForResult(win); - - if ( - args.promptType == "prompt" || - args.promptType == "confirm" || - args.promptType == "custom-prompt" - ) { - return returnValue; - } - return undefined; - }, - - /** - * Spin in a nested event loop until we receive a unblock-modal-prompt message for - * this window. - */ - _waitForResult(win) { - debug("_waitForResult(" + win + ")"); - let utils = win.windowUtils; - - let outerWindowID = win.docShell.outerWindowID; - let innerWindowID = this._tryGetInnerWindowID(win); - if (innerWindowID === null) { - // I have no idea what waiting for a result means when there's no inner - // window, so let's just bail. - debug("_waitForResult: No inner window. Bailing."); - return undefined; - } - - this._windowIDDict[outerWindowID] = Cu.getWeakReference(win); - - debug( - "Entering modal state (outerWindowID=" + - outerWindowID + - ", " + - "innerWindowID=" + - innerWindowID + - ")" - ); - - utils.enterModalState(); - - // We'll decrement win.modalDepth when we receive a unblock-modal-prompt message - // for the window. - if (!win.modalDepth) { - win.modalDepth = 0; - } - win.modalDepth++; - let origModalDepth = win.modalDepth; - - debug("Nested event loop - begin"); - Services.tm.spinEventLoopUntil( - "BrowserElementChildPreload.js:_waitForResult", - () => { - // Bail out of the loop if the inner window changed; that means the - // window navigated. Bail out when we're shutting down because otherwise - // we'll leak our window. - if (this._tryGetInnerWindowID(win) !== innerWindowID) { - debug( - "_waitForResult: Inner window ID changed " + - "while in nested event loop." - ); - return true; - } - - return win.modalDepth !== origModalDepth || this._shuttingDown; - } - ); - debug("Nested event loop - finish"); - - if (win.modalDepth == 0) { - delete this._windowIDDict[outerWindowID]; - } - - // If we exited the loop because the inner window changed, then bail on the - // modal prompt. - if (innerWindowID !== this._tryGetInnerWindowID(win)) { - throw Components.Exception( - "Modal state aborted by navigation", - Cr.NS_ERROR_NOT_AVAILABLE - ); - } - - let returnValue = win.modalReturnValue; - delete win.modalReturnValue; - - if (!this._shuttingDown) { - utils.leaveModalState(); - } - - debug( - "Leaving modal state (outerID=" + - outerWindowID + - ", " + - "innerID=" + - innerWindowID + - ")" - ); - return returnValue; - }, - - _recvStopWaiting(msg) { - let outerID = msg.json.windowID.outer; - let innerID = msg.json.windowID.inner; - let returnValue = msg.json.returnValue; - debug( - "recvStopWaiting(outer=" + - outerID + - ", inner=" + - innerID + - ", returnValue=" + - returnValue + - ")" - ); - - if (!this._windowIDDict[outerID]) { - debug("recvStopWaiting: No record of outer window ID " + outerID); - return; - } - - let win = this._windowIDDict[outerID].get(); - - if (!win) { - debug("recvStopWaiting, but window is gone\n"); - return; - } - - if (innerID !== this._tryGetInnerWindowID(win)) { - debug("recvStopWaiting, but inner ID has changed\n"); - return; - } - - debug("recvStopWaiting " + win); - win.modalReturnValue = returnValue; - win.modalDepth--; - }, -}; - -var api = new BrowserElementChild(); diff --git a/dom/browser-element/BrowserElementParent.jsm b/dom/browser-element/BrowserElementParent.jsm deleted file mode 100644 index ec342dace2..0000000000 --- a/dom/browser-element/BrowserElementParent.jsm +++ /dev/null @@ -1,276 +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"; - -/* BrowserElementParent injects script to listen for certain events in the - * child. We then listen to messages from the child script and take - * appropriate action here in the parent. - */ - -const { BrowserElementPromptService } = ChromeUtils.import( - "resource://gre/modules/BrowserElementPromptService.jsm" -); - -function debug(msg) { - // dump("BrowserElementParent - " + msg + "\n"); -} - -function handleWindowEvent(e) { - if (this._browserElementParents) { - let beps = ChromeUtils.nondeterministicGetWeakMapKeys( - this._browserElementParents - ); - beps.forEach(bep => bep._handleOwnerEvent(e)); - } -} - -function BrowserElementParent() { - debug("Creating new BrowserElementParent object"); -} - -BrowserElementParent.prototype = { - classDescription: "BrowserElementAPI implementation", - classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"), - contractID: "@mozilla.org/dom/browser-element-api;1", - QueryInterface: ChromeUtils.generateQI([ - "nsIBrowserElementAPI", - "nsISupportsWeakReference", - ]), - - setFrameLoader(frameLoader) { - debug("Setting frameLoader"); - this._frameLoader = frameLoader; - this._frameElement = frameLoader.ownerElement; - if (!this._frameElement) { - debug("No frame element?"); - return; - } - // Listen to visibilitychange on the iframe's owner window, and forward - // changes down to the child. We want to do this while registering as few - // visibilitychange listeners on _window as possible, because such a listener - // may live longer than this BrowserElementParent object. - // - // To accomplish this, we register just one listener on the window, and have - // it reference a WeakMap whose keys are all the BrowserElementParent objects - // on the window. Then when the listener fires, we iterate over the - // WeakMap's keys (which we can do, because we're chrome) to notify the - // BrowserElementParents. - if (!this._window._browserElementParents) { - this._window._browserElementParents = new WeakMap(); - let handler = handleWindowEvent.bind(this._window); - let windowEvents = ["visibilitychange"]; - for (let event of windowEvents) { - Services.els.addSystemEventListener( - this._window, - event, - handler, - /* useCapture = */ true - ); - } - } - - this._window._browserElementParents.set(this, null); - - // Insert ourself into the prompt service. - BrowserElementPromptService.mapFrameToBrowserElementParent( - this._frameElement, - this - ); - this._setupMessageListener(); - }, - - destroyFrameScripts() { - debug("Destroying frame scripts"); - this._mm.sendAsyncMessage("browser-element-api:destroy"); - }, - - _setupMessageListener() { - this._mm = this._frameLoader.messageManager; - this._mm.addMessageListener("browser-element-api:call", this); - }, - - receiveMessage(aMsg) { - if (!this._isAlive()) { - return undefined; - } - - // Messages we receive are handed to functions which take a (data) argument, - // where |data| is the message manager's data object. - // We use a single message and dispatch to various function based - // on data.msg_name - let mmCalls = { - hello: this._recvHello, - }; - - let mmSecuritySensitiveCalls = { - showmodalprompt: this._handleShowModalPrompt, - }; - - if (aMsg.data.msg_name in mmCalls) { - return mmCalls[aMsg.data.msg_name].apply(this, arguments); - } else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) { - return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply( - this, - arguments - ); - } - return undefined; - }, - - _removeMessageListener() { - this._mm.removeMessageListener("browser-element-api:call", this); - }, - - /** - * You shouldn't touch this._frameElement or this._window if _isAlive is - * false. (You'll likely get an exception if you do.) - */ - _isAlive() { - return ( - !Cu.isDeadWrapper(this._frameElement) && - !Cu.isDeadWrapper(this._frameElement.ownerDocument) && - !Cu.isDeadWrapper(this._frameElement.ownerGlobal) - ); - }, - - get _window() { - return this._frameElement.ownerGlobal; - }, - - _sendAsyncMsg(msg, data) { - try { - if (!data) { - data = {}; - } - - data.msg_name = msg; - this._mm.sendAsyncMessage("browser-element-api:call", data); - } catch (e) { - return false; - } - return true; - }, - - _recvHello() { - debug("recvHello"); - - // Inform our child if our owner element's document is invisible. Note - // that we must do so here, rather than in the BrowserElementParent - // constructor, because the BrowserElementChild may not be initialized when - // we run our constructor. - if (this._window.document.hidden) { - this._ownerVisibilityChange(); - } - }, - - /** - * Fire either a vanilla or a custom event, depending on the contents of - * |data|. - */ - _fireEventFromMsg(data) { - let detail = data.json; - let name = detail.msg_name; - - // For events that send a "_payload_" property, we just want to transmit - // this in the event. - if ("_payload_" in detail) { - detail = detail._payload_; - } - - debug("fireEventFromMsg: " + name + ", " + JSON.stringify(detail)); - let evt = this._createEvent(name, detail, /* cancelable = */ false); - this._frameElement.dispatchEvent(evt); - }, - - _handleShowModalPrompt(data) { - // Fire a showmodalprmopt event on the iframe. When this method is called, - // the child is spinning in a nested event loop waiting for an - // unblock-modal-prompt message. - // - // If the embedder calls preventDefault() on the showmodalprompt event, - // we'll block the child until event.detail.unblock() is called. - // - // Otherwise, if preventDefault() is not called, we'll send the - // unblock-modal-prompt message to the child as soon as the event is done - // dispatching. - - let detail = data.json; - debug("handleShowPrompt " + JSON.stringify(detail)); - - // Strip off the windowID property from the object we send along in the - // event. - let windowID = detail.windowID; - delete detail.windowID; - debug("Event will have detail: " + JSON.stringify(detail)); - let evt = this._createEvent( - "showmodalprompt", - detail, - /* cancelable = */ true - ); - - let self = this; - let unblockMsgSent = false; - function sendUnblockMsg() { - if (unblockMsgSent) { - return; - } - unblockMsgSent = true; - - // We don't need to sanitize evt.detail.returnValue (e.g. converting the - // return value of confirm() to a boolean); Gecko does that for us. - - let data = { windowID, returnValue: evt.detail.returnValue }; - self._sendAsyncMsg("unblock-modal-prompt", data); - } - - Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: "unblock" }); - - this._frameElement.dispatchEvent(evt); - - if (!evt.defaultPrevented) { - // Unblock the inner frame immediately. Otherwise we'll unblock upon - // evt.detail.unblock(). - sendUnblockMsg(); - } - }, - - _createEvent(evtName, detail, cancelable) { - // This will have to change if we ever want to send a CustomEvent with null - // detail. For now, it's OK. - if (detail !== undefined && detail !== null) { - detail = Cu.cloneInto(detail, this._window); - return new this._window.CustomEvent("mozbrowser" + evtName, { - bubbles: true, - cancelable, - detail, - }); - } - - return new this._window.Event("mozbrowser" + evtName, { - bubbles: true, - cancelable, - }); - }, - - _handleOwnerEvent(evt) { - switch (evt.type) { - case "visibilitychange": - this._ownerVisibilityChange(); - break; - } - }, - - /** - * Called when the visibility of the window which owns this iframe changes. - */ - _ownerVisibilityChange() { - let bc = this._frameLoader?.browsingContext; - if (bc) { - bc.isActive = !this._window.document.hidden; - } - }, -}; - -var EXPORTED_SYMBOLS = ["BrowserElementParent"]; diff --git a/dom/browser-element/BrowserElementPromptService.jsm b/dom/browser-element/BrowserElementPromptService.jsm deleted file mode 100644 index dd73004959..0000000000 --- a/dom/browser-element/BrowserElementPromptService.jsm +++ /dev/null @@ -1,720 +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/. */ -/* vim: set ft=javascript : */ - -"use strict"; - -var Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - -var EXPORTED_SYMBOLS = ["BrowserElementPromptService"]; - -function debug(msg) { - // dump("BrowserElementPromptService - " + msg + "\n"); -} - -function BrowserElementPrompt(win, browserElementChild) { - this._win = win; - this._browserElementChild = browserElementChild; -} - -BrowserElementPrompt.prototype = { - QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]), - - alert(title, text) { - this._browserElementChild.showModalPrompt(this._win, { - promptType: "alert", - title, - message: text, - returnValue: undefined, - }); - }, - - alertCheck(title, text, checkMsg, checkState) { - // Treat this like a normal alert() call, ignoring the checkState. The - // front-end can do its own suppression of the alert() if it wants. - this.alert(title, text); - }, - - confirm(title, text) { - return this._browserElementChild.showModalPrompt(this._win, { - promptType: "confirm", - title, - message: text, - returnValue: undefined, - }); - }, - - confirmCheck(title, text, checkMsg, checkState) { - return this.confirm(title, text); - }, - - // Each button is described by an object with the following schema - // { - // string messageType, // 'builtin' or 'custom' - // string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave', - // // 'revert' or a string from caller if messageType was 'custom'. - // } - // - // Expected result from embedder: - // { - // int button, // Index of the button that user pressed. - // boolean checked, // True if the check box is checked. - // } - confirmEx( - title, - text, - buttonFlags, - button0Title, - button1Title, - button2Title, - checkMsg, - checkState - ) { - let buttonProperties = this._buildConfirmExButtonProperties( - buttonFlags, - button0Title, - button1Title, - button2Title - ); - let defaultReturnValue = { selectedButton: buttonProperties.defaultButton }; - if (checkMsg) { - defaultReturnValue.checked = checkState.value; - } - let ret = this._browserElementChild.showModalPrompt(this._win, { - promptType: "custom-prompt", - title, - message: text, - defaultButton: buttonProperties.defaultButton, - buttons: buttonProperties.buttons, - showCheckbox: !!checkMsg, - checkboxMessage: checkMsg, - checkboxCheckedByDefault: !!checkState.value, - returnValue: defaultReturnValue, - }); - if (checkMsg) { - checkState.value = ret.checked; - } - return buttonProperties.indexToButtonNumberMap[ret.selectedButton]; - }, - - prompt(title, text, value, checkMsg, checkState) { - let rv = this._browserElementChild.showModalPrompt(this._win, { - promptType: "prompt", - title, - message: text, - initialValue: value.value, - returnValue: null, - }); - - value.value = rv; - - // nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt, - // and false if the user pressed "Cancel". - // - // BrowserElementChild returns null for "Cancel" and returns the string the - // user entered otherwise. - return rv !== null; - }, - - promptUsernameAndPassword(title, text, username, password) { - throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - promptPassword(title, text, password) { - throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - select(title, text, aSelectList, aOutSelection) { - throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - _buildConfirmExButtonProperties( - buttonFlags, - button0Title, - button1Title, - button2Title - ) { - let r = { - defaultButton: -1, - buttons: [], - // This map is for translating array index to the button number that - // is recognized by Gecko. This shouldn't be exposed to embedder. - indexToButtonNumberMap: [], - }; - - let defaultButton = 0; // Default to Button 0. - if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) { - defaultButton = 1; - } else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) { - defaultButton = 2; - } - - // Properties of each button. - let buttonPositions = [ - Ci.nsIPrompt.BUTTON_POS_0, - Ci.nsIPrompt.BUTTON_POS_1, - Ci.nsIPrompt.BUTTON_POS_2, - ]; - - function buildButton(buttonTitle, buttonNumber) { - let ret = {}; - let buttonPosition = buttonPositions[buttonNumber]; - let mask = 0xff * buttonPosition; // 8 bit mask - let titleType = (buttonFlags & mask) / buttonPosition; - - ret.messageType = "builtin"; - switch (titleType) { - case Ci.nsIPrompt.BUTTON_TITLE_OK: - ret.message = "ok"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_CANCEL: - ret.message = "cancel"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_YES: - ret.message = "yes"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_NO: - ret.message = "no"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_SAVE: - ret.message = "save"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE: - ret.message = "dontsave"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_REVERT: - ret.message = "revert"; - break; - case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING: - ret.message = buttonTitle; - ret.messageType = "custom"; - break; - default: - // This button is not shown. - return; - } - - // If this is the default button, set r.defaultButton to - // the index of this button in the array. This value is going to be - // exposed to the embedder. - if (defaultButton === buttonNumber) { - r.defaultButton = r.buttons.length; - } - r.buttons.push(ret); - r.indexToButtonNumberMap.push(buttonNumber); - } - - buildButton(button0Title, 0); - buildButton(button1Title, 1); - buildButton(button2Title, 2); - - // If defaultButton is still -1 here, it means the default button won't - // be shown. - if (r.defaultButton === -1) { - throw new Components.Exception( - "Default button won't be shown", - Cr.NS_ERROR_FAILURE - ); - } - - return r; - }, -}; - -function BrowserElementAuthPrompt() {} - -BrowserElementAuthPrompt.prototype = { - QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), - - promptAuth: function promptAuth(channel, level, authInfo) { - throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - asyncPromptAuth: function asyncPromptAuth( - channel, - callback, - context, - level, - authInfo - ) { - debug("asyncPromptAuth"); - - // The cases that we don't support now. - if ( - authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY && - authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD - ) { - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - - let frame = this._getFrameFromChannel(channel); - if (!frame) { - debug("Cannot get frame, asyncPromptAuth fail"); - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - - let browserElementParent = - BrowserElementPromptService.getBrowserElementParentForFrame(frame); - - if (!browserElementParent) { - debug("Failed to load browser element parent."); - throw Components.Exception("", Cr.NS_ERROR_FAILURE); - } - - let consumer = { - QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), - callback, - context, - cancel() { - this.callback.onAuthCancelled(this.context, false); - this.callback = null; - this.context = null; - }, - }; - - let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo); - let hashKey = level + "|" + hostname + "|" + httpRealm; - let asyncPrompt = this._asyncPrompts[hashKey]; - if (asyncPrompt) { - asyncPrompt.consumers.push(consumer); - return consumer; - } - - asyncPrompt = { - consumers: [consumer], - channel, - authInfo, - level, - inProgress: false, - browserElementParent, - }; - - this._asyncPrompts[hashKey] = asyncPrompt; - this._doAsyncPrompt(); - return consumer; - }, - - // Utilities for nsIAuthPrompt2 ---------------- - - _asyncPrompts: {}, - _asyncPromptInProgress: new WeakMap(), - _doAsyncPrompt() { - // Find the key of a prompt whose browser element parent does not have - // async prompt in progress. - let hashKey = null; - for (let key in this._asyncPrompts) { - let prompt = this._asyncPrompts[key]; - if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) { - hashKey = key; - break; - } - } - - // Didn't find an available prompt, so just return. - if (!hashKey) { - return; - } - - let prompt = this._asyncPrompts[hashKey]; - - this._asyncPromptInProgress.set(prompt.browserElementParent, true); - prompt.inProgress = true; - - let self = this; - let callback = function (ok, username, password) { - debug( - "Async auth callback is called, ok = " + ok + ", username = " + username - ); - - // Here we got the username and password provided by embedder, or - // ok = false if the prompt was cancelled by embedder. - delete self._asyncPrompts[hashKey]; - prompt.inProgress = false; - self._asyncPromptInProgress.delete(prompt.browserElementParent); - - // Fill authentication information with username and password provided - // by user. - let flags = prompt.authInfo.flags; - if (username) { - if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) { - // Domain is separated from username by a backslash - let idx = username.indexOf("\\"); - if (idx == -1) { - prompt.authInfo.username = username; - } else { - prompt.authInfo.domain = username.substring(0, idx); - prompt.authInfo.username = username.substring(idx + 1); - } - } else { - prompt.authInfo.username = username; - } - } - - if (password) { - prompt.authInfo.password = password; - } - - for (let consumer of prompt.consumers) { - if (!consumer.callback) { - // Not having a callback means that consumer didn't provide it - // or canceled the notification. - continue; - } - - try { - if (ok) { - debug("Ok, calling onAuthAvailable to finish auth"); - consumer.callback.onAuthAvailable( - consumer.context, - prompt.authInfo - ); - } else { - debug("Cancelled, calling onAuthCancelled to finish auth."); - consumer.callback.onAuthCancelled(consumer.context, true); - } - } catch (e) { - /* Throw away exceptions caused by callback */ - } - } - - // Process the next prompt, if one is pending. - self._doAsyncPrompt(); - }; - - let runnable = { - run() { - // Call promptAuth of browserElementParent, to show the prompt. - prompt.browserElementParent.promptAuth( - self._createAuthDetail(prompt.channel, prompt.authInfo), - callback - ); - }, - }; - - Services.tm.dispatchToMainThread(runnable); - }, - - _getFrameFromChannel(channel) { - let loadContext = channel.notificationCallbacks.getInterface( - Ci.nsILoadContext - ); - return loadContext.topFrameElement; - }, - - _createAuthDetail(channel, authInfo) { - let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo); - return { - host: hostname, - path: channel.URI.pathQueryRef, - realm: httpRealm, - username: authInfo.username, - isProxy: !!(authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY), - isOnlyPassword: !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD), - }; - }, - - // The code is taken from nsLoginManagerPrompter.js, with slight - // modification for parameter name consistency here. - _getAuthTarget(channel, authInfo) { - let hostname, realm; - - // If our proxy is demanding authentication, don't use the - // channel's actual destination. - if (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) { - if (!(channel instanceof Ci.nsIProxiedChannel)) { - throw new Error("proxy auth needs nsIProxiedChannel"); - } - - let info = channel.proxyInfo; - if (!info) { - throw new Error("proxy auth needs nsIProxyInfo"); - } - - // Proxies don't have a scheme, but we'll use "moz-proxy://" - // so that it's more obvious what the login is for. - var idnService = Cc["@mozilla.org/network/idn-service;1"].getService( - Ci.nsIIDNService - ); - hostname = - "moz-proxy://" + - idnService.convertUTF8toACE(info.host) + - ":" + - info.port; - realm = authInfo.realm; - if (!realm) { - realm = hostname; - } - - return [hostname, realm]; - } - - hostname = this._getFormattedHostname(channel.URI); - - // If a HTTP WWW-Authenticate header specified a realm, that value - // will be available here. If it wasn't set or wasn't HTTP, we'll use - // the formatted hostname instead. - realm = authInfo.realm; - if (!realm) { - realm = hostname; - } - - return [hostname, realm]; - }, - - /** - * Strip out things like userPass and path for display. - */ - _getFormattedHostname(uri) { - return uri.scheme + "://" + uri.hostPort; - }, -}; - -function AuthPromptWrapper(oldImpl, browserElementImpl) { - this._oldImpl = oldImpl; - this._browserElementImpl = browserElementImpl; -} - -AuthPromptWrapper.prototype = { - QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), - promptAuth(channel, level, authInfo) { - if (this._canGetParentElement(channel)) { - return this._browserElementImpl.promptAuth(channel, level, authInfo); - } - return this._oldImpl.promptAuth(channel, level, authInfo); - }, - - asyncPromptAuth(channel, callback, context, level, authInfo) { - if (this._canGetParentElement(channel)) { - return this._browserElementImpl.asyncPromptAuth( - channel, - callback, - context, - level, - authInfo - ); - } - return this._oldImpl.asyncPromptAuth( - channel, - callback, - context, - level, - authInfo - ); - }, - - _canGetParentElement(channel) { - try { - let context = channel.notificationCallbacks.getInterface( - Ci.nsILoadContext - ); - let frame = context.topFrameElement; - if (!frame) { - return false; - } - - if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame)) { - return false; - } - - return true; - } catch (e) { - return false; - } - }, -}; - -function BrowserElementPromptFactory(toWrap) { - this._wrapped = toWrap; -} - -BrowserElementPromptFactory.prototype = { - classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"), - QueryInterface: ChromeUtils.generateQI(["nsIPromptFactory"]), - - _mayUseNativePrompt() { - try { - return Services.prefs.getBoolPref("browser.prompt.allowNative"); - } catch (e) { - // This properity is default to true. - return true; - } - }, - - _getNativePromptIfAllowed(win, iid, err) { - if (this._mayUseNativePrompt()) { - return this._wrapped.getPrompt(win, iid); - } - - // Not allowed, throw an exception. - throw err; - }, - - getPrompt(win, iid) { - // It is possible for some object to get a prompt without passing - // valid reference of window, like nsNSSComponent. In such case, we - // should just fall back to the native prompt service - if (!win) { - return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG); - } - - if ( - iid.number != Ci.nsIPrompt.number && - iid.number != Ci.nsIAuthPrompt2.number - ) { - debug( - "We don't recognize the requested IID (" + - iid + - ", " + - "allowed IID: " + - "nsIPrompt=" + - Ci.nsIPrompt + - ", " + - "nsIAuthPrompt2=" + - Ci.nsIAuthPrompt2 + - ")" - ); - return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG); - } - - // Try to find a BrowserElementChild for the window. - let browserElementChild = - BrowserElementPromptService.getBrowserElementChildForWindow(win); - - if (iid.number === Ci.nsIAuthPrompt2.number) { - debug("Caller requests an instance of nsIAuthPrompt2."); - - if (browserElementChild) { - // If we are able to get a BrowserElementChild, it means that - // the auth prompt is for a mozbrowser. Therefore we don't need to - // fall back. - return new BrowserElementAuthPrompt().QueryInterface(iid); - } - - // Because nsIAuthPrompt2 is called in parent process. If caller - // wants nsIAuthPrompt2 and we cannot get BrowserElementchild, - // it doesn't mean that we should fallback. It is possible that we can - // get the BrowserElementParent from nsIChannel that passed to - // functions of nsIAuthPrompt2. - if (this._mayUseNativePrompt()) { - return new AuthPromptWrapper( - this._wrapped.getPrompt(win, iid), - new BrowserElementAuthPrompt().QueryInterface(iid) - ).QueryInterface(iid); - } - // Falling back is not allowed, so we don't need wrap the - // BrowserElementPrompt. - return new BrowserElementAuthPrompt().QueryInterface(iid); - } - - if (!browserElementChild) { - debug( - "We can't find a browserElementChild for " + win + ", " + win.location - ); - return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE); - } - - debug("Returning wrapped getPrompt for " + win); - return new BrowserElementPrompt(win, browserElementChild).QueryInterface( - iid - ); - }, -}; - -var BrowserElementPromptService = { - QueryInterface: ChromeUtils.generateQI([ - "nsIObserver", - "nsISupportsWeakReference", - ]), - - _initialized: false, - - _init() { - if (this._initialized) { - return; - } - - this._initialized = true; - this._browserElementParentMap = new WeakMap(); - - Services.obs.addObserver( - this, - "outer-window-destroyed", - /* ownsWeak = */ true - ); - - // Wrap the existing @mozilla.org/prompter;1 implementation. - var contractID = "@mozilla.org/prompter;1"; - var oldCID = Cm.contractIDToCID(contractID); - var newCID = BrowserElementPromptFactory.prototype.classID; - var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory); - - if (oldCID == newCID) { - debug("WARNING: Wrapped prompt factory is already installed!"); - return; - } - - var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory); - var newInstance = new BrowserElementPromptFactory(oldInstance); - - var newFactory = { - createInstance(iid) { - return newInstance.QueryInterface(iid); - }, - }; - Cm.registerFactory( - newCID, - "BrowserElementPromptService's prompter;1 wrapper", - contractID, - newFactory - ); - - debug("Done installing new prompt factory."); - }, - - _getOuterWindowID(win) { - return win.docShell.outerWindowID; - }, - - _browserElementChildMap: {}, - mapWindowToBrowserElementChild(win, browserElementChild) { - this._browserElementChildMap[this._getOuterWindowID(win)] = - browserElementChild; - }, - unmapWindowToBrowserElementChild(win) { - delete this._browserElementChildMap[this._getOuterWindowID(win)]; - }, - - getBrowserElementChildForWindow(win) { - // We only have a mapping for <iframe mozbrowser>s, not their inner - // <iframes>, so we look up win.top below. window.top (when called from - // script) respects <iframe mozbrowser> boundaries. - return this._browserElementChildMap[this._getOuterWindowID(win.top)]; - }, - - mapFrameToBrowserElementParent(frame, browserElementParent) { - this._browserElementParentMap.set(frame, browserElementParent); - }, - - getBrowserElementParentForFrame(frame) { - return this._browserElementParentMap.get(frame); - }, - - _observeOuterWindowDestroyed(outerWindowID) { - let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data; - debug("observeOuterWindowDestroyed " + id); - delete this._browserElementChildMap[outerWindowID.data]; - }, - - observe(subject, topic, data) { - switch (topic) { - case "outer-window-destroyed": - this._observeOuterWindowDestroyed(subject); - break; - default: - debug("Observed unexpected topic " + topic); - } - }, -}; - -BrowserElementPromptService._init(); diff --git a/dom/browser-element/components.conf b/dom/browser-element/components.conf deleted file mode 100644 index a29f7dc66a..0000000000 --- a/dom/browser-element/components.conf +++ /dev/null @@ -1,14 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Classes = [ - { - 'cid': '{9f171ac4-0939-4ef8-b360-3408aedc3060}', - 'contract_ids': ['@mozilla.org/dom/browser-element-api;1'], - 'jsm': 'resource://gre/modules/BrowserElementParent.jsm', - 'constructor': 'BrowserElementParent', - }, -] diff --git a/dom/browser-element/moz.build b/dom/browser-element/moz.build deleted file mode 100644 index 1f83fbd436..0000000000 --- a/dom/browser-element/moz.build +++ /dev/null @@ -1,37 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -with Files("**"): - BUG_COMPONENT = ("Core", "DOM: Core & HTML") - -XPIDL_SOURCES += [ - "nsIBrowserElementAPI.idl", -] - -XPIDL_MODULE = "browser-element" - -EXTRA_JS_MODULES += [ - "BrowserElementParent.jsm", - "BrowserElementPromptService.jsm", -] - -XPCOM_MANIFESTS += [ - "components.conf", -] - -LOCAL_INCLUDES += [ - "/dom/html", -] - -include("/ipc/chromium/chromium-config.mozbuild") - -FINAL_LIBRARY = "xul" - -LOCAL_INCLUDES += [ - "/dom/", - "/dom/base", - "/dom/ipc", -] diff --git a/dom/browser-element/nsIBrowserElementAPI.idl b/dom/browser-element/nsIBrowserElementAPI.idl deleted file mode 100644 index 4aeba96be8..0000000000 --- a/dom/browser-element/nsIBrowserElementAPI.idl +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -webidl FrameLoader; - -%{C++ -#define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1" -#define BROWSER_ELEMENT_API_CID \ - { 0x651db7e3, 0x1734, 0x4536, \ - { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } } -%} - -/** - * Interface to the BrowserElementParent implementation. All methods - * but setFrameLoader throw when the remote process is dead. - */ -[scriptable, uuid(57758c10-6036-11e5-a837-0800200c9a66)] -interface nsIBrowserElementAPI : nsISupports -{ - /** - * Notify frame scripts that support the API to destroy. - */ - void destroyFrameScripts(); - - void setFrameLoader(in FrameLoader frameLoader); - - void sendMouseEvent(in AString type, - in uint32_t x, - in uint32_t y, - in uint32_t button, - in uint32_t clickCount, - in uint32_t mifiers); - void goBack(); - void goForward(); - void reload(in boolean hardReload); - void stop(); - Promise getCanGoBack(); - Promise getCanGoForward(); -}; diff --git a/dom/cache/Cache.cpp b/dom/cache/Cache.cpp index d9b94f6b14..3f84e08e66 100644 --- a/dom/cache/Cache.cpp +++ b/dom/cache/Cache.cpp @@ -81,13 +81,12 @@ static bool IsValidPutResponseStatus(Response& aResponse, ErrorResult& aRv) { if ((aPolicy == PutStatusPolicy::RequireOK && !aResponse.Ok()) || aResponse.Status() == 206) { - nsCString type(ResponseTypeValues::GetString(aResponse.Type())); - nsAutoString url; aResponse.GetUrl(url); aRv.ThrowTypeError<MSG_CACHE_ADD_FAILED_RESPONSE>( - type, IntToCString(aResponse.Status()), NS_ConvertUTF16toUTF8(url)); + GetEnumString(aResponse.Type()), IntToCString(aResponse.Status()), + NS_ConvertUTF16toUTF8(url)); return false; } diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp index e2622fafa2..b5647ba791 100644 --- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -467,8 +467,9 @@ already_AddRefed<CacheStorage> CacheStorage::Constructor( static_assert( CHROME_ONLY_NAMESPACE == (uint32_t)CacheStorageNamespace::Chrome, "Chrome namespace should match webidl Chrome enum"); - static_assert(NUMBER_OF_NAMESPACES == CacheStorageNamespaceValues::Count, - "Number of namespace should match webidl count"); + static_assert( + NUMBER_OF_NAMESPACES == ContiguousEnumSize<CacheStorageNamespace>::value, + "Number of namespace should match webidl count"); Namespace ns = static_cast<Namespace>(aNamespace); nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); diff --git a/dom/cache/Connection.cpp b/dom/cache/Connection.cpp index 32e5549101..e9c9becebc 100644 --- a/dom/cache/Connection.cpp +++ b/dom/cache/Connection.cpp @@ -227,6 +227,11 @@ Connection::GetVariableLimit(int32_t* aResultOut) { } NS_IMETHODIMP +Connection::SetVariableLimit(int32_t aLimit) { + return mBase->SetVariableLimit(aLimit); +} + +NS_IMETHODIMP Connection::BeginTransaction() { return mBase->BeginTransaction(); } NS_IMETHODIMP diff --git a/dom/cache/DBAction.cpp b/dom/cache/DBAction.cpp index 119242d93c..8f1e962747 100644 --- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/quota/PersistenceType.h" #include "mozilla/dom/quota/ResultExtensions.h" #include "mozilla/net/nsFileProtocolHandler.h" +#include "mozilla/AppShutdown.h" #include "mozIStorageConnection.h" #include "mozIStorageService.h" #include "mozStorageCID.h" @@ -62,7 +63,7 @@ void DBAction::RunOnTarget( MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata); MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir); - if (IsCanceled()) { + if (IsCanceled() || AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { aResolver->Resolve(NS_ERROR_ABORT); return; } diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index a7e16fda22..0c2c9cd4fb 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -278,7 +278,7 @@ static_assert(int(HeadersGuardEnum::None) == 0 && int(HeadersGuardEnum::Request_no_cors) == 2 && int(HeadersGuardEnum::Response) == 3 && int(HeadersGuardEnum::Immutable) == 4 && - HeadersGuardEnumValues::Count == 5, + ContiguousEnumSize<HeadersGuardEnum>::value == 5, "HeadersGuardEnum values are as expected"); static_assert(int(ReferrerPolicy::_empty) == 0 && int(ReferrerPolicy::No_referrer) == 1 && @@ -289,18 +289,18 @@ static_assert(int(ReferrerPolicy::_empty) == 0 && int(ReferrerPolicy::Same_origin) == 6 && int(ReferrerPolicy::Strict_origin) == 7 && int(ReferrerPolicy::Strict_origin_when_cross_origin) == 8 && - ReferrerPolicyValues::Count == 9, + ContiguousEnumSize<ReferrerPolicy>::value == 9, "ReferrerPolicy values are as expected"); static_assert(int(RequestMode::Same_origin) == 0 && int(RequestMode::No_cors) == 1 && int(RequestMode::Cors) == 2 && int(RequestMode::Navigate) == 3 && - RequestModeValues::Count == 4, + ContiguousEnumSize<RequestMode>::value == 4, "RequestMode values are as expected"); static_assert(int(RequestCredentials::Omit) == 0 && int(RequestCredentials::Same_origin) == 1 && int(RequestCredentials::Include) == 2 && - RequestCredentialsValues::Count == 3, + ContiguousEnumSize<RequestCredentials>::value == 3, "RequestCredentials values are as expected"); static_assert(int(RequestCache::Default) == 0 && int(RequestCache::No_store) == 1 && @@ -308,19 +308,19 @@ static_assert(int(RequestCache::Default) == 0 && int(RequestCache::No_cache) == 3 && int(RequestCache::Force_cache) == 4 && int(RequestCache::Only_if_cached) == 5 && - RequestCacheValues::Count == 6, + ContiguousEnumSize<RequestCache>::value == 6, "RequestCache values are as expected"); static_assert(int(RequestRedirect::Follow) == 0 && int(RequestRedirect::Error) == 1 && int(RequestRedirect::Manual) == 2 && - RequestRedirectValues::Count == 3, + ContiguousEnumSize<RequestRedirect>::value == 3, "RequestRedirect values are as expected"); static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 && int(ResponseType::Default) == 2 && int(ResponseType::Error) == 3 && int(ResponseType::Opaque) == 4 && int(ResponseType::Opaqueredirect) == 5 && - ResponseTypeValues::Count == 6, + ContiguousEnumSize<ResponseType>::value == 6, "ResponseType values are as expected"); // If the static_asserts below fails, it means that you have changed the diff --git a/dom/cache/Manager.cpp b/dom/cache/Manager.cpp index ef36309e13..68a19e5862 100644 --- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -651,6 +651,15 @@ class Manager::CacheMatchAction final : public Manager::BaseAction { /* aCreate */ false))); } + // If we entered shutdown on the main thread while we were doing IO, + // bail out now. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { + if (stream) { + stream->Close(); + } + return NS_ERROR_ABORT; + } + mStreamList->Add(mResponse.mBodyId, std::move(stream)); return NS_OK; @@ -718,6 +727,15 @@ class Manager::CacheMatchAllAction final : public Manager::BaseAction { /* aCreate */ false))); } + // If we entered shutdown on the main thread while we were doing IO, + // bail out now. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { + if (stream) { + stream->Close(); + } + return NS_ERROR_ABORT; + } + mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream)); } @@ -1264,6 +1282,15 @@ class Manager::CacheKeysAction final : public Manager::BaseAction { /* aCreate */ false))); } + // If we entered shutdown on the main thread while we were doing IO, + // bail out now. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { + if (stream) { + stream->Close(); + } + return NS_ERROR_ABORT; + } + mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream)); } @@ -1334,6 +1361,15 @@ class Manager::StorageMatchAction final : public Manager::BaseAction { /* aCreate */ false))); } + // If we entered shutdown on the main thread while we were doing IO, + // bail out now. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { + if (stream) { + stream->Close(); + } + return NS_ERROR_ABORT; + } + mStreamList->Add(mSavedResponse.mBodyId, std::move(stream)); return NS_OK; diff --git a/dom/cache/test/browser/browser_cache_pb_window.js b/dom/cache/test/browser/browser_cache_pb_window.js index bb45210796..bd59063a32 100644 --- a/dom/cache/test/browser/browser_cache_pb_window.js +++ b/dom/cache/test/browser/browser_cache_pb_window.js @@ -2,15 +2,15 @@ var name = "pb-window-cache"; function testMatch(browser) { - return SpecialPowers.spawn(browser, [name], function (name) { + return SpecialPowers.spawn(browser, [name], function () { return new Promise((resolve, reject) => { content.caches .match("http://foo.com") - .then(function (response) { + .then(function () { ok(true, "caches.match() should be successful"); resolve(); }) - .catch(function (err) { + .catch(function () { ok(false, "caches.match() should not throw error"); reject(); }); @@ -23,11 +23,11 @@ function testHas(browser) { return new Promise(function (resolve, reject) { content.caches .has(name) - .then(function (result) { + .then(function () { ok(true, "caches.has() should be successful"); resolve(); }) - .catch(function (err) { + .catch(function () { ok(false, "caches.has() should not throw error"); reject(); }); @@ -40,11 +40,11 @@ function testOpen(browser) { return new Promise(function (resolve, reject) { content.caches .open(name) - .then(function (c) { + .then(function () { ok(true, "caches.open() should be successful"); resolve(); }) - .catch(function (err) { + .catch(function () { ok(false, "caches.open() should not throw error"); reject(); }); @@ -57,11 +57,11 @@ function testDelete(browser) { return new Promise(function (resolve, reject) { content.caches .delete(name) - .then(function (result) { + .then(function () { ok(true, "caches.delete() should be successful"); resolve(); }) - .catch(function (err) { + .catch(function () { ok(false, "caches.delete should not throw error"); reject(); }); @@ -70,15 +70,15 @@ function testDelete(browser) { } function testKeys(browser) { - return SpecialPowers.spawn(browser, [name], function (name) { + return SpecialPowers.spawn(browser, [name], function () { return new Promise(function (resolve, reject) { content.caches .keys() - .then(function (names) { + .then(function () { ok(true, "caches.keys() should be successful"); resolve(); }) - .catch(function (err) { + .catch(function () { ok(false, "caches.keys should not throw error"); reject(); }); @@ -129,7 +129,7 @@ function test() { privateTab = BrowserTestUtils.addTab(pw.gBrowser, "http://example.com/"); return BrowserTestUtils.browserLoaded(privateTab.linkedBrowser); }) - .then(tab => { + .then(() => { return Promise.all([ testMatch(privateTab.linkedBrowser), testHas(privateTab.linkedBrowser), diff --git a/dom/cache/test/mochitest/driver.js b/dom/cache/test/mochitest/driver.js index db0231c168..646135a70e 100644 --- a/dom/cache/test/mochitest/driver.js +++ b/dom/cache/test/mochitest/driver.js @@ -32,14 +32,14 @@ function runTests(testFile, order) { // adapted from dom/indexedDB/test/helpers.js function clearStorage() { - var clearUnpartitionedStorage = new Promise(function (resolve, reject) { + var clearUnpartitionedStorage = new Promise(function (resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); var cb = SpecialPowers.wrapCallback(resolve); request.callback = cb; }); - var clearPartitionedStorage = new Promise(function (resolve, reject) { + var clearPartitionedStorage = new Promise(function (resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).partitionedPrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -77,7 +77,7 @@ function runTests(testFile, order) { navigator.serviceWorker == null && SpecialPowers.getBoolPref("dom.cache.privateBrowsing.enabled") ) { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { resolve(true); }); } @@ -86,7 +86,7 @@ function runTests(testFile, order) { } function runFrameTest() { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { var iframe = document.createElement("iframe"); iframe.src = "frame.html"; iframe.onload = function () { diff --git a/dom/cache/test/mochitest/test_cache.js b/dom/cache/test/mochitest/test_cache.js index 7d6d01b34b..1bbe3304c5 100644 --- a/dom/cache/test/mochitest/test_cache.js +++ b/dom/cache/test/mochitest/test_cache.js @@ -67,7 +67,7 @@ caches ); return snafu.put(req, res); }) - .then(function (v) { + .then(function () { return snafu; }); }) diff --git a/dom/cache/test/mochitest/test_cache_match_request.js b/dom/cache/test/mochitest/test_cache_match_request.js index f7a7001444..c6a96c0f2f 100644 --- a/dom/cache/test/mochitest/test_cache_match_request.js +++ b/dom/cache/test/mochitest/test_cache_match_request.js @@ -162,7 +162,7 @@ function testRequest( .then(function () { return caches .match(request, { cacheName: name + "mambojambo" }) - .then(function (result) { + .then(function () { is( typeof r, "undefined", diff --git a/dom/cache/test/mochitest/test_cache_orphaned_body.html b/dom/cache/test/mochitest/test_cache_orphaned_body.html index 98c0f42267..d540d90c9d 100644 --- a/dom/cache/test/mochitest/test_cache_orphaned_body.html +++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html @@ -23,7 +23,7 @@ function setupTestIframe() { } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -33,7 +33,7 @@ function clearStorage() { } function storageUsage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var cb = SpecialPowers.wrapCallback(function(request) { @@ -45,7 +45,7 @@ function storageUsage() { } function groupUsage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { navigator.storage.estimate().then(storageEstimation => { resolve(storageEstimation.usage, 0); }); @@ -53,7 +53,7 @@ function groupUsage() { } function workerGroupUsage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { function workerScript() { navigator.storage.estimate().then(storageEstimation => { postMessage(storageEstimation.usage); @@ -71,7 +71,7 @@ function workerGroupUsage() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -81,7 +81,7 @@ function resetStorage() { } function gc() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { SpecialPowers.exactGC(resolve); }); } diff --git a/dom/cache/test/mochitest/test_cache_orphaned_cache.html b/dom/cache/test/mochitest/test_cache_orphaned_cache.html index 7724a26fef..7d08fc3c55 100644 --- a/dom/cache/test/mochitest/test_cache_orphaned_cache.html +++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html @@ -23,7 +23,7 @@ function setupTestIframe() { } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -33,7 +33,7 @@ function clearStorage() { } function storageUsage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var cb = SpecialPowers.wrapCallback(function(request) { @@ -45,7 +45,7 @@ function storageUsage() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -55,7 +55,7 @@ function resetStorage() { } function gc() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { SpecialPowers.exactGC(resolve); }); } diff --git a/dom/cache/test/mochitest/test_cache_padding.html b/dom/cache/test/mochitest/test_cache_padding.html index 61b890e948..a912f47547 100644 --- a/dom/cache/test/mochitest/test_cache_padding.html +++ b/dom/cache/test/mochitest/test_cache_padding.html @@ -23,7 +23,7 @@ function setupTestIframe() { } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -33,7 +33,7 @@ function clearStorage() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -43,7 +43,7 @@ function resetStorage() { } function getStorageUsage(fromMemory) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var cb = SpecialPowers.wrapCallback(function(request) { diff --git a/dom/cache/test/mochitest/test_cache_restart.html b/dom/cache/test/mochitest/test_cache_restart.html index bebb45c093..2472f7ab4e 100644 --- a/dom/cache/test/mochitest/test_cache_restart.html +++ b/dom/cache/test/mochitest/test_cache_restart.html @@ -22,7 +22,7 @@ function setupTestIframe() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -47,9 +47,9 @@ SpecialPowers.pushPrefEnv({ }).then(function() { return resetStorage(); }).then(function() { - return cache.match(url).then(function(resp) { + return cache.match(url).then(function() { ok(false, "old cache reference should not work after reset"); - }).catch(function(err) { + }).catch(function() { ok(true, "old cache reference should not work after reset"); }); }).then(function() { diff --git a/dom/cache/test/mochitest/test_cache_shrink.html b/dom/cache/test/mochitest/test_cache_shrink.html index 0d50f9e94b..def68ec770 100644 --- a/dom/cache/test/mochitest/test_cache_shrink.html +++ b/dom/cache/test/mochitest/test_cache_shrink.html @@ -23,7 +23,7 @@ function setupTestIframe() { } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -33,7 +33,7 @@ function clearStorage() { } function storageUsage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var cb = SpecialPowers.wrapCallback(function(request) { @@ -45,7 +45,7 @@ function storageUsage() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -55,7 +55,7 @@ function resetStorage() { } function gc() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { SpecialPowers.exactGC(resolve); }); } diff --git a/dom/cache/test/mochitest/test_cache_tons_of_fd.html b/dom/cache/test/mochitest/test_cache_tons_of_fd.html index c370201e50..00f896448c 100644 --- a/dom/cache/test/mochitest/test_cache_tons_of_fd.html +++ b/dom/cache/test/mochitest/test_cache_tons_of_fd.html @@ -23,7 +23,7 @@ } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); diff --git a/dom/cache/test/mochitest/test_cache_updateUsage.html b/dom/cache/test/mochitest/test_cache_updateUsage.html index f095886a1b..5feb70a89b 100644 --- a/dom/cache/test/mochitest/test_cache_updateUsage.html +++ b/dom/cache/test/mochitest/test_cache_updateUsage.html @@ -23,7 +23,7 @@ function setupTestIframe() { } function clearStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.clearStoragesForPrincipal(principal); @@ -33,7 +33,7 @@ function clearStorage() { } function resetStorage() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var request = qms.resetStoragesForPrincipal(principal); @@ -43,7 +43,7 @@ function resetStorage() { } function getStorageUsage(fromMemory) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var qms = SpecialPowers.Services.qms; var principal = SpecialPowers.wrap(document).nodePrincipal; var cb = SpecialPowers.wrapCallback(function(request) { diff --git a/dom/cache/test/mochitest/test_caches.js b/dom/cache/test/mochitest/test_caches.js index c1d3abaff1..31a4238792 100644 --- a/dom/cache/test/mochitest/test_caches.js +++ b/dom/cache/test/mochitest/test_caches.js @@ -17,7 +17,7 @@ function testHas() { ok(!has, name + " should not exist yet"); return caches.open(name); }) - .then(function (c) { + .then(function () { return caches.has(name); }) .then(function (has) { diff --git a/dom/cache/test/xpcshell/test_empty_directories.js b/dom/cache/test/xpcshell/test_empty_directories.js index da8158d993..15fa9d351a 100644 --- a/dom/cache/test/xpcshell/test_empty_directories.js +++ b/dom/cache/test/xpcshell/test_empty_directories.js @@ -3,7 +3,7 @@ */ function resetStorage() { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { var qms = Services.qms; var request = qms.reset(); request.callback = resolve; diff --git a/dom/canvas/CacheInvalidator.h b/dom/canvas/CacheInvalidator.h index e63f1e4cc2..909aacfc37 100644 --- a/dom/canvas/CacheInvalidator.h +++ b/dom/canvas/CacheInvalidator.h @@ -9,8 +9,7 @@ #include "mozilla/Maybe.h" #include "mozilla/UniquePtr.h" -#include <unordered_map> -#include <unordered_set> +#include "DmdStdContainers.h" #include <vector> // - @@ -25,7 +24,7 @@ class CacheInvalidator { friend class AbstractCache; private: - mutable std::unordered_set<AbstractCache*> mCaches; + mutable webgl::dmd_unordered_set<AbstractCache*> mCaches; public: virtual ~CacheInvalidator() { @@ -38,6 +37,12 @@ class CacheInvalidator { } void InvalidateCaches() const; + + // - + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return mCaches.SizeOfExcludingThis(mso); + } }; // - @@ -122,11 +127,15 @@ class CacheWeakMap final { } }; - using MapT = - std::unordered_map<const KeyT*, UniquePtr<Entry>, DerefHash, DerefEqual>; + using MapT = webgl::dmd_unordered_map<const KeyT*, UniquePtr<Entry>, + DerefHash, DerefEqual>; MapT mMap; public: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return mMap.SizeOfExcludingThis(mso); + } + UniquePtr<Entry> MakeEntry(const KeyT& key, ValueT&& value) { return UniquePtr<Entry>(new Entry(*this, key, std::move(value))); } diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 28579d4b75..529466cc81 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -24,7 +24,7 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/GeneratePlaceholderCanvasData.h" #include "mozilla/dom/VideoFrame.h" -#include "mozilla/gfx/CanvasManagerChild.h" +#include "mozilla/gfx/CanvasShutdownManager.h" #include "nsPresContext.h" #include "nsIInterfaceRequestorUtils.h" @@ -118,6 +118,7 @@ #include "mozilla/dom/SVGImageElement.h" #include "mozilla/dom/TextMetrics.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/Logging.h" #include "nsGlobalWindowInner.h" #include "nsDeviceContext.h" #include "nsFontMetrics.h" @@ -871,41 +872,6 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext) -class CanvasShutdownObserver final : public nsIObserver { - public: - explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas) - : mCanvas(aCanvas) {} - - void OnShutdown() { - if (!mCanvas) { - return; - } - - mCanvas = nullptr; - nsContentUtils::UnregisterShutdownObserver(this); - } - - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - private: - ~CanvasShutdownObserver() = default; - - CanvasRenderingContext2D* mCanvas; -}; - -NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver) - -NS_IMETHODIMP -CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) { - if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - mCanvas->OnShutdown(); - OnShutdown(); - } - - return NS_OK; -} - NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) @@ -1242,14 +1208,8 @@ void CanvasRenderingContext2D::OnShutdown() { } bool CanvasRenderingContext2D::AddShutdownObserver() { - auto* const canvasManager = CanvasManagerChild::Get(); + auto* const canvasManager = CanvasShutdownManager::Get(); if (NS_WARN_IF(!canvasManager)) { - if (NS_IsMainThread()) { - mShutdownObserver = new CanvasShutdownObserver(this); - nsContentUtils::RegisterShutdownObserver(mShutdownObserver); - return true; - } - mHasShutdown = true; return false; } @@ -1259,18 +1219,70 @@ bool CanvasRenderingContext2D::AddShutdownObserver() { } void CanvasRenderingContext2D::RemoveShutdownObserver() { - if (mShutdownObserver) { - mShutdownObserver->OnShutdown(); - mShutdownObserver = nullptr; + auto* const canvasManager = CanvasShutdownManager::MaybeGet(); + if (!canvasManager) { return; } - auto* const canvasManager = CanvasManagerChild::MaybeGet(); - if (!canvasManager) { + canvasManager->RemoveShutdownObserver(this); +} + +void CanvasRenderingContext2D::OnRemoteCanvasLost() { + // We only lose context / data if we are using remote canvas, which is only + // for accelerated targets. + if (!mBufferProvider || !mBufferProvider->IsAccelerated() || mIsContextLost) { return; } - canvasManager->RemoveShutdownObserver(this); + // 2. Set context's context lost to true. + mIsContextLost = mAllowContextRestore = true; + + // 3. Reset the rendering context to its default state given context. + ClearTarget(); + + // We dispatch because it isn't safe to call into the script event handlers, + // and we don't want to mutate our state in CanvasShutdownManager. + NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( + "CanvasRenderingContext2D::OnRemoteCanvasLost", [self = RefPtr{this}] { + // 4. Let shouldRestore be the result of firing an event named + // contextlost at canvas, with the cancelable attribute initialized to + // true. + self->mAllowContextRestore = self->DispatchEvent( + u"contextlost"_ns, CanBubble::eNo, Cancelable::eYes); + })); +} + +void CanvasRenderingContext2D::OnRemoteCanvasRestored() { + // We never lost our context if it was not a remote canvas, nor can we restore + // if we have already shutdown. + if (mHasShutdown || !mIsContextLost || !mAllowContextRestore) { + return; + } + + // We dispatch because it isn't safe to call into the script event handlers, + // and we don't want to mutate our state in CanvasShutdownManager. + NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( + "CanvasRenderingContext2D::OnRemoteCanvasRestored", + [self = RefPtr{this}] { + // 5. If shouldRestore is false, then abort these steps. + if (!self->mHasShutdown && self->mIsContextLost && + self->mAllowContextRestore) { + // 7. Set context's context lost to false. + self->mIsContextLost = false; + + // 6. Attempt to restore context by creating a backing storage using + // context's attributes and associating them with context. If this + // fails, then abort these steps. + if (!self->EnsureTarget()) { + self->mIsContextLost = true; + return; + } + + // 8. Fire an event named contextrestored at canvas. + self->DispatchEvent(u"contextrestored"_ns, CanBubble::eNo, + Cancelable::eNo); + } + })); } void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr, @@ -1489,23 +1501,47 @@ bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect, return true; } -bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, +bool CanvasRenderingContext2D::EnsureTarget(ErrorResult& aError, + const gfx::Rect* aCoveredRect, bool aWillClear) { if (AlreadyShutDown()) { gfxCriticalNoteOnce << "Attempt to render into a Canvas2d after shutdown."; SetErrorState(); + aError.ThrowInvalidStateError( + "Cannot use canvas after shutdown initiated."); + return false; + } + + // The spec doesn't say what to do in this case, but Chrome silently fails + // without throwing an error. We should at least throw if the canvas is + // permanently disabled. + if (NS_WARN_IF(mIsContextLost)) { + if (!mAllowContextRestore) { + aError.ThrowInvalidStateError( + "Cannot use canvas as context is lost forever."); + } return false; } if (mTarget) { - return mTarget != sErrorTarget.get(); + if (mTarget == sErrorTarget.get()) { + aError.ThrowInvalidStateError("Canvas is already in error state."); + return false; + } + return true; } // Check that the dimensions are sane if (mWidth > StaticPrefs::gfx_canvas_max_size() || - mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 || - mHeight < 0) { + mHeight > StaticPrefs::gfx_canvas_max_size()) { + SetErrorState(); + aError.ThrowInvalidStateError("Canvas exceeds max size."); + return false; + } + + if (mWidth < 0 || mHeight < 0) { SetErrorState(); + aError.ThrowInvalidStateError("Canvas has invalid size."); return false; } @@ -1547,7 +1583,7 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, if (!TryAcceleratedTarget(newTarget, newProvider) && !TrySharedTarget(newTarget, newProvider) && - !TryBasicTarget(newTarget, newProvider)) { + !TryBasicTarget(newTarget, newProvider, aError)) { gfxCriticalError( CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize()))) << "Failed borrow shared and basic targets."; @@ -1684,15 +1720,27 @@ bool CanvasRenderingContext2D::TryAcceleratedTarget( return false; } - if (!mCanvasElement) { - return false; - } - WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); - if (!renderer) { - return false; + if (mCanvasElement) { + MOZ_ASSERT(NS_IsMainThread()); + + WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); + if (NS_WARN_IF(!renderer)) { + return false; + } + + aOutProvider = PersistentBufferProviderAccelerated::Create( + GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor()); + } else if (mOffscreenCanvas && + StaticPrefs::gfx_canvas_remote_allow_offscreen()) { + RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); + if (NS_WARN_IF(!imageBridge)) { + return false; + } + + aOutProvider = PersistentBufferProviderAccelerated::Create( + GetSize(), GetSurfaceFormat(), imageBridge); } - aOutProvider = PersistentBufferProviderAccelerated::Create( - GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor()); + if (!aOutProvider) { return false; } @@ -1716,8 +1764,10 @@ bool CanvasRenderingContext2D::TrySharedTarget( } if (mCanvasElement) { + MOZ_ASSERT(NS_IsMainThread()); + WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); - if (!renderer) { + if (NS_WARN_IF(!renderer)) { return false; } @@ -1735,7 +1785,7 @@ bool CanvasRenderingContext2D::TrySharedTarget( return false; } - aOutProvider = layers::PersistentBufferProviderShared::Create( + aOutProvider = PersistentBufferProviderShared::Create( GetSize(), GetSurfaceFormat(), imageBridge, !mAllowAcceleration || GetEffectiveWillReadFrequently(), mOffscreenCanvas->GetWindowID()); @@ -1755,10 +1805,12 @@ bool CanvasRenderingContext2D::TrySharedTarget( bool CanvasRenderingContext2D::TryBasicTarget( RefPtr<gfx::DrawTarget>& aOutDT, - RefPtr<layers::PersistentBufferProvider>& aOutProvider) { + RefPtr<layers::PersistentBufferProvider>& aOutProvider, + ErrorResult& aError) { aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget( GetSize(), GetSurfaceFormat()); if (!aOutDT) { + aError.ThrowInvalidStateError("Canvas could not create basic draw target."); return false; } @@ -1767,6 +1819,7 @@ bool CanvasRenderingContext2D::TryBasicTarget( if (!aOutDT->IsValid()) { aOutDT = nullptr; + aError.ThrowInvalidStateError("Canvas could not init basic draw target."); return false; } @@ -2042,7 +2095,7 @@ CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget, // already exists, otherwise we get performance issues. See bug 1567054. if (!EnsureTarget()) { MOZ_ASSERT( - mTarget == sErrorTarget.get(), + mTarget == sErrorTarget.get() || mIsContextLost, "On EnsureTarget failure mTarget should be set to sErrorTarget."); // In rare circumstances we may have failed to create an error target. return mTarget ? mTarget->Snapshot() : nullptr; @@ -2110,36 +2163,36 @@ void CanvasRenderingContext2D::Restore() { void CanvasRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = mTarget->GetTransform(); newMatrix.PreScale(aX, aY); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform(); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::Translate(double aX, double aY, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = mTarget->GetTransform(); newMatrix.PreTranslate(aX, aY); SetTransformInternal(newMatrix); @@ -2148,12 +2201,12 @@ void CanvasRenderingContext2D::Translate(double aX, double aY, void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); newMatrix *= mTarget->GetTransform(); SetTransformInternal(newMatrix); @@ -2161,11 +2214,12 @@ void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return nullptr; } + + MOZ_ASSERT(IsTargetValid()); + RefPtr<DOMMatrix> matrix = new DOMMatrix(GetParentObject(), mTarget->GetTransform()); return matrix.forget(); @@ -2175,24 +2229,24 @@ void CanvasRenderingContext2D::SetTransform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + RefPtr<DOMMatrixReadOnly> matrix = DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); if (!aError.Failed()) { @@ -2420,11 +2474,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern( } else { // Special case for ImageBitmap ImageBitmap& imgBitmap = aSource.GetAsImageBitmap(); - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + if (!EnsureTarget(aError)) { return nullptr; } + + MOZ_ASSERT(IsTargetValid()); + RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget); if (!srcSurf) { aError.ThrowInvalidStateError( @@ -2441,12 +2496,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern( return pat.forget(); } - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + if (!EnsureTarget(aError)) { return nullptr; } + MOZ_ASSERT(IsTargetValid()); + // The canvas spec says that createPattern should use the first frame // of animated images auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | @@ -4883,12 +4938,12 @@ UniquePtr<TextMetrics> CanvasRenderingContext2D::DrawOrMeasureText( processor.mPt.x *= processor.mAppUnitsPerDevPixel; processor.mPt.y *= processor.mAppUnitsPerDevPixel; - EnsureTarget(); - if (!IsTargetValid()) { - aError = NS_ERROR_FAILURE; + if (!EnsureTarget(aError)) { return nullptr; } + MOZ_ASSERT(IsTargetValid()); + Matrix oldTransform = mTarget->GetTransform(); bool restoreTransform = false; // if text is over aMaxWidth, then scale the text horizontally such that its @@ -5329,11 +5384,12 @@ void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage, OffscreenCanvas* offscreenCanvas = nullptr; VideoFrame* videoFrame = nullptr; - EnsureTarget(); - if (!IsTargetValid()) { + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + if (aImage.IsHTMLCanvasElement()) { HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement(); element = canvas; @@ -5776,11 +5832,12 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, GlobalAlpha() == 1.0f && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); const gfx::Rect drawRect(aX, aY, aW, aH); - EnsureTarget(discardContent ? &drawRect : nullptr); - if (!IsTargetValid()) { + if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { return; } + MOZ_ASSERT(IsTargetValid()); + RefPtr<nsPresContext> presContext; nsIDocShell* docshell = aWindow.GetDocShell(); if (docshell) { @@ -5882,7 +5939,11 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, &thebes.ref()); // If this canvas was contained in the drawn window, the pre-transaction // callback may have returned its DT. If so, we must reacquire it here. - EnsureTarget(discardContent ? &drawRect : nullptr); + if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { + return; + } + + MOZ_ASSERT(IsTargetValid()); if (drawDT) { RefPtr<SourceSurface> snapshot = drawDT->Snapshot(); @@ -6222,12 +6283,12 @@ void CanvasRenderingContext2D::PutImageData_explicit( // composition operator must not affect the getImageData() and // putImageData() methods. const gfx::Rect putRect(dirtyRect); - EnsureTarget(&putRect); - - if (!IsTargetValid()) { - return aRv.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aRv, &putRect)) { + return; } + MOZ_ASSERT(IsTargetValid()); + DataSourceSurface::MappedSurface map; uint8_t* dstData; IntSize dstSize; diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 3376e1033c..75a57ab14f 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -570,6 +570,10 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, void OnShutdown(); + bool IsContextLost() const { return mIsContextLost; } + void OnRemoteCanvasLost(); + void OnRemoteCanvasRestored(); + /** * Update CurrentState().filter with the filter description for * CurrentState().filterChain. @@ -699,8 +703,16 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, * * Returns true on success. */ - bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr, + bool EnsureTarget(ErrorResult& aError, + const gfx::Rect* aCoveredRect = nullptr, bool aWillClear = false); + + bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr, + bool aWillClear = false) { + IgnoredErrorResult error; + return EnsureTarget(error, aCoveredRect, aWillClear); + } + // Attempt to borrow a new target from an existing buffer provider. bool BorrowTarget(const gfx::IntRect& aPersistedRect, bool aNeedsClear); @@ -714,7 +726,8 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, RefPtr<layers::PersistentBufferProvider>& aOutProvider); bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT, - RefPtr<layers::PersistentBufferProvider>& aOutProvider); + RefPtr<layers::PersistentBufferProvider>& aOutProvider, + ErrorResult& aError); void RegisterAllocation(); @@ -753,7 +766,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, * Check if the target is valid after calling EnsureTarget. */ bool IsTargetValid() const { - return !!mTarget && mTarget != sErrorTarget.get(); + return !!mTarget && mTarget != sErrorTarget.get() && !mIsContextLost; } /** @@ -836,8 +849,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, bool mWillReadFrequently = false; // Whether or not we have already shutdown. bool mHasShutdown = false; + // Whether or not remote canvas is currently unavailable. + bool mIsContextLost = false; + // Whether or not we can restore the context after restoration. + bool mAllowContextRestore = true; - RefPtr<CanvasShutdownObserver> mShutdownObserver; bool AddShutdownObserver(); void RemoveShutdownObserver(); bool AlreadyShutDown() const { return mHasShutdown; } @@ -1008,9 +1024,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, RefPtr<nsAtom> fontLanguage; nsFont fontFont; - EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles; - EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles; - EnumeratedArray<Style, Style::MAX, nscolor> colorStyles; + EnumeratedArray<Style, RefPtr<CanvasGradient>, size_t(Style::MAX)> + gradientStyles; + EnumeratedArray<Style, RefPtr<CanvasPattern>, size_t(Style::MAX)> + patternStyles; + EnumeratedArray<Style, nscolor, size_t(Style::MAX)> colorStyles; nsCString font; CanvasTextAlign textAlign = CanvasTextAlign::Start; diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp index bde265f9a0..1614d2ead2 100644 --- a/dom/canvas/ClientWebGLContext.cpp +++ b/dom/canvas/ClientWebGLContext.cpp @@ -847,7 +847,12 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) { ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability); const auto principalKey = GetPrincipalHashValue(); const auto initDesc = webgl::InitContextDesc{ - mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey}; + .isWebgl2 = mIsWebGL2, + .resistFingerprinting = resistFingerprinting, + .principalKey = principalKey, + .size = requestedSize, + .options = options, + }; // - @@ -1264,28 +1269,13 @@ RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() { MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride); const auto desc = webgl::ReadPixelsDesc{{0, 0}, size}; - const auto range = Range<uint8_t>(map.GetData(), stride * size.y); - if (!DoReadPixels(desc, range)) return nullptr; - - const auto begin = range.begin().get(); + const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y); + if (!DoReadPixels(desc, pixels)) return nullptr; - std::vector<uint8_t> temp; - temp.resize(stride); - for (const auto i : IntegerRange(size.y / 2)) { - const auto top = begin + stride * i; - const auto bottom = begin + stride * (size.y - 1 - i); - memcpy(temp.data(), top, stride); - memcpy(top, bottom, stride); - gfxUtils::ConvertBGRAtoRGBA(top, stride); - - memcpy(bottom, temp.data(), stride); - gfxUtils::ConvertBGRAtoRGBA(bottom, stride); - } - - if (size.y % 2) { - const auto middle = begin + stride * (size.y / 2); - gfxUtils::ConvertBGRAtoRGBA(middle, stride); - } + // RGBA->BGRA and flip-y. + MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData( + pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(), + stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y})); } return surf; @@ -3419,7 +3409,7 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset, const auto& child = notLost->outOfProcess; child->FlushPendingCmds(); mozilla::ipc::Shmem rawShmem; - if (!child->SendGetBufferSubData(target, srcByteOffset, destView->length(), + if (!child->SendGetBufferSubData(target, srcByteOffset, destView->size(), &rawShmem)) { return; } @@ -3429,14 +3419,13 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset, return; } - const auto shmemView = shmem.ByteRange(); - MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView->length()); + const auto shmemView = Span{shmem.ByteRange()}; + MOZ_RELEASE_ASSERT(shmemView.size() == 1 + destView->size()); - const auto ok = bool(*(shmemView.begin().get())); - const auto srcView = - Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()}; + const auto ok = bool(shmemView[0]); + const auto srcView = shmemView.subspan(1); if (ok) { - Memcpy(destView->begin(), srcView.begin(), srcView.length()); + Memcpy(&*destView, srcView); } }); } @@ -3463,8 +3452,8 @@ void ClientWebGLContext::BufferData( if (!ValidateNonNull("src", maybeSrc)) return; const auto& src = maybeSrc.Value(); - src.ProcessFixedData([&](const Span<uint8_t>& aData) { - Run<RPROC(BufferData)>(target, RawBuffer<>(aData), usage); + src.ProcessFixedData([&](const Span<const uint8_t>& aData) { + Run<RPROC(BufferData)>(target, aData, usage); }); } @@ -3481,7 +3470,7 @@ void ClientWebGLContext::BufferData(GLenum target, if (!range) { return; } - Run<RPROC(BufferData)>(target, RawBuffer<>(*range), usage); + Run<RPROC(BufferData)>(target, *range, usage); }); } @@ -3491,8 +3480,8 @@ void ClientWebGLContext::BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset, const dom::ArrayBuffer& src) { const FuncScope funcScope(*this, "bufferSubData"); - src.ProcessFixedData([&](const Span<uint8_t>& aData) { - Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(aData), + src.ProcessFixedData([&](const Span<const uint8_t>& aData) { + Run<RPROC(BufferSubData)>(target, dstByteOffset, aData, /* unsynchronized */ false); }); } @@ -3511,7 +3500,7 @@ void ClientWebGLContext::BufferSubData(GLenum target, if (!range) { return; } - Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(*range), + Run<RPROC(BufferSubData)>(target, dstByteOffset, *range, /* unsynchronized */ false); }); } @@ -3811,9 +3800,7 @@ void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, void ClientWebGLContext::InvalidateFramebuffer( GLenum target, const dom::Sequence<GLenum>& attachments, ErrorResult& unused) { - const auto range = MakeRange(attachments); - const auto& buffer = RawBufferView(range); - Run<RPROC(InvalidateFramebuffer)>(target, buffer); + Run<RPROC(InvalidateFramebuffer)>(target, Span{attachments}); // Never invalidate the backbuffer, so never needs AfterDrawCall. } @@ -3821,9 +3808,8 @@ void ClientWebGLContext::InvalidateFramebuffer( void ClientWebGLContext::InvalidateSubFramebuffer( GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height, ErrorResult& unused) { - const auto range = MakeRange(attachments); - const auto& buffer = RawBufferView(range); - Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height); + Run<RPROC(InvalidateSubFramebuffer)>(target, Span{attachments}, x, y, width, + height); // Never invalidate the backbuffer, so never needs AfterDrawCall. } @@ -4102,13 +4088,11 @@ Range<T> SubRange(const Range<T>& full, const size_t offset, return Range<T>{newBegin, newBegin + length}; } -Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, - size_t bytesPerElem, - GLuint elemOffset, - GLuint elemCountOverride) { - const auto byteRange = Range(data); // In bytes. - - auto elemCount = byteRange.length() / bytesPerElem; +Maybe<Span<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, + size_t bytesPerElem, + GLuint elemOffset, + GLuint elemCountOverride) { + auto elemCount = data.size() / bytesPerElem; if (elemOffset > elemCount) return {}; elemCount -= elemOffset; @@ -4116,9 +4100,8 @@ Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, if (elemCountOverride > elemCount) return {}; elemCount = elemCountOverride; } - const auto subrange = - SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem); - return Some(subrange); + return Some( + data.subspan(elemOffset * bytesPerElem, elemCount * bytesPerElem)); } // - @@ -4165,7 +4148,8 @@ void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) { CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) * unpack.metrics.totalRows; if (bytesUpperBound.isValid()) { - cpuData->Shrink(bytesUpperBound.value()); + auto& span = *cpuData; + span = span.subspan(0, std::min(span.size(), bytesUpperBound.value())); } } } @@ -4241,7 +4225,7 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget, return Some(webgl::TexUnpackBlobDesc{imageTarget, size.value(), gfxAlphaType::NonPremult, - Some(RawBuffer<>{*range}), + Some(*range), {}}); }); } @@ -4378,12 +4362,9 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget, const auto& contextInfo = mNotLost->info; const auto fallbackReason = [&]() -> Maybe<std::string> { - if (!respecFormat) { - return Some(std::string{ - "Fast uploads not supported for TexSubImage. Use TexImage."}); - } - auto fallbackReason = BlitPreventReason( - level, offset, respecFormat, pi, *desc, contextInfo.isRgb8Renderable); + auto fallbackReason = + BlitPreventReason(level, offset, respecFormat, pi, *desc, + contextInfo.optionalRenderableFormatBits); if (fallbackReason) return fallbackReason; const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType]; @@ -4588,7 +4569,7 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims, RunWithGCData<RPROC(CompressedTexImage)>( std::move(aNoGC), sub, imageTarget, static_cast<uint32_t>(level), - format, CastUvec3(offset), CastUvec3(isize), RawBuffer<>{*range}, + format, CastUvec3(offset), CastUvec3(isize), *range, static_cast<uint32_t>(pboImageSize), Maybe<uint64_t>()); return; }); @@ -4603,8 +4584,8 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims, Run<RPROC(CompressedTexImage)>( sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset), - CastUvec3(isize), RawBuffer<>(), static_cast<uint32_t>(pboImageSize), - Some(*src.mPboOffset)); + CastUvec3(isize), Span<const uint8_t>{}, + static_cast<uint32_t>(pboImageSize), Some(*src.mPboOffset)); } void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget, @@ -4876,9 +4857,8 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType, const auto begin = reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) + elemOffset; - const auto range = Range{begin, availCount}; - RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, - RawBuffer{range}); + const auto range = Span{begin, availCount}; + RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, range); } // - @@ -5095,7 +5075,7 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, } bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, - const Range<uint8_t> dest) const { + const Span<uint8_t> dest) const { const auto notLost = mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF. if (!notLost) return false; @@ -5107,7 +5087,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, const auto& child = notLost->outOfProcess; child->FlushPendingCmds(); webgl::ReadPixelsResultIpc res = {}; - if (!child->SendReadPixels(desc, dest.length(), &res)) { + if (!child->SendReadPixels(desc, dest.size(), &res)) { res = {}; } if (!res.byteStride || !res.shmem) return false; @@ -5119,7 +5099,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, return false; } - const auto& shmemBytes = shmem.ByteRange(); + const auto& shmemBytes = Span{shmem.ByteRange()}; const auto pii = webgl::PackingInfoInfo::For(desc.pi); if (!pii) { @@ -5136,19 +5116,13 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width); const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp; - auto srcItr = shmemBytes.begin() + byteOffset; - auto destItr = dest.begin() + byteOffset; + const auto srcSubrect = shmemBytes.subspan(byteOffset); + const auto destSubrect = dest.subspan(byteOffset); for (const auto i : IntegerRange(subrect.height)) { - if (i) { - // Don't trigger an assert on the last loop by pushing a RangedPtr past - // its bounds. - srcItr += byteStride; - destItr += byteStride; - MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end()); - MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end()); - } - Memcpy(destItr, srcItr, xByteSize); + const auto srcRow = srcSubrect.subspan(i * byteStride, xByteSize); + const auto destRow = destSubrect.subspan(i * byteStride, xByteSize); + Memcpy(&destRow, srcRow); } return true; @@ -5858,7 +5832,7 @@ void ClientWebGLContext::AttachShader(WebGLProgramJS& prog, void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog, const GLuint location, const nsAString& name) const { - const FuncScope funcScope(*this, "detachShader"); + const FuncScope funcScope(*this, "bindAttribLocation"); if (IsContextLost()) return; if (!prog.ValidateUsable(*this, "program")) return; @@ -6572,7 +6546,7 @@ const webgl::LinkResult& ClientWebGLContext::GetLinkResult( // --------------------------- -Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( +Maybe<Span<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( const Span<uint8_t>& bytes, size_t elemSize, GLuint elemOffset, GLuint elemCountOverride, const GLenum errorEnum) const { size_t elemCount = bytes.Length() / elemSize; @@ -6590,8 +6564,7 @@ Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( elemCount = elemCountOverride; } - return Some(Range<uint8_t>( - bytes.Subspan(elemOffset * elemSize, elemCount * elemSize))); + return Some(bytes.Subspan(elemOffset * elemSize, elemCount * elemSize)); } // --------------------------- diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h index 47e03d29c5..3c011a3027 100644 --- a/dom/canvas/ClientWebGLContext.h +++ b/dom/canvas/ClientWebGLContext.h @@ -929,11 +929,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal, void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const; public: - Maybe<Range<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes, - size_t elemSize, - GLuint elemOffset, - GLuint elemCountOverride, - const GLenum errorEnum) const; + Maybe<Span<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes, + size_t elemSize, + GLuint elemOffset, + GLuint elemCountOverride, + const GLenum errorEnum) const; protected: template <typename T> @@ -1087,7 +1087,7 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal, private: RefPtr<gfx::DataSourceSurface> BackBufferSnapshot(); [[nodiscard]] bool DoReadPixels(const webgl::ReadPixelsDesc&, - Range<uint8_t>) const; + Span<uint8_t>) const; uvec2 DrawingBufferSize(); // - diff --git a/dom/canvas/DmdStdContainers.h b/dom/canvas/DmdStdContainers.h new file mode 100644 index 0000000000..2975abc580 --- /dev/null +++ b/dom/canvas/DmdStdContainers.h @@ -0,0 +1,100 @@ +/* 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 dom_canvas_DmdStdContainers_h +#define dom_canvas_DmdStdContainers_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/layers/BuildConstants.h" +#include <unordered_map> +#include <unordered_set> + +namespace mozilla::webgl { + +// - + +template <class T> +class dmd_allocator { + public: + using value_type = T; + + private: + size_t mMallocSize = 0; + + public: + dmd_allocator() = default; + + // - + + template <class U> + friend class dmd_allocator; + + template <class U> + explicit dmd_allocator(const dmd_allocator<U>& rhs) { + if constexpr (kIsDmd) { + mMallocSize = rhs.mMallocSize; + } + } + + // - + + value_type* allocate(const size_t n) { + const auto p = std::allocator<value_type>{}.allocate(n); + if constexpr (kIsDmd) { + mMallocSize += moz_malloc_size_of(p); + } + return p; + } + + void deallocate(value_type* const p, const size_t n) { + if constexpr (kIsDmd) { + mMallocSize -= moz_malloc_size_of(p); + } + std::allocator<value_type>{}.deallocate(p, n); + } + + // - + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf) const { + return mMallocSize; + } +}; + +// - + +template <class Key, class T, class Hash = std::hash<Key>, + class KeyEqual = std::equal_to<Key>, + class Allocator = dmd_allocator<std::pair<const Key, T>>, + class _StdT = std::unordered_map<Key, T, Hash, KeyEqual, Allocator>> +class dmd_unordered_map : public _StdT { + public: + using StdT = _StdT; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + const auto& a = StdT::get_allocator(); + return a.SizeOfExcludingThis(mso); + } +}; + +// - + +template <class Key, class Hash = std::hash<Key>, + class KeyEqual = std::equal_to<Key>, + class Allocator = dmd_allocator<Key>, + class _StdT = std::unordered_set<Key, Hash, KeyEqual, Allocator>> +class dmd_unordered_set : public _StdT { + public: + using StdT = _StdT; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + const auto& a = StdT::get_allocator(); + return a.SizeOfExcludingThis(mso); + } +}; + +// - + +} // namespace mozilla::webgl + +#endif // dom_canvas_DmdStdContainers_h diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp index 6055fa72e1..eae6dd78f3 100644 --- a/dom/canvas/DrawTargetWebgl.cpp +++ b/dom/canvas/DrawTargetWebgl.cpp @@ -19,8 +19,6 @@ #include "mozilla/gfx/PathHelpers.h" #include "mozilla/gfx/PathSkia.h" #include "mozilla/gfx/Swizzle.h" -#include "mozilla/layers/CanvasRenderer.h" -#include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/layers/RemoteTextureMap.h" #include "skia/include/core/SkPixmap.h" @@ -467,9 +465,13 @@ bool SharedContextWebgl::Initialize() { const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting( "Fallback", RFPTarget::WebGLRenderCapability); - const auto initDesc = - webgl::InitContextDesc{/*isWebgl2*/ true, resistFingerprinting, - /*size*/ {1, 1}, options, /*principalKey*/ 0}; + const auto initDesc = webgl::InitContextDesc{ + .isWebgl2 = true, + .resistFingerprinting = resistFingerprinting, + .principalKey = 0, + .size = {1, 1}, + .options = options, + }; webgl::InitContextResult initResult; mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult); @@ -621,13 +623,14 @@ bool SharedContextWebgl::SetNoClipMask() { } mWebgl->ActiveTexture(1); mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask); - static const uint8_t solidMask[4] = {0xFF, 0xFF, 0xFF, 0xFF}; - mWebgl->TexImage( - 0, LOCAL_GL_RGBA8, {0, 0, 0}, {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE}, - {LOCAL_GL_TEXTURE_2D, - {1, 1, 1}, - gfxAlphaType::NonPremult, - Some(RawBuffer(Range<const uint8_t>(solidMask, sizeof(solidMask))))}); + static const auto solidMask = + std::array<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF}; + mWebgl->TexImage(0, LOCAL_GL_RGBA8, {0, 0, 0}, + {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE}, + {LOCAL_GL_TEXTURE_2D, + {1, 1, 1}, + gfxAlphaType::NonPremult, + Some(Span{solidMask})}); InitTexParameters(mNoClipMask, false); mWebgl->ActiveTexture(0); mLastClipMask = mNoClipMask; @@ -1916,11 +1919,11 @@ bool SharedContextWebgl::UploadSurface(DataSourceSurface* aData, int32_t bpp = BytesPerPixel(aFormat); // Get the data pointer range considering the sampling rect offset and // size. - Range<const uint8_t> range( + Span<const uint8_t> range( map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp, std::max(aSrcRect.height - 1, 0) * size_t(stride) + aSrcRect.width * bpp); - texDesc.cpuData = Some(RawBuffer(range)); + texDesc.cpuData = Some(range); // If the stride happens to be 4 byte aligned, assume that is the // desired alignment regardless of format (even A8). Otherwise, we // default to byte alignment. @@ -4724,7 +4727,8 @@ void DrawTargetWebgl::EndFrame() { } bool DrawTargetWebgl::CopyToSwapChain( - layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId, + layers::TextureType aTextureType, layers::RemoteTextureId aId, + layers::RemoteTextureOwnerId aOwnerId, layers::RemoteTextureOwnerClient* aOwnerClient) { if (!mWebglValid && !FlushFromSkia()) { return false; @@ -4739,11 +4743,8 @@ bool DrawTargetWebgl::CopyToSwapChain( StaticPrefs::gfx_canvas_accelerated_async_present(); options.remoteTextureId = aId; options.remoteTextureOwnerId = aOwnerId; - const RefPtr<layers::ImageBridgeChild> imageBridge = - layers::ImageBridgeChild::GetSingleton(); - auto texType = layers::TexTypeForWebgl(imageBridge); - return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, texType, options, - aOwnerClient); + return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, aTextureType, + options, aOwnerClient); } already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget( diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h index 7bc1a83abb..955ccaabf0 100644 --- a/dom/canvas/DrawTargetWebgl.h +++ b/dom/canvas/DrawTargetWebgl.h @@ -577,7 +577,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr { void* GetNativeSurface(NativeSurfaceType aType) override; bool CopyToSwapChain( - layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId, + layers::TextureType aTextureType, layers::RemoteTextureId aId, + layers::RemoteTextureOwnerId aOwnerId, layers::RemoteTextureOwnerClient* aOwnerClient = nullptr); void OnMemoryPressure() { mSharedContext->OnMemoryPressure(); } diff --git a/dom/canvas/HostWebGLContext.h b/dom/canvas/HostWebGLContext.h index c508a164c8..cc385bc26a 100644 --- a/dom/canvas/HostWebGLContext.h +++ b/dom/canvas/HostWebGLContext.h @@ -482,8 +482,9 @@ class HostWebGLContext final : public SupportsWeakPtr { return GetWebGL2Context()->GetBufferSubData(target, srcByteOffset, dest); } - void BufferData(GLenum target, const RawBuffer<>& srcData, GLenum usage) const { - mContext->BufferData(target, srcData.size(), srcData.begin(), usage); + void BufferData(GLenum target, const Span<const uint8_t>& srcData, + GLenum usage) const { + mContext->BufferData(target, srcData.size(), srcData.data(), usage); } void BufferData_SizeOnly(GLenum target, size_t byteSize, GLenum usage) const { @@ -491,11 +492,10 @@ class HostWebGLContext final : public SupportsWeakPtr { } void BufferSubData(GLenum target, uint64_t dstByteOffset, - const RawBuffer<>& srcData, + const Span<const uint8_t>& srcData, bool unsynchronized = false) const { - const auto& range = srcData.Data(); - mContext->BufferSubData(target, dstByteOffset, range.length(), - range.begin().get(), unsynchronized); + mContext->BufferSubData(target, dstByteOffset, srcData.size(), + srcData.data(), unsynchronized); } // -------------------------- Framebuffer Objects -------------------------- @@ -507,16 +507,15 @@ class HostWebGLContext final : public SupportsWeakPtr { } void InvalidateFramebuffer(GLenum target, - const RawBuffer<const GLenum>& attachments) const { - GetWebGL2Context()->InvalidateFramebuffer(target, MakeRange(attachments)); + const Span<const GLenum>& attachments) const { + GetWebGL2Context()->InvalidateFramebuffer(target, attachments); } void InvalidateSubFramebuffer(GLenum target, - const RawBuffer<const GLenum>& attachments, - GLint x, GLint y, GLsizei width, - GLsizei height) const { - GetWebGL2Context()->InvalidateSubFramebuffer(target, MakeRange(attachments), - x, y, width, height); + const Span<const GLenum>& attachments, GLint x, + GLint y, GLsizei width, GLsizei height) const { + GetWebGL2Context()->InvalidateSubFramebuffer(target, attachments, x, y, + width, height); } void ReadBuffer(GLenum mode) const { GetWebGL2Context()->ReadBuffer(mode); } @@ -554,10 +553,11 @@ class HostWebGLContext final : public SupportsWeakPtr { // CompressedTexSubImage if `sub` void CompressedTexImage(bool sub, GLenum imageTarget, uint32_t level, GLenum format, const uvec3& offset, const uvec3& size, - const RawBuffer<>& src, const uint32_t pboImageSize, + const Span<const uint8_t>& src, + const uint32_t pboImageSize, const Maybe<uint64_t>& pboOffset) const { mContext->CompressedTexImage(sub, imageTarget, level, format, offset, size, - MakeRange(src), pboImageSize, pboOffset); + src, pboImageSize, pboOffset); } // CopyTexSubImage if `!respecFormat` @@ -603,8 +603,8 @@ class HostWebGLContext final : public SupportsWeakPtr { // ------------------------ Uniforms and attributes ------------------------ void UniformData(uint32_t loc, bool transpose, - const RawBuffer<webgl::UniformDataVal>& data) const { - mContext->UniformData(loc, transpose, data.Data()); + const Span<const webgl::UniformDataVal>& data) const { + mContext->UniformData(loc, transpose, data); } void VertexAttrib4T(GLuint index, const webgl::TypedQuad& data) const { diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp index 65a9feaa56..28641a1c68 100644 --- a/dom/canvas/ImageBitmap.cpp +++ b/dom/canvas/ImageBitmap.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ImageBitmap.h" +#include "mozilla/AppShutdown.h" #include "mozilla/CheckedInt.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/CanvasRenderingContext2D.h" @@ -362,6 +363,7 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) { return nullptr; } + const int bpp = BytesPerPixel(aSurface->GetFormat()); const IntSize srcSize = aSurface->GetSize(); uint8_t* srcBufferPtr = srcMap.GetData(); const uint32_t stride = srcMap.GetStride(); @@ -372,7 +374,8 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) { } for (int i = 0; i < srcSize.height / 2; ++i) { - std::swap_ranges(srcBufferPtr + stride * i, srcBufferPtr + stride * (i + 1), + std::swap_ranges(srcBufferPtr + stride * i, + srcBufferPtr + stride * i + srcSize.width * bpp, srcBufferPtr + stride * (srcSize.height - 1 - i)); } @@ -1018,7 +1021,6 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal( // handle alpha premultiplication if surface not of correct type gfxAlphaType alphaType = aAlphaType; - bool mustCopy = aMustCopy; bool requiresPremultiply = false; bool requiresUnpremultiply = false; @@ -1027,29 +1029,26 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal( aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { requiresUnpremultiply = true; alphaType = gfxAlphaType::NonPremult; - if (!aAllocatedImageData) { - mustCopy = true; - } } else if (aAlphaType == gfxAlphaType::NonPremult && aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) { requiresPremultiply = true; alphaType = gfxAlphaType::Premult; - if (!aAllocatedImageData) { - mustCopy = true; - } } } /* - * if we don't own the data and need to create a new buffer to flip Y. + * if we don't own the data and need to modify the buffer. * or * we need to crop and flip, where crop must come first. * or - * Or the caller demands a copy (WebGL contexts). + * the caller demands a copy (WebGL contexts). */ - if ((aOptions.mImageOrientation == ImageOrientation::FlipY && - (!aAllocatedImageData || aCropRect.isSome())) || - mustCopy) { + bool willModify = aOptions.mImageOrientation == ImageOrientation::FlipY || + requiresPremultiply || requiresUnpremultiply; + if ((willModify && !aAllocatedImageData) || + (aOptions.mImageOrientation == ImageOrientation::FlipY && + aCropRect.isSome()) || + aMustCopy) { dataSurface = surface->GetDataSurface(); dataSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect); @@ -1430,7 +1429,7 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( return nullptr; } - bool needToReportMemoryAllocation = true; + bool needToReportMemoryAllocation = false; return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, writeOnly, needToReportMemoryAllocation, diff --git a/dom/canvas/ImageUtils.cpp b/dom/canvas/ImageUtils.cpp index 485b87c162..432fa3355e 100644 --- a/dom/canvas/ImageUtils.cpp +++ b/dom/canvas/ImageUtils.cpp @@ -15,27 +15,27 @@ using namespace mozilla::gfx; namespace mozilla::dom { -static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat( +static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromSurfaceFromat( SurfaceFormat aSurfaceFormat) { switch (aSurfaceFormat) { case SurfaceFormat::B8G8R8A8: case SurfaceFormat::B8G8R8X8: - return ImageBitmapFormat::BGRA32; + return Some(ImageBitmapFormat::BGRA32); case SurfaceFormat::R8G8B8A8: case SurfaceFormat::R8G8B8X8: - return ImageBitmapFormat::RGBA32; + return Some(ImageBitmapFormat::RGBA32); case SurfaceFormat::R8G8B8: - return ImageBitmapFormat::RGB24; + return Some(ImageBitmapFormat::RGB24); case SurfaceFormat::B8G8R8: - return ImageBitmapFormat::BGR24; + return Some(ImageBitmapFormat::BGR24); case SurfaceFormat::HSV: - return ImageBitmapFormat::HSV; + return Some(ImageBitmapFormat::HSV); case SurfaceFormat::Lab: - return ImageBitmapFormat::Lab; + return Some(ImageBitmapFormat::Lab); case SurfaceFormat::Depth: - return ImageBitmapFormat::DEPTH; + return Some(ImageBitmapFormat::DEPTH); case SurfaceFormat::A8: - return ImageBitmapFormat::GRAY8; + return Some(ImageBitmapFormat::GRAY8); case SurfaceFormat::R5G6B5_UINT16: case SurfaceFormat::YUV: case SurfaceFormat::NV12: @@ -43,11 +43,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat( case SurfaceFormat::P016: case SurfaceFormat::UNKNOWN: default: - return ImageBitmapFormat::EndGuard_; + return Nothing(); } } -static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( +static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromPlanarYCbCrData( layers::PlanarYCbCrData const* aData) { MOZ_ASSERT(aData); @@ -68,11 +68,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( !CbInterval.Intersects(CrInterval)) { // Three planes. switch (aData->mChromaSubsampling) { case ChromaSubsampling::FULL: - return ImageBitmapFormat::YUV444P; + return Some(ImageBitmapFormat::YUV444P); case ChromaSubsampling::HALF_WIDTH: - return ImageBitmapFormat::YUV422P; + return Some(ImageBitmapFormat::YUV422P); case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT: - return ImageBitmapFormat::YUV420P; + return Some(ImageBitmapFormat::YUV420P); default: break; } @@ -83,14 +83,14 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( // planes. if (!YInterval.Intersects(CbInterval) && aData->mCbChannel == aData->mCrChannel - 1) { // Two planes. - return ImageBitmapFormat::YUV420SP_NV12; // Y-Cb-Cr + return Some(ImageBitmapFormat::YUV420SP_NV12); // Y-Cb-Cr } else if (!YInterval.Intersects(CrInterval) && aData->mCrChannel == aData->mCbChannel - 1) { // Two planes. - return ImageBitmapFormat::YUV420SP_NV21; // Y-Cr-Cb + return Some(ImageBitmapFormat::YUV420SP_NV21); // Y-Cr-Cb } } - return ImageBitmapFormat::EndGuard_; + return Nothing(); } // ImageUtils::Impl implements the _generic_ algorithm which always readback @@ -104,7 +104,7 @@ class ImageUtils::Impl { virtual ~Impl() = default; - virtual ImageBitmapFormat GetFormat() const { + virtual Maybe<ImageBitmapFormat> GetFormat() const { return GetImageBitmapFormatFromSurfaceFromat(Surface()->GetFormat()); } @@ -144,7 +144,7 @@ class YUVImpl final : public ImageUtils::Impl { aImage->GetFormat() == ImageFormat::NV_IMAGE); } - ImageBitmapFormat GetFormat() const override { + Maybe<ImageBitmapFormat> GetFormat() const override { return GetImageBitmapFormatFromPlanarYCbCrData(GetPlanarYCbCrData()); } @@ -189,7 +189,7 @@ ImageUtils::~ImageUtils() { } } -ImageBitmapFormat ImageUtils::GetFormat() const { +Maybe<ImageBitmapFormat> ImageUtils::GetFormat() const { MOZ_ASSERT(mImpl); return mImpl->GetFormat(); } diff --git a/dom/canvas/ImageUtils.h b/dom/canvas/ImageUtils.h index 46b391515c..6b9cd9ca71 100644 --- a/dom/canvas/ImageUtils.h +++ b/dom/canvas/ImageUtils.h @@ -13,6 +13,9 @@ namespace mozilla { +template <typename T> +class Maybe; + namespace layers { class Image; } @@ -51,7 +54,7 @@ class ImageUtils { explicit ImageUtils(layers::Image* aImage); ~ImageUtils(); - ImageBitmapFormat GetFormat() const; + Maybe<ImageBitmapFormat> GetFormat() const; uint32_t GetBufferLength() const; diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp index b1e2ec1694..9e1cbb3e75 100644 --- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp +++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp @@ -16,6 +16,7 @@ #include "mozilla/layers/PersistentBufferProvider.h" #include "mozilla/layers/TextureClientSharedSurface.h" #include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/StaticPrefs_gfx.h" #include "mozilla/SVGObserverUtils.h" #include "nsICanvasRenderingContextInternal.h" #include "nsRFPService.h" @@ -37,11 +38,25 @@ void OffscreenCanvasDisplayHelper::DestroyElement() { MOZ_ASSERT(NS_IsMainThread()); MutexAutoLock lock(mMutex); + if (mImageContainer) { + mImageContainer->ClearAllImages(); + mImageContainer = nullptr; + } + mFrontBufferSurface = nullptr; mCanvasElement = nullptr; } void OffscreenCanvasDisplayHelper::DestroyCanvas() { + if (auto* cm = gfx::CanvasManagerChild::Get()) { + cm->EndCanvasTransaction(); + } + MutexAutoLock lock(mMutex); + if (mImageContainer) { + mImageContainer->ClearAllImages(); + mImageContainer = nullptr; + } + mFrontBufferSurface = nullptr; mOffscreenCanvas = nullptr; mWorkerRef = nullptr; } @@ -140,6 +155,12 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor( nsICanvasRenderingContextInternal* aContext, layers::TextureType aTextureType, const Maybe<OffscreenCanvasDisplayData>& aData) { + auto endTransaction = MakeScopeExit([&]() { + if (auto* cm = gfx::CanvasManagerChild::Get()) { + cm->EndCanvasTransaction(); + } + }); + MutexAutoLock lock(mMutex); gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; @@ -393,81 +414,110 @@ already_AddRefed<gfx::SourceSurface> OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() { MOZ_ASSERT(NS_IsMainThread()); + class SnapshotWorkerRunnable final : public MainThreadWorkerRunnable { + public: + SnapshotWorkerRunnable(WorkerPrivate* aWorkerPrivate, + OffscreenCanvasDisplayHelper* aDisplayHelper) + : MainThreadWorkerRunnable(aWorkerPrivate, "SnapshotWorkerRunnable"), + mMonitor("SnapshotWorkerRunnable::mMonitor"), + mDisplayHelper(aDisplayHelper) {} + + bool WorkerRun(JSContext*, WorkerPrivate*) override { + // The OffscreenCanvas can only be freed on the worker thread, so we + // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its + // destructor. We just need to make sure we don't call into + // OffscreenCanvas while holding the lock since it calls back into the + // OffscreenCanvasDisplayHelper. + RefPtr<OffscreenCanvas> canvas; + { + MutexAutoLock lock(mDisplayHelper->mMutex); + canvas = mDisplayHelper->mOffscreenCanvas; + } + + // Now that we are on the correct thread, we can extract the snapshot. If + // it is a Skia surface, perform a copy to threading issues. + RefPtr<gfx::SourceSurface> surface; + if (canvas) { + if (auto* context = canvas->GetContext()) { + surface = + context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); + if (surface && surface->GetType() == gfx::SurfaceType::SKIA) { + surface = gfx::Factory::CopyDataSourceSurface( + static_cast<gfx::DataSourceSurface*>(surface.get())); + } + } + } + + MonitorAutoLock lock(mMonitor); + mSurface = std::move(surface); + mComplete = true; + lock.NotifyAll(); + return true; + } + + already_AddRefed<gfx::SourceSurface> Wait(int32_t aTimeoutMs) { + MonitorAutoLock lock(mMonitor); + + TimeDuration timeout = TimeDuration::FromMilliseconds(aTimeoutMs); + while (!mComplete) { + if (lock.Wait(timeout) == CVStatus::Timeout) { + return nullptr; + } + } + + return mSurface.forget(); + } + + private: + Monitor mMonitor; + RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper; + RefPtr<gfx::SourceSurface> mSurface MOZ_GUARDED_BY(mMonitor); + bool mComplete MOZ_GUARDED_BY(mMonitor) = false; + }; + bool hasAlpha; bool isAlphaPremult; gl::OriginPos originPos; - Maybe<uint32_t> managerId; - Maybe<int32_t> childId; HTMLCanvasElement* canvasElement; RefPtr<gfx::SourceSurface> surface; - Maybe<layers::RemoteTextureOwnerId> ownerId; + RefPtr<SnapshotWorkerRunnable> workerRunnable; { MutexAutoLock lock(mMutex); +#ifdef MOZ_WIDGET_ANDROID + // On Android, we cannot both display a GL context and read back the pixels. + if (mCanvasElement) { + return nullptr; + } +#endif + hasAlpha = !mData.mIsOpaque; isAlphaPremult = mData.mIsAlphaPremult; originPos = mData.mOriginPos; - ownerId = mData.mOwnerId; - managerId = mContextManagerId; - childId = mContextChildId; canvasElement = mCanvasElement; - surface = mFrontBufferSurface; - } - - if (surface) { - // We already have a copy of the front buffer in our process. - return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - } - -#ifdef MOZ_WIDGET_ANDROID - // On Android, we cannot both display a GL context and read back the pixels. - if (canvasElement) { - return nullptr; - } -#endif - - if (managerId && childId) { - // We don't have a usable surface, and the context lives in the compositor - // process. - return gfx::CanvasManagerChild::Get()->GetSnapshot( - managerId.value(), childId.value(), ownerId, - hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8, - hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft); - } - - if (!canvasElement) { - return nullptr; - } - - // If we don't have any protocol IDs, or an existing surface, it is possible - // it is a main thread OffscreenCanvas instance. If so, then the element's - // OffscreenCanvas is not neutered and has access to the context. We can use - // that to get the snapshot directly. - const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas(); - if (nsICanvasRenderingContextInternal* context = - offscreenCanvas->GetContext()) { - surface = context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); - surface = TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - if (surface) { - return surface.forget(); + if (mWorkerRef) { + workerRunnable = + MakeRefPtr<SnapshotWorkerRunnable>(mWorkerRef->Private(), this); + workerRunnable->Dispatch(); } } - // Finally, we can try peeking into the image container to see if we are able - // to do a readback via the TextureClient. - if (layers::ImageContainer* container = canvasElement->GetImageContainer()) { - AutoTArray<layers::ImageContainer::OwningImage, 1> images; - uint32_t generationCounter; - container->GetCurrentImages(&images, &generationCounter); - if (!images.IsEmpty()) { - if (layers::Image* image = images.LastElement().mImage) { - surface = image->GetAsSourceSurface(); - return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - } + if (workerRunnable) { + // We transferred to a DOM worker, so we need to do the readback on the + // owning thread and wait for the result. + surface = workerRunnable->Wait( + StaticPrefs::gfx_offscreencanvas_snapshot_timeout_ms()); + } else if (canvasElement) { + // If we have a context, it is owned by the main thread. + const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas(); + if (nsICanvasRenderingContextInternal* context = + offscreenCanvas->GetContext()) { + surface = + context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); } } - return nullptr; + return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); } already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() { diff --git a/dom/canvas/QueueParamTraits.h b/dom/canvas/QueueParamTraits.h index fbedcdea0b..a67f5abf51 100644 --- a/dom/canvas/QueueParamTraits.h +++ b/dom/canvas/QueueParamTraits.h @@ -98,6 +98,10 @@ template <> struct BytesAlwaysValidT<webgl::UniformDataVal> { static constexpr bool value = true; }; +template <> +struct BytesAlwaysValidT<const webgl::UniformDataVal> { + static constexpr bool value = true; +}; // - @@ -350,13 +354,13 @@ struct EnumSerializer { static bool Read(ConsumerView<U>& aConsumerView, ParamType* aResult) { DataType value; if (!aConsumerView.ReadParam(&value)) { - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"); return false; } if (!EnumValidator::IsLegalValue(static_cast<DataType>(value))) { - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"); return false; } diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp index 8598c36227..e13dc1c064 100644 --- a/dom/canvas/TexUnpackBlob.cpp +++ b/dom/canvas/TexUnpackBlob.cpp @@ -476,8 +476,7 @@ bool TexUnpackBytes::Validate(const WebGLContext* const webgl, CheckedInt<size_t> availBytes = 0; if (mDesc.cpuData) { - const auto& range = mDesc.cpuData->Data(); - availBytes = range.length(); + availBytes = mDesc.cpuData->size(); } else if (mDesc.pboOffset) { const auto& pboOffset = *mDesc.pboOffset; @@ -514,11 +513,7 @@ bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const uint8_t* uploadPtr = nullptr; if (mDesc.cpuData) { - const auto range = mDesc.cpuData->Data(); - uploadPtr = range.begin().get(); - if (!uploadPtr) { - MOZ_ASSERT(!range.length()); - } + uploadPtr = mDesc.cpuData->data(); } else if (mDesc.pboOffset) { uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset); } @@ -673,11 +668,10 @@ bool TexUnpackImage::Validate(const WebGLContext* const webgl, return ValidateUnpackPixels(webgl, pi, fullRows, *this); } -Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset, - const GLenum internalFormat, - const webgl::PackingInfo& pi, - const TexUnpackBlobDesc& desc, - const bool isRgb8Renderable) { +Maybe<std::string> BlitPreventReason( + const int32_t level, const ivec3& offset, const GLenum internalFormat, + const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc, + const OptionalRenderableFormatBits optionalRenderableFormatBits) { const auto& size = desc.size; const auto& unpacking = desc.unpacking; @@ -710,26 +704,71 @@ Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset, const auto formatReason = [&]() -> const char* { if (pi.type != LOCAL_GL_UNSIGNED_BYTE) { - return "`type` must be `UNSIGNED_BYTE`"; + return "`unpackType` must be `UNSIGNED_BYTE`"; } - switch (internalFormat) { + switch (pi.format) { case LOCAL_GL_RGBA: - case LOCAL_GL_RGBA8: - return nullptr; + return nullptr; // All internalFormats for unpackFormat=RGBA are + // renderable. case LOCAL_GL_RGB: + break; + + default: + return "`unpackFormat` must be `RGBA` or maybe `RGB`"; + } + + // - + + struct { + OptionalRenderableFormatBits bits; + const char* errorMsg; + } required; + + switch (internalFormat) { + case LOCAL_GL_RGB565: + return nullptr; + case LOCAL_GL_RGB: case LOCAL_GL_RGB8: - if (isRgb8Renderable) { - return nullptr; - } + required = { + OptionalRenderableFormatBits::RGB8, + "Unavailable, as blitting internalFormats RGB or RGB8 requires " + "that RGB8 must be a renderable format.", + }; + break; + case LOCAL_GL_SRGB: + case LOCAL_GL_SRGB8: + required = { + OptionalRenderableFormatBits::SRGB8, + "Unavailable, as blitting internalFormats SRGB or SRGB8 requires " + "that SRGB8 must be a renderable format.", + }; + break; + case 0: + // texSubImage, so internalFormat is unknown, and could be anything! + required = { + OptionalRenderableFormatBits::RGB8 | + OptionalRenderableFormatBits::SRGB8, + "Unavailable, as blitting texSubImage with unpackFormat=RGB " + "requires that RGB8 and SRGB8 must be renderable formats.", + }; break; + default: + gfxCriticalError() + << "Unexpected internalFormat for unpackFormat=RGB: 0x" + << gfx::hexa(internalFormat); + return "Unexpected internalFormat for unpackFormat=RGB"; } - if (isRgb8Renderable) { - return "effective format must be RGB8 or RGBA8"; - } else { - return "effective format must be RGBA8"; + + const auto availableBits = optionalRenderableFormatBits; + if ((required.bits | availableBits) != availableBits) { + return required.errorMsg; } + + // - + + return nullptr; }(); if (formatReason) return formatReason; @@ -761,7 +800,7 @@ bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const auto reason = BlitPreventReason(level, {xOffset, yOffset, zOffset}, dui->internalFormat, - pi, mDesc, webgl->mIsRgb8Renderable); + pi, mDesc, webgl->mOptionalRenderableFormatBits); if (reason) { webgl->GeneratePerfWarning( "Failed to hit GPU-copy fast-path." diff --git a/dom/canvas/TexUnpackBlob.h b/dom/canvas/TexUnpackBlob.h index 02cb8dec41..dc850fe5d4 100644 --- a/dom/canvas/TexUnpackBlob.h +++ b/dom/canvas/TexUnpackBlob.h @@ -45,7 +45,7 @@ Maybe<std::string> BlitPreventReason(int32_t level, const ivec3& offset, GLenum internalFormat, const webgl::PackingInfo&, const TexUnpackBlobDesc&, - bool isRgb8Renderable); + OptionalRenderableFormatBits); class TexUnpackBlob { public: diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h index 138dd85b80..018c81f298 100644 --- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -47,9 +47,9 @@ class WebGL2Context final : public WebGLContext { GLbitfield mask, GLenum filter); void InvalidateFramebuffer(GLenum target, - const Range<const GLenum>& attachments); + const Span<const GLenum>& attachments); void InvalidateSubFramebuffer(GLenum target, - const Range<const GLenum>& attachments, GLint x, + const Span<const GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height); void ReadBuffer(GLenum mode); diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp index 7056c83d03..ab9e848f62 100644 --- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -106,7 +106,7 @@ static bool ValidateFramebufferAttachmentEnum(WebGLContext* webgl, } bool WebGLContext::ValidateInvalidateFramebuffer( - GLenum target, const Range<const GLenum>& attachments, + GLenum target, const Span<const GLenum>& attachments, std::vector<GLenum>* const scopedVector, GLsizei* const out_glNumAttachments, const GLenum** const out_glAttachments) { @@ -139,8 +139,8 @@ bool WebGLContext::ValidateInvalidateFramebuffer( } DoBindFB(fb, target); - *out_glNumAttachments = attachments.length(); - *out_glAttachments = attachments.begin().get(); + *out_glNumAttachments = AutoAssertCast(attachments.size()); + *out_glAttachments = attachments.data(); if (fb) { for (const auto& attachment : attachments) { @@ -153,7 +153,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( if (!isDefaultFB) { MOZ_ASSERT(scopedVector->empty()); - scopedVector->reserve(attachments.length()); + scopedVector->reserve(attachments.size()); for (const auto& attachment : attachments) { switch (attachment) { case LOCAL_GL_COLOR: @@ -172,7 +172,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( MOZ_CRASH(); } } - *out_glNumAttachments = scopedVector->size(); + *out_glNumAttachments = AutoAssertCast(scopedVector->size()); *out_glAttachments = scopedVector->data(); } } @@ -183,7 +183,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( } void WebGL2Context::InvalidateFramebuffer( - GLenum target, const Range<const GLenum>& attachments) { + GLenum target, const Span<const GLenum>& attachments) { const FuncScope funcScope(*this, "invalidateFramebuffer"); std::vector<GLenum> scopedVector; @@ -210,7 +210,7 @@ void WebGL2Context::InvalidateFramebuffer( } void WebGL2Context::InvalidateSubFramebuffer( - GLenum target, const Range<const GLenum>& attachments, GLint x, GLint y, + GLenum target, const Span<const GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height) { const FuncScope funcScope(*this, "invalidateSubFramebuffer"); diff --git a/dom/canvas/WebGLChild.cpp b/dom/canvas/WebGLChild.cpp index cf676c2f53..ebfe35db88 100644 --- a/dom/canvas/WebGLChild.cpp +++ b/dom/canvas/WebGLChild.cpp @@ -87,10 +87,8 @@ void WebGLChild::FlushPendingCmds() { // Handle flushesSinceLastCongestionCheck mFlushedCmdInfo.flushesSinceLastCongestionCheck += 1; - const auto startCongestionCheck = 20; - const auto maybeIPCMessageCongestion = 70; - const auto eventTarget = GetCurrentSerialEventTarget(); - MOZ_ASSERT(eventTarget); + constexpr auto START_CONGESTION_CHECK_THRESHOLD = 20; + constexpr auto ASSUME_IPC_CONGESTION_THRESHOLD = 70; RefPtr<WebGLChild> self = this; size_t generation = self->mFlushedCmdInfo.congestionCheckGeneration; @@ -107,17 +105,24 @@ void WebGLChild::FlushPendingCmds() { // messages. // Due to the async nature of the async ping, it is possible for the flush // check to exceed maybeIPCMessageCongestion, but that it it still bounded. - if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == startCongestionCheck) { - SendPing()->Then(eventTarget, __func__, [self, generation]() { - if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) { - // Confirmed IPC messages congestion does not happen. - // Reset flushesSinceLastCongestionCheck for next congestion check. - self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; - self->mFlushedCmdInfo.congestionCheckGeneration++; - } - }); + if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == + START_CONGESTION_CHECK_THRESHOLD) { + const auto eventTarget = RefPtr{GetCurrentSerialEventTarget()}; + MOZ_ASSERT(eventTarget); + if (!eventTarget) { + NS_WARNING("GetCurrentSerialEventTarget()->nullptr in FlushPendingCmds."); + } else { + SendPing()->Then(eventTarget, __func__, [self, generation]() { + if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) { + // Confirmed IPC messages congestion does not happen. + // Reset flushesSinceLastCongestionCheck for next congestion check. + self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; + self->mFlushedCmdInfo.congestionCheckGeneration++; + } + }); + } } else if (mFlushedCmdInfo.flushesSinceLastCongestionCheck > - maybeIPCMessageCongestion) { + ASSUME_IPC_CONGESTION_THRESHOLD) { // IPC messages congestion might happen, send sync SyncPing for flushing // pending messages. SendSyncPing(); diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index c1e6cbbed2..ec19bfef3d 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -653,7 +653,7 @@ RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext* host, out->limits = *webgl->mLimits; out->uploadableSdTypes = UploadableSdTypes(); out->vendor = webgl->gl->Vendor(); - out->isRgb8Renderable = webgl->mIsRgb8Renderable; + out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits; return webgl; } @@ -710,15 +710,36 @@ void WebGLContext::FinishInit() { const auto tex = gl::ScopedTexture(gl); const auto fb = gl::ScopedFramebuffer(gl); gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex); - gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, 1, 1, 0, LOCAL_GL_RGB, - LOCAL_GL_UNSIGNED_BYTE, nullptr); - gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, tex, 0); - const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); - mIsRgb8Renderable = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); + const auto IsRenderable = [&](const GLint internalFormat, + const GLenum unpackFormat) { + gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, + unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr); + const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); + }; + + if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) { + mOptionalRenderableFormatBits |= + webgl::OptionalRenderableFormatBits::RGB8; + } + if (gl->IsSupported(gl::GLFeature::sRGB)) { + struct { + GLint internal; + GLenum unpack; + } formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB}; + const bool isEs2 = (gl->IsGLES() && gl->Version() < 300); + if (isEs2) { + formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB}; + } + if (IsRenderable(formats.internal, formats.unpack)) { + mOptionalRenderableFormatBits |= + webgl::OptionalRenderableFormatBits::SRGB8; + } + } } ////// diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 12727c4969..5ab584174c 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -317,7 +317,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { webgl::InitContextResult* out); private: - bool mIsRgb8Renderable = false; + webgl::OptionalRenderableFormatBits mOptionalRenderableFormatBits = + webgl::OptionalRenderableFormatBits{0}; void FinishInit(); protected: @@ -667,7 +668,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { ////////////////////////// void UniformData(uint32_t loc, bool transpose, - const Range<const webgl::UniformDataVal>& data) const; + const Span<const webgl::UniformDataVal>& data) const; //////////////////////////////////// @@ -968,8 +969,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { // ------------------------------------------------------------------------- // WebGL extensions (implemented in WebGLContextExtensions.cpp) - EnumeratedArray<WebGLExtensionID, WebGLExtensionID::Max, - std::unique_ptr<WebGLExtensionBase>> + EnumeratedArray<WebGLExtensionID, std::unique_ptr<WebGLExtensionBase>, + size_t(WebGLExtensionID::Max)> mExtensions; public: @@ -1156,7 +1157,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { bool ValidateFramebufferTarget(GLenum target) const; bool ValidateInvalidateFramebuffer(GLenum target, - const Range<const GLenum>& attachments, + const Span<const GLenum>& attachments, std::vector<GLenum>* const scopedVector, GLsizei* const out_glNumAttachments, const GLenum** const out_glAttachments); diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp index 7411cec02f..ef068c8999 100644 --- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -1280,7 +1280,7 @@ void WebGLContext::StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, void WebGLContext::UniformData( const uint32_t loc, const bool transpose, - const Range<const webgl::UniformDataVal>& data) const { + const Span<const webgl::UniformDataVal>& data) const { const FuncScope funcScope(*this, "uniform setter"); if (!IsWebGL2() && transpose) { @@ -1306,13 +1306,13 @@ void WebGLContext::UniformData( // - - const auto lengthInType = data.length(); + const auto lengthInType = data.size(); const auto elemCount = lengthInType / channels; if (elemCount > 1 && !validationInfo.isArray) { GenerateError( LOCAL_GL_INVALID_OPERATION, "(uniform %s) `values` length (%u) must exactly match size of %s.", - activeInfo.name.c_str(), lengthInType, + activeInfo.name.c_str(), (uint32_t)lengthInType, EnumString(activeInfo.elemType).c_str()); return; } @@ -1321,9 +1321,9 @@ void WebGLContext::UniformData( const auto& samplerInfo = locInfo->samplerInfo; if (samplerInfo) { - const auto idata = reinterpret_cast<const uint32_t*>(data.begin().get()); + const auto idata = ReinterpretToSpan<const uint32_t>::From(data); const auto maxTexUnits = GLMaxTextureUnits(); - for (const auto& val : Range<const uint32_t>(idata, elemCount)) { + for (const auto& val : idata) { if (val >= maxTexUnits) { ErrorInvalidValue( "This uniform location is a sampler, but %d" @@ -1337,7 +1337,7 @@ void WebGLContext::UniformData( // - // This is a little galaxy-brain, sorry! - const auto ptr = static_cast<const void*>(data.begin().get()); + const auto ptr = static_cast<const void*>(data.data()); (*pfn)(*gl, static_cast<GLint>(loc), elemCount, transpose, ptr); // - @@ -1345,12 +1345,12 @@ void WebGLContext::UniformData( if (samplerInfo) { auto& texUnits = samplerInfo->texUnits; - const auto srcBegin = reinterpret_cast<const uint32_t*>(data.begin().get()); + const auto srcBegin = reinterpret_cast<const uint32_t*>(data.data()); auto destIndex = locInfo->indexIntoUniform; if (destIndex < texUnits.length()) { // Only sample as many indexes as available tex units allow. const auto destCount = std::min(elemCount, texUnits.length() - destIndex); - for (const auto& val : Range<const uint32_t>(srcBegin, destCount)) { + for (const auto& val : Span<const uint32_t>(srcBegin, destCount)) { texUnits[destIndex] = AssertedCast<uint8_t>(val); destIndex += 1; } diff --git a/dom/canvas/WebGLIpdl.h b/dom/canvas/WebGLIpdl.h index 748c1a30a6..5a35c05909 100644 --- a/dom/canvas/WebGLIpdl.h +++ b/dom/canvas/WebGLIpdl.h @@ -10,6 +10,7 @@ #include "ipc/EnumSerializer.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/GfxMessageUtils.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "mozilla/ipc/Shmem.h" #include "mozilla/layers/LayersSurfaces.h" @@ -233,6 +234,16 @@ struct ParamTraits<gfxAlphaType> : public ContiguousEnumSerializerInclusive< gfxAlphaType, gfxAlphaType::Opaque, gfxAlphaType::NonPremult> {}; +template <> +struct ParamTraits<mozilla::dom::WebGLPowerPreference> final + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::WebGLPowerPreference> {}; + +template <> +struct ParamTraits<mozilla::dom::PredefinedColorSpace> final + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::PredefinedColorSpace> {}; + // - // ParamTraits_TiedFields @@ -242,7 +253,7 @@ struct ParamTraits_TiedFields { static void Write(MessageWriter* const writer, const T& in) { const auto& fields = mozilla::TiedFields(in); - MapTuple(fields, [&](const auto& field) { + mozilla::MapTuple(fields, [&](const auto& field) { WriteParam(writer, field); return true; // ignored }); @@ -251,7 +262,7 @@ struct ParamTraits_TiedFields { static bool Read(MessageReader* const reader, T* const out) { const auto& fields = mozilla::TiedFields(*out); bool ok = true; - MapTuple(fields, [&](auto& field) { + mozilla::MapTuple(fields, [&](auto& field) { if (ok) { ok &= ReadParam(reader, &field); } @@ -263,72 +274,17 @@ struct ParamTraits_TiedFields { // - -template <typename T> -bool ValidateParam(const T& val) { - return ParamTraits<T>::Validate(val); -} - -template <typename T> -struct ValidatedPlainOldDataSerializer : public PlainOldDataSerializer<T> { - static void Write(MessageWriter* const writer, const T& in) { - MOZ_ASSERT(ValidateParam(in)); - PlainOldDataSerializer<T>::Write(writer, in); - } - - static bool Read(MessageReader* const reader, T* const out) { - if (!PlainOldDataSerializer<T>::Read(reader, out)) return false; - return ValidateParam(*out); - } - - // static bool Validate(const T&) = 0; -}; - -// - - template <> struct ParamTraits<mozilla::webgl::InitContextDesc> final - : public ValidatedPlainOldDataSerializer<mozilla::webgl::InitContextDesc> { - using T = mozilla::webgl::InitContextDesc; - - static bool Validate(const T& val) { - return ValidateParam(val.options) && (val.size.x && val.size.y); - } -}; + : public ParamTraits_TiedFields<mozilla::webgl::InitContextDesc> {}; template <> struct ParamTraits<mozilla::WebGLContextOptions> final - : public ValidatedPlainOldDataSerializer<mozilla::WebGLContextOptions> { - using T = mozilla::WebGLContextOptions; - - static bool Validate(const T& val) { - bool ok = true; - ok &= ValidateParam(val.powerPreference); - ok &= ValidateParam(val.colorSpace); - return ok; - } -}; - -template <> -struct ParamTraits<mozilla::dom::WebGLPowerPreference> final - : public ValidatedPlainOldDataSerializer< - mozilla::dom::WebGLPowerPreference> { - using T = mozilla::dom::WebGLPowerPreference; - - static bool Validate(const T& val) { return val <= T::High_performance; } -}; - -template <> -struct ParamTraits<mozilla::dom::PredefinedColorSpace> final - : public ValidatedPlainOldDataSerializer< - mozilla::dom::PredefinedColorSpace> { - using T = mozilla::dom::PredefinedColorSpace; - - static bool Validate(const T& val) { return val < T::EndGuard_; } -}; + : public ParamTraits_TiedFields<mozilla::WebGLContextOptions> {}; template <> struct ParamTraits<mozilla::webgl::OpaqueFramebufferOptions> final - : public PlainOldDataSerializer<mozilla::webgl::OpaqueFramebufferOptions> { + : public ParamTraits_TiedFields<mozilla::webgl::OpaqueFramebufferOptions> { }; // - @@ -340,28 +296,24 @@ struct ParamTraits<mozilla::gl::GLVendor> mozilla::gl::kHighestGLVendor> { }; -template <typename T> -struct ParamTraits<mozilla::webgl::EnumMask<T>> final - : public PlainOldDataSerializer<mozilla::webgl::EnumMask<T>> {}; +template <typename U> +struct ParamTraits<mozilla::webgl::EnumMask<U>> final + : public ParamTraits_TiedFields<mozilla::webgl::EnumMask<U>> {}; template <> struct ParamTraits<mozilla::webgl::InitContextResult> final : public ParamTraits_TiedFields<mozilla::webgl::InitContextResult> {}; template <> -struct ParamTraits<mozilla::webgl::ExtensionBits> final - : public PlainOldDataSerializer<mozilla::webgl::ExtensionBits> {}; - -template <> struct ParamTraits<mozilla::webgl::Limits> final - : public PlainOldDataSerializer<mozilla::webgl::Limits> {}; + : public ParamTraits_TiedFields<mozilla::webgl::Limits> {}; template <> struct ParamTraits<mozilla::webgl::PixelPackingState> final - : public PlainOldDataSerializer<mozilla::webgl::PixelPackingState> {}; + : public ParamTraits_TiedFields<mozilla::webgl::PixelPackingState> {}; template <> struct ParamTraits<mozilla::webgl::PixelUnpackStateWebgl> final - : public PlainOldDataSerializer<mozilla::webgl::PixelUnpackStateWebgl> {}; + : public ParamTraits_TiedFields<mozilla::webgl::PixelUnpackStateWebgl> {}; // - @@ -570,6 +522,24 @@ struct ParamTraits<mozilla::webgl::ShaderPrecisionFormat> final { // - template <typename U, size_t N> +struct ParamTraits<std::array<U, N>> final { + using T = std::array<U, N>; + + static void Write(MessageWriter* const writer, const T& in) { + for (const auto& v : in) { + WriteParam(writer, v); + } + } + + static bool Read(MessageReader* const reader, T* const out) { + for (auto& v : *out) { + if (!ReadParam(reader, &v)) return false; + } + return true; + } +}; + +template <typename U, size_t N> struct ParamTraits<U[N]> final { using T = U[N]; static constexpr size_t kByteSize = sizeof(U) * N; @@ -639,6 +609,35 @@ struct ParamTraits<mozilla::avec3<U>> final { } }; +// - + +template <class TT> +struct ParamTraits_IsEnumCase { + using T = TT; + + static void Write(IPC::MessageWriter* const writer, const T& in) { + MOZ_RELEASE_ASSERT(IsEnumCase(in)); + WriteParam(writer, mozilla::UnderlyingValue(in)); + } + + static bool Read(IPC::MessageReader* const reader, T* const out) { + std::underlying_type_t<T> rawVal; + if (!ReadParam(reader, &rawVal)) return false; + *out = static_cast<T>(rawVal); + return IsEnumCase(*out); + } +}; + +// - + +#define USE_IS_ENUM_CASE(T) \ + template <> \ + struct ParamTraits<T> : public ParamTraits_IsEnumCase<T> {}; + +USE_IS_ENUM_CASE(mozilla::webgl::OptionalRenderableFormatBits) + +#undef USE_IS_ENUM_CASE + } // namespace IPC #endif diff --git a/dom/canvas/WebGLMemoryTracker.cpp b/dom/canvas/WebGLMemoryTracker.cpp index 361d1be695..19d3847acc 100644 --- a/dom/canvas/WebGLMemoryTracker.cpp +++ b/dom/canvas/WebGLMemoryTracker.cpp @@ -12,8 +12,9 @@ #include "WebGLTexture.h" namespace mozilla { - -MOZ_DEFINE_MALLOC_SIZE_OF(WebGLShaderMallocSizeOf) +namespace webgl { +MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) +} // namespace webgl void WebGLMemoryTracker::EnsureRegistered() { static bool sIsRegistered = []() { @@ -42,6 +43,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, int64_t shaderCpuSize = 0; size_t texCount = 0; + size_t texHeapOverhead = 0; int64_t texGpuSize = 0; for (const auto& context : contexts) { @@ -70,7 +72,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, shaderCount += context->mShaderMap.size(); for (const auto& pair : context->mShaderMap) { const auto& shader = pair.second; - shaderCpuSize += shader->SizeOfIncludingThis(WebGLShaderMallocSizeOf); + shaderCpuSize += shader->SizeOfIncludingThis(webgl::MallocSizeOf); } // - @@ -79,6 +81,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, for (const auto& pair : context->mTextureMap) { const auto& texture = pair.second; texGpuSize += texture->MemoryUsage(); + texHeapOverhead += texture->SizeOfIncludingThis(webgl::MallocSizeOf); } } @@ -126,10 +129,14 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, "Number of WebGL renderbuffers."); MOZ_COLLECT_REPORT( - "explicit/webgl/shader", KIND_HEAP, UNITS_BYTES, shaderCpuSize, + "explicit/webgl/shaders", KIND_HEAP, UNITS_BYTES, shaderCpuSize, "Combined size of WebGL shader ASCII sources and translation logs " "cached on the heap."); + MOZ_COLLECT_REPORT("explicit/webgl/textures", KIND_HEAP, UNITS_BYTES, + texHeapOverhead, + "WebGLTexture implementation detail overhead."); + MOZ_COLLECT_REPORT("webgl-shader-count", KIND_OTHER, UNITS_COUNT, static_cast<int64_t>(shaderCount), "Number of WebGL shaders."); diff --git a/dom/canvas/WebGLQueueParamTraits.h b/dom/canvas/WebGLQueueParamTraits.h index db3ef0a44b..3c74f08750 100644 --- a/dom/canvas/WebGLQueueParamTraits.h +++ b/dom/canvas/WebGLQueueParamTraits.h @@ -67,8 +67,6 @@ inline constexpr bool IsEnumCase(const dom::WebGLPowerPreference raw) { case dom::WebGLPowerPreference::Low_power: case dom::WebGLPowerPreference::High_performance: return true; - case dom::WebGLPowerPreference::EndGuard_: - break; } return false; } @@ -78,8 +76,6 @@ inline constexpr bool IsEnumCase(const dom::PredefinedColorSpace raw) { case dom::PredefinedColorSpace::Srgb: case dom::PredefinedColorSpace::Display_p3: return true; - case dom::PredefinedColorSpace::EndGuard_: - break; } return false; } @@ -114,23 +110,21 @@ USE_IS_ENUM_CASE(webgl::ProvokingVertex) // Custom QueueParamTraits template <typename T> -struct QueueParamTraits<RawBuffer<T>> { - using ParamType = RawBuffer<T>; - +struct QueueParamTraits<Span<T>> { template <typename U> - static bool Write(ProducerView<U>& view, const ParamType& in) { + static bool Write(ProducerView<U>& view, const Span<T>& in) { const auto& elemCount = in.size(); auto status = view.WriteParam(elemCount); if (!status) return status; if (!elemCount) return status; - status = view.WriteFromRange(in.Data()); + status = view.WriteFromRange(Range<const T>{in}); return status; } template <typename U> - static bool Read(ConsumerView<U>& view, ParamType* const out) { + static bool Read(ConsumerView<U>& view, Span<const T>* const out) { size_t elemCount = 0; auto status = view.ReadParam(&elemCount); if (!status) return status; @@ -140,9 +134,9 @@ struct QueueParamTraits<RawBuffer<T>> { return true; } - auto data = view.template ReadRange<T>(elemCount); + auto data = view.template ReadRange<const T>(elemCount); if (!data) return false; - *out = std::move(RawBuffer<T>{*data}); + *out = Span{*data}; return true; } }; diff --git a/dom/canvas/WebGLShaderValidator.h b/dom/canvas/WebGLShaderValidator.h index ab05528f19..7fc700937f 100644 --- a/dom/canvas/WebGLShaderValidator.h +++ b/dom/canvas/WebGLShaderValidator.h @@ -39,7 +39,7 @@ class ShaderValidatorResults final { bool CanLinkTo(const ShaderValidatorResults& vert, nsCString* const out_log) const; - size_t SizeOfIncludingThis(MallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const; }; class ShaderValidator final { diff --git a/dom/canvas/WebGLTexture.h b/dom/canvas/WebGLTexture.h index 8a451a9277..7d75bf50f6 100644 --- a/dom/canvas/WebGLTexture.h +++ b/dom/canvas/WebGLTexture.h @@ -203,6 +203,14 @@ class WebGLTexture final : public WebGLContextBoundObject, ~WebGLTexture() override; public: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return CacheInvalidator::SizeOfExcludingThis(mso) + + mSamplingCache.SizeOfExcludingThis(mso); + } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mso) const { + return mso(this) + SizeOfExcludingThis(mso); + } + //////////////////////////////////// // GL calls bool BindTexture(TexTarget texTarget); diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp index 6686c34178..1a439ca5bc 100644 --- a/dom/canvas/WebGLTextureUpload.cpp +++ b/dom/canvas/WebGLTextureUpload.cpp @@ -60,6 +60,10 @@ Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size, } const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface; + if (NS_WARN_IF(!surf)) { + return {}; + } + const auto imageSize = *uvec2::FromSize(surf->GetSize()); if (!size) { size.emplace(imageSize.x, imageSize.y, 1); @@ -925,23 +929,7 @@ void WebGLTexture::TexStorage(TexTarget target, uint32_t levels, void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat, const uvec3& offset, const webgl::PackingInfo& pi, const webgl::TexUnpackBlobDesc& src) { - Maybe<RawBuffer<>> cpuDataView; - if (src.cpuData) { - cpuDataView = Some(RawBuffer<>{src.cpuData->Data()}); - } - const auto srcViewDesc = webgl::TexUnpackBlobDesc{src.imageTarget, - src.size, - src.srcAlphaType, - std::move(cpuDataView), - src.pboOffset, - src.structuredSrcSize, - src.image, - src.sd, - src.dataSurf, - src.unpacking, - src.applyUnpackTransforms}; - - const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc); + const auto blob = webgl::TexUnpackBlob::Create(src); if (!blob) { MOZ_ASSERT(false); return; diff --git a/dom/canvas/WebGLTypes.h b/dom/canvas/WebGLTypes.h index b23839b9ca..c268047930 100644 --- a/dom/canvas/WebGLTypes.h +++ b/dom/canvas/WebGLTypes.h @@ -19,11 +19,14 @@ #include "ImageContainer.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" +#include "mozilla/EnumTypeTraits.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Range.h" #include "mozilla/RefCounted.h" #include "mozilla/Result.h" #include "mozilla/ResultVariant.h" +#include "mozilla/Span.h" +#include "mozilla/TypedEnumBits.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/BuildConstants.h" #include "mozilla/gfx/Logging.h" @@ -407,8 +410,6 @@ inline ColorSpace2 ToColorSpace2(const dom::PredefinedColorSpace cs) { return ColorSpace2::SRGB; case dom::PredefinedColorSpace::Display_p3: return ColorSpace2::DISPLAY_P3; - case dom::PredefinedColorSpace::EndGuard_: - break; } MOZ_CRASH("Exhaustive switch"); } @@ -598,9 +599,13 @@ class EnumMask { public: BitRef operator[](const E i) { return {*this, Mask(i)}; } bool operator[](const E i) const { return mBits & Mask(i); } + + // - + + auto MutTiedFields() { return std::tie(mBits); } }; -class ExtensionBits : public EnumMask<WebGLExtensionID> {}; +using ExtensionBits = EnumMask<WebGLExtensionID>; // - @@ -624,9 +629,16 @@ inline bool ReadContextLossReason(const uint8_t val, struct InitContextDesc final { bool isWebgl2 = false; bool resistFingerprinting = false; + std::array<uint8_t, 2> _padding; + uint32_t principalKey = 0; uvec2 size = {}; WebGLContextOptions options; - uint32_t principalKey = 0; + std::array<uint8_t, 3> _padding2; + + auto MutTiedFields() { + return std::tie(isWebgl2, resistFingerprinting, _padding, principalKey, + size, options, _padding2); + } }; constexpr uint32_t kMaxTransformFeedbackSeparateAttribs = 4; @@ -651,10 +663,24 @@ struct Limits final { // Exts bool astcHdr = false; + std::array<uint8_t, 3> _padding; uint32_t maxColorDrawBuffers = 1; + uint32_t maxMultiviewLayers = 0; uint64_t queryCounterBitsTimeElapsed = 0; uint64_t queryCounterBitsTimestamp = 0; - uint32_t maxMultiviewLayers = 0; + + auto MutTiedFields() { + return std::tie(supportedExtensions, + + maxTexUnits, maxTex2dSize, maxTexCubeSize, maxVertexAttribs, + maxViewportDim, pointSizeRange, lineWidthRange, + + maxTexArrayLayers, maxTex3dSize, maxUniformBufferBindings, + uniformBufferOffsetAlignment, + + astcHdr, _padding, maxColorDrawBuffers, maxMultiviewLayers, + queryCounterBitsTimeElapsed, queryCounterBitsTimestamp); + } }; // - @@ -680,18 +706,41 @@ struct Padded { // - +enum class OptionalRenderableFormatBits : uint8_t { + RGB8 = (1 << 0), + SRGB8 = (1 << 1), +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OptionalRenderableFormatBits) +inline constexpr bool IsEnumCase(const OptionalRenderableFormatBits raw) { + auto rawWithoutValidBits = UnderlyingValue(raw); + auto bit = decltype(rawWithoutValidBits){1}; + while (bit) { + switch (OptionalRenderableFormatBits{bit}) { + // -Werror=switch ensures exhaustive. + case OptionalRenderableFormatBits::RGB8: + case OptionalRenderableFormatBits::SRGB8: + rawWithoutValidBits &= ~bit; + break; + } + bit <<= 1; + } + return rawWithoutValidBits == 0; +} + +// - + struct InitContextResult final { Padded<std::string, 32> error; // MINGW 32-bit needs this padding. WebGLContextOptions options; gl::GLVendor vendor; - bool isRgb8Renderable; + OptionalRenderableFormatBits optionalRenderableFormatBits; uint8_t _padding = {}; - webgl::Limits limits; + Limits limits; EnumMask<layers::SurfaceDescriptor::Type> uploadableSdTypes; auto MutTiedFields() { - return std::tie(error, options, vendor, isRgb8Renderable, _padding, limits, - uploadableSdTypes); + return std::tie(error, options, vendor, optionalRenderableFormatBits, + _padding, limits, uploadableSdTypes); } }; @@ -732,8 +781,13 @@ struct CompileResult final { struct OpaqueFramebufferOptions final { bool depthStencil = true; bool antialias = true; + std::array<uint8_t, 2> _padding; uint32_t width = 0; uint32_t height = 0; + + auto MutTiedFields() { + return std::tie(depthStencil, antialias, _padding, width, height); + } }; // - @@ -853,47 +907,6 @@ struct VertAttribPointerCalculated final { } // namespace webgl -// TODO: s/RawBuffer/Span/ -template <typename T = uint8_t> -class RawBuffer final { - const T* mBegin = nullptr; - size_t mLen = 0; - - public: - using ElementType = T; - - explicit RawBuffer(const Range<const T>& data) - : mBegin(data.begin().get()), mLen(data.length()) { - if (mLen) { - MOZ_ASSERT(mBegin); - } - } - - ~RawBuffer() = default; - - Range<const T> Data() const { return {begin(), mLen}; } - const auto& begin() const { - if (mLen) { - MOZ_RELEASE_ASSERT(mBegin); - } - return mBegin; - } - const auto& size() const { return mLen; } - - void Shrink(const size_t newLen) { - if (mLen <= newLen) return; - mLen = newLen; - } - - RawBuffer() = default; - - RawBuffer(const RawBuffer&) = delete; - RawBuffer& operator=(const RawBuffer&) = delete; - - RawBuffer(RawBuffer&&) = default; - RawBuffer& operator=(RawBuffer&&) = default; -}; - template <class T> inline Range<T> ShmemRange(const mozilla::ipc::Shmem& shmem) { return {shmem.get<T>(), shmem.Size<T>()}; @@ -1096,7 +1109,7 @@ struct TexUnpackBlobDesc final { uvec3 size; gfxAlphaType srcAlphaType = gfxAlphaType::NonPremult; - Maybe<RawBuffer<>> cpuData; + Maybe<Span<const uint8_t>> cpuData; Maybe<uint64_t> pboOffset; Maybe<uvec2> structuredSrcSize; @@ -1134,11 +1147,6 @@ inline Range<const T> MakeRange(const dom::Sequence<T>& seq) { return {seq.Elements(), seq.Length()}; } -template <typename T> -inline Range<const T> MakeRange(const RawBuffer<T>& from) { - return from.Data(); -} - // - constexpr auto kUniversalAlignment = alignof(std::max_align_t); @@ -1159,13 +1167,6 @@ inline size_t ByteSize(const Range<T>& range) { // - -template <typename T> -RawBuffer<T> RawBufferView(const Range<T>& range) { - return RawBuffer<T>{range}; -} - -// - - Maybe<webgl::ErrorInfo> CheckBindBufferRange( const GLenum target, const GLuint index, const bool isBuffer, const uint64_t offset, const uint64_t size, const webgl::Limits& limits); @@ -1207,6 +1208,13 @@ inline void Memcpy(const RangedPtr<T>* const destBegin, Memcpy(destBegin, srcRange->begin(), srcRange->length()); } +template <typename Dst, typename Src> +inline void Memcpy(const Span<Dst>* const dest, const Span<Src>& src) { + MOZ_RELEASE_ASSERT(src.size_bytes() >= dest->size_bytes()); + MOZ_ASSERT(src.size_bytes() == dest->size_bytes()); + memcpy(dest->data(), src.data(), dest->size_bytes()); +} + // - inline bool StartsWith(const std::string_view str, @@ -1285,6 +1293,15 @@ inline const char* ToChars(const bool val) { return "false"; } +template <class To> +struct ReinterpretToSpan { + template <class FromT> + static inline constexpr Span<To> From(const Span<FromT>& from) { + static_assert(sizeof(FromT) == sizeof(To)); + return {reinterpret_cast<To*>(from.data()), from.size()}; + } +}; + } // namespace mozilla #endif diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list index 2cd7177b77..e1920b20ee 100644 --- a/dom/canvas/crashtests/crashtests.list +++ b/dom/canvas/crashtests/crashtests.list @@ -4,7 +4,7 @@ load 360293-1.html load 421715-1.html load 553938-1.html load 647480.html -skip-if(Android&&browserIsRemote) load 727547.html # bug 1507207 +skip-if(Android) load 727547.html # bug 1507207 load 729116.html load 745699-1.html load 746813-1.html @@ -27,13 +27,13 @@ load 1183363.html load 1190705.html load 1223740-1.html load 1225381-1.html -skip-if(azureCairo) load 1229983-1.html +load 1229983-1.html load 1229932-1.html load 1244850-1.html load 1246775-1.html load 1284356-1.html load 1284578-1.html -skip-if(d2d) load 1287515-1.html +load 1287515-1.html load 1287652-1.html load 1288872-1.html load 1290628-1.html diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 3337e97ad9..3a533d36d1 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -52,6 +52,7 @@ EXPORTS.mozilla.dom += [ "CanvasRenderingContext2D.h", "CanvasRenderingContextHelper.h", "CanvasUtils.h", + "DmdStdContainers.h", "GeneratePlaceholderCanvasData.h", "ImageBitmap.h", "ImageBitmapRenderingContext.h", diff --git a/dom/canvas/nsICanvasRenderingContextInternal.cpp b/dom/canvas/nsICanvasRenderingContextInternal.cpp index c463a00a1a..ca8e16136c 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.cpp +++ b/dom/canvas/nsICanvasRenderingContextInternal.cpp @@ -7,9 +7,12 @@ #include "mozilla/dom/CanvasUtils.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ErrorResult.h" #include "mozilla/PresShell.h" +#include "nsContentUtils.h" #include "nsPIDOMWindow.h" #include "nsRefreshDriver.h" @@ -116,3 +119,24 @@ bool nsICanvasRenderingContextInternal::ShouldResistFingerprinting( // Last resort, just check the global preference return nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget); } + +bool nsICanvasRenderingContextInternal::DispatchEvent( + const nsAString& eventName, mozilla::CanBubble aCanBubble, + mozilla::Cancelable aIsCancelable) const { + bool useDefaultHandler = true; + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), + mCanvasElement, eventName, aCanBubble, + aIsCancelable, &useDefaultHandler); + } else if (mOffscreenCanvas) { + // OffscreenCanvas case + auto event = mozilla::MakeRefPtr<mozilla::dom::Event>(mOffscreenCanvas, + nullptr, nullptr); + event->InitEvent(eventName, aCanBubble, aIsCancelable); + event->SetTrusted(true); + useDefaultHandler = mOffscreenCanvas->DispatchEvent( + *event, mozilla::dom::CallerType::System, mozilla::IgnoreErrors()); + } + return useDefaultHandler; +} diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index 580a9f07b3..deb642e9e8 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -15,6 +15,7 @@ #include "nsRFPService.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/OffscreenCanvas.h" +#include "mozilla/EventForwards.h" #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/StateWatching.h" @@ -225,6 +226,9 @@ class nsICanvasRenderingContextInternal : public nsISupports, // Checking if fingerprinting protection is enable for the given target. bool ShouldResistFingerprinting(mozilla::RFPTarget aTarget) const; + bool DispatchEvent(const nsAString& eventName, mozilla::CanBubble aCanBubble, + mozilla::Cancelable aIsCancelable) const; + protected: RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement; RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas; diff --git a/dom/canvas/test/mochitest.toml b/dom/canvas/test/mochitest.toml index 86cf26b632..3e96fff26b 100644 --- a/dom/canvas/test/mochitest.toml +++ b/dom/canvas/test/mochitest.toml @@ -185,6 +185,10 @@ support-files = [ ["test_ImageData_ctor.html"] +["test_accelerated_canvas_context_loss.html"] +subsuite = "gpu" +skip-if = ["verify || (os == 'win' && debug)"] + ["test_bitmaprenderer.html"] ["test_bug232227.html"] diff --git a/dom/canvas/test/reftest/colors/_generated_reftest.list b/dom/canvas/test/reftest/colors/_generated_reftest.list new file mode 100644 index 0000000000..29761e3df8 --- /dev/null +++ b/dom/canvas/test/reftest/colors/_generated_reftest.list @@ -0,0 +1,176 @@ +### Generated, do not edit. ### + ### Generated, do not edit. ### + ### Generated, do not edit. ### + +# Generated by `./generate_color_canvas_reftests.py --write`. +# - + +defaults pref(webgl.colorspaces.prototype,true) + +### Generated, do not edit. ### +# - + +# Ensure not white-screening: +!= color_canvas.html?= about:blank +# Ensure differing results with different args: +!= color_canvas.html?e_color=color(srgb,1,0,0) color_canvas.html?e_color=color(srgb,0,1,0) + +### Generated, do not edit. ### +# - +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + ### Generated, do not edit. ### +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) +skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) diff --git a/dom/canvas/test/reftest/colors/color_canvas.html b/dom/canvas/test/reftest/colors/color_canvas.html new file mode 100644 index 0000000000..7abbc86255 --- /dev/null +++ b/dom/canvas/test/reftest/colors/color_canvas.html @@ -0,0 +1,461 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset=utf-8> + <title>color_canvas.html</title> + </head> + <!-- +# color_canvas.html + +* Default is a 100x100 'css' canvas-equivalent div, filled with 50% srgb red. + +* We default to showing the settings pane when loaded without a query string. + This way, someone naively opens this in a browser, they can immediately see + all available options. + +* The 'Publish' button updates the url, and so causes the settings pane to + hide. + +* Clicking on the canvas toggles the settings pane for further editing. + --> + <body> + <form id=e_settings><fieldset><legend>Settings</legend> + Width: <input id=e_width type=text value=100> + <br> + Height: <input id=e_height type=text value=100> + <br> + <fieldset><legend>Canvas Context</legend> + Type: <select id=e_context> + <option value=css selected>css</option> + <option value=2d>2d</option> + <option value=webgl>webgl</option> + </select> + <br> + Options: <input id=e_options type=text value={}> + <br> + Colorspace: <input id=e_cspace type=text placeholder=srgb> + <br> + WebGL Format: <input id=e_webgl_format type=text placeholder=RGBA8> + </fieldset> + <br> + Color: <input id=e_color type=text value='color(srgb 0 0.5 0)'> + <br> + <input id=e_publish type=button value=Publish> + <input type=checkbox id=e_publish_omit_defaults checked><label for=e_publish_omit_defaults>Omit defaults</label> + <hr> + </fieldset></form> + <div id=e_canvas_list><canvas></canvas></div> + <script> +'use strict'; + +// - + +function walk_nodes_depth_first(e, fn) { + if (fn(e) === false) return; // Don't stop on `true`, or `undefined`! + for (const c of e.childNodes) { + walk_nodes_depth_first(c, fn); + } +} + +// - + +// Click the canvas to toggle the settings pane. +e_canvas_list.addEventListener('click', () => { + // Toggle display:none to hide/unhide. + e_settings.hidden = !e_settings.hidden; +}); + +// Hide settings initially if there's a query string in the url. +if (window.location.search.startsWith('?')) { + e_settings.hidden = true; +} + +// - + +// Imply .name from .id, because `new FormData` collects based on names. +walk_nodes_depth_first(e_settings, e => { + if (e.id) { + e.name = e.id; + e._default_value = e.value; + } +}); + +// - + +const URL_PARAMS = new URLSearchParams(window.location.search); +URL_PARAMS.forEach((v,k) => { + const e = window[k]; + if (!e) { + if (k) { + console.warn(`Unrecognized setting: ${k} = ${v}`); + } + return; + } + v = decode_url_v(k,v); + e.value = v; +}); + +// - + +globalThis.ASSERT = (() => { + function toPrettyString(arg) { + if (!arg) return ''+arg; + + if (arg.call) { + let s = arg.toString(); + const RE_TRIVIAL_LAMBDA = /\( *\) *=> *(.*)/; + const m = RE_TRIVIAL_LAMBDA.exec(s); + if (m) { + s = '`' + m[1] + '`'; + } + return s; + } + if (arg.constructor == Array) { + return `[${[].join.call(arg, ', ')}]`; + } + return JSON.stringify(arg); + } + + /// new AssertArg(): Construct a wrapper for args to assert functions. + function AssertArg(dict) { + this.label = Object.keys(dict)[0]; + + this.set = function(arg) { + this.arg = arg; + this.value = (arg && arg.call) ? arg.call() : arg; + this.value = toPrettyString(this.value); + }; + this.set(dict[this.label]); + + this.toString = function() { + let ret = `${this.label} ${toPrettyString(this.arg)}`; + if (this.arg.call) { + ret += ` (${this.value})`; + } + return ret; + } + } + + const eq = (a,b) => a == b; + const neq = (a,b) => a != b; + + const CMP_BY_NAME = { + '==': eq, + '!=': neq, + }; + + function IS(cmp, was, expected, _console) { + _console = _console || console; + + _console.assert(was.call, '`was.call` not defined.'); + was = new AssertArg({was}); + expected = new AssertArg({expected}); + + const fn_cmp = CMP_BY_NAME[cmp] || cmp; + + _console.assert(fn_cmp(was.value, expected.value), `${toPrettyString(was.arg)} => ${was.value} not ${cmp} ${expected}`); + if (was.value != expected.value) { + } else if (globalThis.ASSERT && globalThis.ASSERT.verbose) { + const maybe_cmp_str = (cmp == '==') ? '' : ` ${was.value} ${cmp}`; + _console.log(`${toPrettyString(was.arg)} => ${maybe_cmp_str}${expected}`); + } + } + + // - + + const MOCK_CONSOLE = { + _asserts: [], + assert: function(expr, ...args) { + if (!expr) { + this._asserts.push(args); + } + }, + log: function(...args) { + // Don't record. + }, + }; + + // - + // Test `==` + + IS('==', () => 1, 1, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => 2, 2, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => 5, () => 3, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 1, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => [1,2], () => [1,2], MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + // - + // Test `!=` + + IS('!=', () => [1,2,5], () => [1,2,3], MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + // - + + const ret = { + verbose: false, + IS, + }; + ret.EQ = (was,expected) => ret.IS('==', was, expected); + ret.NEQ = (was,expected) => ret.IS('!=', was, expected); + ret.EEQ = (was,expected) => ret.IS('===', was, expected); + ret.NEEQ = (was,expected) => ret.IS('!==', was, expected); + ret.TRUE = was => ret.EQ(was, true); + ret.FALSE = was => ret.EQ(was, false); + ret.NULL = was => ret.EEQ(was, null); + return ret; +})(); + +// - + +function parse_css_rgb(str) { + // rgb (R ,G ,B /A ) + const m = /rgba?\(([^,]+),([^,]+),([^/)]+)(?: *\/([^)]+))?\)/.exec(str); + if (!m) throw str; + const rgba = m.slice(1,1+4).map((s,i) => { + if (s === undefined && i == 3) { + s = '1'; // Alpha defaults to 1. + } + s = s.trim(); + let v = parseFloat(s); + if (s.endsWith('%')) { + v /= 100; + } else { + if (i < 3) { // r,g,b but not a! + v /= 255; + } + } + return v; + }); + return rgba; +} +ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60)'), () => [20/255, 40/255, 60/255, 1]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0.5)'), () => [20/255, 40/255, 60/255, 0.5]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0)'), () => [20/255, 40/255, 60/255, 0]); + +// - + +function parse_css_color(str) { + // color ( srgb R G B /A ) + const m = /color\( *([^ ]+) +([^ ]+) +([^ ]+) +([^/)]+)(?:\/([^)]+))?\)/.exec(str); + if (!m) { + return ['srgb', ...parse_css_rgb(str)]; + } + + const cspace = m[1].trim(); + let has_extreme_colors = false; + const rgba = m.slice(2, 2+4).map((s,i) => { + if (s === undefined && i == 3) { + s = '1'; // Alpha defaults to 1. + } + s = s.trim(); + let v = parseFloat(s); + if (s.endsWith('%')) { + v /= 100; + } + if (v < 0 || v > 1) { + has_extreme_colors = true; + } + return v; + }); + if (has_extreme_colors) { + console.warn(`parse_css_color('${str}') has colors outside [0.0,1.0]: ${JSON.stringify(rgba)}`); + } + return [cspace, ...rgba]; +} +ASSERT.EQ(() => parse_css_color('rgb(255,255,255)'), ['srgb',1,1,1,1]); +ASSERT.EQ(() => parse_css_color('rgb(20,40,60 / 0.5)'), () => ['srgb', 20/255, 40/255, 60/255, 0.5]); +ASSERT.EQ(() => parse_css_color('color(srgb 1 0 1 /0.3)'), ['srgb',1,0,1,0.3]); +ASSERT.EQ(() => parse_css_color('color(display-p3 1 0% 100%/ 30%)'), ['display-p3',1,0,1,0.3]); + +// - + +class CssColor { + constructor(cspace, r,g,b,a=1) { + this.cspace = cspace; + this.rgba = [this.r, this.g, this.b, this.a] = [r,g,b,a]; + this.rgb = this.rgba.slice(0,3); + this.tuple = [this.cspace, ...this.rgba]; + } + + toString() { + return `color(${this.cspace} ${this.rgb.join(' ')} / ${this.a})`; + } +}; +CssColor.parse = function(str) { + return new CssColor(...parse_css_color(str)); +} + +{ + let STR; + // Test round-trip. + STR = 'color(display-p3 1 0 1 / 0.3)'; + ASSERT.EQ(() => CssColor.parse(STR).toString(), STR); + + // Test round-trip normalization + ASSERT.EQ(() => CssColor.parse('color( display-p3 1 0 1/30% )').toString(), 'color(display-p3 1 0 1 / 0.3)'); +} + +// - + +function redraw() { + while (e_canvas_list.firstChild) { + e_canvas_list.removeChild(e_canvas_list.firstChild); + } + + const c = make_canvas(e_color.value.trim()); + c.style.border = '4px solid black'; + e_canvas_list.appendChild(c); +} + +function fill_canvas_rect(context /*: CanvasRenderingContext | WebGLRenderingContext*/, css_color, rect=null) { + rect = rect || {left: 0, top: 0, w: context.canvas.width, h: context.canvas.height}; + + const is_c2d = ('fillRect' in context); + if (is_c2d) { + const c2d = context; + c2d.fillStyle = css_color; + c2d.fillRect(rect.left, rect.top, rect.w, rect.h); + return; + } + + const is_webgl = ('drawArrays' in context); + if (is_webgl) { + const gl = context; + console.assert(context.canvas.width == gl.drawingBufferWidth, context.canvas.width, '!=', gl.drawingBufferWidth); + console.assert(context.canvas.height == gl.drawingBufferHeight, context.canvas.height, '!=', gl.drawingBufferHeight); + + gl.enable(gl.SCISSOR_TEST); + gl.disable(gl.DEPTH_TEST); + const bottom = rect.top + rect.h; // in y-down c2d coords + gl.scissor(rect.left, context.canvas.height - bottom, rect.w, rect.h); + + const canvas_cspace = context.drawingBufferColorSpace || 'srgb'; + if (css_color.cspace != canvas_cspace) { + console.warn(`Ignoring mismatched color vs webgl canvas cspace: ${css_color.cspace} vs ${canvas_cspace}`); + } + gl.clearColor(...css_color.rgba); + gl.clear(gl.COLOR_BUFFER_BIT); + return; + } + + console.error('Unhandled context kind:', context); +} + +window.e_canvas = null; + +function make_canvas(css_color) { + css_color = CssColor.parse(css_color); + + // `e_width` and e_friends are elements (by id) that we added to the raw HTML above. + // `e_width` is an old shorthand for `window.e_width || document.getElementById('e_width')`. + const W = parseInt(e_width.value); + const H = parseInt(e_height.value); + if (e_context.value == 'css') { + e_canvas = document.createElement('div'); + e_canvas.style.width = `${W}px`; + e_canvas.style.height = `${H}px`; + e_canvas.style.backgroundColor = css_color; + return e_canvas; + } + e_canvas = document.createElement('canvas'); + e_canvas.width = W; + e_canvas.height = H; + + let requested_options = JSON.parse(e_options.value); + requested_options.colorSpace = e_cspace.value || undefined; + + const context = e_canvas.getContext(e_context.value, requested_options); + if (requested_options.colorSpace) { + if (!context.drawingBufferColorSpace) { + console.warn(`${context.constructor.name}.drawingBufferColorSpace not supported by browser.`); + } else { + context.drawingBufferColorSpace = requested_options.colorSpace; + } + } + + if (e_webgl_format.value) { + if (!context.drawingBufferStorage) { + console.warn(`${context.constructor.name}.drawingBufferStorage not supported by browser.`); + } else { + context.drawingBufferStorage(W, H, context[e_webgl_format.value]); + } + } + + let actual_options; + if (!context.getContextAttributes) { + console.warn(`${canvas.constructor.name}.getContextAttributes not supported by browser.`); + actual_options = requested_options; + } else { + actual_options = context.getContextAttributes(); + } + + // - + + fill_canvas_rect(context, css_color); + + return e_canvas; +} + +e_settings.addEventListener('change', async () => { + redraw(); + const e_updated = document.createElement('i'); + e_updated.textContent = '(Updated!)'; + document.body.appendChild(e_updated); + await new Promise(go => setTimeout(go, 1000)); + document.body.removeChild(e_updated); +}); +redraw(); + +// - + +function encode_url_v(k,v) { + if (k == 'e_color') { + v = v.replaceAll(' ', ','); + } + console.assert(!v.includes(' '), v); + return v +} +function decode_url_v(k,v) { + console.assert(!v.includes(' '), v); + if (k == 'e_color') { + v = v.replaceAll(',', ' '); + } + return v +} +ASSERT.EQ(() => decode_url_v('e_color', encode_url_v('e_color', 'color(srgb 1 0 0)')), 'color(srgb 1 0 0)') + +e_publish.addEventListener('click', () => { + const fd = new FormData(e_settings); + let settings = []; + for (let [k,v] of fd) { + const e = window[k]; + if (e_publish_omit_defaults.checked && v == e._default_value) continue; + + v = encode_url_v(k,v); + settings.push(`${k}=${v}`); + } + settings = settings.join('&'); + if (!settings) { + settings = '='; // Empty key-value pair is 'publish with default settings' + } + window.location.search = '?' + settings; +}); + </script> + </body> +</html> diff --git a/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py new file mode 100644 index 0000000000..8c1e5f3788 --- /dev/null +++ b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py @@ -0,0 +1,427 @@ +#! python3 + +# Typecheck: +# `pip -m mypy generate_color_canvas_reftests.py` +# +# Run: +# `./generate_color_canvas_reftests.py [--write]` + +import functools +import json +import math +import pathlib +import re +import sys +from typing import Iterable, NamedTuple, TypeVar + +ARGS = sys.argv[1:] +DEST = pathlib.Path(__file__).parent / "_generated_reftest.list" + +COL_DELIM = " " +COL_ALIGNMENT = 4 + +# - + +T = TypeVar("T") +U = TypeVar("U") + +# - + + +# crossCombine([{a:false},{a:5}], [{},{b:5}]) +# [{a:false}, {a:true}, {a:false,b:5}, {a:true,b:5}] +def cross_combine(*args_tup: list[dict]) -> list[dict]: + args = list(args_tup) + for i, a in enumerate(args): + assert type(a) == list, f"Arg{i} is {type(a)}, expected {list}." + + def cross_combine2(listA, listB): + listC = [] + for a in listA: + for b in listB: + c = dict() + c.update(a) + c.update(b) + listC.append(c) + return listC + + res: list[dict] = [dict()] + while True: + try: + next = args.pop(0) + except IndexError: + break + res = cross_combine2(res, next) + return res + + +# keyed_alternatives('count', [1,2,3]) -> [{count: 1}, {count: 2}, {count: 3}] +def keyed_alternatives(key: T, vals: Iterable[U]) -> list[dict[T, U]]: + """ + res = [] + for v in vals: + d = dict() + d[key] = v + res.append(d) + return res + """ + # return [dict([[key,v]]) for v in vals] + return [{key: v} for v in vals] + + +# - + + +def eprint(*args, **kwargs): + sys.stdout.flush() + print(*args, file=sys.stderr, **kwargs) + sys.stderr.flush() + + +# - + +# color_canvas.html?e_width=100&e_height=100&e_context=css&e_options={}&e_cspace=&e_webgl_format=&e_color=rgb(127,0,0) + +CSPACE_LIST = ["srgb", "display-p3"] +CANVAS_CSPACES = keyed_alternatives("e_cspace", CSPACE_LIST) + +RGB_LIST = [ + "0.000 0.000 0.000", + "0.200 0.200 0.200", # 0.2*255 = 51 + "0.200 0.000 0.000", + "0.000 0.200 0.000", + "0.000 0.000 0.200", + "0.502 0.502 0.502", # 0.502*255 = 128.01 + "0.502 0.000 0.000", + "0.000 0.502 0.000", + "0.000 0.000 0.502", + "1.000 1.000 1.000", + #'1.000 0.000 0.000', # These will hit gamut clipping on most displays. + #'0.000 1.000 0.000', + #'0.000 0.000 1.000', +] + +WEBGL_COLORS = keyed_alternatives("e_color", [f"color(srgb {rgb})" for rgb in RGB_LIST]) + +C2D_COLORS = [] +for cspace in CSPACE_LIST: + C2D_COLORS += keyed_alternatives( + "e_color", [f"color({cspace} {rgb})" for rgb in RGB_LIST] + ) + +# - + +WEBGL_FORMATS = keyed_alternatives( + "e_webgl_format", + [ + "RGBA8", + # Bug 1883748: (webgl.drawingbufferStorage) + #'SRGB8_ALPHA8', + #'RGBA16F', + ], +) +WEBGL = cross_combine( + [{"e_context": "webgl"}], WEBGL_FORMATS, CANVAS_CSPACES, WEBGL_COLORS +) + +# - + +C2D_OPTIONS_COMBOS = cross_combine( + keyed_alternatives("willReadFrequently", ["true", "false"]), # E.g. D2D vs Skia + # keyed_alternatives('alpha', ['true','false']) +) +C2D_OPTIONS = [ + json.dumps(config, separators=(",", ":")) for config in C2D_OPTIONS_COMBOS +] + +C2D = cross_combine( + [{"e_context": "2d"}], + keyed_alternatives("e_options", C2D_OPTIONS), + CANVAS_CSPACES, + C2D_COLORS, +) + +# - + +COMBOS: list[dict[str, str]] = cross_combine(WEBGL + C2D) + +eprint(f"{len(COMBOS)} combinations...") + +# - + +Config = dict[str, str] + + +class CssColor(NamedTuple): + cspace: str + rgb: str + + def rgb_vals(self) -> tuple[float, float, float]: + (r, g, b) = [float(z) for z in self.rgb.split(" ")] + return (r, g, b) + + def is_same_color(x, y) -> bool: + if x == y: + return True + (r, g, b) = x.rgb_vals() + if x.rgb == y.rgb and r == g and g == b: + return True + return False + + +class Reftest(NamedTuple): + notes: list[str] + op: str + test_config: Config + ref_config: Config + + +def make_ref_config(color: CssColor) -> Config: + return { + "e_context": "css", + "e_color": f"color({color.cspace} {color.rgb})", + } + + +class ColorReftest(NamedTuple): + notes: list[str] + test_config: Config + ref_color: CssColor + + def to_reftest(self): + ref_config = make_ref_config(self.ref_color) + return Reftest(self.notes.copy(), "==", self.test_config.copy(), ref_config) + + +class Expectation(NamedTuple): + notes: list[str] + test_config: Config + color: CssColor + + +def parse_css_color(s: str) -> CssColor: + m = re.match("color[(]([^)]+)[)]", s) + assert m, s + (cspace, rgb) = m.group(1).split(" ", 1) + return CssColor(cspace, rgb) + + +def correct_color_from_test_config(test_config: Config) -> CssColor: + canvas_cspace = test_config["e_cspace"] + if not canvas_cspace: + canvas_cspace = "srgb" + + correct_color = parse_css_color(test_config["e_color"]) + if test_config["e_context"] == "webgl": + # Webgl ignores the color's cspace, because webgl has no concept of + # source colorspace for clears/draws to the backbuffer. + # This (correct) behavior is as if the color's cspace were overwritten by the + # cspace of the canvas. (so expect that) + correct_color = CssColor(canvas_cspace, correct_color.rgb) + + return correct_color + + +# ------------------------------------- +# ------------------------------------- +# ------------------------------------- +# Choose (multiple?) reference configs given a test config. + + +def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]: + correct_color = correct_color_from_test_config(test_config) + + if test_config["e_context"] == "2d": + # Canvas2d generally has the same behavior as css, so expect all passing. + yield ColorReftest([], test_config, correct_color) + return + + assert test_config["e_context"] == "webgl", test_config["e_context"] + + # - + + def reftests_from_expected_color( + notes: list[str], expected_color: CssColor + ) -> Iterable[ColorReftest]: + # If expecting failure, generate two tests, both expecting failure: + # 1. expected-fail test == correct_color + # 2. expected-pass test == (incorrect) expected_color + # If we fix an error, we'll see one unexpected-pass and one unexpected-fail. + # If we get a new wrong answer, we'll see one unexpected-fail. + + if not expected_color.is_same_color(correct_color): + yield ColorReftest(notes + ["fails"], test_config, correct_color) + yield ColorReftest(notes, test_config, expected_color) + else: + yield ColorReftest(notes, test_config, correct_color) + + # - + # On Mac, (with the pref) we do tag the IOSurface with the right cspace. + # On other platforms, Webgl always outputs in the display color profile + # right now. This is the same as "srgb". + + expected_color_srgb = CssColor("srgb", correct_color.rgb) + # Mac + yield from reftests_from_expected_color(["skip-if(!cocoaWidget)"], correct_color) + # Win, Lin, Android + yield from reftests_from_expected_color( + ["skip-if(cocoaWidget) "], expected_color_srgb + ) + + +# - + + +def amended_notes_from_reftest(reftest: ColorReftest) -> list[str]: + notes = reftest.notes[:] + + ref_rgb_vals = reftest.ref_color.rgb_vals() + is_green_only = ref_rgb_vals == (0, ref_rgb_vals[1], 0) + if ( + "fails" in reftest.notes + and reftest.test_config["e_context"] == "webgl" + and reftest.test_config["e_cspace"] == "display-p3" + and is_green_only + ): + # Android's display bitdepth rounds srgb green and p3 green to the same thing. + notes[notes.index("fails")] = "fails-if(!Android)" + + return notes + + +# ------------------------------------- +# ------------------------------------- +# ------------------------------------- +# Ok, back to implementation. + + +def encode_url_v(k, v): + if k == "e_color": + # reftest harness can't deal with spaces in urls, and 'color(srgb%201%200%200)' is hard to read. + v = v.replace(" ", ",") + + assert " " not in v, (k, v) + return v + + +# Cool: +assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,0,0,0)" +# Unfortunate, but tolerable: +assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,,,0,,,0,,,0)" + +# - + + +def url_from_config(kvs: Config) -> str: + parts = [f"{k}={encode_url_v(k,v)}" for k, v in kvs.items()] + url = "color_canvas.html?" + "&".join(parts) + return url + + +# - + +color_reftests: list[ColorReftest] = [] +for c in COMBOS: + color_reftests += reftests_from_config(c) +color_reftests = [ + ColorReftest(amended_notes_from_reftest(r), r.test_config, r.ref_color) + for r in color_reftests +] +reftests = [r.to_reftest() for r in color_reftests] + +# - + +HEADINGS = ["# annotations", "op", "test", "reference"] +table: list[list[str]] = [HEADINGS] +table = [ + [ + " ".join(r.notes), + r.op, + url_from_config(r.test_config), + url_from_config(r.ref_config), + ] + for r in reftests +] + +# - + + +def round_to(a, b: int) -> int: + return int(math.ceil(a / b) * b) + + +def aligned_lines_from_table( + rows: list[list[str]], col_delim=" ", col_alignment=4 +) -> Iterable[str]: + max_col_len = functools.reduce( + lambda accum, input: [max(r, len(c)) for r, c in zip(accum, input)], + rows, + [0 for _ in rows[0]], + ) + max_col_len = [round_to(x, col_alignment) for x in max_col_len] + + for i, row in enumerate(rows): + parts = [s + (" " * (col_len - len(s))) for s, col_len in zip(row, max_col_len)] + line = col_delim.join(parts) + yield line + + +# - + +GENERATED_FILE_LINE = "### Generated, do not edit. ###" + +lines = list(aligned_lines_from_table(table, COL_DELIM, COL_ALIGNMENT)) +WARN_EVERY_N_LINES = 5 +i = WARN_EVERY_N_LINES - 1 +while i < len(lines): + lines.insert(i, " " + GENERATED_FILE_LINE) + i += WARN_EVERY_N_LINES + +# - + +GENERATED_BY_ARGS = [f"./{pathlib.Path(__file__).name}"] + ARGS + +REFTEST_LIST_PREAMBLE = f"""\ +{GENERATED_FILE_LINE} + {GENERATED_FILE_LINE} + {GENERATED_FILE_LINE} + +# Generated by `{' '.join(GENERATED_BY_ARGS)}`. +# - + +defaults pref(webgl.colorspaces.prototype,true) + +{GENERATED_FILE_LINE} +# - + +# Ensure not white-screening: +!= {url_from_config({})+'='} about:blank +# Ensure differing results with different args: +!= {url_from_config({'e_color':'color(srgb 1 0 0)'})} {url_from_config({'e_color':'color(srgb 0 1 0)'})} + +{GENERATED_FILE_LINE} +# - +""" + +lines.insert(0, REFTEST_LIST_PREAMBLE) +lines.append("") + +# - + +for line in lines: + print(line) + +if "--write" not in ARGS: + eprint("Use --write to write. Exiting...") + sys.exit(0) + +# - + +eprint("Concatenating...") +file_str = "\n".join([line.rstrip() for line in lines]) + +eprint(f"Writing to {DEST}...") +DEST.write_bytes(file_str.encode()) +eprint("Done!") + +sys.exit(0) diff --git a/dom/canvas/test/reftest/filters/reftest.list b/dom/canvas/test/reftest/filters/reftest.list index cf851e2a3b..e4f21db6ce 100644 --- a/dom/canvas/test/reftest/filters/reftest.list +++ b/dom/canvas/test/reftest/filters/reftest.list @@ -1,7 +1,7 @@ == default-color.html ref.html == drop-shadow.html ref.html == drop-shadow-transformed.html ref.html -fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == global-alpha.html global-alpha-ref.html +fuzzy(0-1,0-1500) == global-alpha.html global-alpha-ref.html == global-composite-operation.html global-composite-operation-ref.html == liveness.html ref.html == liveness-document-open.html data:text/html,PASS @@ -20,7 +20,7 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == gl == units-off-screen.html ref.html fuzzy(0-2,0-700) == fillText-with-filter-opacity-1.html fillText-with-filter-opacity-1-ref.html fuzzy(0-1,0-302) == fillText-with-filter-opacity-2.html fillText-with-filter-opacity-2-ref.html -fuzzy(0-1,0-600) fuzzy-if(d2d&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html +fuzzy(0-1,0-600) fuzzy-if(winWidget&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html fuzzy(0-1,0-600) == strokeText-with-filter-grayscale-2.html strokeText-with-filter-grayscale-2-ref.html != fillText-with-shadow-1.html fillText-without-shadow-1-ref.html != fillText-with-shadow-2.html fillText-without-shadow-2-ref.html diff --git a/dom/canvas/test/reftest/reftest.list b/dom/canvas/test/reftest/reftest.list index 0eef0b3daf..26f373b32c 100644 --- a/dom/canvas/test/reftest/reftest.list +++ b/dom/canvas/test/reftest/reftest.list @@ -1,4 +1,5 @@ # Canvas Filter Reftests +include colors/_generated_reftest.list include filters/reftest.list include color_quads.list @@ -26,15 +27,6 @@ skip-if(Android) == webgl-resize-test.html wrapper.html?green.png # Check that captureStream() displays in a local video element skip-if(Android) == webgl-capturestream-test.html?preserve wrapper.html?green.png -# Some of the failure conditions are a little weird. I'm (jgilbert) setting these based on -# failures encountered when running on Try, and then targetting the Try config by -# differences in the `sandbox` contents. That is, I'm labeling based on symptoms rather -# than cause. -# WinXP R: winWidget && layersGPUAccelerated && !d3d11 -# Win7+ R: winWidget && layersGPUAccelerated && d3d11 -# Win7+ Ru: winWidget && !layersGPUAccelerated && d3d11 -# (Note that we have to remove spaces when used below) - # IMPORTANT: Expected outcomes are evaluated left-to-right, and they replace eachother. # That means that if an unconditional status (`fuzzy()`) is to the right of another status # (such as fails-if), it will overwrite the old status. @@ -233,7 +225,7 @@ fuzzy(0-1,0-150) == clip-multiple-move-2.html clip-multiple-move-2-ref.html == stroketext-shadow.html stroketext-shadow-ref.html # focus rings -fuzzy(0-1,0-2) skip-if(cocoaWidget||winWidget||gtkWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html +fuzzy(0-1,0-2) skip-if(cocoaWidget||gtkWidget||winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html # Check that captureStream() displays in a local video element == capturestream.html wrapper.html?green.png diff --git a/dom/canvas/test/test_accelerated_canvas_context_loss.html b/dom/canvas/test/test_accelerated_canvas_context_loss.html new file mode 100644 index 0000000000..6172420bcb --- /dev/null +++ b/dom/canvas/test/test_accelerated_canvas_context_loss.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Check for contextlost/restored events after GPU process restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<canvas id="c" width="512" height="512"></canvas> + +<script type="application/javascript"> + +function waitRAF() { + return new Promise((resolve, reject) => { + window.requestAnimationFrame(resolve); + }); +} + +async function restartGPUProcess() { + return await SpecialPowers.spawnChrome([], async () => { + const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + if (gfxInfo.usingGPUProcess) { + const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" + ); + let promise = TestUtils.topicObserved("compositor-reinitialized"); + + const remoteCanvas = gfxInfo.usingRemoteCanvas; + const acceleratedCanvas = gfxInfo.usingAcceleratedCanvas; + ok(true, "Restarting GPU process, remote canvas " + remoteCanvas + ", accelerated canvas " + acceleratedCanvas); + + gfxInfo.killGPUProcessForTests(); + await promise; + + return remoteCanvas || acceleratedCanvas; + } + + ok(true, "Not using GPU process"); + return false; + }); +} + +const canvas = document.getElementById("c"); +const context = canvas.getContext("2d"); + +let restoredPromiseResolve; +let restoredPromiseReject; + +const restoredPromise = new Promise((resolve, reject) => { + restoredPromiseResolve = resolve; + restoredPromiseReject = reject; +}); + +let countLostEvents = 0; +let countRestoredEvents = 0; + +function onContextLost() { + ok(context.isContextLost(), "Canvas context should be lost during contextlost event"); + countLostEvents += 1; +} + +function onContextRestored() { + ok(!context.isContextLost(), "Canvas context should not be lost during contextrestored event"); + countRestoredEvents += 1; + + restoredPromiseResolve(true); +} + +function waitContextRestored() { + let timeoutId = window.setTimeout(restoredPromiseReject, 5000); + return restoredPromise.then(() => { + window.clearTimeout(timeoutId); + }); +} + +async function start() { + try { + canvas.addEventListener("contextlost", onContextLost); + canvas.addEventListener("contextrestored", onContextRestored); + + ok(!context.isContextLost(), "Canvas context should not be lost before initial fill"); + + context.fillStyle = 'red'; + context.fill(); + await waitRAF(); + + ok(!context.isContextLost(), "Canvas context should not be lost after initial fill"); + + const restarted = await restartGPUProcess(); + const expectedEvents = restarted ? 1 : 0; + + if (expectedEvents) { + await waitContextRestored(); + } + await waitRAF(); + + is(countLostEvents, expectedEvents, "Should have fired " + expectedEvents + " contextlost events"); + is(countRestoredEvents, expectedEvents, "Should have fired " + expectedEvents + " contextrestored events"); + ok(!context.isContextLost(), "Canvas context should not be lost after restoration"); + + context.fillStyle = 'green'; + context.fill(); + await waitRAF(); + + ok(!context.isContextLost(), "Canvas context should not be lost at completion"); + } catch (err) { + ok(false, "Caught exception " + err); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("Wait for failure condition"); +start(); + +</script> +</body> +</html> diff --git a/dom/canvas/test/webgl-conf/generated-mochitest.toml b/dom/canvas/test/webgl-conf/generated-mochitest.toml index 8a4694100d..0cee17acf4 100644 --- a/dom/canvas/test/webgl-conf/generated-mochitest.toml +++ b/dom/canvas/test/webgl-conf/generated-mochitest.toml @@ -3,7 +3,6 @@ # Mark failing (fail-if) and crashing (skip-if) tests in mochitest-errata.ini. [DEFAULT] -skip-if = ["os == 'android' && isEmulator"] prefs = "media.seamless-looping-video=false" support-files = [ @@ -5669,7 +5668,6 @@ subsuite = "webgl2-core" ["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"] subsuite = "webgl2-core" -fail-if = ["os == 'android' && android_version == '26'"] ["generated/test_2_conformance2__renderbuffers__multisampled-stencil-renderbuffer-initialization.html"] subsuite = "webgl2-core" @@ -9275,7 +9273,6 @@ subsuite = "webgl2-ext" ["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] subsuite = "webgl2-ext" fail-if = [ - "os == 'android' && android_version == '26'", "os == 'android' && !debug", "os == 'mac' && !apple_silicon", ] @@ -14501,7 +14498,6 @@ subsuite = "webgl1-ext" ["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] subsuite = "webgl1-ext" fail-if = [ - "os == 'android' && android_version == '26'", "os == 'mac' && !apple_silicon", ] @@ -16706,7 +16702,10 @@ skip-if = ["os == 'android'"] ["generated/test_conformance__textures__misc__texture-npot-video.html"] subsuite = "webgl1-core" -skip-if = ["win11_2009"] # win11 - 50/50 intermittent +skip-if = [ + "win11_2009", # win11 - 50/50 intermittent + "os == 'android' && android_version == '33'", #Bug 1873144 + ] ["generated/test_conformance__textures__misc__texture-npot.html"] subsuite = "webgl1-core" diff --git a/dom/canvas/test/webgl-conf/mochitest-errata.toml b/dom/canvas/test/webgl-conf/mochitest-errata.toml index 05bcf2c6ac..5bf2b3f89b 100644 --- a/dom/canvas/test/webgl-conf/mochitest-errata.toml +++ b/dom/canvas/test/webgl-conf/mochitest-errata.toml @@ -23,8 +23,6 @@ # * Windows 10: 10.0 [DEFAULT] -# Cross-process WebGL doesn't seem to work under an emulator -skip-if = ["os == 'android' && isEmulator"] # Bug 1799213 prefs = "media.seamless-looping-video=false" @@ -87,7 +85,10 @@ fail-if = ["os == 'mac' && !apple_silicon"] fail-if = ["os == 'android'"] ["generated/test_conformance__textures__misc__texture-npot-video.html"] -skip-if = ["win11_2009"] # win11 - 50/50 intermittent +skip-if = [ + "win11_2009", # win11 - 50/50 intermittent + "os == 'android' && android_version == '33'", #Bug 1873144 + ] #################### # Bugs surfaced during fx93 CTS update @@ -141,14 +142,12 @@ skip-if = ["os == 'linux' && debug"] ["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] # https://bugzilla.mozilla.org/show_bug.cgi?id=1725075 fail-if = [ - "os == 'android' && android_version == '26'", "os == 'mac' && !apple_silicon", ] ["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] # Ditto fail-if = [ - "os == 'android' && android_version == '26'", "os == 'android' && !debug", "os == 'mac' && !apple_silicon", ] @@ -1102,7 +1101,6 @@ fail-if = ["os == 'win' && debug"] skip-if = ["os == 'android'"] ["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"] -fail-if = ["os == 'android' && android_version == '26'"] ["generated/test_conformance__extensions__ext-sRGB.html"] fail-if = ["os == 'android'"] diff --git a/dom/canvas/test/webgl-mochitest/mochitest.toml b/dom/canvas/test/webgl-mochitest/mochitest.toml index c76e2773e0..88e2fab88b 100644 --- a/dom/canvas/test/webgl-mochitest/mochitest.toml +++ b/dom/canvas/test/webgl-mochitest/mochitest.toml @@ -35,7 +35,8 @@ fail-if = ["os == 'android'"] ["ensure-exts/test_EXT_texture_compression_bptc.html"] fail-if = [ "os == 'android'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["ensure-exts/test_EXT_texture_compression_rgtc.html"] @@ -53,36 +54,45 @@ fail-if = ["os == 'linux' && display != 'wayland'"] ["ensure-exts/test_OVR_multiview2.html"] fail-if = [ "os == 'linux'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["ensure-exts/test_WEBGL_color_buffer_float.html"] ["ensure-exts/test_WEBGL_compressed_texture_astc.html"] fail-if = [ - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_etc.html"] fail-if = [ - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_etc1.html"] fail-if = [ "os == 'linux'", - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_pvrtc.html"] fail-if = [ "os == 'android'", "os == 'linux'", - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_s3tc.html"] @@ -137,9 +147,9 @@ support-files = ["../captureStream_common.js"] ["test_has_rbab.html"] fail-if = [ - "os == 'android' && android_version == '26'", "os == 'linux'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["test_hidden_alpha.html"] @@ -157,9 +167,9 @@ fail-if = [ ["test_noprog_draw.html"] ["test_pixel_pack_buffer.html"] -# skip-if = os == "win" && os_version == "10.0" # Bug 1302199 skip-if = [ - "os == 'win'", # Unofficial DXGL support regressed by bug 1632249 + "win10_2009", # Unofficial DXGL support regressed by bug 1632249 + "win11_2009", # Unofficial DXGL support regressed by bug 1632249 "apple_silicon", ] @@ -191,22 +201,25 @@ skip-if = ["win11_2009 && bits == 32"] # No fast video path for h264 decoder (do ["test_video_fastpath_theora.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] ["test_video_fastpath_vp8.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] ["test_video_fastpath_vp9.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index 443afcb86d..3ccb125a1e 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -327,6 +327,8 @@ namespace ChromeUtils { * * In worker threads, aOption is required and only { global: "current" } and * { global: "contextual" } are supported. + * + * In DevTools distinct global, aOptions.global is reuiqred. */ [Throws] object importESModule(DOMString aResourceURI, @@ -344,6 +346,8 @@ namespace ChromeUtils { * * In worker threads, aOption is required and only { global: "current" } and * { global: "contextual" } are supported. + * + * In DevTools distinct global, aOptions.global is reuiqred. */ [Throws] undefined defineESModuleGetters(object aTarget, object aModules, @@ -540,6 +544,9 @@ partial namespace ChromeUtils { * the same file will not cause the module to be re-evaluated, but * the symbols in EXPORTED_SYMBOLS will be exported into the * specified target object and the global object returned as above. + * + * TODO: Remove this once m-c, c-c, and out-of-tree code migrations finish + * (bug 1881888). */ [Throws] object import(UTF8String aResourceURI, optional object aTargetObj); @@ -947,7 +954,6 @@ dictionary IOActivityDataDictionary { [GenerateInitFromJSON] dictionary OriginAttributesDictionary { unsigned long userContextId = 0; - boolean inIsolatedMozBrowser = false; unsigned long privateBrowsingId = 0; DOMString firstPartyDomain = ""; DOMString geckoViewSessionContextId = ""; @@ -957,7 +963,6 @@ dictionary OriginAttributesDictionary { [GenerateInitFromJSON, GenerateToJSON] dictionary OriginAttributesPatternDictionary { unsigned long userContextId; - boolean inIsolatedMozBrowser; unsigned long privateBrowsingId; DOMString firstPartyDomain; DOMString geckoViewSessionContextId; @@ -1030,13 +1035,8 @@ enum ImportESModuleTargetGlobal { }; dictionary ImportESModuleOptionsDictionary { - /** - * If true, a distinct module loader will be used, in the system principal, - * but with a distinct global so that the DevTools can load a distinct set - * of modules and do not interfere with its debuggee. - */ - boolean loadInDevToolsLoader; - + // This field is required for importESModule and defineESModuleGetters in + // DevTools distinct global. ImportESModuleTargetGlobal global; }; diff --git a/dom/chrome-webidl/ConsoleInstance.webidl b/dom/chrome-webidl/ConsoleInstance.webidl new file mode 100644 index 0000000000..b80a0da0f9 --- /dev/null +++ b/dom/chrome-webidl/ConsoleInstance.webidl @@ -0,0 +1,179 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. + * + * For more information on this interface, please see + * https://console.spec.whatwg.org/#console-namespace + */ + +// This is used to propagate console events to the observers. +[GenerateConversionToJS] +dictionary ConsoleEvent { + (unsigned long long or DOMString) ID; + (unsigned long long or DOMString) innerID; + DOMString consoleID = ""; + DOMString addonId = ""; + DOMString level = ""; + DOMString filename = ""; + // Unique identifier within the process for the script source this event is + // associated with, or zero. + unsigned long sourceId = 0; + unsigned long lineNumber = 0; + unsigned long columnNumber = 0; + DOMString functionName = ""; + double timeStamp = 0; + double microSecondTimeStamp = 0; + sequence<any> arguments; + sequence<DOMString?> styles; + boolean private = false; + // stacktrace is handled via a getter in some cases so we can construct it + // lazily. Note that we're not making this whole thing an interface because + // consumers expect to see own properties on it, which would mean making the + // props unforgeable, which means lots of JSFunction allocations. Maybe we + // should fix those consumers, of course.... + // sequence<ConsoleStackEntry> stacktrace; + DOMString groupName = ""; + any timer = null; + any counter = null; + DOMString prefix = ""; + boolean chromeContext = false; +}; + +// Event for profile operations +[GenerateConversionToJS] +dictionary ConsoleProfileEvent { + DOMString action = ""; + sequence<any> arguments; + boolean chromeContext = false; +}; + +// This dictionary is used to manage stack trace data. +[GenerateConversionToJS] +dictionary ConsoleStackEntry { + DOMString filename = ""; + // Unique identifier within the process for the script source this entry is + // associated with, or zero. + unsigned long sourceId = 0; + unsigned long lineNumber = 0; + unsigned long columnNumber = 0; + DOMString functionName = ""; + DOMString? asyncCause; +}; + +[GenerateConversionToJS] +dictionary ConsoleTimerStart { + DOMString name = ""; +}; + +[GenerateConversionToJS] +dictionary ConsoleTimerLogOrEnd { + DOMString name = ""; + double duration = 0; +}; + +[GenerateConversionToJS] +dictionary ConsoleTimerError { + DOMString error = ""; + DOMString name = ""; +}; + +[GenerateConversionToJS] +dictionary ConsoleCounter { + DOMString label = ""; + unsigned long count = 0; +}; + +[GenerateConversionToJS] +dictionary ConsoleCounterError { + DOMString label = ""; + DOMString error = ""; +}; + +[ChromeOnly, + Exposed=(Window,Worker,WorkerDebugger,Worklet)] +// This is basically a copy of the console namespace. +interface ConsoleInstance { + // Logging + undefined assert(optional boolean condition = false, any... data); + undefined clear(); + undefined count(optional DOMString label = "default"); + undefined countReset(optional DOMString label = "default"); + undefined debug(any... data); + undefined error(any... data); + undefined info(any... data); + undefined log(any... data); + undefined table(any... data); // FIXME: The spec is still unclear about this. + undefined trace(any... data); + undefined warn(any... data); + undefined dir(any... data); // FIXME: This doesn't follow the spec yet. + undefined dirxml(any... data); + + // Grouping + undefined group(any... data); + undefined groupCollapsed(any... data); + undefined groupEnd(); + + // Timing + undefined time(optional DOMString label = "default"); + undefined timeLog(optional DOMString label = "default", any... data); + undefined timeEnd(optional DOMString label = "default"); + + // Mozilla only or Webcompat methods + + undefined _exception(any... data); + undefined timeStamp(optional any data); + + undefined profile(any... data); + undefined profileEnd(any... data); + + // Returns true if the given level would log a message. Used for avoiding + // long/significant processing when logging messages. + boolean shouldLog(ConsoleLogLevel level); +}; + +callback ConsoleInstanceDumpCallback = undefined (DOMString message); + +enum ConsoleLogLevel { + "All", "Debug", "Log", "Info", "Clear", "Trace", "TimeLog", "TimeEnd", "Time", + "Group", "GroupEnd", "Profile", "ProfileEnd", "Dir", "Dirxml", "Warn", "Error", + "Off" +}; + +dictionary ConsoleInstanceOptions { + // An optional function to intercept all strings written to stdout. + ConsoleInstanceDumpCallback dump; + + // An optional prefix string to be printed before the actual logged message. + DOMString prefix = ""; + + // An ID representing the source of the message. Normally the inner ID of a + // DOM window. + DOMString innerID = ""; + + // String identified for the console, this will be passed through the console + // notifications. + DOMString consoleID = ""; + + // Identifier that allows to filter which messages are logged based on their + // log level. + ConsoleLogLevel maxLogLevel; + + // String pref name which contains the level to use for maxLogLevel. If the + // pref doesn't exist, gets removed or it is used in workers, the maxLogLevel + // will default to the value passed to this constructor (or "all" if it wasn't + // specified). + UTF8String maxLogLevelPref = ""; +}; + +enum ConsoleLevel { "log", "warning", "error" }; + +// this interface is just for testing +partial interface ConsoleInstance { + [ChromeOnly] + undefined reportForServiceWorkerScope(DOMString scope, DOMString message, + DOMString filename, unsigned long lineNumber, + unsigned long columnNumber, + ConsoleLevel level); +}; diff --git a/dom/chrome-webidl/FrameLoader.webidl b/dom/chrome-webidl/FrameLoader.webidl index df3d03f2b7..e05d5b236b 100644 --- a/dom/chrome-webidl/FrameLoader.webidl +++ b/dom/chrome-webidl/FrameLoader.webidl @@ -123,13 +123,6 @@ interface FrameLoader { readonly attribute unsigned long long childID; /** - * Find out whether the owner content really is a mozbrowser. <xul:browser> - * is not considered to be a mozbrowser frame. - */ - [Pure] - readonly attribute boolean ownerIsMozBrowserFrame; - - /** * The last known width of the frame. Reading this property will not trigger * a reflow, and therefore may not reflect the current state of things. It * should only be used in asynchronous APIs where values are not guaranteed diff --git a/dom/chrome-webidl/IOUtils.webidl b/dom/chrome-webidl/IOUtils.webidl index a3e19c0401..cc0d25bcd6 100644 --- a/dom/chrome-webidl/IOUtils.webidl +++ b/dom/chrome-webidl/IOUtils.webidl @@ -417,7 +417,7 @@ partial namespace IOUtils { * but it would use u16-based strings, so it would basically be a separate * copy of the bindings.) * - * This interface was added for use by `Subprocess.sys.jsm`; other would-be + * This interface was added for use by `Subprocess.sys.mjs`; other would-be * callers may want to just use Subprocess instead of calling this directly. * * @param argv The command to run and its arguments. diff --git a/dom/chrome-webidl/InspectorUtils.webidl b/dom/chrome-webidl/InspectorUtils.webidl index e3ca8a3b51..64cd610804 100644 --- a/dom/chrome-webidl/InspectorUtils.webidl +++ b/dom/chrome-webidl/InspectorUtils.webidl @@ -23,16 +23,17 @@ namespace InspectorUtils { unsigned long getRelativeRuleLine(CSSRule rule); sequence<unsigned long> getRuleIndex(CSSRule rule); boolean hasRulesModifiedByCSSOM(CSSStyleSheet sheet); - // Get a flat list of all rules (including nested ones) of a given stylesheet. - // Useful for DevTools as this is faster than in JS where we'd have a lot of - // proxy access overhead building the same list. - sequence<CSSRule> getAllStyleSheetCSSStyleRules(CSSStyleSheet sheet); + // Get a flat list of specific at-rules (including nested ones) of a given stylesheet. + // Useful for DevTools (StyleEditor at-rules sidebar) as this is faster than in JS + // where we'd have a lot of proxy access overhead building the same list. + InspectorStyleSheetRuleCountAndAtRulesResult getStyleSheetRuleCountAndAtRules(CSSStyleSheet sheet); boolean isInheritedProperty(Document document, UTF8String property); sequence<DOMString> getCSSPropertyNames(optional PropertyNamesOptions options = {}); sequence<PropertyPref> getCSSPropertyPrefs(); [Throws] sequence<DOMString> getCSSValuesForProperty(UTF8String property); UTF8String rgbToColorName(octet r, octet g, octet b); InspectorRGBATuple? colorToRGBA(UTF8String colorString, optional Document? doc = null); + InspectorColorToResult? colorTo(UTF8String fromColor, UTF8String toColorSpace); boolean isValidCSSColor(UTF8String colorString); [Throws] sequence<DOMString> getSubpropertiesForCSSProperty(UTF8String property); [Throws] boolean cssPropertyIsShorthand(UTF8String property); @@ -87,6 +88,17 @@ namespace InspectorUtils { [NewObject] NodeList getOverflowingChildrenOfElement(Element element); sequence<DOMString> getRegisteredCssHighlights(Document document, optional boolean activeOnly = false); sequence<InspectorCSSPropertyDefinition> getCSSRegisteredProperties(Document document); + + // Get the start and end offsets of the first rule body within initialText + // Consider the following example: + // p { + // line-height: 2em; + // color: blue; + // } + // Calling the function with the whole text above would return offsets we can use to + // get "line-height: 2em; color: blue;" + // Returns null when opening curly bracket wasn't found in initialText + InspectorGetRuleBodyTextResult? getRuleBodyTextOffsets(UTF8String initialText); }; dictionary SupportsOptions { @@ -119,6 +131,12 @@ dictionary InspectorRGBATuple { double a = 1; }; +dictionary InspectorColorToResult { + required DOMString color; + required sequence<float> components; + required boolean adjusted; +}; + // Any update to this enum should probably also update // devtools/shared/css/constants.js enum InspectorPropertyType { @@ -159,6 +177,16 @@ dictionary InspectorCSSPropertyDefinition { required boolean fromJS; }; +dictionary InspectorGetRuleBodyTextResult { + required double startOffset; + required double endOffset; +}; + +dictionary InspectorStyleSheetRuleCountAndAtRulesResult { + required sequence<CSSRule> atRules; + required unsigned long ruleCount; +}; + [Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled", Exposed=Window] interface InspectorFontFace { diff --git a/dom/chrome-webidl/JSProcessActor.webidl b/dom/chrome-webidl/JSProcessActor.webidl index a4dc3d69e1..f158af53b3 100644 --- a/dom/chrome-webidl/JSProcessActor.webidl +++ b/dom/chrome-webidl/JSProcessActor.webidl @@ -74,6 +74,9 @@ dictionary ProcessActorSidedOptions { * * If neither this nor `esModuleURI` is passed, the specified side cannot receive * messages, but may send them using `sendAsyncMessage` or `sendQuery`. + * + * TODO: Remove this once m-c, c-c, and out-of-tree code migrations finish + * (bug 1866732). */ ByteString moduleURI; diff --git a/dom/chrome-webidl/JSWindowActor.webidl b/dom/chrome-webidl/JSWindowActor.webidl index 767b4854c5..09432e7e67 100644 --- a/dom/chrome-webidl/JSWindowActor.webidl +++ b/dom/chrome-webidl/JSWindowActor.webidl @@ -132,6 +132,9 @@ dictionary WindowActorSidedOptions { * * If neither this nor `esModuleURI` is passed, the specified side cannot receive * messages, but may send them using `sendAsyncMessage` or `sendQuery`. + * + * TODO: Remove this once m-c, c-c, and out-of-tree code migrations finish + * (bug 1866732). */ ByteString moduleURI; diff --git a/dom/chrome-webidl/moz.build b/dom/chrome-webidl/moz.build index c34b5f6432..54c40cdab9 100644 --- a/dom/chrome-webidl/moz.build +++ b/dom/chrome-webidl/moz.build @@ -50,6 +50,7 @@ WEBIDL_FILES = [ "ChromeNodeList.webidl", "ClonedErrorHolder.webidl", "CommandEvent.webidl", + "ConsoleInstance.webidl", "CSSCustomPropertyRegisteredEvent.webidl", "DebuggerNotification.webidl", "DebuggerNotificationObserver.webidl", diff --git a/dom/clients/manager/ClientIPCUtils.h b/dom/clients/manager/ClientIPCUtils.h index 7f8cf35c1b..5718234084 100644 --- a/dom/clients/manager/ClientIPCUtils.h +++ b/dom/clients/manager/ClientIPCUtils.h @@ -9,6 +9,7 @@ #include "ipc/EnumSerializer.h" #include "X11UndefineNone.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/ClientBinding.h" #include "mozilla/dom/ClientsBinding.h" #include "mozilla/dom/DocumentBinding.h" @@ -17,21 +18,16 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::ClientType> - : public ContiguousEnumSerializer<mozilla::dom::ClientType, - mozilla::dom::ClientType::Window, - mozilla::dom::ClientType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::ClientType> {}; template <> struct ParamTraits<mozilla::dom::FrameType> - : public ContiguousEnumSerializer<mozilla::dom::FrameType, - mozilla::dom::FrameType::Auxiliary, - mozilla::dom::FrameType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::FrameType> {}; template <> struct ParamTraits<mozilla::dom::VisibilityState> - : public ContiguousEnumSerializer< - mozilla::dom::VisibilityState, mozilla::dom::VisibilityState::Hidden, - mozilla::dom::VisibilityState::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::VisibilityState> { +}; template <> struct ParamTraits<mozilla::StorageAccess> diff --git a/dom/clients/manager/ClientManagerParent.cpp b/dom/clients/manager/ClientManagerParent.cpp index d2dabd1616..61e3dd57a4 100644 --- a/dom/clients/manager/ClientManagerParent.cpp +++ b/dom/clients/manager/ClientManagerParent.cpp @@ -88,7 +88,12 @@ ClientManagerParent::AllocPClientSourceParent( IPCResult ClientManagerParent::RecvPClientSourceConstructor( PClientSourceParent* aActor, const ClientSourceConstructorArgs& aArgs) { ClientSourceParent* actor = static_cast<ClientSourceParent*>(aActor); - actor->Init(); + + IPCResult result = actor->Init(); + if (!result) { + return result; + } + return IPC_OK(); } diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp index 8364871cd4..e2c02c0bfd 100644 --- a/dom/clients/manager/ClientSourceParent.cpp +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -26,60 +26,6 @@ using mozilla::ipc::BackgroundParent; using mozilla::ipc::IPCResult; using mozilla::ipc::PrincipalInfo; -namespace { - -// It would be nice to use a lambda instead of this class, but we cannot -// move capture in lambdas yet and ContentParent cannot be AddRef'd off -// the main thread. -class KillContentParentRunnable final : public Runnable { - RefPtr<ThreadsafeContentParentHandle> mHandle; - - public: - explicit KillContentParentRunnable( - RefPtr<ThreadsafeContentParentHandle>&& aHandle) - : Runnable("KillContentParentRunnable"), mHandle(std::move(aHandle)) { - MOZ_ASSERT(mHandle); - } - - NS_IMETHOD - Run() override { - AssertIsOnMainThread(); - if (RefPtr<ContentParent> contentParent = mHandle->GetContentParent()) { - contentParent->KillHard("invalid ClientSourceParent actor"); - } - return NS_OK; - } -}; - -} // anonymous namespace - -void ClientSourceParent::KillInvalidChild() { - // Try to get the content process before we destroy the actor below. - RefPtr<ThreadsafeContentParentHandle> process = - BackgroundParent::GetContentParentHandle(Manager()->Manager()); - - // First, immediately teardown the ClientSource actor. No matter what - // we want to start this process as soon as possible. - Unused << ClientSourceParent::Send__delete__(this); - - // If we are running in non-e10s, then there is nothing else to do here. - // There is no child process and we don't want to crash the entire browser - // in release builds. In general, though, this should not happen in non-e10s - // so we do assert this condition. - if (!process) { - MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s"); - return; - } - - // In e10s mode we also want to kill the child process. Validation failures - // typically mean someone sent us bogus data over the IPC link. We can't - // trust that process any more. We have to do this on the main thread, so - // there is a small window of time before we kill the process. This is why - // we start the actor destruction immediately above. - nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(std::move(process)); - MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); -} - mozilla::ipc::IPCResult ClientSourceParent::RecvWorkerSyncPing() { AssertIsOnBackgroundThread(); // Do nothing here. This is purely a sync message allowing the child to @@ -98,8 +44,7 @@ IPCResult ClientSourceParent::RecvExecutionReady( // to make sure the child actor is not giving us garbage. Since we validate // on the child side as well we treat a failure here as fatal. if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) { - KillInvalidChild(); - return IPC_OK(); + return IPC_FAIL(this, "Invalid creation URL!"); } mClientInfo.SetURL(aArgs.url()); @@ -216,23 +161,23 @@ ClientSourceParent::~ClientSourceParent() { mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); } -void ClientSourceParent::Init() { +IPCResult ClientSourceParent::Init() { // Ensure the principal is reasonable before adding ourself to the service. // Since we validate the principal on the child side as well, any failure // here is treated as fatal. if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { mService->ForgetFutureSource(mClientInfo.ToIPC()); - KillInvalidChild(); - return; + return IPC_FAIL(Manager(), "Invalid PrincipalInfo!"); } // Its possible for AddSource() to fail if there is already an entry for // our UUID. This should not normally happen, but could if someone is // spoofing IPC messages. if (NS_WARN_IF(!mService->AddSource(this))) { - KillInvalidChild(); - return; + return IPC_FAIL(Manager(), "Already registered!"); } + + return IPC_OK(); } const ClientInfo& ClientSourceParent::Info() const { return mClientInfo; } diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h index 8f3ef78603..23ea80160f 100644 --- a/dom/clients/manager/ClientSourceParent.h +++ b/dom/clients/manager/ClientSourceParent.h @@ -28,8 +28,6 @@ class ClientSourceParent final : public PClientSourceParent { bool mExecutionReady; bool mFrozen; - void KillInvalidChild(); - ~ClientSourceParent(); // PClientSourceParent @@ -62,7 +60,7 @@ class ClientSourceParent final : public PClientSourceParent { explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs, const Maybe<ContentParentId>& aContentParentId); - void Init(); + mozilla::ipc::IPCResult Init(); const ClientInfo& Info() const; diff --git a/dom/console/Console.h b/dom/console/Console.h index 410cc8ba46..5898c6b0f5 100644 --- a/dom/console/Console.h +++ b/dom/console/Console.h @@ -9,6 +9,7 @@ #include "domstubs.h" #include "mozilla/dom/ConsoleBinding.h" +#include "mozilla/dom/ConsoleInstanceBinding.h" #include "mozilla/TimeStamp.h" #include "nsCycleCollectionParticipant.h" #include "nsTHashMap.h" diff --git a/dom/console/ConsoleAPIStorage.sys.mjs b/dom/console/ConsoleAPIStorage.sys.mjs index f920d0d5e0..2f537f21e9 100644 --- a/dom/console/ConsoleAPIStorage.sys.mjs +++ b/dom/console/ConsoleAPIStorage.sys.mjs @@ -49,7 +49,7 @@ ConsoleAPIStorageService.prototype = { "nsIObserver", ]), - observe: function CS_observe(aSubject, aTopic, aData) { + observe: function CS_observe(aSubject, aTopic) { if (aTopic == "xpcom-shutdown") { Services.obs.removeObserver(this, "xpcom-shutdown"); Services.obs.removeObserver(this, "inner-window-destroyed"); diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp index 24477da56c..ade82be50f 100644 --- a/dom/console/ConsoleInstance.cpp +++ b/dom/console/ConsoleInstance.cpp @@ -71,7 +71,7 @@ ConsoleInstance::ConsoleInstance(JSContext* aCx, return; } - CopyUTF16toUTF8(aOptions.mMaxLogLevelPref, mMaxLogLevelPref); + mMaxLogLevelPref = aOptions.mMaxLogLevelPref; Preferences::RegisterCallback(MaxLogLevelPrefChangedCallback, mMaxLogLevelPref, this); @@ -106,9 +106,8 @@ ConsoleLogLevel PrefToValue(const nsACString& aPref, return aLevel; } - int index = FindEnumStringIndexImpl(value.get(), value.Length(), - ConsoleLogLevelValues::strings); - if (NS_WARN_IF(index < 0)) { + Maybe<ConsoleLogLevel> level = StringToEnum<ConsoleLogLevel>(value); + if (NS_WARN_IF(level.isNothing())) { nsString message; message.AssignLiteral("Invalid Console.maxLogLevelPref value: "); message.Append(NS_ConvertUTF8toUTF16(value)); @@ -118,8 +117,7 @@ ConsoleLogLevel PrefToValue(const nsACString& aPref, return aLevel; } - MOZ_ASSERT(index < (int)ConsoleLogLevelValues::Count); - return static_cast<ConsoleLogLevel>(index); + return level.value(); } void ConsoleInstance::SetLogLevel() { diff --git a/dom/console/ConsoleUtils.cpp b/dom/console/ConsoleUtils.cpp index 9767dd30dd..585ff2ec3d 100644 --- a/dom/console/ConsoleUtils.cpp +++ b/dom/console/ConsoleUtils.cpp @@ -14,6 +14,7 @@ #include "mozilla/ClearOnShutdown.h" #include "mozilla/NullPrincipal.h" #include "mozilla/dom/ConsoleBinding.h" +#include "mozilla/dom/ConsoleInstanceBinding.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/ScriptSettings.h" #include "js/PropertyAndElement.h" // JS_DefineProperty diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html index ca0f85b110..ec653e1954 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html index 99b897d35e..6d42b5b75a 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html b/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html index 0151f4b6c4..3ff519e954 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html @@ -22,9 +22,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html b/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html index ad5b6ea28c..5f992d6685 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html @@ -17,9 +17,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html b/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html index 4425abf5aa..5507c425de 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html @@ -15,9 +15,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html index ddc6716081..bcff6141dc 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html index 88512a1d22..d2538722f1 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html b/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html index 90c3335eda..5cb09abd00 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_simple.html b/dom/credentialmanagement/identity/tests/mochitest/test_simple.html index 39d34f3d5f..8a2190853b 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_simple.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_simple.html @@ -31,7 +31,7 @@ is(cred.type, "identity", "Correct type on the credential"); - }).catch((err) => { + }).catch(() => { ok(false, "must not have an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html b/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html index 36e99adf75..3ecef462ce 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html @@ -31,7 +31,7 @@ is(cred.type, "identity", "Correct type on the credential"); - }).catch((err) => { + }).catch(() => { ok(false, "must not have an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html b/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html index 5533f71064..aded86a23d 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html @@ -37,7 +37,7 @@ is(cred.type, "identity", "Correct type on the credential"); - }).catch((err) => { + }).catch(() => { ok(false, "must not have an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html b/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html index 8ff1afe04d..57375dd3b0 100644 --- a/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html +++ b/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html @@ -20,9 +20,9 @@ } }); } - ).then((cred) => { + ).then(() => { ok(false, "incorrectly got a credential"); - }).catch((err) => { + }).catch(() => { ok(true, "correctly got an error"); }).finally(() => { SimpleTest.finish(); diff --git a/dom/crypto/test/test-array.js b/dom/crypto/test/test-array.js index 0d7f67433f..357694f9de 100644 --- a/dom/crypto/test/test-array.js +++ b/dom/crypto/test/test-array.js @@ -112,7 +112,7 @@ function WorkerTest(worker, name, test) { // We expect only boolean responses from the worker script. worker.onmessage = e => this.complete(e.data); - worker.onerror = e => this.complete(false); + worker.onerror = () => this.complete(false); }; var base = new Test(name, test); diff --git a/dom/crypto/test/test-worker.js b/dom/crypto/test/test-worker.js index 263877ad08..47a365a66f 100644 --- a/dom/crypto/test/test-worker.js +++ b/dom/crypto/test/test-worker.js @@ -31,7 +31,7 @@ function memcmp_complete(test, value) { }; } -function error(test) { +function error() { return function (x) { throw x; }; diff --git a/dom/crypto/test/test_WebCrypto.html b/dom/crypto/test/test_WebCrypto.html index 90da813880..aded1bdd91 100644 --- a/dom/crypto/test/test_WebCrypto.html +++ b/dom/crypto/test/test_WebCrypto.html @@ -56,7 +56,7 @@ TestArray.addTest( crypto.subtle.importKey("raw", tv.raw, alg, true, ["encrypt"]) .then( error(that), - complete(that, function(x) { return true; }) + complete(that, function() { return true; }) ); } ); @@ -982,7 +982,7 @@ TestArray.addTest( .then(doVerify) .then( error(that), - complete(that, function(x) { return true; }) + complete(that, function() { return true; }) ); } ); diff --git a/dom/crypto/test/test_WebCrypto_ECDH.html b/dom/crypto/test/test_WebCrypto_ECDH.html index 2d8f9bba28..8386d3c3dd 100644 --- a/dom/crypto/test/test_WebCrypto_ECDH.html +++ b/dom/crypto/test/test_WebCrypto_ECDH.html @@ -61,7 +61,7 @@ TestArray.addTest( function setKeyPair(x) { pair = x; } function doDerive(n) { - return function(x) { + return function() { return crypto.subtle.deriveBits({ name: "ECDH", public: pair.publicKey }, pair.privateKey, n * 8); }; } diff --git a/dom/crypto/test/test_WebCrypto_JWK.html b/dom/crypto/test/test_WebCrypto_JWK.html index 6e83a73d8d..4fa2af5dce 100644 --- a/dom/crypto/test/test_WebCrypto_JWK.html +++ b/dom/crypto/test/test_WebCrypto_JWK.html @@ -76,7 +76,7 @@ TestArray.addTest( function doVerify(x) { return crypto.subtle.verify(alg.name, x, tv.rsassa.sig256, tv.rsassa.data); } - function fail(x) { error(that); } + function fail() { error(that); } crypto.subtle.importKey("jwk", tv.rsassa.jwk_pub, alg, false, ["verify"]) .then( doVerify, fail ) diff --git a/dom/docs/webIdlBindings/index.md b/dom/docs/webIdlBindings/index.md index a239179071..eb2586210d 100644 --- a/dom/docs/webIdlBindings/index.md +++ b/dom/docs/webIdlBindings/index.md @@ -894,10 +894,7 @@ which becomes the value `_empty`. For a Web IDL enum named `MyEnum`, the C++ enum is named `MyEnum` and placed in the `mozilla::dom` namespace, while the values are placed in -the `mozilla::dom::MyEnum` namespace. There is also a -`mozilla::dom::MyEnumValues::strings` which is an array of -`mozilla::dom::EnumEntry` structs that gives access to the string -representations of the values. +the `mozilla::dom::MyEnum` namespace. The type of the enum class is automatically selected to be the smallest unsigned integer type that can hold all the values. In practice, this @@ -924,12 +921,35 @@ enum class MyEnum : uint8_t { _empty, Another }; - -namespace MyEnumValues { -extern const EnumEntry strings[10]; -} // namespace MyEnumValues ``` +`mozilla::dom::GetEnumString` is a templated helper function declared in +[`BindingUtils.h`](https://searchfox.org/mozilla-central/source/dom/bindings/BindingUtils.h) +and exported to `mozilla/dom/BindingUtils.h` that can be used to convert an enum +value to its corresponding string value. It returns a `const nsCString&` +containing the string value. + +`mozilla::dom::StringToEnum` is a templated helper function in +[`BindingUtils.h`](https://searchfox.org/mozilla-central/source/dom/bindings/BindingUtils.h) +and exported to `mozilla/dom/BindingUtils.h` that can be used to convert a +string to the corresponding enum value. It needs to be supplied with the enum +class as a template argument, and returns a `mozilla::Maybe<Enum>`. If the string +value passed to it as an argument is not one of the string values for the enum +then it returns `mozilla::Nothing()`, else it returns the right enum value in +the `mozilla::Maybe`. + +`mozilla::dom::WebIDLEnumSerializer` is a templated alias in +[`BindingIPCUtils.h`](https://searchfox.org/mozilla-central/source/dom/bindings/BindingIPCUtils.h) +exported to `mozilla/dom/BindingIPCUtils.h` to implement an IPC serializer with +the right validation for WebIDL enums. It uses a +`mozilla::MaxContinuousEnumValue` that is generated for every WebIDL enum to +implement the validation. + +`mozilla::dom::MakeWebIDLEnumeratedRange` is a templated helper function in +[`BindingUtils.h`](https://searchfox.org/mozilla-central/source/dom/bindings/BindingUtils.h) +and exported to `mozilla/dom/BindingUtils.h` that can be used to create a +`mozilla::EnumeratedRange` for a WebIDL enum. + #### Callback function types Callback functions are represented as an object, inheriting from diff --git a/dom/encoding/test/reftest/reftest.list b/dom/encoding/test/reftest/reftest.list index 1e6bbb23e4..16ba0f2816 100644 --- a/dom/encoding/test/reftest/reftest.list +++ b/dom/encoding/test/reftest/reftest.list @@ -1,5 +1,5 @@ == bug863728-1.html bug863728-1-ref.html -fuzzy(0-128,0-281) fuzzy-if(winWidget&&browserIsFission,47-137,211-251) == bug863728-2.html bug863728-2-ref.html # fission: bug 1717838 +fuzzy(0-128,0-281) fuzzy-if(winWidget&&fission,47-137,211-251) == bug863728-2.html bug863728-2-ref.html # fission: bug 1717838 == bug863728-3.html bug863728-3-ref.html == bug945215-1.html bug945215-1-ref.html -fuzzy(0-128,0-281) fuzzy-if(winWidget&&browserIsFission,47-137,211-251) == bug945215-2.html bug945215-2-ref.html # fission: bug 1717838 +fuzzy(0-128,0-281) fuzzy-if(winWidget&&fission,47-137,211-251) == bug945215-2.html bug945215-2-ref.html # fission: bug 1717838 diff --git a/dom/encoding/test/test_TextDecoder.js b/dom/encoding/test/test_TextDecoder.js index cab246ddab..7d6f340786 100644 --- a/dom/encoding/test/test_TextDecoder.js +++ b/dom/encoding/test/test_TextDecoder.js @@ -59,7 +59,7 @@ function runTextDecoderOptions() { * - This function is not testing the decode function. * */ -function testConstructorFatalOption(data, expectedString) { +function testConstructorFatalOption() { //invalid string to decode passed, fatal = false testCharset({ fatal: false, diff --git a/dom/events/Clipboard.cpp b/dom/events/Clipboard.cpp index 560002dd68..b163bc816f 100644 --- a/dom/events/Clipboard.cpp +++ b/dom/events/Clipboard.cpp @@ -29,6 +29,7 @@ #include "nsArrayUtils.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" #include "nsIClipboard.h" #include "nsIInputStream.h" #include "nsIParserUtils.h" @@ -86,6 +87,16 @@ class ClipboardGetCallback : public nsIAsyncClipboardGetCallback { RefPtr<Promise> mPromise; }; +static nsTArray<nsCString> MandatoryDataTypesAsCStrings() { + // Mandatory data types defined in + // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types + // should be in the same order as kNonPlainTextExternalFormats in + // DataTransfer. + return nsTArray<nsCString>{nsLiteralCString(kHTMLMime), + nsLiteralCString(kTextMime), + nsLiteralCString(kPNGImageMime)}; +} + class ClipboardGetCallbackForRead final : public ClipboardGetCallback { public: explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal, @@ -109,11 +120,15 @@ class ClipboardGetCallbackForRead final : public ClipboardGetCallback { } AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries; - for (const auto& format : flavorList) { - auto entry = MakeRefPtr<ClipboardItem::ItemEntry>( - mGlobal, NS_ConvertUTF8toUTF16(format)); - entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData); - entries.AppendElement(std::move(entry)); + // We might reuse the request from DataTransfer created for paste event, + // which could contain more types that are not in the mandatory list. + for (const auto& format : MandatoryDataTypesAsCStrings()) { + if (flavorList.Contains(format)) { + auto entry = MakeRefPtr<ClipboardItem::ItemEntry>( + mGlobal, NS_ConvertUTF8toUTF16(format)); + entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData); + entries.AppendElement(std::move(entry)); + } } RefPtr<Promise> p(std::move(mPromise)); @@ -214,6 +229,36 @@ NS_IMPL_ISUPPORTS(ClipboardGetCallbackForReadText, nsIAsyncClipboardGetCallback, } // namespace +void Clipboard::RequestRead(Promise& aPromise, const ReadRequestType& aType, + nsPIDOMWindowInner& aOwner, + nsIPrincipal& aSubjectPrincipal, + nsIAsyncGetClipboardData& aRequest) { +#ifdef DEBUG + bool isValid = false; + MOZ_ASSERT(NS_SUCCEEDED(aRequest.GetValid(&isValid)) && isValid); +#endif + + RefPtr<ClipboardGetCallback> callback; + switch (aType) { + case ReadRequestType::eRead: { + callback = + MakeRefPtr<ClipboardGetCallbackForRead>(aOwner.AsGlobal(), &aPromise); + break; + } + case ReadRequestType::eReadText: { + callback = MakeRefPtr<ClipboardGetCallbackForReadText>(&aPromise); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown read type"); + return; + } + } + + MOZ_ASSERT(callback); + callback->OnSuccess(&aRequest); +} + void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType, nsPIDOMWindowInner* aOwner, nsIPrincipal& aPrincipal) { @@ -239,19 +284,14 @@ void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType, callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p)); rv = clipboardService->AsyncGetData( - // Mandatory data types defined in - // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x - AutoTArray<nsCString, 3>{nsDependentCString(kHTMLMime), - nsDependentCString(kTextMime), - nsDependentCString(kPNGImageMime)}, - nsIClipboard::kGlobalClipboard, owner->GetWindowContext(), - &aPrincipal, callback); + MandatoryDataTypesAsCStrings(), nsIClipboard::kGlobalClipboard, + owner->GetWindowContext(), &aPrincipal, callback); break; } case ReadRequestType::eReadText: { callback = MakeRefPtr<ClipboardGetCallbackForReadText>(std::move(p)); rv = clipboardService->AsyncGetData( - AutoTArray<nsCString, 1>{nsDependentCString(kTextMime)}, + AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)}, nsIClipboard::kGlobalClipboard, owner->GetWindowContext(), &aPrincipal, callback); break; @@ -288,6 +328,24 @@ already_AddRefed<Promise> Clipboard::ReadHelper(nsIPrincipal& aSubjectPrincipal, return p.forget(); } + // If a "paste" clipboard event is actively being processed, we're + // intentionally skipping permission/user-activation checks and giving the + // webpage access to the clipboard. + if (RefPtr<DataTransfer> dataTransfer = + nsGlobalWindowInner::Cast(owner)->GetCurrentPasteDataTransfer()) { + // If there is valid nsIAsyncGetClipboardData, use it directly. + if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData = + dataTransfer->GetAsyncGetClipboardData()) { + bool isValid = false; + asyncGetClipboardData->GetValid(&isValid); + if (isValid) { + RequestRead(*p, aType, *owner, aSubjectPrincipal, + *asyncGetClipboardData); + return p.forget(); + } + } + } + if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) { MOZ_LOG(GetClipboardLog(), LogLevel::Debug, ("%s: testing pref enabled or has read permission", __FUNCTION__)); diff --git a/dom/events/Clipboard.h b/dom/events/Clipboard.h index 2df4df1ec5..a9952e6052 100644 --- a/dom/events/Clipboard.h +++ b/dom/events/Clipboard.h @@ -13,7 +13,8 @@ #include "mozilla/Logging.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" -#include "mozilla/dom/DataTransfer.h" + +class nsIAsyncGetClipboardData; namespace mozilla::dom { @@ -75,6 +76,10 @@ class Clipboard : public DOMEventTargetHelper { void RequestRead(Promise* aPromise, ReadRequestType aType, nsPIDOMWindowInner* aOwner, nsIPrincipal& aPrincipal); + + void RequestRead(Promise& aPromise, const ReadRequestType& aType, + nsPIDOMWindowInner& aOwner, nsIPrincipal& aSubjectPrincipal, + nsIAsyncGetClipboardData& aRequest); }; } // namespace mozilla::dom diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index ad8c7059da..ba56575749 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -622,52 +622,64 @@ already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent( } // The order of the types matters. `kFileMime` needs to be one of the first two -// types. -static const char* kNonPlainTextExternalFormats[] = { - kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, kURLMime, - kURLDataMime, kTextMime, kPNGImageMime, kPDFJSMime}; - -/* static */ -void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard, - const bool& aPlainTextOnly, - nsTArray<nsCString>* aResult) { - MOZ_ASSERT(aResult); - +// types. And the order should be the same as the types order defined in +// MandatoryDataTypesAsCStrings() for Clipboard API. +static const nsCString kNonPlainTextExternalFormats[] = { + nsLiteralCString(kCustomTypesMime), nsLiteralCString(kFileMime), + nsLiteralCString(kHTMLMime), nsLiteralCString(kRTFMime), + nsLiteralCString(kURLMime), nsLiteralCString(kURLDataMime), + nsLiteralCString(kTextMime), nsLiteralCString(kPNGImageMime), + nsLiteralCString(kPDFJSMime)}; + +void DataTransfer::GetExternalClipboardFormats(const bool& aPlainTextOnly, + nsTArray<nsCString>& aResult) { // NOTE: When you change this method, you may need to change // GetExternalTransferableFormats() too since those methods should // work similarly. + MOZ_ASSERT(!mAsyncGetClipboardData); + + RefPtr<WindowContext> wc = GetWindowContext(); + if (NS_WARN_IF(!wc)) { + MOZ_ASSERT_UNREACHABLE( + "How could this DataTransfer be created with a non-window global?"); + return; + } + nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1"); - if (!clipboard || aWhichClipboard < 0) { + if (!clipboard || mClipboardType < 0) { return; } + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData; if (aPlainTextOnly) { - bool hasType; - AutoTArray<nsCString, 1> textMime = {nsDependentCString(kTextMime)}; - nsresult rv = - clipboard->HasDataMatchingFlavors(textMime, aWhichClipboard, &hasType); - NS_SUCCEEDED(rv); - if (hasType) { - aResult->AppendElement(kTextMime); - } + rv = clipboard->GetDataSnapshotSync( + AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)}, mClipboardType, + wc, getter_AddRefs(asyncGetClipboardData)); + } else { + AutoTArray<nsCString, ArrayLength(kNonPlainTextExternalFormats)> formats; + formats.AppendElements(Span<const nsCString>(kNonPlainTextExternalFormats)); + rv = clipboard->GetDataSnapshotSync(formats, mClipboardType, wc, + getter_AddRefs(asyncGetClipboardData)); + } + + if (NS_FAILED(rv) || !asyncGetClipboardData) { return; } - // If not plain text only, then instead check all the other types - for (uint32_t f = 0; f < mozilla::ArrayLength(kNonPlainTextExternalFormats); - ++f) { - bool hasType; - AutoTArray<nsCString, 1> format = { - nsDependentCString(kNonPlainTextExternalFormats[f])}; - nsresult rv = - clipboard->HasDataMatchingFlavors(format, aWhichClipboard, &hasType); - NS_SUCCEEDED(rv); - if (hasType) { - aResult->AppendElement(kNonPlainTextExternalFormats[f]); + // Order is important for DataTransfer; ensure the returned list items follow + // the sequence specified in kNonPlainTextExternalFormats. + AutoTArray<nsCString, ArrayLength(kNonPlainTextExternalFormats)> flavors; + asyncGetClipboardData->GetFlavorList(flavors); + for (const auto& format : kNonPlainTextExternalFormats) { + if (flavors.Contains(format)) { + aResult.AppendElement(format); } } + + mAsyncGetClipboardData = asyncGetClipboardData; } /* static */ @@ -695,10 +707,10 @@ void DataTransfer::GetExternalTransferableFormats( } // If not plain text only, then instead check all the other types - for (const char* format : kNonPlainTextExternalFormats) { - auto index = flavors.IndexOf(nsCString(format)); + for (const auto& format : kNonPlainTextExternalFormats) { + auto index = flavors.IndexOf(format); if (index != flavors.NoIndex) { - aResult->AppendElement(nsCString(format)); + aResult->AppendElement(format); } } } @@ -1192,7 +1204,10 @@ void DataTransfer::Disconnect() { } } -void DataTransfer::ClearAll() { mItems->ClearAllItems(); } +void DataTransfer::ClearAll() { + mItems->ClearAllItems(); + mAsyncGetClipboardData = nullptr; +} uint32_t DataTransfer::MozItemCount() const { return mItems->MozItemCount(); } @@ -1260,6 +1275,24 @@ already_AddRefed<nsIGlobalObject> DataTransfer::GetGlobal() const { return global.forget(); } +already_AddRefed<WindowContext> DataTransfer::GetWindowContext() const { + nsCOMPtr<nsIGlobalObject> global = GetGlobal(); + if (!global) { + return nullptr; + } + + const auto* innerWindow = global->GetAsInnerWindow(); + if (!innerWindow) { + return nullptr; + } + + return do_AddRef(innerWindow->GetWindowContext()); +} + +nsIAsyncGetClipboardData* DataTransfer::GetAsyncGetClipboardData() const { + return mAsyncGetClipboardData; +} + nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex, nsIPrincipal* aPrincipal, bool aHidden) { @@ -1357,20 +1390,13 @@ void DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly) { "caching clipboard data for invalid event"); nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal(); - nsTArray<nsCString> typesArray; - - if (XRE_IsContentProcess()) { - ContentChild::GetSingleton()->SendGetExternalClipboardFormats( - mClipboardType, aPlainTextOnly, &typesArray); - } else { - GetExternalClipboardFormats(mClipboardType, aPlainTextOnly, &typesArray); - } - + GetExternalClipboardFormats(aPlainTextOnly, typesArray); if (aPlainTextOnly) { // The only thing that will be in types is kTextMime MOZ_ASSERT(typesArray.IsEmpty() || typesArray.Length() == 1); if (typesArray.Length() == 1) { + MOZ_ASSERT(typesArray.Contains(kTextMime)); CacheExternalData(kTextMime, 0, sysPrincipal, false); } return; diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h index ea0368216a..7ff2bff54b 100644 --- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -23,6 +23,7 @@ #include "mozilla/dom/DataTransferItemList.h" #include "mozilla/dom/File.h" +class nsIAsyncGetClipboardData; class nsINode; class nsITransferable; class nsILoadContext; @@ -386,14 +387,6 @@ class DataTransfer final : public nsISupports, public nsWrapperCache { } bool MozShowFailAnimation() const { return mShowFailAnimation; } - // Retrieve a list of clipboard formats supported - // - // If kFileMime is supported, then it will be placed either at - // index 0 or at index 1 in aResult - static void GetExternalClipboardFormats(const int32_t& aWhichClipboard, - const bool& aPlainTextOnly, - nsTArray<nsCString>* aResult); - // Retrieve a list of supporting formats in aTransferable. // // If kFileMime is supported, then it will be placed either at @@ -428,7 +421,18 @@ class DataTransfer final : public nsISupports, public nsWrapperCache { already_AddRefed<nsIGlobalObject> GetGlobal() const; + already_AddRefed<WindowContext> GetWindowContext() const; + + nsIAsyncGetClipboardData* GetAsyncGetClipboardData() const; + protected: + // Retrieve a list of clipboard formats supported + // + // If kFileMime is supported, then it will be placed either at + // index 0 or at index 1 in aResult + void GetExternalClipboardFormats(const bool& aPlainTextOnly, + nsTArray<nsCString>& aResult); + // caches text and uri-list data formats that exist in the drag service or // clipboard for retrieval later. nsresult CacheExternalData(const char* aFormat, uint32_t aIndex, @@ -506,6 +510,11 @@ class DataTransfer final : public nsISupports, public nsWrapperCache { // drag and drop. int32_t mClipboardType; + // The nsIAsyncGetClipboardData that is used for getting clipboard formats. + // XXXedgar we should get the actual data from this in the future, see bug + // 1879401. + nsCOMPtr<nsIAsyncGetClipboardData> mAsyncGetClipboardData; + // The items contained with the DataTransfer RefPtr<DataTransferItemList> mItems; diff --git a/dom/events/EventListenerService.cpp b/dom/events/EventListenerService.cpp index 7e4919d938..3398f0dd02 100644 --- a/dom/events/EventListenerService.cpp +++ b/dom/events/EventListenerService.cpp @@ -220,21 +220,6 @@ EventListenerService::GetListenerInfoFor( } NS_IMETHODIMP -EventListenerService::GetEventTargetChainFor( - EventTarget* aEventTarget, bool aComposed, - nsTArray<RefPtr<EventTarget>>& aOutArray) { - NS_ENSURE_ARG(aEventTarget); - WidgetEvent event(true, eVoidEvent); - event.SetComposed(aComposed); - nsTArray<EventTarget*> targets; - nsresult rv = EventDispatcher::Dispatch(aEventTarget, nullptr, &event, - nullptr, nullptr, nullptr, &targets); - NS_ENSURE_SUCCESS(rv, rv); - aOutArray.AppendElements(targets); - return NS_OK; -} - -NS_IMETHODIMP EventListenerService::HasListenersFor(EventTarget* aEventTarget, const nsAString& aType, bool* aRetVal) { NS_ENSURE_TRUE(aEventTarget, NS_ERROR_UNEXPECTED); @@ -258,54 +243,6 @@ static already_AddRefed<EventListener> ToEventListener( } NS_IMETHODIMP -EventListenerService::AddSystemEventListener(EventTarget* aTarget, - const nsAString& aType, - JS::Handle<JS::Value> aListener, - bool aUseCapture, JSContext* aCx) { - MOZ_ASSERT(aTarget, "Missing target"); - - NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); - - RefPtr<EventListener> listener = ToEventListener(aCx, aListener); - if (!listener) { - return NS_ERROR_UNEXPECTED; - } - - EventListenerManager* manager = aTarget->GetOrCreateListenerManager(); - NS_ENSURE_STATE(manager); - - EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture() - : TrustedEventsAtSystemGroupBubble(); - manager->AddEventListenerByType(listener, aType, flags); - return NS_OK; -} - -NS_IMETHODIMP -EventListenerService::RemoveSystemEventListener(EventTarget* aTarget, - const nsAString& aType, - JS::Handle<JS::Value> aListener, - bool aUseCapture, - JSContext* aCx) { - MOZ_ASSERT(aTarget, "Missing target"); - - NS_ENSURE_TRUE(aTarget, NS_ERROR_UNEXPECTED); - - RefPtr<EventListener> listener = ToEventListener(aCx, aListener); - if (!listener) { - return NS_ERROR_UNEXPECTED; - } - - EventListenerManager* manager = aTarget->GetExistingListenerManager(); - if (manager) { - EventListenerFlags flags = aUseCapture ? TrustedEventsAtSystemGroupCapture() - : TrustedEventsAtSystemGroupBubble(); - manager->RemoveEventListenerByType(listener, aType, flags); - } - - return NS_OK; -} - -NS_IMETHODIMP EventListenerService::AddListenerForAllEvents( EventTarget* aTarget, JS::Handle<JS::Value> aListener, bool aUseCapture, bool aWantsUntrusted, bool aSystemEventGroup, JSContext* aCx) { diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h index 6c9119d5f9..92c76d000e 100644 --- a/dom/events/EventNameList.h +++ b/dom/events/EventNameList.h @@ -228,6 +228,9 @@ EVENT(lostpointercapture, ePointerLostCapture, EventNameType_All, ePointerEventClass) EVENT(selectstart, eSelectStart, EventNameType_HTMLXUL, eBasicEventClass) +EVENT(contextlost, eContextLost, EventNameType_HTML, eBasicEventClass) +EVENT(contextrestored, eContextRestored, EventNameType_HTML, eBasicEventClass) + // Not supported yet; probably never because "wheel" is a better idea. // EVENT(mousewheel) EVENT(pause, ePause, EventNameType_HTML, eBasicEventClass) diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp index 6c537bfb67..ef6f2b12c0 100644 --- a/dom/events/PointerEventHandler.cpp +++ b/dom/events/PointerEventHandler.cpp @@ -610,15 +610,16 @@ EventMessage PointerEventHandler::ToPointerEventMessage( /* static */ void PointerEventHandler::DispatchPointerFromMouseOrTouch( - PresShell* aShell, nsIFrame* aFrame, nsIContent* aContent, - WidgetGUIEvent* aEvent, bool aDontRetargetEvents, nsEventStatus* aStatus, - nsIContent** aTargetContent) { - MOZ_ASSERT(aFrame || aContent); - MOZ_ASSERT(aEvent); + PresShell* aShell, nsIFrame* aEventTargetFrame, + nsIContent* aEventTargetContent, WidgetGUIEvent* aMouseOrTouchEvent, + bool aDontRetargetEvents, nsEventStatus* aStatus, + nsIContent** aMouseOrTouchEventTarget /* = nullptr */) { + MOZ_ASSERT(aEventTargetFrame || aEventTargetContent); + MOZ_ASSERT(aMouseOrTouchEvent); EventMessage pointerMessage = eVoidEvent; - if (aEvent->mClass == eMouseEventClass) { - WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (aMouseOrTouchEvent->mClass == eMouseEventClass) { + WidgetMouseEvent* mouseEvent = aMouseOrTouchEvent->AsMouseEvent(); // Don't dispatch pointer events caused by a mouse when simulating touch // devices in RDM. Document* doc = aShell->GetDocument(); @@ -636,7 +637,7 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch( // 2. We don't synthesize pointer events for those events that are not // dispatched to DOM. if (!mouseEvent->convertToPointer || - !aEvent->IsAllowedToDispatchDOMEvent()) { + !aMouseOrTouchEvent->IsAllowedToDispatchDOMEvent()) { return; } @@ -648,20 +649,20 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch( InitPointerEventFromMouse(&event, mouseEvent, pointerMessage); event.convertToPointer = mouseEvent->convertToPointer = false; RefPtr<PresShell> shell(aShell); - if (!aFrame) { - shell = PresShell::GetShellForEventTarget(nullptr, aContent); + if (!aEventTargetFrame) { + shell = PresShell::GetShellForEventTarget(nullptr, aEventTargetContent); if (!shell) { return; } } - PreHandlePointerEventsPreventDefault(&event, aEvent); + PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); // Dispatch pointer event to the same target which is found by the // corresponding mouse event. - shell->HandleEventWithTarget(&event, aFrame, aContent, aStatus, true, - aTargetContent); - PostHandlePointerEventsPreventDefault(&event, aEvent); - } else if (aEvent->mClass == eTouchEventClass) { - WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + shell->HandleEventWithTarget(&event, aEventTargetFrame, aEventTargetContent, + aStatus, true, aMouseOrTouchEventTarget); + PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); + } else if (aMouseOrTouchEvent->mClass == eTouchEventClass) { + WidgetTouchEvent* touchEvent = aMouseOrTouchEvent->AsTouchEvent(); // loop over all touches and dispatch pointer events on each touch // copy the event pointerMessage = PointerEventHandler::ToPointerEventMessage(touchEvent); @@ -681,7 +682,7 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch( InitPointerEventFromTouch(event, *touchEvent, *touch, i == 0); event.convertToPointer = touch->convertToPointer = false; event.mCoalescedWidgetEvents = touch->mCoalescedWidgetEvents; - if (aEvent->mMessage == eTouchStart) { + if (aMouseOrTouchEvent->mMessage == eTouchStart) { // We already did hit test for touchstart in PresShell. We should // dispatch pointerdown to the same target as touchstart. nsCOMPtr<nsIContent> content = @@ -696,18 +697,22 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch( continue; } - PreHandlePointerEventsPreventDefault(&event, aEvent); + PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); shell->HandleEventWithTarget(&event, frame, content, aStatus, true, - nullptr); - PostHandlePointerEventsPreventDefault(&event, aEvent); + aMouseOrTouchEventTarget); + PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); } else { // We didn't hit test for other touch events. Spec doesn't mention that // all pointer events should be dispatched to the same target as their // corresponding touch events. Call PresShell::HandleEvent so that we do // hit test for pointer events. - PreHandlePointerEventsPreventDefault(&event, aEvent); - shell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus); - PostHandlePointerEventsPreventDefault(&event, aEvent); + // FIXME: If aDontRetargetEvents is true and the event is fired on + // different document, we cannot track the pointer event target when + // it's removed from the tree. + PreHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); + shell->HandleEvent(aEventTargetFrame, &event, aDontRetargetEvents, + aStatus); + PostHandlePointerEventsPreventDefault(&event, aMouseOrTouchEvent); } } } diff --git a/dom/events/PointerEventHandler.h b/dom/events/PointerEventHandler.h index d1a5b31b5a..0211bfe0d8 100644 --- a/dom/events/PointerEventHandler.h +++ b/dom/events/PointerEventHandler.h @@ -168,11 +168,39 @@ class PointerEventHandler final { static void PostHandlePointerEventsPreventDefault( WidgetPointerEvent* aPointerEvent, WidgetGUIEvent* aMouseOrTouchEvent); - MOZ_CAN_RUN_SCRIPT - static void DispatchPointerFromMouseOrTouch( - PresShell* aShell, nsIFrame* aFrame, nsIContent* aContent, - WidgetGUIEvent* aEvent, bool aDontRetargetEvents, nsEventStatus* aStatus, - nsIContent** aTargetContent); + /** + * Dispatch a pointer event for aMouseOrTouchEvent to aEventTargetContent. + * + * @param aShell The PresShell which is handling the event. + * @param aEventTargetFrame The frame for aEventTargetContent. + * @param aEventTargetContent The event target node. + * @param aMouseOrTouchEvent A mouse or touch event. + * @param aDontRetargetEvents If true, this won't dispatch event with + * different PresShell from aShell. Otherwise, + * pointer events may be fired on different + * document if and only if aMouseOrTOuchEvent is a + * touch event except eTouchStart. + * @param aState [out] The result of the pointer event. + * @param aMouseOrTouchEventTarget + * [out] The event target for the following mouse + * or touch event. If aEventTargetContent has not + * been removed from the tree, this is always set + * to it. If aEventTargetContent is removed from + * the tree and aMouseOrTouchEvent is a mouse + * event, this is set to inclusive ancestor of + * aEventTargetContent which is still connected. + * If aEventTargetContent is removed from the tree + * and aMouseOrTouchEvent is a touch event, this is + * set to aEventTargetContent because touch event + * should be dispatched even on disconnected node. + * FIXME: If the event is a touch event but the + * message is not eTouchStart, this won't be set. + */ + MOZ_CAN_RUN_SCRIPT static void DispatchPointerFromMouseOrTouch( + PresShell* aShell, nsIFrame* aEventTargetFrame, + nsIContent* aEventTargetContent, WidgetGUIEvent* aMouseOrTouchEvent, + bool aDontRetargetEvents, nsEventStatus* aStatus, + nsIContent** aMouseOrTouchEventTarget = nullptr); static void InitPointerEventFromMouse(WidgetPointerEvent* aPointerEvent, WidgetMouseEvent* aMouseEvent, diff --git a/dom/events/TouchEvent.cpp b/dom/events/TouchEvent.cpp index 24068703be..6e50c81db8 100644 --- a/dom/events/TouchEvent.cpp +++ b/dom/events/TouchEvent.cpp @@ -233,7 +233,7 @@ bool TouchEvent::PrefEnabled(nsIDocShell* aDocShell) { // The touch screen data seems to be inaccurate in the parent process, // and we really need the crash annotation in child processes. if (firstTime && !XRE_IsParentProcess()) { - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationBool( CrashReporter::Annotation::HasDeviceTouchScreen, enabled); firstTime = false; } diff --git a/dom/events/nsIEventListenerService.idl b/dom/events/nsIEventListenerService.idl index ead3cc4af1..b8f36e5d0e 100644 --- a/dom/events/nsIEventListenerService.idl +++ b/dom/events/nsIEventListenerService.idl @@ -75,41 +75,11 @@ interface nsIEventListenerService : nsISupports Array<nsIEventListenerInfo> getListenerInfoFor(in EventTarget aEventTarget); /** - * Returns an array of event targets. - * aEventTarget will be at index 0. - * The objects are the ones that would be used as DOMEvent.currentTarget while - * dispatching an event to aEventTarget - * @note Some events, especially 'load', may actually have a shorter - * event target chain than what this methods returns. - */ - [can_run_script] - Array<EventTarget> getEventTargetChainFor(in EventTarget aEventTarget, - in boolean composed); - - /** * Returns true if a event target has any listener for the given type. */ boolean hasListenersFor(in EventTarget aEventTarget, in AString aType); - /** - * Add a system-group eventlistener to a event target. - */ - [implicit_jscontext] - void addSystemEventListener(in EventTarget target, - in AString type, - in jsval listener, - in boolean useCapture); - - /** - * Remove a system-group eventlistener from a event target. - */ - [implicit_jscontext] - void removeSystemEventListener(in EventTarget target, - in AString type, - in jsval listener, - in boolean useCapture); - [implicit_jscontext] void addListenerForAllEvents(in EventTarget target, in jsval listener, diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js b/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js index f504e499c9..97066cd2eb 100644 --- a/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js +++ b/dom/events/test/clipboard/browser_navigator_clipboard_contextmenu_suppression.js @@ -238,16 +238,10 @@ add_task(async function test_context_menu_suppression_image() { await pasteButtonIsShown; info("Test read from same-origin frame before paste contextmenu is closed"); - const clipboarCacheEnabled = SpecialPowers.getBoolPref( - "widget.clipboard.use-cached-data.enabled", - false - ); // If the cached data is used, it uses type order in cached transferable. SimpleTest.isDeeply( await readTypes(browser.browsingContext.children[0]), - clipboarCacheEnabled - ? ["text/plain", "text/html", "image/png"] - : ["text/html", "text/plain", "image/png"], + ["text/html", "text/plain", "image/png"], "read from same-origin should just be resolved without showing paste contextmenu shown" ); @@ -262,3 +256,158 @@ add_task(async function test_context_menu_suppression_image() { ); }); }); + +function testPasteContextMenuSuppressionPasteEvent( + aTriggerPasteFun, + aSuppress, + aMsg +) { + add_task(async function test_context_menu_suppression_paste_event() { + await BrowserTestUtils.withNewTab( + kContentFileUrl, + async function (browser) { + info(`Write data by in cross-origin frame`); + const clipboardText = "X" + Math.random(); + await SpecialPowers.spawn( + browser.browsingContext.children[1], + [clipboardText], + async text => { + content.document.notifyUserGestureActivation(); + return content.eval(`navigator.clipboard.writeText("${text}");`); + } + ); + + info("Test read should show contextmenu"); + let pasteButtonIsShown = waitForPasteContextMenu(); + let readTextRequest = readText(browser); + await pasteButtonIsShown; + + info("Click paste button, request should be resolved"); + await promiseClickPasteButton(); + is(await readTextRequest, clipboardText, "Request should be resolved"); + + info("Test read in paste event handler"); + readTextRequest = SpecialPowers.spawn(browser, [], async () => { + content.document.notifyUserGestureActivation(); + return content.eval(` + (() => { + return new Promise(resolve => { + document.addEventListener("paste", function(e) { + e.preventDefault(); + resolve(navigator.clipboard.readText()); + }, { once: true }); + }); + })(); + `); + }); + + if (aSuppress) { + let listener = function (e) { + if (e.target.getAttribute("id") == kPasteMenuPopupId) { + ok(!aSuppress, "paste contextmenu should not be shown"); + } + }; + document.addEventListener("popupshown", listener); + info(`Trigger paste event by ${aMsg}`); + // trigger paste event + await aTriggerPasteFun(browser); + is( + await readTextRequest, + clipboardText, + "Request should be resolved" + ); + document.removeEventListener("popupshown", listener); + } else { + let pasteButtonIsShown = waitForPasteContextMenu(); + info( + `Trigger paste event by ${aMsg}, read should still show contextmenu` + ); + // trigger paste event + await aTriggerPasteFun(browser); + await pasteButtonIsShown; + + info("Click paste button, request should be resolved"); + await promiseClickPasteButton(); + is( + await readTextRequest, + clipboardText, + "Request should be resolved" + ); + } + + info("Test read should still show contextmenu"); + pasteButtonIsShown = waitForPasteContextMenu(); + readTextRequest = readText(browser); + await pasteButtonIsShown; + + info("Click paste button, request should be resolved"); + await promiseClickPasteButton(); + is(await readTextRequest, clipboardText, "Request should be resolved"); + } + ); + }); +} + +// If platform supports selection clipboard, the middle click paste the content +// from selection clipboard instead, in such case, we don't suppress the +// contextmenu when access global clipboard via async clipboard API. +if ( + !Services.clipboard.isClipboardTypeSupported( + Services.clipboard.kSelectionClipboard + ) +) { + testPasteContextMenuSuppressionPasteEvent( + async browser => { + await SpecialPowers.pushPrefEnv({ + set: [["middlemouse.paste", true]], + }); + + await SpecialPowers.spawn(browser, [], async () => { + EventUtils.synthesizeMouse( + content.document.documentElement, + 1, + 1, + { button: 1 }, + content.window + ); + }); + }, + true, + "middle click" + ); +} + +testPasteContextMenuSuppressionPasteEvent( + async browser => { + await EventUtils.synthesizeAndWaitKey( + "v", + kIsMac ? { accelKey: true } : { ctrlKey: true } + ); + }, + true, + "keyboard shortcut" +); + +testPasteContextMenuSuppressionPasteEvent( + async browser => { + await SpecialPowers.spawn(browser, [], async () => { + return SpecialPowers.doCommand(content.window, "cmd_paste"); + }); + }, + true, + "paste command" +); + +testPasteContextMenuSuppressionPasteEvent( + async browser => { + await SpecialPowers.spawn(browser, [], async () => { + let div = content.document.createElement("div"); + div.setAttribute("contenteditable", "true"); + content.document.documentElement.appendChild(div); + div.focus(); + return SpecialPowers.doCommand(content.window, "cmd_pasteNoFormatting"); + }); + }, + false, + "pasteNoFormatting command" +); diff --git a/dom/events/test/mochitest.toml b/dom/events/test/mochitest.toml index b6b9e58368..53675a8f49 100644 --- a/dom/events/test/mochitest.toml +++ b/dom/events/test/mochitest.toml @@ -472,6 +472,9 @@ skip-if = [ "http2", ] +["test_mouse_events_after_touchend.html"] +skip-if = ["os == 'mac'"] # Bug 1881864 + ["test_mouse_over_at_removing_down_target.html"] ["test_moving_and_expanding_selection_per_page.html"] diff --git a/dom/events/test/pointerevents/mochitest_support_external.js b/dom/events/test/pointerevents/mochitest_support_external.js index 7f22166fdd..7a758a8c5f 100644 --- a/dom/events/test/pointerevents/mochitest_support_external.js +++ b/dom/events/test/pointerevents/mochitest_support_external.js @@ -2,6 +2,8 @@ // to tests on auto MochiTest system with minimum changes. // Author: Maksim Lebedev <alessarik@gmail.com> +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // Function allows to prepare our tests after load document addEventListener( "load", diff --git a/dom/events/test/pointerevents/test_bug1315862.html b/dom/events/test/pointerevents/test_bug1315862.html index 92d61a518f..657947c156 100644 --- a/dom/events/test/pointerevents/test_bug1315862.html +++ b/dom/events/test/pointerevents/test_bug1315862.html @@ -36,10 +36,10 @@ function runTests() { let target = iframe.contentDocument.body.firstChild;
allPointerEvents.forEach((event, idx, arr) => {
- SpecialPowers.addSystemEventListener(target, event, () => {
+ SpecialPowers.wrap(target).addEventListener(event, () => {
ok(false, "Shouldn't dispatch " + event + " in the system group");
receivePointerEvents = true;
- });
+ }, { mozSystemGroup: true });
});
target.addEventListener("pointerdown", (e) => {
target.setPointerCapture(e.pointerId);
diff --git a/dom/events/test/test_accesskey.html b/dom/events/test/test_accesskey.html index cdfff54a28..4d382f2270 100644 --- a/dom/events/test/test_accesskey.html +++ b/dom/events/test/test_accesskey.html @@ -143,7 +143,7 @@ add_task(async function modifyAccessKey() { add_task(async function file_picker() { const file = document.getElementById("file"); const MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); MockFilePicker.returnValue = MockFilePicker.returnCancel; let clicked = false; diff --git a/dom/events/test/test_bug226361.xhtml b/dom/events/test/test_bug226361.xhtml index 143a485757..c2c19ee2a7 100644 --- a/dom/events/test/test_bug226361.xhtml +++ b/dom/events/test/test_bug226361.xhtml @@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=226361 </p> <div id="content" style="display: none"> - + </div> <pre id="test"> <script type="application/javascript"> @@ -43,8 +43,11 @@ function tab_to(id) { } function tab_iframe() { - doc = document; - tab_to('iframe'); + let canTabMoveFocusToRootElement = !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element"); + if (canTabMoveFocusToRootElement) { + doc = document; + tab_to('iframe'); + } // inside iframe doc = document.getElementById('iframe').contentDocument diff --git a/dom/events/test/test_bug238987.html b/dom/events/test/test_bug238987.html index b2712a25d0..34f870eeac 100644 --- a/dom/events/test/test_bug238987.html +++ b/dom/events/test/test_bug238987.html @@ -22,7 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=238987 var shouldStop = false; var activateShift = false; - var expectedResult = "i1,i2,i3,i4,i5,i6,i7,i8,number,i9,i10,i11,i12"; + var expectedResult = "i1,i2,i3,i4,i5,i7,i8,number,i9,i10,i11,i12"; var forwardFocusArray = expectedResult.split(","); var backwardFocusArray = expectedResult.split(","); var forwardBlurArray = expectedResult.split(","); diff --git a/dom/events/test/test_bug336682.js b/dom/events/test/test_bug336682.js index e673f1eb99..11bd7d46bb 100644 --- a/dom/events/test/test_bug336682.js +++ b/dom/events/test/test_bug336682.js @@ -4,6 +4,8 @@ * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/licenses/publicdomain/ */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + var gState = 0; /** * After all the on/offline handlers run, diff --git a/dom/events/test/test_bug448602.html b/dom/events/test/test_bug448602.html index 18b4cb2d2f..452a361a52 100644 --- a/dom/events/test/test_bug448602.html +++ b/dom/events/test/test_bug448602.html @@ -174,28 +174,7 @@ function runTests() { is(SpecialPowers.unwrap(infos[0].listenerObject), l, "Should have the right listener object (4)"); - // Event target chain tests l3 = document.getElementById("testlevel3"); - var textnode = l3.firstChild; - var chain = els.getEventTargetChainFor(textnode, true); - ok(chain.length > 3, "Too short event target chain."); - ok(SpecialPowers.compare(chain[0], textnode), "Wrong chain item (1)"); - ok(SpecialPowers.compare(chain[1], l3), "Wrong chain item (2)"); - ok(SpecialPowers.compare(chain[2], l2), "Wrong chain item (3)"); - ok(SpecialPowers.compare(chain[3], root), "Wrong chain item (4)"); - - var hasDocumentInChain = false; - var hasWindowInChain = false; - for (var i = 0; i < chain.length; ++i) { - if (SpecialPowers.compare(chain[i], document)) { - hasDocumentInChain = true; - } else if (SpecialPowers.compare(chain[i], window)) { - hasWindowInChain = true; - } - } - - ok(hasDocumentInChain, "Should have document in event target chain!"); - ok(hasWindowInChain, "Should have window in event target chain!"); try { els.getListenerInfoFor(null); diff --git a/dom/events/test/test_mouse_events_after_touchend.html b/dom/events/test/test_mouse_events_after_touchend.html new file mode 100644 index 0000000000..146c37e489 --- /dev/null +++ b/dom/events/test/test_mouse_events_after_touchend.html @@ -0,0 +1,232 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<title>Tests for mouse events after touchend</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> +<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<style> +#parent, #child { + width: 300px; + height: 64px; + padding: 16px; +} +#parent { + background-color: black; +} +#child { + background-color: gray; +} +</style> +<script> +"use strict"; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("Required for waiting to prevent double tap at second tap"); +SimpleTest.waitForFocus(async () => { + function stringifyEvent(event) { + return `{ type: ${event.type}, target: ${ + event.target.id || event.target.nodeName + }${ + event.detail !== undefined ? `, detail: ${event.detail}` : "" + }${ + event.button !== undefined ? `, button: ${event.button}` : "" + }${ + event.buttons !== undefined ? `, buttons: ${event.buttons}` : "" + } }`; + } + function stringifyEvents(arrayOfEvents) { + if (!arrayOfEvents.length) { + return "[]"; + } + let ret = ""; + for (const event of arrayOfEvents) { + if (ret === "") { + ret = "[ "; + } else { + ret += ", "; + } + ret += stringifyEvent(event); + } + return ret + " ]"; + } + + let events = []; + for (const type of ["mousemove", + "mousedown", + "mouseup", + "click", + "dblclick", + "contextmenu", + "touchend"]) { + if (type == "touchend") { + addEventListener(type, event => { + info(`Received: ${stringifyEvent(event)}`); + events.push({type, target: event.target}); + }, {capture: true}); + } else { + addEventListener(type, event => { + info(`Received: ${stringifyEvent(event)}`); + events.push({ + type: event.type, + target: event.target, + detail: event.detail, + button: event.button, + buttons: event.buttons, + }); + }, {capture: true}); + } + } + + function shiftEventsBefore(arrayOfEvents, aType) { + const index = arrayOfEvents.findIndex(event => event.type == aType); + if (index <= 0) { + return []; + } + let ret = []; + for (let i = 0; i < index; i++) { + ret.push(arrayOfEvents.shift()); + } + return ret; + } + + const parent = document.getElementById("parent"); + const child = document.getElementById("child"); + + function promiseEvent(aType) { + return new Promise(resolve => + addEventListener(aType, resolve, {once: true}) + ); + } + + async function promiseFlushingAPZGestureState() { + await promiseApzFlushedRepaints(); + // Wait for a while to avoid that the next tap will be treated as 2nd tap of + // a double tap. + return new Promise( + resolve => setTimeout( + resolve, + // NOTE: x1.0 is not enough to avoid intermittent failures. + SpecialPowers.getIntPref("apz.max_tap_time") * 1.2 + ) + ); + } + + await waitUntilApzStable(); + for (const prefValue of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["test.events.async.enabled", prefValue], + ["ui.click_hold_context_menus.delay", 15000], // disable long tap + ] + }); + const desc = `(test.events.async.enabled=${prefValue})`; + + await (async function test_single_tap() { + await promiseFlushingAPZGestureState(); + info("test_single_tap: testing..."); + events = []; + const waitForClick = promiseEvent("click"); + synthesizeTouch(child, 5, 5); + await waitForClick; + is( + stringifyEvents(events), + stringifyEvents([ + { type: "touchend", target: child }, + { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 }, + { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 }, + { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 }, + { type: "click", target: child, detail: 1, button: 0, buttons: 0 }, + ]), + `Single tap should cause a click ${desc}` + ); + })(); + + await (async function test_single_tap_with_consuming_touchstart() { + await promiseFlushingAPZGestureState(); + info("test_single_tap_with_consuming_touchstart: testing..."); + events = []; + const waitForTouchEnd = promiseEvent("touchend"); + child.addEventListener("touchstart", event => { + event.preventDefault(); + }, {once: true}); + synthesizeTouch(child, 5, 5); + await waitForTouchEnd; + const result = stringifyEvents(events); + const expected = stringifyEvents([{ type: "touchend", target: child }]); + // If testing this with APZ, the result is really unstable. Let's allow to + // fail for now. + (prefValue && result != expected ? todo_is : is)( + result, + expected, + `Single tap should not cause mouse events if touchstart is consumed ${desc}` + ); + })(); + + + await (async function test_single_tap_with_consuming_touchend() { + await promiseFlushingAPZGestureState(); + info("test_single_tap_with_consuming_touchend: testing..."); + events = []; + const waitForTouchEnd = promiseEvent("touchend"); + child.addEventListener("touchend", event => { + event.preventDefault(); + }, {once: true}); + synthesizeTouch(child, 5, 5); + await waitForTouchEnd; + is( + stringifyEvents(shiftEventsBefore(events)), + stringifyEvents([]), + `test_single_tap_with_consuming_touchstart() shouldn't cause mouse events after touchend` + ) + is( + stringifyEvents(events), + stringifyEvents([ + { type: "touchend", target: child }, + ]), + `Single tap should not cause mouse events if touchend is consumed ${desc}` + ); + })(); + + await (async function test_multi_touch() { + await promiseFlushingAPZGestureState(); + events = []; + info("test_multi_touch: testing..."); + const waitForTouchEnd = new Promise(resolve => { + let count = 0; + function onTouchEnd(event) { + if (++count == 2) { + removeEventListener("touchend", onTouchEnd, {capture: true}); + requestAnimationFrame(() => requestAnimationFrame(resolve)); + } + } + addEventListener("touchend", onTouchEnd, {capture: true}); + }); + synthesizeTouch(child, [5, 25], 5); + await waitForTouchEnd; + is( + stringifyEvents(shiftEventsBefore(events)), + stringifyEvents([]), + `test_single_tap_with_consuming_touchend() shouldn't cause mouse events after touchend` + ) + is( + stringifyEvents(events), + stringifyEvents([ + { type: "touchend", target: child }, + { type: "touchend", target: child }, + ]), + `Multiple touch should not cause mouse events ${desc}` + ); + })(); + } + SimpleTest.finish(); +}); +</script> +</head> +<body><div id="parent"><div id="child"></div></div></body> +</html> diff --git a/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html b/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html index 3898584b0a..43d8081606 100644 --- a/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html +++ b/dom/events/test/test_use_split_keypress_event_model_on_old_Confluence.html @@ -39,7 +39,7 @@ SimpleTest.waitForExplicitFinish(); SimpleTest.waitForFocus(async function doTests() { let iframe = document.getElementById("iframe"); let waitForCheckKeyPressEventModelEvent = new Promise(resolve => { - SpecialPowers.addSystemEventListener(iframe.contentDocument, "CheckKeyPressEventModel", resolve, {once: true}); + SpecialPowers.wrap(iframe.contentDocument).addEventListener("CheckKeyPressEventModel", resolve, {mozSystemGroup: true, once: true}); }); iframe.contentDocument.body.setAttribute("contenteditable", "true"); await waitForCheckKeyPressEventModelEvent; diff --git a/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html b/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html index 68f52be41d..02047fef90 100644 --- a/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html +++ b/dom/events/test/test_use_split_keypress_event_model_on_old_Office_Online_Server.html @@ -23,7 +23,7 @@ function srcdocLoaded() { waitForCheckKeyPressEventModelEvent = new Promise(resolve => { dump(document.querySelector("iframe").contentDocument.location + "\n"); var doc = document.querySelector("iframe").contentDocument; - SpecialPowers.addSystemEventListener(doc, "CheckKeyPressEventModel", resolve, {once: true}); + SpecialPowers.wrap(doc).addEventListener("CheckKeyPressEventModel", resolve, {mozSystemGroup: true, once: true}); doc.getElementById("WACViewPanel_EditingElement").contentEditable = "true"; }); } diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 77fe5e59bb..c4acc35cf5 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -9,9 +9,11 @@ #include "mozilla/TaskQueue.h" #include "mozilla/dom/FetchDriver.h" +#include "mozilla/dom/FetchPriority.h" #include "mozilla/dom/ReferrerInfo.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "mozilla/dom/Document.h" +#include "nsIBaseChannel.h" #include "nsICookieJarSettings.h" #include "nsIFile.h" #include "nsIInputStream.h" @@ -27,7 +29,6 @@ #include "nsIPipe.h" #include "nsIRedirectHistoryEntry.h" -#include "nsBaseChannel.h" #include "nsContentPolicyUtils.h" #include "nsDataChannel.h" #include "nsDataHandler.h" @@ -46,6 +47,7 @@ #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/PreloaderBase.h" +#include "mozilla/net/ContentRange.h" #include "mozilla/net/InterceptionInfo.h" #include "mozilla/net/NeckoChannelParams.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" @@ -840,11 +842,20 @@ nsresult FetchDriver::HttpFetch( nsIClassOfService::Tail); } - if (mIsTrackingFetch && - StaticPrefs::privacy_trackingprotection_lower_network_priority()) { - nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan); - if (p) { + if (nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan)) { + if (mIsTrackingFetch && + StaticPrefs::privacy_trackingprotection_lower_network_priority()) { p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); + } else if (StaticPrefs::network_fetchpriority_enabled()) { + // TODO: Bug 1881040 - we need to take into account of destination for the + // fetchpriority mapping. + const auto fetchPriority = ToFetchPriority(mRequest->GetPriorityMode()); + // The spec defines the priority to be set in an implementation defined + // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15. + // See corresponding preferences in StaticPrefList.yaml for more context. + const int32_t supportsPriorityDelta = + FETCH_PRIORITY_ADJUSTMENT_FOR(global_fetch_api, fetchPriority); + p->AdjustPriority(supportsPriorityDelta); } } @@ -1028,7 +1039,7 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest) { bool foundOpaqueRedirect = false; - nsAutoCString contentType; + nsAutoCString contentType(VoidCString()); int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE; rv = channel->GetContentLength(&contentLength); @@ -1089,13 +1100,10 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest) { // Should set a Content-Range header for blob scheme // (https://fetch.spec.whatwg.org/#scheme-fetch) nsAutoCString contentRange(VoidCString()); - nsCOMPtr<nsIURI> uri; - channel->GetURI(getter_AddRefs(uri)); - if (IsBlobURI(uri)) { - nsBaseChannel* bchan = static_cast<nsBaseChannel*>(channel.get()); - MOZ_ASSERT(bchan); - Maybe<nsBaseChannel::ContentRange> range = bchan->GetContentRange(); - if (range.isSome()) { + nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel); + if (baseChan) { + RefPtr<mozilla::net::ContentRange> range = baseChan->ContentRange(); + if (range) { range->AsHeader(contentRange); } } @@ -1111,11 +1119,13 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest) { MOZ_ASSERT(!result.Failed()); } - if (uri && uri->SchemeIs("data")) { - nsDataChannel* dchan = static_cast<nsDataChannel*>(channel.get()); - MOZ_ASSERT(dchan); - contentType.Assign(dchan->MimeType()); - } else { + if (baseChan) { + RefPtr<CMimeType> fullMimeType(baseChan->FullMimeType()); + if (fullMimeType) { + fullMimeType->Serialize(contentType); + } + } + if (contentType.IsVoid()) { channel->GetContentType(contentType); if (!contentType.IsEmpty()) { nsAutoCString contentCharset; diff --git a/dom/fetch/FetchIPCTypes.h b/dom/fetch/FetchIPCTypes.h index 343a5acb3f..66d183ec37 100644 --- a/dom/fetch/FetchIPCTypes.h +++ b/dom/fetch/FetchIPCTypes.h @@ -9,6 +9,7 @@ #include "ipc/EnumSerializer.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/HeadersBinding.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" @@ -17,41 +18,30 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::HeadersGuardEnum> - : public ContiguousEnumSerializer< - mozilla::dom::HeadersGuardEnum, mozilla::dom::HeadersGuardEnum::None, - mozilla::dom::HeadersGuardEnum::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::HeadersGuardEnum> {}; template <> struct ParamTraits<mozilla::dom::ReferrerPolicy> - : public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy, - mozilla::dom::ReferrerPolicy::_empty, - mozilla::dom::ReferrerPolicy::EndGuard_> { + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::ReferrerPolicy> { }; template <> struct ParamTraits<mozilla::dom::RequestMode> - : public ContiguousEnumSerializer<mozilla::dom::RequestMode, - mozilla::dom::RequestMode::Same_origin, - mozilla::dom::RequestMode::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RequestMode> {}; template <> struct ParamTraits<mozilla::dom::RequestCredentials> - : public ContiguousEnumSerializer< - mozilla::dom::RequestCredentials, - mozilla::dom::RequestCredentials::Omit, - mozilla::dom::RequestCredentials::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RequestCredentials> {}; template <> struct ParamTraits<mozilla::dom::RequestCache> - : public ContiguousEnumSerializer<mozilla::dom::RequestCache, - mozilla::dom::RequestCache::Default, - mozilla::dom::RequestCache::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RequestCache> {}; template <> struct ParamTraits<mozilla::dom::RequestRedirect> - : public ContiguousEnumSerializer< - mozilla::dom::RequestRedirect, mozilla::dom::RequestRedirect::Follow, - mozilla::dom::RequestRedirect::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RequestRedirect> { +}; template <> struct ParamTraits<mozilla::dom::ResponseType> - : public ContiguousEnumSerializer<mozilla::dom::ResponseType, - mozilla::dom::ResponseType::Basic, - mozilla::dom::ResponseType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::ResponseType> {}; + template <> struct ParamTraits<mozilla::dom::FetchDriverObserver::EndReason> : public ContiguousEnumSerializerInclusive< diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 41797d3e82..b5cc7cf291 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -49,6 +49,7 @@ SafeRefPtr<InternalRequest> InternalRequest::GetRequestConstructorCopy( copy->mCredentialsMode = mCredentialsMode; copy->mCacheMode = mCacheMode; copy->mRedirectMode = mRedirectMode; + copy->mPriorityMode = mPriorityMode; copy->mContentPolicyTypeOverridden = mContentPolicyTypeOverridden; copy->mPreferredAlternativeDataType = mPreferredAlternativeDataType; @@ -91,7 +92,8 @@ InternalRequest::InternalRequest(const nsACString& aURL, mMode(RequestMode::No_cors), mCredentialsMode(RequestCredentials::Omit), mCacheMode(RequestCache::Default), - mRedirectMode(RequestRedirect::Follow) { + mRedirectMode(RequestRedirect::Follow), + mPriorityMode(RequestPriority::Auto) { MOZ_ASSERT(!aURL.IsEmpty()); AddURL(aURL, aFragment); } @@ -101,7 +103,8 @@ InternalRequest::InternalRequest( RequestCache aCacheMode, RequestMode aMode, RequestRedirect aRequestRedirect, RequestCredentials aRequestCredentials, const nsAString& aReferrer, ReferrerPolicy aReferrerPolicy, - nsContentPolicyType aContentPolicyType, const nsAString& aIntegrity) + RequestPriority aPriority, nsContentPolicyType aContentPolicyType, + const nsAString& aIntegrity) : mMethod(aMethod), mHeaders(aHeaders), mBodyLength(InternalResponse::UNKNOWN_BODY_SIZE), @@ -113,6 +116,7 @@ InternalRequest::InternalRequest( mCredentialsMode(aRequestCredentials), mCacheMode(aCacheMode), mRedirectMode(aRequestRedirect), + mPriorityMode(aPriority), mIntegrity(aIntegrity) { MOZ_ASSERT(!aURL.IsEmpty()); AddURL(aURL, aFragment); @@ -132,6 +136,7 @@ InternalRequest::InternalRequest(const InternalRequest& aOther, mResponseTainting(aOther.mResponseTainting), mCacheMode(aOther.mCacheMode), mRedirectMode(aOther.mRedirectMode), + mPriorityMode(aOther.mPriorityMode), mIntegrity(aOther.mIntegrity), mMozErrors(aOther.mMozErrors), mFragment(aOther.mFragment), diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index 3dfbb9284d..d2b7721f5e 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -93,6 +93,7 @@ class InternalRequest final : public AtomicSafeRefCounted<InternalRequest> { RequestRedirect aRequestRedirect, RequestCredentials aRequestCredentials, const nsAString& aReferrer, ReferrerPolicy aReferrerPolicy, + RequestPriority aPriority, nsContentPolicyType aContentPolicyType, const nsAString& aIntegrity); @@ -250,6 +251,12 @@ class InternalRequest final : public AtomicSafeRefCounted<InternalRequest> { mRedirectMode = aRedirectMode; } + RequestPriority GetPriorityMode() const { return mPriorityMode; } + + void SetPriorityMode(RequestPriority aPriorityMode) { + mPriorityMode = aPriorityMode; + } + const nsString& GetIntegrity() const { return mIntegrity; } void SetIntegrity(const nsAString& aIntegrity) { @@ -445,6 +452,7 @@ class InternalRequest final : public AtomicSafeRefCounted<InternalRequest> { LoadTainting mResponseTainting = LoadTainting::Basic; RequestCache mCacheMode; RequestRedirect mRedirectMode; + RequestPriority mPriorityMode = RequestPriority::Auto; nsString mIntegrity; bool mMozErrors = false; nsCString mFragment; diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 8108d2522b..e042dd6271 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -329,27 +329,37 @@ SafeRefPtr<Request> Request::Constructor(nsIGlobalObject* aGlobal, if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - RequestMode fallbackMode = RequestMode::EndGuard_; - RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_; - RequestCache fallbackCache = RequestCache::EndGuard_; + Maybe<RequestMode> mode; + if (aInit.mMode.WasPassed()) { + if (aInit.mMode.Value() == RequestMode::Navigate) { + aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>("navigate"); + return nullptr; + } + + mode.emplace(aInit.mMode.Value()); + } + Maybe<RequestCredentials> credentials; + if (aInit.mCredentials.WasPassed()) { + credentials.emplace(aInit.mCredentials.Value()); + } + Maybe<RequestCache> cache; + if (aInit.mCache.WasPassed()) { + cache.emplace(aInit.mCache.Value()); + } if (aInput.IsUSVString()) { - fallbackMode = RequestMode::Cors; - fallbackCredentials = RequestCredentials::Same_origin; - fallbackCache = RequestCache::Default; + if (mode.isNothing()) { + mode.emplace(RequestMode::Cors); + } + if (credentials.isNothing()) { + credentials.emplace(RequestCredentials::Same_origin); + } + if (cache.isNothing()) { + cache.emplace(RequestCache::Default); + } } - RequestMode mode = - aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode; - RequestCredentials credentials = aInit.mCredentials.WasPassed() - ? aInit.mCredentials.Value() - : fallbackCredentials; - - if (mode == RequestMode::Navigate) { - aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>("navigate"); - return nullptr; - } if (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate) { - mode = RequestMode::Same_origin; + mode = Some(RequestMode::Same_origin); } if (aInit.IsAnyMemberPresent()) { @@ -430,6 +440,13 @@ SafeRefPtr<Request> Request::Constructor(nsIGlobalObject* aGlobal, signal = aInit.mSignal.Value(); } + // https://fetch.spec.whatwg.org/#dom-global-fetch + // https://fetch.spec.whatwg.org/#dom-request + // The priority of init overrides input's priority. + if (aInit.mPriority.WasPassed()) { + request->SetPriorityMode(aInit.mPriority.Value()); + } + UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo; nsILoadInfo::CrossOriginEmbedderPolicy coep = nsILoadInfo::EMBEDDER_POLICY_NULL; @@ -473,24 +490,22 @@ SafeRefPtr<Request> Request::Constructor(nsIGlobalObject* aGlobal, request->SetPrincipalInfo(std::move(principalInfo)); request->SetEmbedderPolicy(coep); - if (mode != RequestMode::EndGuard_) { - request->SetMode(mode); + if (mode.isSome()) { + request->SetMode(mode.value()); } - if (credentials != RequestCredentials::EndGuard_) { - request->SetCredentialsMode(credentials); + if (credentials.isSome()) { + request->SetCredentialsMode(credentials.value()); } - RequestCache cache = - aInit.mCache.WasPassed() ? aInit.mCache.Value() : fallbackCache; - if (cache != RequestCache::EndGuard_) { - if (cache == RequestCache::Only_if_cached && + if (cache.isSome()) { + if (cache.value() == RequestCache::Only_if_cached && request->Mode() != RequestMode::Same_origin) { - nsCString modeString(RequestModeValues::GetString(request->Mode())); - aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>(modeString); + aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>( + GetEnumString(request->Mode())); return nullptr; } - request->SetCacheMode(cache); + request->SetCacheMode(cache.value()); } if (aInit.mRedirect.WasPassed()) { diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index 0086e076de..ee8deffe5a 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -53,6 +53,8 @@ class Request final : public FetchBody<Request>, public nsWrapperCache { RequestRedirect Redirect() const { return mRequest->GetRedirectMode(); } + RequestPriority Priority() const { return mRequest->GetPriorityMode(); } + void GetIntegrity(nsAString& aIntegrity) const { aIntegrity = mRequest->GetIntegrity(); } diff --git a/dom/file/ipc/tests/script_file.js b/dom/file/ipc/tests/script_file.js index 03cefeb3f1..331fd4a9c5 100644 --- a/dom/file/ipc/tests/script_file.js +++ b/dom/file/ipc/tests/script_file.js @@ -3,7 +3,7 @@ // eslint-disable-next-line mozilla/reject-importGlobalProperties Cu.importGlobalProperties(["File"]); -addMessageListener("file.open", function (e) { +addMessageListener("file.open", function () { var testFile = Services.dirsvc .QueryInterface(Ci.nsIProperties) .get("ProfD", Ci.nsIFile); @@ -29,7 +29,7 @@ addMessageListener("file.open", function (e) { }); }); -addMessageListener("emptyfile.open", function (e) { +addMessageListener("emptyfile.open", function () { var testFile = Services.dirsvc .QueryInterface(Ci.nsIProperties) .get("ProfD", Ci.nsIFile); diff --git a/dom/file/tests/common_blob.js b/dom/file/tests/common_blob.js index 261909af0d..39ece679e0 100644 --- a/dom/file/tests/common_blob.js +++ b/dom/file/tests/common_blob.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + const RANGE_1 = 1; const RANGE_2 = 2; diff --git a/dom/file/tests/worker_blob_reading.js b/dom/file/tests/worker_blob_reading.js index f57161c220..8ad421062f 100644 --- a/dom/file/tests/worker_blob_reading.js +++ b/dom/file/tests/worker_blob_reading.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + importScripts("common_blob_reading.js"); function info(message) { diff --git a/dom/file/tests/worker_fileReader.js b/dom/file/tests/worker_fileReader.js index 2e8408d1bb..fe7c49e64f 100644 --- a/dom/file/tests/worker_fileReader.js +++ b/dom/file/tests/worker_fileReader.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + importScripts("common_fileReader.js"); function ok(a, msg) { diff --git a/dom/file/uri/BlobURLInputStream.cpp b/dom/file/uri/BlobURLInputStream.cpp index 34a1bb63e8..41259baa82 100644 --- a/dom/file/uri/BlobURLInputStream.cpp +++ b/dom/file/uri/BlobURLInputStream.cpp @@ -12,6 +12,7 @@ #include "mozilla/ScopeExit.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/net/ContentRange.h" #include "nsStreamUtils.h" #include "nsMimeTypes.h" @@ -488,9 +489,9 @@ nsresult BlobURLInputStream::StoreBlobImplStream( // If a Range header was in the request then fetch/XHR will have set a // ContentRange on the channel earlier so we may slice the blob now. blobImpl->GetType(blobContentType); - const Maybe<nsBaseChannel::ContentRange>& contentRange = - mChannel->GetContentRange(); - if (contentRange.isSome()) { + const RefPtr<mozilla::net::ContentRange>& contentRange = + mChannel->ContentRange(); + if (contentRange) { IgnoredErrorResult result; uint64_t start = contentRange->Start(); uint64_t end = contentRange->End(); diff --git a/dom/file/uri/BlobURLProtocolHandler.cpp b/dom/file/uri/BlobURLProtocolHandler.cpp index 3e2d8e788e..978e88cc8b 100644 --- a/dom/file/uri/BlobURLProtocolHandler.cpp +++ b/dom/file/uri/BlobURLProtocolHandler.cpp @@ -948,7 +948,7 @@ nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec, } // Blob requests may specify a range header. We parse, validate, and -// store that info here, and save it on the nsBaseChannel, where it +// store that info here, and save it on the nsIBaseChannel, where it // can be accessed by BlobURLInputStream::StoreBlobImplStream. nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel, nsIURI* aURI, nsACString& aRangeHeader) { @@ -963,9 +963,8 @@ nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel, nsIURI* aURI, if (result.Failed()) { return NS_ERROR_NO_CONTENT; } - nsBaseChannel* bchan = static_cast<nsBaseChannel*>(aChannel); - MOZ_ASSERT(bchan); - if (!bchan->SetContentRange(aRangeHeader, size)) { + nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(aChannel); + if (!baseChan || !baseChan->SetContentRangeFromHeader(aRangeHeader, size)) { return NS_ERROR_NET_PARTIAL_TRANSFER; } return NS_OK; diff --git a/dom/filesystem/compat/tests/script_entries.js b/dom/filesystem/compat/tests/script_entries.js index 7f52fe6bf2..74f3ee18bc 100644 --- a/dom/filesystem/compat/tests/script_entries.js +++ b/dom/filesystem/compat/tests/script_entries.js @@ -3,7 +3,7 @@ Cu.importGlobalProperties(["File", "Directory"]); var tmpFile, tmpDir; -addMessageListener("entries.open", function (e) { +addMessageListener("entries.open", function () { tmpFile = Services.dirsvc .QueryInterface(Ci.nsIProperties) .get("TmpD", Ci.nsIFile); @@ -40,7 +40,7 @@ addMessageListener("entries.open", function (e) { }); }); -addMessageListener("entries.delete", function (e) { +addMessageListener("entries.delete", function () { tmpFile.remove(true); tmpDir.remove(true); sendAsyncMessage("entries.deleted"); diff --git a/dom/filesystem/compat/tests/test_basic.html b/dom/filesystem/compat/tests/test_basic.html index 4ad0c37d67..552827eb4f 100644 --- a/dom/filesystem/compat/tests/test_basic.html +++ b/dom/filesystem/compat/tests/test_basic.html @@ -168,7 +168,7 @@ function test_directoryEntry_getFile_simple() { function(e) { is(e.name, "foo.txt", "We have the right FileEntry."); test_getParent(e, directoryEntry, /* nested */ false); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -178,7 +178,7 @@ function test_directoryEntry_getFile_deep() { function(e) { is(e.name, "bar..txt", "We have the right FileEntry."); test_getParent(e, directoryEntry, /* nested */ true); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -228,7 +228,7 @@ function test_directoryEntry_getDirectory_simple() { function(e) { is(e.name, "subdir", "We have the right DirectoryEntry."); test_getParent(e, directoryEntry, /* nested */ false); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -238,7 +238,7 @@ function test_directoryEntry_getDirectory_deep() { function(e) { is(e.name, "subsubdir", "We have the right DirectoryEntry."); test_getParent(e, directoryEntry, /* nested */ true); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -308,7 +308,7 @@ function test_root_getFile_simple() { function(e) { is(e.name, fileEntry.name, "We have the right FileEntry."); next(); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -318,7 +318,7 @@ function test_root_getFile_deep() { function(e) { is(e.name, "bar..txt", "We have the right FileEntry."); next(); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -368,7 +368,7 @@ function test_root_getDirectory_simple() { function(e) { is(e.name, directoryEntry.name, "We have the right DirectoryEntry."); next(); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -378,7 +378,7 @@ function test_root_getDirectory_deep() { function(e) { is(e.name, "subsubdir", "We have the right DirectoryEntry."); next(); - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } @@ -402,7 +402,7 @@ function test_getParent(entry, parentEntry, nested) { } else { test_getParent(e, parentEntry, false); } - }, function(e) { + }, function() { ok(false, "This should not happen."); }); } diff --git a/dom/filesystem/tests/script_fileList.js b/dom/filesystem/tests/script_fileList.js index 47438aa7b3..2730a96261 100644 --- a/dom/filesystem/tests/script_fileList.js +++ b/dom/filesystem/tests/script_fileList.js @@ -153,7 +153,7 @@ addMessageListener("dir.open", function (e) { }); }); -addMessageListener("file.open", function (e) { +addMessageListener("file.open", function () { var testFile = Services.dirsvc .QueryInterface(Ci.nsIProperties) .get("ProfD", Ci.nsIFile); @@ -164,7 +164,7 @@ addMessageListener("file.open", function (e) { }); }); -addMessageListener("symlink.open", function (e) { +addMessageListener("symlink.open", function () { let testDir = createTestFile(); let testFile = testDir.clone(); testFile.append("subdir"); diff --git a/dom/filesystem/tests/script_promptHandler.js b/dom/filesystem/tests/script_promptHandler.js index ed3bda7404..235842b455 100644 --- a/dom/filesystem/tests/script_promptHandler.js +++ b/dom/filesystem/tests/script_promptHandler.js @@ -2,17 +2,17 @@ let dialogObserverTopic = "common-dialog-loaded"; -function dialogObserver(subj, topic, data) { +function dialogObserver(subj) { subj.document.querySelector("dialog").acceptDialog(); sendAsyncMessage("promptAccepted"); } -addMessageListener("init", message => { +addMessageListener("init", () => { Services.obs.addObserver(dialogObserver, dialogObserverTopic); sendAsyncMessage("initDone"); }); -addMessageListener("cleanup", message => { +addMessageListener("cleanup", () => { Services.obs.removeObserver(dialogObserver, dialogObserverTopic); sendAsyncMessage("cleanupDone"); }); diff --git a/dom/filesystem/tests/test_basic.html b/dom/filesystem/tests/test_basic.html index b4daea79c9..a0464f631e 100644 --- a/dom/filesystem/tests/test_basic.html +++ b/dom/filesystem/tests/test_basic.html @@ -40,7 +40,7 @@ function create_fileList(aPath) { script.sendAsyncMessage("dir.open", { path: aPath }); } -function test_simpleFilePicker(aPath) { +function test_simpleFilePicker() { var url = SimpleTest.getTestFileURL("script_fileList.js"); var script = SpecialPowers.loadChromeScript(url); diff --git a/dom/filesystem/tests/test_webkitdirectory.html b/dom/filesystem/tests/test_webkitdirectory.html index 50080ad7ba..982887cbcf 100644 --- a/dom/filesystem/tests/test_webkitdirectory.html +++ b/dom/filesystem/tests/test_webkitdirectory.html @@ -48,7 +48,7 @@ function populateInputFile(aInputFile) { var script = SpecialPowers.loadChromeScript(url); var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); async function onOpened(message) { MockFilePicker.useDirectory(message.dir); @@ -134,7 +134,7 @@ async function test_individualSymlink(aInputFile) { // -- Have the picker pick it var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen); MockFilePicker.displayDirectory = dir; let pickerShown = new Promise(resolve => { @@ -186,7 +186,7 @@ function test_changeDataWhileWorking() { var script = SpecialPowers.loadChromeScript(url); var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); let promptHandled; // Let's start retrieving the root nsIFile object @@ -203,7 +203,7 @@ function test_changeDataWhileWorking() { // input.click() pointing to the root dir .then(async function(aDir) { MockFilePicker.cleanup(); - MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); MockFilePicker.useDirectory(aDir); var input = document.getElementById("inputFileDirectoryChange"); @@ -228,7 +228,7 @@ function test_changeDataWhileWorking() { // Now let's click again and wait for onchange. .then(async function(aDir) { MockFilePicker.cleanup(); - MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeGetFolder); MockFilePicker.useDirectory(aDir); let input = document.getElementById("inputFileDirectoryChange"); diff --git a/dom/fs/api/FileSystemDirectoryHandle.cpp b/dom/fs/api/FileSystemDirectoryHandle.cpp index 3529f29e78..bdc8185e3a 100644 --- a/dom/fs/api/FileSystemDirectoryHandle.cpp +++ b/dom/fs/api/FileSystemDirectoryHandle.cpp @@ -6,6 +6,8 @@ #include "FileSystemDirectoryHandle.h" +#include <cstdint> + #include "FileSystemDirectoryIteratorFactory.h" #include "fs/FileSystemRequestHandler.h" #include "js/StructuredClone.h" @@ -153,7 +155,7 @@ already_AddRefed<FileSystemDirectoryHandle> FileSystemDirectoryHandle::ReadStructuredClone( JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader) { - uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + uint32_t kind = UINT32_MAX; if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), sizeof(uint32_t))) { diff --git a/dom/fs/api/FileSystemFileHandle.cpp b/dom/fs/api/FileSystemFileHandle.cpp index 4d8306857f..83d2933af5 100644 --- a/dom/fs/api/FileSystemFileHandle.cpp +++ b/dom/fs/api/FileSystemFileHandle.cpp @@ -99,7 +99,7 @@ already_AddRefed<FileSystemFileHandle> FileSystemFileHandle::ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader) { - uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + uint32_t kind = UINT32_MAX; if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), sizeof(uint32_t))) { diff --git a/dom/fs/api/FileSystemHandle.cpp b/dom/fs/api/FileSystemHandle.cpp index d0accdea00..79151920c0 100644 --- a/dom/fs/api/FileSystemHandle.cpp +++ b/dom/fs/api/FileSystemHandle.cpp @@ -210,7 +210,7 @@ already_AddRefed<FileSystemHandle> FileSystemHandle::ReadStructuredClone( JSStructuredCloneReader* aReader) { LOG_VERBOSE(("Reading File/DirectoryHandle")); - uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + uint32_t kind = UINT32_MAX; if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), sizeof(uint32_t))) { diff --git a/dom/fs/child/FileSystemWritableFileStreamChild.h b/dom/fs/child/FileSystemWritableFileStreamChild.h index 9728fc4c78..ec4ce4fdad 100644 --- a/dom/fs/child/FileSystemWritableFileStreamChild.h +++ b/dom/fs/child/FileSystemWritableFileStreamChild.h @@ -21,7 +21,6 @@ class FileSystemWritableFileStreamChild NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStreamChild, override) FileSystemWritableFileStream* MutableWritableFileStreamPtr() const { - MOZ_ASSERT(mStream); return mStream; } diff --git a/dom/gamepad/fallback/FallbackGamepad.cpp b/dom/gamepad/fallback/FallbackGamepad.cpp index 4b37732f51..a69ed8e1d6 100644 --- a/dom/gamepad/fallback/FallbackGamepad.cpp +++ b/dom/gamepad/fallback/FallbackGamepad.cpp @@ -6,6 +6,7 @@ #include <stdint.h> #include "mozilla/dom/GamepadHandle.h" +#include "mozilla/Tainting.h" namespace mozilla { namespace dom { diff --git a/dom/gamepad/ipc/GamepadMessageUtils.h b/dom/gamepad/ipc/GamepadMessageUtils.h index c29293d3a5..53b3e5f4c9 100644 --- a/dom/gamepad/ipc/GamepadMessageUtils.h +++ b/dom/gamepad/ipc/GamepadMessageUtils.h @@ -8,6 +8,7 @@ #define mozilla_dom_gamepad_GamepadMessageUtils_h #include "ipc/EnumSerializer.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/GamepadBinding.h" #include "mozilla/dom/GamepadHandle.h" #include "mozilla/dom/GamepadLightIndicatorBinding.h" @@ -18,24 +19,17 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::GamepadLightIndicatorType> - : public ContiguousEnumSerializer< - mozilla::dom::GamepadLightIndicatorType, - mozilla::dom::GamepadLightIndicatorType(0), - mozilla::dom::GamepadLightIndicatorType( - mozilla::dom::GamepadLightIndicatorType::EndGuard_)> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::GamepadLightIndicatorType> {}; template <> struct ParamTraits<mozilla::dom::GamepadMappingType> - : public ContiguousEnumSerializer< - mozilla::dom::GamepadMappingType, mozilla::dom::GamepadMappingType(0), - mozilla::dom::GamepadMappingType( - mozilla::dom::GamepadMappingType::EndGuard_)> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::GamepadMappingType> {}; template <> struct ParamTraits<mozilla::dom::GamepadHand> - : public ContiguousEnumSerializer< - mozilla::dom::GamepadHand, mozilla::dom::GamepadHand(0), - mozilla::dom::GamepadHand(mozilla::dom::GamepadHand::EndGuard_)> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::GamepadHand> {}; template <> struct ParamTraits<mozilla::dom::GamepadCapabilityFlags> diff --git a/dom/html/ElementInternals.cpp b/dom/html/ElementInternals.cpp index aaf58f818e..9a19744603 100644 --- a/dom/html/ElementInternals.cpp +++ b/dom/html/ElementInternals.cpp @@ -22,6 +22,10 @@ #include "nsDebug.h" #include "nsGenericHTMLElement.h" +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif + namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals) @@ -482,4 +486,44 @@ void ElementInternals::InitializeControlNumber() { mControlNumber = mTarget->OwnerDoc()->GetNextControlNumber(); } +void ElementInternals::SetAttrElement(nsAtom* aAttr, Element* aElement) { + // Accessibility requires that no other attribute changes occur between + // AttrElementWillChange and AttrElementChanged. Scripts could cause + // this, so don't let them run here. We do this even if accessibility isn't + // running so that the JS behavior is consistent regardless of accessibility. + // Otherwise, JS might be able to use this difference to determine whether + // accessibility is running, which would be a privacy concern. + nsAutoScriptBlocker scriptBlocker; + +#ifdef ACCESSIBILITY + // If the target has this attribute defined then it overrides the defaults + // defined here in the Internals instance. In that case we don't need to + // notify the change to a11y since the attribute hasn't changed, just the + // underlying default. We can set accService to null and not notify. + nsAccessibilityService* accService = + !mTarget->HasAttr(aAttr) ? GetAccService() : nullptr; + if (accService) { + accService->NotifyAttrElementWillChange(mTarget, aAttr); + } +#endif + + if (aElement) { + mAttrElements.InsertOrUpdate(aAttr, do_GetWeakReference(aElement)); + } else { + mAttrElements.Remove(aAttr); + } + +#ifdef ACCESSIBILITY + if (accService) { + accService->NotifyAttrElementChanged(mTarget, aAttr); + } +#endif +} + +Element* ElementInternals::GetAttrElement(nsAtom* aAttr) const { + nsWeakPtr weakAttrEl = mAttrElements.Get(aAttr); + nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl); + return attrEl; +} + } // namespace mozilla::dom diff --git a/dom/html/ElementInternals.h b/dom/html/ElementInternals.h index 7ced3b9771..4bbf5c96f3 100644 --- a/dom/html/ElementInternals.h +++ b/dom/html/ElementInternals.h @@ -27,6 +27,13 @@ aResult = ErrorResult(SetAttr(nsGkAtoms::attr, aValue)); \ } +#define ARIA_REFLECT_ATTR_ELEMENT(method, attr) \ + Element* Get##method() const { return GetAttrElement(nsGkAtoms::attr); } \ + \ + void Set##method(Element* aElement) { \ + SetAttrElement(nsGkAtoms::attr, aElement); \ + } + class nsINodeList; class nsGenericHTMLElement; @@ -119,9 +126,12 @@ class ElementInternals final : public nsIFormControl, ARIA_REFLECT_ATTR(Role, role) // AriaAttributes + ARIA_REFLECT_ATTR_ELEMENT(AriaActiveDescendantElement, aria_activedescendant) ARIA_REFLECT_ATTR(AriaAtomic, aria_atomic) ARIA_REFLECT_ATTR(AriaAutoComplete, aria_autocomplete) ARIA_REFLECT_ATTR(AriaBusy, aria_busy) + ARIA_REFLECT_ATTR(AriaBrailleLabel, aria_braillelabel) + ARIA_REFLECT_ATTR(AriaBrailleRoleDescription, aria_brailleroledescription) ARIA_REFLECT_ATTR(AriaChecked, aria_checked) ARIA_REFLECT_ATTR(AriaColCount, aria_colcount) ARIA_REFLECT_ATTR(AriaColIndex, aria_colindex) @@ -172,6 +182,18 @@ class ElementInternals final : public nsIFormControl, private: ~ElementInternals() = default; + /** + * Gets the attribute element for the given attribute. + * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element + */ + Element* GetAttrElement(nsAtom* aAttr) const; + + /** + * Sets an attribute element for the given attribute. + * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element + */ + void SetAttrElement(nsAtom* aAttr, Element* aElement); + // It's a target element which is a custom element. RefPtr<HTMLElement> mTarget; @@ -211,6 +233,12 @@ class ElementInternals final : public nsIFormControl, // owner document. This is only set to a number for elements inserted into the // document by the parser from the network. Otherwise, it is -1. int32_t mControlNumber; + + /** + * Explicitly set attr-elements, see + * https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element + */ + nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mAttrElements; }; } // namespace mozilla::dom diff --git a/dom/html/FetchPriority.h b/dom/html/FetchPriority.h index 5719577771..525f0ffb17 100644 --- a/dom/html/FetchPriority.h +++ b/dom/html/FetchPriority.h @@ -21,6 +21,17 @@ enum class FetchPriority : uint8_t { High, Low, Auto }; FetchPriority ToFetchPriority(RequestPriority aRequestPriority); +#define FETCH_PRIORITY_ADJUSTMENT_FOR(feature, fetchPriority) \ + (fetchPriority == FetchPriority::Auto \ + ? StaticPrefs::network_fetchpriority_adjustments_##feature##_auto() \ + : (fetchPriority == FetchPriority::High \ + ? StaticPrefs:: \ + network_fetchpriority_adjustments_##feature##_high() \ + : (fetchPriority == FetchPriority::Low \ + ? StaticPrefs:: \ + network_fetchpriority_adjustments_##feature##_low() \ + : 0))) + // @param aSupportsPriority see <nsISupportsPriority.idl>. void LogPriorityMapping(LazyLogModule& aLazyLogModule, FetchPriority aFetchPriority, diff --git a/dom/html/HTMLAnchorElement.cpp b/dom/html/HTMLAnchorElement.cpp index a5d958139e..fffe8d9545 100644 --- a/dom/html/HTMLAnchorElement.cpp +++ b/dom/html/HTMLAnchorElement.cpp @@ -75,14 +75,14 @@ nsresult HTMLAnchorElement::BindToTree(BindContext& aContext, return rv; } -void HTMLAnchorElement::UnbindFromTree(bool aNullParent) { +void HTMLAnchorElement::UnbindFromTree(UnbindContext& aContext) { // Cancel any DNS prefetches // Note: Must come before ResetLinkState. If called after, it will recreate // mCachedURI based on data that is invalid - due to a call to Link::GetURI() // via GetURIForDNSPrefetch(). CancelDNSPrefetch(*this); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); // Without removing the link state we risk a dangling pointer in the // mStyledLinks hashtable diff --git a/dom/html/HTMLAnchorElement.h b/dom/html/HTMLAnchorElement.h index 2a524b96c2..4d89c6a75b 100644 --- a/dom/html/HTMLAnchorElement.h +++ b/dom/html/HTMLAnchorElement.h @@ -47,7 +47,7 @@ class HTMLAnchorElement final : public nsGenericHTMLElement, NS_DECL_ADDSIZEOFEXCLUDINGTHIS nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) override; diff --git a/dom/html/HTMLAreaElement.cpp b/dom/html/HTMLAreaElement.cpp index 81389a4d14..e07150cc06 100644 --- a/dom/html/HTMLAreaElement.cpp +++ b/dom/html/HTMLAreaElement.cpp @@ -72,8 +72,8 @@ nsresult HTMLAreaElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLAreaElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLElement::UnbindFromTree(aNullParent); +void HTMLAreaElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLElement::UnbindFromTree(aContext); // Without removing the link state we risk a dangling pointer in the // mStyledLinks hashtable Link::UnbindFromTree(); diff --git a/dom/html/HTMLAreaElement.h b/dom/html/HTMLAreaElement.h index 3cba9dd833..3effb1a87e 100644 --- a/dom/html/HTMLAreaElement.h +++ b/dom/html/HTMLAreaElement.h @@ -43,7 +43,7 @@ class HTMLAreaElement final : public nsGenericHTMLElement, public Link { already_AddRefed<nsIURI> GetHrefURI() const override; virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp index 7aa7428a26..0d9cc921ad 100644 --- a/dom/html/HTMLButtonElement.cpp +++ b/dom/html/HTMLButtonElement.cpp @@ -308,8 +308,8 @@ nsresult HTMLButtonElement::BindToTree(BindContext& aContext, return NS_OK; } -void HTMLButtonElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent); +void HTMLButtonElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); UpdateBarredFromConstraintValidation(); UpdateValidityElementStates(false); diff --git a/dom/html/HTMLButtonElement.h b/dom/html/HTMLButtonElement.h index 57bc51c05c..b1a5c53f64 100644 --- a/dom/html/HTMLButtonElement.h +++ b/dom/html/HTMLButtonElement.h @@ -65,7 +65,7 @@ class HTMLButtonElement final : public nsGenericHTMLFormControlElementWithState, // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void DoneCreatingElement() override; void UpdateBarredFromConstraintValidation(); diff --git a/dom/html/HTMLDetailsElement.cpp b/dom/html/HTMLDetailsElement.cpp index c8e9c11c4f..afb0087e20 100644 --- a/dom/html/HTMLDetailsElement.cpp +++ b/dom/html/HTMLDetailsElement.cpp @@ -114,8 +114,9 @@ void HTMLDetailsElement::SetupShadowTree() { } nsAutoString defaultSummaryText; - nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, - "DefaultSummary", defaultSummaryText); + nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES, + "DefaultSummary", OwnerDoc(), + defaultSummaryText); RefPtr<nsTextNode> description = new (nim) nsTextNode(nim); description->SetText(defaultSummaryText, kNotify); summary->AppendChildTo(description, kNotify, IgnoreErrors()); diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp index 6d4bda0392..cd36201182 100644 --- a/dom/html/HTMLDialogElement.cpp +++ b/dom/html/HTMLDialogElement.cpp @@ -106,9 +106,9 @@ void HTMLDialogElement::StorePreviouslyFocusedElement() { } } -void HTMLDialogElement::UnbindFromTree(bool aNullParent) { +void HTMLDialogElement::UnbindFromTree(UnbindContext& aContext) { RemoveFromTopLayerIfNeeded(); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLDialogElement::ShowModal(ErrorResult& aError) { diff --git a/dom/html/HTMLDialogElement.h b/dom/html/HTMLDialogElement.h index 556e86b891..72c1a6c743 100644 --- a/dom/html/HTMLDialogElement.h +++ b/dom/html/HTMLDialogElement.h @@ -35,7 +35,7 @@ class HTMLDialogElement final : public nsGenericHTMLElement { mReturnValue = aReturnValue; } - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void Close(const mozilla::dom::Optional<nsAString>& aReturnValue); MOZ_CAN_RUN_SCRIPT void Show(ErrorResult& aError); diff --git a/dom/html/HTMLElement.cpp b/dom/html/HTMLElement.cpp index ff9c786c55..bccb117034 100644 --- a/dom/html/HTMLElement.cpp +++ b/dom/html/HTMLElement.cpp @@ -77,8 +77,8 @@ nsresult HTMLElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLFormElement::UnbindFromTree(aNullParent); +void HTMLElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLFormElement::UnbindFromTree(aContext); UpdateBarredFromConstraintValidation(); UpdateValidityElementStates(false); diff --git a/dom/html/HTMLElement.h b/dom/html/HTMLElement.h index 8fbbc6f2b9..727e6ab487 100644 --- a/dom/html/HTMLElement.h +++ b/dom/html/HTMLElement.h @@ -30,7 +30,7 @@ class HTMLElement final : public nsGenericHTMLFormElement { // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void DoneCreatingElement() override; // Element diff --git a/dom/html/HTMLEmbedElement.cpp b/dom/html/HTMLEmbedElement.cpp index 34d79defbf..39582063a7 100644 --- a/dom/html/HTMLEmbedElement.cpp +++ b/dom/html/HTMLEmbedElement.cpp @@ -65,9 +65,9 @@ nsresult HTMLEmbedElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void HTMLEmbedElement::UnbindFromTree(bool aNullParent) { - nsObjectLoadingContent::UnbindFromTree(aNullParent); - nsGenericHTMLElement::UnbindFromTree(aNullParent); +void HTMLEmbedElement::UnbindFromTree(UnbindContext& aContext) { + nsObjectLoadingContent::UnbindFromTree(); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLEmbedElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, diff --git a/dom/html/HTMLEmbedElement.h b/dom/html/HTMLEmbedElement.h index 0cb82c0baa..8e7bcd4166 100644 --- a/dom/html/HTMLEmbedElement.h +++ b/dom/html/HTMLEmbedElement.h @@ -36,7 +36,7 @@ class HTMLEmbedElement final : public nsGenericHTMLElement, const Element* AsElement() const final { return this; } nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) override; diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 46f2c2a735..7796c4b1f4 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -209,22 +209,16 @@ void HTMLFormElement::GetMethod(nsAString& aValue) { GetEnumAttr(nsGkAtoms::method, kFormDefaultMethod->tag, aValue); } -void HTMLFormElement::ReportInvalidUnfocusableElements() { +void HTMLFormElement::ReportInvalidUnfocusableElements( + const nsTArray<RefPtr<Element>>&& aInvalidElements) { RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); MOZ_ASSERT(focusManager); - // This shouldn't be called recursively, so use a rather large value - // for the preallocated buffer. - AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls; - if (NS_FAILED(mControls->GetSortedControls(sortedControls))) { - return; - } - - for (auto& _e : sortedControls) { - // MOZ_CAN_RUN_SCRIPT requires explicit copy, Bug 1620312 - RefPtr<nsGenericHTMLFormElement> element = _e; + for (const auto& element : aInvalidElements) { bool isFocusable = false; - focusManager->ElementIsFocusable(element, 0, &isFocusable); + // MOZ_KnownLive because 'aInvalidElements' is guaranteed to keep it alive. + // This can go away once bug 1620312 is fixed. + focusManager->ElementIsFocusable(MOZ_KnownLive(element), 0, &isFocusable); if (!isFocusable) { nsTArray<nsString> params; nsAutoCString messageName("InvalidFormControlUnfocusable"); @@ -291,7 +285,6 @@ void HTMLFormElement::MaybeSubmit(Element* aSubmitter) { HasAttr(nsGkAtoms::novalidate) || (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formnovalidate)); if (!noValidateState && !CheckValidFormSubmission()) { - ReportInvalidUnfocusableElements(); return; } @@ -494,7 +487,7 @@ static void CollectOrphans(nsINode* aRemovalRoot, } } -void HTMLFormElement::UnbindFromTree(bool aNullParent) { +void HTMLFormElement::UnbindFromTree(UnbindContext& aContext) { MaybeFireFormRemoved(); // Note, this is explicitly using uncomposed doc, since we count @@ -506,7 +499,7 @@ void HTMLFormElement::UnbindFromTree(bool aNullParent) { MarkOrphans(mControls->mNotInElements.AsList()); MarkOrphans(mImageElements.AsList()); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); nsINode* ancestor = this; nsINode* cur; @@ -1759,7 +1752,8 @@ bool HTMLFormElement::CheckValidFormSubmission() { /** * Check for form validity: do not submit a form if there are unhandled * invalid controls in the form. - * This should not be done if the form has been submitted with .submit(). + * This should not be done if the form has been submitted with .submit() or + * has been submitted and novalidate/formnovalidate is used. * * NOTE: for the moment, we are also checking that whether the MozInvalidForm * event gets prevented default so it will prevent blocking form submission if @@ -1770,9 +1764,6 @@ bool HTMLFormElement::CheckValidFormSubmission() { * submitted when invalid. See bug 587671. */ - NS_ASSERTION(!HasAttr(nsGkAtoms::novalidate), - "We shouldn't be there if novalidate is set!"); - AutoTArray<RefPtr<Element>, 32> invalidElements; if (CheckFormValidity(&invalidElements)) { return true; @@ -1797,6 +1788,8 @@ bool HTMLFormElement::CheckValidFormSubmission() { DispatchEvent(*event); + ReportInvalidUnfocusableElements(std::move(invalidElements)); + return !event->DefaultPrevented(); } diff --git a/dom/html/HTMLFormElement.h b/dom/html/HTMLFormElement.h index 68dd627554..263f665843 100644 --- a/dom/html/HTMLFormElement.h +++ b/dom/html/HTMLFormElement.h @@ -70,7 +70,7 @@ class HTMLFormElement final : public nsGenericHTMLElement { nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue, bool aNotify) override; @@ -212,11 +212,11 @@ class HTMLFormElement final : public nsGenericHTMLElement { * * @return Whether the form is valid. * - * @note Do not call this method if novalidate/formnovalidate is used. * @note This method might disappear with bug 592124, hopefuly. * @see * https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#interactively-validate-the-constraints */ + MOZ_CAN_RUN_SCRIPT bool CheckValidFormSubmission(); /** @@ -330,6 +330,7 @@ class HTMLFormElement final : public nsGenericHTMLElement { bool CheckValidity() { return CheckFormValidity(nullptr); } + MOZ_CAN_RUN_SCRIPT bool ReportValidity() { return CheckValidFormSubmission(); } Element* IndexedGetter(uint32_t aIndex, bool& aFound); @@ -584,7 +585,8 @@ class HTMLFormElement final : public nsGenericHTMLElement { void MaybeFireFormRemoved(); MOZ_CAN_RUN_SCRIPT - void ReportInvalidUnfocusableElements(); + void ReportInvalidUnfocusableElements( + const nsTArray<RefPtr<Element>>&& aInvalidElements); ~HTMLFormElement(); }; diff --git a/dom/html/HTMLIFrameElement.h b/dom/html/HTMLIFrameElement.h index 8a87c02472..3124e493b5 100644 --- a/dom/html/HTMLIFrameElement.h +++ b/dom/html/HTMLIFrameElement.h @@ -142,11 +142,6 @@ class HTMLIFrameElement final : public nsGenericHTMLFrameElement { Document* GetSVGDocument(nsIPrincipal& aSubjectPrincipal) { return GetContentDocument(aSubjectPrincipal); } - bool Mozbrowser() const { return GetBoolAttr(nsGkAtoms::mozbrowser); } - void SetMozbrowser(bool aAllow, ErrorResult& aError) { - SetHTMLBoolAttr(nsGkAtoms::mozbrowser, aAllow, aError); - } - using nsGenericHTMLFrameElement::SetMozbrowser; // nsGenericHTMLFrameElement::GetFrameLoader is fine // nsGenericHTMLFrameElement::GetAppManifestURL is fine diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index 1aa23cdea8..691216d152 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -10,19 +10,17 @@ #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/HTMLImageElementBinding.h" #include "mozilla/dom/NameSpaceConstants.h" +#include "mozilla/dom/UnbindContext.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" -#include "nsStyleConsts.h" #include "nsPresContext.h" #include "nsSize.h" #include "mozilla/dom/Document.h" #include "nsImageFrame.h" -#include "nsIScriptContext.h" #include "nsContentUtils.h" #include "nsContainerFrame.h" #include "nsNodeInfoManager.h" #include "mozilla/MouseEvents.h" -#include "nsContentPolicyUtils.h" #include "nsFocusManager.h" #include "mozilla/dom/DOMIntersectionObserver.h" #include "mozilla/dom/HTMLFormElement.h" @@ -30,7 +28,6 @@ #include "mozilla/dom/UserActivation.h" #include "nsAttrValueOrString.h" #include "imgLoader.h" -#include "Image.h" // Responsive images! #include "mozilla/dom/HTMLSourceElement.h" @@ -223,6 +220,10 @@ bool HTMLImageElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, if (aAttribute == nsGkAtoms::loading) { return ParseLoadingAttribute(aValue, aResult); } + if (aAttribute == nsGkAtoms::fetchpriority) { + ParseFetchPriority(aValue, aResult); + return true; + } if (ParseImageAttribute(aAttribute, aValue, aResult)) { return true; } @@ -564,9 +565,9 @@ nsresult HTMLImageElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLImageElement::UnbindFromTree(bool aNullParent) { +void HTMLImageElement::UnbindFromTree(UnbindContext& aContext) { if (mForm) { - if (aNullParent || !FindAncestorForm(mForm)) { + if (aContext.IsUnbindRoot(this) || !FindAncestorForm(mForm)) { ClearForm(true); } else { UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); @@ -578,8 +579,8 @@ void HTMLImageElement::UnbindFromTree(bool aNullParent) { mInDocResponsiveContent = false; } - nsImageLoadingContent::UnbindFromTree(aNullParent); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsImageLoadingContent::UnbindFromTree(); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLImageElement::UpdateFormOwner() { @@ -1377,4 +1378,8 @@ void HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad) { CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget()); } +FetchPriority HTMLImageElement::GetFetchPriorityForImage() const { + return nsGenericHTMLElement::GetFetchPriority(); +} + } // namespace mozilla::dom diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h index 8e10c6867e..bf41c68d40 100644 --- a/dom/html/HTMLImageElement.h +++ b/dom/html/HTMLImageElement.h @@ -74,7 +74,7 @@ class HTMLImageElement final : public nsGenericHTMLElement, int32_t* aTabIndex) override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; @@ -263,6 +263,8 @@ class HTMLImageElement final : public nsGenericHTMLElement, // element. const StyleLockedDeclarationBlock* GetMappedAttributesFromSource() const; + FetchPriority GetFetchPriorityForImage() const override; + protected: virtual ~HTMLImageElement(); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 4e7241ec7a..d5a65a1755 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -49,7 +49,6 @@ #include "nsSearchControlFrame.h" #include "nsPIDOMWindow.h" #include "nsRepeatService.h" -#include "nsContentCID.h" #include "mozilla/dom/ProgressEvent.h" #include "nsGkAtoms.h" #include "nsStyleConsts.h" @@ -779,8 +778,8 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { // Get parent nsPIDOMWindow object. nsCOMPtr<Document> doc = OwnerDoc(); - nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow(); - if (!win) { + RefPtr<BrowsingContext> bc = doc->GetBrowsingContext(); + if (!bc) { return NS_ERROR_FAILURE; } @@ -793,15 +792,14 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { nsAutoString okButtonLabel; if (aType == FILE_PICKER_DIRECTORY) { nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES, - "DirectoryUpload", OwnerDoc(), - title); + "DirectoryUpload", doc, title); nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES, - "DirectoryPickerOkButtonLabel", - OwnerDoc(), okButtonLabel); + "DirectoryPickerOkButtonLabel", doc, + okButtonLabel); } else { nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES, - "FileUpload", OwnerDoc(), title); + "FileUpload", doc, title); } nsCOMPtr<nsIFilePicker> filePicker = @@ -818,8 +816,7 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { mode = nsIFilePicker::modeOpen; } - nsresult rv = - filePicker->Init(win, title, mode, OwnerDoc()->GetBrowsingContext()); + nsresult rv = filePicker->Init(bc, title, mode); NS_ENSURE_SUCCESS(rv, rv); if (!okButtonLabel.IsEmpty()) { @@ -4447,7 +4444,7 @@ void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement* aForm) { dispatcher->PostDOMEvent(); } -void HTMLInputElement::UnbindFromTree(bool aNullParent) { +void HTMLInputElement::UnbindFromTree(UnbindContext& aContext) { if (mType == FormControlType::InputPassword) { MaybeFireInputPasswordRemoved(); } @@ -4465,8 +4462,8 @@ void HTMLInputElement::UnbindFromTree(bool aNullParent) { NotifyUAWidgetTeardown(); } - nsImageLoadingContent::UnbindFromTree(aNullParent); - nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent); + nsImageLoadingContent::UnbindFromTree(); + nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); // If we are contained within a disconnected subtree, attempt to add // ourselves to the subtree root's radio group. @@ -6483,7 +6480,8 @@ bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, } // Current radio button is not selected. - // But make it tabbable if nothing in group is selected. + // Make it tabbable if nothing in group is selected and it is the first radio + // button. auto* container = GetCurrentRadioGroupContainer(); if (!container) { *aIsFocusable = defaultFocusable; @@ -6493,7 +6491,8 @@ bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, nsAutoString name; GetAttr(nsGkAtoms::name, name); - if (container->GetCurrentRadioButton(name)) { + if (container->GetCurrentRadioButton(name) || + container->GetFirstRadioButton(name) != this) { *aTabIndex = -1; } *aIsFocusable = defaultFocusable; diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 8805ce762b..2c90aa83fe 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -213,7 +213,7 @@ class HTMLInputElement final : public TextControlElement, SnapToTickMarks = SnapToTickMarks::No); nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoneCreatingElement() override; diff --git a/dom/html/HTMLLegendElement.cpp b/dom/html/HTMLLegendElement.cpp index 2f110dd6ce..6cc079c4e7 100644 --- a/dom/html/HTMLLegendElement.cpp +++ b/dom/html/HTMLLegendElement.cpp @@ -62,8 +62,8 @@ nsresult HTMLLegendElement::BindToTree(BindContext& aContext, return nsGenericHTMLElement::BindToTree(aContext, aParent); } -void HTMLLegendElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLElement::UnbindFromTree(aNullParent); +void HTMLLegendElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLLegendElement::Focus(const FocusOptions& aOptions, diff --git a/dom/html/HTMLLegendElement.h b/dom/html/HTMLLegendElement.h index cccab9239b..2b0c8581df 100644 --- a/dom/html/HTMLLegendElement.h +++ b/dom/html/HTMLLegendElement.h @@ -31,7 +31,7 @@ class HTMLLegendElement final : public nsGenericHTMLElement { // nsIContent virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index 53c2d86d8d..cb316a6f7b 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -110,7 +110,7 @@ void HTMLLinkElement::LinkAdded() { CreateAndDispatchEvent(u"DOMLinkAdded"_ns); } -void HTMLLinkElement::UnbindFromTree(bool aNullParent) { +void HTMLLinkElement::UnbindFromTree(UnbindContext& aContext) { CancelDNSPrefetch(*this); CancelPrefetchOrPreload(); @@ -130,7 +130,7 @@ void HTMLLinkElement::UnbindFromTree(bool aNullParent) { } } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); Unused << UpdateStyleSheetInternal(oldDoc, oldShadowRoot); } @@ -164,6 +164,12 @@ bool HTMLLinkElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, ParseFetchPriority(aValue, aResult); return true; } + + if (aAttribute == nsGkAtoms::blocking && + StaticPrefs::dom_element_blocking_enabled()) { + aResult.ParseAtomArray(aValue); + return true; + } } return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, @@ -703,4 +709,14 @@ nsDOMTokenList* HTMLLinkElement::Blocking() { return mBlocking; } +bool HTMLLinkElement::IsPotentiallyRenderBlocking() { + return BlockingContainsRender(); + + // TODO: handle implicitly potentially render blocking + // https://html.spec.whatwg.org/#implicitly-potentially-render-blocking + // The default type for resources given by the stylesheet keyword is text/css. + // A link element of this type is implicitly potentially render-blocking if + // the element was created by its node document's parser. +} + } // namespace mozilla::dom diff --git a/dom/html/HTMLLinkElement.h b/dom/html/HTMLLinkElement.h index 26683aae7b..4381aeea85 100644 --- a/dom/html/HTMLLinkElement.h +++ b/dom/html/HTMLLinkElement.h @@ -48,7 +48,7 @@ class HTMLLinkElement final : public nsGenericHTMLElement, // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, bool aNotify) override; void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, @@ -167,6 +167,7 @@ class HTMLLinkElement final : public nsGenericHTMLElement, } nsDOMTokenList* Blocking(); + bool IsPotentiallyRenderBlocking() override; void NodeInfoChanged(Document* aOldDoc) final { mCachedURI = nullptr; diff --git a/dom/html/HTMLMarqueeElement.cpp b/dom/html/HTMLMarqueeElement.cpp index 61308bf03e..9719f83ea3 100644 --- a/dom/html/HTMLMarqueeElement.cpp +++ b/dom/html/HTMLMarqueeElement.cpp @@ -56,14 +56,14 @@ nsresult HTMLMarqueeElement::BindToTree(BindContext& aContext, return rv; } -void HTMLMarqueeElement::UnbindFromTree(bool aNullParent) { +void HTMLMarqueeElement::UnbindFromTree(UnbindContext& aContext) { if (IsInComposedDoc()) { // We don't want to unattach the shadow root because it used to // contain a <slot>. NotifyUAWidgetTeardown(UnattachShadowRoot::No); } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLMarqueeElement::GetBehavior(nsAString& aValue) { diff --git a/dom/html/HTMLMarqueeElement.h b/dom/html/HTMLMarqueeElement.h index 250d7c2cf9..f93f6b4944 100644 --- a/dom/html/HTMLMarqueeElement.h +++ b/dom/html/HTMLMarqueeElement.h @@ -19,7 +19,7 @@ class HTMLMarqueeElement final : public nsGenericHTMLElement { NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLMarqueeElement, marquee); nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; static const int kDefaultLoop = -1; static const int kDefaultScrollAmount = 6; diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 78e9a7b861..4ea52ccdf6 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -51,6 +51,7 @@ #include "base/basictypes.h" #include "jsapi.h" #include "js/PropertyAndElement.h" // JS_DefineProperty +#include "mozilla/AppShutdown.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/EMEUtils.h" @@ -4891,14 +4892,14 @@ nsresult HTMLMediaElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLMediaElement::UnbindFromTree(bool aNullParent) { +void HTMLMediaElement::UnbindFromTree(UnbindContext& aContext) { mVisibilityState = Visibility::Untracked; if (IsInComposedDoc()) { NotifyUAWidgetTeardown(); } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); MOZ_ASSERT(IsActuallyInvisible()); NotifyDecoderActivityChanges(); diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 0d35fcc85c..eef276b43d 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -202,7 +202,7 @@ class HTMLMediaElement : public nsGenericHTMLElement, nsAttrValue& aResult) override; virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void DoneCreatingElement() override; virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, diff --git a/dom/html/HTMLMetaElement.cpp b/dom/html/HTMLMetaElement.cpp index b99ffabdaf..01c7e1e824 100644 --- a/dom/html/HTMLMetaElement.cpp +++ b/dom/html/HTMLMetaElement.cpp @@ -119,14 +119,14 @@ nsresult HTMLMetaElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLMetaElement::UnbindFromTree(bool aNullParent) { +void HTMLMetaElement::UnbindFromTree(UnbindContext& aContext) { if (Document* oldDoc = GetUncomposedDoc()) { if (const nsAttrValue* name = GetParsedAttr(nsGkAtoms::name)) { MetaRemoved(*oldDoc, *name, ChangeKind::TreeChange); } CreateAndDispatchEvent(*oldDoc, u"DOMMetaRemoved"_ns); } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLMetaElement::CreateAndDispatchEvent(Document&, diff --git a/dom/html/HTMLMetaElement.h b/dom/html/HTMLMetaElement.h index e492b49e8b..04069fa3a3 100644 --- a/dom/html/HTMLMetaElement.h +++ b/dom/html/HTMLMetaElement.h @@ -21,7 +21,7 @@ class HTMLMetaElement final : public nsGenericHTMLElement { NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLMetaElement, nsGenericHTMLElement) nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp index f8e5d99963..f77a1f3ba2 100644 --- a/dom/html/HTMLObjectElement.cpp +++ b/dom/html/HTMLObjectElement.cpp @@ -89,9 +89,9 @@ nsresult HTMLObjectElement::BindToTree(BindContext& aContext, return NS_OK; } -void HTMLObjectElement::UnbindFromTree(bool aNullParent) { - nsObjectLoadingContent::UnbindFromTree(aNullParent); - nsGenericHTMLFormControlElement::UnbindFromTree(aNullParent); +void HTMLObjectElement::UnbindFromTree(UnbindContext& aContext) { + nsObjectLoadingContent::UnbindFromTree(); + nsGenericHTMLFormControlElement::UnbindFromTree(aContext); } void HTMLObjectElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, diff --git a/dom/html/HTMLObjectElement.h b/dom/html/HTMLObjectElement.h index c627511b5c..00bcbc70d5 100644 --- a/dom/html/HTMLObjectElement.h +++ b/dom/html/HTMLObjectElement.h @@ -40,7 +40,7 @@ class HTMLObjectElement final : public nsGenericHTMLFormControlElement, bool IsInteractiveHTMLContent() const override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) override; diff --git a/dom/html/HTMLOptionElement.cpp b/dom/html/HTMLOptionElement.cpp index 733e15c609..5a9762de89 100644 --- a/dom/html/HTMLOptionElement.cpp +++ b/dom/html/HTMLOptionElement.cpp @@ -247,8 +247,8 @@ nsresult HTMLOptionElement::BindToTree(BindContext& aContext, return NS_OK; } -void HTMLOptionElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLElement::UnbindFromTree(aNullParent); +void HTMLOptionElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLElement::UnbindFromTree(aContext); // Our previous parent could have been involved in :disabled/:enabled state. UpdateDisabledState(false); diff --git a/dom/html/HTMLOptionElement.h b/dom/html/HTMLOptionElement.h index 4d8e920a52..7c9569e87d 100644 --- a/dom/html/HTMLOptionElement.h +++ b/dom/html/HTMLOptionElement.h @@ -62,7 +62,7 @@ class HTMLOptionElement final : public nsGenericHTMLElement { void UpdateDisabledState(bool aNotify); nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; diff --git a/dom/html/HTMLScriptElement.cpp b/dom/html/HTMLScriptElement.cpp index 006bff23a7..9fbf46699d 100644 --- a/dom/html/HTMLScriptElement.cpp +++ b/dom/html/HTMLScriptElement.cpp @@ -88,6 +88,12 @@ bool HTMLScriptElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, ParseFetchPriority(aValue, aResult); return true; } + + if (aAttribute == nsGkAtoms::blocking && + StaticPrefs::dom_element_blocking_enabled()) { + aResult.ParseAtomArray(aValue); + return true; + } } return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, @@ -251,4 +257,14 @@ nsDOMTokenList* HTMLScriptElement::Blocking() { return mBlocking; } +bool HTMLScriptElement::IsPotentiallyRenderBlocking() { + return BlockingContainsRender(); + + // TODO: handle implicitly potentially render blocking + // https://html.spec.whatwg.org/#implicitly-potentially-render-blocking + // A script element el is implicitly potentially render-blocking if el's type + // is "classic", el is parser-inserted, and el does not have an async or defer + // attribute. +} + } // namespace mozilla::dom diff --git a/dom/html/HTMLScriptElement.h b/dom/html/HTMLScriptElement.h index db09e247bc..c5e99c9e5d 100644 --- a/dom/html/HTMLScriptElement.h +++ b/dom/html/HTMLScriptElement.h @@ -138,6 +138,7 @@ class HTMLScriptElement final : public nsGenericHTMLElement, } nsDOMTokenList* Blocking(); + bool IsPotentiallyRenderBlocking() override; // Required for the webidl-binding because `GetFetchPriority` is overloaded. using nsGenericHTMLElement::GetFetchPriority; diff --git a/dom/html/HTMLSelectElement.cpp b/dom/html/HTMLSelectElement.cpp index 18bf2b79b2..6ca4209cd9 100644 --- a/dom/html/HTMLSelectElement.cpp +++ b/dom/html/HTMLSelectElement.cpp @@ -1089,8 +1089,8 @@ nsresult HTMLSelectElement::BindToTree(BindContext& aContext, return rv; } -void HTMLSelectElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent); +void HTMLSelectElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); // We might be no longer disabled because our parent chain changed. // XXXbz is this still needed now that fieldset changes always call diff --git a/dom/html/HTMLSelectElement.h b/dom/html/HTMLSelectElement.h index 223da65c31..1ba5dc29f6 100644 --- a/dom/html/HTMLSelectElement.h +++ b/dom/html/HTMLSelectElement.h @@ -270,7 +270,7 @@ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState, * Called when an attribute is about to be changed */ nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, bool aNotify) override; void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, diff --git a/dom/html/HTMLSharedElement.cpp b/dom/html/HTMLSharedElement.cpp index b18f5e3339..85849f9f79 100644 --- a/dom/html/HTMLSharedElement.cpp +++ b/dom/html/HTMLSharedElement.cpp @@ -181,10 +181,10 @@ nsresult HTMLSharedElement::BindToTree(BindContext& aContext, return NS_OK; } -void HTMLSharedElement::UnbindFromTree(bool aNullParent) { +void HTMLSharedElement::UnbindFromTree(UnbindContext& aContext) { Document* doc = GetUncomposedDoc(); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); // If we're removing a <base> from a document, we may need to update the // document's base URI and base target diff --git a/dom/html/HTMLSharedElement.h b/dom/html/HTMLSharedElement.h index cb1e5ff288..8e9e15648e 100644 --- a/dom/html/HTMLSharedElement.h +++ b/dom/html/HTMLSharedElement.h @@ -32,7 +32,7 @@ class HTMLSharedElement final : public nsGenericHTMLElement { nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; diff --git a/dom/html/HTMLSlotElement.cpp b/dom/html/HTMLSlotElement.cpp index 9fb3986e93..18d65c2c8a 100644 --- a/dom/html/HTMLSlotElement.cpp +++ b/dom/html/HTMLSlotElement.cpp @@ -64,10 +64,10 @@ nsresult HTMLSlotElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void HTMLSlotElement::UnbindFromTree(bool aNullParent) { +void HTMLSlotElement::UnbindFromTree(UnbindContext& aContext) { RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow(); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); if (oldContainingShadow && !GetContainingShadow()) { oldContainingShadow->RemoveSlot(this); diff --git a/dom/html/HTMLSlotElement.h b/dom/html/HTMLSlotElement.h index fa12f0df26..1659ca9683 100644 --- a/dom/html/HTMLSlotElement.h +++ b/dom/html/HTMLSlotElement.h @@ -26,7 +26,7 @@ class HTMLSlotElement final : public nsGenericHTMLElement { // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, bool aNotify) override; diff --git a/dom/html/HTMLSourceElement.cpp b/dom/html/HTMLSourceElement.cpp index b08d449594..1bff67274e 100644 --- a/dom/html/HTMLSourceElement.cpp +++ b/dom/html/HTMLSourceElement.cpp @@ -166,9 +166,9 @@ nsresult HTMLSourceElement::BindToTree(BindContext& aContext, return NS_OK; } -void HTMLSourceElement::UnbindFromTree(bool aNullParent) { +void HTMLSourceElement::UnbindFromTree(UnbindContext& aContext) { mMappedAttributesForImage = nullptr; - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } JSObject* HTMLSourceElement::WrapNode(JSContext* aCx, diff --git a/dom/html/HTMLSourceElement.h b/dom/html/HTMLSourceElement.h index 4d4a1b212d..91914fb781 100644 --- a/dom/html/HTMLSourceElement.h +++ b/dom/html/HTMLSourceElement.h @@ -35,7 +35,7 @@ class HTMLSourceElement final : public nsGenericHTMLElement { // child source element. nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; // If this element's media attr matches for its owner document. Returns true // if no media attr was set. diff --git a/dom/html/HTMLStyleElement.cpp b/dom/html/HTMLStyleElement.cpp index ed4c141897..e0bc228011 100644 --- a/dom/html/HTMLStyleElement.cpp +++ b/dom/html/HTMLStyleElement.cpp @@ -91,15 +91,29 @@ nsresult HTMLStyleElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void HTMLStyleElement::UnbindFromTree(bool aNullParent) { +void HTMLStyleElement::UnbindFromTree(UnbindContext& aContext) { RefPtr<Document> oldDoc = GetUncomposedDoc(); ShadowRoot* oldShadow = GetContainingShadow(); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); Unused << UpdateStyleSheetInternal(oldDoc, oldShadow); } +bool HTMLStyleElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::blocking && + StaticPrefs::dom_element_blocking_enabled()) { + aResult.ParseAtomArray(aValue); + return true; + } + + return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + void HTMLStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, @@ -199,4 +213,13 @@ nsDOMTokenList* HTMLStyleElement::Blocking() { return mBlocking; } +bool HTMLStyleElement::IsPotentiallyRenderBlocking() { + return BlockingContainsRender(); + + // TODO: handle implicitly potentially render blocking + // https://html.spec.whatwg.org/#implicitly-potentially-render-blocking + // A style element is implicitly potentially render-blocking if the element + // was created by its node document's parser. +} + } // namespace mozilla::dom diff --git a/dom/html/HTMLStyleElement.h b/dom/html/HTMLStyleElement.h index 5815740ac5..8f637dc60d 100644 --- a/dom/html/HTMLStyleElement.h +++ b/dom/html/HTMLStyleElement.h @@ -47,7 +47,11 @@ class HTMLStyleElement final : public nsGenericHTMLElement, void SetDevtoolsAsTriggeringPrincipal(); virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; virtual void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, @@ -74,6 +78,7 @@ class HTMLStyleElement final : public nsGenericHTMLElement, } nsDOMTokenList* Blocking(); + bool IsPotentiallyRenderBlocking() override; virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; diff --git a/dom/html/HTMLTableElement.cpp b/dom/html/HTMLTableElement.cpp index 97329dcef1..08944ea180 100644 --- a/dom/html/HTMLTableElement.cpp +++ b/dom/html/HTMLTableElement.cpp @@ -961,9 +961,9 @@ nsresult HTMLTableElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void HTMLTableElement::UnbindFromTree(bool aNullParent) { +void HTMLTableElement::UnbindFromTree(UnbindContext& aContext) { ReleaseInheritedAttributes(); - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, diff --git a/dom/html/HTMLTableElement.h b/dom/html/HTMLTableElement.h index 38d3e24a83..9c9d59084c 100644 --- a/dom/html/HTMLTableElement.h +++ b/dom/html/HTMLTableElement.h @@ -156,7 +156,7 @@ class HTMLTableElement final : public nsGenericHTMLElement { nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; /** * Called when an attribute is about to be changed */ diff --git a/dom/html/HTMLTemplateElement.h b/dom/html/HTMLTemplateElement.h index be643d215b..f54967868b 100644 --- a/dom/html/HTMLTemplateElement.h +++ b/dom/html/HTMLTemplateElement.h @@ -51,12 +51,18 @@ class HTMLTemplateElement final : public nsGenericHTMLElement { SetHTMLAttr(nsGkAtoms::shadowrootmode, aValue); } - bool ShadowRootDelegatesFocus() { + bool ShadowRootDelegatesFocus() const { return GetBoolAttr(nsGkAtoms::shadowrootdelegatesfocus); } - void SetShadowRootDelegatesFocus(bool aValue) { - SetHTMLBoolAttr(nsGkAtoms::shadowrootdelegatesfocus, aValue, - IgnoredErrorResult()); + void SetShadowRootDelegatesFocus(bool aValue, ErrorResult& aRv) { + SetHTMLBoolAttr(nsGkAtoms::shadowrootdelegatesfocus, aValue, aRv); + } + + bool ShadowRootClonable() const { + return GetBoolAttr(nsGkAtoms::shadowrootclonable); + } + void SetShadowRootClonable(bool aValue, ErrorResult& aRv) { + SetHTMLBoolAttr(nsGkAtoms::shadowrootclonable, aValue, aRv); } MOZ_CAN_RUN_SCRIPT diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index ce28575a4d..be4ba5a891 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -19,7 +19,6 @@ #include "mozilla/TextControlState.h" #include "nsAttrValueInlines.h" #include "nsBaseCommandController.h" -#include "nsContentCID.h" #include "nsContentCreatorFunctions.h" #include "nsError.h" #include "nsFocusManager.h" @@ -772,8 +771,8 @@ nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext, return rv; } -void HTMLTextAreaElement::UnbindFromTree(bool aNullParent) { - nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent); +void HTMLTextAreaElement::UnbindFromTree(UnbindContext& aContext) { + nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); // We might be no longer disabled because of parent chain changed. UpdateValueMissingValidityState(); diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index ac3eb8bbf5..0adaa48b21 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -108,7 +108,7 @@ class HTMLTextAreaElement final : public TextControlElement, // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, diff --git a/dom/html/HTMLTitleElement.cpp b/dom/html/HTMLTitleElement.cpp index 776c76f7e5..d8d65491cc 100644 --- a/dom/html/HTMLTitleElement.cpp +++ b/dom/html/HTMLTitleElement.cpp @@ -72,11 +72,11 @@ nsresult HTMLTitleElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void HTMLTitleElement::UnbindFromTree(bool aNullParent) { +void HTMLTitleElement::UnbindFromTree(UnbindContext& aContext) { SendTitleChangeEvent(false); // Let this fall through. - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } void HTMLTitleElement::DoneAddingChildren(bool aHaveNotified) { diff --git a/dom/html/HTMLTitleElement.h b/dom/html/HTMLTitleElement.h index 63cafa75a2..3d56940e25 100644 --- a/dom/html/HTMLTitleElement.h +++ b/dom/html/HTMLTitleElement.h @@ -41,7 +41,7 @@ class HTMLTitleElement final : public nsGenericHTMLElement, virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void DoneAddingChildren(bool aHaveNotified) override; diff --git a/dom/html/HTMLTrackElement.cpp b/dom/html/HTMLTrackElement.cpp index 5363d3e399..e0e31dbb1b 100644 --- a/dom/html/HTMLTrackElement.cpp +++ b/dom/html/HTMLTrackElement.cpp @@ -11,10 +11,9 @@ #include "mozilla/LoadInfo.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/dom/HTMLTrackElementBinding.h" -#include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/dom/UnbindContext.h" #include "nsAttrValueInlines.h" #include "nsCOMPtr.h" -#include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsCycleCollectionParticipant.h" #include "nsGenericHTMLElement.h" @@ -30,7 +29,6 @@ #include "nsNetUtil.h" #include "nsStyleConsts.h" #include "nsThreadUtils.h" -#include "nsVideoFrame.h" extern mozilla::LazyLogModule gTextTrackLog; #define LOG(msg, ...) \ @@ -408,8 +406,8 @@ nsresult HTMLTrackElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void HTMLTrackElement::UnbindFromTree(bool aNullParent) { - if (mMediaParent && aNullParent) { +void HTMLTrackElement::UnbindFromTree(UnbindContext& aContext) { + if (mMediaParent && aContext.IsUnbindRoot(this)) { // mTrack can be null if HTMLTrackElement::LoadResource has never been // called. if (mTrack) { @@ -419,7 +417,7 @@ void HTMLTrackElement::UnbindFromTree(bool aNullParent) { mMediaParent = nullptr; } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } TextTrackReadyState HTMLTrackElement::ReadyState() const { diff --git a/dom/html/HTMLTrackElement.h b/dom/html/HTMLTrackElement.h index 20a239778d..8c5b4a4e91 100644 --- a/dom/html/HTMLTrackElement.h +++ b/dom/html/HTMLTrackElement.h @@ -86,7 +86,7 @@ class HTMLTrackElement final : public nsGenericHTMLElement { // Override BindToTree() so that we can trigger a load when we become // the child of a media element. virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, diff --git a/dom/html/HTMLVideoElement.cpp b/dom/html/HTMLVideoElement.cpp index effb21706c..925a14c931 100644 --- a/dom/html/HTMLVideoElement.cpp +++ b/dom/html/HTMLVideoElement.cpp @@ -6,6 +6,7 @@ #include "mozilla/dom/HTMLVideoElement.h" +#include "mozilla/AppShutdown.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/HTMLVideoElementBinding.h" #include "nsGenericHTMLElement.h" @@ -188,7 +189,7 @@ nsMapRuleToAttributesFunc HTMLVideoElement::GetAttributeMappingFunction() return &MapAttributesIntoRule; } -void HTMLVideoElement::UnbindFromTree(bool aNullParent) { +void HTMLVideoElement::UnbindFromTree(UnbindContext& aContext) { if (mVisualCloneSource) { mVisualCloneSource->EndCloningVisually(); } else if (mVisualCloneTarget) { @@ -198,7 +199,7 @@ void HTMLVideoElement::UnbindFromTree(bool aNullParent) { EndCloningVisually(); } - HTMLMediaElement::UnbindFromTree(aNullParent); + HTMLMediaElement::UnbindFromTree(aContext); } nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel) { diff --git a/dom/html/HTMLVideoElement.h b/dom/html/HTMLVideoElement.h index eda62d759a..af90cc79a1 100644 --- a/dom/html/HTMLVideoElement.h +++ b/dom/html/HTMLVideoElement.h @@ -53,7 +53,7 @@ class HTMLVideoElement final : public HTMLMediaElement { nsresult Clone(NodeInfo*, nsINode** aResult) const override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; mozilla::Maybe<mozilla::CSSIntSize> GetVideoSize() const; diff --git a/dom/html/TextControlState.cpp b/dom/html/TextControlState.cpp index 3e2c06c53a..11f7619699 100644 --- a/dom/html/TextControlState.cpp +++ b/dom/html/TextControlState.cpp @@ -14,7 +14,6 @@ #include "nsCOMPtr.h" #include "nsView.h" #include "nsCaret.h" -#include "nsLayoutCID.h" #include "nsITextControlFrame.h" #include "nsContentCreatorFunctions.h" #include "nsTextControlFrame.h" diff --git a/dom/html/moz.build b/dom/html/moz.build index f3fdd30917..9971f600a0 100644 --- a/dom/html/moz.build +++ b/dom/html/moz.build @@ -117,7 +117,6 @@ EXPORTS.mozilla.dom += [ "ImageDocument.h", "MediaDocument.h", "MediaError.h", - "nsBrowserElement.h", "PlayPromise.h", "RadioNodeList.h", "TextTrackManager.h", @@ -203,7 +202,6 @@ UNIFIED_SOURCES += [ "ImageDocument.cpp", "MediaDocument.cpp", "MediaError.cpp", - "nsBrowserElement.cpp", "nsDOMStringMap.cpp", "nsGenericHTMLElement.cpp", "nsGenericHTMLFrameElement.cpp", diff --git a/dom/html/nsBrowserElement.cpp b/dom/html/nsBrowserElement.cpp deleted file mode 100644 index 69284e77ab..0000000000 --- a/dom/html/nsBrowserElement.cpp +++ /dev/null @@ -1,57 +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/. */ - -#include "nsBrowserElement.h" - -#include "mozilla/Preferences.h" -#include "mozilla/dom/Promise.h" -#include "mozilla/dom/ScriptSettings.h" -#include "mozilla/dom/ToJSValue.h" - -#include "nsComponentManagerUtils.h" -#include "nsFrameLoader.h" -#include "nsINode.h" - -#include "js/Wrapper.h" - -using namespace mozilla::dom; - -namespace mozilla { - -bool nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv) { - if (mBrowserElementAPI) { - return true; - } - aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR); - return false; -} - -void nsBrowserElement::InitBrowserElementAPI() { - RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); - NS_ENSURE_TRUE_VOID(frameLoader); - - if (!frameLoader->OwnerIsMozBrowserFrame()) { - return; - } - - if (!mBrowserElementAPI) { - mBrowserElementAPI = - do_CreateInstance("@mozilla.org/dom/browser-element-api;1"); - if (NS_WARN_IF(!mBrowserElementAPI)) { - return; - } - } - mBrowserElementAPI->SetFrameLoader(frameLoader); -} - -void nsBrowserElement::DestroyBrowserElementFrameScripts() { - if (!mBrowserElementAPI) { - return; - } - mBrowserElementAPI->DestroyFrameScripts(); -} - -} // namespace mozilla diff --git a/dom/html/nsBrowserElement.h b/dom/html/nsBrowserElement.h deleted file mode 100644 index c81529de33..0000000000 --- a/dom/html/nsBrowserElement.h +++ /dev/null @@ -1,57 +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 nsBrowserElement_h -#define nsBrowserElement_h - -#include "mozilla/dom/BindingDeclarations.h" - -#include "nsCOMPtr.h" -#include "nsIBrowserElementAPI.h" - -class nsFrameLoader; - -namespace mozilla { - -namespace dom { -class Promise; -} // namespace dom - -class ErrorResult; - -/** - * A helper class for browser-element frames - */ -class nsBrowserElement { - public: - nsBrowserElement() = default; - virtual ~nsBrowserElement() = default; - - void SendMouseEvent(const nsAString& aType, uint32_t aX, uint32_t aY, - uint32_t aButton, uint32_t aClickCount, - uint32_t aModifiers, ErrorResult& aRv); - void GoBack(ErrorResult& aRv); - void GoForward(ErrorResult& aRv); - void Reload(bool aHardReload, ErrorResult& aRv); - void Stop(ErrorResult& aRv); - - already_AddRefed<dom::Promise> GetCanGoBack(ErrorResult& aRv); - already_AddRefed<dom::Promise> GetCanGoForward(ErrorResult& aRv); - - protected: - virtual already_AddRefed<nsFrameLoader> GetFrameLoader() = 0; - - void InitBrowserElementAPI(); - void DestroyBrowserElementFrameScripts(); - nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI; - - private: - bool IsBrowserElementOrThrow(ErrorResult& aRv); -}; - -} // namespace mozilla - -#endif // nsBrowserElement_h diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index de29276fdc..1014ba2a25 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -18,7 +18,6 @@ #include "mozilla/StaticPrefs_dom.h" #include "mozilla/TextEditor.h" #include "mozilla/TextEvents.h" -#include "mozilla/StaticPrefs_html5.h" #include "mozilla/StaticPrefs_accessibility.h" #include "mozilla/dom/FetchPriority.h" #include "mozilla/dom/FormData.h" @@ -29,6 +28,7 @@ #include "nsQueryObject.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/UnbindContext.h" #include "nsPIDOMWindow.h" #include "nsIFrameInlines.h" #include "nsIScrollableFrame.h" @@ -524,7 +524,7 @@ nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext, return rv; } -void nsGenericHTMLElement::UnbindFromTree(bool aNullParent) { +void nsGenericHTMLElement::UnbindFromTree(UnbindContext& aContext) { if (IsInComposedDoc()) { // https://html.spec.whatwg.org/#dom-trees:hide-popover-algorithm // If removedNode's popover attribute is not in the no popover state, then @@ -544,7 +544,7 @@ void nsGenericHTMLElement::UnbindFromTree(bool aNullParent) { } } - nsStyledElement::UnbindFromTree(aNullParent); + nsStyledElement::UnbindFromTree(aContext); // Invalidate .labels list. It will be repopulated when used the next time. nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); @@ -745,6 +745,16 @@ void nsGenericHTMLElement::AfterSetPopoverAttr() { } } +void nsGenericHTMLElement::OnAttrSetButNotChanged( + int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue, + bool aNotify) { + if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::popovertarget) { + ClearExplicitlySetAttrElement(aName); + } + return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID, aName, + aValue, aNotify); +} + void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, @@ -763,7 +773,7 @@ void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this, &nsGenericHTMLElement::AfterSetPopoverAttr)); } else if (aName == nsGkAtoms::popovertarget) { - ClearExplicitlySetAttrElement(nsGkAtoms::popovertarget); + ClearExplicitlySetAttrElement(aName); } else if (aName == nsGkAtoms::dir) { auto dir = Directionality::Ltr; // A boolean tracking whether we need to recompute our directionality. @@ -1259,25 +1269,24 @@ bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute, bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString, nsAttrValue& aResult) { using mozilla::dom::ReferrerInfo; + // This is a bit sketchy, we assume GetEnumString(…).get() points to a static + // buffer, relying on the fact that GetEnumString(…) returns a literal string. static const nsAttrValue::EnumTable kReferrerPolicyTable[] = { - {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::No_referrer), + {GetEnumString(ReferrerPolicy::No_referrer).get(), static_cast<int16_t>(ReferrerPolicy::No_referrer)}, - {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Origin), + {GetEnumString(ReferrerPolicy::Origin).get(), static_cast<int16_t>(ReferrerPolicy::Origin)}, - {ReferrerInfo::ReferrerPolicyToString( - ReferrerPolicy::Origin_when_cross_origin), + {GetEnumString(ReferrerPolicy::Origin_when_cross_origin).get(), static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)}, - {ReferrerInfo::ReferrerPolicyToString( - ReferrerPolicy::No_referrer_when_downgrade), + {GetEnumString(ReferrerPolicy::No_referrer_when_downgrade).get(), static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)}, - {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Unsafe_url), + {GetEnumString(ReferrerPolicy::Unsafe_url).get(), static_cast<int16_t>(ReferrerPolicy::Unsafe_url)}, - {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Strict_origin), + {GetEnumString(ReferrerPolicy::Strict_origin).get(), static_cast<int16_t>(ReferrerPolicy::Strict_origin)}, - {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Same_origin), + {GetEnumString(ReferrerPolicy::Same_origin).get(), static_cast<int16_t>(ReferrerPolicy::Same_origin)}, - {ReferrerInfo::ReferrerPolicyToString( - ReferrerPolicy::Strict_origin_when_cross_origin), + {GetEnumString(ReferrerPolicy::Strict_origin_when_cross_origin).get(), static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin)}, {nullptr, ReferrerPolicy::_empty}}; return aResult.ParseEnumValue(aString, kReferrerPolicyTable, false); @@ -1811,14 +1820,14 @@ nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext, return NS_OK; } -void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) { +void nsGenericHTMLFormElement::UnbindFromTree(UnbindContext& aContext) { // Save state before doing anything else. SaveState(); if (IsFormAssociatedElement()) { if (HTMLFormElement* form = GetFormInternal()) { // Might need to unset form - if (aNullParent) { + if (aContext.IsUnbindRoot(this)) { // No more parent means no more form ClearForm(true, true); } else { @@ -1839,7 +1848,7 @@ void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) { } } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); // The element might not have a fieldset anymore. UpdateFieldSet(false); diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index f6e7d2415d..227e052fdf 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -332,7 +332,7 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase { public: // Implementation for nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; Focusable IsFocusableWithoutStyle(bool aWithMouse) override { Focusable result; @@ -727,6 +727,10 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase { const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify) override; + void OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValueOrString& aValue, + bool aNotify) override; + MOZ_CAN_RUN_SCRIPT void AfterSetPopoverAttr(); mozilla::EventListenerManager* GetEventListenerManagerForAttr( @@ -1004,7 +1008,7 @@ class nsGenericHTMLFormElement : public nsGenericHTMLElement { // nsIContent void SaveSubtreeState() override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; /** * This callback is called by a fieldest on all its elements whenever its diff --git a/dom/html/nsGenericHTMLFrameElement.cpp b/dom/html/nsGenericHTMLFrameElement.cpp index ae2c4dcce5..92591d7b72 100644 --- a/dom/html/nsGenericHTMLFrameElement.cpp +++ b/dom/html/nsGenericHTMLFrameElement.cpp @@ -35,7 +35,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement, @@ -45,22 +44,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement, } NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAPI) NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED( - nsGenericHTMLFrameElement, nsGenericHTMLElement, nsFrameLoaderOwner, - nsIDOMMozBrowserFrame, nsIMozBrowserFrame, nsGenericHTMLFrameElement) - -NS_IMETHODIMP -nsGenericHTMLFrameElement::GetMozbrowser(bool* aValue) { - *aValue = GetBoolAttr(nsGkAtoms::mozbrowser); - return NS_OK; -} -NS_IMETHODIMP -nsGenericHTMLFrameElement::SetMozbrowser(bool aValue) { - return SetBoolAttr(nsGkAtoms::mozbrowser, aValue); -} +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement, + nsGenericHTMLElement, + nsFrameLoaderOwner, + nsGenericHTMLFrameElement) int32_t nsGenericHTMLFrameElement::TabIndexDefault() { return 0; } @@ -202,7 +191,7 @@ nsresult nsGenericHTMLFrameElement::BindToTree(BindContext& aContext, return rv; } -void nsGenericHTMLFrameElement::UnbindFromTree(bool aNullParent) { +void nsGenericHTMLFrameElement::UnbindFromTree(UnbindContext& aContext) { if (mFrameLoader) { // This iframe is being taken out of the document, destroy the // iframe's frame loader (doing that will tear down the window in @@ -214,7 +203,7 @@ void nsGenericHTMLFrameElement::UnbindFromTree(bool aNullParent) { mFrameLoader = nullptr; } - nsGenericHTMLElement::UnbindFromTree(aNullParent); + nsGenericHTMLElement::UnbindFromTree(aContext); } /* static */ @@ -260,9 +249,6 @@ void nsGenericHTMLFrameElement::AfterSetAttr( child->SendScrollbarPreferenceChanged(pref); } } - } else if (aName == nsGkAtoms::mozbrowser) { - mReallyIsBrowser = !!aValue && XRE_IsParentProcess() && - NodePrincipal()->IsSystemPrincipal(); } } @@ -336,28 +322,3 @@ bool nsGenericHTMLFrameElement::IsHTMLFocusable(bool aWithMouse, *aIsFocusable = true; return false; } - -/** - * Return true if this frame element really is a mozbrowser. (It - * needs to have the right attributes, and its creator must have the right - * permissions.) - */ -/* [infallible] */ -nsresult nsGenericHTMLFrameElement::GetReallyIsBrowser(bool* aOut) { - *aOut = mReallyIsBrowser; - return NS_OK; -} - -NS_IMETHODIMP -nsGenericHTMLFrameElement::InitializeBrowserAPI() { - MOZ_ASSERT(mFrameLoader); - InitBrowserElementAPI(); - return NS_OK; -} - -NS_IMETHODIMP -nsGenericHTMLFrameElement::DestroyBrowserFrameScripts() { - MOZ_ASSERT(mFrameLoader); - DestroyBrowserElementFrameScripts(); - return NS_OK; -} diff --git a/dom/html/nsGenericHTMLFrameElement.h b/dom/html/nsGenericHTMLFrameElement.h index 4ac6401721..587e861b88 100644 --- a/dom/html/nsGenericHTMLFrameElement.h +++ b/dom/html/nsGenericHTMLFrameElement.h @@ -8,12 +8,10 @@ #define nsGenericHTMLFrameElement_h #include "mozilla/Attributes.h" -#include "mozilla/dom/nsBrowserElement.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsGenericHTMLElement.h" -#include "nsIMozBrowserFrame.h" namespace mozilla { class ErrorResult; @@ -38,39 +36,30 @@ class XULFrameElement; * A helper class for frame elements */ class nsGenericHTMLFrameElement : public nsGenericHTMLElement, - public nsFrameLoaderOwner, - public mozilla::nsBrowserElement, - public nsIMozBrowserFrame { + public nsFrameLoaderOwner { public: nsGenericHTMLFrameElement( already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, mozilla::dom::FromParser aFromParser) : nsGenericHTMLElement(std::move(aNodeInfo)), mSrcLoadHappened(false), - mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK), - mBrowserFrameListenersRegistered(false), - mReallyIsBrowser(false) {} + mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK) {} NS_DECL_ISUPPORTS_INHERITED - NS_DECL_NSIDOMMOZBROWSERFRAME - NS_DECL_NSIMOZBROWSERFRAME - NS_DECLARE_STATIC_IID_ACCESSOR(NS_GENERICHTMLFRAMEELEMENT_IID) // nsIContent virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) override; virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void DestroyContent() override; nsresult CopyInnerTo(mozilla::dom::Element* aDest); virtual int32_t TabIndexDefault() override; - virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() override { return this; } - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement) @@ -94,11 +83,6 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement, return mSrcTriggeringPrincipal; } - // Needed for nsBrowserElement - already_AddRefed<nsFrameLoader> GetFrameLoader() override { - return nsFrameLoaderOwner::GetFrameLoader(); - } - protected: virtual ~nsGenericHTMLFrameElement(); @@ -132,9 +116,6 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement, */ bool mNetworkCreated; - bool mBrowserFrameListenersRegistered; - bool mReallyIsBrowser; - // This flag is only used by <iframe>. See HTMLIFrameElement:: // FullscreenFlag() for details. It is placed here so that we // do not bloat any struct. diff --git a/dom/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp index 0c22b3e9aa..85bf8375e2 100644 --- a/dom/html/nsHTMLContentSink.cpp +++ b/dom/html/nsHTMLContentSink.cpp @@ -53,8 +53,6 @@ #include "nsIDocShell.h" #include "nsIScriptContext.h" -#include "nsLayoutCID.h" - #include "nsEscape.h" #include "nsNodeInfoManager.h" #include "nsContentCreatorFunctions.h" diff --git a/dom/html/test/formData_test.js b/dom/html/test/formData_test.js index 3997aff4d1..8dff5bd606 100644 --- a/dom/html/test/formData_test.js +++ b/dom/html/test/formData_test.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function testHas() { var f = new FormData(); f.append("foo", "bar"); diff --git a/dom/html/test/forms/test_change_event.html b/dom/html/test/forms/test_change_event.html index 8be4554c58..b20a623439 100644 --- a/dom/html/test/forms/test_change_event.html +++ b/dom/html/test/forms/test_change_event.html @@ -61,7 +61,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=722599 SimpleTest.waitForExplicitFinish(); var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); function fileInputBlurTest() { var btn = document.getElementById('fileInput'); diff --git a/dom/html/test/forms/test_input_event.html b/dom/html/test/forms/test_input_event.html index 72863ca335..d3fbba4b3b 100644 --- a/dom/html/test/forms/test_input_event.html +++ b/dom/html/test/forms/test_input_event.html @@ -149,7 +149,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780 } var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window); + MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); function testUserInput() { // Simulating an OK click and with a file name return. diff --git a/dom/html/test/forms/test_input_file_picker.html b/dom/html/test/forms/test_input_file_picker.html index 296c12bb7e..f05ee9e6a0 100644 --- a/dom/html/test/forms/test_input_file_picker.html +++ b/dom/html/test/forms/test_input_file_picker.html @@ -78,7 +78,7 @@ SimpleTest.waitForExplicitFinish(); SimpleTest.requestFlakyTimeout("untriaged"); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); // The following lists are from toolkit/content/filepicker.properties which is used by filePicker var imageExtensionList = "*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw; *.webp; *.heic" diff --git a/dom/html/test/mochitest.toml b/dom/html/test/mochitest.toml index d1dd78705b..d9a3b34b58 100644 --- a/dom/html/test/mochitest.toml +++ b/dom/html/test/mochitest.toml @@ -190,10 +190,13 @@ support-files = [ ["test_allowMedia.html"] skip-if = [ - "verify && (os == 'linux' || os == 'win')", - "!debug && os == 'mac' && bits == 64", - "debug && os == 'win'", - "debug && os == 'linux' && os_version == '18.04'", #Bug 1434744 + "apple_catalina && !debug", + "apple_silicon && !debug", + "os == 'linux' && os_version == '18.04' && verify", + "os == 'linux' && os_version == '18.04' && debug", #Bug 1434744 + "win11_2009 && verify", + "win10_2009 && debug", + "win11_2009 && debug", ] ["test_anchor_href_cache_invalidation.html"] @@ -324,7 +327,7 @@ skip-if = ["os == 'android'"] #TIMED_OUT ["test_bug369370.html"] skip-if = [ "os == 'android'", - "os == 'linux'", # disabled on linux bug 1258103 + "os == 'linux' && os_version == '18.04'", # disabled on linux bug 1258103 ] ["test_bug371375.html"] @@ -597,7 +600,8 @@ fail-if = ["xorigin"] ["test_bug615833.html"] skip-if = [ "os == 'android'", - "os == 'mac'", #TIMED_OUT # form control not selected/checked with synthesizeMouse, osx(bug 1275664) + "apple_catalina", #TIMED_OUT # form control not selected/checked with synthesizeMouse, osx(bug 1275664) + "apple_silicon", #TIMED_OUT # form control not selected/checked with synthesizeMouse, osx(bug 1275664) ] ["test_bug618948.html"] @@ -769,17 +773,22 @@ skip-if = [ ["test_bug1261673.html"] skip-if = [ "os == 'android'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["test_bug1261674-1.html"] skip-if = [ "os == 'android'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["test_bug1261674-2.html"] -skip-if = ["os == 'mac'"] +skip-if = [ + "apple_catalina", + "apple_silicon", +] ["test_bug1264157.html"] @@ -979,7 +988,8 @@ tags = "openwindow" skip-if = [ "os == 'android' && debug", "os == 'linux'", - "os == 'win' && debug && bits == 64", # Bug 1533759 + "win11_2009 && bits == 64 && debug", # Bug 1533759 + "win10_2009 && bits == 64 && debug", # Bug 1533759 ] ["test_window_open_from_closing.html"] diff --git a/dom/html/test/test_bug389797.html b/dom/html/test/test_bug389797.html index 701d6e65c9..66396ce54f 100644 --- a/dom/html/test/test_bug389797.html +++ b/dom/html/test/test_bug389797.html @@ -115,7 +115,6 @@ HTML_TAG("figure", "") HTML_TAG("font", "Font"); HTML_TAG("footer", "") HTML_TAG("form", "Form"); -HTML_TAG("frame", "Frame", [ "nsIDOMMozBrowserFrame" ]); HTML_TAG("frameset", "FrameSet"); HTML_TAG("h1", "Heading"); HTML_TAG("h2", "Heading"); @@ -129,7 +128,6 @@ HTML_TAG("hgroup", "") HTML_TAG("hr", "HR"); HTML_TAG("html", "Html"); HTML_TAG("i", ""); -HTML_TAG("iframe", "IFrame", [ "nsIDOMMozBrowserFrame" ]); HTML_TAG("image", ""); HTML_TAG("img", "Image", [ "nsIImageLoadingContent" ], []); HTML_TAG("input", "Input", [], [ "imgINotificationObserver", @@ -171,7 +169,7 @@ HTML_TAG("rtc", ""); HTML_TAG("ruby", ""); HTML_TAG("s", ""); HTML_TAG("samp", ""); -HTML_TAG("script", "Script", [ "nsIScriptLoaderObserver" ], []); +HTML_TAG("script", "Script"); HTML_TAG("section", "") HTML_TAG("select", "Select"); HTML_TAG("small", ""); diff --git a/dom/html/test/test_bug500885.html b/dom/html/test/test_bug500885.html index 3ab9225a4c..5972ed171e 100644 --- a/dom/html/test/test_bug500885.html +++ b/dom/html/test/test_bug500885.html @@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=500885 <script type="text/javascript"> var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); MockFilePicker.returnValue = MockFilePicker.returnOK; async function test() { diff --git a/dom/html/test/test_bug556645.html b/dom/html/test/test_bug556645.html index 3c308f9ef6..08a230370f 100644 --- a/dom/html/test/test_bug556645.html +++ b/dom/html/test/test_bug556645.html @@ -56,10 +56,12 @@ function runTest(aObjectOrEmbed) const pbutton = document.getElementById("pbutton"); pbutton.focus(); - synthesizeKey("KEY_Tab"); - is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab`); - is(childDoc.activeElement, childDoc.documentElement, `${desc}: focus in child after tab`); - + let canTabMoveFocusToRootElement = !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element"); + if (canTabMoveFocusToRootElement) { + synthesizeKey("KEY_Tab"); + is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab`); + is(childDoc.activeElement, childDoc.documentElement, `${desc}: focus in child after tab`); + } synthesizeKey("KEY_Tab"); is(document.activeElement, aObjectOrEmbed, `${desc}: focus in parent after tab 2`); is(childDoc.activeElement, button, `${desc}: focus in child after tab 2`); diff --git a/dom/html/test/test_bug592802.html b/dom/html/test/test_bug592802.html index e8b30d84c8..701559237e 100644 --- a/dom/html/test/test_bug592802.html +++ b/dom/html/test/test_bug592802.html @@ -26,7 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=592802 SimpleTest.waitForExplicitFinish(); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); var testData = [ /* visibility | display | multiple */ diff --git a/dom/html/test/test_filepicker_default_directory.html b/dom/html/test/test_filepicker_default_directory.html index 2be811655a..565687235f 100644 --- a/dom/html/test/test_filepicker_default_directory.html +++ b/dom/html/test/test_filepicker_default_directory.html @@ -34,7 +34,7 @@ var customUploadDirectory = Cc["@mozilla.org/file/directory_service;1"] //info("customUploadDirectory" + customUploadDirectory.path); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); // need to show the MockFilePicker so .displayDirectory gets set var f = document.getElementById("f"); diff --git a/dom/html/test/test_input_file_cancel_event.html b/dom/html/test/test_input_file_cancel_event.html index f0fd81c433..64db440485 100644 --- a/dom/html/test/test_input_file_cancel_event.html +++ b/dom/html/test/test_input_file_cancel_event.html @@ -14,7 +14,7 @@ SimpleTest.waitForExplicitFinish(); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); MockFilePicker.useBlobFile(); MockFilePicker.returnValue = MockFilePicker.returnCancel; diff --git a/dom/html/test/test_input_files_not_nsIFile.html b/dom/html/test/test_input_files_not_nsIFile.html index e70bc093ee..57a3795de8 100644 --- a/dom/html/test/test_input_files_not_nsIFile.html +++ b/dom/html/test/test_input_files_not_nsIFile.html @@ -20,7 +20,7 @@ SimpleTest.waitForExplicitFinish(); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); SimpleTest.waitForFocus(function() { MockFilePicker.useBlobFile(); diff --git a/dom/html/test/test_multipleFilePicker.html b/dom/html/test/test_multipleFilePicker.html index c4a71151aa..9b342bdfaf 100644 --- a/dom/html/test/test_multipleFilePicker.html +++ b/dom/html/test/test_multipleFilePicker.html @@ -25,7 +25,7 @@ foo.addEventListener('click', _ => { }); let MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); let pickerCount = 0; diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index bc0baf1157..47360094ca 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -291,7 +291,8 @@ static_assert(kSQLiteGrowthIncrement >= 0 && "Must be 0 (disabled) or a positive multiple of the page size!"); // The maximum number of threads that can be used for database activity at a -// single time. +// single time. Please keep in sync with the constants in +// test_connection_idle_maintenance*.js tests const uint32_t kMaxConnectionThreadCount = 20; static_assert(kMaxConnectionThreadCount, "Must have at least one thread!"); @@ -304,7 +305,8 @@ static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount, "Idle thread limit must be less than total thread limit!"); // The length of time that database connections will be held open after all -// transactions have completed before doing idle maintenance. +// transactions have completed before doing idle maintenance. Please keep in +// sync with the timeouts in test_connection_idle_maintenance*.js tests const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds // The length of time that database connections will be held open after all @@ -1162,7 +1164,8 @@ class DatabaseConnection final : public CachingDatabaseConnection { return CheckpointInternal(CheckpointMode::Full); } - void DoIdleProcessing(bool aNeedsCheckpoint); + void DoIdleProcessing(bool aNeedsCheckpoint, + const Atomic<bool>& aInterrupted); void Close(); @@ -1189,7 +1192,8 @@ class DatabaseConnection final : public CachingDatabaseConnection { */ Result<bool, nsresult> ReclaimFreePagesWhileIdle( CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement, - uint32_t aFreelistCount, bool aNeedsCheckpoint); + uint32_t aFreelistCount, bool aNeedsCheckpoint, + const Atomic<bool>& aInterrupted); Result<int64_t, nsresult> GetFileSize(const nsAString& aPath); }; @@ -1362,6 +1366,34 @@ class ConnectionPool final { } }; + struct PerformingIdleMaintenanceDatabaseInfo { + const NotNull<DatabaseInfo*> mDatabaseInfo; + RefPtr<IdleConnectionRunnable> mIdleConnectionRunnable; + + PerformingIdleMaintenanceDatabaseInfo( + DatabaseInfo& aDatabaseInfo, + RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable); + + PerformingIdleMaintenanceDatabaseInfo( + const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete; + PerformingIdleMaintenanceDatabaseInfo( + PerformingIdleMaintenanceDatabaseInfo&& aOther) noexcept + : mDatabaseInfo{aOther.mDatabaseInfo}, + mIdleConnectionRunnable{std::move(aOther.mIdleConnectionRunnable)} { + MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); + } + PerformingIdleMaintenanceDatabaseInfo& operator=( + const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete; + PerformingIdleMaintenanceDatabaseInfo& operator=( + PerformingIdleMaintenanceDatabaseInfo&& aOther) = delete; + + ~PerformingIdleMaintenanceDatabaseInfo(); + + bool operator==(const DatabaseInfo* aDatabaseInfo) const { + return mDatabaseInfo == aDatabaseInfo; + } + }; + class ThreadInfo { public: ThreadInfo(); @@ -1456,7 +1488,8 @@ class ConnectionPool final { nsCOMPtr<nsIThreadPool> mIOTarget; nsTArray<IdleThreadInfo> mIdleThreads; nsTArray<IdleDatabaseInfo> mIdleDatabases; - nsTArray<NotNull<DatabaseInfo*>> mDatabasesPerformingIdleMaintenance; + nsTArray<PerformingIdleMaintenanceDatabaseInfo> + mDatabasesPerformingIdleMaintenance; nsCOMPtr<nsITimer> mIdleTimer; TimeStamp mTargetIdleTime; @@ -1559,6 +1592,7 @@ class ConnectionPool::ConnectionRunnable : public Runnable { class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable { const bool mNeedsCheckpoint; + Atomic<bool> mInterrupted; public: IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint) @@ -1567,6 +1601,8 @@ class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable { NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable, ConnectionRunnable) + void Interrupt() { mInterrupted = true; } + private: ~IdleConnectionRunnable() override = default; @@ -2061,6 +2097,7 @@ class TransactionDatabaseOperationBase : public DatabaseOperationBase { class Factory final : public PBackgroundIDBFactoryParent, public AtomicSafeRefCounted<Factory> { + nsCString mSystemLocale; RefPtr<DatabaseLoggingInfo> mLoggingInfo; #ifdef DEBUG @@ -2072,7 +2109,7 @@ class Factory final : public PBackgroundIDBFactoryParent, public: [[nodiscard]] static SafeRefPtr<Factory> Create( - const LoggingInfo& aLoggingInfo); + const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale); DatabaseLoggingInfo* GetLoggingInfo() const { AssertIsOnBackgroundThread(); @@ -2081,11 +2118,14 @@ class Factory final : public PBackgroundIDBFactoryParent, return mLoggingInfo; } + const nsCString& GetSystemLocale() const { return mSystemLocale; } + MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory) MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted) // Only constructed in Create(). - explicit Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo); + Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo, + const nsACString& aSystemLocale); // IPDL methods are only called by IPDL. void ActorDestroy(ActorDestroyReason aWhy) override; @@ -2961,17 +3001,11 @@ class FactoryOp protected: enum class State { - // Just created on the PBackground thread, dispatched to the main thread. - // Next step is either SendingResults if permission is denied, - // PermissionChallenge if the permission is unknown, or FinishOpen - // if permission is granted. + // Just created on the PBackground thread, dispatched to the current thread. + // Next step is either SendingResults if opening initialization failed, or + // DirectoryOpenPending if the opening initialization succeeded. Initial, - // Ensuring quota manager is created and opening directory on the - // PBackground thread. Next step is either SendingResults if quota manager - // is not available or DirectoryOpenPending if quota manager is available. - FinishOpen, - // Waiting for directory open allowed on the PBackground thread. The next // step is either SendingResults if directory lock failed to acquire, or // DatabaseOpenPending if directory lock is acquired. @@ -3129,10 +3163,6 @@ class FactoryOp virtual void SendBlockedNotification() = 0; private: - mozilla::Result<PermissionValue, nsresult> CheckPermission(); - - nsresult FinishOpen(); - // Test whether this FactoryOp needs to wait for the given op. bool MustWaitFor(const FactoryOp& aExistingOp); }; @@ -6517,7 +6547,7 @@ already_AddRefed<nsIThreadPool> MakeConnectionIOTarget() { ******************************************************************************/ already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent( - const LoggingInfo& aLoggingInfo) { + const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { @@ -6531,15 +6561,15 @@ already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent( return nullptr; } - SafeRefPtr<Factory> actor = Factory::Create(aLoggingInfo); + SafeRefPtr<Factory> actor = Factory::Create(aLoggingInfo, aSystemLocale); MOZ_ASSERT(actor); return actor.forget(); } bool RecvPBackgroundIDBFactoryConstructor( - PBackgroundIDBFactoryParent* aActor, - const LoggingInfo& /* aLoggingInfo */) { + PBackgroundIDBFactoryParent* aActor, const LoggingInfo& /* aLoggingInfo */, + const nsACString& /* aSystemLocale */) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); @@ -6862,7 +6892,8 @@ nsresult DatabaseConnection::CheckpointInternal(CheckpointMode aMode) { return NS_OK; } -void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint) { +void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint, + const Atomic<bool>& aInterrupted) { AssertIsOnConnectionThread(); MOZ_ASSERT(mInReadTransaction); MOZ_ASSERT(!mInWriteTransaction); @@ -6887,22 +6918,23 @@ void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint) { mInReadTransaction = false; } - const bool freedSomePages = freelistCount && [this, &freelistStmt, - &rollbackStmt, freelistCount, - aNeedsCheckpoint] { - // Warn in case of an error, but do not propagate it. Just indicate we - // didn't free any pages. - QM_TRY_INSPECT(const bool& res, - ReclaimFreePagesWhileIdle(freelistStmt, rollbackStmt, - freelistCount, aNeedsCheckpoint), - false); + const bool freedSomePages = + freelistCount && [this, &freelistStmt, &rollbackStmt, freelistCount, + aNeedsCheckpoint, &aInterrupted] { + // Warn in case of an error, but do not propagate it. Just indicate we + // didn't free any pages. + QM_TRY_INSPECT( + const bool& res, + ReclaimFreePagesWhileIdle(freelistStmt, rollbackStmt, freelistCount, + aNeedsCheckpoint, aInterrupted), + false); - // Make sure we didn't leave a transaction running. - MOZ_ASSERT(!mInReadTransaction); - MOZ_ASSERT(!mInWriteTransaction); + // Make sure we didn't leave a transaction running. + MOZ_ASSERT(!mInReadTransaction); + MOZ_ASSERT(!mInWriteTransaction); - return res; - }(); + return res; + }(); // Truncate the WAL if we were asked to or if we managed to free some space. if (aNeedsCheckpoint || freedSomePages) { @@ -6922,7 +6954,8 @@ void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint) { Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement, - uint32_t aFreelistCount, bool aNeedsCheckpoint) { + uint32_t aFreelistCount, bool aNeedsCheckpoint, + const Atomic<bool>& aInterrupted) { AssertIsOnConnectionThread(); MOZ_ASSERT(aFreelistStatement); MOZ_ASSERT(aRollbackStatement); @@ -6932,11 +6965,14 @@ Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( AUTO_PROFILER_LABEL("DatabaseConnection::ReclaimFreePagesWhileIdle", DOM); - // Make sure we don't keep working if anything else needs this thread. - nsIThread* currentThread = NS_GetCurrentThread(); - MOZ_ASSERT(currentThread); + uint32_t pauseOnConnectionThreadMs = StaticPrefs:: + dom_indexedDB_connectionIdleMaintenance_pauseOnConnectionThreadMs(); + if (pauseOnConnectionThreadMs > 0) { + PR_Sleep(PR_MillisecondsToInterval(pauseOnConnectionThreadMs)); + } - if (NS_HasPendingEvents(currentThread)) { + // Make sure we don't keep working if anything else needs this thread. + if (aInterrupted) { return false; } @@ -6968,7 +7004,7 @@ Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( mInWriteTransaction = true; - bool freedSomePages = false, interrupted = false; + bool freedSomePages = false; const auto rollback = [&aRollbackStatement, this](const auto&) { MOZ_ASSERT(mInWriteTransaction); @@ -6984,14 +7020,12 @@ Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( uint64_t previousFreelistCount = (uint64_t)aFreelistCount + 1; QM_TRY(CollectWhile( - [&aFreelistCount, &previousFreelistCount, &interrupted, - currentThread]() -> Result<bool, nsresult> { - if (NS_HasPendingEvents(currentThread)) { - // Abort if something else wants to use the thread, and - // roll back this transaction. It's ok if we never make - // progress here because the idle service should - // eventually reclaim this space. - interrupted = true; + [&aFreelistCount, &previousFreelistCount, + &aInterrupted]() -> Result<bool, nsresult> { + if (aInterrupted) { + // On interrupt, abort and roll back this transaction. It's ok + // if we never make progress here because the idle service + // should eventually reclaim this space. return false; } // If we were not able to free anything, we might either see @@ -7001,7 +7035,7 @@ Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( bool madeProgress = previousFreelistCount != aFreelistCount; previousFreelistCount = aFreelistCount; MOZ_ASSERT(madeProgress); - QM_WARNONLY_TRY(MOZ_TO_RESULT(!madeProgress)); + QM_WARNONLY_TRY(MOZ_TO_RESULT(madeProgress)); return madeProgress && (aFreelistCount != 0); }, [&aFreelistStatement, &aFreelistCount, &incrementalVacuumStmt, @@ -7015,9 +7049,9 @@ Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( return Ok{}; }) - .andThen([&commitStmt, &freedSomePages, &interrupted, &rollback, + .andThen([&commitStmt, &freedSomePages, &aInterrupted, &rollback, this](Ok) -> Result<Ok, nsresult> { - if (interrupted) { + if (aInterrupted) { rollback(Ok{}); freedSomePages = false; } @@ -7982,8 +8016,9 @@ void ConnectionPool::CloseIdleDatabases() { } if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) { - for (const auto dbInfo : mDatabasesPerformingIdleMaintenance) { - CloseDatabase(*dbInfo); + for (PerformingIdleMaintenanceDatabaseInfo& performingIdleMaintenanceInfo : + mDatabasesPerformingIdleMaintenance) { + CloseDatabase(*performingIdleMaintenanceInfo.mDatabaseInfo); } mDatabasesPerformingIdleMaintenance.Clear(); } @@ -8059,16 +8094,13 @@ bool ConnectionPool::ScheduleTransaction(TransactionInfo& aTransactionInfo, // deliberately const to prevent the attempt to wrongly optimize the // refcounting by passing runnable.forget() to the Dispatch method, see // bug 1598559. - const nsCOMPtr<nsIRunnable> runnable = - new Runnable("IndexedDBDummyRunnable"); for (uint32_t index = mDatabasesPerformingIdleMaintenance.Length(); index > 0; index--) { - const auto dbInfo = mDatabasesPerformingIdleMaintenance[index - 1]; - dbInfo->mThreadInfo.AssertValid(); + const auto& performingIdleMaintenanceInfo = + mDatabasesPerformingIdleMaintenance[index - 1]; - MOZ_ALWAYS_SUCCEEDS(dbInfo->mThreadInfo.ThreadRef().Dispatch( - runnable, NS_DISPATCH_NORMAL)); + performingIdleMaintenanceInfo.mIdleConnectionRunnable->Interrupt(); } } @@ -8369,12 +8401,15 @@ void ConnectionPool::PerformIdleDatabaseMaintenance( aDatabaseInfo.mNeedsCheckpoint = false; aDatabaseInfo.mIdle = false; + auto idleConnectionRunnable = + MakeRefPtr<IdleConnectionRunnable>(aDatabaseInfo, neededCheckpoint); + mDatabasesPerformingIdleMaintenance.AppendElement( - WrapNotNullUnchecked(&aDatabaseInfo)); + PerformingIdleMaintenanceDatabaseInfo{aDatabaseInfo, + idleConnectionRunnable}); MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mThreadInfo.ThreadRef().Dispatch( - MakeAndAddRef<IdleConnectionRunnable>(aDatabaseInfo, neededCheckpoint), - NS_DISPATCH_NORMAL)); + idleConnectionRunnable.forget(), NS_DISPATCH_NORMAL)); } void ConnectionPool::CloseDatabase(DatabaseInfo& aDatabaseInfo) const { @@ -8437,7 +8472,8 @@ ConnectionPool::IdleConnectionRunnable::Run() { // The connection could be null if EnsureConnection() didn't run or was not // successful in TransactionDatabaseOperationBase::RunOnConnectionThread(). if (mDatabaseInfo.mConnection) { - mDatabaseInfo.mConnection->DoIdleProcessing(mNeedsCheckpoint); + mDatabaseInfo.mConnection->DoIdleProcessing(mNeedsCheckpoint, + mInterrupted); } MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL)); @@ -8733,6 +8769,25 @@ ConnectionPool::IdleDatabaseInfo::~IdleDatabaseInfo() { MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo); } +ConnectionPool::PerformingIdleMaintenanceDatabaseInfo:: + PerformingIdleMaintenanceDatabaseInfo( + DatabaseInfo& aDatabaseInfo, + RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable) + : mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)), + mIdleConnectionRunnable(std::move(aIdleConnectionRunnable)) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mIdleConnectionRunnable); + + MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); +} + +ConnectionPool::PerformingIdleMaintenanceDatabaseInfo:: + ~PerformingIdleMaintenanceDatabaseInfo() { + AssertIsOnBackgroundThread(); + + MOZ_COUNT_DTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); +} + ConnectionPool::IdleThreadInfo::IdleThreadInfo(ThreadInfo aThreadInfo) : IdleResource(TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(kConnectionThreadIdleMS)), @@ -8915,8 +8970,10 @@ DatabaseLoggingInfo::~DatabaseLoggingInfo() { * Factory ******************************************************************************/ -Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo) - : mLoggingInfo(std::move(aLoggingInfo)) +Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo, + const nsACString& aSystemLocale) + : mSystemLocale(aSystemLocale), + mLoggingInfo(std::move(aLoggingInfo)) #ifdef DEBUG , mActorDestroyed(false) @@ -8929,7 +8986,8 @@ Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo) Factory::~Factory() { MOZ_ASSERT(mActorDestroyed); } // static -SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo) { +SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo, + const nsACString& aSystemLocale) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); @@ -8966,7 +9024,7 @@ SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo) { return do_AddRef(entry.Data()); }); - return MakeSafeRefPtr<Factory>(std::move(loggingInfo)); + return MakeSafeRefPtr<Factory>(std::move(loggingInfo), aSystemLocale); } void Factory::ActorDestroy(ActorDestroyReason aWhy) { @@ -9045,6 +9103,14 @@ Factory::AllocPBackgroundIDBFactoryRequestParent( return nullptr; } + if (NS_AUUF_OR_WARN_IF( + principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && + QuotaManager::IsOriginInternal( + principalInfo.get_ContentPrincipalInfo().originNoSuffix()) && + metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) { + return nullptr; + } + Maybe<ContentParentId> contentParentId; uint64_t childID = BackgroundParent::GetChildID(Manager()); @@ -9087,7 +9153,7 @@ mozilla::ipc::IPCResult Factory::RecvPBackgroundIDBFactoryRequestConstructor( auto* op = static_cast<FactoryOp*>(aActor); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(op)); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op)); return IPC_OK(); } @@ -12247,8 +12313,9 @@ nsresult QuotaClient::GetUsageForOriginInternal( // If this fails, it probably means we are in a serious situation. // e.g. Filesystem corruption. Will handle this in bug 1521541. QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory( - *directory, subdirNameBase, nullptr, aPersistenceType, - aOriginMetadata, u""_ns)), + *directory, subdirNameBase, /* aQuotaManager */ nullptr, + aPersistenceType, aOriginMetadata, + /* aDatabaseName */ u""_ns)), Err(NS_ERROR_UNEXPECTED)); databaseFilenames.Remove(subdirNameBase); @@ -14476,10 +14543,6 @@ void FactoryOp::StringifyState(nsACString& aResult) const { aResult.AppendLiteral("Initial"); return; - case State::FinishOpen: - aResult.AppendLiteral("FinishOpen"); - return; - case State::DirectoryOpenPending: aResult.AppendLiteral("DirectoryOpenPending"); return; @@ -14538,23 +14601,50 @@ void FactoryOp::Stringify(nsACString& aResult) const { } nsresult FactoryOp::Open() { - AssertIsOnMainThread(); + AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::Initial); + MOZ_ASSERT(mOriginMetadata.mOrigin.IsEmpty()); + MOZ_ASSERT(!mDirectoryLock); - if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || - !OperationMayProceed()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsActorDestroyed()) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } + QM_TRY(QuotaManager::EnsureCreated()); + + QuotaManager* const quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + const DatabaseMetadata& metadata = mCommonParams.metadata(); + + const PersistenceType persistenceType = metadata.persistenceType(); + const PrincipalInfo& principalInfo = mCommonParams.principalInfo(); + + QM_TRY_UNWRAP(auto principalMetadata, + quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo)); + + mOriginMetadata = {std::move(principalMetadata), persistenceType}; + if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { MOZ_ASSERT(mCommonParams.metadata().persistenceType() == PERSISTENCE_TYPE_PERSISTENT); + + mEnforcingQuota = false; } else if (principalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { const ContentPrincipalInfo& contentPrincipalInfo = principalInfo.get_ContentPrincipalInfo(); - if (contentPrincipalInfo.attrs().mPrivateBrowsingId != 0) { + + MOZ_ASSERT_IF( + QuotaManager::IsOriginInternal(contentPrincipalInfo.originNoSuffix()), + mCommonParams.metadata().persistenceType() == + PERSISTENCE_TYPE_PERSISTENT); + + mEnforcingQuota = persistenceType != PERSISTENCE_TYPE_PERSISTENT; + + if (mOriginMetadata.mIsPrivate) { if (StaticPrefs::dom_indexedDB_privateBrowsing_enabled()) { // Explicitly disallow moz-extension urls from using the encrypted // indexedDB storage mode when the caller is an extension (see Bug @@ -14573,35 +14663,46 @@ nsresult FactoryOp::Open() { MOZ_ASSERT(false); } - QM_TRY_INSPECT(const auto& permission, CheckPermission()); + QuotaManager::GetStorageId(persistenceType, mOriginMetadata.mOrigin, + Client::IDB, mDatabaseId); - MOZ_ASSERT(permission == PermissionValue::kPermissionAllowed || - permission == PermissionValue::kPermissionDenied); + mDatabaseId.Append('*'); + mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name())); - if (permission == PermissionValue::kPermissionDenied) { - return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; - } + // Need to get database file path before opening the directory. + // XXX: For what reason? + QM_TRY_UNWRAP( + mDatabaseFilePath, + ([this, metadata, quotaManager]() -> mozilla::Result<nsString, nsresult> { + QM_TRY_INSPECT(const auto& dbFile, + quotaManager->GetOriginDirectory(mOriginMetadata)); - { - // These services have to be started on the main thread currently. + QM_TRY(MOZ_TO_RESULT(dbFile->Append( + NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); - IndexedDatabaseManager* mgr; - if (NS_WARN_IF(!(mgr = IndexedDatabaseManager::GetOrCreate()))) { - IDB_REPORT_INTERNAL_ERR(); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } + QM_TRY(MOZ_TO_RESULT( + dbFile->Append(GetDatabaseFilenameBase(metadata.name(), + mOriginMetadata.mIsPrivate) + + kSQLiteSuffix))); - nsCOMPtr<mozIStorageService> ss; - if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) { - IDB_REPORT_INTERNAL_ERR(); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - } + QM_TRY_RETURN( + MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); + }())); - MOZ_ASSERT(permission == PermissionValue::kPermissionAllowed); + // Open directory + mState = State::DirectoryOpenPending; - mState = State::FinishOpen; - MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); + quotaManager->OpenClientDirectory({mOriginMetadata, Client::IDB}) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr(this)]( + const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + self->DirectoryLockAcquired(aValue.ResolveValue()); + } else { + self->DirectoryLockFailed(); + } + }); return NS_OK; } @@ -14724,37 +14825,6 @@ void FactoryOp::FinishSendResults() { mFactory = nullptr; } -Result<PermissionValue, nsresult> FactoryOp::CheckPermission() { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mState == State::Initial); - - const PrincipalInfo& principalInfo = mCommonParams.principalInfo(); - MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || - principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); - - if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { - MOZ_ASSERT(mState == State::Initial); - - return PermissionValue::kPermissionAllowed; - } - - QM_TRY_INSPECT( - const auto& permission, - ([persistenceType = mCommonParams.metadata().persistenceType(), - origin = QuotaManager::GetOriginFromValidatedPrincipalInfo( - principalInfo)]() -> mozilla::Result<PermissionValue, nsresult> { - if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) { - if (QuotaManager::IsOriginInternal(origin)) { - return PermissionValue::kPermissionAllowed; - } - return Err(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); - } - return PermissionValue::kPermissionAllowed; - })()); - - return permission; -} - nsresult FactoryOp::SendVersionChangeMessages( DatabaseActorInfo* aDatabaseActorInfo, Maybe<Database&> aOpeningDatabase, uint64_t aOldVersion, const Maybe<uint64_t>& aNewVersion) { @@ -14795,91 +14865,6 @@ nsresult FactoryOp::SendVersionChangeMessages( return NS_OK; } // namespace indexedDB -nsresult FactoryOp::FinishOpen() { - AssertIsOnOwningThread(); - MOZ_ASSERT(mState == State::FinishOpen); - MOZ_ASSERT(mOriginMetadata.mOrigin.IsEmpty()); - MOZ_ASSERT(!mDirectoryLock); - - if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || - IsActorDestroyed()) { - IDB_REPORT_INTERNAL_ERR(); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - QM_TRY(QuotaManager::EnsureCreated()); - - QuotaManager* const quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - const PrincipalInfo& principalInfo = mCommonParams.principalInfo(); - - const DatabaseMetadata& metadata = mCommonParams.metadata(); - - const PersistenceType persistenceType = metadata.persistenceType(); - - if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { - mOriginMetadata = {QuotaManager::GetInfoForChrome(), persistenceType}; - - MOZ_ASSERT(QuotaManager::IsOriginInternal(mOriginMetadata.mOrigin)); - - mEnforcingQuota = false; - } else { - MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); - - QM_TRY_UNWRAP( - auto principalMetadata, - quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo)); - - mOriginMetadata = {std::move(principalMetadata), persistenceType}; - - mEnforcingQuota = persistenceType != PERSISTENCE_TYPE_PERSISTENT; - } - - QuotaManager::GetStorageId(persistenceType, mOriginMetadata.mOrigin, - Client::IDB, mDatabaseId); - - mDatabaseId.Append('*'); - mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name())); - - // Need to get database file path before opening the directory. - // XXX: For what reason? - QM_TRY_UNWRAP( - mDatabaseFilePath, - ([this, metadata, quotaManager]() -> mozilla::Result<nsString, nsresult> { - QM_TRY_INSPECT(const auto& dbFile, - quotaManager->GetOriginDirectory(mOriginMetadata)); - - QM_TRY(MOZ_TO_RESULT(dbFile->Append( - NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); - - QM_TRY(MOZ_TO_RESULT( - dbFile->Append(GetDatabaseFilenameBase(metadata.name(), - mOriginMetadata.mIsPrivate) + - kSQLiteSuffix))); - - QM_TRY_RETURN( - MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); - }())); - - // Open directory - mState = State::DirectoryOpenPending; - - quotaManager->OpenClientDirectory({mOriginMetadata, Client::IDB}) - ->Then( - GetCurrentSerialEventTarget(), __func__, - [self = RefPtr(this)]( - const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) { - if (aValue.IsResolve()) { - self->DirectoryLockAcquired(aValue.ResolveValue()); - } else { - self->DirectoryLockFailed(); - } - }); - - return NS_OK; -} - bool FactoryOp::MustWaitFor(const FactoryOp& aExistingOp) { AssertIsOnOwningThread(); @@ -14920,10 +14905,6 @@ FactoryOp::Run() { QM_WARNONLY_TRY(MOZ_TO_RESULT(Open()), handleError); break; - case State::FinishOpen: - QM_WARNONLY_TRY(MOZ_TO_RESULT(FinishOpen()), handleError); - break; - case State::DatabaseOpenPending: QM_WARNONLY_TRY(MOZ_TO_RESULT(DatabaseOpen()), handleError); break; @@ -15328,7 +15309,7 @@ nsresult OpenDatabaseOp::LoadDatabaseInformation( QM_TRY_INSPECT( const auto& lastIndexId, - ([&aConnection, + ([this, &aConnection, &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> { // Load index information QM_TRY_INSPECT( @@ -15345,7 +15326,7 @@ nsresult OpenDatabaseOp::LoadDatabaseInformation( QM_TRY(CollectWhileHasResult( *stmt, - [&lastIndexId, &objectStores, &aConnection, + [this, &lastIndexId, &objectStores, &aConnection, usedIds = Maybe<nsTHashSet<uint64_t>>{}, usedNames = Maybe<nsTHashSet<nsString>>{}]( auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { @@ -15436,8 +15417,7 @@ nsresult OpenDatabaseOp::LoadDatabaseInformation( indexMetadata->mCommonMetadata.locale(); const bool& isAutoLocale = indexMetadata->mCommonMetadata.autoLocale(); - const nsCString& systemLocale = - IndexedDatabaseManager::GetLocale(); + const nsCString& systemLocale = mFactory->GetSystemLocale(); if (!systemLocale.IsEmpty() && isAutoLocale && !indexedLocale.Equals(systemLocale)) { QM_TRY(MOZ_TO_RESULT(UpdateLocaleAwareIndex( @@ -16985,7 +16965,8 @@ TransactionBase::CommitOp::Run() { connection->FinishWriteTransaction(); if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup) { - connection->DoIdleProcessing(/* aNeedsCheckpoint */ true); + connection->DoIdleProcessing(/* aNeedsCheckpoint */ true, + /* aInterrupted */ Atomic<bool>(false)); connection->EnableQuotaChecks(); } diff --git a/dom/indexedDB/ActorsParent.h b/dom/indexedDB/ActorsParent.h index ab6c6c142f..45c0bf8688 100644 --- a/dom/indexedDB/ActorsParent.h +++ b/dom/indexedDB/ActorsParent.h @@ -38,10 +38,11 @@ class PBackgroundIDBFactoryParent; class PBackgroundIndexedDBUtilsParent; already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent( - const LoggingInfo& aLoggingInfo); + const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale); bool RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor, - const LoggingInfo& aLoggingInfo); + const LoggingInfo& aLoggingInfo, + const nsACString& aSystemLocale); bool DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor); diff --git a/dom/indexedDB/ActorsParentCommon.cpp b/dom/indexedDB/ActorsParentCommon.cpp index 637906b6d3..1b92e15dad 100644 --- a/dom/indexedDB/ActorsParentCommon.cpp +++ b/dom/indexedDB/ActorsParentCommon.cpp @@ -728,7 +728,7 @@ nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection, Span<const nsLiteralCString> aSQLCommands) { for (const auto& aSQLCommand : aSQLCommands) { const auto extraInfo = quota::ScopedLogExtraInfo{ - quota::ScopedLogExtraInfo::kTagQuery, aSQLCommand}; + quota::ScopedLogExtraInfo::kTagQueryTainted, aSQLCommand}; QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(aSQLCommand))); } diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index be34e1914d..81dce07d7e 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -242,6 +242,12 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForMainThreadJSInternal( return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } + nsresult rv = mgr->EnsureLocale(); + if (NS_WARN_IF(NS_FAILED(rv))) { + IDB_REPORT_INTERNAL_ERR(); + return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + }; + return CreateInternal(aGlobal, std::move(aPrincipalInfo), /* aInnerWindowID */ 0); } @@ -283,10 +289,17 @@ nsresult IDBFactory::AllowedForWindowInternal( MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aWindow); - if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { + IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate(); + if (NS_WARN_IF(!mgr)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } + nsresult rv = mgr->EnsureLocale(); + if (NS_WARN_IF(NS_FAILED(rv))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + }; + StorageAccess access = StorageAllowedForWindow(aWindow); // the factory callsite records whether the browser is in private browsing. @@ -341,10 +354,16 @@ bool IDBFactory::AllowedForPrincipal(nsIPrincipal* aPrincipal, MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); - if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { + IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate(); + if (NS_WARN_IF(!mgr)) { return false; } + nsresult rv = mgr->EnsureLocale(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + }; + if (aPrincipal->IsSystemPrincipal()) { if (aIsSystemPrincipal) { *aIsSystemPrincipal = true; @@ -573,19 +592,20 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( PersistenceType persistenceType; - bool isInternal = principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo; - if (!isInternal && + bool isPersistent = + principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo; + if (!isPersistent && principalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { nsCString origin = principalInfo.get_ContentPrincipalInfo().originNoSuffix(); - isInternal = QuotaManager::IsOriginInternal(origin); + isPersistent = QuotaManager::IsOriginInternal(origin); } const bool isPrivate = principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && principalInfo.get_ContentPrincipalInfo().attrs().mPrivateBrowsingId > 0; - if (isInternal) { + if (isPersistent) { // Chrome privilege and internal origins always get persistent storage. persistenceType = PERSISTENCE_TYPE_PERSISTENT; } else if (isPrivate) { @@ -641,7 +661,8 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( mBackgroundActor = static_cast<BackgroundFactoryChild*>( backgroundActor->SendPBackgroundIDBFactoryConstructor( - actor, idbThreadLocal->GetLoggingInfo())); + actor, idbThreadLocal->GetLoggingInfo(), + IndexedDatabaseManager::GetLocale())); if (NS_WARN_IF(!mBackgroundActor)) { mBackgroundActorFailed = true; diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index 5ee16ec5e9..0558c38826 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -296,25 +296,6 @@ nsresult IndexedDatabaseManager::Init() { Preferences::RegisterCallbackAndCall(MaxPreloadExtraRecordsPrefChangeCallback, kPrefMaxPreloadExtraRecords); - nsAutoCString acceptLang; - Preferences::GetLocalizedCString("intl.accept_languages", acceptLang); - - // Split values on commas. - for (const auto& lang : - nsCCharSeparatedTokenizer(acceptLang, ',').ToRange()) { - mozilla::intl::LocaleCanonicalizer::Vector asciiString{}; - auto result = mozilla::intl::LocaleCanonicalizer::CanonicalizeICULevel1( - PromiseFlatCString(lang).get(), asciiString); - if (result.isOk()) { - mLocale.AssignASCII(asciiString); - break; - } - } - - if (mLocale.IsEmpty()) { - mLocale.AssignLiteral("en_US"); - } - return NS_OK; } @@ -652,11 +633,37 @@ void IndexedDatabaseManager::LoggingModePrefChangedCallback( } } +nsresult IndexedDatabaseManager::EnsureLocale() { + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoCString acceptLang; + Preferences::GetLocalizedCString("intl.accept_languages", acceptLang); + + // Split values on commas. + for (const auto& lang : + nsCCharSeparatedTokenizer(acceptLang, ',').ToRange()) { + mozilla::intl::LocaleCanonicalizer::Vector asciiString{}; + auto result = mozilla::intl::LocaleCanonicalizer::CanonicalizeICULevel1( + PromiseFlatCString(lang).get(), asciiString); + if (result.isOk()) { + mLocale.AssignASCII(asciiString); + break; + } + } + + if (mLocale.IsEmpty()) { + mLocale.AssignLiteral("en_US"); + } + + return NS_OK; +} + // static const nsCString& IndexedDatabaseManager::GetLocale() { IndexedDatabaseManager* idbManager = Get(); MOZ_ASSERT(idbManager, "IDBManager is not ready!"); + MOZ_ASSERT(!idbManager->mLocale.IsEmpty()); return idbManager->mLocale; } diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index 5bf6485aff..0766143dd6 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -123,6 +123,9 @@ class IndexedDatabaseManager final { nsresult FlushPendingFileDeletions(); + // XXX This extra explicit initialization should go away with bug 1730706. + nsresult EnsureLocale(); + static const nsCString& GetLocale(); static bool ResolveSandboxBinding(JSContext* aCx); diff --git a/dom/indexedDB/SchemaUpgrades.cpp b/dom/indexedDB/SchemaUpgrades.cpp index 6066390740..234c4c9f04 100644 --- a/dom/indexedDB/SchemaUpgrades.cpp +++ b/dom/indexedDB/SchemaUpgrades.cpp @@ -2861,8 +2861,10 @@ nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory, // purpose is to store file ids without adding more complexity or code // duplication. auto fileManager = MakeSafeRefPtr<DatabaseFileManager>( - PERSISTENCE_TYPE_INVALID, quota::OriginMetadata{}, u""_ns, ""_ns, false, - false); + PERSISTENCE_TYPE_INVALID, quota::OriginMetadata{}, + /* aDatabaseName */ u""_ns, /* aDatabaseID */ ""_ns, + /* aEnforcingQuota */ false, + /* aIsInPrivateBrowsingMode */ false); nsresult rv = fileManager->Init(aFMDirectory, aConnection); if (NS_WARN_IF(NS_FAILED(rv))) { diff --git a/dom/indexedDB/SerializationHelpers.h b/dom/indexedDB/SerializationHelpers.h index 7bc05720d4..488c1eb044 100644 --- a/dom/indexedDB/SerializationHelpers.h +++ b/dom/indexedDB/SerializationHelpers.h @@ -10,6 +10,7 @@ #include "ipc/EnumSerializer.h" #include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/indexedDB/Key.h" #include "mozilla/dom/indexedDB/KeyPath.h" #include "mozilla/dom/IDBCursor.h" @@ -61,10 +62,8 @@ struct ParamTraits<mozilla::dom::indexedDB::KeyPath> { template <> struct ParamTraits<mozilla::dom::IDBCursor::Direction> - : public ContiguousEnumSerializer< - mozilla::dom::IDBCursor::Direction, - mozilla::dom::IDBCursor::Direction::Next, - mozilla::dom::IDBCursor::Direction::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::IDBCursor::Direction> {}; template <> struct ParamTraits<mozilla::dom::IDBTransaction::Mode> diff --git a/dom/indexedDB/test/abort_on_reload.html b/dom/indexedDB/test/abort_on_reload.html index 4e4fe3a339..7b3cba6d95 100644 --- a/dom/indexedDB/test/abort_on_reload.html +++ b/dom/indexedDB/test/abort_on_reload.html @@ -3,7 +3,7 @@ <body> <script> function createDb() { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const openRequest = indexedDB.open("test-abort-on-reload", 1); openRequest.onsuccess = () => { const db = openRequest.result; @@ -14,7 +14,7 @@ }; resolve(); }; - openRequest.onupgradeneeded = (evt) => { + openRequest.onupgradeneeded = () => { // Interrupt upgrade window.location.reload(); opener.info('reload requested\n'); @@ -24,7 +24,7 @@ } function reset() { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const request = indexedDB.deleteDatabase("test-abort-on-reload"); request.onsuccess = resolve; }); diff --git a/dom/indexedDB/test/bfcache_page1.html b/dom/indexedDB/test/bfcache_page1.html index e537d42008..a78d4c0d94 100644 --- a/dom/indexedDB/test/bfcache_page1.html +++ b/dom/indexedDB/test/bfcache_page1.html @@ -6,7 +6,7 @@ request.onupgradeneeded = function(e) { var db = e.target.result; // This should never be called - db.onversionchange = function(e) { + db.onversionchange = function() { db.transaction(["mystore"]).objectStore("mystore").put({ hello: "fail" }, 42); }; var trans = e.target.transaction; diff --git a/dom/indexedDB/test/blob_worker_crash_iframe.html b/dom/indexedDB/test/blob_worker_crash_iframe.html index 15d8277515..8f98ba206a 100644 --- a/dom/indexedDB/test/blob_worker_crash_iframe.html +++ b/dom/indexedDB/test/blob_worker_crash_iframe.html @@ -80,7 +80,7 @@ dump("EXCEPTION IN CREATION: " + e + "\n " + e.stack + "\n"); objectStore.delete(42).onsuccess = closeDBTellOwningThread; } - function closeDBTellOwningThread(event) { + function closeDBTellOwningThread() { // Now that worker has latched the blob, clean up the database. db.close(); db = null; diff --git a/dom/indexedDB/test/browser_private_idb.js b/dom/indexedDB/test/browser_private_idb.js index 5627361fc6..fb8108ef35 100644 --- a/dom/indexedDB/test/browser_private_idb.js +++ b/dom/indexedDB/test/browser_private_idb.js @@ -12,7 +12,7 @@ async function idbCheckFunc() { try { console.log("opening db"); const req = factory.open("db", 1); - const result = await new Promise((resolve, reject) => { + const result = await new Promise(resolve => { req.onerror = () => { resolve("error"); }; @@ -21,7 +21,7 @@ async function idbCheckFunc() { resolve("created"); }; // ...so this will lose the race - req.onsuccess = event => { + req.onsuccess = () => { resolve("already-exists"); }; }); diff --git a/dom/indexedDB/test/bug839193.js b/dom/indexedDB/test/bug839193.js index b5b951e32e..d267c00348 100644 --- a/dom/indexedDB/test/bug839193.js +++ b/dom/indexedDB/test/bug839193.js @@ -6,7 +6,7 @@ const nsIQuotaManagerService = Ci.nsIQuotaManagerService; var gURI = Services.io.newURI("http://localhost"); -function onUsageCallback(request) {} +function onUsageCallback() {} function onLoad() { var quotaManagerService = Cc[ diff --git a/dom/indexedDB/test/error_events_abort_transactions_iframe.html b/dom/indexedDB/test/error_events_abort_transactions_iframe.html index 672b7c3a5b..b6df9d8b0b 100644 --- a/dom/indexedDB/test/error_events_abort_transactions_iframe.html +++ b/dom/indexedDB/test/error_events_abort_transactions_iframe.html @@ -29,7 +29,7 @@ finishTest(); } - function unexpectedSuccessHandler(event) { + function unexpectedSuccessHandler() { ok(false, "got success when it was not expected!"); finishTest(); } @@ -43,7 +43,7 @@ }, 0); } - window.onerror = function(message, filename, lineno) { + window.onerror = function(message) { is(message, "ConstraintError", "Expect a constraint error"); }; @@ -82,7 +82,7 @@ request = objectStore.add({}, 1); request.onsuccess = unexpectedSuccessHandler; - request.onerror = function(event) { + request.onerror = function() { // Don't do anything! ConstraintError is expected in window.onerror. }; event = yield undefined; diff --git a/dom/indexedDB/test/event_propagation_iframe.html b/dom/indexedDB/test/event_propagation_iframe.html index 3788958014..2d852b3b98 100644 --- a/dom/indexedDB/test/event_propagation_iframe.html +++ b/dom/indexedDB/test/event_propagation_iframe.html @@ -101,7 +101,7 @@ event = yield undefined; request = objectStore.add({}, 1); - request.onsuccess = function(event) { + request.onsuccess = function() { ok(false, "Did not expect second add to succeed."); }; request.onerror = errorEventCounter; @@ -119,7 +119,7 @@ event = yield undefined; request = objectStore.add({}, 1); - request.onsuccess = function(event) { + request.onsuccess = function() { ok(false, "Did not expect second add to succeed."); }; request.onerror = errorEventCounter; diff --git a/dom/indexedDB/test/exceptions_in_events_iframe.html b/dom/indexedDB/test/exceptions_in_events_iframe.html index 25a4f01e77..b67d4c5b21 100644 --- a/dom/indexedDB/test/exceptions_in_events_iframe.html +++ b/dom/indexedDB/test/exceptions_in_events_iframe.html @@ -31,7 +31,7 @@ finishTest(); } - function unexpectedSuccessHandler(event) { + function unexpectedSuccessHandler() { ok(false, "got success when it was not expected!"); finishTest(); } @@ -105,7 +105,7 @@ ok(db.objectStoreNames.contains("foo"), "Has correct objectStore"); request = objectStore.add({}, 1); - request.onsuccess = function(event) { + request.onsuccess = function() { throw new Error("foo"); }; diff --git a/dom/indexedDB/test/head.js b/dom/indexedDB/test/head.js index 802f093c6b..b06658e466 100644 --- a/dom/indexedDB/test/head.js +++ b/dom/indexedDB/test/head.js @@ -74,7 +74,7 @@ function triggerSecondaryCommand(popup, win) { EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {}, win); } -function dismissNotification(popup) { +function dismissNotification() { info("dismissing notification"); executeSoon(function () { EventUtils.synthesizeKey("KEY_Escape"); diff --git a/dom/indexedDB/test/helpers.js b/dom/indexedDB/test/helpers.js index 3555abf722..2ba2a440df 100644 --- a/dom/indexedDB/test/helpers.js +++ b/dom/indexedDB/test/helpers.js @@ -3,6 +3,8 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // testSteps is expected to be defined by the test using this file. /* global testSteps:false */ @@ -482,7 +484,7 @@ function workerScript() { self.executeSoon = function (_fun_) { var channel = new MessageChannel(); channel.port1.postMessage(""); - channel.port2.onmessage = function (event) { + channel.port2.onmessage = function () { _fun_(); }; }; diff --git a/dom/indexedDB/test/leaving_page_iframe.html b/dom/indexedDB/test/leaving_page_iframe.html index 4ed299df33..712fb89991 100644 --- a/dom/indexedDB/test/leaving_page_iframe.html +++ b/dom/indexedDB/test/leaving_page_iframe.html @@ -23,7 +23,7 @@ function madeMod() { parent.postMessage("didcommit", "*"); }; - store.put({ hello: "officer" }, 42).onsuccess = function(e) { + store.put({ hello: "officer" }, 42).onsuccess = function() { // Make this transaction run until the end of time or until the page is // navigated away, whichever comes first. function doGet() { diff --git a/dom/indexedDB/test/mochitest-common.toml b/dom/indexedDB/test/mochitest-common.toml index 4ba2312bd5..d42228da45 100644 --- a/dom/indexedDB/test/mochitest-common.toml +++ b/dom/indexedDB/test/mochitest-common.toml @@ -282,7 +282,7 @@ skip-if = ["true"] # disabled for the moment ["test_names_sorted.html"] skip-if = [ "xorigin && !debug", # Hangs - "os == 'linux' && bits == 64 && !debug", # Bug 1602927 + "os == 'linux' && os_version == '18.04' && bits == 64 && !debug", # Bug 1602927 ] ["test_objectCursors.html"] @@ -382,7 +382,9 @@ skip-if = [ ["test_upgrade_add_index.html"] skip-if = [ - "!debug && bits == 64 && (os == 'linux' || os == 'mac')", + "os == 'linux' && os_version == '18.04' && bits == 64 && !debug", + "apple_silicon && !debug", + "apple_catalina && !debug", "os == 'win'", #Bug 1637715 ] scheme = "https" diff --git a/dom/indexedDB/test/test_abort_on_reload.html b/dom/indexedDB/test/test_abort_on_reload.html index fd31e709c6..43e3178977 100644 --- a/dom/indexedDB/test/test_abort_on_reload.html +++ b/dom/indexedDB/test/test_abort_on_reload.html @@ -17,7 +17,7 @@ openedWindow = window.open("abort_on_reload.html"); } - function messageListener(event) { + function messageListener() { ok(true, "reload recorded"); if (++reloads == 20) { diff --git a/dom/indexedDB/test/test_event_listener_leaks.html b/dom/indexedDB/test/test_event_listener_leaks.html index d1d9a69068..ebe5c202dd 100644 --- a/dom/indexedDB/test/test_event_listener_leaks.html +++ b/dom/indexedDB/test/test_event_listener_leaks.html @@ -37,7 +37,7 @@ async function useIDB(contentWindow) { store.get(0).onsuccess = spin; } - store.put(0, "purgatory").onsuccess = e => { + store.put(0, "purgatory").onsuccess = () => { contentWindow.putCount += 1; spin(); }; diff --git a/dom/indexedDB/test/test_open_for_principal.html b/dom/indexedDB/test/test_open_for_principal.html index bd9da12f74..04cd86126b 100644 --- a/dom/indexedDB/test/test_open_for_principal.html +++ b/dom/indexedDB/test/test_open_for_principal.html @@ -10,6 +10,7 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <script type="text/javascript"> + // eslint-disable-next-line require-yield function* testSteps() { is("open" in indexedDB, true, "open() defined"); diff --git a/dom/indexedDB/test/third_party_window.html b/dom/indexedDB/test/third_party_window.html index 14b16bf5d4..20d453d7a3 100644 --- a/dom/indexedDB/test/third_party_window.html +++ b/dom/indexedDB/test/third_party_window.html @@ -14,7 +14,7 @@ let iframe = document.getElementById("iframe1"); iframe.src = evt.data.iframeUrl; - iframe.addEventListener("load", e => { + iframe.addEventListener("load", () => { iframe.contentWindow.postMessage(JSON.stringify(evt.data), "*"); }); diff --git a/dom/indexedDB/test/unit/test_blocked_order.js b/dom/indexedDB/test/unit/test_blocked_order.js index 212b1b3254..0301c12df2 100644 --- a/dom/indexedDB/test/unit/test_blocked_order.js +++ b/dom/indexedDB/test/unit/test_blocked_order.js @@ -33,7 +33,7 @@ function* testSteps() { let db = request.result; is(db.version, 1, "Got version 1"); - db.onversionchange = function (event) { + db.onversionchange = function () { info("Closing database " + thisIndex); db.close(); @@ -51,7 +51,7 @@ function* testSteps() { request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; - request.onblocked = function (event) { + request.onblocked = function () { ok(false, "Should not receive a blocked event"); }; @@ -95,7 +95,7 @@ function* testSteps() { let db = request.result; is(db.version, 1, "Got version 1"); - db.onversionchange = function (event) { + db.onversionchange = function () { if (thisIndex == databaseCount - 1) { info("Closing all databases with version 1"); @@ -121,7 +121,7 @@ function* testSteps() { request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; - request.onblocked = function (event) { + request.onblocked = function () { ok(false, "Should not receive a blocked event"); }; diff --git a/dom/indexedDB/test/unit/test_cleanup_transaction.js b/dom/indexedDB/test/unit/test_cleanup_transaction.js index cac3c9bf60..25af4c5729 100644 --- a/dom/indexedDB/test/unit/test_cleanup_transaction.js +++ b/dom/indexedDB/test/unit/test_cleanup_transaction.js @@ -74,12 +74,12 @@ function* testSteps() { event.stopPropagation(); }; - trans.oncomplete = function (event) { + trans.oncomplete = function () { i++; j++; testGenerator.next(true); }; - trans.onabort = function (event) { + trans.onabort = function () { is(trans.error.name, "QuotaExceededError", "Reached quota limit"); testGenerator.next(false); }; diff --git a/dom/indexedDB/test/unit/test_clear.js b/dom/indexedDB/test/unit/test_clear.js index d504ff8851..87eef2c0ab 100644 --- a/dom/indexedDB/test/unit/test_clear.js +++ b/dom/indexedDB/test/unit/test_clear.js @@ -70,7 +70,7 @@ function* testSteps() { request = db.transaction("foo").objectStore("foo").openCursor(); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { let cursor = request.result; if (cursor) { ok(false, "Shouldn't have any entries"); diff --git a/dom/indexedDB/test/unit/test_connection_idle_maintenance.js b/dom/indexedDB/test/unit/test_connection_idle_maintenance.js new file mode 100644 index 0000000000..288f656c65 --- /dev/null +++ b/dom/indexedDB/test/unit/test_connection_idle_maintenance.js @@ -0,0 +1,97 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* exported testSteps */ +async function testSteps() { + // A constant used to deal with small decrease in usage when transactions are + // transferred from the WAL file back into the original database, also called + // as checkpointing. + const cosmologicalConstant = 31768; + + // The length of time that database connections will be held open after all + // transactions have completed before doing idle maintenance. + const connectionIdleMaintenanceMS = 2 * 1000; + + const name = "test_connection_idle_maintenance"; + const abc = "abcdefghijklmnopqrstuvwxyz"; + + // IndexedDB on Android does `PRAGMA auto_vacuum = FULL`, so the freelist + // pages are moved to the end of the database file and the database file is + // truncated to remove the freelist pages at every transaction commit. + if (mozinfo.os == "android") { + info("Test disabled on Android for now"); + return; + } + + info("Creating database"); + + let request = indexedDB.open(name, 1); + + let event = await expectingUpgrade(request); + + let database = event.target.result; + + let objectStore = database.createObjectStore(name); + + // Add lots of data... + for (let i = 0; i < 10000; i++) { + objectStore.add(abc, i); + } + + // And then clear it so that maintenance has some space to reclaim. + objectStore.clear(); + + await expectingSuccess(request); + + info("Getting database usage before maintenance"); + + let databaseUsageBeforeMaintenance = await new Promise(function (resolve) { + getCurrentUsage(function (request) { + resolve(request.result.databaseUsage); + }); + }); + + info("Waiting for maintenance to start"); + + // This time is a double of connectionIdleMaintenanceMS which should be + // pessimistic enough to work with randomly slowed down threads in the chaos + // mode. + await new Promise(function (resolve) { + do_timeout(2 * connectionIdleMaintenanceMS, resolve); + }); + + info("Waiting for maintenance to finish"); + + // This time is a double of connectionIdleMaintenanceMS which should be + // pessimistic enough to work with randomly slowed down threads in the chaos + // mode. + await new Promise(function (resolve) { + do_timeout(2 * connectionIdleMaintenanceMS, resolve); + }); + + info("Getting database usage after maintenance"); + + let databaseUsageAfterMaintenance = await new Promise(function (resolve) { + getCurrentUsage(function (request) { + resolve(request.result.databaseUsage); + }); + }); + + info( + "Database usage before: " + + databaseUsageBeforeMaintenance + + ". " + + "Database usage after: " + + databaseUsageAfterMaintenance + ); + + // Checkpointing done immediately after the maintenance can slightly decrease + // the usage even when the maintenance was not run at all. + ok( + databaseUsageBeforeMaintenance - databaseUsageAfterMaintenance >= + cosmologicalConstant, + "Maintenance significantly decreased database usage" + ); +} diff --git a/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js b/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js new file mode 100644 index 0000000000..33dc69b210 --- /dev/null +++ b/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js @@ -0,0 +1,204 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* exported testSteps */ +async function testSteps() { + // A constant used to deal with small decrease in usage when transactions are + // transferred from the WAL file back into the original database, also called + // as checkpointing. + const cosmologicalConstant = 32768; + + // The maximum number of threads that can be used for database activity at a + // single time. + const maxConnectionThreadCount = 20; + + // The length of time that database connections will be held open after all + // transactions have completed before doing idle maintenance. + const connectionIdleMaintenanceMS = 2 * 1000; + + const name = "test_connection_idle_maintenance_stop"; + const abc = "abcdefghijklmnopqrstuvwxyz"; + + // IndexedDB on Android does `PRAGMA auto_vacuum = FULL`, so the freelist + // pages are moved to the end of the database file and the database file is + // truncated to remove the freelist pages at every transaction commit. + if (mozinfo.os == "android") { + info("Test disabled on Android for now"); + return; + } + + info("Setting pref"); + + Services.prefs.setIntPref( + "dom.indexedDB.connectionIdleMaintenance.pauseOnConnectionThreadMs", + 2 * connectionIdleMaintenanceMS + ); + + info("Forcing only one connection thread to be available"); + + let done = false; + + // Create databases which will continuously keep their connection threads + // busy. + const completePromises = await (async function () { + let promises = []; + + for (let index = 0; index < maxConnectionThreadCount - 1; index++) { + const request = indexedDB.open(name + "-" + index, 1); + + { + const event = await expectingUpgrade(request); + + const database = event.target.result; + + const objectStore = database.createObjectStore(name); + + objectStore.add("foo", 42); + } + + const event = await expectingSuccess(request); + + const database = event.target.result; + + const transaction = database.transaction(name); + + const objectStore = transaction.objectStore(name); + + function doWork() { + const request = objectStore.get(42); + request.onsuccess = function () { + if (!done) { + doWork(); + } + }; + } + + doWork(); + + const promise = new Promise(function (resolve) { + transaction.oncomplete = resolve; + }); + + promises.push(promise); + } + + return promises; + })(); + + info("Creating database A"); + + // Create a database which will be used for stopping of the connection idle + // maintenance. + const databaseA = await (async function () { + const request = indexedDB.open(name + "-a", 1); + + { + const event = await expectingUpgrade(request); + + const database = event.target.result; + + database.createObjectStore(name); + } + + const event = await expectingSuccess(request); + + const database = event.target.result; + + return database; + })(); + + info("Creating database B"); + + // Create a database for checking of the connection idle maintenance. + { + const request = indexedDB.open(name + "-b", 1); + + const event = await expectingUpgrade(request); + + const database = event.target.result; + + const objectStore = database.createObjectStore(name); + + // Add lots of data... + for (let index = 0; index < 10000; index++) { + objectStore.add(abc, index); + } + + // And then clear it so that maintenance has some space to reclaim. + objectStore.clear(); + + await expectingSuccess(request); + } + + info("Getting database usage before maintenance"); + + const databaseUsageBeforeMaintenance = await new Promise(function (resolve) { + getCurrentUsage(function (request) { + resolve(request.result.databaseUsage); + }); + }); + + info("Waiting for maintenance to start"); + + // This time is a double of connectionIdleMaintenanceMS which should be + // pessimistic enough to work with randomly slowed down threads in the + // chaos mode. + await new Promise(function (resolve) { + do_timeout(2 * connectionIdleMaintenanceMS, resolve); + }); + + info("Activating database A"); + + // Activate an open database which should trigger stopping of the connection + // idle maintenance of the database which had a lot of data. + { + const transaction = databaseA.transaction(name); + + const objectStore = transaction.objectStore(name); + + const request = objectStore.get(42); + + await requestSucceeded(request); + } + + info("Waiting for maintenance to finish"); + + // This time is a double of connectionIdleMaintenanceMS which should be + // pessimistic enough to work with randomly slowed down threads in the + // chaos mode. + await new Promise(function (resolve) { + do_timeout(2 * connectionIdleMaintenanceMS, resolve); + }); + + info("Getting database usage after maintenance"); + + const databaseUsageAfterMaintenance = await new Promise(function (resolve) { + getCurrentUsage(function (request) { + resolve(request.result.databaseUsage); + }); + }); + + info( + "Database usage before: " + + databaseUsageBeforeMaintenance + + ". " + + "Database usage after: " + + databaseUsageAfterMaintenance + ); + + // Checkpointing done immediately after the maintenance can slightly decrease + // the usage even when the maintenance was stopped very early. + ok( + databaseUsageBeforeMaintenance - databaseUsageAfterMaintenance < + cosmologicalConstant, + "Maintenance did not significantly decrease database usage" + ); + + done = true; + + info("Waiting for transactions to complete"); + + await Promise.all(completePromises); +} diff --git a/dom/indexedDB/test/unit/test_cursor_cycle.js b/dom/indexedDB/test/unit/test_cursor_cycle.js index 09099387f3..3c60c24238 100644 --- a/dom/indexedDB/test/unit/test_cursor_cycle.js +++ b/dom/indexedDB/test/unit/test_cursor_cycle.js @@ -34,7 +34,7 @@ function* testSteps() { let cursor = event.target.result; if (cursor) { let objectStore = event.target.transaction.objectStore("foo"); - objectStore.delete(Bob.ss).onsuccess = function (event) { + objectStore.delete(Bob.ss).onsuccess = function () { cursor.continue(); }; } diff --git a/dom/indexedDB/test/unit/test_cursor_mutation.js b/dom/indexedDB/test/unit/test_cursor_mutation.js index 6af7df5c9b..c948b8d6c9 100644 --- a/dom/indexedDB/test/unit/test_cursor_mutation.js +++ b/dom/indexedDB/test/unit/test_cursor_mutation.js @@ -101,10 +101,10 @@ function* testSteps() { if (count == 1) { let objectStore = event.target.transaction.objectStore("foo"); - objectStore.delete(objectStoreData[0].ss).onsuccess = function (event) { + objectStore.delete(objectStoreData[0].ss).onsuccess = function () { objectStore.add( objectStoreData[objectStoreData.length - 1] - ).onsuccess = function (event) { + ).onsuccess = function () { cursor.continue(); }; }; diff --git a/dom/indexedDB/test/unit/test_cursors.js b/dom/indexedDB/test/unit/test_cursors.js index 15e3d94355..84d0f082a8 100644 --- a/dom/indexedDB/test/unit/test_cursors.js +++ b/dom/indexedDB/test/unit/test_cursors.js @@ -77,7 +77,7 @@ function* testSteps() { for (let i in keys) { request = objectStore.add("foo", keys[i]); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { if (++keyIndex == keys.length) { testGenerator.next(); } @@ -233,7 +233,7 @@ function* testSteps() { if (keyIndex == 4) { request = cursor.update("bar"); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { keyIndex++; cursor.continue(); }; diff --git a/dom/indexedDB/test/unit/test_database_onclose.js b/dom/indexedDB/test/unit/test_database_onclose.js index 0162ffc3ab..8d24ad620b 100644 --- a/dom/indexedDB/test/unit/test_database_onclose.js +++ b/dom/indexedDB/test/unit/test_database_onclose.js @@ -104,7 +104,7 @@ function* testSteps() { let objectId = 0; while (true) { let addRequest = objectStore.add({ foo: "foo" }, objectId); - addRequest.onerror = function (event) { + addRequest.onerror = function () { info("addRequest.onerror, objectId: " + objectId); txn.onerror = grabEventAndContinueHandler; testGenerator.next(true); @@ -200,7 +200,7 @@ function* testSteps() { let numberOfReadObjects = 0; let readRequest = objectStore.openCursor(); - readRequest.onerror = function (event) { + readRequest.onerror = function () { info("readRequest.onerror, numberOfReadObjects: " + numberOfReadObjects); testGenerator.next(true); }; diff --git a/dom/indexedDB/test/unit/test_getAll.js b/dom/indexedDB/test/unit/test_getAll.js index 6ada30d845..b8c3e6854a 100644 --- a/dom/indexedDB/test/unit/test_getAll.js +++ b/dom/indexedDB/test/unit/test_getAll.js @@ -33,7 +33,7 @@ function* testSteps() { for (let i in values) { request = objectStore.add(values[i]); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { if (++addedCount == values.length) { executeSoon(function () { testGenerator.next(); diff --git a/dom/indexedDB/test/unit/test_index_object_cursors.js b/dom/indexedDB/test/unit/test_index_object_cursors.js index 850e1c6007..992fe9e0e3 100644 --- a/dom/indexedDB/test/unit/test_index_object_cursors.js +++ b/dom/indexedDB/test/unit/test_index_object_cursors.js @@ -102,7 +102,7 @@ function* testSteps() { let obj = cursor.value; obj.updated = true; - cursor.update(obj).onsuccess = function (event) { + cursor.update(obj).onsuccess = function () { ok(true, "Object updated"); cursor.continue(); keyIndex++; @@ -110,7 +110,7 @@ function* testSteps() { return; } - cursor.delete().onsuccess = function (event) { + cursor.delete().onsuccess = function () { ok(true, "Object deleted"); cursor.continue(); keyIndex++; diff --git a/dom/indexedDB/test/unit/test_index_update_delete.js b/dom/indexedDB/test/unit/test_index_update_delete.js index da43feb3b9..df6d3d86f4 100644 --- a/dom/indexedDB/test/unit/test_index_update_delete.js +++ b/dom/indexedDB/test/unit/test_index_update_delete.js @@ -73,7 +73,7 @@ function* testSteps() { is(cursor.key, modifiedEntry, "Correct key"); cursor.value.index = unique ? 30 : 35; - cursor.update(cursor.value).onsuccess = function (event) { + cursor.update(cursor.value).onsuccess = function () { cursor.continue(); }; } else { @@ -106,7 +106,7 @@ function* testSteps() { is(cursor.key, modifiedEntry, "Correct key"); delete cursor.value.index; - cursor.update(cursor.value).onsuccess = function (event) { + cursor.update(cursor.value).onsuccess = function () { indexCount--; cursor.continue(); }; diff --git a/dom/indexedDB/test/unit/test_indexes_bad_values.js b/dom/indexedDB/test/unit/test_indexes_bad_values.js index c695490f74..d8426d60cc 100644 --- a/dom/indexedDB/test/unit/test_indexes_bad_values.js +++ b/dom/indexedDB/test/unit/test_indexes_bad_values.js @@ -74,7 +74,7 @@ function* testSteps() { badObjectStoreData[i].key ); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { if (++addedData == badObjectStoreData.length) { executeSoon(function () { testGenerator.next(); diff --git a/dom/indexedDB/test/unit/test_invalid_version.js b/dom/indexedDB/test/unit/test_invalid_version.js index 79d74ab2cc..ea5e2953d0 100644 --- a/dom/indexedDB/test/unit/test_invalid_version.js +++ b/dom/indexedDB/test/unit/test_invalid_version.js @@ -6,6 +6,7 @@ /* exported testGenerator */ var testGenerator = testSteps(); +// eslint-disable-next-line require-yield function* testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; diff --git a/dom/indexedDB/test/unit/test_quotaExceeded_recovery.js b/dom/indexedDB/test/unit/test_quotaExceeded_recovery.js index 20366be417..3f5ef96ba4 100644 --- a/dom/indexedDB/test/unit/test_quotaExceeded_recovery.js +++ b/dom/indexedDB/test/unit/test_quotaExceeded_recovery.js @@ -82,14 +82,14 @@ function* testSteps() { event.stopPropagation(); }; - trans.oncomplete = function (event) { + trans.oncomplete = function () { if (iter == 1) { i++; } j++; testGenerator.next(true); }; - trans.onabort = function (event) { + trans.onabort = function () { is(trans.error.name, "QuotaExceededError", "Reached quota limit"); testGenerator.next(false); }; diff --git a/dom/indexedDB/test/unit/test_remove_objectStore.js b/dom/indexedDB/test/unit/test_remove_objectStore.js index bdc16d9a6a..8d4cd17246 100644 --- a/dom/indexedDB/test/unit/test_remove_objectStore.js +++ b/dom/indexedDB/test/unit/test_remove_objectStore.js @@ -28,7 +28,7 @@ function* testSteps() { for (let i = 0; i < 100; i++) { request = objectStore.add({ foo: i }); request.onerror = errorHandler; - request.onsuccess = function (event) { + request.onsuccess = function () { if (++addedCount == 100) { executeSoon(function () { testGenerator.next(); diff --git a/dom/indexedDB/test/unit/test_setVersion_throw.js b/dom/indexedDB/test/unit/test_setVersion_throw.js index 2706c2c2a4..b0b5392ba2 100644 --- a/dom/indexedDB/test/unit/test_setVersion_throw.js +++ b/dom/indexedDB/test/unit/test_setVersion_throw.js @@ -17,7 +17,7 @@ function* testSteps() { let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; - request.onupgradeneeded = function (event) { + request.onupgradeneeded = function () { info("Got upgradeneeded event for db 1"); }; let event = yield undefined; @@ -36,7 +36,7 @@ function* testSteps() { request = indexedDB.open(name, 2); request.onerror = grabEventAndContinueHandler; request.onsuccess = unexpectedSuccessHandler; - request.onupgradeneeded = function (event) { + request.onupgradeneeded = function () { info("Got upgradeneeded event for db 2"); expectUncaughtException(true); // eslint-disable-next-line no-undef diff --git a/dom/indexedDB/test/unit/test_table_locks.js b/dom/indexedDB/test/unit/test_table_locks.js index e1f3b5e7d6..3363520775 100644 --- a/dom/indexedDB/test/unit/test_table_locks.js +++ b/dom/indexedDB/test/unit/test_table_locks.js @@ -81,7 +81,7 @@ function doReadOnlyTransaction(db, key, remaining) { let cursor = event.target.result; ok(cursor, "Got readonly cursor"); - objectStore.get(cursor.primaryKey).onsuccess = function (event) { + objectStore.get(cursor.primaryKey).onsuccess = function () { if (++key == objDataCount) { key = 0; } @@ -113,7 +113,7 @@ function doReadWriteTransaction(db, key, remaining) { let value = cursor.value; value[idxKeyPathProp]++; - cursor.update(value).onsuccess = function (event) { + cursor.update(value).onsuccess = function () { if (++key == objDataCount) { key = 0; } diff --git a/dom/indexedDB/test/unit/test_transaction_abort.js b/dom/indexedDB/test/unit/test_transaction_abort.js index 6829392842..48f3677e0d 100644 --- a/dom/indexedDB/test/unit/test_transaction_abort.js +++ b/dom/indexedDB/test/unit/test_transaction_abort.js @@ -150,7 +150,7 @@ function* testSteps() { request.onsuccess = grabEventAndContinueHandler; event = yield undefined; - event.target.transaction.onabort = function (event) { + event.target.transaction.onabort = function () { ok(false, "Shouldn't see an abort event!"); }; event.target.transaction.oncomplete = grabEventAndContinueHandler; @@ -168,7 +168,7 @@ function* testSteps() { key = event.target.result; event.target.transaction.onabort = grabEventAndContinueHandler; - event.target.transaction.oncomplete = function (event) { + event.target.transaction.oncomplete = function () { ok(false, "Shouldn't see a complete event here!"); }; @@ -303,7 +303,7 @@ function* testSteps() { r.onerror = abortErrorHandler; } makeNewRequest(); - transaction.objectStore("foo").get(1).onsuccess = function (event) { + transaction.objectStore("foo").get(1).onsuccess = function () { executeSoon(function () { transaction.abort(); expectedAbortEventCount++; @@ -315,7 +315,7 @@ function* testSteps() { // During COMMITTING transaction = db.transaction("foo", "readwrite"); transaction.objectStore("foo").put({ hello: "world" }, 1).onsuccess = - function (event) { + function () { continueToNextStep(); }; yield undefined; @@ -335,11 +335,10 @@ function* testSteps() { // Abort both failing and succeeding requests transaction = db.transaction("foo", "readwrite"); transaction.onabort = transaction.oncomplete = grabEventAndContinueHandler; - transaction.objectStore("foo").add({ indexKey: "key" }).onsuccess = function ( - event - ) { - transaction.abort(); - }; + transaction.objectStore("foo").add({ indexKey: "key" }).onsuccess = + function () { + transaction.abort(); + }; let request1 = transaction.objectStore("foo").add({ indexKey: "key" }); request1.onsuccess = grabEventAndContinueHandler; request1.onerror = grabEventAndContinueHandler; diff --git a/dom/indexedDB/test/unit/test_transaction_abort_hang.js b/dom/indexedDB/test/unit/test_transaction_abort_hang.js index 6a2c61128b..cb7e4f7133 100644 --- a/dom/indexedDB/test/unit/test_transaction_abort_hang.js +++ b/dom/indexedDB/test/unit/test_transaction_abort_hang.js @@ -47,7 +47,7 @@ function* testSteps() { // Last one, finish the test. transaction.oncomplete = grabEventAndContinueHandler; } else if (i == abortedTransactionIndex - 1) { - transaction.oncomplete = function (event) { + transaction.oncomplete = function () { ok( true, "Completed transaction " + @@ -84,7 +84,7 @@ function* testSteps() { transaction.abort(); }); } else { - transaction.oncomplete = function (event) { + transaction.oncomplete = function () { ok(true, "Completed transaction " + ++completedTransactionCount); }; } diff --git a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js index f297b72d25..f6bd2608ef 100644 --- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js +++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js @@ -28,7 +28,7 @@ function isnot(a, b, msg) { Assert.notEqual(a, b, msg); } -function todo(condition, name, diag) { +function todo(condition) { todo_check_true(condition); } @@ -122,7 +122,7 @@ function expectedErrorHandler(name) { }; } -function expectUncaughtException(expecting) { +function expectUncaughtException() { // This is dummy for xpcshell test. } @@ -196,19 +196,19 @@ function compareKeys(k1, k2) { return false; } -function addPermission(permission, url) { +function addPermission() { throw new Error("addPermission"); } -function removePermission(permission, url) { +function removePermission() { throw new Error("removePermission"); } -function allowIndexedDB(url) { +function allowIndexedDB() { throw new Error("allowIndexedDB"); } -function disallowIndexedDB(url) { +function disallowIndexedDB() { throw new Error("disallowIndexedDB"); } @@ -240,7 +240,7 @@ function scheduleGC() { function setTimeout(fun, timeout) { let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); var event = { - notify(timer) { + notify() { fun(); }, }; diff --git a/dom/indexedDB/test/unit/xpcshell-parent-process.toml b/dom/indexedDB/test/unit/xpcshell-parent-process.toml index d63e3d6bf2..01740cdd82 100644 --- a/dom/indexedDB/test/unit/xpcshell-parent-process.toml +++ b/dom/indexedDB/test/unit/xpcshell-parent-process.toml @@ -44,6 +44,10 @@ skip-if = ["true"] # Only used for recreating URLSearchParams_profile.zip requesttimeoutfactor = 2 skip-if = ["tsan"] +["test_connection_idle_maintenance.js"] + +["test_connection_idle_maintenance_stop.js"] + ["test_database_close_without_onclose.js"] ["test_database_onclose.js"] diff --git a/dom/interfaces/base/nsITextInputProcessor.idl b/dom/interfaces/base/nsITextInputProcessor.idl index 6d702af385..1dea6640bc 100644 --- a/dom/interfaces/base/nsITextInputProcessor.idl +++ b/dom/interfaces/base/nsITextInputProcessor.idl @@ -664,11 +664,3 @@ interface nsITextInputProcessor : nsISupports in AString aKeyValue, [optional] in jsval aLocation); }; - -%{C++ -#define TEXT_INPUT_PROCESSOR_CID \ - { 0xcaaab47f, 0x1e31, 0x478e, \ - { 0x89, 0x19, 0x97, 0x09, 0x04, 0xe9, 0xcb, 0x72 } } -#define TEXT_INPUT_PROCESSOR_CONTRACTID \ - "@mozilla.org/text-input-processor;1" -%} diff --git a/dom/interfaces/html/nsIDOMMozBrowserFrame.idl b/dom/interfaces/html/nsIDOMMozBrowserFrame.idl deleted file mode 100644 index 5ca9a19e6a..0000000000 --- a/dom/interfaces/html/nsIDOMMozBrowserFrame.idl +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */ - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -[scriptable, builtinclass, uuid(4CAFE116-581B-4194-B0DE-7F02378FC51D)] -interface nsIDOMMozBrowserFrame : nsISupports -{ - /** - * <iframe> element may have the mozbrowser attribute. - * - * The mozbrowser attribute has no effect unless the <iframe> element is - * contained in a document privileged to create browser frames. - * - * An <iframe> element in a privileged document with the mozbrowser attribute - * emits a variety of events when various things happen inside the frame. - * - * This will be documented eventually, but for more information at the moment, - * see dom/browser-element/BrowserElement{Child,Parent}.js. - * - */ - [infallible] attribute boolean mozbrowser; -}; diff --git a/dom/interfaces/html/nsIMozBrowserFrame.idl b/dom/interfaces/html/nsIMozBrowserFrame.idl deleted file mode 100644 index aec4fdfbeb..0000000000 --- a/dom/interfaces/html/nsIMozBrowserFrame.idl +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */ - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsIDOMMozBrowserFrame.idl" - -interface nsIRemoteTab; - -[scriptable, builtinclass, uuid(0c0a862c-1a47-43c0-ae9e-d51835e3e1a6)] -interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame -{ - /** - * Gets whether this frame really is a browser frame. - * - * In order to really be a browser frame, this frame's - * nsIDOMMozBrowserFrame::mozbrowser attribute must be true, and the frame - * may have to pass various security checks. - */ - [infallible] readonly attribute boolean reallyIsBrowser; - - /** - * Initialize the API, and add frame message listener that supports API - * invocations. - */ - [noscript] void initializeBrowserAPI(); - - /** - * Notify frame scripts that support the API to destroy. - */ - [noscript] void destroyBrowserFrameScripts(); -}; diff --git a/dom/interfaces/payments/nsIPaymentActionResponse.idl b/dom/interfaces/payments/nsIPaymentActionResponse.idl index 36d5be3b94..7ff24c3b7f 100644 --- a/dom/interfaces/payments/nsIPaymentActionResponse.idl +++ b/dom/interfaces/payments/nsIPaymentActionResponse.idl @@ -364,43 +364,9 @@ interface nsIBasicCardChangeDetails : nsIMethodChangeDetails %{C++ -#define NS_GENERAL_RESPONSE_DATA_CID \ - { 0xb986773e, 0x2b30, 0x4ed2, { 0xb8, 0xfe, 0x6a, 0x96, 0x63, 0x1c, 0x80, 0x00 } } -#define NS_GENERAL_RESPONSE_DATA_CONTRACT_ID \ - "@mozilla.org/dom/payments/general-response-data;1" - -#define NS_BASICCARD_RESPONSE_DATA_CID \ - { 0x0d55a5e6, 0xd185, 0x44f0, { 0xb9, 0x92, 0xa8, 0xe1, 0x32, 0x1e, 0x4b, 0xce } } -#define NS_BASICCARD_RESPONSE_DATA_CONTRACT_ID \ - "@mozilla.org/dom/payments/basiccard-response-data;1" - -#define NS_PAYMENT_CANMAKE_ACTION_RESPONSE_CID \ - { 0x52fc3f9f, 0xc0cb, 0x4874, { 0xb3, 0xd4, 0xee, 0x4b, 0x6e, 0x9c, 0xbe, 0x9c } } #define NS_PAYMENT_CANMAKE_ACTION_RESPONSE_CONTRACT_ID \ "@mozilla.org/dom/payments/payment-canmake-action-response;1" -#define NS_PAYMENT_SHOW_ACTION_RESPONSE_CID \ - { 0x184385cb, 0x2d35, 0x4b99, { 0xa9, 0xa3, 0x7c, 0x78, 0x0b, 0xf6, 0x6b, 0x9b } } #define NS_PAYMENT_SHOW_ACTION_RESPONSE_CONTRACT_ID \ "@mozilla.org/dom/payments/payment-show-action-response;1" - -#define NS_PAYMENT_ABORT_ACTION_RESPONSE_CID \ - { 0x8c72bcdb, 0x0c37, 0x4786, { 0xa9, 0xe5, 0x51, 0x0a, 0xfa, 0x2f, 0x8e, 0xde } } -#define NS_PAYMENT_ABORT_ACTION_RESPONSE_CONTRACT_ID \ - "@mozilla.org/dom/payments/payment-abort-action-response;1" - -#define NS_PAYMENT_COMPLETE_ACTION_RESPONSE_CID \ - { 0x62c01e69, 0x9ca4, 0x4060, { 0x99, 0xe4, 0xb9, 0x5f, 0x62, 0x8c, 0x8e, 0x6d } } -#define NS_PAYMENT_COMPLETE_ACTION_RESPONSE_CONTRACT_ID \ - "@mozilla.org/dom/payments/payment-complete-action-response;1" - -#define NS_GENERAL_CHANGE_DETAILS_CID \ - { 0xe031267e, 0xbec8, 0x4f3c, { 0xb0, 0xb1, 0x39, 0x6b, 0x77, 0xca, 0x26, 0x0c } } -#define NS_GENERAL_CHANGE_DETAILS_CONTRACT_ID \ - "@mozilla.org/dom/payments/general-change-details;1" - -#define NS_BASICCARD_CHANGE_DETAILS_CID \ - { 0x5296f79e, 0x15ea, 0x40c3, { 0x81, 0x96, 0x19, 0xcf, 0xa6, 0x4d, 0x32, 0x8c } } -#define NS_BASICCARD_CHANGE_DETAILS_CONTRACT_ID \ - "@mozilla.org/dom/payments/basiccard-change-details;1" %} diff --git a/dom/interfaces/payments/nsIPaymentAddress.idl b/dom/interfaces/payments/nsIPaymentAddress.idl index 7f9bf39e51..e3827c017c 100644 --- a/dom/interfaces/payments/nsIPaymentAddress.idl +++ b/dom/interfaces/payments/nsIPaymentAddress.idl @@ -34,10 +34,3 @@ interface nsIPaymentAddress : nsISupports in AString aRecipient, in AString aPhone); }; - -%{C++ -#define NS_PAYMENT_ADDRESS_CID \ - { 0x49a02241, 0x7e48, 0x477a, { 0x93, 0x45, 0x9f, 0x24, 0x69, 0x25, 0xdc, 0xb3 } } -#define NS_PAYMENT_ADDRESS_CONTRACT_ID \ - "@mozilla.org/dom/payments/payment-address;1" -%} diff --git a/dom/interfaces/payments/nsIPaymentRequestService.idl b/dom/interfaces/payments/nsIPaymentRequestService.idl index 34c296df94..45d2a3685b 100644 --- a/dom/interfaces/payments/nsIPaymentRequestService.idl +++ b/dom/interfaces/payments/nsIPaymentRequestService.idl @@ -95,8 +95,6 @@ interface nsIPaymentRequestService : nsISupports }; %{C++ -#define NS_PAYMENT_REQUEST_SERVICE_CID \ - { 0xcccd665f, 0xedf3, 0x41fc, { 0xab, 0x9b, 0xfc, 0x55, 0xb3, 0x73, 0x40, 0xaa } } #define NS_PAYMENT_REQUEST_SERVICE_CONTRACT_ID \ "@mozilla.org/dom/payments/payment-request-service;1" %} diff --git a/dom/interfaces/payments/nsIPaymentUIService.idl b/dom/interfaces/payments/nsIPaymentUIService.idl index 21b181d6a1..b5213d9868 100644 --- a/dom/interfaces/payments/nsIPaymentUIService.idl +++ b/dom/interfaces/payments/nsIPaymentUIService.idl @@ -78,8 +78,6 @@ interface nsIPaymentUIService : nsISupports }; %{C++ -#define NS_PAYMENT_UI_SERVICE_CID \ - { 0x01f8bd55, 0x9017, 0x438b, { 0x85, 0xec, 0x7c, 0x15, 0xd2, 0xb3, 0x5c, 0xdc } } #define NS_PAYMENT_UI_SERVICE_CONTRACT_ID \ "@mozilla.org/dom/payments/payment-ui-service;1" %} diff --git a/dom/interfaces/push/nsIPushNotifier.idl b/dom/interfaces/push/nsIPushNotifier.idl index 1c3a582803..ded62832d6 100644 --- a/dom/interfaces/push/nsIPushNotifier.idl +++ b/dom/interfaces/push/nsIPushNotifier.idl @@ -6,9 +6,6 @@ #include "nsISupports.idl" %{C++ -#define PUSHNOTIFIER_CONTRACTID \ - "@mozilla.org/push/Notifier;1" - // These constants are duplicated in `PushComponents.js`. #define OBSERVER_TOPIC_PUSH "push-message" #define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change" diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 830ad8579a..bdd10fdcb2 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -463,7 +463,7 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent, mIPCOpen = true; - if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + if (SessionStorePlatformCollection()) { mSessionStoreChild = SessionStoreChild::GetOrCreate(mBrowsingContext); } @@ -814,7 +814,8 @@ mozilla::ipc::IPCResult BrowserChild::RecvLoadURL( } docShell->LoadURI(aLoadState, true); - CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, spec); + CrashReporter::RecordAnnotationNSCString(CrashReporter::Annotation::URL, + spec); return IPC_OK(); } @@ -2529,6 +2530,8 @@ mozilla::ipc::IPCResult BrowserChild::RecvNavigateByKey( aForward ? (aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FIRSTDOC) + : StaticPrefs::dom_disable_tab_focus_to_root_element() + ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FIRST) : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_ROOT)) : (aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_LASTDOC) @@ -3481,8 +3484,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress, annotationURI = aLocation; } - CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, - annotationURI->GetSpecOrDefault()); + CrashReporter::RecordAnnotationNSCString( + CrashReporter::Annotation::URL, annotationURI->GetSpecOrDefault()); } #endif } diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index d369556588..5d13d2e814 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -1322,8 +1322,20 @@ mozilla::ipc::IPCResult BrowserParent::RecvPDocAccessibleConstructor( #endif already_AddRefed<PFilePickerParent> BrowserParent::AllocPFilePickerParent( - const nsString& aTitle, const nsIFilePicker::Mode& aMode) { - return MakeAndAddRef<FilePickerParent>(aTitle, aMode); + const nsString& aTitle, const nsIFilePicker::Mode& aMode, + const MaybeDiscarded<BrowsingContext>& aBrowsingContext) { + RefPtr<CanonicalBrowsingContext> browsingContext = + [&]() -> CanonicalBrowsingContext* { + if (aBrowsingContext.IsNullOrDiscarded()) { + return nullptr; + } + if (!aBrowsingContext.get_canonical()->IsOwnedByProcess( + Manager()->ChildID())) { + return nullptr; + } + return aBrowsingContext.get_canonical(); + }(); + return MakeAndAddRef<FilePickerParent>(aTitle, aMode, browsingContext); } already_AddRefed<PSessionStoreParent> diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h index 4d4ae287b4..63a52c6ac4 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -595,7 +595,8 @@ class BrowserParent final : public PBrowserParent, const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics); already_AddRefed<PFilePickerParent> AllocPFilePickerParent( - const nsString& aTitle, const nsIFilePicker::Mode& aMode); + const nsString& aTitle, const nsIFilePicker::Mode& aMode, + const MaybeDiscarded<BrowsingContext>& aBrowsingContext); bool GetGlobalJSObject(JSContext* cx, JSObject** globalp); diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index e3b0ebd795..7507d88fe0 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1714,12 +1714,12 @@ mozilla::ipc::IPCResult ContentChild::RecvSetProcessSandbox( DisconnectWindowServer(sandboxEnabled); # endif - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationBool( CrashReporter::Annotation::ContentSandboxEnabled, sandboxEnabled); # if defined(XP_LINUX) && !defined(ANDROID) - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationU32( CrashReporter::Annotation::ContentSandboxCapabilities, - static_cast<int>(SandboxInfo::Get().AsInteger())); + SandboxInfo::Get().AsInteger()); # endif /* XP_LINUX && !ANDROID */ #endif /* MOZ_SANDBOX */ @@ -1984,12 +1984,6 @@ PRemotePrintJobChild* ContentChild::AllocPRemotePrintJobChild() { #endif } -already_AddRefed<PClipboardReadRequestChild> -ContentChild::AllocPClipboardReadRequestChild( - const nsTArray<nsCString>& aTypes) { - return MakeAndAddRef<ClipboardReadRequestChild>(aTypes); -} - media::PMediaChild* ContentChild::AllocPMediaChild() { return media::AllocPMediaChild(); } @@ -2223,9 +2217,8 @@ void ContentChild::ProcessingError(Result aCode, const char* aReason) { MOZ_CRASH("not reached"); } - nsDependentCString reason(aReason); - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::ipc_channel_error, reason); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::ipc_channel_error, aReason); MOZ_CRASH("Content child abort due to IPC error"); } @@ -2677,8 +2670,8 @@ mozilla::ipc::IPCResult ContentChild::RecvRemoteType( } // Use the prefix to avoid URIs from Fission isolated processes. - CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::RemoteType, - remoteTypePrefix); + CrashReporter::RecordAnnotationNSCString( + CrashReporter::Annotation::RemoteType, remoteTypePrefix); // Defer RemoteWorkerService initialization until the child process does // receive its specific remoteType and can become actionable for the @@ -2951,8 +2944,7 @@ void ContentChild::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure) { } mozilla::ipc::IPCResult ContentChild::RecvShutdownConfirmedHP() { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "RecvShutdownConfirmedHP entry"_ns); // Bug 1755376: If we see "RecvShutdownConfirmedHP entry" often in @@ -2963,8 +2955,7 @@ mozilla::ipc::IPCResult ContentChild::RecvShutdownConfirmedHP() { } mozilla::ipc::IPCResult ContentChild::RecvShutdown() { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, "RecvShutdown entry"_ns); + ProcessChild::AppendToIPCShutdownStateAnnotation("RecvShutdown entry"_ns); // Signal the ongoing shutdown to AppShutdown, this // will make abort nested SpinEventLoopUntilOrQuit loops @@ -2973,8 +2964,7 @@ mozilla::ipc::IPCResult ContentChild::RecvShutdown() { nsCOMPtr<nsIObserverService> os = services::GetObserverService(); if (os) { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "content-child-will-shutdown started"_ns); os->NotifyObservers(ToSupports(this), "content-child-will-shutdown", @@ -2986,8 +2976,7 @@ mozilla::ipc::IPCResult ContentChild::RecvShutdown() { } void ContentChild::ShutdownInternal() { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, "ShutdownInternal entry"_ns); + ProcessChild::AppendToIPCShutdownStateAnnotation("ShutdownInternal entry"_ns); // If we receive the shutdown message from within a nested event loop, we want // to wait for that event loop to finish. Otherwise we could prematurely @@ -3025,8 +3014,7 @@ void ContentChild::ShutdownInternal() { nsCOMPtr<nsIObserverService> os = services::GetObserverService(); if (os) { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "content-child-shutdown started"_ns); os->NotifyObservers(ToSupports(this), "content-child-shutdown", nullptr); } @@ -3035,21 +3023,21 @@ void ContentChild::ShutdownInternal() { if (mProfilerController) { const bool isProfiling = profiler_is_active(); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - GrabShutdownProfileAndShutdown"_ns - : "Not profiling - GrabShutdownProfileAndShutdown"_ns); + isProfiling ? "Profiling - GrabShutdownProfileAndShutdown" + : "Not profiling - GrabShutdownProfileAndShutdown"); ProfileAndAdditionalInformation shutdownProfileAndAdditionalInformation = mProfilerController->GrabShutdownProfileAndShutdown(); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - Destroying ChildProfilerController"_ns - : "Not profiling - Destroying ChildProfilerController"_ns); + isProfiling ? "Profiling - Destroying ChildProfilerController" + : "Not profiling - Destroying ChildProfilerController"); mProfilerController = nullptr; - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - SendShutdownProfile (sending)"_ns - : "Not profiling - SendShutdownProfile (sending)"_ns); + isProfiling ? "Profiling - SendShutdownProfile (sending)" + : "Not profiling - SendShutdownProfile (sending)"); if (const size_t len = shutdownProfileAndAdditionalInformation.SizeOf(); len >= size_t(IPC::Channel::kMaximumMessageSize)) { shutdownProfileAndAdditionalInformation.mProfile = nsPrintfCString( @@ -3061,13 +3049,12 @@ void ContentChild::ShutdownInternal() { // message channel, which we know will survive for long enough. bool sent = SendShutdownProfile(shutdownProfileAndAdditionalInformation.mProfile); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - sent ? (isProfiling ? "Profiling - SendShutdownProfile (sent)"_ns - : "Not profiling - SendShutdownProfile (sent)"_ns) - : (isProfiling - ? "Profiling - SendShutdownProfile (failed)"_ns - : "Not profiling - SendShutdownProfile (failed)"_ns)); + sent ? (isProfiling ? "Profiling - SendShutdownProfile (sent)" + : "Not profiling - SendShutdownProfile (sent)") + : (isProfiling ? "Profiling - SendShutdownProfile (failed)" + : "Not profiling - SendShutdownProfile (failed)")); } if (PerfStats::GetCollectionMask() != 0) { @@ -3077,12 +3064,10 @@ void ContentChild::ShutdownInternal() { // Start a timer that will ensure we quickly exit after a reasonable period // of time. Prevents shutdown hangs after our connection to the parent // closes or when the parent is too busy to ever kill us. - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, "StartForceKillTimer"_ns); + ProcessChild::AppendToIPCShutdownStateAnnotation("StartForceKillTimer"_ns); StartForceKillTimer(); - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "SendFinishShutdown (sending)"_ns); // Notify the parent that we are done with shutdown. This is sent with high @@ -3094,8 +3079,7 @@ void ContentChild::ShutdownInternal() { // ever process for this ContentChild. bool sent = SendFinishShutdown(); - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( sent ? "SendFinishShutdown (sent)"_ns : "SendFinishShutdown (failed)"_ns); } @@ -4345,8 +4329,8 @@ mozilla::ipc::IPCResult ContentChild::RecvLoadURI( annotationURI = aLoadState->URI(); } - CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, - annotationURI->GetSpecOrDefault()); + CrashReporter::RecordAnnotationNSCString(CrashReporter::Annotation::URL, + annotationURI->GetSpecOrDefault()); } #endif @@ -4379,8 +4363,8 @@ mozilla::ipc::IPCResult ContentChild::RecvInternalLoad( annotationURI = aLoadState->URI(); } - CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, - annotationURI->GetSpecOrDefault()); + CrashReporter::RecordAnnotationNSCString(CrashReporter::Annotation::URL, + annotationURI->GetSpecOrDefault()); } #endif diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index 4cd1e0771c..b9ab77ef5b 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -227,9 +227,6 @@ class ContentChild final : public PContentChild, PRemotePrintJobChild* AllocPRemotePrintJobChild(); - already_AddRefed<PClipboardReadRequestChild> AllocPClipboardReadRequestChild( - const nsTArray<nsCString>& aTypes); - PMediaChild* AllocPMediaChild(); bool DeallocPMediaChild(PMediaChild* aActor); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 13206216f3..2dbdb662ff 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -78,6 +78,7 @@ #include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_threads.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/StorageAccessAPIHelper.h" #include "mozilla/StyleSheet.h" @@ -213,7 +214,6 @@ #include "nsILocalStorageManager.h" #include "nsIMemoryInfoDumper.h" #include "nsIMemoryReporter.h" -#include "nsIMozBrowserFrame.h" #include "nsINetworkLinkService.h" #include "nsIObserverService.h" #include "nsIParentChannel.h" @@ -1601,27 +1601,24 @@ void ContentParent::BroadcastThemeUpdate(widget::ThemeChangeKind aKind) { /*static */ void ContentParent::BroadcastMediaCodecsSupportedUpdate( RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported) { - // Merge incoming codec support with existing support list - media::MCSInfo::AddSupport(aSupported); - auto support = media::MCSInfo::GetSupport(); - - // Update processes - sCodecsSupported[aLocation] = support; + // Update processes and print the support info from the given location. + sCodecsSupported[aLocation] = aSupported; for (auto* cp : AllProcesses(eAll)) { - Unused << cp->SendUpdateMediaCodecsSupported(aLocation, support); + Unused << cp->SendUpdateMediaCodecsSupported(aLocation, aSupported); } - - // Generate + save support string for display in about:support nsCString supportString; - media::MCSInfo::GetMediaCodecsSupportedString(supportString, support); - gfx::gfxVars::SetCodecSupportInfo(supportString); - - // Print the support info only from the given location for debug purpose. - supportString.Truncate(); media::MCSInfo::GetMediaCodecsSupportedString(supportString, aSupported); - supportString.ReplaceSubstring("\n"_ns, ", "_ns); LOGPDM("Broadcast support from '%s', support=%s", RemoteDecodeInToStr(aLocation), supportString.get()); + + // Merge incoming support with existing support list from other locations + media::MCSInfo::AddSupport(aSupported); + auto fullSupport = media::MCSInfo::GetSupport(); + + // Generate + save FULL support string for display in about:support + supportString.Truncate(); + media::MCSInfo::GetMediaCodecsSupportedString(supportString, fullSupport); + gfx::gfxVars::SetCodecSupportInfo(supportString); } const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; } @@ -1798,6 +1795,14 @@ bool ContentParent::ShutDownProcess(ShutDownMethod aMethod) { SetInputPriorityEventEnabled(false); // If we did not earlier, let's signal the shutdown to JS now. SignalImpendingShutdownToContentJS(); + + // Adjust the QoS priorities for shutdown, if they exist. + if (StaticPrefs::threads_use_low_power_enabled() && + StaticPrefs:: + threads_lower_mainthread_priority_in_background_enabled()) { + SetMainThreadQoSPriority(nsIThread::QOS_PRIORITY_NORMAL); + } + // Send a high priority announcement first. If this fails, SendShutdown // will also fail. Unused << SendShutdownConfirmedHP(); @@ -3524,17 +3529,30 @@ mozilla::ipc::IPCResult ContentParent::RecvClipboardHasType( return IPC_OK(); } -mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats( - const int32_t& aWhichClipboard, const bool& aPlainTextOnly, - nsTArray<nsCString>* aTypes) { - MOZ_ASSERT(aTypes); - DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly, - aTypes); - return IPC_OK(); -} - namespace { +static Result<ClipboardReadRequest, nsresult> CreateClipboardReadRequest( + ContentParent& aContentParent, + nsIAsyncGetClipboardData& aAsyncGetClipboardData) { + nsTArray<nsCString> flavors; + nsresult rv = aAsyncGetClipboardData.GetFlavorList(flavors); + if (NS_FAILED(rv)) { + return Err(rv); + } + + auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>( + &aContentParent, &aAsyncGetClipboardData); + + // Open a remote endpoint for our PClipboardReadRequest actor. + ManagedEndpoint<PClipboardReadRequestChild> childEndpoint = + aContentParent.OpenPClipboardReadRequestEndpoint(requestParent); + if (NS_WARN_IF(!childEndpoint.IsValid())) { + return Err(NS_ERROR_FAILURE); + } + + return ClipboardReadRequest(std::move(childEndpoint), std::move(flavors)); +} + class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback { public: ClipboardGetCallback(ContentParent* aContentParent, @@ -3548,20 +3566,16 @@ class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback { // nsIAsyncClipboardGetCallback NS_IMETHOD OnSuccess( nsIAsyncGetClipboardData* aAsyncGetClipboardData) override { - nsTArray<nsCString> flavors; - nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavors); - if (NS_FAILED(rv)) { - return OnError(rv); - } + MOZ_ASSERT(mContentParent); + MOZ_ASSERT(aAsyncGetClipboardData); - auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>( - mContentParent, aAsyncGetClipboardData); - if (!mContentParent->SendPClipboardReadRequestConstructor( - requestParent, std::move(flavors))) { - return OnError(NS_ERROR_FAILURE); + auto result = + CreateClipboardReadRequest(*mContentParent, *aAsyncGetClipboardData); + if (result.isErr()) { + return OnError(result.unwrapErr()); } - mResolver(PClipboardReadRequestOrError(requestParent)); + mResolver(result.unwrap()); return NS_OK; } @@ -3624,6 +3638,48 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync( return IPC_OK(); } +mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataSnapshotSync( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + ClipboardReadRequestOrError* aRequestOrError) { + // If the requesting context has been discarded, cancel the paste. + if (aRequestingWindowContext.IsDiscarded()) { + *aRequestOrError = NS_ERROR_FAILURE; + return IPC_OK(); + } + + RefPtr<WindowGlobalParent> requestingWindow = + aRequestingWindowContext.get_canonical(); + if (requestingWindow && requestingWindow->GetContentParent() != this) { + return IPC_FAIL( + this, "attempt to paste into WindowContext loaded in another process"); + } + + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID)); + if (!clipboard) { + *aRequestOrError = NS_ERROR_FAILURE; + return IPC_OK(); + } + + nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData; + nsresult rv = + clipboard->GetDataSnapshotSync(aTypes, aWhichClipboard, requestingWindow, + getter_AddRefs(asyncGetClipboardData)); + if (NS_FAILED(rv)) { + *aRequestOrError = rv; + return IPC_OK(); + } + + auto result = CreateClipboardReadRequest(*this, *asyncGetClipboardData); + if (result.isErr()) { + *aRequestOrError = result.unwrapErr(); + return IPC_OK(); + } + + *aRequestOrError = result.unwrap(); + return IPC_OK(); +} + already_AddRefed<PClipboardWriteRequestParent> ContentParent::AllocPClipboardWriteRequestParent( const int32_t& aClipboardType) { @@ -4444,11 +4500,11 @@ void ContentParent::GeneratePairedMinidump(const char* aReason) { // minidump tagging along, so we have to tell the crash reporter that // it exists and is being appended. nsAutoCString additionalDumps("browser"); - mCrashReporter->AddAnnotation( + mCrashReporter->AddAnnotationNSCString( CrashReporter::Annotation::additional_minidumps, additionalDumps); nsDependentCString reason(aReason); - mCrashReporter->AddAnnotation(CrashReporter::Annotation::ipc_channel_error, - reason); + mCrashReporter->AddAnnotationNSCString( + CrashReporter::Annotation::ipc_channel_error, reason); // Generate the report and insert into the queue for submittal. if (mCrashReporter->GenerateMinidumpAndPair(this, "browser"_ns)) { @@ -7084,7 +7140,7 @@ mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaFullScreenState( mozilla::ipc::IPCResult ContentParent::RecvNotifyPositionStateChanged( const MaybeDiscarded<BrowsingContext>& aContext, - const PositionState& aState) { + const Maybe<PositionState>& aState) { if (aContext.IsNullOrDiscarded()) { return IPC_OK(); } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 53994e45e7..15139a93f1 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -964,16 +964,17 @@ class ContentParent final : public PContentParent, const int32_t& aWhichClipboard, bool* aHasType); - mozilla::ipc::IPCResult RecvGetExternalClipboardFormats( - const int32_t& aWhichClipboard, const bool& aPlainTextOnly, - nsTArray<nsCString>* aTypes); - mozilla::ipc::IPCResult RecvGetClipboardAsync( nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, const MaybeDiscarded<WindowContext>& aRequestingWindowContext, mozilla::NotNull<nsIPrincipal*> aRequestingPrincipal, GetClipboardAsyncResolver&& aResolver); + mozilla::ipc::IPCResult RecvGetClipboardDataSnapshotSync( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + ClipboardReadRequestOrError* aRequestOrError); + already_AddRefed<PClipboardWriteRequestParent> AllocPClipboardWriteRequestParent(const int32_t& aClipboardType); @@ -1270,7 +1271,7 @@ class ContentParent final : public PContentParent, mozilla::ipc::IPCResult RecvNotifyPositionStateChanged( const MaybeDiscarded<BrowsingContext>& aContext, - const PositionState& aState); + const Maybe<PositionState>& aState); mozilla::ipc::IPCResult RecvAddOrRemovePageAwakeRequest( const MaybeDiscarded<BrowsingContext>& aContext, diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp index ebd24cb0d3..ea65f79c23 100644 --- a/dom/ipc/FilePickerParent.cpp +++ b/dom/ipc/FilePickerParent.cpp @@ -219,25 +219,17 @@ void FilePickerParent::Done(nsIFilePicker::ResultCode aResult) { } bool FilePickerParent::CreateFilePicker() { - mFilePicker = do_CreateInstance("@mozilla.org/filepicker;1"); - if (!mFilePicker) { + if (!mBrowsingContext) { return false; } - auto* browserParent = BrowserParent::GetFrom(Manager()); - auto* browsingContext = browserParent->GetBrowsingContext(); - Element* element = browserParent->GetOwnerElement(); - if (!element) { - return false; - } + mFilePicker = do_CreateInstance("@mozilla.org/filepicker;1"); - nsCOMPtr<mozIDOMWindowProxy> window = element->OwnerDoc()->GetWindow(); - if (!window) { + if (!mFilePicker) { return false; } - return NS_SUCCEEDED( - mFilePicker->Init(window, mTitle, mMode, browsingContext)); + return NS_SUCCEEDED(mFilePicker->Init(mBrowsingContext, mTitle, mMode)); } mozilla::ipc::IPCResult FilePickerParent::RecvOpen( diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h index f0fe0dc2d0..3cdb5ab912 100644 --- a/dom/ipc/FilePickerParent.h +++ b/dom/ipc/FilePickerParent.h @@ -12,6 +12,7 @@ #include "nsCOMArray.h" #include "nsThreadUtils.h" #include "mozilla/dom/File.h" +#include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/PFilePickerParent.h" class nsIFile; @@ -20,8 +21,12 @@ namespace mozilla::dom { class FilePickerParent : public PFilePickerParent { public: - FilePickerParent(const nsString& aTitle, const nsIFilePicker::Mode& aMode) - : mTitle(aTitle), mMode(aMode), mResult(nsIFilePicker::returnOK) {} + FilePickerParent(const nsString& aTitle, const nsIFilePicker::Mode& aMode, + BrowsingContext* aBrowsingContext) + : mTitle(aTitle), + mMode(aMode), + mBrowsingContext(aBrowsingContext), + mResult(nsIFilePicker::returnOK) {} private: virtual ~FilePickerParent(); @@ -93,6 +98,7 @@ class FilePickerParent : public PFilePickerParent { nsString mTitle; nsIFilePicker::Mode mMode; + RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; nsIFilePicker::ResultCode mResult; }; diff --git a/dom/ipc/JSValidatorChild.cpp b/dom/ipc/JSValidatorChild.cpp index 5070a46492..86ddeb3434 100644 --- a/dom/ipc/JSValidatorChild.cpp +++ b/dom/ipc/JSValidatorChild.cpp @@ -179,7 +179,12 @@ JSValidatorChild::GetUTF8EncodedContent( JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS( const mozilla::Span<const char>& aSpan) const { - MOZ_ASSERT(!aSpan.IsEmpty()); + // It's possible that the data we get is not valid UTF-8, so aSpan + // ends empty here. We should treat it as a failure because this + // is not valid JS. + if (aSpan.IsEmpty()) { + return ValidatorResult::Failure; + } MOZ_DIAGNOSTIC_ASSERT(IsUtf8(aSpan)); @@ -202,9 +207,8 @@ JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS( prefableOptions.setAsmJSOption(JS::AsmJSOption::DisabledByAsmJSPref); JS::CompileOptions options(prefableOptions); - JS::CompilationStorage storage; RefPtr<JS::Stencil> stencil = - JS::CompileGlobalScriptToStencil(fc, options, srcBuf, storage); + JS::CompileGlobalScriptToStencil(fc, options, srcBuf); if (!stencil) { JS::ClearFrontendErrors(fc); diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index bc126724f4..caef472ec2 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -428,7 +428,7 @@ parent: */ async PColorPicker(nsString title, nsString initialColor, nsString[] defaultColors); - async PFilePicker(nsString aTitle, Mode aMode); + async PFilePicker(nsString aTitle, Mode aMode, MaybeDiscardedBrowsingContext aBrowsingContext); /** * Tells the containing widget whether the given input block results in a diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index b8155fd375..3789c046c8 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -188,7 +188,7 @@ struct SystemFontListEntry { uint8_t index; FontVisibility visibility; }; -#elif defined(XP_MACOSX) +#elif defined(XP_MACOSX) || defined(XP_IOS) // Used on Mac OS X to pass the list of font families (not faces) // from chrome to content processes. // The entryType field distinguishes several types of font family @@ -470,8 +470,13 @@ struct IPCImage { ImageIntSize size; }; -union PClipboardReadRequestOrError { - PClipboardReadRequest; +struct ClipboardReadRequest { + ManagedEndpoint<PClipboardReadRequestChild> childEndpoint; + nsCString[] availableTypes; +}; + +union ClipboardReadRequestOrError { + ClipboardReadRequest; nsresult; }; @@ -1057,8 +1062,6 @@ child: async PRemotePrintJob(); - async PClipboardReadRequest(nsCString[] aTypes); - parent: async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext, nullable nsILayoutHistoryState aState); @@ -1226,14 +1229,16 @@ parent: MaybeDiscardedWindowContext aRequestingWindowContext) returns (IPCTransferableData transferableData); - // Returns a list of formats supported by the clipboard - sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes); - // Requests getting data from clipboard. async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard, MaybeDiscardedWindowContext aRequestingWindowContext, nsIPrincipal aRequestingPrincipal) - returns (PClipboardReadRequestOrError aClipboardReadRequest); + returns (ClipboardReadRequestOrError aClipboardReadRequestOrError); + + // Requests getting data from clipboard. + sync GetClipboardDataSnapshotSync(nsCString[] aTypes, int32_t aWhichClipboard, + MaybeDiscardedWindowContext aRequestingWindowContext) + returns (ClipboardReadRequestOrError aClipboardReadRequestOrError); // Clears the clipboard. async EmptyClipboard(int32_t aWhichClipboard); @@ -1733,7 +1738,7 @@ parent: */ async NotifyPositionStateChanged( MaybeDiscardedBrowsingContext aContext, - PositionState aState); + PositionState? aState); /** * This method will make canonical browsing context to update the count of diff --git a/dom/ipc/ProcessHangMonitor.cpp b/dom/ipc/ProcessHangMonitor.cpp index 1dcba24406..7b0f45598c 100644 --- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -86,6 +86,8 @@ using namespace mozilla::ipc; namespace { +LazyLogModule gQoSLog("QoSPriority"); // For RecvSetMainThreadQoSPriority. + /* Child process objects */ class HangMonitorChild : public PProcessHangMonitorChild, @@ -560,12 +562,10 @@ mozilla::ipc::IPCResult HangMonitorChild::RecvRequestContentJSInterrupt() { // In order to cancel JS execution on shutdown, we expect that // ProcessChild::NotifiedImpendingShutdown has been called before. if (mozilla::ipc::ProcessChild::ExpectingShutdown()) { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "HangMonitorChild::RecvRequestContentJSInterrupt (expected)"_ns); } else { - CrashReporter::AppendToCrashReportAnnotation( - CrashReporter::Annotation::IPCShutdownState, + ProcessChild::AppendToIPCShutdownStateAnnotation( "HangMonitorChild::RecvRequestContentJSInterrupt (unexpected)"_ns); } JS_RequestInterruptCallback(mContext); @@ -651,9 +651,21 @@ mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning( return IPC_OK(); } +const char* DefineQoS(const nsIThread::QoSPriority& aQoSPriority) { + if (aQoSPriority == nsIThread::QOS_PRIORITY_LOW) { + return "BACKGROUND"; + } + // As of right now, all non-low QoS priorities default to the thread's normal + // priority. + return "NORMAL"; +} + mozilla::ipc::IPCResult HangMonitorChild::RecvSetMainThreadQoSPriority( const nsIThread::QoSPriority& aQoSPriority) { MOZ_RELEASE_ASSERT(IsOnThread()); + MOZ_LOG(gQoSLog, LogLevel::Debug, + ("Priority change %s recieved by content process.", + DefineQoS(aQoSPriority))); #ifdef XP_MACOSX // If the new priority is the background (low) priority, we can tell the OS to @@ -672,14 +684,22 @@ mozilla::ipc::IPCResult HangMonitorChild::RecvSetMainThreadQoSPriority( pthread_override_qos_class_start_np(mMainPThread, qosClass, 0); if (NS_FAILED(NS_DispatchToMainThread(NS_NewRunnableFunction( "HangMonitorChild::RecvSetMainThreadQoSPriority", - [qosClass, qosOverride] { + [qosClass, qosOverride, aQoSPriority] { + MOZ_LOG( + gQoSLog, LogLevel::Debug, + ("Override %s sent to main thread.", DefineQoS(aQoSPriority))); pthread_set_qos_class_self_np(qosClass, 0); if (qosOverride) { pthread_override_qos_class_end_np(qosOverride); + MOZ_LOG(gQoSLog, LogLevel::Debug, + ("Override %s removed from main thread.", + DefineQoS(aQoSPriority))); } })))) { // If we fail to dispatch, go ahead and end the override anyway. pthread_override_qos_class_end_np(qosOverride); + MOZ_LOG(gQoSLog, LogLevel::Debug, + ("Override %s removed from main thread.", DefineQoS(aQoSPriority))); } #endif diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp index 2c54b43295..10fca879a6 100644 --- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -203,8 +203,6 @@ class ProcessPriorityManagerImpl final : public nsIObserver, void BrowserPriorityChanged(CanonicalBrowsingContext* aBC, bool aPriority); void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority); - void ResetPriority(ContentParent* aContentParent); - private: static bool sPrefListenersRegistered; static bool sInitialized; @@ -559,12 +557,6 @@ void ProcessPriorityManagerImpl::BrowserPriorityChanged( } } -void ProcessPriorityManagerImpl::ResetPriority(ContentParent* aContentParent) { - if (RefPtr pppm = GetParticularProcessPriorityManager(aContentParent)) { - pppm->ResetPriority(); - } -} - NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsITimerCallback, nsISupportsWeakReference, nsINamed); @@ -839,11 +831,14 @@ void ParticularProcessPriorityManager::SetPriorityNow( // thread on low-power cores. Alternately, if we are changing from the // background to a higher priority, we change the main thread back to its // normal state. + // During shutdown, we will manually set the priority to the highest + // possible and disallow any additional priority changes. // // The messages for this will be relayed using the ProcessHangMonitor such // that the priority can be raised even if the main thread is unresponsive. - if (PriorityUsesLowPowerMainThread(mPriority) != - (PriorityUsesLowPowerMainThread(oldPriority))) { + if (!mContentParent->IsShuttingDown() && + PriorityUsesLowPowerMainThread(mPriority) != + PriorityUsesLowPowerMainThread(oldPriority)) { if (PriorityUsesLowPowerMainThread(mPriority) && PrefsUseLowPriorityThreads()) { mContentParent->SetMainThreadQoSPriority(nsIThread::QOS_PRIORITY_LOW); @@ -1042,26 +1037,4 @@ void ProcessPriorityManager::BrowserPriorityChanged( singleton->BrowserPriorityChanged(aBrowserParent, aPriority); } -/* static */ -void ProcessPriorityManager::RemoteBrowserFrameShown( - nsFrameLoader* aFrameLoader) { - ProcessPriorityManagerImpl* singleton = - ProcessPriorityManagerImpl::GetSingleton(); - if (!singleton) { - return; - } - - BrowserParent* bp = BrowserParent::GetFrom(aFrameLoader); - NS_ENSURE_TRUE_VOID(bp); - - MOZ_ASSERT(XRE_IsParentProcess()); - - // Ignore calls that aren't from a Browser. - if (!aFrameLoader->OwnerIsMozBrowserFrame()) { - return; - } - - singleton->ResetPriority(bp->Manager()); -} - } // namespace mozilla diff --git a/dom/ipc/ProcessPriorityManager.h b/dom/ipc/ProcessPriorityManager.h index 8153a60993..be1508bfbd 100644 --- a/dom/ipc/ProcessPriorityManager.h +++ b/dom/ipc/ProcessPriorityManager.h @@ -80,8 +80,6 @@ class ProcessPriorityManager final { static void BrowserPriorityChanged(dom::BrowserParent* aBrowserParent, bool aPriority); - static void RemoteBrowserFrameShown(nsFrameLoader* aFrameLoader); - private: ProcessPriorityManager(); ProcessPriorityManager(const ProcessPriorityManager&) = delete; diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn deleted file mode 100644 index 6c43857aea..0000000000 --- a/dom/ipc/jar.mn +++ /dev/null @@ -1,7 +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/. - -toolkit.jar: - content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js) - content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js) diff --git a/dom/ipc/jsactor/JSActorManager.cpp b/dom/ipc/jsactor/JSActorManager.cpp index b8791570d6..c6be8488b5 100644 --- a/dom/ipc/jsactor/JSActorManager.cpp +++ b/dom/ipc/jsactor/JSActorManager.cpp @@ -63,19 +63,21 @@ already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx, // If the JSActor uses `loadInDevToolsLoader`, force loading in the DevTools // specific's loader. RefPtr loader = protocol->mLoadInDevToolsLoader - ? mozJSModuleLoader::GetOrCreateDevToolsLoader() + ? mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx) : mozJSModuleLoader::Get(); MOZ_ASSERT(loader); // We're about to construct the actor, so make sure we're in the loader realm // while importing etc. - JSAutoRealm ar(aCx, loader->GetSharedGlobal(aCx)); + JSAutoRealm ar(aCx, loader->GetSharedGlobal()); // If a module URI was provided, use it to construct an instance of the actor. JS::Rooted<JSObject*> actorObj(aCx); if (side.mModuleURI || side.mESModuleURI) { JS::Rooted<JSObject*> exports(aCx); if (side.mModuleURI) { + // TODO: Remove this once m-c, c-c, and out-of-tree code migrations finish + // (bug 1866732). JS::Rooted<JSObject*> global(aCx); aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports); if (aRv.Failed()) { @@ -143,9 +145,9 @@ void JSActorManager::ReceiveRawMessage( Maybe<ipc::StructuredCloneData>&& aStack) { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); - CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::AutoRecordAnnotation autoActorName( CrashReporter::Annotation::JSActorName, aMetadata.actorName()); - CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::AutoRecordAnnotation autoMessageName( CrashReporter::Annotation::JSActorMessage, NS_LossyConvertUTF16toASCII(aMetadata.messageName())); @@ -237,7 +239,7 @@ void JSActorManager::JSActorWillDestroy() { void JSActorManager::JSActorDidDestroy() { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); - CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::AutoRecordAnnotation autoMessageName( CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns); // Swap the table with `mJSActors` so that we don't invalidate it while @@ -245,7 +247,7 @@ void JSActorManager::JSActorDidDestroy() { const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors = std::move(mJSActors); for (const auto& entry : actors.Values()) { - CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::AutoRecordAnnotation autoActorName( CrashReporter::Annotation::JSActorName, entry->Name()); // Do not risk to run script very late in shutdown if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { diff --git a/dom/ipc/jsactor/JSActorService.cpp b/dom/ipc/jsactor/JSActorService.cpp index 3fde76a206..5b6c63d2a4 100644 --- a/dom/ipc/jsactor/JSActorService.cpp +++ b/dom/ipc/jsactor/JSActorService.cpp @@ -106,9 +106,9 @@ void JSActorService::RegisterWindowActor(const nsACString& aName, void JSActorService::UnregisterWindowActor(const nsACString& aName) { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); - CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::AutoRecordAnnotation autoActorName( CrashReporter::Annotation::JSActorName, aName); - CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::AutoRecordAnnotation autoMessageName( CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns); nsAutoCString name(aName); @@ -268,9 +268,9 @@ void JSActorService::RegisterProcessActor(const nsACString& aName, void JSActorService::UnregisterProcessActor(const nsACString& aName) { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); - CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::AutoRecordAnnotation autoActorName( CrashReporter::Annotation::JSActorName, aName); - CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::AutoRecordAnnotation autoMessageName( CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns); nsAutoCString name(aName); diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 68f82f5a38..3f10b9fbab 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -257,8 +257,6 @@ DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": DEFINES["MOZ_ENABLE_FREETYPE"] = True -JAR_MANIFESTS += ["jar.mn"] - BROWSER_CHROME_MANIFESTS += [ "tests/browser.toml", "tests/JSProcessActor/browser.toml", diff --git a/dom/ipc/tests/JSWindowActor/browser.toml b/dom/ipc/tests/JSWindowActor/browser.toml index a9dc7e8b8f..5f406464f4 100644 --- a/dom/ipc/tests/JSWindowActor/browser.toml +++ b/dom/ipc/tests/JSWindowActor/browser.toml @@ -6,7 +6,10 @@ support-files = ["head.js"] ["browser_crash_report.js"] ["browser_destroy_callbacks.js"] -skip-if = ["!debug && os == 'mac'"] #Bug 1604538 +skip-if = [ + "apple_catalina && !debug", #Bug 1604538 + "apple_silicon && !debug", #Bug 1604538 +] ["browser_event_listener.js"] support-files = ["file_dummyChromePage.html"] diff --git a/dom/ipc/tests/JSWindowActor/browser_crash_report.js b/dom/ipc/tests/JSWindowActor/browser_crash_report.js index f029f1a85a..d2b2b3a694 100644 --- a/dom/ipc/tests/JSWindowActor/browser_crash_report.js +++ b/dom/ipc/tests/JSWindowActor/browser_crash_report.js @@ -5,7 +5,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ declTest("crash actor", { allFrames: true, - async test(browser) { + async test() { if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { ok(true, "Cannot test crash annotations without a crash reporter"); return; diff --git a/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js index 74cbae9415..e92de1b82f 100644 --- a/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js +++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js @@ -142,7 +142,7 @@ declTest("destroy actor by page navigates", { declTest("destroy actor by tab being closed", { allFrames: true, - async test(browser) { + async test() { info("creating a new tab"); let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); let newTabBrowser = newTab.linkedBrowser; @@ -158,7 +158,7 @@ declTest("destroy actor by tab being closed", { let didDestroyPromise = new Promise(resolve => { Services.ppmm.addMessageListener( "test-jswindowactor-diddestroy", - function onmessage(msg) { + function onmessage() { Services.ppmm.removeMessageListener( "test-jswindowactor-diddestroy", onmessage diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js index a10697c989..691cffe603 100644 --- a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js +++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js @@ -169,7 +169,7 @@ declTest("getActor with iframe messageManagerGroups match", { let parent = browser.browsingContext.currentWindowGlobal; ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value."); - await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + await SpecialPowers.spawn(browser, [TEST_URL], async function () { let child = content.windowGlobalChild; ok(child, "WindowGlobalChild should have value."); ok(child.getActor("TestWindow"), "JSWindowActorChild should have value."); @@ -189,7 +189,7 @@ declTest("getActor with iframe messageManagerGroups mismatch", { "Should throw if its messageManagerGroups doesn't match." ); - await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + await SpecialPowers.spawn(browser, [TEST_URL], async function () { let child = content.windowGlobalChild; ok(child, "WindowGlobalChild should have value."); Assert.throws( diff --git a/dom/ipc/tests/browser_child_clipboard_restricted.js b/dom/ipc/tests/browser_child_clipboard_restricted.js index be2d1bca9c..6fd062d559 100644 --- a/dom/ipc/tests/browser_child_clipboard_restricted.js +++ b/dom/ipc/tests/browser_child_clipboard_restricted.js @@ -9,7 +9,7 @@ add_task(async function () { ); let browser = tab.linkedBrowser; - await SpecialPowers.spawn(browser, [], async function (arg) { + await SpecialPowers.spawn(browser, [], async function () { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); diff --git a/dom/ipc/tests/browser_gc_schedule.js b/dom/ipc/tests/browser_gc_schedule.js index 8b44c98eae..e1381f82c0 100644 --- a/dom/ipc/tests/browser_gc_schedule.js +++ b/dom/ipc/tests/browser_gc_schedule.js @@ -15,7 +15,7 @@ async function waitForGCBegin() { // This fixes a ReferenceError for Date, it's weird. ok(Date.now(), "Date.now()"); var when = await new Promise(resolve => { - observer.observe = function (subject, topic, data) { + observer.observe = function () { resolve(Date.now()); }; @@ -40,7 +40,7 @@ async function waitForGCEnd() { // This fixes a ReferenceError for Date, it's weird. ok(Date.now(), "Date.now()"); let when = await new Promise(resolve => { - observer.observe = function (subject, topic, data) { + observer.observe = function () { resolve(Date.now()); }; diff --git a/dom/ipc/tests/browser_wpi_base.js b/dom/ipc/tests/browser_wpi_base.js index 7a01c9a161..f09255a99c 100644 --- a/dom/ipc/tests/browser_wpi_base.js +++ b/dom/ipc/tests/browser_wpi_base.js @@ -183,7 +183,7 @@ const heuristics = [ }, { name: "hasSavedLogin", - setup_com: async expected => { + setup_com: async () => { // add .com to the password manager let LoginInfo = new Components.Constructor( "@mozilla.org/login-manager/loginInfo;1", @@ -208,7 +208,7 @@ const heuristics = [ }, { name: "isLoggedIn", - setup_com: async expected => { + setup_com: async () => { let p = new Promise(resolve => { Services.obs.addObserver(function obs() { Services.obs.removeObserver( diff --git a/dom/ipc/tests/file_broadcast_currenturi_onload.html b/dom/ipc/tests/file_broadcast_currenturi_onload.html index b92c46c944..e258fbdf83 100644 --- a/dom/ipc/tests/file_broadcast_currenturi_onload.html +++ b/dom/ipc/tests/file_broadcast_currenturi_onload.html @@ -33,7 +33,7 @@ async function getURIs() { return { location: location.href, docURI, curURI }; } -addEventListener("load", async e => { +addEventListener("load", async () => { // If a payload parameter was included, just send the message. const payloadStr = url.searchParams.get("payload"); if (payloadStr) { diff --git a/dom/ipc/tests/file_endless_js.html b/dom/ipc/tests/file_endless_js.html index 926fb1d8ab..338cea5e57 100644 --- a/dom/ipc/tests/file_endless_js.html +++ b/dom/ipc/tests/file_endless_js.html @@ -2,7 +2,7 @@ <html> <head><meta charset="utf-8"></head> <script> - function hang(m) { + function hang() { let i = 1; while (i > 0) { i = Date.now(); diff --git a/dom/ipc/tests/process_error.xhtml b/dom/ipc/tests/process_error.xhtml index 3d57a3f456..8dea882c78 100644 --- a/dom/ipc/tests/process_error.xhtml +++ b/dom/ipc/tests/process_error.xhtml @@ -25,7 +25,7 @@ let browser = document.getElementById('thebrowser'); let observerPromise = new Promise(resolve => { - let crashObserver = (subject, topic, data) => { + let crashObserver = (subject, topic) => { is(topic, 'ipc:content-shutdown', 'Received correct observer topic.'); ok(subject instanceof Ci.nsIPropertyBag2, 'Subject implements nsIPropertyBag2.'); diff --git a/dom/ipc/tests/test_bug1086684.js b/dom/ipc/tests/test_bug1086684.js index 8a34906686..218d2b14a6 100644 --- a/dom/ipc/tests/test_bug1086684.js +++ b/dom/ipc/tests/test_bug1086684.js @@ -37,7 +37,7 @@ function childFrameScript() { ); function parentReady(message) { - MockFilePicker.init(content); + MockFilePicker.init(content.browsingContext); MockFilePicker.setFiles([message.data.file]); MockFilePicker.returnValue = MockFilePicker.returnOK; diff --git a/dom/ipc/tests/test_temporaryfile_stream.html b/dom/ipc/tests/test_temporaryfile_stream.html index 9fa76a2155..1a0cfcaef4 100644 --- a/dom/ipc/tests/test_temporaryfile_stream.html +++ b/dom/ipc/tests/test_temporaryfile_stream.html @@ -54,7 +54,7 @@ function startTest() { video.id = "recorded-video"; video.src = URL.createObjectURL(xhr.response); video.play(); - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/ipc/tests/test_window_open_discarded_bc.html b/dom/ipc/tests/test_window_open_discarded_bc.html index 4cd81463e0..fae731d068 100644 --- a/dom/ipc/tests/test_window_open_discarded_bc.html +++ b/dom/ipc/tests/test_window_open_discarded_bc.html @@ -11,7 +11,7 @@ add_task(async function() { const TOPIC = "dangerous:test-only:new-browser-child-ready"; let found = false; - function observer(subject, topic, data) { + function observer(subject) { let win = SpecialPowers.wrap(subject); if (SpecialPowers.compare(win.opener, window)) { found = true; diff --git a/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html b/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html index 278baa15dd..b84f035313 100644 --- a/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html +++ b/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html @@ -37,7 +37,7 @@ title2 = Hello Another World is(h1.textContent, "Hello World"); - const mo = new MutationObserver(function onMutations(mutations) { + const mo = new MutationObserver(function onMutations() { is(h1.textContent, "Hello Another World"); mo.disconnect(); SimpleTest.finish(); diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties index 5fc7bcfdf5..f077bc8ab0 100644 --- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -3,20 +3,103 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # CSP Warnings: -# LOCALIZATION NOTE (CSPViolation): -# %1$S is the reason why the resource has not been loaded. -CSPViolation = The page’s settings blocked the loading of a resource: %1$S -# LOCALIZATION NOTE (CSPViolationWithURI): -# %1$S is the directive that has been violated. + +# LOCALIZATION NOTE (CSPInlineStyleViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. style-src-elem) +CSPInlineStyleViolation = The page’s settings blocked an inline style (%2$S) from being applied because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROInlineStyleViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. style-src-elem) +CSPROInlineStyleViolation = (Report-Only policy) The page’s settings would block an inline style (%2$S) from being applied because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPInlineScriptViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src-elem) +CSPInlineScriptViolation = The page’s settings blocked an inline script (%2$S) from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROInlineScriptViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src-elem) +CSPROInlineScriptViolation = (Report-Only policy) The page’s settings would block an inline script (%2$S) from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPEventHandlerScriptViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src-attr) +CSPEventHandlerScriptViolation = The page’s settings blocked an event handler (%2$S) from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROEventHandlerScriptViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src-attr) +CSPROEventHandlerScriptViolation = (Report-Only policy) The page’s settings would block an event handler (%2$S) from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPEvalScriptViolation): +# Don't translate/change "'unsafe-eval'", including the single quote. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src) +CSPEvalScriptViolation = The page’s settings blocked a JavaScript eval (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'unsafe-eval') +# LOCALIZATION NOTE (CSPROEvalScriptViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# Don't translate/change "'unsafe-eval'", including the single quote. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src) +CSPROEvalScriptViolation = (Report-Only policy) The page’s settings would block a JavaScript eval (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'unsafe-eval') +# LOCALIZATION NOTE (CSPWasmEvalScriptViolation): +# WebAssembly is a feature name. +# Don't translate/change "'wasm-unsafe-eval'" or "'unsafe-eval'", including the single quote. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src) +CSPWasmEvalScriptViolation = The page’s settings blocked WebAssembly (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'wasm-unsafe-eval' or 'unsafe-eval') +# LOCALIZATION NOTE (CSPROWasmEvalScriptViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# WebAssembly is a feature name. +# Don't translate/change "'wasm-unsafe-eval'" or "'unsafe-eval'", including the single quote. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the type of directive used by the resource (e.g. script-src) +CSPROWasmEvalScriptViolation = (Report-Only policy) The page’s settings would block WebAssembly (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'wasm-unsafe-eval' or 'unsafe-eval') +# LOCALIZATION NOTE (CSPStyleViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. style-src) +CSPStyleViolation = The page’s settings blocked a style (%3$S) at %2$S from being applied because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROStyleViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. style-src) +CSPROStyleViolation = (Report-Only policy) The page’s settings would block a style (%3$S) at %2$S from being applied because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPScriptViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. script-src-elem) +CSPScriptViolation = The page’s settings blocked a script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROScriptViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") # %2$S is the URI of the resource which violated the directive. -CSPViolationWithURI = The page’s settings blocked the loading of a resource at %2$S (“%1$S”). -# LOCALIZATION NOTE (CSPROViolation): -# %1$S is the reason why the resource has not been loaded. -CSPROViolation = A violation occurred for a report-only CSP policy (“%1$S”). The behavior was allowed, and a CSP report was sent. -# LOCALIZATION NOTE (CSPROViolationWithURI): -# %1$S is the directive that has been violated. +# %3$S is the type of directive used by the resource (e.g. script-src-elem) +CSPROScriptViolation = (Report-Only policy) The page’s settings would block a script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPWorkerViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") # %2$S is the URI of the resource which violated the directive. -CSPROViolationWithURI = The page’s settings observed the loading of a resource at %2$S (“%1$S”). A CSP report is being sent. +# %3$S is the type of directive used by the resource (e.g. worker-src) +CSPWorkerViolation = The page’s settings blocked a worker script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROWorkerViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. worker-src) +CSPROWorkerViolation = (Report-Only policy) The page’s settings would block a worker script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPGenericViolation): +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. image-src) +CSPGenericViolation = The page’s settings blocked the loading of a resource (%3$S) at %2$S because it violates the following directive: “%1$S” +# LOCALIZATION NOTE (CSPROGenericViolation): +# Don't translate "Report-Only" as it's part of the name Content-Security-Policy-Report-Only. +# %1$S is the entire directive that has been violated. (e.g. "default-src 'none'") +# %2$S is the URI of the resource which violated the directive. +# %3$S is the type of directive used by the resource (e.g. image-src) +CSPROGenericViolation = (Report-Only policy) The page’s settings would block the loading of a resource (%3$S) at %2$S because it violates the following directive: “%1$S” + # LOCALIZATION NOTE (triedToSendReport): # %1$S is the URI we attempted to send a report to. triedToSendReport = Tried to send report to invalid URI: “%1$S” diff --git a/dom/locales/en-US/chrome/security/security.properties b/dom/locales/en-US/chrome/security/security.properties index c19fc2d2bf..fc58fe4f7d 100644 --- a/dom/locales/en-US/chrome/security/security.properties +++ b/dom/locales/en-US/chrome/security/security.properties @@ -44,7 +44,7 @@ LoadingMixedActiveContent2=Loading mixed (insecure) active content “%1$S” on LoadingMixedDisplayContent2=Loading mixed (insecure) display content “%1$S” on a secure page LoadingMixedDisplayObjectSubrequestDeprecation=Loading mixed (insecure) content “%1$S” within a plugin on a secure page is discouraged and will be blocked soon. # LOCALIZATION NOTE: "%S" is the URI of the insecure mixed content download -MixedContentBlockedDownload = Blocked downloading insecure content “%S”. +BlockedInsecureDownload = We blocked a download that’s not secure: “%S”. # LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe" BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing. diff --git a/dom/localstorage/LSSnapshot.cpp b/dom/localstorage/LSSnapshot.cpp index 85920b8b09..a9c0c4bc5d 100644 --- a/dom/localstorage/LSSnapshot.cpp +++ b/dom/localstorage/LSSnapshot.cpp @@ -378,7 +378,7 @@ nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue, { quota::ScopedLogExtraInfo scope{ - quota::ScopedLogExtraInfo::kTagContext, + quota::ScopedLogExtraInfo::kTagContextTainted, "dom::localstorage::LSSnapshot::SetItem::UpdateUsage"_ns}; QM_TRY(MOZ_TO_RESULT(UpdateUsage(delta)), QM_PROPAGATE, QM_NO_CLEANUP, ([]() { diff --git a/dom/localstorage/test/unit/databaseShadowing-shared.js b/dom/localstorage/test/unit/databaseShadowing-shared.js index ffee8579cb..77d6c09e39 100644 --- a/dom/localstorage/test/unit/databaseShadowing-shared.js +++ b/dom/localstorage/test/unit/databaseShadowing-shared.js @@ -1,5 +1,7 @@ /* import-globals-from head.js */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + const principalInfos = [ { url: "http://example.com", attrs: {} }, diff --git a/dom/locks/test/test_strongworker.html b/dom/locks/test/test_strongworker.html index 9c6905919c..1ee8de16f4 100644 --- a/dom/locks/test/test_strongworker.html +++ b/dom/locks/test/test_strongworker.html @@ -34,7 +34,7 @@ SpecialPowers.DOMWindowUtils.garbageCollect(); }); - channel.onmessage = async event => { + channel.onmessage = async () => { const query = await navigator.locks.query(); is(query.pending.length, 0, "No pending request"); SimpleTest.finish(); diff --git a/dom/manifest/ImageObjectProcessor.sys.mjs b/dom/manifest/ImageObjectProcessor.sys.mjs index f72c5ff9fe..ea57d596a1 100644 --- a/dom/manifest/ImageObjectProcessor.sys.mjs +++ b/dom/manifest/ImageObjectProcessor.sys.mjs @@ -6,7 +6,7 @@ * Implementation of Image Object processing algorithms from: * http://www.w3.org/TR/appmanifest/#image-object-and-its-members * - * This is intended to be used in conjunction with ManifestProcessor.jsm + * This is intended to be used in conjunction with ManifestProcessor.sys.mjs * * Creates an object to process Image Objects as defined by the * W3C specification. This is used to process things like the diff --git a/dom/manifest/Manifest.sys.mjs b/dom/manifest/Manifest.sys.mjs index f6fab11277..15e1e2ef93 100644 --- a/dom/manifest/Manifest.sys.mjs +++ b/dom/manifest/Manifest.sys.mjs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - * Manifest.jsm is the top level api for managing installed web applications + * Manifest.sys.mjs is the top level api for managing installed web applications * https://www.w3.org/TR/appmanifest/ * * It is used to trigger the installation of a web application via .install() diff --git a/dom/manifest/ManifestIcons.sys.mjs b/dom/manifest/ManifestIcons.sys.mjs index 9994c40d55..62e07e07ae 100644 --- a/dom/manifest/ManifestIcons.sys.mjs +++ b/dom/manifest/ManifestIcons.sys.mjs @@ -54,7 +54,7 @@ async function getIcon(aWindow, icons, expectedSize) { index = icons.length - 1; } - return fetchIcon(aWindow, icons[index].src).catch(err => { + return fetchIcon(aWindow, icons[index].src).catch(() => { // Remove all icons with the failed source, the same source // may have been used for multiple sizes icons = icons.filter(x => x.src !== icons[index].src); diff --git a/dom/manifest/test/test_ManifestProcessor_warnings.html b/dom/manifest/test/test_ManifestProcessor_warnings.html index f2092c1dcf..06c7d0325a 100644 --- a/dom/manifest/test/test_ManifestProcessor_warnings.html +++ b/dom/manifest/test/test_ManifestProcessor_warnings.html @@ -131,7 +131,7 @@ const options = {...data, checkConformance: true } ; }, warn: "The id member must have the same origin as the start_url member.", } -].forEach((test, index) => { +].forEach((test) => { test.func(); const result = processor.process(options); let messages = []; diff --git a/dom/mathml/MathMLElement.cpp b/dom/mathml/MathMLElement.cpp index b6d1ad7dec..af5e9bc22b 100644 --- a/dom/mathml/MathMLElement.cpp +++ b/dom/mathml/MathMLElement.cpp @@ -77,8 +77,8 @@ nsresult MathMLElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void MathMLElement::UnbindFromTree(bool aNullParent) { - MathMLElementBase::UnbindFromTree(aNullParent); +void MathMLElement::UnbindFromTree(UnbindContext& aContext) { + MathMLElementBase::UnbindFromTree(aContext); // Without removing the link state we risk a dangling pointer in the // mStyledLinks hashtable Link::UnbindFromTree(); diff --git a/dom/mathml/MathMLElement.h b/dom/mathml/MathMLElement.h index 46198d877d..a8c21e841a 100644 --- a/dom/mathml/MathMLElement.h +++ b/dom/mathml/MathMLElement.h @@ -34,7 +34,7 @@ class MathMLElement final : public MathMLElementBase, public Link { NS_IMPL_FROMNODE(MathMLElement, kNameSpaceID_MathML) nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, diff --git a/dom/mathml/moz.build b/dom/mathml/moz.build index e8b9fd61bb..a1e38eef33 100644 --- a/dom/mathml/moz.build +++ b/dom/mathml/moz.build @@ -22,5 +22,3 @@ FINAL_LIBRARY = "xul" LOCAL_INCLUDES += [ "/dom/base", ] - -MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.toml"] diff --git a/dom/mathml/tests/mochitest/mochitest.toml b/dom/mathml/tests/mochitest/mochitest.toml deleted file mode 100644 index 3da0a28b92..0000000000 --- a/dom/mathml/tests/mochitest/mochitest.toml +++ /dev/null @@ -1,3 +0,0 @@ -[DEFAULT] - -["test_math_tabindex_focus.html"] diff --git a/dom/mathml/tests/mochitest/test_math_tabindex_focus.html b/dom/mathml/tests/mochitest/test_math_tabindex_focus.html deleted file mode 100644 index fe03c13358..0000000000 --- a/dom/mathml/tests/mochitest/test_math_tabindex_focus.html +++ /dev/null @@ -1,47 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=1128054 ---> -<head> - <title>Test for Bug 1128054</title> - <script src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1128054">Mozilla Bug 1128054</a> -<p id="display"></p> -<!-- Test default focusability --> -<math></math> -<math href="#" data-focusable=true></math> -<!-- Test tabindex=0 focusability --> -<math tabindex="0" data-focusable=true></math> -<!-- Test tabindex=-1 focusability --> -<math tabindex="-1" data-focusable=true></math> -<!-- Test tabindex=invalid focusability --> -<math tabindex="invalid"></math> -<math href="#" tabindex="invalid" data-focusable=true></math> -<!-- Tests code --> -<script class="testbody" type="text/javascript"> - -/** Test for Bug 1128054 **/ - -add_task(function test_math_tabindex_focus() { - for (let element of document.querySelectorAll("math")) { - let focusable = element.dataset && element.dataset.focusable; - let desc = "<math"; - for (let attr of ["href", "tabindex"]) { - if (element.hasAttribute(attr)) { - desc += ` ${attr}=${element.getAttribute(attr)}`; - } - } - desc += ">"; - element.focus(); - focusable ? is(document.activeElement, element, desc + " should focusable") - : isnot(document.activeElement, element, desc + " should not focusable"); - } -}); - -</script> -</body> -</html> diff --git a/dom/media/ADTSDemuxer.cpp b/dom/media/ADTSDemuxer.cpp index 29ea270461..49135efc52 100644 --- a/dom/media/ADTSDemuxer.cpp +++ b/dom/media/ADTSDemuxer.cpp @@ -10,6 +10,7 @@ #include "VideoUtils.h" #include "mozilla/Logging.h" #include "mozilla/UniquePtr.h" +#include "Adts.h" #include <inttypes.h> extern mozilla::LazyLogModule gMediaDemuxerLog; @@ -21,227 +22,6 @@ extern mozilla::LazyLogModule gMediaDemuxerLog; DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__) namespace mozilla { -namespace adts { - -// adts::FrameHeader - Holds the ADTS frame header and its parsing -// state. -// -// ADTS Frame Structure -// -// 11111111 1111BCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP(QQQQQQQQ -// QQQQQQQQ) -// -// Header consists of 7 or 9 bytes(without or with CRC). -// Letter Length(bits) Description -// { sync } 12 syncword 0xFFF, all bits must be 1 -// B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 -// C 2 Layer: always 0 -// D 1 protection absent, Warning, set to 1 if there is no -// CRC and 0 if there is CRC -// E 2 profile, the MPEG-4 Audio Object Type minus 1 -// F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) -// H 3 MPEG-4 Channel Configuration (in the case of 0, the -// channel configuration is sent via an in-band PCE) -// M 13 frame length, this value must include 7 or 9 bytes of -// header length: FrameLength = -// (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) -// O 11 Buffer fullness -// P 2 Number of AAC frames(RDBs) in ADTS frame minus 1, for -// maximum compatibility always use 1 AAC frame per ADTS -// frame -// Q 16 CRC if protection absent is 0 -class FrameHeader { - public: - uint32_t mFrameLength{}; - uint32_t mSampleRate{}; - uint32_t mSamples{}; - uint32_t mChannels{}; - uint8_t mObjectType{}; - uint8_t mSamplingIndex{}; - uint8_t mChannelConfig{}; - uint8_t mNumAACFrames{}; - bool mHaveCrc{}; - - // Returns whether aPtr matches a valid ADTS header sync marker - static bool MatchesSync(const uint8_t* aPtr) { - return aPtr[0] == 0xFF && (aPtr[1] & 0xF6) == 0xF0; - } - - FrameHeader() { Reset(); } - - // Header size - uint64_t HeaderSize() const { return (mHaveCrc) ? 9 : 7; } - - bool IsValid() const { return mFrameLength > 0; } - - // Resets the state to allow for a new parsing session. - void Reset() { PodZero(this); } - - // Returns whether the byte creates a valid sequence up to this point. - bool Parse(const uint8_t* aPtr) { - const uint8_t* p = aPtr; - - if (!MatchesSync(p)) { - return false; - } - - // AAC has 1024 samples per frame per channel. - mSamples = 1024; - - mHaveCrc = !(p[1] & 0x01); - mObjectType = ((p[2] & 0xC0) >> 6) + 1; - mSamplingIndex = (p[2] & 0x3C) >> 2; - mChannelConfig = (p[2] & 0x01) << 2 | (p[3] & 0xC0) >> 6; - mFrameLength = static_cast<uint32_t>( - (p[3] & 0x03) << 11 | (p[4] & 0xFF) << 3 | (p[5] & 0xE0) >> 5); - mNumAACFrames = (p[6] & 0x03) + 1; - - static const uint32_t SAMPLE_RATES[] = {96000, 88200, 64000, 48000, 44100, - 32000, 24000, 22050, 16000, 12000, - 11025, 8000, 7350}; - if (mSamplingIndex >= ArrayLength(SAMPLE_RATES)) { - LOG(("ADTS: Init() failure: invalid sample-rate index value: %" PRIu32 - ".", - mSamplingIndex)); - return false; - } - mSampleRate = SAMPLE_RATES[mSamplingIndex]; - - MOZ_ASSERT(mChannelConfig < 8); - mChannels = (mChannelConfig == 7) ? 8 : mChannelConfig; - - return true; - } -}; - -// adts::Frame - Frame meta container used to parse and hold a frame -// header and side info. -class Frame { - public: - Frame() : mOffset(0) {} - - uint64_t Offset() const { return mOffset; } - size_t Length() const { - // TODO: If fields are zero'd when invalid, this check wouldn't be - // necessary. - if (!mHeader.IsValid()) { - return 0; - } - - return mHeader.mFrameLength; - } - - // Returns the offset to the start of frame's raw data. - uint64_t PayloadOffset() const { return mOffset + mHeader.HeaderSize(); } - - // Returns the length of the frame's raw data (excluding the header) in bytes. - size_t PayloadLength() const { - // TODO: If fields are zero'd when invalid, this check wouldn't be - // necessary. - if (!mHeader.IsValid()) { - return 0; - } - - return mHeader.mFrameLength - mHeader.HeaderSize(); - } - - // Returns the parsed frame header. - const FrameHeader& Header() const { return mHeader; } - - bool IsValid() const { return mHeader.IsValid(); } - - // Resets the frame header and data. - void Reset() { - mHeader.Reset(); - mOffset = 0; - } - - // Returns whether the valid - bool Parse(uint64_t aOffset, const uint8_t* aStart, const uint8_t* aEnd) { - MOZ_ASSERT(aStart && aEnd); - - bool found = false; - const uint8_t* ptr = aStart; - // Require at least 7 bytes of data at the end of the buffer for the minimum - // ADTS frame header. - while (ptr < aEnd - 7 && !found) { - found = mHeader.Parse(ptr); - ptr++; - } - - mOffset = aOffset + (static_cast<size_t>(ptr - aStart)) - 1u; - - return found; - } - - private: - // The offset to the start of the header. - uint64_t mOffset; - - // The currently parsed frame header. - FrameHeader mHeader; -}; - -class FrameParser { - public: - // Returns the currently parsed frame. Reset via Reset or EndFrameSession. - const Frame& CurrentFrame() const { return mFrame; } - - // Returns the first parsed frame. Reset via Reset. - const Frame& FirstFrame() const { return mFirstFrame; } - - // Resets the parser. Don't use between frames as first frame data is reset. - void Reset() { - EndFrameSession(); - mFirstFrame.Reset(); - } - - // Clear the last parsed frame to allow for next frame parsing, i.e.: - // - sets PrevFrame to CurrentFrame - // - resets the CurrentFrame - // - resets ID3Header if no valid header was parsed yet - void EndFrameSession() { mFrame.Reset(); } - - // Parses contents of given ByteReader for a valid frame header and returns - // true if one was found. After returning, the variable passed to - // 'aBytesToSkip' holds the amount of bytes to be skipped (if any) in order to - // jump across a large ID3v2 tag spanning multiple buffers. - bool Parse(uint64_t aOffset, const uint8_t* aStart, const uint8_t* aEnd) { - const bool found = mFrame.Parse(aOffset, aStart, aEnd); - - if (mFrame.Length() && !mFirstFrame.Length()) { - mFirstFrame = mFrame; - } - - return found; - } - - private: - // We keep the first parsed frame around for static info access, the - // previously parsed frame for debugging and the currently parsed frame. - Frame mFirstFrame; - Frame mFrame; -}; - -// Initialize the AAC AudioSpecificConfig. -// Only handles two-byte version for AAC-LC. -static void InitAudioSpecificConfig(const Frame& frame, - MediaByteBuffer* aBuffer) { - const FrameHeader& header = frame.Header(); - MOZ_ASSERT(header.IsValid()); - - int audioObjectType = header.mObjectType; - int samplingFrequencyIndex = header.mSamplingIndex; - int channelConfig = header.mChannelConfig; - - uint8_t asc[2]; - asc[0] = (audioObjectType & 0x1F) << 3 | (samplingFrequencyIndex & 0x0E) >> 1; - asc[1] = (samplingFrequencyIndex & 0x01) << 7 | (channelConfig & 0x0F) << 3; - - aBuffer->AppendElements(asc, 2); -} - -} // namespace adts using media::TimeUnit; @@ -292,7 +72,7 @@ bool ADTSDemuxer::IsSeekable() const { // ADTSTrackDemuxer ADTSTrackDemuxer::ADTSTrackDemuxer(MediaResource* aSource) : mSource(aSource), - mParser(new adts::FrameParser()), + mParser(new ADTS::FrameParser()), mOffset(0), mNumParsedFrames(0), mFrameIndex(0), @@ -535,7 +315,7 @@ TimeUnit ADTSTrackDemuxer::Duration(int64_t aNumFrames) const { return TimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond); } -const adts::Frame& ADTSTrackDemuxer::FindNextFrame( +const ADTS::Frame& ADTSTrackDemuxer::FindNextFrame( bool findFirstFrame /*= false*/) { static const int BUFFER_SIZE = 4096; static const int MAX_SKIPPED_BYTES = 10 * BUFFER_SIZE; @@ -568,7 +348,7 @@ const adts::Frame& ADTSTrackDemuxer::FindNextFrame( break; } - const adts::Frame& currentFrame = mParser->CurrentFrame(); + const ADTS::Frame& currentFrame = mParser->CurrentFrame(); foundFrame = mParser->Parse(frameHeaderOffset, buffer, buffer + read); if (findFirstFrame && foundFrame) { // Check for sync marker after the found frame, since it's @@ -579,7 +359,7 @@ const adts::Frame& ADTSTrackDemuxer::FindNextFrame( currentFrame.Offset() + currentFrame.Length(); uint32_t read = Read(buffer, AssertedCast<int64_t>(nextFrameHeaderOffset), 2); - if (read != 2 || !adts::FrameHeader::MatchesSync(buffer)) { + if (read != 2 || !ADTS::FrameHeader::MatchesSync(buffer)) { frameHeaderOffset = currentFrame.Offset() + 1; mParser->Reset(); foundFrame = false; @@ -621,7 +401,7 @@ const adts::Frame& ADTSTrackDemuxer::FindNextFrame( return mParser->CurrentFrame(); } -bool ADTSTrackDemuxer::SkipNextFrame(const adts::Frame& aFrame) { +bool ADTSTrackDemuxer::SkipNextFrame(const ADTS::Frame& aFrame) { if (!mNumParsedFrames || !aFrame.Length()) { RefPtr<MediaRawData> frame(GetNextFrame(aFrame)); return frame; @@ -639,7 +419,7 @@ bool ADTSTrackDemuxer::SkipNextFrame(const adts::Frame& aFrame) { } already_AddRefed<MediaRawData> ADTSTrackDemuxer::GetNextFrame( - const adts::Frame& aFrame) { + const ADTS::Frame& aFrame) { ADTSLOG("GetNext() Begin({mOffset=%" PRIu64 " HeaderSize()=%" PRIu64 " Length()=%zu})", aFrame.Offset(), aFrame.Header().HeaderSize(), @@ -735,7 +515,7 @@ int64_t ADTSTrackDemuxer::FrameIndexFromTime(const TimeUnit& aTime) const { return std::max<int64_t>(0, frameIndex); } -void ADTSTrackDemuxer::UpdateState(const adts::Frame& aFrame) { +void ADTSTrackDemuxer::UpdateState(const ADTS::Frame& aFrame) { uint32_t frameLength = aFrame.Length(); // Prevent overflow. if (mTotalFrameLen + frameLength < mTotalFrameLen) { @@ -750,7 +530,7 @@ void ADTSTrackDemuxer::UpdateState(const adts::Frame& aFrame) { mTotalFrameLen += frameLength; if (!mSamplesPerFrame) { - const adts::FrameHeader& header = aFrame.Header(); + const ADTS::FrameHeader& header = aFrame.Header(); mSamplesPerFrame = header.mSamples; mSamplesPerSecond = header.mSampleRate; mChannels = header.mChannels; @@ -795,15 +575,15 @@ bool ADTSDemuxer::ADTSSniffer(const uint8_t* aData, const uint32_t aLength) { if (aLength < 7) { return false; } - if (!adts::FrameHeader::MatchesSync(aData)) { + if (!ADTS::FrameHeader::MatchesSync(Span(aData, aLength))) { return false; } - auto parser = MakeUnique<adts::FrameParser>(); + auto parser = MakeUnique<ADTS::FrameParser>(); if (!parser->Parse(0, aData, aData + aLength)) { return false; } - const adts::Frame& currentFrame = parser->CurrentFrame(); + const ADTS::Frame& currentFrame = parser->CurrentFrame(); // Check for sync marker after the found frame, since it's // possible to find sync marker in AAC data. If sync marker // exists after the current frame then we've found a frame @@ -812,7 +592,8 @@ bool ADTSDemuxer::ADTSSniffer(const uint8_t* aData, const uint32_t aLength) { currentFrame.Offset() + currentFrame.Length(); return aLength > nextFrameHeaderOffset && aLength - nextFrameHeaderOffset >= 2 && - adts::FrameHeader::MatchesSync(aData + nextFrameHeaderOffset); + ADTS::FrameHeader::MatchesSync(Span(aData + nextFrameHeaderOffset, + aLength - nextFrameHeaderOffset)); } } // namespace mozilla diff --git a/dom/media/ADTSDemuxer.h b/dom/media/ADTSDemuxer.h index 40ff44898e..67a661883f 100644 --- a/dom/media/ADTSDemuxer.h +++ b/dom/media/ADTSDemuxer.h @@ -11,14 +11,10 @@ #include "mozilla/Maybe.h" #include "MediaDataDemuxer.h" #include "MediaResource.h" +#include "Adts.h" namespace mozilla { -namespace adts { -class Frame; -class FrameParser; -} // namespace adts - class ADTSTrackDemuxer; DDLoggedTypeDeclNameAndBase(ADTSDemuxer, MediaDataDemuxer); @@ -87,16 +83,16 @@ class ADTSTrackDemuxer : public MediaTrackDemuxer, media::TimeUnit ScanUntil(const media::TimeUnit& aTime); // Finds the next valid frame and returns its byte range. - const adts::Frame& FindNextFrame(bool findFirstFrame = false); + const ADTS::Frame& FindNextFrame(bool findFirstFrame = false); // Skips the next frame given the provided byte range. - bool SkipNextFrame(const adts::Frame& aFrame); + bool SkipNextFrame(const ADTS::Frame& aFrame); // Returns the next ADTS frame, if available. - already_AddRefed<MediaRawData> GetNextFrame(const adts::Frame& aFrame); + already_AddRefed<MediaRawData> GetNextFrame(const ADTS::Frame& aFrame); // Updates post-read meta data. - void UpdateState(const adts::Frame& aFrame); + void UpdateState(const ADTS::Frame& aFrame); // Returns the frame index for the given offset. int64_t FrameIndexFromOffset(uint64_t aOffset) const; @@ -115,7 +111,7 @@ class ADTSTrackDemuxer : public MediaTrackDemuxer, MediaResourceIndex mSource; // ADTS frame parser used to detect frames and extract side info. - adts::FrameParser* mParser; + ADTS::FrameParser* mParser; // Current byte offset in the source stream. uint64_t mOffset; diff --git a/dom/media/AudibilityMonitor.h b/dom/media/AudibilityMonitor.h index fdcf474403..25a715cebf 100644 --- a/dom/media/AudibilityMonitor.h +++ b/dom/media/AudibilityMonitor.h @@ -67,7 +67,7 @@ class AudibilityMonitor { for (uint32_t i = 0; i < frameCount; i++) { bool atLeastOneAudible = false; for (uint32_t j = 0; j < aChannels; j++) { - if (std::fabs(AudioSampleToFloat(samples[readIndex++])) > + if (std::fabs(ConvertAudioSample<float>(samples[readIndex++])) > AUDIBILITY_THRESHOLD) { atLeastOneAudible = true; } diff --git a/dom/media/AudioSampleFormat.h b/dom/media/AudioSampleFormat.h index 1cec31a385..7a329d06df 100644 --- a/dom/media/AudioSampleFormat.h +++ b/dom/media/AudioSampleFormat.h @@ -1,13 +1,16 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* vim: set ts=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/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #ifndef MOZILLA_AUDIOSAMPLEFORMAT_H_ #define MOZILLA_AUDIOSAMPLEFORMAT_H_ #include "mozilla/Assertions.h" +#include "mozilla/PodOperations.h" #include <algorithm> +#include <type_traits> +#include <limits> namespace mozilla { @@ -62,113 +65,191 @@ class AudioSampleTypeToFormat<short> { static const AudioSampleFormat Format = AUDIO_FORMAT_S16; }; -// Single-sample conversion -/* - * Use "2^N" conversion since it's simple, fast, "bit transparent", used by - * many other libraries and apparently behaves reasonably. - * http://blog.bjornroche.com/2009/12/int-float-int-its-jungle-out-there.html - * http://blog.bjornroche.com/2009/12/linearity-and-dynamic-range-in-int.html - */ -inline float AudioSampleToFloat(float aValue) { return aValue; } -inline float AudioSampleToFloat(int16_t aValue) { - return static_cast<float>(aValue) / 32768.0f; -} -inline float AudioSampleToFloat(int32_t aValue) { - return static_cast<float>(aValue) / (float)(1U << 31); +template <typename T> +constexpr float MaxAsFloat() { + return static_cast<float>(std::numeric_limits<T>::max()); } template <typename T> -T FloatToAudioSample(float aValue); - -template <> -inline float FloatToAudioSample<float>(float aValue) { - return aValue; -} -template <> -inline int16_t FloatToAudioSample<int16_t>(float aValue) { - float v = aValue * 32768.0f; - float clamped = std::max(-32768.0f, std::min(32767.0f, v)); - return int16_t(clamped); +constexpr float LowestAsFloat() { + return static_cast<float>(std::numeric_limits<T>::lowest()); } +// The maximum value for an audio sample. If T is signed, the absolute value of +// this number is smaller (by exactly 1) than ::Min(). template <typename T> -T UInt8bitToAudioSample(uint8_t aValue); - -template <> -inline float UInt8bitToAudioSample<float>(uint8_t aValue) { - return static_cast<float>(aValue) * (static_cast<float>(2) / UINT8_MAX) - - static_cast<float>(1); -} -template <> -inline int16_t UInt8bitToAudioSample<int16_t>(uint8_t aValue) { - return static_cast<int16_t>((aValue << 8) + aValue + INT16_MIN); +constexpr T Max() { + return std::numeric_limits<T>::max(); } +// The minimum value for an audio sample. If T is signed, the absolute value of +// this number is greater (by exactly 1) than ::Max() template <typename T> -T IntegerToAudioSample(int16_t aValue); +constexpr T Min() { + return std::numeric_limits<T>::lowest(); +} template <> -inline float IntegerToAudioSample<float>(int16_t aValue) { - return static_cast<float>(aValue) / 32768.0f; +constexpr float Max<float>() { + return 1.0f; } + template <> -inline int16_t IntegerToAudioSample<int16_t>(int16_t aValue) { - return aValue; +constexpr float Min<float>() { + return -1.0f; } +// The bias value is the middle of the range. In linear PCM audio, if the +// values are all equal to the bias value, the audio is silent. template <typename T> -T Int24bitToAudioSample(int32_t aValue); +constexpr T Bias() { + return 0; +} template <> -inline float Int24bitToAudioSample<float>(int32_t aValue) { - return static_cast<float>(aValue) / static_cast<float>(1 << 23); +constexpr uint8_t Bias<uint8_t>() { + return 128; } -template <> -inline int16_t Int24bitToAudioSample<int16_t>(int32_t aValue) { - return static_cast<int16_t>(aValue / 256); + +// Clip a floating point audio sample to its nominal range. This is +// destructive, and is only used here for avoiding overflow in some edge cases, +// so it's not going to be generally audible. +inline float Clip(float aValue) { return std::clamp(aValue, -1.0f, 1.0f); } + +template <typename T> +T FloatToAudioSample(float aValue) { + if constexpr (std::is_same_v<float, T>) { + return aValue; + } + if constexpr (std::is_same_v<uint8_t, T>) { + return static_cast<T>(std::clamp((aValue + 1.0f) * 128.f, + LowestAsFloat<T>(), MaxAsFloat<T>())); + } else if constexpr (std::is_same_v<int16_t, T>) { + // This produces correct results accross the range. + return static_cast<T>(std::clamp(aValue * -LowestAsFloat<T>(), + LowestAsFloat<T>(), MaxAsFloat<T>())); + } else if constexpr (std::is_same_v<int32_t, T>) { + // We need to handle this differently because of rounding between INT32_MAX + // and float 32-bits, to maximise precision. + if (aValue >= 0.) { + // if the input sample is greater OR EQUAL to 1.0, then clip and return + // the max value. + if (aValue >= 1.0) { + return std::numeric_limits<T>::max(); + } + // otherwise cast to a double and map to the positive range. + // float 32-bits cannot represent int32_max (but can represent int32_min) + constexpr double magnitudePos = std::numeric_limits<int32_t>::max(); + return static_cast<int32_t>(aValue * magnitudePos); + } + // Similarly for the negative range. + if (aValue <= -1.0) { + return std::numeric_limits<T>::lowest(); + } + constexpr double magnitudeNegative = + -1.0 * std::numeric_limits<int32_t>::lowest(); + return static_cast<int32_t>(aValue * magnitudeNegative); + } } -template <typename SrcT, typename DstT> -inline void ConvertAudioSample(SrcT aIn, DstT& aOut); +template <typename T> +T UInt8bitToAudioSample(uint8_t aValue) { + if constexpr (std::is_same_v<uint8_t, T>) { + return aValue; + } else if constexpr (std::is_same_v<int16_t, T>) { + return (static_cast<int16_t>(aValue) << 8) - (1 << 15); + } else if constexpr (std::is_same_v<int32_t, T>) { + return (static_cast<int32_t>(aValue) << 24) - (1 << 31); + } else if constexpr (std::is_same_v<float, T>) { + float biased = static_cast<float>(aValue) - Bias<uint8_t>(); + if (aValue >= Bias<uint8_t>()) { + return Clip(biased / MaxAsFloat<int8_t>()); + } + return Clip(biased / -LowestAsFloat<int8_t>()); + } +} -template <> -inline void ConvertAudioSample(int16_t aIn, int16_t& aOut) { - aOut = aIn; +template <typename T> +T Int16ToAudioSample(int16_t aValue) { + if constexpr (std::is_same_v<uint8_t, T>) { + return static_cast<uint8_t>(aValue >> 8) + 128; + } else if constexpr (std::is_same_v<int16_t, T>) { + return aValue; + } else if constexpr (std::is_same_v<int32_t, T>) { + return aValue << 16; + } else if constexpr (std::is_same_v<float, T>) { + if (aValue >= 0) { + return Clip(static_cast<float>(aValue) / MaxAsFloat<int16_t>()); + } + return Clip(static_cast<float>(aValue) / -LowestAsFloat<int16_t>()); + } } -template <> -inline void ConvertAudioSample(int16_t aIn, float& aOut) { - aOut = AudioSampleToFloat(aIn); +// 24-bits audio samples are stored in 32-bits variables. +template <typename T> +T Int24ToAudioSample(int32_t aValue) { + if constexpr (std::is_same_v<uint8_t, T>) { + return static_cast<uint8_t>(aValue >> 16) + 128; + } else if constexpr (std::is_same_v<int16_t, T>) { + return static_cast<int16_t>(aValue >> 8); + } else if constexpr (std::is_same_v<int32_t, T>) { + return aValue << 8; + } else if constexpr (std::is_same_v<float, T>) { + const int32_t min = -(2 << 22); + const int32_t max = (2 << 22) - 1; + if (aValue >= 0) { + return Clip(static_cast<float>(aValue) / static_cast<float>(max)); + } + return Clip(static_cast<float>(aValue) / -static_cast<float>(min)); + } } -template <> -inline void ConvertAudioSample(float aIn, float& aOut) { - aOut = aIn; +template <typename T> +T Int32ToAudioSample(int32_t aValue) { + if constexpr (std::is_same_v<uint8_t, T>) { + return static_cast<uint8_t>(aValue >> 24) + 128; + } else if constexpr (std::is_same_v<int16_t, T>) { + return aValue >> 16; + } else if constexpr (std::is_same_v<int32_t, T>) { + return aValue; + } else if constexpr (std::is_same_v<float, T>) { + if (aValue >= 0) { + return Clip(static_cast<float>(aValue) / MaxAsFloat<int32_t>()); + } + return Clip(static_cast<float>(aValue) / -LowestAsFloat<int32_t>()); + } } -template <> -inline void ConvertAudioSample(float aIn, int16_t& aOut) { - aOut = FloatToAudioSample<int16_t>(aIn); +// This does not handle 24-bits audio, call the function explicitly when +// needed. +template <typename D, typename S> +inline D ConvertAudioSample(const S& aSource) { + if constexpr (std::is_same_v<S, D>) { + return aSource; + } else if constexpr (std::is_same_v<S, uint8_t>) { + return UInt8bitToAudioSample<D>(aSource); + } else if constexpr (std::is_same_v<S, int16_t>) { + return Int16ToAudioSample<D>(aSource); + } else if constexpr (std::is_same_v<S, int32_t>) { + return Int32ToAudioSample<D>(aSource); + } else if constexpr (std::is_same_v<S, float>) { + return FloatToAudioSample<D>(aSource); + } } // Sample buffer conversion - template <typename From, typename To> inline void ConvertAudioSamples(const From* aFrom, To* aTo, int aCount) { + if constexpr (std::is_same_v<From, To>) { + PodCopy(aTo, aFrom, aCount); + return; + } for (int i = 0; i < aCount; ++i) { - aTo[i] = FloatToAudioSample<To>(AudioSampleToFloat(aFrom[i])); + aTo[i] = ConvertAudioSample<To>(aFrom[i]); } } -inline void ConvertAudioSamples(const int16_t* aFrom, int16_t* aTo, - int aCount) { - memcpy(aTo, aFrom, sizeof(*aTo) * aCount); -} -inline void ConvertAudioSamples(const float* aFrom, float* aTo, int aCount) { - memcpy(aTo, aFrom, sizeof(*aTo) * aCount); -} // Sample buffer conversion with scale - template <typename From, typename To> inline void ConvertAudioSamplesWithScale(const From* aFrom, To* aTo, int aCount, float aScale) { @@ -177,7 +258,8 @@ inline void ConvertAudioSamplesWithScale(const From* aFrom, To* aTo, int aCount, return; } for (int i = 0; i < aCount; ++i) { - aTo[i] = FloatToAudioSample<To>(AudioSampleToFloat(aFrom[i]) * aScale); + aTo[i] = + ConvertAudioSample<To>(ConvertAudioSample<float>(aFrom[i]) * aScale); } } inline void ConvertAudioSamplesWithScale(const int16_t* aFrom, int16_t* aTo, @@ -194,7 +276,8 @@ inline void ConvertAudioSamplesWithScale(const int16_t* aFrom, int16_t* aTo, return; } for (int i = 0; i < aCount; ++i) { - aTo[i] = FloatToAudioSample<int16_t>(AudioSampleToFloat(aFrom[i]) * aScale); + aTo[i] = FloatToAudioSample<int16_t>(ConvertAudioSample<float>(aFrom[i]) * + aScale); } } @@ -202,8 +285,9 @@ template <typename From, typename To> inline void AddAudioSamplesWithScale(const From* aFrom, To* aTo, int aCount, float aScale) { for (int i = 0; i < aCount; ++i) { - aTo[i] = FloatToAudioSample<To>(AudioSampleToFloat(aTo[i]) + - AudioSampleToFloat(aFrom[i]) * aScale); + aTo[i] = + ConvertAudioSample<To>(ConvertAudioSample<float>(aTo[i]) + + ConvertAudioSample<float>(aFrom[i]) * aScale); } } diff --git a/dom/media/AudioSegment.h b/dom/media/AudioSegment.h index 006f996c39..7d60bf7ca5 100644 --- a/dom/media/AudioSegment.h +++ b/dom/media/AudioSegment.h @@ -79,7 +79,8 @@ static void InterleaveAndConvertBuffer(const SrcT* const* aSourceChannels, DestT* output = aOutput; for (size_t i = 0; i < aLength; ++i) { for (size_t channel = 0; channel < aChannels; ++channel) { - float v = AudioSampleToFloat(aSourceChannels[channel][i]) * aVolume; + float v = + ConvertAudioSample<float>(aSourceChannels[channel][i]) * aVolume; *output = FloatToAudioSample<DestT>(v); ++output; } @@ -93,7 +94,8 @@ static void DeinterleaveAndConvertBuffer(const SrcT* aSourceBuffer, for (size_t i = 0; i < aChannels; i++) { size_t interleavedIndex = i; for (size_t j = 0; j < aFrames; j++) { - ConvertAudioSample(aSourceBuffer[interleavedIndex], aOutput[i][j]); + aOutput[i][j] = + ConvertAudioSample<DestT>(aSourceBuffer[interleavedIndex]); interleavedIndex += aChannels; } } @@ -148,7 +150,7 @@ void DownmixAndInterleave(Span<const SrcT* const> aChannelData, * separate pointers to each channel's buffer. */ struct AudioChunk { - typedef mozilla::AudioSampleFormat SampleFormat; + using SampleFormat = mozilla::AudioSampleFormat; AudioChunk() = default; @@ -318,7 +320,7 @@ struct AudioChunk { * A list of audio samples consisting of a sequence of slices of SharedBuffers. * The audio rate is determined by the track, not stored in this class. */ -class AudioSegment : public MediaSegmentBase<AudioSegment, AudioChunk> { +class AudioSegment final : public MediaSegmentBase<AudioSegment, AudioChunk> { // The channel count that MaxChannelCount() returned last time it was called. uint32_t mMemoizedMaxChannelCount = 0; diff --git a/dom/media/CallbackThreadRegistry.cpp b/dom/media/CallbackThreadRegistry.cpp index f4d2af5bd1..84ef7b7cb4 100644 --- a/dom/media/CallbackThreadRegistry.cpp +++ b/dom/media/CallbackThreadRegistry.cpp @@ -6,6 +6,7 @@ #include "CallbackThreadRegistry.h" #include "mozilla/ClearOnShutdown.h" +#include "nsThreadUtils.h" namespace mozilla { struct CallbackThreadRegistrySingleton { diff --git a/dom/media/ChannelMediaDecoder.cpp b/dom/media/ChannelMediaDecoder.cpp index c6da221f94..12f6c11e47 100644 --- a/dom/media/ChannelMediaDecoder.cpp +++ b/dom/media/ChannelMediaDecoder.cpp @@ -221,9 +221,11 @@ MediaDecoderStateMachineBase* ChannelMediaDecoder::CreateStateMachine( mReader = DecoderTraits::CreateReader(ContainerType(), init); #ifdef MOZ_WMF_MEDIA_ENGINE - // TODO : Only for testing development for now. In the future this should be - // used for encrypted content only. - if (StaticPrefs::media_wmf_media_engine_enabled() && + // This state machine is mainly used for the encrypted playback. However, for + // testing purpose we would also use it the non-encrypted playback. + // 1=enabled encrypted and clear, 3=enabled clear + if ((StaticPrefs::media_wmf_media_engine_enabled() == 1 || + StaticPrefs::media_wmf_media_engine_enabled() == 3) && StaticPrefs::media_wmf_media_engine_channel_decoder_enabled() && !aDisableExternalEngine) { return new ExternalEngineStateMachine(this, mReader); diff --git a/dom/media/ExternalEngineStateMachine.cpp b/dom/media/ExternalEngineStateMachine.cpp index 68fb053b83..acfc1f5fa2 100644 --- a/dom/media/ExternalEngineStateMachine.cpp +++ b/dom/media/ExternalEngineStateMachine.cpp @@ -10,6 +10,7 @@ # include "mozilla/MFMediaEngineChild.h" # include "mozilla/StaticPrefs_media.h" #endif +#include "mozilla/AppShutdown.h" #include "mozilla/Atomics.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/ProfilerLabels.h" @@ -203,6 +204,10 @@ ExternalEngineStateMachine::ExternalEngineStateMachine( InitEngine(); } +ExternalEngineStateMachine::~ExternalEngineStateMachine() { + LOG("ExternalEngineStateMachine is destroyed"); +} + void ExternalEngineStateMachine::InitEngine() { MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine()); #ifdef MOZ_WMF_MEDIA_ENGINE @@ -565,6 +570,9 @@ RefPtr<ShutdownPromise> ExternalEngineStateMachine::Shutdown() { mSetCDMProxyPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__); mSetCDMProxyRequest.DisconnectIfExists(); + mInitEngineForCDMRequest.DisconnectIfExists(); + + mPendingTasks.Clear(); mEngine->Shutdown(); @@ -607,49 +615,59 @@ void ExternalEngineStateMachine::BufferedRangeUpdated() { } } -// Note: the variadic only supports passing member variables. -#define PERFORM_WHEN_ALLOW(Func, ...) \ - do { \ - /* Initialzation is not done yet, postpone the operation */ \ - if ((mState.IsInitEngine() || mState.IsRecoverEngine()) && \ - mState.AsInitEngine()->mInitPromise) { \ - LOG("%s is called before init", __func__); \ - mState.AsInitEngine()->mInitPromise->Then( \ - OwnerThread(), __func__, \ - [self = RefPtr{this}, this]( \ - const GenericNonExclusivePromise::ResolveOrRejectValue& aVal) { \ - if (aVal.IsResolve()) { \ - Func(__VA_ARGS__); \ - } \ - }); \ - return; \ - } else if (mState.IsShutdownEngine()) { \ - return; \ - } \ +#define PERFORM_WHEN_ALLOW(Func) \ + do { \ + if (mState.IsShutdownEngine() || mHasFatalError || \ + AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { \ + return; \ + } \ + /* Initialzation is not done yet, postpone the operation */ \ + if ((mState.IsInitEngine() || mState.IsRecoverEngine()) && \ + mState.AsInitEngine()->mInitPromise) { \ + LOG("%s is called before init", __func__); \ + mPendingTasks.AppendElement(NewRunnableMethod( \ + __func__, this, &ExternalEngineStateMachine::Func)); \ + return; \ + } \ } while (false) void ExternalEngineStateMachine::SetPlaybackRate(double aPlaybackRate) { AssertOnTaskQueue(); + // TODO : consider to make `mPlaybackRate` a mirror to fit other usages like + // `mVolume` and `mPreservesPitch`. mPlaybackRate = aPlaybackRate; - PERFORM_WHEN_ALLOW(SetPlaybackRate, mPlaybackRate); - mEngine->SetPlaybackRate(aPlaybackRate); + PlaybackRateChanged(); +} + +void ExternalEngineStateMachine::PlaybackRateChanged() { + AssertOnTaskQueue(); + PERFORM_WHEN_ALLOW(PlaybackRateChanged); + MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() || + mState.IsSeekingData()); + mEngine->SetPlaybackRate(mPlaybackRate); } void ExternalEngineStateMachine::VolumeChanged() { AssertOnTaskQueue(); PERFORM_WHEN_ALLOW(VolumeChanged); + MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() || + mState.IsSeekingData()); mEngine->SetVolume(mVolume); } void ExternalEngineStateMachine::PreservesPitchChanged() { AssertOnTaskQueue(); PERFORM_WHEN_ALLOW(PreservesPitchChanged); + MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() || + mState.IsSeekingData()); mEngine->SetPreservesPitch(mPreservesPitch); } void ExternalEngineStateMachine::PlayStateChanged() { AssertOnTaskQueue(); PERFORM_WHEN_ALLOW(PlayStateChanged); + MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() || + mState.IsSeekingData()); if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING) { mEngine->Play(); } else if (mPlayState == MediaDecoder::PLAY_STATE_PAUSED) { @@ -660,6 +678,8 @@ void ExternalEngineStateMachine::PlayStateChanged() { void ExternalEngineStateMachine::LoopingChanged() { AssertOnTaskQueue(); PERFORM_WHEN_ALLOW(LoopingChanged); + MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() || + mState.IsSeekingData()); mEngine->SetLooping(mLooping); } @@ -775,6 +795,13 @@ void ExternalEngineStateMachine::StartRunningEngine() { if (HasVideo()) { RunningEngineUpdate(MediaData::Type::VIDEO_DATA); } + // Run tasks which was called before the engine is ready. + if (!mPendingTasks.IsEmpty()) { + for (auto& task : mPendingTasks) { + Unused << OwnerThread()->Dispatch(task.forget()); + } + mPendingTasks.Clear(); + } } void ExternalEngineStateMachine::RunningEngineUpdate(MediaData::Type aType) { @@ -1141,6 +1168,14 @@ void ExternalEngineStateMachine::RecoverFromCDMProcessCrashIfNeeded() { return; } + if (mState.IsInitEngine()) { + LOG("Failed on the engine initialization, the media engine playback might " + "not be supported"); + DecodeError( + MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR)); + return; + } + LOG("CDM process crashed, recover the engine again (last time=%" PRId64 ")", mCurrentPosition.Ref().ToMicroseconds()); ChangeStateTo(State::RecoverEngine); @@ -1180,24 +1215,28 @@ RefPtr<SetCDMPromise> ExternalEngineStateMachine::SetCDMProxy( if (mState.IsInitEngine() && mState.AsInitEngine()->mInitPromise) { LOG("SetCDMProxy is called before init"); - mState.AsInitEngine()->mInitPromise->Then( - OwnerThread(), __func__, - [self = RefPtr{this}, proxy = RefPtr{aProxy}, - this](const GenericNonExclusivePromise::ResolveOrRejectValue& aVal) { - SetCDMProxy(proxy) - ->Then(OwnerThread(), __func__, - [self = RefPtr{this}, - this](const SetCDMPromise::ResolveOrRejectValue& aVal) { - mSetCDMProxyRequest.Complete(); - if (aVal.IsResolve()) { - mSetCDMProxyPromise.Resolve(true, __func__); - } else { - mSetCDMProxyPromise.Reject(NS_ERROR_DOM_MEDIA_CDM_ERR, - __func__); - } - }) - ->Track(mSetCDMProxyRequest); - }); + mState.AsInitEngine() + ->mInitPromise + ->Then( + OwnerThread(), __func__, + [self = RefPtr{this}, proxy = RefPtr{aProxy}, this]( + const GenericNonExclusivePromise::ResolveOrRejectValue& aVal) { + mInitEngineForCDMRequest.Complete(); + SetCDMProxy(proxy) + ->Then(OwnerThread(), __func__, + [self = RefPtr{this}, this]( + const SetCDMPromise::ResolveOrRejectValue& aVal) { + mSetCDMProxyRequest.Complete(); + if (aVal.IsResolve()) { + mSetCDMProxyPromise.Resolve(true, __func__); + } else { + mSetCDMProxyPromise.Reject( + NS_ERROR_DOM_MEDIA_CDM_ERR, __func__); + } + }) + ->Track(mSetCDMProxyRequest); + }) + ->Track(mInitEngineForCDMRequest); return mSetCDMProxyPromise.Ensure(__func__); } @@ -1232,6 +1271,7 @@ bool ExternalEngineStateMachine::IsCDMProxySupported(CDMProxy* aProxy) { void ExternalEngineStateMachine::ReportTelemetry(const MediaResult& aError) { glean::mfcdm::ErrorExtra extraData; extraData.errorName = Some(aError.ErrorName()); + extraData.currentState = Some(nsAutoCString{StateToStr(mState.mName)}); nsAutoCString resolution; if (mInfo) { if (mInfo->HasAudio()) { @@ -1268,6 +1308,14 @@ void ExternalEngineStateMachine::ReportTelemetry(const MediaResult& aError) { } } +void ExternalEngineStateMachine::DecodeError(const MediaResult& aError) { + if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA || + aError != NS_ERROR_DOM_MEDIA_CANCELED) { + mHasFatalError = true; + } + MediaDecoderStateMachineBase ::DecodeError(aError); +} + #undef FMT #undef LOG #undef LOGV diff --git a/dom/media/ExternalEngineStateMachine.h b/dom/media/ExternalEngineStateMachine.h index 84dedbe717..79183f894d 100644 --- a/dom/media/ExternalEngineStateMachine.h +++ b/dom/media/ExternalEngineStateMachine.h @@ -102,8 +102,10 @@ class ExternalEngineStateMachine final bool IsCDMProxySupported(CDMProxy* aProxy) override; + bool IsExternalEngineStateMachine() const override { return true; } + private: - ~ExternalEngineStateMachine() = default; + ~ExternalEngineStateMachine(); void AssertOnTaskQueue() const { MOZ_ASSERT(OnTaskQueue()); } @@ -233,6 +235,7 @@ class ExternalEngineStateMachine final void PreservesPitchChanged() override; void PlayStateChanged() override; void LoopingChanged() override; + void PlaybackRateChanged(); // Not supported. void SetIsLiveStream(bool aIsLiveStream) override {} @@ -293,6 +296,8 @@ class ExternalEngineStateMachine final void ReportTelemetry(const MediaResult& aError); + void DecodeError(const MediaResult& aError) override; + UniquePtr<ExternalPlaybackEngine> mEngine; bool mHasEnoughAudio = false; @@ -303,12 +308,20 @@ class ExternalEngineStateMachine final // Only used if setting CDM happens before the engine finishes initialization. MozPromiseHolder<SetCDMPromise> mSetCDMProxyPromise; MozPromiseRequestHolder<SetCDMPromise> mSetCDMProxyRequest; + MozPromiseRequestHolder<GenericNonExclusivePromise> mInitEngineForCDMRequest; // It would be zero for audio-only playback. gfx::IntSize mVideoDisplay; // It would be set if playback is encrypted. nsCString mKeySystem; + + // This array stores the tasks which needs to be executed only after the + // engine is ready but is called before that. It will be executed when + // starting running the engine. + nsTArray<RefPtr<nsIRunnable>> mPendingTasks; + + bool mHasFatalError = false; }; class ExternalPlaybackEngine { diff --git a/dom/media/IdpSandbox.sys.mjs b/dom/media/IdpSandbox.sys.mjs index 8b94abf7dd..c8735c223d 100644 --- a/dom/media/IdpSandbox.sys.mjs +++ b/dom/media/IdpSandbox.sys.mjs @@ -56,7 +56,7 @@ ResourceLoader.prototype = { this.data += stream.read(count); }, - onStartRequest(request) {}, + onStartRequest() {}, onStopRequest(request, status) { if (Components.isSuccessCode(status)) { diff --git a/dom/media/ImageToI420.cpp b/dom/media/ImageToI420.cpp index 8fc5198b4a..0f7976cb63 100644 --- a/dom/media/ImageToI420.cpp +++ b/dom/media/ImageToI420.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/ImageUtils.h" #include "mozilla/gfx/Point.h" #include "mozilla/RefPtr.h" +#include "mozilla/Result.h" #include "nsThreadUtils.h" using mozilla::ImageFormat; @@ -75,7 +76,12 @@ nsresult ConvertToI420(Image* aImage, uint8_t* aDestY, int aDestStrideY, if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) { const ImageUtils imageUtils(aImage); - switch (imageUtils.GetFormat()) { + Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat(); + if (format.isNothing()) { + MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; + } + switch (format.value()) { case ImageBitmapFormat::YUV420P: return MapRv(libyuv::I420ToI420( data->mYChannel, data->mYStride, data->mCbChannel, diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index ee1e204815..3ae8c1dbc2 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -97,8 +97,16 @@ class AlignedBuffer { } AlignedBuffer& operator=(AlignedBuffer&& aOther) noexcept { - this->~AlignedBuffer(); - new (this) AlignedBuffer(std::move(aOther)); + if (&aOther == this) { + return *this; + } + mData = aOther.mData; + mLength = aOther.mLength; + mBuffer = std::move(aOther.mBuffer); + mCapacity = aOther.mCapacity; + aOther.mData = nullptr; + aOther.mLength = 0; + aOther.mCapacity = 0; return *this; } @@ -268,7 +276,7 @@ class InflatableShortBuffer { // capacity, and the loop goes backward. float* output = reinterpret_cast<float*>(mBuffer.mData); for (size_t i = Length(); i--;) { - output[i] = AudioSampleToFloat(mBuffer.mData[i]); + output[i] = ConvertAudioSample<float>(mBuffer.mData[i]); } AlignedFloatBuffer rv; rv.mBuffer = std::move(mBuffer.mBuffer); diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index c7fdcb6844..23c30eed2a 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -140,9 +140,7 @@ void MediaDecoder::InitStatics() { # if defined(MOZ_FFMPEG) Preferences::Lock("media.utility-ffmpeg.enabled"); # endif // defined(MOZ_FFMPEG) -# if defined(MOZ_FFVPX) Preferences::Lock("media.utility-ffvpx.enabled"); -# endif // defined(MOZ_FFVPX) # if defined(MOZ_WMF) Preferences::Lock("media.utility-wmf.enabled"); # endif // defined(MOZ_WMF) @@ -194,6 +192,12 @@ void MediaDecoder::SetOutputCaptureState(OutputCaptureState aState, MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load()."); MOZ_ASSERT_IF(aState == OutputCaptureState::Capture, aDummyTrack); + + if (mOutputCaptureState.Ref() != aState) { + LOG("Capture state change from %s to %s", + OutputCaptureStateToStr(mOutputCaptureState.Ref()), + OutputCaptureStateToStr(aState)); + } mOutputCaptureState = aState; if (mOutputDummyTrack.Ref().get() != aDummyTrack) { mOutputDummyTrack = nsMainThreadPtrHandle<SharedDummyTrack>( @@ -450,6 +454,7 @@ void MediaDecoder::OnPlaybackErrorEvent(const MediaResult& aError) { } LOG("Need to create a new %s state machine", needExternalEngine ? "external engine" : "normal"); + mStateMachineRecreated = true; nsresult rv = CreateAndInitStateMachine( false /* live stream */, @@ -611,6 +616,7 @@ nsresult MediaDecoder::CreateAndInitStateMachine(bool aIsLiveStream, NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE); GetStateMachine()->DispatchIsLiveStream(aIsLiveStream); + mMDSMCreationTime = Some(TimeStamp::Now()); nsresult rv = mDecoderStateMachine->Init(this); NS_ENSURE_SUCCESS(rv, rv); @@ -885,6 +891,14 @@ void MediaDecoder::FirstFrameLoaded( ChangeState(mNextState); } + // We only care about video first frame. + if (mInfo->HasVideo() && mMDSMCreationTime) { + mTelemetryProbesReporter->OntFirstFrameLoaded( + TimeStamp::Now() - *mMDSMCreationTime, IsMSE(), + mDecoderStateMachine->IsExternalEngineStateMachine()); + mMDSMCreationTime.reset(); + } + // GetOwner()->FirstFrameLoaded() might call us back. Put it at the bottom of // this function to avoid unexpected shutdown from reentrant calls. if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) { diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index f2f10a67c6..a5494e9a84 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -207,6 +207,20 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> { // not connected to streams created by captureStreamUntilEnded. enum class OutputCaptureState { Capture, Halt, None }; + const char* OutputCaptureStateToStr(OutputCaptureState aState) const { + switch (aState) { + case OutputCaptureState::Capture: + return "Capture"; + case OutputCaptureState::Halt: + return "Halt"; + case OutputCaptureState::None: + return "None"; + default: + MOZ_ASSERT_UNREACHABLE("Not defined state!"); + return "Not-defined"; + } + } + // Set the output capture state of this decoder. // @param aState Capture: Output is captured into output tracks, and // aDummyTrack must be provided. @@ -742,6 +756,12 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> { bool mShouldDelaySeek = false; Maybe<SeekTarget> mDelayedSeekTarget; +# ifdef MOZ_WMF_MEDIA_ENGINE + // True if we've ever recreated a new state machine due to the previous state + // didn't support the media format or key system. + bool mStateMachineRecreated = false; +# endif + public: Canonical<double>& CanonicalVolume() { return mVolume; } Canonical<bool>& CanonicalPreservesPitch() { return mPreservesPitch; } @@ -815,6 +835,11 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> { // consistent with the previous destroyed one. bool mPendingStatusUpdateForNewlyCreatedStateMachine = false; # endif + + // The time of creating the media decoder state machine, it's used to record + // the probe for measuring the first video frame loaded time. Reset after + // reporting the measurement to avoid a dulpicated report. + Maybe<TimeStamp> mMDSMCreationTime; }; } // namespace mozilla diff --git a/dom/media/MediaDecoderStateMachineBase.cpp b/dom/media/MediaDecoderStateMachineBase.cpp index e60937c31f..38d51dbd8e 100644 --- a/dom/media/MediaDecoderStateMachineBase.cpp +++ b/dom/media/MediaDecoderStateMachineBase.cpp @@ -163,7 +163,9 @@ bool MediaDecoderStateMachineBase::OnTaskQueue() const { void MediaDecoderStateMachineBase::DecodeError(const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); - LOGE("Decode error: %s", aError.Description().get()); + if (aError != NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR) { + LOGE("Decode error: %s", aError.Description().get()); + } PROFILER_MARKER_TEXT("MDSMBase::DecodeError", MEDIA_PLAYBACK, {}, aError.Description()); // Notify the decode error and MediaDecoder will shut down MDSM. diff --git a/dom/media/MediaDecoderStateMachineBase.h b/dom/media/MediaDecoderStateMachineBase.h index b4950746e8..5872151015 100644 --- a/dom/media/MediaDecoderStateMachineBase.h +++ b/dom/media/MediaDecoderStateMachineBase.h @@ -169,6 +169,8 @@ class MediaDecoderStateMachineBase { virtual bool IsCDMProxySupported(CDMProxy* aProxy) = 0; + virtual bool IsExternalEngineStateMachine() const { return false; } + protected: virtual ~MediaDecoderStateMachineBase() = default; @@ -195,7 +197,7 @@ class MediaDecoderStateMachineBase { virtual RefPtr<MediaDecoder::SeekPromise> Seek(const SeekTarget& aTarget) = 0; - void DecodeError(const MediaResult& aError); + virtual void DecodeError(const MediaResult& aError); // Functions used by assertions to ensure we're calling things // on the appropriate threads. diff --git a/dom/media/MediaDevices.cpp b/dom/media/MediaDevices.cpp index cfbc148337..b312fc1ec3 100644 --- a/dom/media/MediaDevices.cpp +++ b/dom/media/MediaDevices.cpp @@ -315,8 +315,6 @@ RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices( } haveDefaultOutput = true; break; - case MediaDeviceKind::EndGuard_: - continue; // Avoid `default:` so that `-Wswitch` catches missing // enumerators at compile time. } @@ -334,8 +332,6 @@ bool MediaDevices::CanExposeInfo(MediaDeviceKind aKind) const { case MediaDeviceKind::Audiooutput: // Assumes caller has used FilterExposedDevices() return true; - case MediaDeviceKind::EndGuard_: - break; // Avoid `default:` so that `-Wswitch` catches missing enumerators at // compile time. } @@ -550,7 +546,7 @@ already_AddRefed<Promise> MediaDevices::GetDisplayMedia( // for us. vc.mMediaSource.Reset(); vc.mMediaSource.Construct().AssignASCII( - dom::MediaSourceEnumValues::GetString(MediaSourceEnum::Screen)); + dom::GetEnumString(MediaSourceEnum::Screen)); RefPtr<MediaDevices> self(this); MediaManager::Get() diff --git a/dom/media/MediaInfo.h b/dom/media/MediaInfo.h index abe5dfd9da..73704d1593 100644 --- a/dom/media/MediaInfo.h +++ b/dom/media/MediaInfo.h @@ -219,14 +219,6 @@ inline already_AddRefed<MediaByteBuffer> ForceGetAudioCodecSpecificBlob( // information as a blob or where a blob is ambiguous. inline already_AddRefed<MediaByteBuffer> GetAudioCodecSpecificBlob( const AudioCodecSpecificVariant& v) { - MOZ_ASSERT(!v.is<NoCodecSpecificData>(), - "NoCodecSpecificData shouldn't be used as a blob"); - MOZ_ASSERT(!v.is<AacCodecSpecificData>(), - "AacCodecSpecificData has 2 blobs internally, one should " - "explicitly be selected"); - MOZ_ASSERT(!v.is<Mp3CodecSpecificData>(), - "Mp3CodecSpecificData shouldn't be used as a blob"); - return ForceGetAudioCodecSpecificBlob(v); } @@ -470,7 +462,8 @@ class VideoInfo : public TrackInfo { rv.AppendPrintf("extra data: %zu bytes", mExtraData->Length()); } rv.AppendPrintf("rotation: %d", static_cast<int>(mRotation)); - rv.AppendPrintf("colors: %s", ColorDepthStrings[static_cast<int>(mColorDepth)]); + rv.AppendPrintf("colors: %s", + ColorDepthStrings[static_cast<int>(mColorDepth)]); if (mColorSpace) { rv.AppendPrintf( "YUV colorspace: %s ", @@ -486,7 +479,8 @@ class VideoInfo : public TrackInfo { "transfer function %s ", TransferFunctionStrings[static_cast<int>(mTransferFunction.value())]); } - rv.AppendPrintf("color range: %s", ColorRangeStrings[static_cast<int>(mColorRange)]); + rv.AppendPrintf("color range: %s", + ColorRangeStrings[static_cast<int>(mColorRange)]); if (mImageRect) { rv.AppendPrintf("image rect: %dx%d", mImageRect->Width(), mImageRect->Height()); diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 25eeb876d4..422769587a 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -872,8 +872,7 @@ MediaDevice::MediaDevice(MediaEngine* aEngine, MediaSourceEnum aMediaSource, mCanRequestOsLevelPrompt(canRequestOsLevelPrompt == OsPromptable::Yes), mIsFake(mEngine->IsFake()), mIsPlaceholder(aIsPlaceholder == IsPlaceholder::Yes), - mType( - NS_ConvertASCIItoUTF16(dom::MediaDeviceKindValues::GetString(mKind))), + mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind))), mRawID(aRawID), mRawGroupID(aRawGroupID), mRawName(aRawName) { @@ -895,8 +894,7 @@ MediaDevice::MediaDevice(MediaEngine* aEngine, mCanRequestOsLevelPrompt(false), mIsFake(false), mIsPlaceholder(false), - mType( - NS_ConvertASCIItoUTF16(dom::MediaDeviceKindValues::GetString(mKind))), + mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind))), mRawID(aRawID), mRawGroupID(mAudioDeviceInfo->GroupID()), mRawName(mAudioDeviceInfo->Name()) {} @@ -1064,8 +1062,7 @@ LocalMediaDevice::GetMediaSource(nsAString& aMediaSource) { if (Kind() == MediaDeviceKind::Audiooutput) { aMediaSource.Truncate(); } else { - aMediaSource.AssignASCII( - dom::MediaSourceEnumValues::GetString(GetMediaSource())); + aMediaSource.AssignASCII(dom::GetEnumString(GetMediaSource())); } return NS_OK; } @@ -2249,14 +2246,6 @@ MediaManager::MediaManager(already_AddRefed<TaskQueue> aMediaThread) GetPrefs(branch, nullptr); } } - LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, aec: %s," - "agc: %s, hpf: %s, noise: %s, agc level: %d, agc version: %s, noise " - "level: %d, transient: %s, channels %d", - __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mFreq, - mPrefs.mAecOn ? "on" : "off", mPrefs.mAgcOn ? "on" : "off", - mPrefs.mHPFOn ? "on" : "off", mPrefs.mNoiseOn ? "on" : "off", mPrefs.mAgc, - mPrefs.mAgc2Forced ? "2" : "1", mPrefs.mNoise, - mPrefs.mTransientOn ? "on" : "off", mPrefs.mChannels); } NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIMemoryReporter, @@ -2755,10 +2744,10 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia( auto& vc = c.mVideo.GetAsMediaTrackConstraints(); if (!vc.mMediaSource.WasPassed()) { vc.mMediaSource.Construct().AssignASCII( - dom::MediaSourceEnumValues::GetString(MediaSourceEnum::Camera)); + dom::GetEnumString(MediaSourceEnum::Camera)); } - videoType = StringToEnum(dom::MediaSourceEnumValues::strings, - vc.mMediaSource.Value(), MediaSourceEnum::Other); + videoType = dom::StringToEnum<MediaSourceEnum>(vc.mMediaSource.Value()) + .valueOr(MediaSourceEnum::Other); Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE, (uint32_t)videoType); switch (videoType) { @@ -2823,8 +2812,7 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia( if (videoType == MediaSourceEnum::Screen || videoType == MediaSourceEnum::Browser) { videoType = MediaSourceEnum::Window; - vc.mMediaSource.Value().AssignASCII( - dom::MediaSourceEnumValues::GetString(videoType)); + vc.mMediaSource.Value().AssignASCII(dom::GetEnumString(videoType)); } // only allow privileged content to set the window id if (vc.mBrowserWindow.WasPassed()) { @@ -2848,10 +2836,10 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia( auto& ac = c.mAudio.GetAsMediaTrackConstraints(); if (!ac.mMediaSource.WasPassed()) { ac.mMediaSource.Construct(NS_ConvertASCIItoUTF16( - dom::MediaSourceEnumValues::GetString(MediaSourceEnum::Microphone))); + dom::GetEnumString(MediaSourceEnum::Microphone))); } - audioType = StringToEnum(dom::MediaSourceEnumValues::strings, - ac.mMediaSource.Value(), MediaSourceEnum::Other); + audioType = dom::StringToEnum<MediaSourceEnum>(ac.mMediaSource.Value()) + .valueOr(MediaSourceEnum::Other); Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE, (uint32_t)audioType); @@ -3498,6 +3486,14 @@ void MediaManager::GetPrefs(nsIPrefBranch* aBranch, const char* aData) { GetPref(aBranch, "media.getusermedia.noise", aData, &mPrefs.mNoise); GetPref(aBranch, "media.getusermedia.channels", aData, &mPrefs.mChannels); #endif + LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, aec: %s, " + "agc: %s, hpf: %s, noise: %s, agc level: %d, agc version: %s, noise " + "level: %d, transient: %s, channels %d", + __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mFreq, + mPrefs.mAecOn ? "on" : "off", mPrefs.mAgcOn ? "on" : "off", + mPrefs.mHPFOn ? "on" : "off", mPrefs.mNoiseOn ? "on" : "off", mPrefs.mAgc, + mPrefs.mAgc2Forced ? "2" : "1", mPrefs.mNoise, + mPrefs.mTransientOn ? "on" : "off", mPrefs.mChannels); } void MediaManager::Shutdown() { @@ -3650,8 +3646,6 @@ nsresult MediaManager::Observe(nsISupports* aSubject, const char* aTopic, nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(aSubject)); if (branch) { GetPrefs(branch, NS_ConvertUTF16toUTF8(aData).get()); - LOG("%s: %dx%d @%dfps", __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, - mPrefs.mFPS); DeviceListChanged(); } } else if (!strcmp(aTopic, "last-pb-context-exited")) { @@ -4006,8 +4000,7 @@ void DeviceListener::Activate(RefPtr<LocalMediaDevice> aDevice, MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); LOG("DeviceListener %p activating %s device %p", this, - nsCString(dom::MediaDeviceKindValues::GetString(aDevice->Kind())).get(), - aDevice.get()); + dom::GetEnumString(aDevice->Kind()).get(), aDevice.get()); MOZ_ASSERT(!mStopped, "Cannot activate stopped device listener"); MOZ_ASSERT(!Activated(), "Already activated"); @@ -4063,18 +4056,15 @@ DeviceListener::InitializeAsync() { } if (NS_FAILED(rv)) { nsCString log; - log.AppendPrintf( - "Starting %s failed", - nsCString(dom::MediaDeviceKindValues::GetString(kind)) - .get()); + log.AppendPrintf("Starting %s failed", + dom::GetEnumString(kind).get()); aHolder.Reject( MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, std::move(log)), __func__); return; } - LOG("started %s device %p", - nsCString(dom::MediaDeviceKindValues::GetString(kind)).get(), + LOG("started %s device %p", dom::GetEnumString(kind).get(), device.get()); aHolder.Resolve(true, __func__); }) @@ -4182,9 +4172,7 @@ auto DeviceListener::UpdateDevice(bool aOn) -> RefPtr<DeviceOperationPromise> { } LOG("DeviceListener %p turning %s %s input device %s", this, aOn ? "on" : "off", - nsCString( - dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get(), + dom::GetEnumString(GetDevice()->Kind()).get(), NS_SUCCEEDED(aResult) ? "succeeded" : "failed"); if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT) { @@ -4217,8 +4205,7 @@ void DeviceListener::SetDeviceEnabled(bool aEnable) { LOG("DeviceListener %p %s %s device", this, aEnable ? "enabling" : "disabling", - nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get()); + dom::GetEnumString(GetDevice()->Kind()).get()); state.mTrackEnabled = aEnable; @@ -4274,9 +4261,7 @@ void DeviceListener::SetDeviceEnabled(bool aEnable) { LOG("DeviceListener %p %s %s device - starting device operation", this, aEnable ? "enabling" : "disabling", - nsCString( - dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get()); + dom::GetEnumString(GetDevice()->Kind()).get()); if (state.mStopped) { // Source was stopped between timer resolving and this runnable. @@ -4345,8 +4330,7 @@ void DeviceListener::SetDeviceMuted(bool aMute) { DeviceState& state = *mDeviceState; LOG("DeviceListener %p %s %s device", this, aMute ? "muting" : "unmuting", - nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get()); + dom::GetEnumString(GetDevice()->Kind()).get()); if (state.mStopped) { // Device terminally stopped. Updating device state is pointless. @@ -4360,8 +4344,7 @@ void DeviceListener::SetDeviceMuted(bool aMute) { LOG("DeviceListener %p %s %s device - starting device operation", this, aMute ? "muting" : "unmuting", - nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get()); + dom::GetEnumString(GetDevice()->Kind()).get()); state.mDeviceMuted = aMute; @@ -4467,9 +4450,7 @@ RefPtr<DeviceListener::DeviceListenerPromise> DeviceListener::ApplyConstraints( if (mStopped || mDeviceState->mStopped) { LOG("DeviceListener %p %s device applyConstraints, but device is stopped", - this, - nsCString(dom::MediaDeviceKindValues::GetString(GetDevice()->Kind())) - .get()); + this, dom::GetEnumString(GetDevice()->Kind()).get()); return DeviceListenerPromise::CreateAndResolve(false, __func__); } diff --git a/dom/media/MediaQueue.h b/dom/media/MediaQueue.h index 4609493339..9c4c829c87 100644 --- a/dom/media/MediaQueue.h +++ b/dom/media/MediaQueue.h @@ -25,6 +25,7 @@ extern LazyLogModule gMediaDecoderLog; class AudioData; class VideoData; +class EncodedFrame; template <typename T> struct TimestampAdjustmentTrait { @@ -46,13 +47,24 @@ struct NonTimestampAdjustmentTrait { static const bool mValue = !TimestampAdjustmentTrait<T>::mValue; }; +template <typename T> +struct DurationTypeTrait { + using type = media::TimeUnit; +}; + +template <> +struct DurationTypeTrait<EncodedFrame> { + using type = uint64_t; +}; + template <class T> class MediaQueue : private nsRefPtrDeque<T> { public: - MediaQueue() + explicit MediaQueue(bool aEnablePreciseDuration = false) : nsRefPtrDeque<T>(), mRecursiveMutex("mediaqueue"), - mEndOfStream(false) {} + mEndOfStream(false), + mEnablePreciseDuration(aEnablePreciseDuration) {} ~MediaQueue() { Reset(); } @@ -97,6 +109,7 @@ class MediaQueue : private nsRefPtrDeque<T> { AdjustTimeStampIfNeeded(aItem); } nsRefPtrDeque<T>::PushFront(aItem); + AddDurationToPreciseDuration(aItem); } inline void Push(T* aItem) { @@ -112,6 +125,7 @@ class MediaQueue : private nsRefPtrDeque<T> { MOZ_DIAGNOSTIC_ASSERT(item->GetEndTime() >= item->mTime); AdjustTimeStampIfNeeded(item); nsRefPtrDeque<T>::Push(dont_AddRef(item)); + AddDurationToPreciseDuration(item); mPushEvent.Notify(RefPtr<T>(item)); // Pushing new data after queue has ended means that the stream is active @@ -126,6 +140,7 @@ class MediaQueue : private nsRefPtrDeque<T> { RefPtr<T> rv = nsRefPtrDeque<T>::PopFront(); if (rv) { MOZ_DIAGNOSTIC_ASSERT(rv->GetEndTime() >= rv->mTime); + SubtractDurationFromPreciseDuration(rv); mPopFrontEvent.Notify(RefPtr<T>(rv)); } return rv.forget(); @@ -133,7 +148,12 @@ class MediaQueue : private nsRefPtrDeque<T> { inline already_AddRefed<T> PopBack() { RecursiveMutexAutoLock lock(mRecursiveMutex); - return nsRefPtrDeque<T>::Pop(); + RefPtr<T> rv = nsRefPtrDeque<T>::Pop(); + if (rv) { + MOZ_DIAGNOSTIC_ASSERT(rv->GetEndTime() >= rv->mTime); + SubtractDurationFromPreciseDuration(rv); + } + return rv.forget(); } inline RefPtr<T> PeekFront() const { @@ -151,6 +171,7 @@ class MediaQueue : private nsRefPtrDeque<T> { nsRefPtrDeque<T>::Erase(); SetOffset(media::TimeUnit::Zero()); mEndOfStream = false; + ResetPreciseDuration(); } bool AtEndOfStream() const { @@ -186,6 +207,12 @@ class MediaQueue : private nsRefPtrDeque<T> { return (last->GetEndTime() - first->mTime).ToMicroseconds(); } + // Return a precise duration if the feature is enabled. Otherwise, return -1. + int64_t PreciseDuration() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return GetPreciseDuration(); + } + void LockedForEach(nsDequeFunctor<T>& aFunctor) const { RecursiveMutexAutoLock lock(mRecursiveMutex); nsRefPtrDeque<T>::ForEach(aFunctor); @@ -268,6 +295,59 @@ class MediaQueue : private nsRefPtrDeque<T> { // the media queue starts receiving looped data, which timestamp needs to be // modified. media::TimeUnit mOffset; + + inline void AddDurationToPreciseDuration(T* aItem) { + if (!mEnablePreciseDuration) { + return; + } + if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + media::TimeUnit> || + std::is_same_v<typename DurationTypeTrait<T>::type, + uint64_t>) { + mPreciseDuration += aItem->mDuration; + } + } + + inline void SubtractDurationFromPreciseDuration(T* aItem) { + if (!mEnablePreciseDuration) { + return; + } + if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + media::TimeUnit> || + std::is_same_v<typename DurationTypeTrait<T>::type, + uint64_t>) { + mPreciseDuration -= aItem->mDuration; + } + } + + inline void ResetPreciseDuration() { + if (!mEnablePreciseDuration) { + return; + } + if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + media::TimeUnit>) { + mPreciseDuration = media::TimeUnit::Zero(); + } else if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + uint64_t>) { + mPreciseDuration = 0; + } + } + + inline int64_t GetPreciseDuration() const { + if (mEnablePreciseDuration) { + if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + media::TimeUnit>) { + return mPreciseDuration.ToMicroseconds(); + } else if constexpr (std::is_same_v<typename DurationTypeTrait<T>::type, + uint64_t>) { + return mPreciseDuration; + } + } + return -1; + } + + typename DurationTypeTrait<T>::type mPreciseDuration; + const bool mEnablePreciseDuration = false; }; } // namespace mozilla diff --git a/dom/media/MediaStreamTrack.cpp b/dom/media/MediaStreamTrack.cpp index 8ddb16d8e1..4235d96d13 100644 --- a/dom/media/MediaStreamTrack.cpp +++ b/dom/media/MediaStreamTrack.cpp @@ -324,7 +324,7 @@ void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult, } if (aResult.mFacingMode.WasPassed()) { aResult.mFacingMode.Value().AssignASCII( - VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User)); + GetEnumString(VideoFacingModeEnum::User)); } } diff --git a/dom/media/MediaTrackGraph.cpp b/dom/media/MediaTrackGraph.cpp index 157ad403d2..2af7aacb1f 100644 --- a/dom/media/MediaTrackGraph.cpp +++ b/dom/media/MediaTrackGraph.cpp @@ -1393,6 +1393,9 @@ void MediaTrackGraphImpl::SelectOutputDeviceForAEC() { if (currentDeviceIndex == mOutputDevices.NoIndex) { // Outputs for this device have been removed. // Fall back to the primary output device. + LOG(LogLevel::Info, ("%p: No remaining outputs to device %p. " + "Switch to primary output device %p for AEC", + this, mOutputDeviceForAEC, PrimaryOutputDeviceID())); mOutputDeviceForAEC = PrimaryOutputDeviceID(); currentDeviceIndex = 0; MOZ_ASSERT(mOutputDevices[0].mDeviceID == mOutputDeviceForAEC); @@ -1425,6 +1428,9 @@ void MediaTrackGraphImpl::SelectOutputDeviceForAEC() { for (const auto& output : outputDeviceEntry.mTrackOutputs) { if (HasNonNullAudio(output)) { // Switch to this device. + LOG(LogLevel::Info, + ("%p: Switch output device for AEC from silent %p to non-null %p", + this, mOutputDeviceForAEC, outputDeviceEntry.mDeviceID)); mOutputDeviceForAEC = outputDeviceEntry.mDeviceID; return; } diff --git a/dom/media/PeerConnection.sys.mjs b/dom/media/PeerConnection.sys.mjs index 1bebde8159..00b4023c2f 100644 --- a/dom/media/PeerConnection.sys.mjs +++ b/dom/media/PeerConnection.sys.mjs @@ -24,7 +24,7 @@ const PC_COREQUEST_CID = Components.ID( "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}" ); -function logMsg(msg, file, line, flag, winID) { +function logWebRTCMsg(msg, file, line, flag, win) { let scriptErrorClass = Cc["@mozilla.org/scripterror;1"]; let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError); scriptError.initWithWindowID( @@ -35,9 +35,14 @@ function logMsg(msg, file, line, flag, winID) { 0, flag, "content javascript", - winID + win.windowGlobalChild.innerWindowId ); Services.console.logMessage(scriptError); + if ( + Services.prefs.getBoolPref("media.peerconnection.treat_warnings_as_errors") + ) { + throw new win.TypeError(msg); + } } let setupPrototype = (_class, dict) => { @@ -100,7 +105,7 @@ export class GlobalPCList { if (this._list[winID] === undefined) { return; } - this._list[winID] = this._list[winID].filter(function (e, i, a) { + this._list[winID] = this._list[winID].filter(function (e) { return e.get() !== null; }); @@ -237,14 +242,12 @@ export class RTCSessionDescription { init(win) { this._win = win; this._winID = this._win.windowGlobalChild.innerWindowId; + this._legacyPref = Services.prefs.getBoolPref( + "media.peerconnection.description.legacy.enabled" + ); } __init({ type, sdp }) { - if (!type) { - throw new this._win.TypeError( - "Missing required 'type' member of RTCSessionDescriptionInit" - ); - } Object.assign(this, { _type: type, _sdp: sdp }); } @@ -252,6 +255,10 @@ export class RTCSessionDescription { return this._type; } set type(type) { + if (!this._legacyPref) { + // TODO: this throws even in sloppy mode. Remove in bug 1883992 + throw new this._win.TypeError("setting getter-only property type"); + } this.warn(); this._type = type; } @@ -260,6 +267,10 @@ export class RTCSessionDescription { return this._sdp; } set sdp(sdp) { + if (!this._legacyPref) { + // TODO: this throws even in sloppy mode. Remove in bug 1883992 + throw new this._win.TypeError("setting getter-only property sdp"); + } this.warn(); this._sdp = sdp; } @@ -267,23 +278,26 @@ export class RTCSessionDescription { warn() { if (!this._warned) { // Warn once per RTCSessionDescription about deprecated writable usage. - this.logWarning( - "RTCSessionDescription's members are readonly! " + - "Writing to them is deprecated and will break soon!" - ); + if (this._legacyPref) { + this.logMsg( + "RTCSessionDescription's members are readonly! " + + "Writing to them is deprecated and will break soon!", + Ci.nsIScriptError.warningFlag + ); + } else { + this.logMsg( + "RTCSessionDescription's members are readonly! " + + "Writing to them no longer works!", + Ci.nsIScriptError.errorFlag + ); + } this._warned = true; } } - logWarning(msg) { + logMsg(msg, flag) { let err = this._win.Error(); - logMsg( - msg, - err.fileName, - err.lineNumber, - Ci.nsIScriptError.warningFlag, - this._winID - ); + logWebRTCMsg(msg, err.fileName, err.lineNumber, flag, this._win); } } @@ -350,6 +364,13 @@ export class RTCPeerConnection { constructor() { this._pc = null; this._closed = false; + this._pendingLocalDescription = null; + this._pendingRemoteDescription = null; + this._currentLocalDescription = null; + this._currentRemoteDescription = null; + this._legacyPref = Services.prefs.getBoolPref( + "media.peerconnection.description.legacy.enabled" + ); // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9 // canTrickle == null means unknown; when a remote description is received it @@ -723,7 +744,7 @@ export class RTCPeerConnection { } }; - var stunServers = 0; + let stunServers = 0; iceServers.forEach(({ urls, username, credential, credentialType }) => { if (!urls) { @@ -788,11 +809,7 @@ export class RTCPeerConnection { } if (stunServers >= 5) { this.logError( - "Using five or more STUN/TURN servers causes problems" - ); - } else if (stunServers > 2) { - this.logWarning( - "Using more than two STUN/TURN servers slows down discovery" + "Using five or more STUN/TURN servers slows down discovery" ); } }); @@ -861,7 +878,7 @@ export class RTCPeerConnection { } logMsg(msg, file, line, flag) { - return logMsg(msg, file, line, flag, this._winID); + return logWebRTCMsg(msg, file, line, flag, this._win); } getEH(type) { @@ -996,7 +1013,7 @@ export class RTCPeerConnection { return this._async(() => this._createAnswer(optionsOrOnSucc)); } - _createAnswer(options) { + _createAnswer() { this._checkClosed(); return this._chain(() => this._createAnAnswer()); } @@ -1192,11 +1209,6 @@ export class RTCPeerConnection { } _setRemoteDescription({ type, sdp }) { - if (!type) { - throw new this._win.TypeError( - "Missing required 'type' member of RTCSessionDescriptionInit" - ); - } if (type == "pranswer") { throw new this._win.DOMException( "pranswer not yet implemented", @@ -1541,24 +1553,36 @@ export class RTCPeerConnection { return this.pendingLocalDescription || this.currentLocalDescription; } + cacheDescription(name, type, sdp) { + if ( + !this[name] || + this[name].type != type || + this[name].sdp != sdp || + this._legacyPref + ) { + this[name] = sdp.length + ? new this._win.RTCSessionDescription({ type, sdp }) + : null; + } + return this[name]; + } + get currentLocalDescription() { this._checkClosed(); - const sdp = this._pc.currentLocalDescription; - if (!sdp.length) { - return null; - } - const type = this._pc.currentOfferer ? "offer" : "answer"; - return new this._win.RTCSessionDescription({ type, sdp }); + return this.cacheDescription( + "_currentLocalDescription", + this._pc.currentOfferer ? "offer" : "answer", + this._pc.currentLocalDescription + ); } get pendingLocalDescription() { this._checkClosed(); - const sdp = this._pc.pendingLocalDescription; - if (!sdp.length) { - return null; - } - const type = this._pc.pendingOfferer ? "offer" : "answer"; - return new this._win.RTCSessionDescription({ type, sdp }); + return this.cacheDescription( + "_pendingLocalDescription", + this._pc.pendingOfferer ? "offer" : "answer", + this._pc.pendingLocalDescription + ); } get remoteDescription() { @@ -1567,22 +1591,20 @@ export class RTCPeerConnection { get currentRemoteDescription() { this._checkClosed(); - const sdp = this._pc.currentRemoteDescription; - if (!sdp.length) { - return null; - } - const type = this._pc.currentOfferer ? "answer" : "offer"; - return new this._win.RTCSessionDescription({ type, sdp }); + return this.cacheDescription( + "_currentRemoteDescription", + this._pc.currentOfferer ? "answer" : "offer", + this._pc.currentRemoteDescription + ); } get pendingRemoteDescription() { this._checkClosed(); - const sdp = this._pc.pendingRemoteDescription; - if (!sdp.length) { - return null; - } - const type = this._pc.pendingOfferer ? "answer" : "offer"; - return new this._win.RTCSessionDescription({ type, sdp }); + return this.cacheDescription( + "_pendingRemoteDescription", + this._pc.pendingOfferer ? "answer" : "offer", + this._pc.pendingRemoteDescription + ); } get peerIdentity() { @@ -1918,8 +1940,7 @@ export class PeerConnectionObserver { switch (state) { case "IceConnectionState": - let connState = this._dompc._pc.iceConnectionState; - this.handleIceConnectionStateChange(connState); + this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState); break; case "IceGatheringState": diff --git a/dom/media/VideoFrameConverter.h b/dom/media/VideoFrameConverter.h index 36132c1b45..31b3104955 100644 --- a/dom/media/VideoFrameConverter.h +++ b/dom/media/VideoFrameConverter.h @@ -109,8 +109,8 @@ class VideoFrameConverter { // for processing so it can be immediately sent. mLastFrameQueuedForProcessing.mTime = time; - MOZ_ALWAYS_SUCCEEDS(mTaskQueue->Dispatch( - NewRunnableMethod<StoreCopyPassByLRef<FrameToProcess>>( + MOZ_ALWAYS_SUCCEEDS( + mTaskQueue->Dispatch(NewRunnableMethod<FrameToProcess>( "VideoFrameConverter::ProcessVideoFrame", this, &VideoFrameConverter::ProcessVideoFrame, mLastFrameQueuedForProcessing))); @@ -138,8 +138,8 @@ class VideoFrameConverter { mLastFrameQueuedForProcessing.mForceBlack = true; mLastFrameQueuedForProcessing.mImage = nullptr; - MOZ_ALWAYS_SUCCEEDS(mTaskQueue->Dispatch( - NewRunnableMethod<StoreCopyPassByLRef<FrameToProcess>>( + MOZ_ALWAYS_SUCCEEDS( + mTaskQueue->Dispatch(NewRunnableMethod<FrameToProcess>( "VideoFrameConverter::ProcessVideoFrame", this, &VideoFrameConverter::ProcessVideoFrame, mLastFrameQueuedForProcessing))); @@ -293,11 +293,10 @@ class VideoFrameConverter { return; } - MOZ_ALWAYS_SUCCEEDS(mTaskQueue->Dispatch( - NewRunnableMethod<StoreCopyPassByLRef<FrameToProcess>>( - "VideoFrameConverter::ProcessVideoFrame", this, - &VideoFrameConverter::ProcessVideoFrame, - mLastFrameQueuedForProcessing))); + MOZ_ALWAYS_SUCCEEDS(mTaskQueue->Dispatch(NewRunnableMethod<FrameToProcess>( + "VideoFrameConverter::ProcessVideoFrame", this, + &VideoFrameConverter::ProcessVideoFrame, + mLastFrameQueuedForProcessing))); } void ProcessVideoFrame(const FrameToProcess& aFrame) { @@ -364,7 +363,9 @@ class VideoFrameConverter { aFrame.mImage->AsPlanarYCbCrImage(); if (image) { dom::ImageUtils utils(image); - if (utils.GetFormat() == dom::ImageBitmapFormat::YUV420P && + Maybe<dom::ImageBitmapFormat> format = utils.GetFormat(); + if (format.isSome() && + format.value() == dom::ImageBitmapFormat::YUV420P && image->GetData()) { const layers::PlanarYCbCrData* data = image->GetData(); rtc::scoped_refptr<webrtc::I420BufferInterface> video_frame_buffer = diff --git a/dom/media/VideoUtils.cpp b/dom/media/VideoUtils.cpp index 279ce9b912..90057f7c83 100644 --- a/dom/media/VideoUtils.cpp +++ b/dom/media/VideoUtils.cpp @@ -4,7 +4,6 @@ #include "VideoUtils.h" -#include <functional> #include <stdint.h> #include "CubebUtils.h" @@ -19,7 +18,6 @@ #include "mozilla/StaticPrefs_accessibility.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/TaskQueue.h" -#include "mozilla/Telemetry.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentTypeParser.h" #include "nsIConsoleService.h" @@ -29,7 +27,6 @@ #include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" -#include "AudioStream.h" namespace mozilla { diff --git a/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js b/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js index 77ce4ddbc1..433e4fd51e 100644 --- a/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js +++ b/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js @@ -31,8 +31,8 @@ async function checkIsIframeVideoDocumentAutoplay(browser) { const iframe = content.document.createElement("iframe"); iframe.src = pageURL; content.document.body.appendChild(iframe); - const iframeLoaded = new Promise((resolve, reject) => { - iframe.addEventListener("load", e => resolve(), { once: true }); + const iframeLoaded = new Promise(resolve => { + iframe.addEventListener("load", () => resolve(), { once: true }); }); await iframeLoaded; return iframe.browsingContext; diff --git a/dom/media/autoplay/test/browser/head.js b/dom/media/autoplay/test/browser/head.js index c84850900a..6522104b7f 100644 --- a/dom/media/autoplay/test/browser/head.js +++ b/dom/media/autoplay/test/browser/head.js @@ -29,10 +29,10 @@ function loadAutoplayVideo(browser, args) { info("- create a new autoplay video -"); let video = content.document.createElement("video"); video.id = "v1"; - video.didPlayPromise = new Promise((resolve, reject) => { + video.didPlayPromise = new Promise(resolve => { video.addEventListener( "playing", - e => { + () => { video.didPlay = true; resolve(); }, @@ -40,7 +40,7 @@ function loadAutoplayVideo(browser, args) { ); video.addEventListener( "blocked", - e => { + () => { video.didPlay = false; resolve(); }, @@ -54,7 +54,7 @@ function loadAutoplayVideo(browser, args) { info("will call play() when reached loadedmetadata"); video.addEventListener( "loadedmetadata", - e => { + () => { video.play().then( () => { info("video play() resolved"); diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html index e25b6401d1..9fcda2afe9 100644 --- a/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html +++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html @@ -36,8 +36,8 @@ let x = eventNames.map( (eventName) => { - return new Promise(function (resolve, reject) { - window.addEventListener(eventName, async function (event) { + return new Promise(function (resolve) { + window.addEventListener(eventName, async function () { let p = await element.play().then(() => true, () => false); ok(p, "Expect to be activated already in " + eventName); resolve(); diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html index b901df3324..fc8c47065e 100644 --- a/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html +++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html @@ -91,7 +91,7 @@ input.blur(); } - async function testAutoplayKeyBlacklist(testCase, parent_window) { + async function testAutoplayKeyBlacklist() { let element = document.createElement("video"); element.preload = "auto"; element.src = "short.mp4"; diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html index 3594d0f236..dd358e6dc1 100644 --- a/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html +++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html @@ -22,7 +22,7 @@ window.is = window.opener.is; window.info = window.opener.info; - async function testPlayBeforeLoadedMetata(testCase, parent_window) { + async function testPlayBeforeLoadedMetata(testCase) { info("testPlayBeforeLoadedMetata: " + testCase.resource); let element = document.createElement("video"); diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html index 125ee156b6..f3b4e7da4d 100644 --- a/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html +++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html @@ -22,8 +22,8 @@ window.is = window.opener.is; window.info = window.opener.info; - function testAutoplayUnmutePauses(testCase, parent_window) { - return new Promise(function (resolve, reject) { + function testAutoplayUnmutePauses(testCase) { + return new Promise(function (resolve) { info("testAutoplayUnmutePauses: " + testCase.property); diff --git a/dom/media/doctor/test/browser/browser_doctor_notification.js b/dom/media/doctor/test/browser/browser_doctor_notification.js index 5789622e23..6068304dfa 100644 --- a/dom/media/doctor/test/browser/browser_doctor_notification.js +++ b/dom/media/doctor/test/browser/browser_doctor_notification.js @@ -233,15 +233,11 @@ async function setFormatDiagnosticsReportForMimeType(tab, params) { async function setDecodeError(tab, params) { info(`start check for ${params.error}`); - await SpecialPowers.spawn( - tab.linkedBrowser, - [params], - async (params, shouldReportNotification) => { - const video = content.document.createElement("video"); - SpecialPowers.wrap(video).setDecodeError(params.error); - await content._waitForReport(params, params.shouldReportNotification); - } - ); + await SpecialPowers.spawn(tab.linkedBrowser, [params], async params => { + const video = content.document.createElement("video"); + SpecialPowers.wrap(video).setDecodeError(params.error); + await content._waitForReport(params, params.shouldReportNotification); + }); ok(true, `finished check for ${params.error}`); } diff --git a/dom/media/eme/EMEUtils.cpp b/dom/media/eme/EMEUtils.cpp index 294951e7e6..5a6b645df2 100644 --- a/dom/media/eme/EMEUtils.cpp +++ b/dom/media/eme/EMEUtils.cpp @@ -8,13 +8,13 @@ #include "jsfriendapi.h" #include "MediaData.h" +#include "KeySystemConfig.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/dom/KeySystemNames.h" #include "mozilla/dom/UnionTypes.h" #ifdef MOZ_WMF_CDM # include "mozilla/PMFCDM.h" -# include "KeySystemConfig.h" #endif namespace mozilla { @@ -143,22 +143,35 @@ const char* ToMediaKeyStatusStr(dom::MediaKeyStatus aStatus) { bool IsHardwareDecryptionSupported( const dom::MediaKeySystemConfiguration& aConfig) { - bool supportHardwareDecryption = false; for (const auto& capabilities : aConfig.mAudioCapabilities) { if (capabilities.mRobustness.EqualsLiteral("HW_SECURE_ALL")) { - supportHardwareDecryption = true; - break; + return true; } } for (const auto& capabilities : aConfig.mVideoCapabilities) { if (capabilities.mRobustness.EqualsLiteral("3000") || capabilities.mRobustness.EqualsLiteral("HW_SECURE_ALL") || capabilities.mRobustness.EqualsLiteral("HW_SECURE_DECODE")) { - supportHardwareDecryption = true; - break; + return true; + } + } + return false; +} + +bool IsHardwareDecryptionSupported(const KeySystemConfig& aConfig) { + for (const auto& robustness : aConfig.mAudioRobustness) { + if (robustness.EqualsLiteral("HW_SECURE_ALL")) { + return true; + } + } + for (const auto& robustness : aConfig.mVideoRobustness) { + if (robustness.EqualsLiteral("3000") || + robustness.EqualsLiteral("HW_SECURE_ALL") || + robustness.EqualsLiteral("HW_SECURE_DECODE")) { + return true; } } - return supportHardwareDecryption; + return false; } const char* EncryptionSchemeStr(const CryptoScheme& aScheme) { @@ -245,4 +258,16 @@ bool CheckIfHarewareDRMConfigExists( return foundHWDRMconfig; } +bool DoesKeySystemSupportHardwareDecryption(const nsAString& aKeySystem) { +#ifdef MOZ_WMF_CDM + if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) || + aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName) || + aKeySystem.EqualsLiteral(kWidevineExperimentKeySystemName) || + aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName)) { + return true; + } +#endif + return false; +} + } // namespace mozilla diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h index ca5c684a9f..3fbf22f359 100644 --- a/dom/media/eme/EMEUtils.h +++ b/dom/media/eme/EMEUtils.h @@ -89,6 +89,7 @@ const char* ToMediaKeyStatusStr(dom::MediaKeyStatus aStatus); // Return true if given config supports hardware decryption (SL3000 or L1). bool IsHardwareDecryptionSupported( const dom::MediaKeySystemConfiguration& aConfig); +bool IsHardwareDecryptionSupported(const KeySystemConfig& aConfig); const char* EncryptionSchemeStr(const CryptoScheme& aScheme); @@ -104,6 +105,8 @@ bool DoesKeySystemSupportClearLead(const nsAString& aKeySystem); bool CheckIfHarewareDRMConfigExists( const nsTArray<dom::MediaKeySystemConfiguration>& aConfigs); +bool DoesKeySystemSupportHardwareDecryption(const nsAString& aKeySystem); + } // namespace mozilla #endif // EME_LOG_H_ diff --git a/dom/media/eme/KeySystemConfig.cpp b/dom/media/eme/KeySystemConfig.cpp index b1b1f9ff63..0cb5da1a56 100644 --- a/dom/media/eme/KeySystemConfig.cpp +++ b/dom/media/eme/KeySystemConfig.cpp @@ -57,9 +57,10 @@ bool KeySystemConfig::Supports(const nsAString& aKeySystem) { {nsCString(kWidevineExperimentKeySystemName)}); } - if ((IsPlayReadyKeySystemAndSupported(aKeySystem) || - IsWMFClearKeySystemAndSupported(aKeySystem)) && - WMFCDMImpl::Supports(aKeySystem)) { + // PlayReady and WMF-based ClearKey are always installed, we don't need to + // download them. + if (IsPlayReadyKeySystemAndSupported(aKeySystem) || + IsWMFClearKeySystemAndSupported(aKeySystem)) { return true; } #endif @@ -69,7 +70,8 @@ bool KeySystemConfig::Supports(const nsAString& aKeySystem) { /* static */ bool KeySystemConfig::CreateKeySystemConfigs( - const nsAString& aKeySystem, nsTArray<KeySystemConfig>& aOutConfigs) { + const nsAString& aKeySystem, const DecryptionInfo aDecryption, + nsTArray<KeySystemConfig>& aOutConfigs) { if (!Supports(aKeySystem)) { return false; } @@ -103,10 +105,16 @@ bool KeySystemConfig::CreateKeySystemConfigs( config->mMP4.SetCanDecrypt(EME_CODEC_FLAC); config->mMP4.SetCanDecrypt(EME_CODEC_OPUS); config->mMP4.SetCanDecrypt(EME_CODEC_VP9); +#ifdef MOZ_AV1 + config->mMP4.SetCanDecrypt(EME_CODEC_AV1); +#endif config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS); config->mWebM.SetCanDecrypt(EME_CODEC_OPUS); config->mWebM.SetCanDecrypt(EME_CODEC_VP8); config->mWebM.SetCanDecrypt(EME_CODEC_VP9); +#ifdef MOZ_AV1 + config->mWebM.SetCanDecrypt(EME_CODEC_AV1); +#endif if (StaticPrefs::media_clearkey_test_key_systems_enabled()) { // Add testing key systems. These offer the same capabilities as the @@ -156,6 +164,10 @@ bool KeySystemConfig::CreateKeySystemConfigs( &config->mMP4}, {nsCString(VIDEO_MP4), EME_CODEC_VP9, java::MediaDrmProxy::AVC, &config->mMP4}, +# ifdef MOZ_AV1 + {nsCString(VIDEO_MP4), EME_CODEC_AV1, java::MediaDrmProxy::AV1, + &config->mMP4}, +# endif {nsCString(AUDIO_MP4), EME_CODEC_AAC, java::MediaDrmProxy::AAC, &config->mMP4}, {nsCString(AUDIO_MP4), EME_CODEC_FLAC, java::MediaDrmProxy::FLAC, @@ -166,6 +178,10 @@ bool KeySystemConfig::CreateKeySystemConfigs( &config->mWebM}, {nsCString(VIDEO_WEBM), EME_CODEC_VP9, java::MediaDrmProxy::VP9, &config->mWebM}, +# ifdef MOZ_AV1 + {nsCString(VIDEO_WEBM), EME_CODEC_AV1, java::MediaDrmProxy::AV1, + &config->mWebM}, +# endif {nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, java::MediaDrmProxy::VORBIS, &config->mWebM}, {nsCString(AUDIO_WEBM), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS, @@ -198,10 +214,16 @@ bool KeySystemConfig::CreateKeySystemConfigs( config->mMP4.SetCanDecrypt(EME_CODEC_OPUS); config->mMP4.SetCanDecryptAndDecode(EME_CODEC_H264); config->mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9); +# ifdef MOZ_AV1 + config->mMP4.SetCanDecryptAndDecode(EME_CODEC_AV1); +# endif config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS); config->mWebM.SetCanDecrypt(EME_CODEC_OPUS); config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8); config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9); +# ifdef MOZ_AV1 + config->mWebM.SetCanDecryptAndDecode(EME_CODEC_AV1); +# endif #endif return true; } @@ -209,7 +231,8 @@ bool KeySystemConfig::CreateKeySystemConfigs( if (IsPlayReadyKeySystemAndSupported(aKeySystem) || IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { RefPtr<WMFCDMImpl> cdm = MakeRefPtr<WMFCDMImpl>(aKeySystem); - return cdm->GetCapabilities(aOutConfigs); + return cdm->GetCapabilities(aDecryption == DecryptionInfo::Hardware, + aOutConfigs); } #endif return false; @@ -243,7 +266,9 @@ void KeySystemConfig::GetGMPKeySystemConfigs(dom::Promise* aPromise) { continue; } #endif - if (KeySystemConfig::CreateKeySystemConfigs(name, keySystemConfigs)) { + if (KeySystemConfig::CreateKeySystemConfigs( + name, KeySystemConfig::DecryptionInfo::Software, + keySystemConfigs)) { auto* info = cdmInfo.AppendElement(fallible); if (!info) { aPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); diff --git a/dom/media/eme/KeySystemConfig.h b/dom/media/eme/KeySystemConfig.h index 8bd7d98217..cc35ba76de 100644 --- a/dom/media/eme/KeySystemConfig.h +++ b/dom/media/eme/KeySystemConfig.h @@ -36,6 +36,7 @@ struct KeySystemConfig { static constexpr auto EME_CODEC_VORBIS = "vorbis"_ns; static constexpr auto EME_CODEC_FLAC = "flac"_ns; static constexpr auto EME_CODEC_H264 = "h264"_ns; + static constexpr auto EME_CODEC_AV1 = "av1"_ns; static constexpr auto EME_CODEC_VP8 = "vp8"_ns; static constexpr auto EME_CODEC_VP9 = "vp9"_ns; static constexpr auto EME_CODEC_HEVC = "hevc"_ns; @@ -123,7 +124,13 @@ struct KeySystemConfig { // Return true if given key system is supported on the current device. static bool Supports(const nsAString& aKeySystem); + + enum class DecryptionInfo : uint8_t { + Software, + Hardware, + }; static bool CreateKeySystemConfigs(const nsAString& aKeySystem, + const DecryptionInfo aDecryption, nsTArray<KeySystemConfig>& aOutConfigs); static void GetGMPKeySystemConfigs(dom::Promise* aPromise); diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp index 908df5f7c8..8a3a01dd5c 100644 --- a/dom/media/eme/MediaKeySession.cpp +++ b/dom/media/eme/MediaKeySession.cpp @@ -52,7 +52,7 @@ static const uint32_t MAX_CENC_INIT_DATA_LENGTH = 64 * 1024; MediaKeySession::MediaKeySession(nsPIDOMWindowInner* aParent, MediaKeys* aKeys, const nsAString& aKeySystem, MediaKeySessionType aSessionType, - ErrorResult& aRv) + bool aHardwareDecryption, ErrorResult& aRv) : DOMEventTargetHelper(aParent), mKeys(aKeys), mKeySystem(aKeySystem), @@ -61,7 +61,8 @@ MediaKeySession::MediaKeySession(nsPIDOMWindowInner* aParent, MediaKeys* aKeys, mIsClosed(false), mUninitialized(true), mKeyStatusMap(new MediaKeyStatusMap(aParent)), - mExpiration(JS::GenericNaN()) { + mExpiration(JS::GenericNaN()), + mHardwareDecryption(aHardwareDecryption) { EME_LOG("MediaKeySession[%p,''] ctor", this); MOZ_ASSERT(aParent); @@ -123,9 +124,8 @@ void MediaKeySession::UpdateKeyStatusMap() { nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {", this, NS_ConvertUTF16toUTF8(mSessionId).get())); for (const CDMCaps::KeyStatus& status : keyStatuses) { - message.AppendPrintf( - " (%s,%s)", ToHexString(status.mId).get(), - nsCString(MediaKeyStatusValues::GetString(status.mStatus)).get()); + message.AppendPrintf(" (%s,%s)", ToHexString(status.mId).get(), + GetEnumString(status.mStatus).get()); } message.AppendLiteral(" }"); // Use %s so we aren't exposing random strings to printf interpolation. @@ -250,8 +250,8 @@ already_AddRefed<Promise> MediaKeySession::GenerateRequest( // cdm implementation value does not support initDataType as an // Initialization Data Type, return a promise rejected with a // NotSupportedError. String comparison is case-sensitive. - if (!MediaKeySystemAccess::KeySystemSupportsInitDataType(mKeySystem, - aInitDataType)) { + if (!MediaKeySystemAccess::KeySystemSupportsInitDataType( + mKeySystem, aInitDataType, mHardwareDecryption)) { promise->MaybeRejectWithNotSupportedError( "Unsupported initDataType passed to MediaKeySession.generateRequest()"); EME_LOG( @@ -542,8 +542,7 @@ void MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType, EME_LOG( "MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message='%s'", this, NS_ConvertUTF16toUTF8(mSessionId).get(), - nsCString(MediaKeyMessageTypeValues::GetString(aMessageType)).get(), - ToHexString(aMessage).get()); + GetEnumString(aMessageType).get(), ToHexString(aMessage).get()); } RefPtr<MediaKeyMessageEvent> event( @@ -611,12 +610,8 @@ void MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback) { SetEventHandler(nsGkAtoms::onmessage, aCallback); } -nsCString ToCString(MediaKeySessionType aType) { - return nsCString(MediaKeySessionTypeValues::GetString(aType)); -} - nsString ToString(MediaKeySessionType aType) { - return NS_ConvertUTF8toUTF16(ToCString(aType)); + return NS_ConvertUTF8toUTF16(GetEnumString(aType)); } } // namespace mozilla::dom diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h index e19488c311..7204f99eef 100644 --- a/dom/media/eme/MediaKeySession.h +++ b/dom/media/eme/MediaKeySession.h @@ -36,8 +36,6 @@ class ArrayBufferViewOrArrayBuffer; class MediaKeyError; class MediaKeyStatusMap; -nsCString ToCString(MediaKeySessionType aType); - nsString ToString(MediaKeySessionType aType); class MediaKeySession final : public DOMEventTargetHelper, @@ -49,7 +47,7 @@ class MediaKeySession final : public DOMEventTargetHelper, public: MediaKeySession(nsPIDOMWindowInner* aParent, MediaKeys* aKeys, const nsAString& aKeySystem, MediaKeySessionType aSessionType, - ErrorResult& aRv); + bool aHardwareDecryption, ErrorResult& aRv); void SetSessionId(const nsAString& aSessionId); @@ -134,6 +132,9 @@ class MediaKeySession final : public DOMEventTargetHelper, bool mUninitialized; RefPtr<MediaKeyStatusMap> mKeyStatusMap; double mExpiration; + + // True if this key session is related with hardware decryption. + bool mHardwareDecryption; }; } // namespace dom diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp index b58ff76424..d498c2a773 100644 --- a/dom/media/eme/MediaKeySystemAccess.cpp +++ b/dom/media/eme/MediaKeySystemAccess.cpp @@ -214,6 +214,9 @@ static KeySystemConfig::EMECodecString ToEMEAPICodecString( if (IsH264CodecString(aCodec)) { return KeySystemConfig::EME_CODEC_H264; } + if (IsAV1CodecString(aCodec)) { + return KeySystemConfig::EME_CODEC_AV1; + } if (IsVP8CodecString(aCodec)) { return KeySystemConfig::EME_CODEC_VP8; } @@ -228,31 +231,53 @@ static KeySystemConfig::EMECodecString ToEMEAPICodecString( return ""_ns; } -static nsTArray<KeySystemConfig> GetSupportedKeySystems() { +static nsTArray<KeySystemConfig> GetSupportedKeySystems( + const nsAString& aKeySystem, bool aIsHardwareDecryption) { + using DecryptionInfo = KeySystemConfig::DecryptionInfo; nsTArray<KeySystemConfig> keySystemConfigs; - - const nsTArray<nsString> keySystemNames{ - NS_ConvertUTF8toUTF16(kClearKeyKeySystemName), - NS_ConvertUTF8toUTF16(kWidevineKeySystemName), + if (IsWidevineKeySystem(aKeySystem) || IsClearkeyKeySystem(aKeySystem)) { + Unused << KeySystemConfig::CreateKeySystemConfigs( + aKeySystem, DecryptionInfo::Software, keySystemConfigs); + } #ifdef MOZ_WMF_CDM - NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), - NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware), - NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName), - NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName), - NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName), -#endif - }; - for (const auto& name : keySystemNames) { - Unused << KeySystemConfig::CreateKeySystemConfigs(name, keySystemConfigs); + if (IsPlayReadyKeySystem(aKeySystem)) { + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), + DecryptionInfo::Software, keySystemConfigs); + if (aIsHardwareDecryption) { + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), + DecryptionInfo::Hardware, keySystemConfigs); + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware), + DecryptionInfo::Hardware, keySystemConfigs); + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName), + DecryptionInfo::Hardware, keySystemConfigs); + } + } + // If key system is kWidevineKeySystemName but with hardware decryption + // requirement, then we need to check those experiement key systems which are + // used for hardware decryption. + if (IsWidevineExperimentKeySystem(aKeySystem) || + (IsWidevineKeySystem(aKeySystem) && aIsHardwareDecryption)) { + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName), + DecryptionInfo::Hardware, keySystemConfigs); + Unused << KeySystemConfig::CreateKeySystemConfigs( + NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName), + DecryptionInfo::Hardware, keySystemConfigs); } +#endif return keySystemConfigs; } static bool GetKeySystemConfigs( - const nsAString& aKeySystem, + const nsAString& aKeySystem, bool aIsHardwareDecryption, nsTArray<KeySystemConfig>& aOutKeySystemConfig) { bool foundConfigs = false; - for (auto& config : GetSupportedKeySystems()) { + for (auto& config : + GetSupportedKeySystems(aKeySystem, aIsHardwareDecryption)) { if (config.IsSameKeySystem(aKeySystem)) { aOutKeySystemConfig.AppendElement(std::move(config)); foundConfigs = true; @@ -263,9 +288,10 @@ static bool GetKeySystemConfigs( /* static */ bool MediaKeySystemAccess::KeySystemSupportsInitDataType( - const nsAString& aKeySystem, const nsAString& aInitDataType) { + const nsAString& aKeySystem, const nsAString& aInitDataType, + bool aIsHardwareDecryption) { nsTArray<KeySystemConfig> implementations; - GetKeySystemConfigs(aKeySystem, implementations); + GetKeySystemConfigs(aKeySystem, aIsHardwareDecryption, implementations); bool containInitType = false; for (const auto& config : implementations) { if (config.mInitDataTypes.Contains(aInitDataType)) { @@ -352,15 +378,13 @@ static bool SupportsEncryptionScheme( static bool ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType) { - if (aSessionType.Equals(ToString(MediaKeySessionType::Temporary))) { - aOutType = MediaKeySessionType::Temporary; - return true; - } - if (aSessionType.Equals(ToString(MediaKeySessionType::Persistent_license))) { - aOutType = MediaKeySessionType::Persistent_license; - return true; + Maybe<MediaKeySessionType> type = + StringToEnum<MediaKeySessionType>(aSessionType); + if (type.isNothing()) { + return false; } - return false; + aOutType = type.value(); + return true; } // 5.1.1 Is persistent session type? @@ -395,6 +419,7 @@ static CodecType GetCodecType(const KeySystemConfig::EMECodecString& aCodec) { return Audio; } if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264) || + aCodec.Equals(KeySystemConfig::EME_CODEC_AV1) || aCodec.Equals(KeySystemConfig::EME_CODEC_VP8) || aCodec.Equals(KeySystemConfig::EME_CODEC_VP9) || aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) { @@ -1040,7 +1065,11 @@ bool MediaKeySystemAccess::GetSupportedConfig( DecoderDoctorDiagnostics* aDiagnostics, bool aIsPrivateBrowsing, const std::function<void(const char*)>& aDeprecationLogFn) { nsTArray<KeySystemConfig> implementations; - if (!GetKeySystemConfigs(aKeySystem, implementations)) { + const bool isHardwareDecryptionRequest = + CheckIfHarewareDRMConfigExists(aConfigs) || + DoesKeySystemSupportHardwareDecryption(aKeySystem); + if (!GetKeySystemConfigs(aKeySystem, isHardwareDecryptionRequest, + implementations)) { return false; } for (const auto& implementation : implementations) { @@ -1082,7 +1111,7 @@ static nsCString ToCString(const nsString& aString) { static nsCString ToCString(const MediaKeysRequirement aValue) { nsCString str("'"); - str.AppendASCII(MediaKeysRequirementValues::GetString(aValue)); + str.AppendASCII(GetEnumString(aValue)); str.AppendLiteral("'"); return str; } diff --git a/dom/media/eme/MediaKeySystemAccess.h b/dom/media/eme/MediaKeySystemAccess.h index 954ff7adf3..18eec47008 100644 --- a/dom/media/eme/MediaKeySystemAccess.h +++ b/dom/media/eme/MediaKeySystemAccess.h @@ -67,7 +67,8 @@ class MediaKeySystemAccess final : public nsISupports, public nsWrapperCache { const std::function<void(const char*)>& aDeprecationLogFn); static bool KeySystemSupportsInitDataType(const nsAString& aKeySystem, - const nsAString& aInitDataType); + const nsAString& aInitDataType, + bool aIsHardwareDecryption); static nsCString ToCString( const Sequence<MediaKeySystemConfiguration>& aConfig); diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp index 2bc12d57d7..8ebe7ceee7 100644 --- a/dom/media/eme/MediaKeySystemAccessManager.cpp +++ b/dom/media/eme/MediaKeySystemAccessManager.cpp @@ -412,8 +412,7 @@ void MediaKeySystemAccessManager::RequestMediaKeySystemAccess( "MediaKeySystemAccess::GetKeySystemStatus(%s) " "result=%s msg='%s'", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get(), - nsCString(MediaKeySystemStatusValues::GetString(status)).get(), - message.get()); + GetEnumString(status).get(), message.get()); LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg)); EME_LOG("%s", msg.get()); diff --git a/dom/media/eme/MediaKeys.cpp b/dom/media/eme/MediaKeys.cpp index c4340885a3..9a3f4bba7a 100644 --- a/dom/media/eme/MediaKeys.cpp +++ b/dom/media/eme/MediaKeys.cpp @@ -23,7 +23,6 @@ #include "mozilla/dom/UnionTypes.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowGlobalChild.h" -#include "nsContentCID.h" #include "nsContentTypeParser.h" #include "nsContentUtils.h" #include "nsIScriptObjectPrincipal.h" @@ -429,7 +428,8 @@ class MediaKeysGMPCrashHelper : public GMPCrashHelper { already_AddRefed<CDMProxy> MediaKeys::CreateCDMProxy() { const bool isHardwareDecryptionSupported = - IsHardwareDecryptionSupported(mConfig); + IsHardwareDecryptionSupported(mConfig) || + DoesKeySystemSupportHardwareDecryption(mKeySystem); EME_LOG("MediaKeys[%p]::CreateCDMProxy(), isHardwareDecryptionSupported=%d", this, isHardwareDecryptionSupported); RefPtr<CDMProxy> proxy; @@ -662,8 +662,12 @@ already_AddRefed<MediaKeySession> MediaKeys::CreateSession( EME_LOG("MediaKeys[%p] Creating session", this); - RefPtr<MediaKeySession> session = new MediaKeySession( - GetParentObject(), this, mKeySystem, aSessionType, aRv); + const bool isHardwareDecryption = + IsHardwareDecryptionSupported(mConfig) || + DoesKeySystemSupportHardwareDecryption(mKeySystem); + RefPtr<MediaKeySession> session = + new MediaKeySession(GetParentObject(), this, mKeySystem, aSessionType, + isHardwareDecryption, aRv); if (aRv.Failed()) { return nullptr; @@ -792,8 +796,7 @@ void MediaKeys::GetSessionsInfo(nsString& sessionsInfo) { sessionsInfo.AppendLiteral("(kid="); sessionsInfo.Append(keyID); sessionsInfo.AppendLiteral(" status="); - sessionsInfo.AppendASCII( - MediaKeyStatusValues::GetString(keyStatusMap->GetValueAtIndex(i))); + sessionsInfo.AppendASCII(GetEnumString(keyStatusMap->GetValueAtIndex(i))); sessionsInfo.AppendLiteral(")"); } sessionsInfo.AppendLiteral(")"); @@ -824,7 +827,7 @@ already_AddRefed<Promise> MediaKeys::GetStatusForPolicy( } EME_LOG("GetStatusForPolicy minHdcpVersion = %s.", - HDCPVersionValues::GetString(aPolicy.mMinHdcpVersion.Value()).data()); + GetEnumString(aPolicy.mMinHdcpVersion.Value()).get()); mProxy->GetStatusForPolicy(StorePromise(promise), aPolicy.mMinHdcpVersion.Value()); return promise.forget(); diff --git a/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp index e5431f50fd..f3cfb6c026 100644 --- a/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp +++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp @@ -9,7 +9,6 @@ #include "nsString.h" #include "mozilla/dom/MediaKeys.h" #include "mozilla/dom/MediaKeySession.h" -#include "nsContentCID.h" #include "nsServiceManagerUtils.h" #include "MainThreadUtils.h" #include "mozilla/EMEUtils.h" diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp index 1fe42aa8e2..add978f755 100644 --- a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp +++ b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp @@ -15,37 +15,8 @@ namespace mozilla { -/* static */ -bool WMFCDMImpl::Supports(const nsAString& aKeySystem) { - MOZ_ASSERT(NS_IsMainThread()); - if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { - return false; - } - - static std::map<nsString, bool> sSupports; - static bool sSetRunOnShutdown = false; - if (!sSetRunOnShutdown) { - GetMainThreadSerialEventTarget()->Dispatch( - NS_NewRunnableFunction("WMFCDMImpl::Supports", [&] { - RunOnShutdown([&] { sSupports.clear(); }, - ShutdownPhase::XPCOMShutdown); - })); - sSetRunOnShutdown = true; - } - - nsString key(aKeySystem); - if (const auto& s = sSupports.find(key); s != sSupports.end()) { - return s->second; - } - - RefPtr<WMFCDMImpl> cdm = MakeRefPtr<WMFCDMImpl>(aKeySystem); - nsTArray<KeySystemConfig> configs; - bool s = cdm->GetCapabilities(configs); - sSupports[key] = s; - return s; -} - -bool WMFCDMImpl::GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs) { +bool WMFCDMImpl::GetCapabilities(bool aIsHardwareDecryption, + nsTArray<KeySystemConfig>& aOutConfigs) { MOZ_ASSERT(NS_IsMainThread()); if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return false; @@ -67,13 +38,14 @@ bool WMFCDMImpl::GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs) { auto keySystem = std::string{NS_ConvertUTF16toUTF8(mKeySystem).get()}; if (auto rv = sKeySystemConfigs.find(keySystem); rv != sKeySystemConfigs.end()) { - EME_LOG("Return cached capabilities for %s", keySystem.c_str()); for (const auto& config : rv->second) { - aOutConfigs.AppendElement(config); - EME_LOG("-- capabilities (%s)", - NS_ConvertUTF16toUTF8(config.GetDebugInfo()).get()); + if (IsHardwareDecryptionSupported(config) == aIsHardwareDecryption) { + EME_LOG("Return cached capabilities for %s (%s)", keySystem.c_str(), + NS_ConvertUTF16toUTF8(config.GetDebugInfo()).get()); + aOutConfigs.AppendElement(config); + return true; + } } - return true; } // Not cached result, ask the remote process. @@ -83,42 +55,33 @@ bool WMFCDMImpl::GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs) { mCDM = MakeRefPtr<MFCDMChild>(mKeySystem); } bool ok = false; - static const bool sIsHwSecure[2] = {false, true}; - for (const auto& isHWSecure : sIsHwSecure) { - media::Await( - do_AddRef(backgroundTaskQueue), mCDM->GetCapabilities(isHWSecure), - [&ok, &aOutConfigs, keySystem, - isHWSecure](const MFCDMCapabilitiesIPDL& capabilities) { - EME_LOG("capabilities: keySystem=%s (hw-secure=%d)", - keySystem.c_str(), isHWSecure); - for (const auto& v : capabilities.videoCapabilities()) { - EME_LOG("capabilities: video=%s", - NS_ConvertUTF16toUTF8(v.contentType()).get()); - } - for (const auto& a : capabilities.audioCapabilities()) { - EME_LOG("capabilities: audio=%s", - NS_ConvertUTF16toUTF8(a.contentType()).get()); - } - for (const auto& v : capabilities.encryptionSchemes()) { - EME_LOG("capabilities: encryptionScheme=%s", - EncryptionSchemeStr(v)); - } - KeySystemConfig* config = aOutConfigs.AppendElement(); - MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, *config); - sKeySystemConfigs[keySystem].AppendElement(*config); - // This is equal to "com.microsoft.playready.recommendation.3000", so - // we can store it directly without asking the remote process again. - if (keySystem.compare(kPlayReadyKeySystemName) == 0 && isHWSecure) { - config->mKeySystem.AssignLiteral(kPlayReadyKeySystemHardware); - sKeySystemConfigs["com.microsoft.playready.recommendation.3000"] - .AppendElement(*config); - } - ok = true; - }, - [](nsresult rv) { - EME_LOG("Fail to get key system capabilities. rv=%x", uint32_t(rv)); - }); - } + media::Await( + do_AddRef(backgroundTaskQueue), + mCDM->GetCapabilities(aIsHardwareDecryption), + [&ok, &aOutConfigs, keySystem, + aIsHardwareDecryption](const MFCDMCapabilitiesIPDL& capabilities) { + EME_LOG("capabilities: keySystem=%s (hw-secure=%d)", keySystem.c_str(), + aIsHardwareDecryption); + for (const auto& v : capabilities.videoCapabilities()) { + EME_LOG("capabilities: video=%s", + NS_ConvertUTF16toUTF8(v.contentType()).get()); + } + for (const auto& a : capabilities.audioCapabilities()) { + EME_LOG("capabilities: audio=%s", + NS_ConvertUTF16toUTF8(a.contentType()).get()); + } + for (const auto& v : capabilities.encryptionSchemes()) { + EME_LOG("capabilities: encryptionScheme=%s", EncryptionSchemeStr(v)); + } + KeySystemConfig* config = aOutConfigs.AppendElement(); + MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, *config); + sKeySystemConfigs[keySystem].AppendElement(*config); + ok = true; + }, + [](nsresult rv) { + EME_LOG("Fail to get key system capabilities. rv=%x", uint32_t(rv)); + }); + return ok; } diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.h b/dom/media/eme/mediafoundation/WMFCDMImpl.h index 452629ec84..b7e6308848 100644 --- a/dom/media/eme/mediafoundation/WMFCDMImpl.h +++ b/dom/media/eme/mediafoundation/WMFCDMImpl.h @@ -34,9 +34,9 @@ class WMFCDMImpl final { explicit WMFCDMImpl(const nsAString& aKeySystem) : mKeySystem(aKeySystem) {} - static bool Supports(const nsAString& aKeySystem); // TODO: make this async? - bool GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs); + bool GetCapabilities(bool aIsHardwareDecryption, + nsTArray<KeySystemConfig>& aOutConfigs); using InitPromise = GenericPromise; struct InitParams { diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp index 21207ecc22..f7e05dfb6a 100644 --- a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp +++ b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp @@ -381,8 +381,7 @@ void WMFCDMProxy::GetStatusForPolicy(PromiseId aPromiseId, RETURN_IF_SHUTDOWN(); EME_LOG("WMFCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32 ", minHDCP=%s)", - this, aPromiseId, - dom::HDCPVersionValues::GetString(aMinHdcpVersion).data()); + this, aPromiseId, dom::GetEnumString(aMinHdcpVersion).get()); mCDM->GetStatusForPolicy(aPromiseId, aMinHdcpVersion) ->Then( mMainThread, __func__, diff --git a/dom/media/flac/FlacDecoder.cpp b/dom/media/flac/FlacDecoder.cpp index 2f205c9aae..07d9f10983 100644 --- a/dom/media/flac/FlacDecoder.cpp +++ b/dom/media/flac/FlacDecoder.cpp @@ -11,13 +11,7 @@ namespace mozilla { /* static */ -bool FlacDecoder::IsEnabled() { -#ifdef MOZ_FFVPX - return StaticPrefs::media_flac_enabled(); -#else - return false; -#endif -} +bool FlacDecoder::IsEnabled() { return StaticPrefs::media_flac_enabled(); } /* static */ bool FlacDecoder::IsSupportedType(const MediaContainerType& aContainerType) { diff --git a/dom/media/gmp/ChromiumCDMProxy.cpp b/dom/media/gmp/ChromiumCDMProxy.cpp index e8d871a113..566b386b0b 100644 --- a/dom/media/gmp/ChromiumCDMProxy.cpp +++ b/dom/media/gmp/ChromiumCDMProxy.cpp @@ -605,8 +605,7 @@ void ChromiumCDMProxy::GetStatusForPolicy( MOZ_ASSERT(NS_IsMainThread()); EME_LOG("ChromiumCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32 ") minHdcpVersion=%s", - this, aPromiseId, - dom::HDCPVersionValues::GetString(aMinHdcpVersion).data()); + this, aPromiseId, dom::GetEnumString(aMinHdcpVersion).get()); RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent(); if (!cdm) { diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp index 1eea8b108b..d543d46387 100644 --- a/dom/media/gmp/GMPChild.cpp +++ b/dom/media/gmp/GMPChild.cpp @@ -237,16 +237,15 @@ mozilla::ipc::IPCResult GMPChild::RecvPreloadLibs(const nsCString& aLibs) { bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) { nsCOMPtr<nsIFile> libFile; -#define GMP_PATH_CRASH(explain) \ - do { \ - nsAutoString path; \ - if (!libFile || NS_FAILED(libFile->GetPath(path))) { \ - path = mPluginPath; \ - } \ - CrashReporter::AnnotateCrashReport( \ - CrashReporter::Annotation::GMPLibraryPath, \ - NS_ConvertUTF16toUTF8(path)); \ - MOZ_CRASH(explain); \ +#define GMP_PATH_CRASH(explain) \ + do { \ + nsAutoString path; \ + if (!libFile || NS_FAILED(libFile->GetPath(path))) { \ + path = mPluginPath; \ + } \ + CrashReporter::RecordAnnotationNSString( \ + CrashReporter::Annotation::GMPLibraryPath, path); \ + MOZ_CRASH(explain); \ } while (false) nsresult rv = NS_NewLocalFile(mPluginPath, true, getter_AddRefs(libFile)); @@ -514,7 +513,7 @@ mozilla::ipc::IPCResult GMPChild::RecvStartPlugin(const nsString& aAdapter) { nsAutoCString libPath; if (!GetUTF8LibPath(libPath)) { - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationNSCString( CrashReporter::Annotation::GMPLibraryPath, NS_ConvertUTF16toUTF8(mPluginPath)); @@ -556,7 +555,7 @@ mozilla::ipc::IPCResult GMPChild::RecvStartPlugin(const nsString& aAdapter) { NS_WARNING("Failed to load GMP"); #endif delete platformAPI; - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationNSCString( CrashReporter::Annotation::GMPLibraryPath, NS_ConvertUTF16toUTF8(mPluginPath)); @@ -727,21 +726,21 @@ mozilla::ipc::IPCResult GMPChild::RecvShutdown(ShutdownResolver&& aResolver) { } const bool isProfiling = profiler_is_active(); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - GrabShutdownProfileAndShutdown"_ns - : "Not profiling - GrabShutdownProfileAndShutdown"_ns); + isProfiling ? "Profiling - GrabShutdownProfileAndShutdown" + : "Not profiling - GrabShutdownProfileAndShutdown"); ProfileAndAdditionalInformation shutdownProfileAndAdditionalInformation = mProfilerController->GrabShutdownProfileAndShutdown(); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - Destroying ChildProfilerController"_ns - : "Not profiling - Destroying ChildProfilerController"_ns); + isProfiling ? "Profiling - Destroying ChildProfilerController" + : "Not profiling - Destroying ChildProfilerController"); mProfilerController = nullptr; - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - SendShutdownProfile (resovling)"_ns - : "Not profiling - SendShutdownProfile (resolving)"_ns); + isProfiling ? "Profiling - SendShutdownProfile (resovling)" + : "Not profiling - SendShutdownProfile (resolving)"); if (const size_t len = shutdownProfileAndAdditionalInformation.SizeOf(); len >= size_t(IPC::Channel::kMaximumMessageSize)) { shutdownProfileAndAdditionalInformation.mProfile = @@ -752,10 +751,10 @@ mozilla::ipc::IPCResult GMPChild::RecvShutdown(ShutdownResolver&& aResolver) { // Send the shutdown profile to the parent process through our own // message channel, which we know will survive for long enough. aResolver(shutdownProfileAndAdditionalInformation.mProfile); - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::ProfilerChildShutdownPhase, - isProfiling ? "Profiling - SendShutdownProfile (resolved)"_ns - : "Not profiling - SendShutdownProfile (resolved)"_ns); + isProfiling ? "Profiling - SendShutdownProfile (resolved)" + : "Not profiling - SendShutdownProfile (resolved)"); return IPC_OK(); } diff --git a/dom/media/gmp/GMPDiskStorage.cpp b/dom/media/gmp/GMPDiskStorage.cpp index 5f7a023b9b..c1c0c8215a 100644 --- a/dom/media/gmp/GMPDiskStorage.cpp +++ b/dom/media/gmp/GMPDiskStorage.cpp @@ -13,7 +13,6 @@ #include "mozilla/EndianUtils.h" #include "nsClassHashtable.h" #include "prio.h" -#include "nsContentCID.h" #include "nsServiceManagerUtils.h" namespace mozilla::gmp { diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp index 9fa7756165..c0c9364c7b 100644 --- a/dom/media/gmp/GMPLoader.cpp +++ b/dom/media/gmp/GMPLoader.cpp @@ -84,7 +84,7 @@ class PassThroughGMPAdapter : public GMPAdapter { bool GMPLoader::Load(const char* aUTF8LibPath, uint32_t aUTF8LibPathLen, const GMPPlatformAPI* aPlatformAPI, GMPAdapter* aAdapter) { - CrashReporter::AutoAnnotateCrashReport autoLibPath( + CrashReporter::AutoRecordAnnotation autoLibPath( CrashReporter::Annotation::GMPLibraryPath, nsDependentCString(aUTF8LibPath)); diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp index 115e9b7392..0830313be9 100644 --- a/dom/media/gmp/GMPParent.cpp +++ b/dom/media/gmp/GMPParent.cpp @@ -770,13 +770,15 @@ bool GMPParent::EnsureProcessLoaded() { void GMPParent::AddCrashAnnotations() { if (mCrashReporter) { - mCrashReporter->AddAnnotation(CrashReporter::Annotation::GMPPlugin, true); - mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename, - NS_ConvertUTF16toUTF8(mName)); - mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName, - mDisplayName); - mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion, - mVersion); + mCrashReporter->AddAnnotationBool(CrashReporter::Annotation::GMPPlugin, + true); + mCrashReporter->AddAnnotationNSCString( + CrashReporter::Annotation::PluginFilename, + NS_ConvertUTF16toUTF8(mName)); + mCrashReporter->AddAnnotationNSCString( + CrashReporter::Annotation::PluginName, mDisplayName); + mCrashReporter->AddAnnotationNSCString( + CrashReporter::Annotation::PluginVersion, mVersion); } } diff --git a/dom/media/gmp/moz.build b/dom/media/gmp/moz.build index a2b1be5ed8..3c34021506 100644 --- a/dom/media/gmp/moz.build +++ b/dom/media/gmp/moz.build @@ -126,7 +126,7 @@ PREPROCESSED_IPDL_SOURCES += [ "PGMPContent.ipdl", ] -if CONFIG["OS_TARGET"] in ["WINNT", "Darwin"]: +if CONFIG["TARGET_OS"] in ["WINNT", "OSX"]: DEFINES["SUPPORT_STORAGE_ID"] = 1 include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/media/gmp/rlz/moz.build b/dom/media/gmp/rlz/moz.build index 8e2d9ea5d1..a874d54c65 100644 --- a/dom/media/gmp/rlz/moz.build +++ b/dom/media/gmp/rlz/moz.build @@ -9,19 +9,19 @@ FINAL_LIBRARY = 'xul' -if CONFIG['OS_TARGET'] in ['WINNT', 'Darwin']: +if CONFIG['TARGET_OS'] in ['WINNT', 'OSX']: UNIFIED_SOURCES += [ 'lib/crc8.cc', 'lib/machine_id.cc', 'lib/string_utils.cc', ] -if CONFIG['OS_TARGET'] == 'WINNT': +if CONFIG['TARGET_OS'] == 'WINNT': UNIFIED_SOURCES += [ 'win/lib/machine_id_win.cc', ] -if CONFIG['OS_TARGET'] == 'Darwin': +if CONFIG['TARGET_OS'] == 'OSX': UNIFIED_SOURCES += [ 'mac/lib/machine_id_mac.cc', ] diff --git a/dom/media/gtest/TestAudioSampleFormat.cpp b/dom/media/gtest/TestAudioSampleFormat.cpp new file mode 100644 index 0000000000..4737a73a23 --- /dev/null +++ b/dom/media/gtest/TestAudioSampleFormat.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AudioSampleFormat.h" +#include "gtest/gtest.h" +#include <type_traits> + +using namespace mozilla; + +template <typename T> +constexpr T LowestSample() { + if constexpr (std::is_integral_v<T>) { + return std::numeric_limits<T>::lowest(); + } else { + return -1.0f; + } +} + +// When converting a sample-type to another sample-type, this returns the +// maximum value possible in the destination format +template <typename Dest> +constexpr Dest HighestSample() { + if constexpr (std::is_integral_v<Dest>) { + return std::numeric_limits<Dest>::max(); + } else { + return +1.0f; + } +} + +// When converting a sample-type to another sample-type, this returns the +// maximum value expected in the destination format +template <typename Dest, typename Source> +constexpr Dest HighestSampleExpected() { + // When converting small integer samples to large integer sample, the higher + // bound isn't reached because of positive / negative integer assymetry. + if constexpr (std::is_same_v<Source, uint8_t> && + std::is_same_v<Dest, int16_t>) { + return 32512; // INT16_MAX - 2 << 7 + 1 + } else if constexpr (std::is_same_v<Source, uint8_t> && + std::is_same_v<Dest, int32_t>) { + return 2130706432; // INT32_MAX - (2 << 23) + 1 + } else if constexpr (std::is_same_v<Source, int16_t> && + std::is_same_v<Dest, int32_t>) { + return 2147418112; // INT32_MAX - UINT16_MAX + } + + if constexpr (std::is_integral_v<Dest>) { + return std::numeric_limits<Dest>::max(); + } else { + return +1.0f; + } +} + +template <typename Source, typename Dest> +void TestSampleTypePair() { + std::cout << __PRETTY_FUNCTION__ << std::endl; + + ASSERT_EQ(LowestSample<Dest>(), + ConvertAudioSample<Dest>(LowestSample<Source>())); + Dest expected = HighestSampleExpected<Dest, Source>(); + ASSERT_EQ(expected, ConvertAudioSample<Dest>(HighestSample<Source>())); + ASSERT_EQ(Bias<Dest>(), ConvertAudioSample<Dest>(Bias<Source>())); +} + +template <typename T> +void TestSampleType24bits() { + std::cout << __PRETTY_FUNCTION__ << std::endl; + + int32_t max_sample_24bits = (2 << 22) - 1; + int32_t min_sample_24bits = -(2 << 22); + int32_t silence_24bits = 0; + + ASSERT_EQ(LowestSample<T>(), Int24ToAudioSample<T>(min_sample_24bits)); + ASSERT_EQ(Int24ToAudioSample<T>(min_sample_24bits), LowestSample<T>()); + if constexpr (std::is_same_v<T, int32_t>) { + // Quantization issue: 2147483392 + (2<<8 - 1) == INT32_MAX + // See comment on HighestSampleExpected above + const int32_t HIGHEST_FROM_24BITS = 2147483392; + ASSERT_EQ(HIGHEST_FROM_24BITS, Int24ToAudioSample<T>(max_sample_24bits)); + ASSERT_EQ(Int24ToAudioSample<T>(max_sample_24bits), HIGHEST_FROM_24BITS); + } else { + ASSERT_EQ(HighestSample<T>(), Int24ToAudioSample<T>(max_sample_24bits)); + ASSERT_EQ(Int24ToAudioSample<T>(max_sample_24bits), HighestSample<T>()); + } + ASSERT_EQ(Bias<T>(), Int24ToAudioSample<T>(silence_24bits)); + ASSERT_EQ(Int24ToAudioSample<T>(silence_24bits), Bias<T>()); +} + +TEST(AudioSampleFormat, Boundaries) +{ + TestSampleTypePair<uint8_t, uint8_t>(); + TestSampleTypePair<uint8_t, int16_t>(); + TestSampleTypePair<uint8_t, int32_t>(); + TestSampleTypePair<uint8_t, float>(); + TestSampleTypePair<int16_t, uint8_t>(); + TestSampleTypePair<int16_t, int16_t>(); + TestSampleTypePair<int16_t, int32_t>(); + TestSampleTypePair<int16_t, float>(); + TestSampleTypePair<int32_t, uint8_t>(); + TestSampleTypePair<int32_t, int16_t>(); + TestSampleTypePair<int32_t, int32_t>(); + TestSampleTypePair<int32_t, float>(); + TestSampleTypePair<float, uint8_t>(); + TestSampleTypePair<float, int16_t>(); + TestSampleTypePair<float, int32_t>(); + TestSampleTypePair<float, float>(); + + // Separately test 24-bit audio stored in 32-bits integers. + TestSampleType24bits<uint8_t>(); + TestSampleType24bits<int16_t>(); + TestSampleType24bits<int32_t>(); + TestSampleType24bits<float>(); +} diff --git a/dom/media/gtest/TestAudioSegment.cpp b/dom/media/gtest/TestAudioSegment.cpp index ee44839283..ea0e43f0cd 100644 --- a/dom/media/gtest/TestAudioSegment.cpp +++ b/dom/media/gtest/TestAudioSegment.cpp @@ -31,7 +31,7 @@ float GetLowValue<float>() { template <> int16_t GetLowValue<short>() { - return -INT16_MAX; + return INT16_MIN; } template <> @@ -62,7 +62,7 @@ const T* const* GetPlanarChannelArray(size_t aChannels, size_t aSize) { for (size_t c = 0; c < aChannels; c++) { channels[c] = new T[aSize]; for (size_t i = 0; i < aSize; i++) { - channels[c][i] = FloatToAudioSample<T>(1. / (c + 1)); + channels[c][i] = ConvertAudioSample<T>(1.f / static_cast<float>(c + 1)); } } return channels; @@ -104,7 +104,7 @@ const T* GetInterleavedChannelArray(size_t aChannels, size_t aSize) { T* samples = new T[sampleCount]; for (size_t i = 0; i < sampleCount; i++) { uint32_t channel = (i % aChannels) + 1; - samples[i] = FloatToAudioSample<T>(1. / channel); + samples[i] = ConvertAudioSample<T>(1.f / static_cast<float>(channel)); } return samples; } @@ -128,8 +128,9 @@ void TestInterleaveAndConvert() { uint32_t channelIndex = 0; for (size_t i = 0; i < arraySize * channels; i++) { - ASSERT_TRUE(FuzzyEqual( - dst[i], FloatToAudioSample<DstT>(1. / (channelIndex + 1)))); + ASSERT_TRUE( + FuzzyEqual(dst[i], ConvertAudioSample<DstT>( + 1.f / static_cast<float>(channelIndex + 1)))); channelIndex++; channelIndex %= channels; } @@ -151,8 +152,9 @@ void TestDeinterleaveAndConvert() { for (size_t channel = 0; channel < channels; channel++) { for (size_t i = 0; i < arraySize; i++) { - ASSERT_TRUE(FuzzyEqual(dst[channel][i], - FloatToAudioSample<DstT>(1. / (channel + 1)))); + ASSERT_TRUE(FuzzyEqual( + dst[channel][i], + ConvertAudioSample<DstT>(1.f / static_cast<float>(channel + 1)))); } } diff --git a/dom/media/gtest/TestAudioTrackGraph.cpp b/dom/media/gtest/TestAudioTrackGraph.cpp index 457c50e731..1bd255bed1 100644 --- a/dom/media/gtest/TestAudioTrackGraph.cpp +++ b/dom/media/gtest/TestAudioTrackGraph.cpp @@ -1462,7 +1462,7 @@ float rmsf32(AudioDataValue* aSamples, uint32_t aChannels, uint32_t aFrames) { for (uint32_t i = 0; i < aFrames; i++) { downmixed = 0.; for (uint32_t j = 0; j < aChannels; j++) { - downmixed += AudioSampleToFloat(aSamples[readIdx++]); + downmixed += ConvertAudioSample<float>(aSamples[readIdx++]); } rms += downmixed * downmixed; } diff --git a/dom/media/gtest/TestMediaDataDecoder.cpp b/dom/media/gtest/TestMediaDataDecoder.cpp index 79a92842b6..820b15b718 100644 --- a/dom/media/gtest/TestMediaDataDecoder.cpp +++ b/dom/media/gtest/TestMediaDataDecoder.cpp @@ -63,8 +63,7 @@ TEST(MediaDataDecoder, H264) } // Decoding AV1 via. ffvpx is supported on Linux only. -#if defined(MOZ_AV1) && defined(MOZ_WIDGET_GTK) && defined(MOZ_FFVPX) && \ - !defined(MOZ_FFVPX_AUDIOONLY) +#if defined(MOZ_AV1) && defined(MOZ_WIDGET_GTK) && !defined(MOZ_FFVPX_AUDIOONLY) TEST(MediaDataDecoder, AV1) { if (!MP4Decoder::IsSupportedType(MediaContainerType(MEDIAMIMETYPE(VIDEO_MP4)), diff --git a/dom/media/gtest/TestMediaDataEncoder.cpp b/dom/media/gtest/TestMediaDataEncoder.cpp index bdab94cfe5..27a6b7cd07 100644 --- a/dom/media/gtest/TestMediaDataEncoder.cpp +++ b/dom/media/gtest/TestMediaDataEncoder.cpp @@ -382,8 +382,7 @@ TEST_F(MediaDataEncoderTest, AndroidNotSupportedSize) { } #endif -#if defined(XP_LINUX) && !defined(ANDROID) && \ - (defined(MOZ_FFMPEG) || defined(MOZ_FFVPX)) +#if defined(XP_LINUX) && !defined(ANDROID) TEST_F(MediaDataEncoderTest, H264AVCC) { RUN_IF_SUPPORTED(CodecType::H264, [this]() { // Encod frames in avcC format. @@ -508,8 +507,7 @@ TEST_F(MediaDataEncoderTest, VP8Duration) { }); } -#if defined(XP_LINUX) && !defined(ANDROID) && \ - (defined(MOZ_FFMPEG) || defined(MOZ_FFVPX)) +#if defined(XP_LINUX) && !defined(ANDROID) TEST_F(MediaDataEncoderTest, VP8EncodeAfterDrain) { RUN_IF_SUPPORTED(CodecType::VP8, [this]() { RefPtr<MediaDataEncoder> e = CreateVP8Encoder(); @@ -673,8 +671,7 @@ TEST_F(MediaDataEncoderTest, VP9Duration) { }); } -#if defined(XP_LINUX) && !defined(ANDROID) && \ - (defined(MOZ_FFMPEG) || defined(MOZ_FFVPX)) +#if defined(XP_LINUX) && !defined(ANDROID) TEST_F(MediaDataEncoderTest, VP9EncodeAfterDrain) { RUN_IF_SUPPORTED(CodecType::VP9, [this]() { RefPtr<MediaDataEncoder> e = CreateVP9Encoder(); diff --git a/dom/media/gtest/TestMediaQueue.cpp b/dom/media/gtest/TestMediaQueue.cpp index 5b049dc7fe..7176de069f 100644 --- a/dom/media/gtest/TestMediaQueue.cpp +++ b/dom/media/gtest/TestMediaQueue.cpp @@ -6,6 +6,7 @@ #include "MediaData.h" #include "MediaQueue.h" +#include "nsISupportsImpl.h" using namespace mozilla; using mozilla::media::TimeUnit; @@ -285,4 +286,19 @@ TEST(MediaQueue, TimestampAdjustmentForNotSupportDataType) EXPECT_EQ(data->GetEndTime(), TimeUnit::FromMicroseconds(10)); } +TEST(MediaQueue, PreciseDuration) +{ + MediaQueue<MediaData> queueOff; + queueOff.Push(CreateDataRawPtr(5, 10)); + queueOff.Push(CreateDataRawPtr(0, 5)); + EXPECT_EQ(queueOff.Duration(), 0); + EXPECT_EQ(queueOff.PreciseDuration(), -1); + + MediaQueue<MediaData> queueOn(true /* aEnablePreciseDuration */); + queueOn.Push(CreateDataRawPtr(5, 10)); + queueOn.Push(CreateDataRawPtr(0, 5)); + EXPECT_EQ(queueOn.Duration(), 0); + EXPECT_EQ(queueOn.PreciseDuration(), 10); +} + #undef EXPECT_EQUAL_SIZE_T diff --git a/dom/media/gtest/TestMediaUtils.cpp b/dom/media/gtest/TestMediaUtils.cpp index 33a32b7ea0..3708f43f01 100644 --- a/dom/media/gtest/TestMediaUtils.cpp +++ b/dom/media/gtest/TestMediaUtils.cpp @@ -15,7 +15,7 @@ using namespace mozilla::gtest; using namespace mozilla::media; // Spawning the death test child process aborts on Android. -#if !defined(ANDROID) +#if !defined(ANDROID) && defined(GTEST_HAS_DEATH_TEST) // Kept here for reference as it can be handy during development. # define DISABLE_CRASH_REPORTING \ diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build index 581be004ef..df67aeef18 100644 --- a/dom/media/gtest/moz.build +++ b/dom/media/gtest/moz.build @@ -31,6 +31,7 @@ UNIFIED_SOURCES += [ "TestAudioMixer.cpp", "TestAudioPacketizer.cpp", "TestAudioRingBuffer.cpp", + "TestAudioSampleFormat.cpp", "TestAudioSegment.cpp", "TestAudioSinkWrapper.cpp", "TestAudioTrackEncoder.cpp", diff --git a/dom/media/ipc/MFCDMSerializers.h b/dom/media/ipc/MFCDMSerializers.h index 587f30ae09..64867f4dbb 100644 --- a/dom/media/ipc/MFCDMSerializers.h +++ b/dom/media/ipc/MFCDMSerializers.h @@ -5,9 +5,9 @@ #ifndef DOM_MEDIA_IPC_MFCDMSERIALIZERS_H_ #define DOM_MEDIA_IPC_MFCDMSERIALIZERS_H_ -#include "ipc/EnumSerializer.h" #include "MediaData.h" #include "mozilla/KeySystemConfig.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/MediaKeyMessageEventBinding.h" #include "mozilla/dom/MediaKeyStatusMapBinding.h" @@ -35,23 +35,17 @@ struct ParamTraits<mozilla::CryptoScheme> template <> struct ParamTraits<mozilla::dom::MediaKeyMessageType> - : public ContiguousEnumSerializer< - mozilla::dom::MediaKeyMessageType, - mozilla::dom::MediaKeyMessageType::License_request, - mozilla::dom::MediaKeyMessageType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::MediaKeyMessageType> {}; template <> struct ParamTraits<mozilla::dom::MediaKeyStatus> - : public ContiguousEnumSerializer<mozilla::dom::MediaKeyStatus, - mozilla::dom::MediaKeyStatus::Usable, - mozilla::dom::MediaKeyStatus::EndGuard_> { + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::MediaKeyStatus> { }; template <> struct ParamTraits<mozilla::dom::HDCPVersion> - : public ContiguousEnumSerializer<mozilla::dom::HDCPVersion, - mozilla::dom::HDCPVersion::_1_0, - mozilla::dom::HDCPVersion::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::HDCPVersion> {}; } // namespace IPC diff --git a/dom/media/ipc/MFMediaEngineParent.cpp b/dom/media/ipc/MFMediaEngineParent.cpp index 0238956781..5ed1b71160 100644 --- a/dom/media/ipc/MFMediaEngineParent.cpp +++ b/dom/media/ipc/MFMediaEngineParent.cpp @@ -113,7 +113,6 @@ void MFMediaEngineParent::DestroyEngineIfExists( mRequestSampleListener.DisconnectIfExists(); if (mDXGIDeviceManager) { mDXGIDeviceManager = nullptr; - wmf::MFUnlockDXGIDeviceManager(); } if (aError) { Unused << SendNotifyError(*aError); @@ -191,6 +190,9 @@ void MFMediaEngineParent::InitializeDXGIDeviceManager() { UINT deviceResetToken; RETURN_VOID_IF_FAILED( wmf::MFLockDXGIDeviceManager(&deviceResetToken, &mDXGIDeviceManager)); + if (!mDXGIDeviceManager) { + return; + } RETURN_VOID_IF_FAILED( mDXGIDeviceManager->ResetDevice(d3d11Device.get(), deviceResetToken)); LOG("Initialized DXGI manager"); diff --git a/dom/media/ipc/RDDProcessHost.cpp b/dom/media/ipc/RDDProcessHost.cpp index 0c0d35e7b5..d855b7804b 100644 --- a/dom/media/ipc/RDDProcessHost.cpp +++ b/dom/media/ipc/RDDProcessHost.cpp @@ -53,10 +53,6 @@ bool RDDProcessHost::Launch(StringVector aExtraOpts) { } mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts); -#if defined(XP_WIN) && defined(MOZ_SANDBOX) - mSandboxLevel = Preferences::GetInt("security.sandbox.rdd.level"); -#endif - mLaunchPhase = LaunchPhase::Waiting; mLaunchTime = TimeStamp::Now(); diff --git a/dom/media/ipc/RDDProcessManager.cpp b/dom/media/ipc/RDDProcessManager.cpp index e7da3c3569..cb51b68282 100644 --- a/dom/media/ipc/RDDProcessManager.cpp +++ b/dom/media/ipc/RDDProcessManager.cpp @@ -170,8 +170,8 @@ RefPtr<GenericNonExclusivePromise> RDDProcessManager::LaunchRDDProcess() { } mQueuedPrefs.Clear(); - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::RDDProcessStatus, "Running"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::RDDProcessStatus, "Running"); if (!CreateVideoBridge()) { mNumProcessAttempts++; @@ -271,8 +271,8 @@ void RDDProcessManager::DestroyProcess() { mRDDChild = nullptr; mQueuedPrefs.Clear(); - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::RDDProcessStatus, "Destroyed"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::RDDProcessStatus, "Destroyed"); } bool RDDProcessManager::CreateContentBridge( diff --git a/dom/media/ipc/RemoteDecoderManagerChild.cpp b/dom/media/ipc/RemoteDecoderManagerChild.cpp index 48da254f39..b0b075e4d2 100644 --- a/dom/media/ipc/RemoteDecoderManagerChild.cpp +++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp @@ -50,8 +50,8 @@ using namespace gfx; // Used so that we only ever attempt to check if the RDD/GPU/Utility processes // should be launched serially. Protects sLaunchPromise StaticMutex sLaunchMutex; -static EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, - StaticRefPtr<GenericNonExclusivePromise>> +static EnumeratedArray<RemoteDecodeIn, StaticRefPtr<GenericNonExclusivePromise>, + size_t(RemoteDecodeIn::SENTINEL)> sLaunchPromises MOZ_GUARDED_BY(sLaunchMutex); // Only modified on the main-thread, read on any thread. While it could be read @@ -61,8 +61,8 @@ static StaticDataMutex<StaticRefPtr<nsIThread>> sRemoteDecoderManagerChildThread("sRemoteDecoderManagerChildThread"); // Only accessed from sRemoteDecoderManagerChildThread -static EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, - StaticRefPtr<RemoteDecoderManagerChild>> +static EnumeratedArray<RemoteDecodeIn, StaticRefPtr<RemoteDecoderManagerChild>, + size_t(RemoteDecodeIn::SENTINEL)> sRemoteDecoderManagerChildForProcesses; static StaticAutoPtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks; @@ -70,8 +70,8 @@ static StaticAutoPtr<nsTArray<RefPtr<Runnable>>> sRecreateTasks; // Used for protecting codec support information collected from different remote // processes. StaticMutex sProcessSupportedMutex; -static EnumeratedArray<RemoteDecodeIn, RemoteDecodeIn::SENTINEL, - Maybe<media::MediaCodecsSupported>> +static EnumeratedArray<RemoteDecodeIn, Maybe<media::MediaCodecsSupported>, + size_t(RemoteDecodeIn::SENTINEL)> sProcessSupported MOZ_GUARDED_BY(sProcessSupportedMutex); class ShutdownObserver final : public nsIObserver { @@ -311,6 +311,16 @@ RemoteDecoderManagerChild::CreateAudioDecoder( __func__); } + if (!aParams.mMediaEngineId && + aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, + nsPrintfCString("%s only support for media engine playback", + RemoteDecodeInToStr(aLocation)) + .get()), + __func__); + } + RefPtr<GenericNonExclusivePromise> launchPromise; if (StaticPrefs::media_utility_process_enabled() && (aLocation == RemoteDecodeIn::UtilityProcess_Generic || @@ -385,6 +395,16 @@ RemoteDecoderManagerChild::CreateVideoDecoder( __func__); } + if (!aParams.mMediaEngineId && + aLocation == RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM) { + return PlatformDecoderModule::CreateDecoderPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, + nsPrintfCString("%s only support for media engine playback", + RemoteDecodeInToStr(aLocation)) + .get()), + __func__); + } + MOZ_ASSERT(aLocation != RemoteDecodeIn::Unspecified); RefPtr<GenericNonExclusivePromise> p; diff --git a/dom/media/mediacapabilities/MediaCapabilities.cpp b/dom/media/mediacapabilities/MediaCapabilities.cpp index abc83dae4d..d3123f913b 100644 --- a/dom/media/mediacapabilities/MediaCapabilities.cpp +++ b/dom/media/mediacapabilities/MediaCapabilities.cpp @@ -45,21 +45,6 @@ static nsCString VideoConfigurationToStr(const VideoConfiguration* aConfig) { return nsCString(); } - nsCString hdrMetaType( - aConfig->mHdrMetadataType.WasPassed() - ? HdrMetadataTypeValues::GetString(aConfig->mHdrMetadataType.Value()) - : "?"); - - nsCString colorGamut( - aConfig->mColorGamut.WasPassed() - ? ColorGamutValues::GetString(aConfig->mColorGamut.Value()) - : "?"); - - nsCString transferFunction(aConfig->mTransferFunction.WasPassed() - ? TransferFunctionValues::GetString( - aConfig->mTransferFunction.Value()) - : "?"); - auto str = nsPrintfCString( "[contentType:%s width:%d height:%d bitrate:%" PRIu64 " framerate:%lf hasAlphaChannel:%s hdrMetadataType:%s colorGamut:%s " @@ -69,7 +54,15 @@ static nsCString VideoConfigurationToStr(const VideoConfiguration* aConfig) { aConfig->mHasAlphaChannel.WasPassed() ? aConfig->mHasAlphaChannel.Value() ? "true" : "false" : "?", - hdrMetaType.get(), colorGamut.get(), transferFunction.get(), + aConfig->mHdrMetadataType.WasPassed() + ? GetEnumString(aConfig->mHdrMetadataType.Value()).get() + : "?", + aConfig->mColorGamut.WasPassed() + ? GetEnumString(aConfig->mColorGamut.Value()).get() + : "?", + aConfig->mTransferFunction.WasPassed() + ? GetEnumString(aConfig->mTransferFunction.Value()).get() + : "?", aConfig->mScalabilityMode.WasPassed() ? NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.Value()).get() : "?"); diff --git a/dom/media/mediacontrol/ContentMediaController.cpp b/dom/media/mediacontrol/ContentMediaController.cpp index 1171785fe4..c0b466ff0f 100644 --- a/dom/media/mediacontrol/ContentMediaController.cpp +++ b/dom/media/mediacontrol/ContentMediaController.cpp @@ -286,8 +286,8 @@ void ContentMediaAgent::NotifyMediaFullScreenState(uint64_t aBrowsingContextId, } } -void ContentMediaAgent::UpdatePositionState(uint64_t aBrowsingContextId, - const PositionState& aState) { +void ContentMediaAgent::UpdatePositionState( + uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) { RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); if (!bc || bc->IsDiscarded()) { return; diff --git a/dom/media/mediacontrol/ContentMediaController.h b/dom/media/mediacontrol/ContentMediaController.h index 9e162dbb27..a58be24b9d 100644 --- a/dom/media/mediacontrol/ContentMediaController.h +++ b/dom/media/mediacontrol/ContentMediaController.h @@ -66,7 +66,7 @@ class ContentMediaAgent : public IMediaInfoUpdater { void NotifyMediaFullScreenState(uint64_t aBrowsingContextId, bool aIsInFullScreen) override; void UpdatePositionState(uint64_t aBrowsingContextId, - const PositionState& aState) override; + const Maybe<PositionState>& aState) override; // Use these methods to register/unregister `ContentMediaControlKeyReceiver` // in order to listen to media control key events. diff --git a/dom/media/mediacontrol/ContentPlaybackController.cpp b/dom/media/mediacontrol/ContentPlaybackController.cpp index ba48c0a5ce..fcc8e3ab58 100644 --- a/dom/media/mediacontrol/ContentPlaybackController.cpp +++ b/dom/media/mediacontrol/ContentPlaybackController.cpp @@ -168,8 +168,12 @@ void ContentMediaControlKeyHandler::HandleMediaControlAction( if (!aContext->GetDocShell()) { return; } + if (aAction.mKey.isNothing()) { + MOZ_ASSERT_UNREACHABLE("Invalid media control key."); + return; + } ContentPlaybackController controller(aContext); - switch (aAction.mKey) { + switch (aAction.mKey.value()) { case MediaControlKey::Focus: controller.Focus(); return; @@ -179,6 +183,9 @@ void ContentMediaControlKeyHandler::HandleMediaControlAction( case MediaControlKey::Pause: controller.Pause(); return; + case MediaControlKey::Playpause: + MOZ_ASSERT_UNREACHABLE("Invalid media control key."); + return; case MediaControlKey::Stop: controller.Stop(); return; diff --git a/dom/media/mediacontrol/MediaControlKeyManager.cpp b/dom/media/mediacontrol/MediaControlKeyManager.cpp index 4cb562aa84..ba6ed3a524 100644 --- a/dom/media/mediacontrol/MediaControlKeyManager.cpp +++ b/dom/media/mediacontrol/MediaControlKeyManager.cpp @@ -184,10 +184,16 @@ void MediaControlKeyManager::SetEnablePictureInPictureMode(bool aIsEnabled) { } } -void MediaControlKeyManager::SetPositionState(const PositionState& aState) { - LOG_INFO("Set PositionState, duration=%f, playbackRate=%f, position=%f", - aState.mDuration, aState.mPlaybackRate, - aState.mLastReportedPlaybackPosition); +void MediaControlKeyManager::SetPositionState( + const Maybe<PositionState>& aState) { + if (aState) { + LOG_INFO("Set PositionState, duration=%f, playbackRate=%f, position=%f", + aState->mDuration, aState->mPlaybackRate, + aState->mLastReportedPlaybackPosition); + } else { + LOG_INFO("Set PositionState, Nothing"); + } + if (mEventSource && mEventSource->IsOpened()) { mEventSource->SetPositionState(aState); } diff --git a/dom/media/mediacontrol/MediaControlKeyManager.h b/dom/media/mediacontrol/MediaControlKeyManager.h index feb857e335..9b029f3bc9 100644 --- a/dom/media/mediacontrol/MediaControlKeyManager.h +++ b/dom/media/mediacontrol/MediaControlKeyManager.h @@ -41,7 +41,7 @@ class MediaControlKeyManager final : public MediaControlKeySource, void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override; void SetEnableFullScreen(bool aIsEnabled) override; void SetEnablePictureInPictureMode(bool aIsEnabled) override; - void SetPositionState(const PositionState& aState) override; + void SetPositionState(const Maybe<PositionState>& aState) override; private: ~MediaControlKeyManager(); diff --git a/dom/media/mediacontrol/MediaControlKeySource.cpp b/dom/media/mediacontrol/MediaControlKeySource.cpp index 22756c860a..db372fc57b 100644 --- a/dom/media/mediacontrol/MediaControlKeySource.cpp +++ b/dom/media/mediacontrol/MediaControlKeySource.cpp @@ -36,7 +36,11 @@ void MediaControlKeyHandler::OnActionPerformed( return; } - switch (aAction.mKey) { + if (aAction.mKey.isNothing()) { + MOZ_ASSERT_UNREACHABLE("Error : undefined media key!"); + return; + } + switch (aAction.mKey.value()) { case MediaControlKey::Focus: controller->Focus(); return; diff --git a/dom/media/mediacontrol/MediaControlKeySource.h b/dom/media/mediacontrol/MediaControlKeySource.h index f5d62a429e..4ab2c8b8be 100644 --- a/dom/media/mediacontrol/MediaControlKeySource.h +++ b/dom/media/mediacontrol/MediaControlKeySource.h @@ -28,10 +28,10 @@ struct SeekDetails { struct MediaControlAction { MediaControlAction() = default; - explicit MediaControlAction(MediaControlKey aKey) : mKey(aKey) {} + explicit MediaControlAction(MediaControlKey aKey) : mKey(Some(aKey)) {} MediaControlAction(MediaControlKey aKey, const SeekDetails& aDetails) - : mKey(aKey), mDetails(Some(aDetails)) {} - MediaControlKey mKey = MediaControlKey::EndGuard_; + : mKey(Some(aKey)), mDetails(Some(aDetails)) {} + Maybe<MediaControlKey> mKey; Maybe<SeekDetails> mDetails; }; @@ -109,7 +109,7 @@ class MediaControlKeySource { // to notify change to the embedded application. virtual void SetEnableFullScreen(bool aIsEnabled){}; virtual void SetEnablePictureInPictureMode(bool aIsEnabled){}; - virtual void SetPositionState(const PositionState& aState){}; + virtual void SetPositionState(const Maybe<PositionState>& aState){}; protected: virtual ~MediaControlKeySource() = default; diff --git a/dom/media/mediacontrol/MediaControlService.cpp b/dom/media/mediacontrol/MediaControlService.cpp index c321e080d2..f45ab4253d 100644 --- a/dom/media/mediacontrol/MediaControlService.cpp +++ b/dom/media/mediacontrol/MediaControlService.cpp @@ -157,6 +157,10 @@ void MediaControlService::NotifyMediaControlHasEverBeenUsed() { Telemetry::ScalarSet(Telemetry::ScalarID::MEDIA_CONTROL_PLATFORM_USAGE, u"Android"_ns, usedOnMediaControl); #endif +#ifdef MOZ_WIDGET_UIKIT + Telemetry::ScalarSet(Telemetry::ScalarID::MEDIA_CONTROL_PLATFORM_USAGE, + u"iOS"_ns, usedOnMediaControl); +#endif } void MediaControlService::NotifyMediaControlHasEverBeenEnabled() { @@ -182,6 +186,10 @@ void MediaControlService::NotifyMediaControlHasEverBeenEnabled() { Telemetry::ScalarSet(Telemetry::ScalarID::MEDIA_CONTROL_PLATFORM_USAGE, u"Android"_ns, enableOnMediaControl); #endif +#ifdef MOZ_WIDGET_UIKIT + Telemetry::ScalarSet(Telemetry::ScalarID::MEDIA_CONTROL_PLATFORM_USAGE, + u"iOS"_ns, enableOnMediaControl); +#endif } NS_IMETHODIMP @@ -510,7 +518,7 @@ void MediaControlService::ControllerManager::ConnectMainControllerEvents() { mSource->SetEnablePictureInPictureMode(aIsEnabled); }); mPositionChangedListener = mMainController->PositionChangedEvent().Connect( - AbstractThread::MainThread(), [this](const PositionState& aState) { + AbstractThread::MainThread(), [this](const Maybe<PositionState>& aState) { mSource->SetPositionState(aState); }); } diff --git a/dom/media/mediacontrol/MediaControlUtils.h b/dom/media/mediacontrol/MediaControlUtils.h index a327c2f3d8..e4e75e7c97 100644 --- a/dom/media/mediacontrol/MediaControlUtils.h +++ b/dom/media/mediacontrol/MediaControlUtils.h @@ -50,6 +50,14 @@ inline const char* ToMediaControlKeyStr(MediaControlKey aKey) { } } +inline const char* ToMediaControlKeyStr(const Maybe<MediaControlKey>& aKey) { + if (aKey.isNothing()) { + MOZ_ASSERT_UNREACHABLE("Invalid action."); + return "Unknown"; + } + return ToMediaControlKeyStr(aKey.value()); +} + inline const char* ToMediaSessionActionStr(MediaSessionAction aAction) { switch (aAction) { case MediaSessionAction::Play: @@ -99,11 +107,6 @@ inline MediaControlKey ConvertMediaSessionActionToControlKey( } } -inline MediaSessionAction ConvertToMediaSessionAction(uint8_t aActionValue) { - MOZ_DIAGNOSTIC_ASSERT(aActionValue < uint8_t(MediaSessionAction::EndGuard_)); - return static_cast<MediaSessionAction>(aActionValue); -} - inline const char* ToMediaPlaybackStateStr(MediaPlaybackState aState) { switch (aState) { case MediaPlaybackState::eStarted: diff --git a/dom/media/mediacontrol/MediaController.cpp b/dom/media/mediacontrol/MediaController.cpp index bfb98f24c9..4290e952b0 100644 --- a/dom/media/mediacontrol/MediaController.cpp +++ b/dom/media/mediacontrol/MediaController.cpp @@ -186,9 +186,10 @@ bool MediaController::ShouldPropagateActionToAllContexts( // These three actions have default action handler for each frame, so we // need to propagate to all contexts. We would handle default handlers in // `ContentMediaController::HandleMediaKey`. - return aAction.mKey == MediaControlKey::Play || - aAction.mKey == MediaControlKey::Pause || - aAction.mKey == MediaControlKey::Stop; + return aAction.mKey.isSome() && + (aAction.mKey.value() == MediaControlKey::Play || + aAction.mKey.value() == MediaControlKey::Pause || + aAction.mKey.value() == MediaControlKey::Stop); } void MediaController::UpdateMediaControlActionToContentMediaIfNeeded( @@ -493,11 +494,16 @@ void MediaController::HandleSupportedMediaSessionActionsChanged( MediaController_Binding::ClearCachedSupportedKeysValue(this); } -void MediaController::HandlePositionStateChanged(const PositionState& aState) { +void MediaController::HandlePositionStateChanged( + const Maybe<PositionState>& aState) { + if (!aState) { + return; + } + PositionStateEventInit init; - init.mDuration = aState.mDuration; - init.mPlaybackRate = aState.mPlaybackRate; - init.mPosition = aState.mLastReportedPlaybackPosition; + init.mDuration = aState->mDuration; + init.mPlaybackRate = aState->mPlaybackRate; + init.mPosition = aState->mLastReportedPlaybackPosition; RefPtr<PositionStateEvent> event = PositionStateEvent::Constructor(this, u"positionstatechange"_ns, init); DispatchAsyncEvent(event.forget()); diff --git a/dom/media/mediacontrol/MediaController.h b/dom/media/mediacontrol/MediaController.h index 82b351ead7..f95ff6368e 100644 --- a/dom/media/mediacontrol/MediaController.h +++ b/dom/media/mediacontrol/MediaController.h @@ -159,7 +159,7 @@ class MediaController final : public DOMEventTargetHelper, void HandleSupportedMediaSessionActionsChanged( const nsTArray<MediaSessionAction>& aSupportedAction); - void HandlePositionStateChanged(const PositionState& aState); + void HandlePositionStateChanged(const Maybe<PositionState>& aState); void HandleMetadataChanged(const MediaMetadataBase& aMetadata); // This would register controller to the media control service that takes a diff --git a/dom/media/mediacontrol/MediaStatusManager.cpp b/dom/media/mediacontrol/MediaStatusManager.cpp index 4365e6b531..9187e56f25 100644 --- a/dom/media/mediacontrol/MediaStatusManager.cpp +++ b/dom/media/mediacontrol/MediaStatusManager.cpp @@ -154,6 +154,7 @@ void MediaStatusManager::SetActiveMediaSessionContextId( *mActiveMediaSessionContextId); mMetadataChangedEvent.Notify(GetCurrentMediaMetadata()); mSupportedActionsChangedEvent.Notify(GetSupportedActions()); + mPositionStateChangedEvent.Notify(GetCurrentPositionState()); if (StaticPrefs::media_mediacontrol_testingevents_enabled()) { if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { obs->NotifyObservers(nullptr, "active-media-session-changed", nullptr); @@ -170,6 +171,7 @@ void MediaStatusManager::ClearActiveMediaSessionContextIdIfNeeded() { StoreMediaSessionContextIdOnWindowContext(); mMetadataChangedEvent.Notify(GetCurrentMediaMetadata()); mSupportedActionsChangedEvent.Notify(GetSupportedActions()); + mPositionStateChangedEvent.Notify(GetCurrentPositionState()); if (StaticPrefs::media_mediacontrol_testingevents_enabled()) { if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { obs->NotifyObservers(nullptr, "active-media-session-changed", nullptr); @@ -362,8 +364,14 @@ void MediaStatusManager::DisableAction(uint64_t aBrowsingContextId, NotifySupportedKeysChangedIfNeeded(aBrowsingContextId); } -void MediaStatusManager::UpdatePositionState(uint64_t aBrowsingContextId, - const PositionState& aState) { +void MediaStatusManager::UpdatePositionState( + uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) { + auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId); + if (info) { + LOG("Update position state for context %" PRIu64, aBrowsingContextId); + info->mPositionState = aState; + } + // The position state comes from non-active media session which we don't care. if (!mActiveMediaSessionContextId || *mActiveMediaSessionContextId != aBrowsingContextId) { @@ -393,9 +401,8 @@ CopyableTArray<MediaSessionAction> MediaStatusManager::GetSupportedActions() MediaSessionInfo info = mMediaSessionInfoMap.Get(*mActiveMediaSessionContextId); - const uint8_t actionNums = uint8_t(MediaSessionAction::EndGuard_); - for (uint8_t actionValue = 0; actionValue < actionNums; actionValue++) { - MediaSessionAction action = ConvertToMediaSessionAction(actionValue); + for (MediaSessionAction action : + MakeWebIDLEnumeratedRange<MediaSessionAction>()) { if (info.IsActionSupported(action)) { supportedActions.AppendElement(action); } @@ -421,6 +428,16 @@ MediaMetadataBase MediaStatusManager::GetCurrentMediaMetadata() const { return CreateDefaultMetadata(); } +Maybe<PositionState> MediaStatusManager::GetCurrentPositionState() const { + if (mActiveMediaSessionContextId) { + auto info = mMediaSessionInfoMap.Lookup(*mActiveMediaSessionContextId); + if (info) { + return info->mPositionState; + } + } + return Nothing(); +} + void MediaStatusManager::FillMissingTitleAndArtworkIfNeeded( MediaMetadataBase& aMetadata) const { // If the metadata doesn't set its title and artwork properly, we would like diff --git a/dom/media/mediacontrol/MediaStatusManager.h b/dom/media/mediacontrol/MediaStatusManager.h index 24247d119d..a4216c8453 100644 --- a/dom/media/mediacontrol/MediaStatusManager.h +++ b/dom/media/mediacontrol/MediaStatusManager.h @@ -53,6 +53,7 @@ class MediaSessionInfo { Maybe<MediaMetadataBase> mMetadata; MediaSessionPlaybackState mDeclaredPlaybackState = MediaSessionPlaybackState::None; + Maybe<PositionState> mPositionState; // Use bitwise to store the supported actions. uint32_t mSupportedActions = 0; }; @@ -118,7 +119,7 @@ class IMediaInfoUpdater { // Use this method when media session update its position state. virtual void UpdatePositionState(uint64_t aBrowsingContextId, - const PositionState& aState) = 0; + const Maybe<PositionState>& aState) = 0; }; /** @@ -163,7 +164,7 @@ class MediaStatusManager : public IMediaInfoUpdater { void DisableAction(uint64_t aBrowsingContextId, MediaSessionAction aAction) override; void UpdatePositionState(uint64_t aBrowsingContextId, - const PositionState& aState) override; + const Maybe<PositionState>& aState) override; // Return active media session's metadata if active media session exists and // it has already set its metadata. Otherwise, return default media metadata @@ -180,7 +181,7 @@ class MediaStatusManager : public IMediaInfoUpdater { return mMetadataChangedEvent; } - MediaEventSource<PositionState>& PositionChangedEvent() { + MediaEventSource<Maybe<PositionState>>& PositionChangedEvent() { return mPositionStateChangedEvent; } @@ -246,6 +247,10 @@ class MediaStatusManager : public IMediaInfoUpdater { // media session doesn't exist, return 'None' instead. MediaSessionPlaybackState GetCurrentDeclaredPlaybackState() const; + // Return the active media session's position state. If the active media + // session doesn't exist or doesn't have any state, Nothing is returned. + Maybe<PositionState> GetCurrentPositionState() const; + // This state can match to the `guessed playback state` in the spec [1], it // indicates if we have any media element playing within the tab which this // controller belongs to. But currently we only take media elements into @@ -266,7 +271,7 @@ class MediaStatusManager : public IMediaInfoUpdater { MediaEventProducer<MediaMetadataBase> mMetadataChangedEvent; MediaEventProducer<nsTArray<MediaSessionAction>> mSupportedActionsChangedEvent; - MediaEventProducer<PositionState> mPositionStateChangedEvent; + MediaEventProducer<Maybe<PositionState>> mPositionStateChangedEvent; MediaEventProducer<MediaSessionPlaybackState> mPlaybackStateChangedEvent; MediaPlaybackStatus mPlaybackStatusDelegate; }; diff --git a/dom/media/mediacontrol/tests/browser/browser_media_control_before_media_starts.js b/dom/media/mediacontrol/tests/browser/browser_media_control_before_media_starts.js index 292c2f521f..a0c75bfa7c 100644 --- a/dom/media/mediacontrol/tests/browser/browser_media_control_before_media_starts.js +++ b/dom/media/mediacontrol/tests/browser/browser_media_control_before_media_starts.js @@ -184,7 +184,7 @@ function enableMediaFullScreenInIframe(tab) { } function waitUntilIframeMediaStartedPlaying(tab) { - return SpecialPowers.spawn(tab.linkedBrowser, [IFRAME_URL], async url => { + return SpecialPowers.spawn(tab.linkedBrowser, [IFRAME_URL], async () => { info(`check if media in iframe starts playing`); const iframe = content.document.getElementById("iframe"); iframe.contentWindow.postMessage("check-playing", "*"); diff --git a/dom/media/mediacontrol/tests/gtest/MediaKeyListenerTest.h b/dom/media/mediacontrol/tests/gtest/MediaKeyListenerTest.h index 5145eb7dbb..50ef254947 100644 --- a/dom/media/mediacontrol/tests/gtest/MediaKeyListenerTest.h +++ b/dom/media/mediacontrol/tests/gtest/MediaKeyListenerTest.h @@ -18,7 +18,7 @@ class MediaKeyListenerTest : public MediaControlKeyListener { void Clear() { mReceivedKey = mozilla::Nothing(); } void OnActionPerformed(const MediaControlAction& aAction) override { - mReceivedKey = mozilla::Some(aAction.mKey); + mReceivedKey = aAction.mKey; } bool IsResultEqualTo(MediaControlKey aResult) const { if (mReceivedKey) { diff --git a/dom/media/mediacontrol/tests/gtest/moz.build b/dom/media/mediacontrol/tests/gtest/moz.build index 7043bfcd5e..a8602f6718 100644 --- a/dom/media/mediacontrol/tests/gtest/moz.build +++ b/dom/media/mediacontrol/tests/gtest/moz.build @@ -11,7 +11,7 @@ UNIFIED_SOURCES += [ "TestMediaKeysEvent.cpp", ] -if CONFIG["MOZ_APPLEMEDIA"]: +if CONFIG["MOZ_APPLEMEDIA"] and CONFIG["TARGET_OS"] == "OSX": UNIFIED_SOURCES += ["TestMediaKeysEventMac.mm", "TestMediaKeysEventMediaCenter.mm"] include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/media/mediasession/MediaSession.cpp b/dom/media/mediasession/MediaSession.cpp index e55fa28d96..9120aa5379 100644 --- a/dom/media/mediasession/MediaSession.cpp +++ b/dom/media/mediasession/MediaSession.cpp @@ -20,6 +20,29 @@ namespace mozilla::dom { +double PositionState::CurrentPlaybackPosition(TimeStamp aNow) const { + // https://w3c.github.io/mediasession/#current-playback-position + + // Set time elapsed to the system time in seconds minus the last position + // updated time. + auto timeElapsed = aNow - mPositionUpdatedTime; + // Mutliply time elapsed with actual playback rate. + timeElapsed = timeElapsed.MultDouble(mPlaybackRate); + // Set position to time elapsed added to last reported playback position. + auto position = timeElapsed.ToSeconds() + mLastReportedPlaybackPosition; + + // If position is less than zero, return zero. + if (position < 0.0) { + return 0.0; + } + // If position is greater than duration, return duration. + if (position > mDuration) { + return mDuration; + } + // Return position. + return position; +} + // We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to // unregister MediaSession from document's activity listeners. NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaSession) @@ -138,6 +161,7 @@ void MediaSession::SetPositionState(const MediaPositionState& aState, // If the state is an empty dictionary then clear the position state. if (!aState.IsAnyMemberPresent()) { mPositionState.reset(); + NotifyPositionStateChanged(); return; } @@ -175,8 +199,8 @@ void MediaSession::SetPositionState(const MediaPositionState& aState, // Update the position state and last position updated time. MOZ_ASSERT(aState.mDuration.WasPassed()); - mPositionState = - Some(PositionState(aState.mDuration.Value(), playbackRate, position)); + mPositionState = Some(PositionState(aState.mDuration.Value(), playbackRate, + position, TimeStamp::Now())); NotifyPositionStateChanged(); } @@ -328,7 +352,7 @@ void MediaSession::NotifyPositionStateChanged() { RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); MOZ_ASSERT(currentBC, "Update action after context destroyed!"); if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { - updater->UpdatePositionState(currentBC->Id(), *mPositionState); + updater->UpdatePositionState(currentBC->Id(), mPositionState); } } diff --git a/dom/media/mediasession/MediaSession.h b/dom/media/mediasession/MediaSession.h index db6864c842..6784fb531f 100644 --- a/dom/media/mediasession/MediaSession.h +++ b/dom/media/mediasession/MediaSession.h @@ -11,6 +11,7 @@ #include "mozilla/Attributes.h" #include "mozilla/dom/MediaSessionBinding.h" #include "mozilla/EnumeratedArray.h" +#include "mozilla/TimeStamp.h" #include "nsCycleCollectionParticipant.h" #include "nsIDocumentActivity.h" #include "nsWrapperCache.h" @@ -29,13 +30,21 @@ class MediaMetadata; struct PositionState { PositionState() = default; PositionState(double aDuration, double aPlaybackRate, - double aLastReportedTime) + double aLastReportedTime, TimeStamp aPositionUpdatedTime) : mDuration(aDuration), mPlaybackRate(aPlaybackRate), - mLastReportedPlaybackPosition(aLastReportedTime) {} - double mDuration; - double mPlaybackRate; - double mLastReportedPlaybackPosition; + mLastReportedPlaybackPosition(aLastReportedTime), + mPositionUpdatedTime(aPositionUpdatedTime) {} + + double mDuration = 0.0; + double mPlaybackRate = 0.0; + double mLastReportedPlaybackPosition = 0.0; + TimeStamp mPositionUpdatedTime; + + // Returns the playback position in seconds (from 0 to mDuration) + // at the current time (aNow). + // https://w3c.github.io/mediasession/#current-playback-position + double CurrentPlaybackPosition(TimeStamp aNow = TimeStamp::Now()) const; }; class MediaSession final : public nsIDocumentActivity, public nsWrapperCache { @@ -111,8 +120,7 @@ class MediaSession final : public nsIDocumentActivity, public nsWrapperCache { RefPtr<MediaMetadata> mMediaMetadata; - EnumeratedArray<MediaSessionAction, MediaSessionAction::EndGuard_, - RefPtr<MediaSessionActionHandler>> + EnumeratedArray<MediaSessionAction, RefPtr<MediaSessionActionHandler>> mActionHandlers; // This is used as is a hint for the user agent to determine whether the diff --git a/dom/media/mediasession/MediaSessionIPCUtils.h b/dom/media/mediasession/MediaSessionIPCUtils.h index c44f4b4553..610b123b6c 100644 --- a/dom/media/mediasession/MediaSessionIPCUtils.h +++ b/dom/media/mediasession/MediaSessionIPCUtils.h @@ -6,6 +6,7 @@ #define DOM_MEDIA_MEDIASESSION_MEDIASESSIONIPCUTILS_H_ #include "ipc/EnumSerializer.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "MediaMetadata.h" #include "mozilla/dom/MediaSession.h" #include "mozilla/dom/MediaSessionBinding.h" @@ -71,12 +72,14 @@ struct ParamTraits<mozilla::dom::PositionState> { WriteParam(aWriter, aParam.mDuration); WriteParam(aWriter, aParam.mPlaybackRate); WriteParam(aWriter, aParam.mLastReportedPlaybackPosition); + WriteParam(aWriter, aParam.mPositionUpdatedTime); } static bool Read(MessageReader* aReader, paramType* aResult) { if (!ReadParam(aReader, &(aResult->mDuration)) || !ReadParam(aReader, &(aResult->mPlaybackRate)) || - !ReadParam(aReader, &(aResult->mLastReportedPlaybackPosition))) { + !ReadParam(aReader, &(aResult->mLastReportedPlaybackPosition)) || + !ReadParam(aReader, &(aResult->mPositionUpdatedTime))) { return false; } return true; @@ -85,17 +88,13 @@ struct ParamTraits<mozilla::dom::PositionState> { template <> struct ParamTraits<mozilla::dom::MediaSessionPlaybackState> - : public ContiguousEnumSerializer< - mozilla::dom::MediaSessionPlaybackState, - mozilla::dom::MediaSessionPlaybackState::None, - mozilla::dom::MediaSessionPlaybackState::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::MediaSessionPlaybackState> {}; template <> struct ParamTraits<mozilla::dom::MediaSessionAction> - : public ContiguousEnumSerializer< - mozilla::dom::MediaSessionAction, - mozilla::dom::MediaSessionAction::Play, - mozilla::dom::MediaSessionAction::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::MediaSessionAction> {}; } // namespace IPC diff --git a/dom/media/mediasession/moz.build b/dom/media/mediasession/moz.build index 805bf67971..70f69fc14f 100644 --- a/dom/media/mediasession/moz.build +++ b/dom/media/mediasession/moz.build @@ -6,6 +6,10 @@ MOCHITEST_MANIFESTS += ["test/mochitest.toml"] +TEST_DIRS += [ + "test/gtest", +] + EXPORTS.mozilla.dom += [ "MediaMetadata.h", "MediaSession.h", diff --git a/dom/media/mediasession/test/gtest/TestPositionState.cpp b/dom/media/mediasession/test/gtest/TestPositionState.cpp new file mode 100644 index 0000000000..5fa38d0294 --- /dev/null +++ b/dom/media/mediasession/test/gtest/TestPositionState.cpp @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "MediaSession.h" + +using namespace mozilla; +using namespace mozilla::dom; + +struct CurrentPlaybackPositionFixture { + double mDuration = 0.0; + double mPlaybackRate = 0.0; + double mLastReportedTime = 0.0; + + TimeDuration mDelta; + double mExpectedPosition = -1; +}; + +class CurrentPlaybackPositionTest + : public testing::TestWithParam<CurrentPlaybackPositionFixture> {}; + +static const std::initializer_list<CurrentPlaybackPositionFixture> kFixtures = { + // position must be positive + { + 10.0, + 1.0, + 0.0, + TimeDuration::FromSeconds(-1.0), + 0.0, + }, + // no time elapsed + { + 10.0, + 1.0, + 0.0, + TimeDuration::FromSeconds(0.0), + 0.0, + }, + { + 10.0, + 1.0, + 0.0, + TimeDuration::FromSeconds(3.0), + 3.0, + }, + { + 10.0, + 1.0, + 0.0, + TimeDuration::FromSeconds(10.0), + 10.0, + }, + // position is clamped to the duration + { + 10.0, + 1.0, + 0.0, + TimeDuration::FromSeconds(20.0), + 10.0, + }, + { + 10.0, + 1.0, + 5.0, + TimeDuration::FromSeconds(-1.0), + 4.0, + }, + { + 10.0, + 1.0, + 5.0, + TimeDuration::FromSeconds(-6.0), + 0.0, + }, + { + 10.0, + 1.0, + 5.0, + TimeDuration::FromSeconds(6.0), + 10.0, + }, + // expected: 5s + 2 * 2s + { + 10.0, + 2.0, + 5.0, + TimeDuration::FromSeconds(2.0), + 9.0, + }, + // expected: 5s + 0.5 * 2s + { + 10.0, + 0.5, + 5.0, + TimeDuration::FromSeconds(2.0), + 6.0, + }, + { + 5.0, + 4.0, + 10.0, + TimeDuration::FromSeconds(20.0), + 5.0, + }, + // empty media (0s) + { + 0.0, + 4.0, + 5.0, + TimeDuration::FromSeconds(20.0), + 0.0, + }, +}; + +TEST_P(CurrentPlaybackPositionTest, Run) { + const auto& fixture = GetParam(); + PositionState state(fixture.mDuration, fixture.mPlaybackRate, + fixture.mLastReportedTime, TimeStamp::Now()); + + ASSERT_DOUBLE_EQ(state.CurrentPlaybackPosition(state.mPositionUpdatedTime + + fixture.mDelta), + fixture.mExpectedPosition); +} + +INSTANTIATE_TEST_SUITE_P(PositionState, CurrentPlaybackPositionTest, + testing::ValuesIn(kFixtures)); diff --git a/dom/interfaces/html/moz.build b/dom/media/mediasession/test/gtest/moz.build index 516bce30e2..fd2a9a6e30 100644 --- a/dom/interfaces/html/moz.build +++ b/dom/media/mediasession/test/gtest/moz.build @@ -4,12 +4,12 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -with Files("**"): - BUG_COMPONENT = ("Core", "DOM: Core & HTML") +UNIFIED_SOURCES += [ + "TestPositionState.cpp", +] -XPIDL_SOURCES += [ - "nsIDOMMozBrowserFrame.idl", - "nsIMozBrowserFrame.idl", +LOCAL_INCLUDES += [ + "/dom/media/mediasession", ] -XPIDL_MODULE = "dom_html" +FINAL_LIBRARY = "xul-gtest" diff --git a/dom/media/mediasource/MediaSourceDecoder.cpp b/dom/media/mediasource/MediaSourceDecoder.cpp index 24a74e261b..3a1308a9ca 100644 --- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -57,8 +57,13 @@ MediaDecoderStateMachineBase* MediaSourceDecoder::CreateStateMachine( TrackingId::TrackAcrossProcesses::Yes); mReader = new MediaFormatReader(init, mDemuxer); #ifdef MOZ_WMF_MEDIA_ENGINE - // TODO : Only for testing development for now. In the future this should be - // used for encrypted content only. + // Our main purpose is to only using this state machine for encrypted playback + // (unless explicitly set the pref to allow non-encrypted playback), but we + // can't determine if playback is encrypted or not at the moment. Therefore, + // we will handle that in ExternalEngineStateMachine, and report special + // errors, such as NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR or + // NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR, to switch the state + // machine if necessary. if (StaticPrefs::media_wmf_media_engine_enabled() && !aDisableExternalEngine) { return new ExternalEngineStateMachine(this, mReader); @@ -319,12 +324,13 @@ bool MediaSourceDecoder::CanPlayThroughImpl() { } // If we have data up to the mediasource's duration or 3s ahead, we can // assume that we can play without interruption. - dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers(); - TimeUnit bufferedEnd = sourceBuffers->GetHighestBufferedEndTime(); + TimeIntervals buffered = GetBuffered(); + buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2); TimeUnit timeAhead = std::min(duration, currentPosition + TimeUnit::FromSeconds(3)); TimeInterval interval(currentPosition, timeAhead); - return bufferedEnd >= timeAhead; + return buffered.ToMicrosecondResolution().ContainsWithStrictEnd( + ClampIntervalToEnd(interval)); } TimeInterval MediaSourceDecoder::ClampIntervalToEnd( @@ -366,6 +372,23 @@ bool MediaSourceDecoder::HadCrossOriginRedirects() { return false; } +#ifdef MOZ_WMF_MEDIA_ENGINE +void MediaSourceDecoder::MetadataLoaded( + UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags, + MediaDecoderEventVisibility aEventVisibility) { + // If the previous state machine has loaded the metadata, then we don't need + // to load it again. This can happen when the media format or key system is + // not supported by previous state machine. + if (mFiredMetadataLoaded && mStateMachineRecreated) { + MSE_DEBUG( + "Metadata already loaded and being informed by previous state machine"); + return; + } + MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags), + aEventVisibility); +} +#endif + #undef MSE_DEBUG #undef MSE_DEBUGV diff --git a/dom/media/mediasource/MediaSourceDecoder.h b/dom/media/mediasource/MediaSourceDecoder.h index ff312cb6cf..2ebba67a17 100644 --- a/dom/media/mediasource/MediaSourceDecoder.h +++ b/dom/media/mediasource/MediaSourceDecoder.h @@ -85,6 +85,11 @@ class MediaSourceDecoder : public MediaDecoder, media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval); bool CanPlayThroughImpl() override; +#ifdef MOZ_WMF_MEDIA_ENGINE + void MetadataLoaded(UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags, + MediaDecoderEventVisibility aEventVisibility) override; +#endif + RefPtr<nsIPrincipal> mPrincipal; // The owning MediaSource holds a strong reference to this decoder, and diff --git a/dom/media/mediasource/test/mochitest.toml b/dom/media/mediasource/test/mochitest.toml index b9ed95b2bb..7e60af8929 100644 --- a/dom/media/mediasource/test/mochitest.toml +++ b/dom/media/mediasource/test/mochitest.toml @@ -270,3 +270,5 @@ skip-if = [ ["test_WaitingOnMissingData_mp4.html"] ["test_WaitingToEndedTransition_mp4.html"] + +["test_BufferedSeekCanPlayThrough.html"] diff --git a/dom/media/mediasource/test/test_BufferedSeekCanPlayThrough.html b/dom/media/mediasource/test/test_BufferedSeekCanPlayThrough.html new file mode 100644 index 0000000000..5c789a7b6e --- /dev/null +++ b/dom/media/mediasource/test/test_BufferedSeekCanPlayThrough.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html><head> +<meta http-equiv="content-type" content="text/html; charset=windows-1252"> + <title>MSE: Don't get stuck buffering for too long when we have frames to show</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="mediasource.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"><script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +runWithMSE(async (ms, v) => { + logEvents(v); + await once(ms, "sourceopen"); + ok(true, "Receive a sourceopen event"); + ms.addEventListener("sourceopen", () => ok(false, "No more sourceopen")); + const sb = ms.addSourceBuffer("video/mp4"); + ok(sb, "Create a SourceBuffer"); + sb.addEventListener("error", e => { + ok(false, "Got Error: " + e); + SimpleTest.finish(); + }); + + // Load just the beginning of the media, and the end. Verify + // that canplaythrough isn't fired, and waiting is fired. + await fetchAndLoad(sb, "bipbop/bipbop_video", ["init"], ".mp4"); + await fetchAndLoad(sb, "bipbop/bipbop_video", ["1"], ".m4s"); + await fetchAndLoad(sb, "bipbop/bipbop_video", ["9"], ".m4s"); + // Slighly before the end of the first segment. + v.currentTime = v.buffered.end(0) - 0.1; + v.onseeked = function() { + is(v.readyState, HTMLMediaElement.HAVE_FUTURE_DATA, + "readyState is HAVE_FUTURE_DATA after seeking close to a large gap"); + SimpleTest.finish(); + } + v.oncanplaythrough = function() { + ok(false, "Should not have received canplaythrough"); + SimpleTest.finish(); + } +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/mediasource/test/test_EndOfStream.html b/dom/media/mediasource/test/test_EndOfStream.html index b926869f1f..bcaf2ee54d 100644 --- a/dom/media/mediasource/test/test_EndOfStream.html +++ b/dom/media/mediasource/test/test_EndOfStream.html @@ -12,7 +12,7 @@ SimpleTest.waitForExplicitFinish(); -runWithMSE(async (ms, v) => { +runWithMSE(async (ms) => { await once(ms, "sourceopen"); const sb = ms.addSourceBuffer("video/webm"); diff --git a/dom/media/mediasource/test/test_EndOfStream_mp4.html b/dom/media/mediasource/test/test_EndOfStream_mp4.html index 9319b80390..140641565d 100644 --- a/dom/media/mediasource/test/test_EndOfStream_mp4.html +++ b/dom/media/mediasource/test/test_EndOfStream_mp4.html @@ -12,7 +12,7 @@ SimpleTest.waitForExplicitFinish(); -runWithMSE(async (ms, v) => { +runWithMSE(async (ms) => { await once(ms, "sourceopen"); const sb = ms.addSourceBuffer("video/mp4"); diff --git a/dom/media/mediasource/test/test_ExperimentalAsync.html b/dom/media/mediasource/test/test_ExperimentalAsync.html index 6617716f26..e64a9befeb 100644 --- a/dom/media/mediasource/test/test_ExperimentalAsync.html +++ b/dom/media/mediasource/test/test_ExperimentalAsync.html @@ -61,7 +61,7 @@ runWithMSE(async function(ms, el) { await once(el, "seeked"); dump("dump: seeked to " + seekTime); is(el.currentTime, seekTime, "correctly seeked to " + seekTime); - await audiosb.appendBufferAsync(audioBuffer).catch(async function(ex2) { + await audiosb.appendBufferAsync(audioBuffer).catch(async function() { ok(false, "Shouldn't throw another time when data can be evicted"); dump(JSON.stringify(await SpecialPowers.wrap(el).mozRequestDebugInfo())); SimpleTest.finish(); @@ -73,7 +73,7 @@ runWithMSE(async function(ms, el) { await audiosb.removeAsync(ms.duration + 1, Infinity).catch(async function(ex4) { ok(true, "remove promise got rejected with start > duration"); is(ex4.name, "TypeError"); - await audiosb.removeAsync(0, Infinity).catch(function(ex5) { + await audiosb.removeAsync(0, Infinity).catch(function() { ok(false, "shouldn't throw"); }); ok(true, "remove succeeded"); diff --git a/dom/media/mediasource/test/test_SetModeThrows.html b/dom/media/mediasource/test/test_SetModeThrows.html index c715854b41..c81cb1b70f 100644 --- a/dom/media/mediasource/test/test_SetModeThrows.html +++ b/dom/media/mediasource/test/test_SetModeThrows.html @@ -13,7 +13,7 @@ SimpleTest.waitForExplicitFinish(); // MSE supports setting mode now. make sure it does not throw. -runWithMSE(function(ms, v) { +runWithMSE(function(ms) { ms.addEventListener("sourceopen", () => { const sb = ms.addSourceBuffer("video/webm"); diff --git a/dom/media/metrics.yaml b/dom/media/metrics.yaml index 46801ebaf4..fe2ed5ff6a 100644 --- a/dom/media/metrics.yaml +++ b/dom/media/metrics.yaml @@ -96,3 +96,47 @@ media.audio: notification_emails: - media-alerts@mozilla.com expires: never + +media.playback: + first_frame_loaded: + type: event + description: + The time that the media pipeline takes to load the first video frame. + metadata: + tags: + - 'Core :: Audio/Video: Playback' + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1882205 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1882205 + data_sensitivity: + - technical + notification_emails: + - media-alerts@mozilla.com + extra_keys: + first_frame_loaded_time: + description: + How long (in milliseconds) does the our media pipeline take to load + the first video frame. + type: quantity + playback_type: + description: + The type of the playback. The value could be one of following + (1) Non-MSE playback + (2) MSE playback + (3) EME playback + // Following are Windows-only + (4) Non-MSE media-engine playback + (5) MSE media-engine playback + (6) EME media-engine playback + type: string + video_codec: + description: The video codec used for playback + type: string + resolution: + description: The video resolution used for playback + type: string + key_system: + description: The key system used for the EME playback if exists + type: string + expires: never diff --git a/dom/media/nsIMediaManager.idl b/dom/media/nsIMediaManager.idl index 9cc39d04f4..68334b2e6a 100644 --- a/dom/media/nsIMediaManager.idl +++ b/dom/media/nsIMediaManager.idl @@ -8,11 +8,6 @@ interface nsIArray; interface nsIDOMWindow; interface nsIMediaDevice; -%{C++ -#define NS_MEDIAMANAGERSERVICE_CID {0xabc622ea, 0x9655, 0x4123, {0x80, 0xd9, 0x22, 0x62, 0x1b, 0xdd, 0x54, 0x65}} -#define MEDIAMANAGERSERVICE_CONTRACTID "@mozilla.org/mediaManagerService;1" -%} - [scriptable, builtinclass, uuid(24b23e01-33fd-401f-ba25-6e52658750b0)] interface nsIMediaManagerService : nsISupports { diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp index 2964527e07..00f46385e2 100644 --- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -45,9 +45,6 @@ # include "mozilla/CDMProxy.h" # endif #endif -#ifdef MOZ_FFVPX -# include "FFVPXRuntimeLinker.h" -#endif #ifdef MOZ_FFMPEG # include "FFmpegRuntimeLinker.h" #endif @@ -60,6 +57,7 @@ #ifdef MOZ_OMX # include "OmxDecoderModule.h" #endif +#include "FFVPXRuntimeLinker.h" #include <functional> @@ -99,14 +97,12 @@ class PDMInitializer final { #ifdef MOZ_APPLEMEDIA AppleDecoderModule::Init(); #endif -#ifdef MOZ_FFVPX - FFVPXRuntimeLinker::Init(); -#endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_rdd_ffmpeg_enabled()) { FFmpegRuntimeLinker::Init(); } #endif + FFVPXRuntimeLinker::Init(); } static void InitUtilityPDMs() { @@ -127,11 +123,9 @@ class PDMInitializer final { AppleDecoderModule::Init(); } #endif -#ifdef MOZ_FFVPX if (kind == ipc::SandboxingKind::GENERIC_UTILITY) { FFVPXRuntimeLinker::Init(); } -#endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_utility_ffmpeg_enabled() && kind == ipc::SandboxingKind::GENERIC_UTILITY) { @@ -160,9 +154,7 @@ class PDMInitializer final { #ifdef MOZ_OMX OmxDecoderModule::Init(); #endif -#ifdef MOZ_FFVPX FFVPXRuntimeLinker::Init(); -#endif #ifdef MOZ_FFMPEG FFmpegRuntimeLinker::Init(); #endif @@ -183,9 +175,7 @@ class PDMInitializer final { #ifdef MOZ_OMX OmxDecoderModule::Init(); #endif -#ifdef MOZ_FFVPX FFVPXRuntimeLinker::Init(); -#endif #ifdef MOZ_FFMPEG FFmpegRuntimeLinker::Init(); #endif @@ -547,12 +537,7 @@ void PDMFactory::CreateRddPDMs() { CreateAndStartupPDM<AppleDecoderModule>(); } #endif -#ifdef MOZ_FFVPX - if (StaticPrefs::media_ffvpx_enabled() && - StaticPrefs::media_rdd_ffvpx_enabled()) { - StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); - } -#endif + StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && StaticPrefs::media_rdd_ffmpeg_enabled() && @@ -580,12 +565,9 @@ void PDMFactory::CreateUtilityPDMs() { } #endif if (aKind == ipc::SandboxingKind::GENERIC_UTILITY) { -#ifdef MOZ_FFVPX - if (StaticPrefs::media_ffvpx_enabled() && - StaticPrefs::media_utility_ffvpx_enabled()) { + if (StaticPrefs::media_utility_ffvpx_enabled()) { StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); } -#endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && StaticPrefs::media_utility_ffmpeg_enabled() && @@ -667,11 +649,7 @@ void PDMFactory::CreateContentPDMs() { CreateAndStartupPDM<OmxDecoderModule>(); } #endif -#ifdef MOZ_FFVPX - if (StaticPrefs::media_ffvpx_enabled()) { - StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); - } -#endif + StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && !StartupPDM(FFmpegRuntimeLinker::CreateDecoder())) { @@ -719,11 +697,7 @@ void PDMFactory::CreateDefaultPDMs() { CreateAndStartupPDM<OmxDecoderModule>(); } #endif -#ifdef MOZ_FFVPX - if (StaticPrefs::media_ffvpx_enabled()) { - StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); - } -#endif + StartupPDM(FFVPXRuntimeLinker::CreateDecoder()); #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && !StartupPDM(FFmpegRuntimeLinker::CreateDecoder())) { @@ -898,9 +872,6 @@ DecodeSupportSet PDMFactory::SupportsMimeType( /* static */ bool PDMFactory::AllDecodersAreRemote() { return StaticPrefs::media_rdd_process_enabled() && -#if defined(MOZ_FFVPX) - StaticPrefs::media_rdd_ffvpx_enabled() && -#endif StaticPrefs::media_rdd_opus_enabled() && StaticPrefs::media_rdd_theora_enabled() && StaticPrefs::media_rdd_vorbis_enabled() && diff --git a/dom/media/platforms/PEMFactory.cpp b/dom/media/platforms/PEMFactory.cpp index 9647c5b079..a5b42914eb 100644 --- a/dom/media/platforms/PEMFactory.cpp +++ b/dom/media/platforms/PEMFactory.cpp @@ -20,13 +20,12 @@ # include "WMFEncoderModule.h" #endif -#ifdef MOZ_FFVPX -# include "FFVPXRuntimeLinker.h" -#endif #ifdef MOZ_FFMPEG # include "FFmpegRuntimeLinker.h" #endif +#include "FFVPXRuntimeLinker.h" + #include "mozilla/StaticPrefs_media.h" #include "mozilla/gfx/gfxVars.h" @@ -56,15 +55,12 @@ PEMFactory::PEMFactory() { mCurrentPEMs.AppendElement(new WMFEncoderModule()); #endif -#ifdef MOZ_FFVPX - if (StaticPrefs::media_ffvpx_enabled() && - StaticPrefs::media_ffmpeg_encoder_enabled()) { + if (StaticPrefs::media_ffmpeg_encoder_enabled()) { if (RefPtr<PlatformEncoderModule> pem = FFVPXRuntimeLinker::CreateEncoder()) { mCurrentPEMs.AppendElement(pem); } } -#endif #ifdef MOZ_FFMPEG if (StaticPrefs::media_ffmpeg_enabled() && diff --git a/dom/media/platforms/agnostic/bytestreams/Adts.cpp b/dom/media/platforms/agnostic/bytestreams/Adts.cpp index 5f31904d9c..71c9f15308 100644 --- a/dom/media/platforms/agnostic/bytestreams/Adts.cpp +++ b/dom/media/platforms/agnostic/bytestreams/Adts.cpp @@ -4,37 +4,56 @@ #include "Adts.h" #include "MediaData.h" +#include "PlatformDecoderModule.h" #include "mozilla/Array.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/Logging.h" +#include "ADTSDemuxer.h" + +extern mozilla::LazyLogModule gMediaDemuxerLog; +#define LOG(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__) +#define ADTSLOG(msg, ...) \ + DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__) +#define ADTSLOGV(msg, ...) \ + DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__) namespace mozilla { +namespace ADTS { static const int kADTSHeaderSize = 7; -int8_t Adts::GetFrequencyIndex(uint32_t aSamplesPerSecond) { - static const uint32_t freq_lookup[] = {96000, 88200, 64000, 48000, 44100, - 32000, 24000, 22050, 16000, 12000, - 11025, 8000, 7350, 0}; +constexpr std::array FREQ_LOOKUP{96000, 88200, 64000, 48000, 44100, + 32000, 24000, 22050, 16000, 12000, + 11025, 8000, 7350, 0}; - int8_t i = 0; - while (freq_lookup[i] && aSamplesPerSecond < freq_lookup[i]) { - i++; - } +Result<uint8_t, bool> GetFrequencyIndex(uint32_t aSamplesPerSecond) { + auto found = + std::find(FREQ_LOOKUP.begin(), FREQ_LOOKUP.end(), aSamplesPerSecond); - if (!freq_lookup[i]) { - return -1; + if (found == FREQ_LOOKUP.end()) { + return Err(false); } - return i; + return std::distance(FREQ_LOOKUP.begin(), found); } -bool Adts::ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex, - int8_t aProfile, MediaRawData* aSample) { +bool ConvertSample(uint16_t aChannelCount, uint8_t aFrequencyIndex, + uint8_t aProfile, MediaRawData* aSample) { size_t newSize = aSample->Size() + kADTSHeaderSize; + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Converting sample to ADTS format: newSize: %zu, ch: %u, " + "profile: %u, freq index: %d", + newSize, aChannelCount, aProfile, aFrequencyIndex)); + // ADTS header uses 13 bits for packet size. - if (newSize >= (1 << 13) || aChannelCount > 15 || aFrequencyIndex < 0 || - aProfile < 1 || aProfile > 4) { + if (newSize >= (1 << 13) || aChannelCount > 15 || aProfile < 1 || + aProfile > 4 || aFrequencyIndex >= FREQ_LOOKUP.size()) { + MOZ_LOG(sPDMLog, LogLevel::Debug, + ("Couldn't convert sample to ADTS format: newSize: %zu, ch: %u, " + "profile: %u, freq index: %d", + newSize, aChannelCount, aProfile, aFrequencyIndex)); return false; } @@ -66,7 +85,36 @@ bool Adts::ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex, return true; } -bool Adts::RevertSample(MediaRawData* aSample) { +bool StripHeader(MediaRawData* aSample) { + if (aSample->Size() < kADTSHeaderSize) { + return false; + } + + FrameHeader header; + auto data = Span{aSample->Data(), aSample->Size()}; + MOZ_ASSERT(FrameHeader::MatchesSync(data), + "Don't attempt to strip the ADTS header of a raw AAC packet."); + + bool crcPresent = header.mHaveCrc; + + LOG(("Stripping ADTS, crc %spresent", crcPresent ? "" : "not ")); + + size_t toStrip = crcPresent ? kADTSHeaderSize + 2 : kADTSHeaderSize; + + UniquePtr<MediaRawDataWriter> writer(aSample->CreateWriter()); + writer->PopFront(toStrip); + + if (aSample->mCrypto.IsEncrypted()) { + if (aSample->mCrypto.mPlainSizes.Length() > 0 && + writer->mCrypto.mPlainSizes[0] >= kADTSHeaderSize) { + writer->mCrypto.mPlainSizes[0] -= kADTSHeaderSize; + } + } + + return true; +} + +bool RevertSample(MediaRawData* aSample) { if (aSample->Size() < kADTSHeaderSize) { return false; } @@ -91,4 +139,156 @@ bool Adts::RevertSample(MediaRawData* aSample) { return true; } -} // namespace mozilla + +bool FrameHeader::MatchesSync(const Span<const uint8_t>& aData) { + return aData.Length() >= 2 && aData[0] == 0xFF && (aData[1] & 0xF6) == 0xF0; +} + +FrameHeader::FrameHeader() { Reset(); } + +// Header size +uint64_t FrameHeader::HeaderSize() const { return (mHaveCrc) ? 9 : 7; } + +bool FrameHeader::IsValid() const { return mFrameLength > 0; } + +// Resets the state to allow for a new parsing session. +void FrameHeader::Reset() { PodZero(this); } + +// Returns whether the byte creates a valid sequence up to this point. +bool FrameHeader::Parse(const Span<const uint8_t>& aData) { + if (!MatchesSync(aData)) { + return false; + } + + // AAC has 1024 samples per frame per channel. + mSamples = 1024; + + mHaveCrc = !(aData[1] & 0x01); + mObjectType = ((aData[2] & 0xC0) >> 6) + 1; + mSamplingIndex = (aData[2] & 0x3C) >> 2; + mChannelConfig = (aData[2] & 0x01) << 2 | (aData[3] & 0xC0) >> 6; + mFrameLength = + static_cast<uint32_t>((aData[3] & 0x03) << 11 | (aData[4] & 0xFF) << 3 | + (aData[5] & 0xE0) >> 5); + mNumAACFrames = (aData[6] & 0x03) + 1; + + static const uint32_t SAMPLE_RATES[] = {96000, 88200, 64000, 48000, 44100, + 32000, 24000, 22050, 16000, 12000, + 11025, 8000, 7350}; + if (mSamplingIndex >= ArrayLength(SAMPLE_RATES)) { + LOG(("ADTS: Init() failure: invalid sample-rate index value: %" PRIu32 ".", + mSamplingIndex)); + // This marks the header as invalid. + mFrameLength = 0; + return false; + } + mSampleRate = SAMPLE_RATES[mSamplingIndex]; + + MOZ_ASSERT(mChannelConfig < 8); + mChannels = (mChannelConfig == 7) ? 8 : mChannelConfig; + + return true; +} + +Frame::Frame() : mOffset(0), mHeader() {} +uint64_t Frame::Offset() const { return mOffset; } +size_t Frame::Length() const { + // TODO: If fields are zero'd when invalid, this check wouldn't be + // necessary. + if (!mHeader.IsValid()) { + return 0; + } + + return mHeader.mFrameLength; +} + +// Returns the offset to the start of frame's raw data. +uint64_t Frame::PayloadOffset() const { return mOffset + mHeader.HeaderSize(); } + +// Returns the length of the frame's raw data (excluding the header) in bytes. +size_t Frame::PayloadLength() const { + // TODO: If fields are zero'd when invalid, this check wouldn't be + // necessary. + if (!mHeader.IsValid()) { + return 0; + } + + return mHeader.mFrameLength - mHeader.HeaderSize(); +} + +// Returns the parsed frame header. +const FrameHeader& Frame::Header() const { return mHeader; } + +bool Frame::IsValid() const { return mHeader.IsValid(); } + +// Resets the frame header and data. +void Frame::Reset() { + mHeader.Reset(); + mOffset = 0; +} + +// Returns whether the valid +bool Frame::Parse(uint64_t aOffset, const uint8_t* aStart, + const uint8_t* aEnd) { + MOZ_ASSERT(aStart && aEnd && aStart <= aEnd); + + bool found = false; + const uint8_t* ptr = aStart; + // Require at least 7 bytes of data at the end of the buffer for the minimum + // ADTS frame header. + while (ptr < aEnd - 7 && !found) { + found = mHeader.Parse(Span(ptr, aEnd)); + ptr++; + } + + mOffset = aOffset + (static_cast<size_t>(ptr - aStart)) - 1u; + + return found; +} + +const Frame& FrameParser::CurrentFrame() { return mFrame; } + +const Frame& FrameParser::FirstFrame() const { return mFirstFrame; } + +void FrameParser::Reset() { + EndFrameSession(); + mFirstFrame.Reset(); +} + +void FrameParser::EndFrameSession() { mFrame.Reset(); } + +bool FrameParser::Parse(uint64_t aOffset, const uint8_t* aStart, + const uint8_t* aEnd) { + const bool found = mFrame.Parse(aOffset, aStart, aEnd); + + if (mFrame.Length() && !mFirstFrame.Length()) { + mFirstFrame = mFrame; + } + + return found; +} + +// Initialize the AAC AudioSpecificConfig. +// Only handles two-byte version for AAC-LC. +void InitAudioSpecificConfig(const ADTS::Frame& frame, + MediaByteBuffer* aBuffer) { + const ADTS::FrameHeader& header = frame.Header(); + MOZ_ASSERT(header.IsValid()); + + int audioObjectType = header.mObjectType; + int samplingFrequencyIndex = header.mSamplingIndex; + int channelConfig = header.mChannelConfig; + + uint8_t asc[2]; + asc[0] = (audioObjectType & 0x1F) << 3 | (samplingFrequencyIndex & 0x0E) >> 1; + asc[1] = (samplingFrequencyIndex & 0x01) << 7 | (channelConfig & 0x0F) << 3; + + aBuffer->AppendElements(asc, 2); +} + +}; // namespace ADTS +}; // namespace mozilla + +#undef LOG +#undef ADTSLOG +#undef ADTSLOGV diff --git a/dom/media/platforms/agnostic/bytestreams/Adts.h b/dom/media/platforms/agnostic/bytestreams/Adts.h index c2b6b558b6..e6d20806ab 100644 --- a/dom/media/platforms/agnostic/bytestreams/Adts.h +++ b/dom/media/platforms/agnostic/bytestreams/Adts.h @@ -6,17 +6,124 @@ #define ADTS_H_ #include <stdint.h> +#include "MediaData.h" +#include "mozilla/Result.h" namespace mozilla { class MediaRawData; -class Adts { +namespace ADTS { + +// adts::FrameHeader - Holds the ADTS frame header and its parsing +// state. +// +// ADTS Frame Structure +// +// 11111111 1111BCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP(QQQQQQQQ +// QQQQQQQQ) +// +// Header consists of 7 or 9 bytes(without or with CRC). +// Letter Length(bits) Description +// { sync } 12 syncword 0xFFF, all bits must be 1 +// B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 +// C 2 Layer: always 0 +// D 1 protection absent, Warning, set to 1 if there is no +// CRC and 0 if there is CRC +// E 2 profile, the MPEG-4 Audio Object Type minus 1 +// F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) +// H 3 MPEG-4 Channel Configuration (in the case of 0, the +// channel configuration is sent via an in-band PCE) +// M 13 frame length, this value must include 7 or 9 bytes of +// header length: FrameLength = +// (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) +// O 11 Buffer fullness +// P 2 Number of AAC frames(RDBs) in ADTS frame minus 1, for +// maximum compatibility always use 1 AAC frame per ADTS +// frame +// Q 16 CRC if protection absent is 0 +class FrameHeader { public: - static int8_t GetFrequencyIndex(uint32_t aSamplesPerSecond); - static bool ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex, - int8_t aProfile, mozilla::MediaRawData* aSample); - static bool RevertSample(MediaRawData* aSample); + uint32_t mFrameLength{}; + uint32_t mSampleRate{}; + uint32_t mSamples{}; + uint32_t mChannels{}; + uint8_t mObjectType{}; + uint8_t mSamplingIndex{}; + uint8_t mChannelConfig{}; + uint8_t mNumAACFrames{}; + bool mHaveCrc{}; + + // Returns whether aPtr matches a valid ADTS header sync marker + static bool MatchesSync(const Span<const uint8_t>& aData); + FrameHeader(); + // Header size + uint64_t HeaderSize() const; + bool IsValid() const; + // Resets the state to allow for a new parsing session. + void Reset(); + + // Returns whether the byte creates a valid sequence up to this point. + bool Parse(const Span<const uint8_t>& aData); }; +class Frame { + public: + Frame(); + + uint64_t Offset() const; + size_t Length() const; + // Returns the offset to the start of frame's raw data. + uint64_t PayloadOffset() const; + + size_t PayloadLength() const; + // Returns the parsed frame header. + const FrameHeader& Header() const; + bool IsValid() const; + // Resets the frame header and data. + void Reset(); + // Returns whether the valid + bool Parse(uint64_t aOffset, const uint8_t* aStart, const uint8_t* aEnd); + + private: + // The offset to the start of the header. + uint64_t mOffset; + // The currently parsed frame header. + FrameHeader mHeader; +}; + +class FrameParser { + public: + // Returns the currently parsed frame. Reset via Reset or EndFrameSession. + const Frame& CurrentFrame(); + // Returns the first parsed frame. Reset via Reset. + const Frame& FirstFrame() const; + // Resets the parser. Don't use between frames as first frame data is reset. + void Reset(); + // Clear the last parsed frame to allow for next frame parsing, i.e.: + // - sets PrevFrame to CurrentFrame + // - resets the CurrentFrame + // - resets ID3Header if no valid header was parsed yet + void EndFrameSession(); + // Parses contents of given ByteReader for a valid frame header and returns + // true if one was found. After returning, the variable passed to + // 'aBytesToSkip' holds the amount of bytes to be skipped (if any) in order to + // jump across a large ID3v2 tag spanning multiple buffers. + bool Parse(uint64_t aOffset, const uint8_t* aStart, const uint8_t* aEnd); + + private: + // We keep the first parsed frame around for static info access, the + // previously parsed frame for debugging and the currently parsed frame. + Frame mFirstFrame; + Frame mFrame; +}; + +// Extract the audiospecificconfig from an ADTS header +void InitAudioSpecificConfig(const Frame& aFrame, MediaByteBuffer* aBuffer); +bool StripHeader(MediaRawData* aSample); +Result<uint8_t, bool> GetFrequencyIndex(uint32_t aSamplesPerSecond); +bool ConvertSample(uint16_t aChannelCount, uint8_t aFrequencyIndex, + uint8_t aProfile, mozilla::MediaRawData* aSample); +bool RevertSample(MediaRawData* aSample); +} // namespace ADTS } // namespace mozilla #endif diff --git a/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp b/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp index 086936dcc6..4721ddefc3 100644 --- a/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp +++ b/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp @@ -256,21 +256,21 @@ static Result<Ok, nsresult> FindStartCodeInternal(BufferReader& aBr) { while (aBr.Remaining() >= 6) { uint32_t x32; MOZ_TRY_VAR(x32, aBr.PeekU32()); - if ((x32 - 0x01010101) & (~x32) & 0x80808080) { - if ((x32 >> 8) == 0x000001) { + if ((x32 - 0x01010101) & (~x32) & 0x80808080) { // Has 0x00 byte(s). + if ((x32 >> 8) == 0x000001) { // 0x000001?? return Ok(); } - if (x32 == 0x000001) { + if ((x32 & 0xffffff) == 0x000001) { // 0x??000001 mozilla::Unused << aBr.Read(1); return Ok(); } - if ((x32 & 0xff) == 0) { + if ((x32 & 0xff) == 0) { // 0x??????00 const uint8_t* p = aBr.Peek(1); - if ((x32 & 0xff00) == 0 && p[4] == 1) { + if ((x32 & 0xff00) == 0 && p[4] == 1) { // 0x????0000,01 mozilla::Unused << aBr.Read(2); return Ok(); } - if (p[4] == 0 && p[5] == 1) { + if (p[4] == 0 && p[5] == 1) { // 0x??????00,00,01 mozilla::Unused << aBr.Read(3); return Ok(); } diff --git a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp index e71632e6d3..4c74fa8723 100644 --- a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp @@ -11,6 +11,9 @@ #include "GMPVideoDecoder.h" #include "MP4Decoder.h" #include "VPXDecoder.h" +#ifdef MOZ_AV1 +# include "AOMDecoder.h" +#endif namespace mozilla { @@ -45,6 +48,21 @@ static uint32_t ToCDMH264Profile(uint8_t aProfile) { return cdm::VideoCodecProfile::kUnknownVideoCodecProfile; } +#ifdef MOZ_AV1 +static uint32_t ToCDMAV1Profile(uint8_t aProfile) { + switch (aProfile) { + case 0: + return cdm::VideoCodecProfile::kAv1ProfileMain; + case 1: + return cdm::VideoCodecProfile::kAv1ProfileHigh; + case 2: + return cdm::VideoCodecProfile::kAv1ProfilePro; + default: + return cdm::VideoCodecProfile::kUnknownVideoCodecProfile; + } +} +#endif + RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMVideoDecoder::Init() { if (!mCDMParent) { // Must have failed to get the CDMParent from the ChromiumCDMProxy @@ -60,6 +78,16 @@ RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMVideoDecoder::Init() { ToCDMH264Profile(mConfig.mExtraData->SafeElementAt(1, 0)); config.mExtraData() = mConfig.mExtraData->Clone(); mConvertToAnnexB = true; +#ifdef MOZ_AV1 + } else if (AOMDecoder::IsAV1(mConfig.mMimeType)) { + AOMDecoder::AV1SequenceInfo seqInfo; + MediaResult seqHdrResult; + AOMDecoder::TryReadAV1CBox(mConfig.mExtraData, seqInfo, seqHdrResult); + config.mCodec() = cdm::VideoCodec::kCodecAv1; + config.mProfile() = NS_SUCCEEDED(seqHdrResult.Code()) + ? ToCDMAV1Profile(seqInfo.mProfile) + : cdm::VideoCodecProfile::kUnknownVideoCodecProfile; +#endif } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { config.mCodec() = cdm::VideoCodec::kCodecVp8; config.mProfile() = cdm::VideoCodecProfile::kProfileNotNeeded; @@ -105,9 +133,16 @@ nsCString ChromiumCDMVideoDecoder::GetDescriptionName() const { nsCString ChromiumCDMVideoDecoder::GetCodecName() const { if (MP4Decoder::IsH264(mConfig.mMimeType)) { return "h264"_ns; - } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + } +#ifdef MOZ_AV1 + if (AOMDecoder::IsAV1(mConfig.mMimeType)) { + return "av1"_ns; + } +#endif + if (VPXDecoder::IsVP8(mConfig.mMimeType)) { return "vp8"_ns; - } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + } + if (VPXDecoder::IsVP9(mConfig.mMimeType)) { return "vp9"_ns; } return "unknown"_ns; diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp index c143172073..a06dd30f89 100644 --- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -28,7 +28,7 @@ namespace mozilla { -typedef MozPromiseRequestHolder<DecryptPromise> DecryptPromiseRequestHolder; +using DecryptPromiseRequestHolder = MozPromiseRequestHolder<DecryptPromise>; DDLoggedTypeDeclNameAndBase(EMEDecryptor, MediaDataDecoder); @@ -45,7 +45,7 @@ class ADTSSampleConverter { // doesn't care what is set. , mProfile(aInfo.mProfile < 1 || aInfo.mProfile > 4 ? 2 : aInfo.mProfile), - mFrequencyIndex(Adts::GetFrequencyIndex(aInfo.mRate)) { + mFrequencyIndex(ADTS::GetFrequencyIndex(aInfo.mRate).unwrapOr(255)) { EME_LOG("ADTSSampleConvertor(): aInfo.mProfile=%" PRIi8 " aInfo.mExtendedProfile=%" PRIi8, aInfo.mProfile, aInfo.mExtendedProfile); @@ -56,17 +56,17 @@ class ADTSSampleConverter { } } bool Convert(MediaRawData* aSample) const { - return Adts::ConvertSample(mNumChannels, mFrequencyIndex, mProfile, + return ADTS::ConvertSample(mNumChannels, mFrequencyIndex, mProfile, aSample); } bool Revert(MediaRawData* aSample) const { - return Adts::RevertSample(aSample); + return ADTS::RevertSample(aSample); } private: const uint32_t mNumChannels; const uint8_t mProfile; - const uint8_t mFrequencyIndex; + const uint8_t mFrequencyIndex{}; }; class EMEDecryptor final : public MediaDataDecoder, @@ -124,7 +124,7 @@ class EMEDecryptor final : public MediaDataDecoder, mThroughputLimiter->Throttle(aSample) ->Then( mThread, __func__, - [self](RefPtr<MediaRawData> aSample) { + [self](const RefPtr<MediaRawData>& aSample) { self->mThrottleRequest.Complete(); self->AttemptDecode(aSample); }, @@ -223,7 +223,7 @@ class EMEDecryptor final : public MediaDataDecoder, mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); mThroughputLimiter->Flush(); for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { - auto holder = iter.UserData(); + auto* holder = iter.UserData(); holder->DisconnectIfExists(); iter.Remove(); } @@ -240,7 +240,7 @@ class EMEDecryptor final : public MediaDataDecoder, MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(), "Must wait for decoding to complete"); for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { - auto holder = iter.UserData(); + auto* holder = iter.UserData(); holder->DisconnectIfExists(); iter.Remove(); } @@ -323,7 +323,7 @@ RefPtr<MediaDataDecoder::DecodePromise> EMEMediaDataDecoderProxy::Decode( mSamplesWaitingForKey->WaitIfKeyNotUsable(sample) ->Then( mThread, __func__, - [self, this](RefPtr<MediaRawData> aSample) { + [self, this](const RefPtr<MediaRawData>& aSample) { mKeyRequest.Complete(); MediaDataDecoderProxy::Decode(aSample) diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp index f01c7e94e4..e9c41be1f0 100644 --- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp @@ -6,6 +6,9 @@ #include "GMPDecoderModule.h" +#ifdef MOZ_AV1 +# include "AOMDecoder.h" +#endif #include "DecoderDoctorDiagnostics.h" #include "GMPService.h" #include "GMPUtils.h" @@ -43,6 +46,9 @@ static already_AddRefed<MediaDataDecoderProxy> CreateDecoderWrapper( already_AddRefed<MediaDataDecoder> GMPDecoderModule::CreateVideoDecoder( const CreateDecoderParams& aParams) { if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) && +#ifdef MOZ_AV1 + !AOMDecoder::IsAV1(aParams.mConfig.mMimeType) && +#endif !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) && !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) { return nullptr; @@ -63,6 +69,10 @@ media::DecodeSupportSet GMPDecoderModule::SupportsMimeType( AutoTArray<nsCString, 2> tags; if (MP4Decoder::IsH264(aMimeType)) { tags.AppendElement("h264"_ns); +#ifdef MOZ_AV1 + } else if (AOMDecoder::IsAV1(aMimeType)) { + tags.AppendElement("av1"_ns); +#endif } else if (VPXDecoder::IsVP9(aMimeType)) { tags.AppendElement("vp9"_ns); } else if (VPXDecoder::IsVP8(aMimeType)) { diff --git a/dom/media/platforms/apple/AppleATDecoder.cpp b/dom/media/platforms/apple/AppleATDecoder.cpp index ed64b62d60..3065ac0c27 100644 --- a/dom/media/platforms/apple/AppleATDecoder.cpp +++ b/dom/media/platforms/apple/AppleATDecoder.cpp @@ -14,6 +14,9 @@ #include "mozilla/SyncRunnable.h" #include "mozilla/UniquePtr.h" #include "nsTArray.h" +#include "ADTSDemuxer.h" + +#include <array> #define LOG(...) DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__) #define LOGEX(_this, ...) \ @@ -62,6 +65,7 @@ AppleATDecoder::~AppleATDecoder() { RefPtr<MediaDataDecoder::InitPromise> AppleATDecoder::Init() { if (!mFormatID) { + LOG("AppleATDecoder::Init failure: unknown format ID"); return InitPromise::CreateAndReject( MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Non recognised format")), @@ -85,6 +89,7 @@ RefPtr<MediaDataDecoder::FlushPromise> AppleATDecoder::Flush() { } } if (mErrored) { + LOG("Flush error"); mParsedFramesForAACMagicCookie = 0; mMagicCookie.Clear(); ProcessShutdown(); @@ -188,18 +193,28 @@ RefPtr<MediaDataDecoder::DecodePromise> AppleATDecoder::Decode( MediaResult rv = NS_OK; if (!mConverter) { + LOG("Lazily initing the decoder"); rv = SetupDecoder(aSample); if (rv != NS_OK && rv != NS_ERROR_NOT_INITIALIZED) { + LOG("Decoder not initialized"); return DecodePromise::CreateAndReject(rv, __func__); } } + if (mIsADTS) { + bool rv = ADTS::StripHeader(aSample); + if (!rv) { + LOG("Stripping the ADTS header in AppleATDecoder failed"); + } + } + mQueuedSamples.AppendElement(aSample); if (rv == NS_OK) { for (size_t i = 0; i < mQueuedSamples.Length(); i++) { rv = DecodeSample(mQueuedSamples[i]); if (NS_FAILED(rv)) { + LOG("Decoding error"); mErrored = true; return DecodePromise::CreateAndReject(rv, __func__); } @@ -277,7 +292,7 @@ MediaResult AppleATDecoder::DecodeSample(MediaRawData* aSample) { } size_t numFrames = outputData.Length() / channels; - int rate = mOutputFormat.mSampleRate; + int rate = AssertedCast<int>(mOutputFormat.mSampleRate); media::TimeUnit duration(numFrames, rate); if (!duration.IsValid()) { NS_WARNING("Invalid count of accumulated audio samples"); @@ -340,8 +355,8 @@ MediaResult AppleATDecoder::GetInputAudioDescription( aDesc.mChannelsPerFrame = mConfig.mChannels; aDesc.mSampleRate = mConfig.mRate; UInt32 inputFormatSize = sizeof(aDesc); - OSStatus rv = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, - &inputFormatSize, &aDesc); + OSStatus rv = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, + nullptr, &inputFormatSize, &aDesc); if (NS_WARN_IF(rv)) { return MediaResult( NS_ERROR_FAILURE, @@ -419,7 +434,7 @@ nsresult AppleATDecoder::SetupChannelLayout() { UInt32 propertySize; UInt32 size; OSStatus status = AudioConverterGetPropertyInfo( - mConverter, kAudioConverterOutputChannelLayout, &propertySize, NULL); + mConverter, kAudioConverterOutputChannelLayout, &propertySize, nullptr); if (status || !propertySize) { LOG("Couldn't get channel layout property (%s)", FourCC2Str(status)); return NS_ERROR_FAILURE; @@ -504,15 +519,36 @@ MediaResult AppleATDecoder::SetupDecoder(MediaRawData* aSample) { MOZ_ASSERT(mThread->IsOnCurrentThread()); static const uint32_t MAX_FRAMES = 2; + bool isADTS = + ADTS::FrameHeader::MatchesSync(Span{aSample->Data(), aSample->Size()}); + + if (isADTS) { + ADTS::FrameParser parser; + if (!parser.Parse(0, aSample->Data(), aSample->Data() + aSample->Size())) { + LOG("ADTS frame parsing error"); + return NS_ERROR_NOT_INITIALIZED; + } + + AudioCodecSpecificBinaryBlob blob; + ADTS::InitAudioSpecificConfig(parser.FirstFrame(), blob.mBinaryBlob); + mConfig.mCodecSpecificConfig = AudioCodecSpecificVariant{std::move(blob)}; + mConfig.mProfile = mConfig.mExtendedProfile = + parser.FirstFrame().Header().mObjectType; + mIsADTS = true; + } + if (mFormatID == kAudioFormatMPEG4AAC && mConfig.mExtendedProfile == 2 && mParsedFramesForAACMagicCookie < MAX_FRAMES) { + LOG("Attempting to get implicit AAC magic cookie"); // Check for implicit SBR signalling if stream is AAC-LC // This will provide us with an updated magic cookie for use with // GetInputAudioDescription. if (NS_SUCCEEDED(GetImplicitAACMagicCookie(aSample)) && - !mMagicCookie.Length()) { + !mMagicCookie.Length() && !isADTS) { // nothing found yet, will try again later + LOG("Getting implicit AAC magic cookie failed"); mParsedFramesForAACMagicCookie++; + LOG("Not initialized -- need magic cookie"); return NS_ERROR_NOT_INITIALIZED; } // An error occurred, fallback to using default stream description @@ -538,6 +574,7 @@ MediaResult AppleATDecoder::SetupDecoder(MediaRawData* aSample) { MediaResult rv = GetInputAudioDescription(inputFormat, magicCookie); if (NS_FAILED(rv)) { + LOG("GetInputAudioDescription failure"); return rv; } // Fill in the output format manually. @@ -617,28 +654,41 @@ static void _SampleCallback(void* aSBR, UInt32 aNumBytes, UInt32 aNumPackets, const void* aData, AudioStreamPacketDescription* aPackets) {} -nsresult AppleATDecoder::GetImplicitAACMagicCookie( - const MediaRawData* aSample) { +nsresult AppleATDecoder::GetImplicitAACMagicCookie(MediaRawData* aSample) { MOZ_ASSERT(mThread->IsOnCurrentThread()); - // Prepend ADTS header to AAC audio. - RefPtr<MediaRawData> adtssample(aSample->Clone()); - if (!adtssample) { - return NS_ERROR_OUT_OF_MEMORY; - } - int8_t frequency_index = Adts::GetFrequencyIndex(mConfig.mRate); + bool isADTS = + ADTS::FrameHeader::MatchesSync(Span{aSample->Data(), aSample->Size()}); - bool rv = Adts::ConvertSample(mConfig.mChannels, frequency_index, - mConfig.mProfile, adtssample); - if (!rv) { - NS_WARNING("Failed to apply ADTS header"); - return NS_ERROR_FAILURE; + RefPtr<MediaRawData> adtssample = aSample; + + if (!isADTS) { + // Prepend ADTS header to AAC audio. + adtssample = aSample->Clone(); + if (!adtssample) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto frequency_index = ADTS::GetFrequencyIndex(mConfig.mRate); + + if (frequency_index.isErr()) { + LOG("%d isn't a valid rate for AAC", mConfig.mRate); + return NS_ERROR_FAILURE; + } + + // Arbitrarily pick main profile if not specified + int profile = mConfig.mProfile ? mConfig.mProfile : 1; + bool rv = ADTS::ConvertSample(mConfig.mChannels, frequency_index.unwrap(), + profile, adtssample); + if (!rv) { + LOG("Failed to apply ADTS header"); + return NS_ERROR_FAILURE; + } } if (!mStream) { OSStatus rv = AudioFileStreamOpen(this, _MetadataCallback, _SampleCallback, kAudioFileAAC_ADTSType, &mStream); if (rv) { - NS_WARNING("Couldn't open AudioFileStream"); + LOG("Couldn't open AudioFileStream"); return NS_ERROR_FAILURE; } } @@ -646,7 +696,7 @@ nsresult AppleATDecoder::GetImplicitAACMagicCookie( OSStatus status = AudioFileStreamParseBytes( mStream, adtssample->Size(), adtssample->Data(), 0 /* discontinuity */); if (status) { - NS_WARNING("Couldn't parse sample"); + LOG("Couldn't parse sample"); } if (status || mFileStreamError || mMagicCookie.Length()) { diff --git a/dom/media/platforms/apple/AppleATDecoder.h b/dom/media/platforms/apple/AppleATDecoder.h index d7aba2aacb..392b39993f 100644 --- a/dom/media/platforms/apple/AppleATDecoder.h +++ b/dom/media/platforms/apple/AppleATDecoder.h @@ -38,7 +38,7 @@ class AppleATDecoder final : public MediaDataDecoder, nsCString GetCodecName() const override; // Callbacks also need access to the config. - const AudioInfo mConfig; + AudioInfo mConfig; // Use to extract magic cookie for HE-AAC detection. nsTArray<uint8_t> mMagicCookie; @@ -67,11 +67,12 @@ class AppleATDecoder final : public MediaDataDecoder, // Setup AudioConverter once all information required has been gathered. // Will return NS_ERROR_NOT_INITIALIZED if more data is required. MediaResult SetupDecoder(MediaRawData* aSample); - nsresult GetImplicitAACMagicCookie(const MediaRawData* aSample); + nsresult GetImplicitAACMagicCookie(MediaRawData* aSample); nsresult SetupChannelLayout(); uint32_t mParsedFramesForAACMagicCookie; uint32_t mEncoderDelay = 0; uint64_t mTotalMediaFrames = 0; + bool mIsADTS = false; bool mErrored; }; diff --git a/dom/media/platforms/apple/AppleDecoderModule.cpp b/dom/media/platforms/apple/AppleDecoderModule.cpp index 520685fff6..c54593a495 100644 --- a/dom/media/platforms/apple/AppleDecoderModule.cpp +++ b/dom/media/platforms/apple/AppleDecoderModule.cpp @@ -124,8 +124,7 @@ DecodeSupportSet AppleDecoderModule::Supports( case MediaCodec::VP8: [[fallthrough]]; case MediaCodec::VP9: - if (StaticPrefs::media_ffvpx_enabled() && - StaticPrefs::media_rdd_vpx_enabled() && + if (StaticPrefs::media_rdd_vpx_enabled() && StaticPrefs::media_utility_ffvpx_enabled()) { dss += DecodeSupport::SoftwareDecode; } @@ -233,6 +232,7 @@ bool AppleDecoderModule::CanCreateHWDecoder(MediaCodec aCodec) { /* static */ bool AppleDecoderModule::RegisterSupplementalVP9Decoder() { +#ifdef XP_MACOSX static bool sRegisterIfAvailable = []() { if (__builtin_available(macos 11.0, *)) { VTRegisterSupplementalVideoDecoderIfAvailable(kCMVideoCodecType_VP9); @@ -241,6 +241,9 @@ bool AppleDecoderModule::RegisterSupplementalVP9Decoder() { return false; }(); return sRegisterIfAvailable; +#else // iOS + return false; +#endif } /* static */ diff --git a/dom/media/platforms/apple/AppleVTDecoder.cpp b/dom/media/platforms/apple/AppleVTDecoder.cpp index aae9c1fc9b..ae34c2d142 100644 --- a/dom/media/platforms/apple/AppleVTDecoder.cpp +++ b/dom/media/platforms/apple/AppleVTDecoder.cpp @@ -7,7 +7,7 @@ #include "AppleVTDecoder.h" #include <CoreVideo/CVPixelBufferIOSurface.h> -#include <IOSurface/IOSurface.h> +#include <IOSurface/IOSurfaceRef.h> #include <limits> #include "AppleDecoderModule.h" @@ -486,7 +486,6 @@ void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage, // Unlock the returned image data. CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly); } else { -#ifndef MOZ_WIDGET_UIKIT // Set pixel buffer properties on aImage before we extract its surface. // This ensures that we can use defined enums to set values instead // of later setting magic CFSTR values on the surface itself. @@ -535,9 +534,6 @@ void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage, info.mDisplay, aFrameRef.byte_offset, aFrameRef.composition_timestamp, aFrameRef.duration, image.forget(), aFrameRef.is_sync_point, aFrameRef.decode_timestamp); -#else - MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS"); -#endif } if (!data) { @@ -719,7 +715,6 @@ CFDictionaryRef AppleVTDecoder::CreateOutputConfiguration() { &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } -#ifndef MOZ_WIDGET_UIKIT // Output format type: bool is10Bit = (gfx::BitDepthForColorDepth(mColorDepth) == 10); @@ -754,9 +749,6 @@ CFDictionaryRef AppleVTDecoder::CreateOutputConfiguration() { return CFDictionaryCreate( kCFAllocatorDefault, outputKeys, outputValues, ArrayLength(outputKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); -#else - MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS"); -#endif } } // namespace mozilla diff --git a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp index 43041f81ea..1e8e488e25 100644 --- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp @@ -164,7 +164,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, int16_t* data = reinterpret_cast<int16_t**>(aFrame->data)[0]; for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = AudioSampleToFloat(*data++); + *tmp++ = ConvertAudioSample<float>(*data++); } } } else if (aFrame->format == AV_SAMPLE_FMT_S16P) { @@ -174,7 +174,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, int16_t** data = reinterpret_cast<int16_t**>(aFrame->data); for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = AudioSampleToFloat(data[channel][frame]); + *tmp++ = ConvertAudioSample<float>(data[channel][frame]); } } } else if (aFrame->format == AV_SAMPLE_FMT_S32) { @@ -183,7 +183,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, int32_t* data = reinterpret_cast<int32_t**>(aFrame->data)[0]; for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = AudioSampleToFloat(*data++); + *tmp++ = ConvertAudioSample<float>(*data++); } } } else if (aFrame->format == AV_SAMPLE_FMT_S32P) { @@ -193,7 +193,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, int32_t** data = reinterpret_cast<int32_t**>(aFrame->data); for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = AudioSampleToFloat(data[channel][frame]); + *tmp++ = ConvertAudioSample<float>(data[channel][frame]); } } } else if (aFrame->format == AV_SAMPLE_FMT_U8) { @@ -202,7 +202,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, uint8_t* data = reinterpret_cast<uint8_t**>(aFrame->data)[0]; for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = UInt8bitToAudioSample<AudioDataValue>(*data++); + *tmp++ = ConvertAudioSample<float>(*data++); } } } else if (aFrame->format == AV_SAMPLE_FMT_U8P) { @@ -212,7 +212,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame, uint8_t** data = reinterpret_cast<uint8_t**>(aFrame->data); for (uint32_t frame = 0; frame < aNumAFrames; frame++) { for (uint32_t channel = 0; channel < aNumChannels; channel++) { - *tmp++ = UInt8bitToAudioSample<AudioDataValue>(data[channel][frame]); + *tmp++ = ConvertAudioSample<float>(data[channel][frame]); } } } diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp index 4a30f2dd2d..bfb3105a57 100644 --- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp @@ -177,9 +177,6 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() { AV_FUNC(av_packet_alloc, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)) AV_FUNC(av_packet_unref, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)) AV_FUNC(av_packet_free, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)) - AV_FUNC_OPTION(av_rdft_init, AV_FUNC_AVCODEC_ALL) - AV_FUNC_OPTION(av_rdft_calc, AV_FUNC_AVCODEC_ALL) - AV_FUNC_OPTION(av_rdft_end, AV_FUNC_AVCODEC_ALL) AV_FUNC(avcodec_descriptor_get, AV_FUNC_AVCODEC_ALL) AV_FUNC(av_log_set_level, AV_FUNC_AVUTIL_ALL) AV_FUNC(av_malloc, AV_FUNC_AVUTIL_ALL) @@ -254,6 +251,10 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() { AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60) #endif + + AV_FUNC_OPTION(av_tx_init, AV_FUNC_AVUTIL_ALL) + AV_FUNC_OPTION(av_tx_uninit, AV_FUNC_AVUTIL_ALL) + #undef AV_FUNC #undef AV_FUNC_OPTION diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h index 98ab2f7930..eacbba286a 100644 --- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h +++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h @@ -5,9 +5,9 @@ #ifndef __FFmpegLibWrapper_h__ #define __FFmpegLibWrapper_h__ -#include "FFmpegRDFTTypes.h" // for AvRdftInitFn, etc. #include "mozilla/Attributes.h" #include "mozilla/Types.h" +#include "ffvpx/tx.h" struct AVCodec; struct AVCodecContext; @@ -148,11 +148,6 @@ struct MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FFmpegLibWrapper { int (*avcodec_send_frame)(AVCodecContext* avctx, const AVFrame* frame); int (*avcodec_receive_frame)(AVCodecContext* avctx, AVFrame* frame); - // libavcodec optional - AvRdftInitFn av_rdft_init; - AvRdftCalcFn av_rdft_calc; - AvRdftEndFn av_rdft_end; - // libavutil void (*av_log_set_level)(int level); void* (*av_malloc)(size_t size); @@ -216,6 +211,10 @@ struct MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FFmpegLibWrapper { void* (*vaGetDisplayDRM)(int fd); #endif + // Only ever used with ffvpx + decltype(::av_tx_init)* av_tx_init; + decltype(::av_tx_uninit)* av_tx_uninit; + PRLibrary* mAVCodecLib; PRLibrary* mAVUtilLib; #ifdef MOZ_WIDGET_GTK diff --git a/dom/media/platforms/ffmpeg/FFmpegRDFTTypes.h b/dom/media/platforms/ffmpeg/FFmpegRDFTTypes.h deleted file mode 100644 index cb3e2476fb..0000000000 --- a/dom/media/platforms/ffmpeg/FFmpegRDFTTypes.h +++ /dev/null @@ -1,34 +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 https://mozilla.org/MPL/2.0/. */ - -#ifndef FFmpegRDFTTypes_h -#define FFmpegRDFTTypes_h - -struct RDFTContext; - -typedef float FFTSample; - -enum RDFTransformType { - DFT_R2C, - IDFT_C2R, - IDFT_R2C, - DFT_C2R, -}; - -extern "C" { - -typedef RDFTContext* (*AvRdftInitFn)(int nbits, enum RDFTransformType trans); -typedef void (*AvRdftCalcFn)(RDFTContext* s, FFTSample* data); -typedef void (*AvRdftEndFn)(RDFTContext* s); -} - -struct FFmpegRDFTFuncs { - AvRdftInitFn init; - AvRdftCalcFn calc; - AvRdftEndFn end; -}; - -#endif // FFmpegRDFTTypes_h diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp index dcc3d9a88d..a3cfdf1b1d 100644 --- a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp @@ -504,128 +504,64 @@ MediaResult FFmpegVideoEncoder<LIBAV_VER>::InitInternal() { mCodecContext->flags |= AV_CODEC_FLAG_FRAME_DURATION; #endif mCodecContext->gop_size = static_cast<int>(mConfig.mKeyframeInterval); - // TODO (bug 1872871): Move the following extra settings to some helpers - // instead. + if (mConfig.mUsage == MediaDataEncoder::Usage::Realtime) { mLib->av_opt_set(mCodecContext->priv_data, "deadline", "realtime", 0); // Explicitly ask encoder do not keep in flight at any one time for // lookahead purposes. mLib->av_opt_set(mCodecContext->priv_data, "lag-in-frames", "0", 0); } - // Apply SVC settings. - if (Maybe<VPXSVCSetting> svc = - GetVPXSVCSetting(mConfig.mScalabilityMode, mConfig.mBitrate)) { - // For libvpx. - if (mCodecName == "libvpx" || mCodecName == "libvpx-vp9") { - // Show a warning if mScalabilityMode mismatches mNumTemporalLayers - if (mConfig.mCodecSpecific) { - if (mConfig.mCodecSpecific->is<VP8Specific>() || - mConfig.mCodecSpecific->is<VP9Specific>()) { - const uint8_t numTemporalLayers = - mConfig.mCodecSpecific->is<VP8Specific>() - ? mConfig.mCodecSpecific->as<VP8Specific>().mNumTemporalLayers - : mConfig.mCodecSpecific->as<VP9Specific>() - .mNumTemporalLayers; - if (numTemporalLayers != svc->mNumberLayers) { - FFMPEGV_LOG( - "Force using %zu layers defined in scalability mode instead of " - "the %u layers defined in VP8/9Specific", - svc->mNumberLayers, numTemporalLayers); - } - } - } - // Set ts_layering_mode. - nsPrintfCString parameters("ts_layering_mode=%u", svc->mLayeringMode); - // Set ts_target_bitrate. - parameters.Append(":ts_target_bitrate="); - for (size_t i = 0; i < svc->mTargetBitrates.Length(); ++i) { - if (i > 0) { - parameters.Append(","); - } - parameters.AppendPrintf("%d", svc->mTargetBitrates[i]); - } - // TODO: Set ts_number_layers, ts_periodicity, ts_layer_id and - // ts_rate_decimator if they are different from the preset values in - // ts_layering_mode. - - // Set parameters into ts-parameters. - mLib->av_opt_set(mCodecContext->priv_data, "ts-parameters", - parameters.get(), 0); - - // FFmpegVideoEncoder would be reset after Drain(), so mSVCInfo should be - // reset() before emplace(). - mSVCInfo.reset(); - mSVCInfo.emplace(std::move(svc->mLayerIds)); - - // TODO: layer settings should be changed dynamically when the frame's - // color space changed. - } else { - FFMPEGV_LOG("SVC setting is not implemented for %s codec", - mCodecName.get()); - } + if (Maybe<SVCSettings> settings = GetSVCSettings()) { + SVCSettings s = settings.extract(); + mLib->av_opt_set(mCodecContext->priv_data, s.mSettingKeyValue.first.get(), + s.mSettingKeyValue.second.get(), 0); + + // FFmpegVideoEncoder is reset after Drain(), so mSVCInfo should be reset() + // before emplace(). + mSVCInfo.reset(); + mSVCInfo.emplace(std::move(s.mTemporalLayerIds)); + + // TODO: layer settings should be changed dynamically when the frame's + // color space changed. } - // Apply codec specific settings. - nsAutoCString codecSpecificLog; - if (mConfig.mCodecSpecific) { - if (mConfig.mCodecSpecific->is<H264Specific>()) { - // For libx264. - if (mCodecName == "libx264") { - codecSpecificLog.Append(", H264:"); - - const H264Specific& specific = - mConfig.mCodecSpecific->as<H264Specific>(); - - // Set profile. - Maybe<H264Setting> profile = GetH264Profile(specific.mProfile); - if (!profile) { - FFMPEGV_LOG("failed to get h264 profile"); - return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, - RESULT_DETAIL("H264 profile is unknown")); - } - codecSpecificLog.Append( - nsPrintfCString(" profile - %d", profile->mValue)); - mCodecContext->profile = profile->mValue; - if (!profile->mString.IsEmpty()) { - codecSpecificLog.AppendPrintf(" (%s)", profile->mString.get()); - mLib->av_opt_set(mCodecContext->priv_data, "profile", - profile->mString.get(), 0); - } - - // Set level. - Maybe<H264Setting> level = GetH264Level(specific.mLevel); - if (!level) { - FFMPEGV_LOG("failed to get h264 level"); - return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, - RESULT_DETAIL("H264 level is unknown")); - } - codecSpecificLog.AppendPrintf(", level %d (%s)", level->mValue, - level->mString.get()); - mCodecContext->level = level->mValue; - MOZ_ASSERT(!level->mString.IsEmpty()); - mLib->av_opt_set(mCodecContext->priv_data, "level", - level->mString.get(), 0); - - // Set format: libx264's default format is annexb - if (specific.mFormat == H264BitStreamFormat::AVC) { - codecSpecificLog.Append(", AVCC"); - mLib->av_opt_set(mCodecContext->priv_data, "x264-params", "annexb=0", - 0); - // mCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER - // if we don't want to append SPS/PPS data in all keyframe - // (LIBAVCODEC_VERSION_MAJOR >= 57 only). - } else { - codecSpecificLog.Append(", AnnexB"); - // Set annexb explicitly even if it's default format. - mLib->av_opt_set(mCodecContext->priv_data, "x264-params", "annexb=1", - 0); - } - } else { - FFMPEGV_LOG("H264 settings is not implemented for codec %s ", - mCodecName.get()); + + nsAutoCString h264Log; + if (mConfig.mCodecSpecific && mConfig.mCodecSpecific->is<H264Specific>()) { + // TODO: Set profile, level, avcc/annexb for openh264 and others. + if (mCodecName == "libx264") { + const H264Specific& h264Specific = + mConfig.mCodecSpecific->as<H264Specific>(); + H264Settings s = GetH264Settings(h264Specific); + mCodecContext->profile = s.mProfile; + mCodecContext->level = s.mLevel; + for (const auto& pair : s.mSettingKeyValuePairs) { + mLib->av_opt_set(mCodecContext->priv_data, pair.first.get(), + pair.second.get(), 0); } + + // Log the settings. + // When using profile other than EXTENDED, the profile string is in the + // first element of mSettingKeyValuePairs, while EXTENDED profile has no + // profile string. + + MOZ_ASSERT_IF( + s.mSettingKeyValuePairs.Length() != 3, + h264Specific.mProfile == H264_PROFILE::H264_PROFILE_EXTENDED); + const char* profileStr = s.mSettingKeyValuePairs.Length() == 3 + ? s.mSettingKeyValuePairs[0].second.get() + : "extended"; + const char* levelStr = s.mSettingKeyValuePairs.Length() == 3 + ? s.mSettingKeyValuePairs[1].second.get() + : s.mSettingKeyValuePairs[0].second.get(); + const char* formatStr = + h264Specific.mFormat == H264BitStreamFormat::AVC ? "AVCC" : "AnnexB"; + h264Log.AppendPrintf(", H264: profile - %d (%s), level %d (%s), %s", + mCodecContext->profile, profileStr, + mCodecContext->level, levelStr, formatStr); } } + // TODO: keyint_min, max_b_frame? // - if mConfig.mDenoising is set: av_opt_set_int(mCodecContext->priv_data, // "noise_sensitivity", x, 0), where the x is from 0(disabled) to 6. @@ -657,7 +593,7 @@ MediaResult FFmpegVideoEncoder<LIBAV_VER>::InitInternal() { static_cast<int64_t>(mCodecContext->bit_rate), mCodecContext->width, mCodecContext->height, mCodecContext->time_base.num, mCodecContext->time_base.den, - codecSpecificLog.IsEmpty() ? "" : codecSpecificLog.get()); + h264Log.IsEmpty() ? "" : h264Log.get()); return MediaResult(NS_OK); } @@ -1152,4 +1088,99 @@ void FFmpegVideoEncoder<LIBAV_VER>::ForceEnablingFFmpegDebugLogs() { #endif // DEBUG } +Maybe<FFmpegVideoEncoder<LIBAV_VER>::SVCSettings> +FFmpegVideoEncoder<LIBAV_VER>::GetSVCSettings() { + MOZ_ASSERT(!mCodecName.IsEmpty()); + + // TODO: Add support for AV1 and H264. + if (mCodecName != "libvpx" && mCodecName != "libvpx-vp9") { + FFMPEGV_LOG("SVC setting is not implemented for %s codec", + mCodecName.get()); + return Nothing(); + } + + Maybe<VPXSVCSetting> svc = + GetVPXSVCSetting(mConfig.mScalabilityMode, mConfig.mBitrate); + if (!svc) { + FFMPEGV_LOG("No SVC settings obtained. Skip"); + return Nothing(); + } + + // Check if the number of temporal layers in codec specific settings matches + // the number of layers for the given scalability mode. + + auto GetNumTemporalLayers = [&]() -> uint8_t { + uint8_t layers = 0; + if (mConfig.mCodecSpecific) { + if (mConfig.mCodecSpecific->is<VP8Specific>()) { + layers = mConfig.mCodecSpecific->as<VP8Specific>().mNumTemporalLayers; + MOZ_ASSERT(layers > 0); + } else if (mConfig.mCodecSpecific->is<VP9Specific>()) { + layers = mConfig.mCodecSpecific->as<VP9Specific>().mNumTemporalLayers; + MOZ_ASSERT(layers > 0); + } + } + return layers; + }; + + DebugOnly<uint8_t> numTemporalLayers = GetNumTemporalLayers(); + MOZ_ASSERT_IF(numTemporalLayers > 0, numTemporalLayers == svc->mNumberLayers); + + // Form an SVC setting string for libvpx. + + nsPrintfCString parameters("ts_layering_mode=%u", svc->mLayeringMode); + parameters.Append(":ts_target_bitrate="); + for (size_t i = 0; i < svc->mTargetBitrates.Length(); ++i) { + if (i > 0) { + parameters.Append(","); + } + parameters.AppendPrintf("%d", svc->mTargetBitrates[i]); + } + + // TODO: Set ts_number_layers, ts_periodicity, ts_layer_id and + // ts_rate_decimator if they are different from the preset values in + // ts_layering_mode. + + return Some( + SVCSettings{std::move(svc->mLayerIds), + std::make_pair("ts-parameters"_ns, std::move(parameters))}); +} + +FFmpegVideoEncoder<LIBAV_VER>::H264Settings FFmpegVideoEncoder< + LIBAV_VER>::GetH264Settings(const H264Specific& aH264Specific) { + MOZ_ASSERT(mCodecName == "libx264", + "GetH264Settings is libx264-only for now"); + + nsTArray<std::pair<nsCString, nsCString>> keyValuePairs; + + Maybe<H264Setting> profile = GetH264Profile(aH264Specific.mProfile); + MOZ_RELEASE_ASSERT(profile.isSome()); + if (!profile->mString.IsEmpty()) { + keyValuePairs.AppendElement(std::make_pair("profile"_ns, profile->mString)); + } else { + MOZ_RELEASE_ASSERT(aH264Specific.mProfile == + H264_PROFILE::H264_PROFILE_EXTENDED); + } + + Maybe<H264Setting> level = GetH264Level(aH264Specific.mLevel); + MOZ_RELEASE_ASSERT(level.isSome()); + MOZ_RELEASE_ASSERT(!level->mString.IsEmpty()); + keyValuePairs.AppendElement(std::make_pair("level"_ns, level->mString)); + + // Set format: libx264's default format is annexb. + if (aH264Specific.mFormat == H264BitStreamFormat::AVC) { + keyValuePairs.AppendElement(std::make_pair("x264-params"_ns, "annexb=0")); + // mCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER + // if we don't want to append SPS/PPS data in all keyframe + // (LIBAVCODEC_VERSION_MAJOR >= 57 only). + } else { + // Set annexb explicitly even if it's default format. + keyValuePairs.AppendElement(std::make_pair("x264-params"_ns, "annexb=1")); + } + + return H264Settings{.mProfile = profile->mValue, + .mLevel = level->mValue, + .mSettingKeyValuePairs = std::move(keyValuePairs)}; +} + } // namespace mozilla diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h index 1bcdd3eaf9..07c433ddd7 100644 --- a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h +++ b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h @@ -75,6 +75,19 @@ class FFmpegVideoEncoder<LIBAV_VER> final : public MediaDataEncoder { Result<already_AddRefed<MediaByteBuffer>, nsresult> GetExtraData( AVPacket* aPacket); void ForceEnablingFFmpegDebugLogs(); + struct SVCSettings { + nsTArray<uint8_t> mTemporalLayerIds; + // A key-value pair for av_opt_set. + std::pair<nsCString, nsCString> mSettingKeyValue; + }; + Maybe<SVCSettings> GetSVCSettings(); + struct H264Settings { + int mProfile; + int mLevel; + // A list of key-value pairs for av_opt_set. + nsTArray<std::pair<nsCString, nsCString>> mSettingKeyValuePairs; + }; + H264Settings GetH264Settings(const H264Specific& aH264Specific); // This refers to a static FFmpegLibWrapper, so raw pointer is adequate. const FFmpegLibWrapper* mLib; diff --git a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp index ba9ca4834e..dfc8244f1d 100644 --- a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp +++ b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp @@ -145,19 +145,13 @@ already_AddRefed<PlatformEncoderModule> FFVPXRuntimeLinker::CreateEncoder() { } /* static */ -void FFVPXRuntimeLinker::GetRDFTFuncs(FFmpegRDFTFuncs* aOutFuncs) { +void FFVPXRuntimeLinker::GetFFTFuncs(FFmpegFFTFuncs* aOutFuncs) { []() MOZ_NO_THREAD_SAFETY_ANALYSIS { MOZ_ASSERT(sLinkStatus != LinkStatus_INIT); }(); - if (sFFVPXLib.av_rdft_init && sFFVPXLib.av_rdft_calc && - sFFVPXLib.av_rdft_end) { - aOutFuncs->init = sFFVPXLib.av_rdft_init; - aOutFuncs->calc = sFFVPXLib.av_rdft_calc; - aOutFuncs->end = sFFVPXLib.av_rdft_end; - } else { - NS_WARNING("RDFT functions expected but not found"); - *aOutFuncs = FFmpegRDFTFuncs(); // zero - } + MOZ_ASSERT(sFFVPXLib.av_tx_init && sFFVPXLib.av_tx_uninit); + aOutFuncs->init = sFFVPXLib.av_tx_init; + aOutFuncs->uninit = sFFVPXLib.av_tx_uninit; } } // namespace mozilla diff --git a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.h b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.h index e52f108272..dccd37c7da 100644 --- a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.h +++ b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.h @@ -11,8 +11,12 @@ #include "PlatformEncoderModule.h" #include "mozilla/StaticMutex.h" #include "mozilla/ThreadSafety.h" +#include "ffvpx/tx.h" -struct FFmpegRDFTFuncs; +struct FFmpegFFTFuncs { + decltype(av_tx_init)* init; + decltype(av_tx_uninit)* uninit; +}; namespace mozilla { @@ -23,7 +27,7 @@ class FFVPXRuntimeLinker { static already_AddRefed<PlatformEncoderModule> CreateEncoder(); // Call (on any thread) after Init(). - static void GetRDFTFuncs(FFmpegRDFTFuncs* aOutFuncs); + static void GetFFTFuncs(FFmpegFFTFuncs* aOutFuncs); private: // Provide critical-section for Init() and sLinkStatus. diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build index 34acf4e8d1..6f71c5cc12 100644 --- a/dom/media/platforms/moz.build +++ b/dom/media/platforms/moz.build @@ -47,19 +47,13 @@ DIRS += ["agnostic/bytestreams", "agnostic/eme", "agnostic/gmp", "omx"] if CONFIG["MOZ_WMF"]: DIRS += ["wmf"] -if CONFIG["MOZ_FFVPX"] or CONFIG["MOZ_FFMPEG"]: - # common code to either FFmpeg or FFVPX - EXPORTS += [ - "ffmpeg/FFmpegRDFTTypes.h", - ] - UNIFIED_SOURCES += [ - "ffmpeg/FFmpegLibWrapper.cpp", - ] +UNIFIED_SOURCES += [ + "ffmpeg/FFmpegLibWrapper.cpp", +] -if CONFIG["MOZ_FFVPX"]: - DIRS += [ - "ffmpeg/ffvpx", - ] +DIRS += [ + "ffmpeg/ffvpx", +] if CONFIG["MOZ_FFMPEG"]: DIRS += [ diff --git a/dom/media/platforms/wmf/MFCDMSession.cpp b/dom/media/platforms/wmf/MFCDMSession.cpp index cec783cbc6..b797898abb 100644 --- a/dom/media/platforms/wmf/MFCDMSession.cpp +++ b/dom/media/platforms/wmf/MFCDMSession.cpp @@ -304,8 +304,7 @@ void MFCDMSession::OnSessionKeyMessage( case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST: return dom::MediaKeyMessageType::Individualization_request; default: - MOZ_ASSERT_UNREACHABLE("Unknown session message type"); - return dom::MediaKeyMessageType::EndGuard_; + MOZ_CRASH("Unknown session message type"); } }; LOG("Notify 'keymessage' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get()); diff --git a/dom/media/platforms/wmf/MFMediaEngineAudioStream.cpp b/dom/media/platforms/wmf/MFMediaEngineAudioStream.cpp index 4acf26e041..969f817882 100644 --- a/dom/media/platforms/wmf/MFMediaEngineAudioStream.cpp +++ b/dom/media/platforms/wmf/MFMediaEngineAudioStream.cpp @@ -93,7 +93,7 @@ HRESULT MFMediaEngineAudioStream::CreateMediaType(const TrackInfo& aInfo, bool MFMediaEngineAudioStream::HasEnoughRawData() const { // If more than this much raw audio is queued, we'll hold off request more // audio. - return mRawDataQueueForFeedingEngine.Duration() >= + return mRawDataQueueForFeedingEngine.PreciseDuration() >= StaticPrefs::media_wmf_media_engine_raw_data_threshold_audio(); } diff --git a/dom/media/platforms/wmf/MFMediaEngineDecoderModule.cpp b/dom/media/platforms/wmf/MFMediaEngineDecoderModule.cpp index 5b99fb0f2c..e291ab6a54 100644 --- a/dom/media/platforms/wmf/MFMediaEngineDecoderModule.cpp +++ b/dom/media/platforms/wmf/MFMediaEngineDecoderModule.cpp @@ -6,6 +6,7 @@ #include "MFTDecoder.h" #include "VideoUtils.h" +#include "mozilla/gfx/gfxVars.h" #include "mozilla/MFMediaEngineParent.h" #include "mozilla/MFMediaEngineUtils.h" #include "mozilla/RemoteDecoderManagerChild.h" @@ -99,6 +100,11 @@ media::DecodeSupportSet MFMediaEngineDecoderModule::SupportInternal( if (!StaticPrefs::media_wmf_media_engine_enabled()) { return media::DecodeSupportSet{}; } + // Only support hardware decoding. + if (!gfx::gfxVars::CanUseHardwareVideoDecoding() && + !StaticPrefs::media_wmf_media_engine_bypass_gfx_blocklist()) { + return media::DecodeSupportSet{}; + } bool supports = false; WMFStreamType type = GetStreamTypeFromMimeType(aParams.MimeType()); if (type != WMFStreamType::Unknown) { @@ -107,13 +113,11 @@ media::DecodeSupportSet MFMediaEngineDecoderModule::SupportInternal( MOZ_LOG(sPDMLog, LogLevel::Debug, ("MFMediaEngine decoder %s requested type '%s'", supports ? "supports" : "rejects", aParams.MimeType().get())); - // We only support HEVC hardware decoding. - if (supports && type == WMFStreamType::HEVC) { - return media::DecodeSupport::HardwareDecode; + if (!supports) { + return media::DecodeSupportSet{}; } - // TODO : find a way to report accurate result. - return supports ? media::DecodeSupport::SoftwareDecode - : media::DecodeSupportSet{}; + return StreamTypeIsVideo(type) ? media::DecodeSupport::HardwareDecode + : media::DecodeSupport::SoftwareDecode; } static bool CreateMFTDecoderOnMTA(const WMFStreamType& aType) { diff --git a/dom/media/platforms/wmf/MFMediaEngineDecoderModule.h b/dom/media/platforms/wmf/MFMediaEngineDecoderModule.h index c23b9010cc..1c8de5a161 100644 --- a/dom/media/platforms/wmf/MFMediaEngineDecoderModule.h +++ b/dom/media/platforms/wmf/MFMediaEngineDecoderModule.h @@ -10,6 +10,8 @@ namespace mozilla { +// MFMediaEngineDecoderModule is used for the media engine playback, which only +// supports hardware decoding. class MFMediaEngineDecoderModule final : public PlatformDecoderModule { public: static void Init(); diff --git a/dom/media/platforms/wmf/MFMediaEngineStream.cpp b/dom/media/platforms/wmf/MFMediaEngineStream.cpp index 6dce37ee35..70ffa50142 100644 --- a/dom/media/platforms/wmf/MFMediaEngineStream.cpp +++ b/dom/media/platforms/wmf/MFMediaEngineStream.cpp @@ -107,7 +107,11 @@ MFMediaEngineStreamWrapper::NeedsConversion() const { } MFMediaEngineStream::MFMediaEngineStream() - : mIsShutdown(false), mIsSelected(false), mReceivedEOS(false) { + : mIsShutdown(false), + mIsSelected(false), + mRawDataQueueForFeedingEngine(true /* aEnablePreciseDuration */), + mRawDataQueueForGeneratingOutput(true /* aEnablePreciseDuration */), + mReceivedEOS(false) { MOZ_COUNT_CTOR(MFMediaEngineStream); } @@ -282,17 +286,8 @@ void MFMediaEngineStream::ReplySampleRequestIfPossible() { while (!mSampleRequestTokens.empty()) { mSampleRequestTokens.pop(); } - - SLOG("Notify end events"); - MOZ_ASSERT(mRawDataQueueForFeedingEngine.GetSize() == 0); MOZ_ASSERT(mSampleRequestTokens.empty()); - RETURN_VOID_IF_FAILED(mMediaEventQueue->QueueEventParamUnk( - MEEndOfStream, GUID_NULL, S_OK, nullptr)); - mEndedEvent.Notify(TrackType()); - PROFILER_MARKER_TEXT( - "MFMediaEngineStream:NotifyEnd", MEDIA_PLAYBACK, {}, - nsPrintfCString("stream=%s, id=%" PRIu64, GetDescriptionName().get(), - mStreamId)); + NotifyEndEvent(); return; } @@ -318,6 +313,18 @@ void MFMediaEngineStream::ReplySampleRequestIfPossible() { MEMediaSample, GUID_NULL, S_OK, inputSample.Get())); } +void MFMediaEngineStream::NotifyEndEvent() { + AssertOnTaskQueue(); + SLOG("Notify end event"); + MOZ_ASSERT(mRawDataQueueForFeedingEngine.GetSize() == 0); + RETURN_VOID_IF_FAILED(mMediaEventQueue->QueueEventParamUnk( + MEEndOfStream, GUID_NULL, S_OK, nullptr)); + mEndedEvent.Notify(TrackType()); + PROFILER_MARKER_TEXT("MFMediaEngineStream:NotifyEnd", MEDIA_PLAYBACK, {}, + nsPrintfCString("stream=%s, id=%" PRIu64, + GetDescriptionName().get(), mStreamId)); +} + bool MFMediaEngineStream::ShouldServeSamples() const { AssertOnTaskQueue(); return mParentSource && @@ -486,7 +493,7 @@ void MFMediaEngineStream::NotifyNewData(MediaRawData* aSample) { "], queue size=%zu, queue duration=%" PRId64, aSample->mTime.ToMicroseconds(), aSample->GetEndTime().ToMicroseconds(), mRawDataQueueForFeedingEngine.GetSize(), - mRawDataQueueForFeedingEngine.Duration()); + mRawDataQueueForFeedingEngine.PreciseDuration()); if (mReceivedEOS) { SLOG("Receive a new data, cancel old EOS flag"); mReceivedEOS = false; @@ -501,7 +508,7 @@ void MFMediaEngineStream::SendRequestSampleEvent(bool aIsEnough) { AssertOnTaskQueue(); SLOGV("data is %s, queue duration=%" PRId64, aIsEnough ? "enough" : "not enough", - mRawDataQueueForFeedingEngine.Duration()); + mRawDataQueueForFeedingEngine.PreciseDuration()); mParentSource->mRequestSampleEvent.Notify( SampleRequest{TrackType(), aIsEnough}); } diff --git a/dom/media/platforms/wmf/MFMediaEngineStream.h b/dom/media/platforms/wmf/MFMediaEngineStream.h index aa3bf7e65d..e11d900498 100644 --- a/dom/media/platforms/wmf/MFMediaEngineStream.h +++ b/dom/media/platforms/wmf/MFMediaEngineStream.h @@ -84,7 +84,7 @@ class MFMediaEngineStream // Return the type of the track, the result should be either audio or video. virtual TrackInfo::TrackType TrackType() = 0; - RefPtr<MediaDataDecoder::FlushPromise> Flush(); + virtual RefPtr<MediaDataDecoder::FlushPromise> Flush(); MediaEventProducer<TrackInfo::TrackType>& EndedEvent() { return mEndedEvent; } @@ -93,7 +93,7 @@ class MFMediaEngineStream virtual MFMediaEngineVideoStream* AsVideoStream() { return nullptr; } - RefPtr<MediaDataDecoder::DecodePromise> OutputData( + virtual RefPtr<MediaDataDecoder::DecodePromise> OutputData( RefPtr<MediaRawData> aSample); virtual RefPtr<MediaDataDecoder::DecodePromise> Drain(); @@ -133,11 +133,13 @@ class MFMediaEngineStream // should uses `mRawDataQueueForGeneratingOutput` to generate output. virtual already_AddRefed<MediaData> OutputDataInternal() = 0; - void SendRequestSampleEvent(bool aIsEnough); + virtual void SendRequestSampleEvent(bool aIsEnough); HRESULT AddEncryptAttributes(IMFSample* aSample, const CryptoSample& aCryptoConfig); + void NotifyEndEvent(); + void AssertOnTaskQueue() const; void AssertOnMFThreadPool() const; diff --git a/dom/media/platforms/wmf/MFMediaEngineVideoStream.cpp b/dom/media/platforms/wmf/MFMediaEngineVideoStream.cpp index ca043478f0..0fedcd31b9 100644 --- a/dom/media/platforms/wmf/MFMediaEngineVideoStream.cpp +++ b/dom/media/platforms/wmf/MFMediaEngineVideoStream.cpp @@ -49,7 +49,7 @@ void MFMediaEngineVideoStream::SetKnowsCompositor( this]() { mKnowsCompositor = knowCompositor; LOG("Set SetKnowsCompositor=%p", mKnowsCompositor.get()); - ResolvePendingDrainPromiseIfNeeded(); + ResolvePendingPromisesIfNeeded(); })); } @@ -74,7 +74,7 @@ void MFMediaEngineVideoStream::SetDCompSurfaceHandle(HANDLE aDCompSurfaceHandle, } } LOG("Set DCompSurfaceHandle, handle=%p", mDCompSurfaceHandle); - ResolvePendingDrainPromiseIfNeeded(); + ResolvePendingPromisesIfNeeded(); })); } @@ -209,7 +209,7 @@ HRESULT MFMediaEngineVideoStream::CreateMediaType(const TrackInfo& aInfo, bool MFMediaEngineVideoStream::HasEnoughRawData() const { // If more than this much raw video is queued, we'll hold off request more // video. - return mRawDataQueueForFeedingEngine.Duration() >= + return mRawDataQueueForFeedingEngine.PreciseDuration() >= StaticPrefs::media_wmf_media_engine_raw_data_threshold_video(); } @@ -240,6 +240,32 @@ bool MFMediaEngineVideoStream::IsDCompImageReady() { return true; } +RefPtr<MediaDataDecoder::DecodePromise> MFMediaEngineVideoStream::OutputData( + RefPtr<MediaRawData> aSample) { + if (IsShutdown()) { + return MediaDataDecoder::DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_FAILURE, + RESULT_DETAIL("MFMediaEngineStream is shutdown")), + __func__); + } + AssertOnTaskQueue(); + NotifyNewData(aSample); + MediaDataDecoder::DecodedData outputs; + if (RefPtr<MediaData> outputData = OutputDataInternal()) { + outputs.AppendElement(outputData); + LOGV("Output data [%" PRId64 ",%" PRId64 "]", + outputData->mTime.ToMicroseconds(), + outputData->GetEndTime().ToMicroseconds()); + } + if (ShouldDelayVideoDecodeBeforeDcompReady()) { + LOG("Dcomp isn't ready and we already have enough video data. We will send " + "them back together at one when Dcomp is ready"); + return mVideoDecodeBeforeDcompPromise.Ensure(__func__); + } + return MediaDataDecoder::DecodePromise::CreateAndResolve(std::move(outputs), + __func__); +} + already_AddRefed<MediaData> MFMediaEngineVideoStream::OutputDataInternal() { AssertOnTaskQueue(); if (mRawDataQueueForGeneratingOutput.GetSize() == 0 || !IsDCompImageReady()) { @@ -261,28 +287,62 @@ RefPtr<MediaDataDecoder::DecodePromise> MFMediaEngineVideoStream::Drain() { MediaDataDecoder::DecodedData outputs; if (!IsDCompImageReady()) { LOGV("Waiting for dcomp image for draining"); + // A workaround for a special case where we have sent all input data to the + // media engine, and waiting for an output. Sometime media engine would + // never return the first frame to us, unless we notify it the end event, + // which happens on the case where the video only contains one frame. If we + // don't send end event to the media engine, the drain promise would be + // pending forever. + if (!mSampleRequestTokens.empty() && + mRawDataQueueForFeedingEngine.GetSize() == 0) { + NotifyEndEvent(); + } return mPendingDrainPromise.Ensure(__func__); } return MFMediaEngineStream::Drain(); } -void MFMediaEngineVideoStream::ResolvePendingDrainPromiseIfNeeded() { +RefPtr<MediaDataDecoder::FlushPromise> MFMediaEngineVideoStream::Flush() { + AssertOnTaskQueue(); + auto promise = MFMediaEngineStream::Flush(); + mPendingDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mVideoDecodeBeforeDcompPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, + __func__); + return promise; +} + +void MFMediaEngineVideoStream::ResolvePendingPromisesIfNeeded() { AssertOnTaskQueue(); - if (mPendingDrainPromise.IsEmpty()) { - return; - } if (!IsDCompImageReady()) { return; } - MediaDataDecoder::DecodedData outputs; - while (RefPtr<MediaData> outputData = OutputDataInternal()) { - outputs.AppendElement(outputData); - LOGV("Output data [%" PRId64 ",%" PRId64 "]", - outputData->mTime.ToMicroseconds(), - outputData->GetEndTime().ToMicroseconds()); + + // Resolve decoding promise first, then drain promise + if (!mVideoDecodeBeforeDcompPromise.IsEmpty()) { + MediaDataDecoder::DecodedData outputs; + while (RefPtr<MediaData> outputData = OutputDataInternal()) { + outputs.AppendElement(outputData); + LOGV("Output data [%" PRId64 ",%" PRId64 "]", + outputData->mTime.ToMicroseconds(), + outputData->GetEndTime().ToMicroseconds()); + } + mVideoDecodeBeforeDcompPromise.Resolve(std::move(outputs), __func__); + LOG("Resolved video decode before Dcomp promise"); + } + + // This drain promise could return no data, if all data has been processed in + // the decoding promise. + if (!mPendingDrainPromise.IsEmpty()) { + MediaDataDecoder::DecodedData outputs; + while (RefPtr<MediaData> outputData = OutputDataInternal()) { + outputs.AppendElement(outputData); + LOGV("Output data [%" PRId64 ",%" PRId64 "]", + outputData->mTime.ToMicroseconds(), + outputData->GetEndTime().ToMicroseconds()); + } + mPendingDrainPromise.Resolve(std::move(outputs), __func__); + LOG("Resolved pending drain promise"); } - mPendingDrainPromise.Resolve(std::move(outputs), __func__); - LOG("Resolved pending drain promise"); } MediaDataDecoder::ConversionRequired MFMediaEngineVideoStream::NeedsConversion() @@ -336,6 +396,20 @@ void MFMediaEngineVideoStream::UpdateConfig(const VideoInfo& aInfo) { void MFMediaEngineVideoStream::ShutdownCleanUpOnTaskQueue() { AssertOnTaskQueue(); mPendingDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mVideoDecodeBeforeDcompPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, + __func__); +} + +void MFMediaEngineVideoStream::SendRequestSampleEvent(bool aIsEnough) { + AssertOnTaskQueue(); + MFMediaEngineStream::SendRequestSampleEvent(aIsEnough); + // We need more data to be sent in, we should resolve the promise to allow + // more input data to be sent. + if (!aIsEnough && !mVideoDecodeBeforeDcompPromise.IsEmpty()) { + LOG("Resolved pending input promise to allow more input be sent in"); + mVideoDecodeBeforeDcompPromise.Resolve(MediaDataDecoder::DecodedData{}, + __func__); + } } bool MFMediaEngineVideoStream::IsEnded() const { @@ -352,6 +426,10 @@ bool MFMediaEngineVideoStream::IsEnded() const { bool MFMediaEngineVideoStream::IsEncrypted() const { return mIsEncrypted; } +bool MFMediaEngineVideoStream::ShouldDelayVideoDecodeBeforeDcompReady() { + return HasEnoughRawData() && !IsDCompImageReady(); +} + nsCString MFMediaEngineVideoStream::GetCodecName() const { switch (mStreamType) { case WMFStreamType::H264: diff --git a/dom/media/platforms/wmf/MFMediaEngineVideoStream.h b/dom/media/platforms/wmf/MFMediaEngineVideoStream.h index df17c264e4..51fbe4876b 100644 --- a/dom/media/platforms/wmf/MFMediaEngineVideoStream.h +++ b/dom/media/platforms/wmf/MFMediaEngineVideoStream.h @@ -19,6 +19,7 @@ class DcompSurfaceImage; } // namespace layers class MFMediaSource; +class MediaRawData; class MFMediaEngineVideoStream final : public MFMediaEngineStream { public: @@ -50,8 +51,13 @@ class MFMediaEngineVideoStream final : public MFMediaEngineStream { // change happens during playback. void SetConfig(const TrackInfo& aConfig); + RefPtr<MediaDataDecoder::DecodePromise> OutputData( + RefPtr<MediaRawData> aSample) override; + RefPtr<MediaDataDecoder::DecodePromise> Drain() override; + RefPtr<MediaDataDecoder::FlushPromise> Flush() override; + bool IsEncrypted() const override; private: @@ -66,12 +72,25 @@ class MFMediaEngineVideoStream final : public MFMediaEngineStream { bool IsDCompImageReady(); - void ResolvePendingDrainPromiseIfNeeded(); + // Those promises are used to handle decode/drain which happens before the + // Dcomp surface is ready. + void ResolvePendingPromisesIfNeeded(); void ShutdownCleanUpOnTaskQueue() override; bool IsEnded() const override; + // Before Dcomp surface is ready, we can't return any video data due to + // lacking of the image, which should only happen on the beginning of the + // video playback. In that situation, once we have enough video raw data, we + // can stop delaying the decode promise by waiting the Dcomp surface and + // resolveing the promise when Dcomp surface is ready. Doing so helps to keep + // the decode promise pending, so that the MFR won't keep sending more input + // data, which we actually don't need that many. + bool ShouldDelayVideoDecodeBeforeDcompReady(); + + void SendRequestSampleEvent(bool aIsEnough) override; + // Task queue only members. HANDLE mDCompSurfaceHandle; bool mNeedRecreateImage; @@ -98,6 +117,12 @@ class MFMediaEngineVideoStream final : public MFMediaEngineStream { // have dcomp image. MozPromiseHolder<MediaDataDecoder::DecodePromise> mPendingDrainPromise; + // The promise used to return all video output which are requested before the + // Dcomp surface is ready. This should only be used once in entire playback, + // typically happening around the beginning of the playback. + MozPromiseHolder<MediaDataDecoder::DecodePromise> + mVideoDecodeBeforeDcompPromise; + // Set when `CreateMediaType()` is called. bool mIsEncrypted = false; }; diff --git a/dom/media/platforms/wmf/MFMediaSource.h b/dom/media/platforms/wmf/MFMediaSource.h index 735d53579e..0e44ef12aa 100644 --- a/dom/media/platforms/wmf/MFMediaSource.h +++ b/dom/media/platforms/wmf/MFMediaSource.h @@ -132,8 +132,6 @@ class MFMediaSource : public Microsoft::WRL::RuntimeClass< void AssertOnManagerThread() const; void AssertOnMFThreadPool() const; - void NotifyEndOfStreamInternal(TrackInfo::TrackType aType); - bool IsSeekable() const; // A thread-safe event queue. diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp index 6ebcf9a80a..63db5efae8 100644 --- a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp @@ -55,6 +55,9 @@ WMFAudioMFTManager::WMFAudioMFTManager(const AudioInfo& aConfig) audioSpecConfig = audioCodecSpecificBinaryBlob->Elements(); configLength = audioCodecSpecificBinaryBlob->Length(); } + // If no extradata has been provided, assume this is ADTS. Otherwise, + // assume raw AAC packets. + mIsADTS = !configLength; AACAudioSpecificConfigToUserData(aConfig.mExtendedProfile, audioSpecConfig, configLength, mUserData); } @@ -104,7 +107,8 @@ bool WMFAudioMFTManager::Init() { NS_ENSURE_TRUE(SUCCEEDED(hr), false); if (mStreamType == WMFStreamType::AAC) { - hr = inputType->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0x0); // Raw AAC packet + UINT32 payloadType = mIsADTS ? 1 : 0; + hr = inputType->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, payloadType); NS_ENSURE_TRUE(SUCCEEDED(hr), false); hr = inputType->SetBlob(MF_MT_USER_DATA, mUserData.Elements(), @@ -144,7 +148,8 @@ WMFAudioMFTManager::Input(MediaRawData* aSample) { nsCString WMFAudioMFTManager::GetCodecName() const { if (mStreamType == WMFStreamType::AAC) { return "aac"_ns; - } else if (mStreamType == WMFStreamType::MP3) { + } + if (mStreamType == WMFStreamType::MP3) { return "mp3"_ns; } return "unknown"_ns; @@ -177,8 +182,8 @@ WMFAudioMFTManager::UpdateOutputType() { } HRESULT -WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) { - aOutData = nullptr; +WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutput) { + aOutput = nullptr; RefPtr<IMFSample> sample; HRESULT hr; int typeChangeCount = 0; @@ -242,8 +247,8 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) { NS_ENSURE_TRUE(SUCCEEDED(hr), hr); // Output is made of floats. - int32_t numSamples = currentLength / sizeof(float); - int32_t numFrames = numSamples / mAudioChannels; + uint32_t numSamples = currentLength / sizeof(float); + uint32_t numFrames = numSamples / mAudioChannels; MOZ_ASSERT(numFrames >= 0); MOZ_ASSERT(numSamples >= 0); if (numFrames == 0) { @@ -275,10 +280,10 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) { return MF_E_TRANSFORM_NEED_MORE_INPUT; } - aOutData = new AudioData(aStreamOffset, pts, std::move(audioData), - mAudioChannels, mAudioRate, mChannelsMap); - MOZ_DIAGNOSTIC_ASSERT(duration == aOutData->mDuration, "must be equal"); - mLastOutputDuration = aOutData->mDuration; + aOutput = new AudioData(aStreamOffset, pts, std::move(audioData), + mAudioChannels, mAudioRate, mChannelsMap); + MOZ_DIAGNOSTIC_ASSERT(duration == aOutput->mDuration, "must be equal"); + mLastOutputDuration = aOutput->mDuration; #ifdef LOG_SAMPLE_DECODE LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.h b/dom/media/platforms/wmf/WMFAudioMFTManager.h index b5dc379396..f772593545 100644 --- a/dom/media/platforms/wmf/WMFAudioMFTManager.h +++ b/dom/media/platforms/wmf/WMFAudioMFTManager.h @@ -58,6 +58,7 @@ class WMFAudioMFTManager : public MFTManager { media::TimeUnit mLastOutputDuration = media::TimeUnit::Zero(); bool mFirstFrame = true; + bool mIsADTS = false; uint64_t mTotalMediaFrames = 0; uint32_t mEncoderDelay = 0; diff --git a/dom/media/platforms/wmf/WMFMediaDataEncoder.h b/dom/media/platforms/wmf/WMFMediaDataEncoder.h index 13848b47ad..31a63c8347 100644 --- a/dom/media/platforms/wmf/WMFMediaDataEncoder.h +++ b/dom/media/platforms/wmf/WMFMediaDataEncoder.h @@ -202,7 +202,9 @@ class WMFMediaDataEncoder final : public MediaDataEncoder { MOZ_ASSERT(mEncoder); const layers::PlanarYCbCrImage* image = aData->mImage->AsPlanarYCbCrImage(); - MOZ_ASSERT(image); + // TODO: Take care non planar Y-Cb-Cr image (Bug 1881647). + NS_ENSURE_TRUE(image, nullptr); + const layers::PlanarYCbCrData* yuv = image->GetData(); auto ySize = yuv->YDataSize(); auto cbcrSize = yuv->CbCrDataSize(); @@ -223,6 +225,7 @@ class WMFMediaDataEncoder final : public MediaDataEncoder { LockBuffer lockBuffer(buffer); NS_ENSURE_TRUE(SUCCEEDED(lockBuffer.Result()), nullptr); + // TODO: Take care non I420 image (Bug 1881647). bool ok = libyuv::I420ToNV12( yuv->mYChannel, yuv->mYStride, yuv->mCbChannel, yuv->mCbCrStride, yuv->mCrChannel, yuv->mCbCrStride, diff --git a/dom/media/platforms/wmf/WMFUtils.cpp b/dom/media/platforms/wmf/WMFUtils.cpp index d096979919..dda9df808e 100644 --- a/dom/media/platforms/wmf/WMFUtils.cpp +++ b/dom/media/platforms/wmf/WMFUtils.cpp @@ -177,7 +177,8 @@ Maybe<gfx::YUVColorSpace> GetYUVColorSpace(IMFMediaType* aType) { } int32_t MFOffsetToInt32(const MFOffset& aOffset) { - return int32_t(aOffset.value + (aOffset.fract / 65536.0f)); + return AssertedCast<int32_t>(AssertedCast<float>(aOffset.value) + + (AssertedCast<float>(aOffset.fract) / 65536.0f)); } TimeUnit GetSampleDuration(IMFSample* aSample) { @@ -204,7 +205,7 @@ GetPictureRegion(IMFMediaType* aMediaType, gfx::IntRect& aOutPictureRegion) { // Determine if "pan and scan" is enabled for this media. If it is, we // only display a region of the video frame, not the entire frame. BOOL panScan = - MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE); + !!MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE); // If pan and scan mode is enabled. Try to get the display region. HRESULT hr = E_FAIL; @@ -300,11 +301,14 @@ const char* MFTMessageTypeToStr(MFT_MESSAGE_TYPE aMsg) { GUID AudioMimeTypeToMediaFoundationSubtype(const nsACString& aMimeType) { if (aMimeType.EqualsLiteral("audio/mpeg")) { return MFAudioFormat_MP3; - } else if (MP4Decoder::IsAAC(aMimeType)) { + } + if (MP4Decoder::IsAAC(aMimeType)) { return MFAudioFormat_AAC; - } else if (aMimeType.EqualsLiteral("audio/vorbis")) { + } + if (aMimeType.EqualsLiteral("audio/vorbis")) { return MFAudioFormat_Vorbis; - } else if (aMimeType.EqualsLiteral("audio/opus")) { + } + if (aMimeType.EqualsLiteral("audio/opus")) { return MFAudioFormat_Opus; } NS_WARNING("Unsupport audio mimetype"); @@ -314,17 +318,19 @@ GUID AudioMimeTypeToMediaFoundationSubtype(const nsACString& aMimeType) { GUID VideoMimeTypeToMediaFoundationSubtype(const nsACString& aMimeType) { if (MP4Decoder::IsH264(aMimeType)) { return MFVideoFormat_H264; - } else if (VPXDecoder::IsVP8(aMimeType)) { + } + if (VPXDecoder::IsVP8(aMimeType)) { return MFVideoFormat_VP80; - } else if (VPXDecoder::IsVP9(aMimeType)) { + } + if (VPXDecoder::IsVP9(aMimeType)) { return MFVideoFormat_VP90; } #ifdef MOZ_AV1 - else if (AOMDecoder::IsAV1(aMimeType)) { + if (AOMDecoder::IsAV1(aMimeType)) { return MFVideoFormat_AV1; } #endif - else if (MP4Decoder::IsHEVC(aMimeType)) { + if (MP4Decoder::IsHEVC(aMimeType)) { return MFVideoFormat_HEVC; } NS_WARNING("Unsupport video mimetype"); @@ -368,7 +374,9 @@ void AACAudioSpecificConfigToUserData(uint8_t aAACProfileLevelIndication, // the rest can be all 0x00. BYTE heeInfo[heeInfoLen] = {0}; WORD* w = (WORD*)heeInfo; - w[0] = 0x0; // Payload type raw AAC packet + // If extradata has been provided, assume raw AAC packets (0). Otherwise, + // assume ADTS (1) + w[0] = aConfigLength ? 0 : 1; w[1] = aAACProfileLevelIndication; aOutUserData.AppendElements(heeInfo, heeInfoLen); @@ -377,10 +385,10 @@ void AACAudioSpecificConfigToUserData(uint8_t aAACProfileLevelIndication, // The AudioSpecificConfig is TTTTTFFF|FCCCCGGG // (T=ObjectType, F=Frequency, C=Channel, G=GASpecificConfig) // If frequency = 0xf, then the frequency is explicitly defined on 24 bits. - int8_t frequency = + uint8_t frequency = (aAudioSpecConfig[0] & 0x7) << 1 | (aAudioSpecConfig[1] & 0x80) >> 7; - int8_t channels = (aAudioSpecConfig[1] & 0x78) >> 3; - int8_t gasc = aAudioSpecConfig[1] & 0x7; + uint8_t channels = (aAudioSpecConfig[1] & 0x78) >> 3; + uint8_t gasc = aAudioSpecConfig[1] & 0x7; if (frequency != 0xf && channels && !gasc) { // We enter this condition if the AudioSpecificConfig should theorically // be 2 bytes long but it's not. diff --git a/dom/media/systemservices/android_video_capture/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java b/dom/media/systemservices/android_video_capture/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java index cc54009a7b..b4cc65ec9c 100644 --- a/dom/media/systemservices/android_video_capture/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java +++ b/dom/media/systemservices/android_video_capture/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java @@ -17,6 +17,7 @@ import android.content.Context; import android.util.Log; import android.view.Surface; import android.view.WindowManager; +import androidx.annotation.NonNull; import java.util.concurrent.CountDownLatch; @@ -37,10 +38,8 @@ public class VideoCaptureAndroid implements CameraVideoCapturer.CameraEventsHand private final String deviceName; private volatile long native_capturer; // |VideoCaptureAndroid*| in C++. - private Context context; + private final Context context; private CameraVideoCapturer cameraVideoCapturer; - private EglBase eglBase; - private SurfaceTextureHelper surfaceTextureHelper; // This class is recreated everytime we start/stop capture, so we // can safely create the CountDownLatches here. @@ -48,8 +47,16 @@ public class VideoCaptureAndroid implements CameraVideoCapturer.CameraEventsHand private boolean capturerStartedSucceeded = false; private final CountDownLatch capturerStopped = new CountDownLatch(1); - @WebRTCJNITarget - public VideoCaptureAndroid(String deviceName) { + @WebRTCJNITarget + public static VideoCaptureAndroid create(@NonNull final String deviceName) { + final Context context = GetContext(); + return new VideoCaptureAndroid(context, deviceName, + Camera2Enumerator.isSupported(context) + ? new Camera2Enumerator(context) + : new Camera1Enumerator()); + } + + private VideoCaptureAndroid(@NonNull final Context context, @NonNull final String deviceName, @NonNull final CameraEnumerator enumerator) { // Remove the camera facing information from the name. String[] parts = deviceName.split("Facing (front|back):"); if (parts.length == 2) { @@ -58,20 +65,14 @@ public class VideoCaptureAndroid implements CameraVideoCapturer.CameraEventsHand Log.e(TAG, "VideoCaptureAndroid: Expected facing mode as part of name: " + deviceName); this.deviceName = deviceName; } - this.context = GetContext(); + this.context = context; - CameraEnumerator enumerator; - if (Camera2Enumerator.isSupported(context)) { - enumerator = new Camera2Enumerator(context); - } else { - enumerator = new Camera1Enumerator(); - } try { cameraVideoCapturer = enumerator.createCapturer(this.deviceName, this); - eglBase = EglBase.create(); - surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureAndroidSurfaceTextureHelper", eglBase.getEglBaseContext()); + final EglBase eglBase = EglBase.create(); + final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureAndroidSurfaceTextureHelper", eglBase.getEglBaseContext()); cameraVideoCapturer.initialize(surfaceTextureHelper, context, this); - } catch (java.lang.IllegalArgumentException e) { + } catch (java.lang.RuntimeException e) { Log.e(TAG, "VideoCaptureAndroid: Exception while creating capturer: " + e); } } @@ -80,6 +81,10 @@ public class VideoCaptureAndroid implements CameraVideoCapturer.CameraEventsHand @WebRTCJNITarget private static native Context GetContext(); + public boolean canCapture() { + return cameraVideoCapturer != null; + } + // Called by native code. Returns true if capturer is started. // // Note that this actually opens the camera, and Camera callbacks run on the diff --git a/dom/media/systemservices/android_video_capture/video_capture_android.cc b/dom/media/systemservices/android_video_capture/video_capture_android.cc index da0715db27..cd5a73ceb6 100644 --- a/dom/media/systemservices/android_video_capture/video_capture_android.cc +++ b/dom/media/systemservices/android_video_capture/video_capture_android.cc @@ -177,12 +177,14 @@ int32_t VideoCaptureAndroid::Init(const char* deviceUniqueIdUTF8) { AttachThreadScoped ats(g_jvm_capture); JNIEnv* env = ats.env(); - jmethodID ctor = env->GetMethodID(g_java_capturer_class, "<init>", - "(Ljava/lang/String;)V"); - assert(ctor); + jmethodID factory = + env->GetStaticMethodID(g_java_capturer_class, "create", + "(Ljava/lang/String;)" + "Lorg/webrtc/videoengine/VideoCaptureAndroid;"); + assert(factory); jstring j_deviceName = env->NewStringUTF(_deviceUniqueId); - _jCapturer = env->NewGlobalRef( - env->NewObject(g_java_capturer_class, ctor, j_deviceName)); + _jCapturer = env->NewGlobalRef(env->CallStaticObjectMethod( + g_java_capturer_class, factory, j_deviceName)); assert(_jCapturer); return 0; } diff --git a/dom/media/systemservices/objc_video_capture/video_capture_avfoundation.mm b/dom/media/systemservices/objc_video_capture/video_capture_avfoundation.mm index 36d5f56b16..ca7bf4db9b 100644 --- a/dom/media/systemservices/objc_video_capture/video_capture_avfoundation.mm +++ b/dom/media/systemservices/objc_video_capture/video_capture_avfoundation.mm @@ -88,10 +88,11 @@ AVCaptureDeviceFormat* _Nullable FindFormat( - (void)capturer:(RTCVideoCapturer* _Nonnull)capturer didCaptureVideoFrame:(RTCVideoFrame* _Nonnull)frame { - rtc::scoped_refptr<webrtc::videocapturemodule::VideoCaptureAvFoundation> cap; + webrtc::scoped_refptr<webrtc::videocapturemodule::VideoCaptureAvFoundation> + cap; { webrtc::MutexLock lock(&_mutex); - cap = rtc::scoped_refptr(_capturer); + cap = webrtc::scoped_refptr(_capturer); } if (!cap) return; cap->OnFrame(frame); diff --git a/dom/media/test/background_video.js b/dom/media/test/background_video.js index 508f8fd89a..6cce797474 100644 --- a/dom/media/test/background_video.js +++ b/dom/media/test/background_video.js @@ -72,7 +72,7 @@ function appendVideoToDocWithoutLoad(token, width, height) { } function loadAndWaitUntilLoadedmetadata(video, url, preloadType = "metadata") { - return new Promise((resolve, reject) => { + return new Promise(resolve => { video.preload = preloadType; video.addEventListener( "loadedmetadata", diff --git a/dom/media/test/bipbop-clearkey-video-av1.mp4 b/dom/media/test/bipbop-clearkey-video-av1.mp4 Binary files differnew file mode 100644 index 0000000000..78c1e56b2b --- /dev/null +++ b/dom/media/test/bipbop-clearkey-video-av1.mp4 diff --git a/dom/media/test/bipbop-clearkey-video-av1.mp4^headers^ b/dom/media/test/bipbop-clearkey-video-av1.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-clearkey-video-av1.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-clearkey-video-av1.webm b/dom/media/test/bipbop-clearkey-video-av1.webm Binary files differnew file mode 100644 index 0000000000..840888a8b1 --- /dev/null +++ b/dom/media/test/bipbop-clearkey-video-av1.webm diff --git a/dom/media/test/bipbop-clearkey-video-av1.webm^headers^ b/dom/media/test/bipbop-clearkey-video-av1.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-clearkey-video-av1.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/browser/browser.toml b/dom/media/test/browser/browser.toml index cf25369576..3e83448d47 100644 --- a/dom/media/test/browser/browser.toml +++ b/dom/media/test/browser/browser.toml @@ -2,6 +2,7 @@ subsuite = "media-bc" prefs = ["gfx.font_loader.delay=0"] support-files = [ + "head.js", "file_empty_page.html", "file_media.html", "../av1.mp4", @@ -16,11 +17,14 @@ support-files = [ "../small-shot.mp3", "../small-shot.ogg", "../TestPatternHDR.mp4", + "../../mediasource/test/bipbop/bipbop2s.mp4", ] ["browser_encrypted_play_time_telemetry.js"] skip-if = ["apple_silicon"] # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs +["browser_glean_first_frame_loaded_time.js"] + ["browser_tab_visibility_and_play_time.js"] ["browser_telemetry_video_hardware_decoding_support.js"] diff --git a/dom/media/test/browser/browser_encrypted_play_time_telemetry.js b/dom/media/test/browser/browser_encrypted_play_time_telemetry.js index ff4f2753ec..1a64717419 100644 --- a/dom/media/test/browser/browser_encrypted_play_time_telemetry.js +++ b/dom/media/test/browser/browser_encrypted_play_time_telemetry.js @@ -29,76 +29,6 @@ async function clearTelemetry() { }); } -// Opens a tab containing a blank page, returns a promise that will resolve -// to that tab. -async function openTab() { - const emptyPageUri = - "https://example.com/browser/dom/media/test/browser/file_empty_page.html"; - return BrowserTestUtils.openNewForegroundTab(window.gBrowser, emptyPageUri); -} - -// Creates and configures a video element for EME playback in `tab`. Does not -// start playback for the element. Returns a promise that will resolve once -// the element is setup and ready for playback. -async function loadEmeVideo(tab) { - const emeHelperUri = - gTestPath.substr(0, gTestPath.lastIndexOf("/")) + "/eme_standalone.js"; - return SpecialPowers.spawn( - tab.linkedBrowser, - [emeHelperUri], - async _emeHelperUri => { - // Begin helper functions. - async function once(target, name) { - return new Promise(r => - target.addEventListener(name, r, { once: true }) - ); - } - - // Helper to clone data into content so the EME helper can use the data. - function cloneIntoContent(data) { - return Cu.cloneInto(data, content.wrappedJSObject); - } - // End helper functions. - - // Load the EME helper into content. - Services.scriptloader.loadSubScript(_emeHelperUri, content); - // Setup EME with the helper. - let video = content.document.createElement("video"); - video.id = "media"; - content.document.body.appendChild(video); - let emeHelper = new content.wrappedJSObject.EmeHelper(); - emeHelper.SetKeySystem( - content.wrappedJSObject.EmeHelper.GetClearkeyKeySystemString() - ); - emeHelper.SetInitDataTypes(cloneIntoContent(["webm"])); - emeHelper.SetVideoCapabilities( - cloneIntoContent([{ contentType: 'video/webm; codecs="vp9"' }]) - ); - emeHelper.AddKeyIdAndKey( - "2cdb0ed6119853e7850671c3e9906c3c", - "808b9adac384de1e4f56140f4ad76194" - ); - emeHelper.onerror = error => { - is(false, `Got unexpected error from EME helper: ${error}`); - }; - await emeHelper.ConfigureEme(video); - // Done setting up EME. - - // Setup MSE. - const ms = new content.wrappedJSObject.MediaSource(); - video.src = content.wrappedJSObject.URL.createObjectURL(ms); - await once(ms, "sourceopen"); - const sb = ms.addSourceBuffer("video/webm"); - const videoFile = "sintel-short-clearkey-subsample-encrypted-video.webm"; - let fetchResponse = await content.fetch(videoFile); - sb.appendBuffer(await fetchResponse.arrayBuffer()); - await once(sb, "updateend"); - ms.endOfStream(); - await once(ms, "sourceended"); - } - ); -} - // Plays the media in `tab` until the 'ended' event is fire. Returns a promise // that resolves once that state has been reached. async function playMediaThrough(tab) { diff --git a/dom/media/test/browser/browser_glean_first_frame_loaded_time.js b/dom/media/test/browser/browser_glean_first_frame_loaded_time.js new file mode 100644 index 0000000000..1acfa9957e --- /dev/null +++ b/dom/media/test/browser/browser_glean_first_frame_loaded_time.js @@ -0,0 +1,81 @@ +"use strict"; + +// Disabling undef warning because in `run()` we use functions from head.js +/* eslint-disable no-undef */ + +/** + * This test is used to ensure that Glean probe 'first_frame_loaded' can be + * recorded correctly in different situations. + */ + +const testCases = [ + { + expected: { + playback_type: "Non-MSE playback", + video_codec: "video/avc", + resolution: "AV,240<h<=480", + key_system: undefined, + }, + async run(tab) { + await loadVideo(tab); + }, + }, + { + expected: { + playback_type: "MSE playback", + video_codec: "video/avc", + resolution: "AV,240<h<=480", + key_system: undefined, + }, + async run(tab) { + await loadMseVideo(tab); + }, + }, + { + expected: { + playback_type: "EME playback", + video_codec: "video/vp9", + resolution: "V,240<h<=480", + key_system: "org.w3.clearkey", + }, + async run(tab) { + await loadEmeVideo(tab); + }, + }, +]; + +add_task(async function testGleanMediaPlayackFirstFrameLoaded() { + for (let test of testCases) { + Services.fog.testResetFOG(); + + const expected = test.expected; + info(`running test for '${expected.playback_type}'`); + const tab = await openTab(); + await test.run(tab); + + info(`waiting until glean probe is ready on the parent process`); + await Services.fog.testFlushAllChildren(); + + info("checking the collected results"); + const extra = Glean.mediaPlayback.firstFrameLoaded.testGetValue()[0].extra; + Assert.greater( + parseInt(extra.first_frame_loaded_time), + 0, + `${extra.first_frame_loaded_time} is correct` + ); + is( + extra.playback_type, + expected.playback_type, + `${extra.playback_type} is correct` + ); + is( + extra.video_codec, + expected.video_codec, + `${extra.video_codec} is correct` + ); + is(extra.resolution, expected.resolution, `${extra.resolution} is correct`); + is(extra.key_system, expected.key_system, `${extra.key_system} is correct`); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/dom/media/test/browser/browser_tab_visibility_and_play_time.js b/dom/media/test/browser/browser_tab_visibility_and_play_time.js index 4d33826091..b152f490c9 100644 --- a/dom/media/test/browser/browser_tab_visibility_and_play_time.js +++ b/dom/media/test/browser/browser_tab_visibility_and_play_time.js @@ -54,7 +54,7 @@ async function openMediaTab(url) { return new Promise(resolve => { element.addEventListener( "timeupdate", - e => { + () => { resolve(); }, { once: true } diff --git a/dom/media/test/browser/head.js b/dom/media/test/browser/head.js new file mode 100644 index 0000000000..7ef578a804 --- /dev/null +++ b/dom/media/test/browser/head.js @@ -0,0 +1,119 @@ +"use strict"; + +/* import-globals-from ../eme_standalone.js */ + +// Opens a tab containing a blank page, returns a promise that will resolve +// to that tab. +function openTab() { + const emptyPageUri = + "https://example.com/browser/dom/media/test/browser/file_empty_page.html"; + return BrowserTestUtils.openNewForegroundTab(window.gBrowser, emptyPageUri); +} + +// Creates and configures a video element for non-MSE playback in `tab`. Does not +// start playback for the element. Returns a promise that will resolve once +// the element is setup and ready for playback. +function loadVideo(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { + let video = content.document.createElement("video"); + video.id = "media"; + content.document.body.appendChild(video); + + video.src = "gizmo.mp4"; + video.load(); + + info(`waiting 'loadeddata' event to ensure playback is ready`); + await new Promise(r => (video.onloadeddata = r)); + }); +} + +// Creates and configures a video element for MSE playback in `tab`. Does not +// start playback for the element. Returns a promise that will resolve once +// the element is setup and ready for playback. +function loadMseVideo(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { + async function once(target, name) { + return new Promise(r => target.addEventListener(name, r, { once: true })); + } + + let video = content.document.createElement("video"); + video.id = "media"; + content.document.body.appendChild(video); + + info(`starting setup MSE`); + const ms = new content.wrappedJSObject.MediaSource(); + video.src = content.wrappedJSObject.URL.createObjectURL(ms); + await once(ms, "sourceopen"); + const sb = ms.addSourceBuffer("video/mp4"); + const videoFile = "bipbop2s.mp4"; + let fetchResponse = await content.fetch(videoFile); + sb.appendBuffer(await fetchResponse.arrayBuffer()); + await once(sb, "updateend"); + ms.endOfStream(); + await once(ms, "sourceended"); + + info(`waiting 'loadeddata' event to ensure playback is ready`); + await once(video, "loadeddata"); + }); +} + +// Creates and configures a video element for EME playback in `tab`. Does not +// start playback for the element. Returns a promise that will resolve once +// the element is setup and ready for playback. +function loadEmeVideo(tab) { + const emeHelperUri = + gTestPath.substr(0, gTestPath.lastIndexOf("/")) + "/eme_standalone.js"; + return SpecialPowers.spawn( + tab.linkedBrowser, + [emeHelperUri], + async _emeHelperUri => { + async function once(target, name) { + return new Promise(r => + target.addEventListener(name, r, { once: true }) + ); + } + + // Helper to clone data into content so the EME helper can use the data. + function cloneIntoContent(data) { + return Cu.cloneInto(data, content.wrappedJSObject); + } + + info(`starting setup EME`); + Services.scriptloader.loadSubScript(_emeHelperUri, content); + let video = content.document.createElement("video"); + video.id = "media"; + content.document.body.appendChild(video); + let emeHelper = new content.wrappedJSObject.EmeHelper(); + emeHelper.SetKeySystem( + content.wrappedJSObject.EmeHelper.GetClearkeyKeySystemString() + ); + emeHelper.SetInitDataTypes(cloneIntoContent(["webm"])); + emeHelper.SetVideoCapabilities( + cloneIntoContent([{ contentType: 'video/webm; codecs="vp9"' }]) + ); + emeHelper.AddKeyIdAndKey( + "2cdb0ed6119853e7850671c3e9906c3c", + "808b9adac384de1e4f56140f4ad76194" + ); + emeHelper.onerror = error => { + is(false, `Got unexpected error from EME helper: ${error}`); + }; + await emeHelper.ConfigureEme(video); + + info(`starting setup MSE`); + const ms = new content.wrappedJSObject.MediaSource(); + video.src = content.wrappedJSObject.URL.createObjectURL(ms); + await once(ms, "sourceopen"); + const sb = ms.addSourceBuffer("video/webm"); + const videoFile = "sintel-short-clearkey-subsample-encrypted-video.webm"; + let fetchResponse = await content.fetch(videoFile); + sb.appendBuffer(await fetchResponse.arrayBuffer()); + await once(sb, "updateend"); + ms.endOfStream(); + await once(ms, "sourceended"); + + info(`waiting 'loadeddata' event to ensure playback is ready`); + await once(video, "loadeddata"); + } + ); +} diff --git a/dom/media/test/browser/wmfme/browser.toml b/dom/media/test/browser/wmfme/browser.toml index 422ea29228..7449d232d9 100644 --- a/dom/media/test/browser/wmfme/browser.toml +++ b/dom/media/test/browser/wmfme/browser.toml @@ -3,11 +3,17 @@ subsuite = "media-bc" tags = "media-engine-compatible" run-if = ["wmfme"] support-files = [ - "head.js", "file_video.html", + "head.js", + "../head.js", + "../../eme_standalone.js", "../../gizmo.mp4", + "../../sintel-short-clearkey-subsample-encrypted-video.webm", + "../../../mediasource/test/bipbop/bipbop2s.mp4", ] ["browser_wmfme_crash.js"] +["browser_wmfme_glean_first_frame_loaded_time.js"] + ["browser_wmfme_max_crashes.js"] diff --git a/dom/media/test/browser/wmfme/browser_wmfme_glean_first_frame_loaded_time.js b/dom/media/test/browser/wmfme/browser_wmfme_glean_first_frame_loaded_time.js new file mode 100644 index 0000000000..f9e97aaa49 --- /dev/null +++ b/dom/media/test/browser/wmfme/browser_wmfme_glean_first_frame_loaded_time.js @@ -0,0 +1,98 @@ +"use strict"; + +// Disabling undef warning because in `run()` we use functions from head.js +/* eslint-disable no-undef */ + +/** + * This test is used to ensure that Glean probe 'first_frame_loaded' can be + * recorded correctly in different situations. + */ + +/* import-globals-from ../head.js */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/dom/media/test/browser/head.js", + this +); + +add_task(async function setupTestingPref() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.wmf.media-engine.enabled", 1], + ["media.wmf.media-engine.channel-decoder.enabled", true], + ["media.eme.wmf.clearkey.enabled", true], + ], + }); +}); + +const testCases = [ + { + expected: { + playback_type: "Non-MSE media-engine playback", + video_codec: "video/avc", + resolution: "AV,240<h<=480", + key_system: undefined, + }, + async run(tab) { + await loadVideo(tab); + }, + }, + { + expected: { + playback_type: "MSE media-engine playback", + video_codec: "video/avc", + resolution: "AV,240<h<=480", + key_system: undefined, + }, + async run(tab) { + await loadMseVideo(tab); + }, + }, + // TODO : we're not able to run MFCDM EME playback yet, see bug 1870722. + // { + // expected: { + // playback_type: "EME media-engine playback", + // video_codec: "video/vp9", + // resolution: "V,240<h<=480", + // key_system: "org.w3.clearkey", + // }, + // async run(tab) { + // await loadEmeVideo(tab); + // }, + // }, +]; + +add_task(async function testGleanMediaPlayackFirstFrameLoaded() { + for (let test of testCases) { + Services.fog.testResetFOG(); + + const expected = test.expected; + info(`running test for '${expected.playback_type}'`); + const tab = await openTab(); + await test.run(tab); + + info(`waiting until glean probe is ready on the parent process`); + await Services.fog.testFlushAllChildren(); + + info("checking the collected results"); + const extra = Glean.mediaPlayback.firstFrameLoaded.testGetValue()[0].extra; + Assert.greater( + parseInt(extra.first_frame_loaded_time), + 0, + `${extra.first_frame_loaded_time} is correct` + ); + is( + extra.playback_type, + expected.playback_type, + `${extra.playback_type} is correct` + ); + is( + extra.video_codec, + expected.video_codec, + `${extra.video_codec} is correct` + ); + is(extra.resolution, expected.resolution, `${extra.resolution} is correct`); + is(extra.key_system, expected.key_system, `${extra.key_system} is correct`); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/dom/media/test/can_play_type_ogg.js b/dom/media/test/can_play_type_ogg.js index 79bf8a554c..6572ab7c0f 100644 --- a/dom/media/test/can_play_type_ogg.js +++ b/dom/media/test/can_play_type_ogg.js @@ -4,7 +4,7 @@ function check_ogg(v, enabled, finish) { } function basic_test() { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { // Ogg types check("video/ogg", "maybe"); check("audio/ogg", "maybe"); diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list index 637b161e7f..2383510ce9 100644 --- a/dom/media/test/crashtests/crashtests.list +++ b/dom/media/test/crashtests/crashtests.list @@ -1,4 +1,4 @@ -skip-if(OSX) load 1185191.html # this needs to run near the beginning of the test suite +skip-if(cocoaWidget) load 1185191.html # this needs to run near the beginning of the test suite load 0-timescale.html # bug 1229166 skip-if(Android) pref(media.autoplay.default,0) load 459439-1.html # bug 888557 load 466607-1.html @@ -14,7 +14,7 @@ load 497734-2.html load 576612-1.html load 752784-1.html skip-if(Android) load 789075-1.html # bug 1374405 for android -skip-if(Android&&AndroidVersion=='22') HTTP load 795892-1.html # bug 1358718 +HTTP load 795892-1.html # bug 1358718 load 844563.html load 846612.html load 852838.html @@ -76,7 +76,7 @@ skip-if(Android) test-pref(media.navigator.permission.disabled,true) load 102845 load 1041466.html load 1045650.html load 1080986.html -skip-if(Android&&AndroidVersion=='21') load 1180881.html # bug 1409365 +load 1180881.html # bug 1409365 load 1197935.html load 1122218.html load 1127188.html @@ -113,7 +113,7 @@ HTTP load media-element-source-seek-1.html load offline-buffer-source-ended-1.html load oscillator-ended-1.html load oscillator-ended-2.html -skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876 +load video-replay-after-audio-end.html # bug 1315125, bug 1358876 # This needs to run at the end to avoid leaking busted state into other tests. skip-if(Android||ThreadSanitizer) load 691096-1.html # Bug 1365451 load 1236639.html @@ -122,7 +122,7 @@ load 1414444.mp4 load 1494073.html skip-if(Android) load 1526044.html # Bug 1528391 load 1530897.webm -skip-if(Android&&AndroidVersion<21) load encrypted-track-with-bad-sample-description-index.mp4 # Bug 1533211, unkip after bug 1550912 +load encrypted-track-with-bad-sample-description-index.mp4 # Bug 1533211, unkip after bug 1550912 load encrypted-track-without-tenc.mp4 # Bug 1533215 asserts-if(Android,0-1) load encrypted-track-with-sample-missing-cenc-aux.mp4 # Bug 1533625, bug 1588967 load 1538727.html diff --git a/dom/media/test/eme.js b/dom/media/test/eme.js index 927c99876a..d8eb1e78eb 100644 --- a/dom/media/test/eme.js +++ b/dom/media/test/eme.js @@ -440,7 +440,7 @@ function SetupEME(v, test, token) { } function fetchWithXHR(uri, onLoadFunction) { - var p = new Promise(function (resolve, reject) { + var p = new Promise(function (resolve) { var xhr = new XMLHttpRequest(); xhr.open("GET", uri, true); xhr.responseType = "arraybuffer"; @@ -463,7 +463,7 @@ function fetchWithXHR(uri, onLoadFunction) { } function once(target, name, cb) { - var p = new Promise(function (resolve, reject) { + var p = new Promise(function (resolve) { target.addEventListener( name, function (arg) { diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index bb4557d498..c357309021 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -817,7 +817,7 @@ var gOggTrackInfoResults = { function makeAbsolutePathConverter() { const url = SimpleTest.getTestFileURL("chromeHelper.js"); const script = SpecialPowers.loadChromeScript(url); - return new Promise((resolve, reject) => { + return new Promise(resolve => { script.addMessageListener("media-test:cwd", cwd => { if (!cwd) { ok(false, "Failed to find path to test files"); @@ -1891,6 +1891,46 @@ var gEMETests = [ sessionCount: 1, duration: 1.98, }, + // ffmpeg -i bipbop.mp4 -t 00:00:02 -c:v libaom-av1 bipbop_av1.mp4 + // packager-linux-x64 in=bipbop_av1.mp4,stream=video,out=bipbop-clearkey-video-av1.mp4 --enable_raw_key_encryption --keys label=:key_id=8b5df745ad84145b5617c33116e35a67:key=bddfd35dd9be033ee73bc18bc1885056 --clear_lead 0 + { + name: "MP4 av1 video clearkey", + tracks: [ + { + name: "video", + type: 'video/mp4; codecs="av1"', + fragments: ["bipbop-clearkey-video-av1.mp4"], + }, + ], + keys: { + // "keyid" : "key" + "8b5df745ad84145b5617c33116e35a67": "bddfd35dd9be033ee73bc18bc1885056", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 2.0, + skipTests: ["waitingforkey"], + }, + // ffmpeg -i bipbop.mp4 -t 00:00:02 -c:v libaom-av1 bipbop_av1.webm + // packager-linux-x64 in=bipbop_av1.webm,stream=video,out=bipbop-clearkey-video-av1.webm --enable_raw_key_encryption --keys label=:key_id=8b5df745ad84145b5617c33116e35a67:key=bddfd35dd9be033ee73bc18bc1885056 --clear_lead 0 + { + name: "WebM av1 video clearkey", + tracks: [ + { + name: "video", + type: 'video/webm; codecs="av1"', + fragments: ["bipbop-clearkey-video-av1.webm"], + }, + ], + keys: { + // "keyid" : "key" + "8b5df745ad84145b5617c33116e35a67": "bddfd35dd9be033ee73bc18bc1885056", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 2.0, + skipTests: ["waitingforkey"], + }, { name: "WebM vorbis audio & vp8 video clearkey", tracks: [ @@ -2241,7 +2281,7 @@ function removeNodeAndSource(n) { } function once(target, name, cb) { - var p = new Promise(function (resolve, reject) { + var p = new Promise(function (resolve) { target.addEventListener( name, function () { @@ -2262,7 +2302,7 @@ function once(target, name, cb) { * @returns {Promise} A promise that is resolved when event happens. */ function nextEvent(video, eventName) { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { let f = function (event) { video.removeEventListener(eventName, f); resolve(event); diff --git a/dom/media/test/mochitest.toml b/dom/media/test/mochitest.toml index de8c690255..3c8a382766 100644 --- a/dom/media/test/mochitest.toml +++ b/dom/media/test/mochitest.toml @@ -88,6 +88,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", @@ -798,6 +802,7 @@ skip-if = [ "os == 'mac'", # 1517199 (timeout-on-osx) "os == 'linux'", # 1713397, 1719881 (high intermittent failure on linux tsan), 1776937 "apple_silicon", # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs + "mda_gpu", # Disabled because high intermittent failure ] ["test_chaining.html"] diff --git a/dom/media/test/mochitest_background_video.toml b/dom/media/test/mochitest_background_video.toml index 762a9a8648..e1bc542264 100644 --- a/dom/media/test/mochitest_background_video.toml +++ b/dom/media/test/mochitest_background_video.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", diff --git a/dom/media/test/mochitest_bugs.toml b/dom/media/test/mochitest_bugs.toml index 9d4a0dea2f..5c68d0e795 100644 --- a/dom/media/test/mochitest_bugs.toml +++ b/dom/media/test/mochitest_bugs.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", @@ -769,7 +773,10 @@ support-files = [ ["test_bug465498.html"] ["test_bug495145.html"] -skip-if = ["os == 'win'"] #Bug 1404373 +skip-if = [ + "win10_2009", #Bug 1404373 + "win11_2009", #Bug 1404373 +] ["test_bug495300.html"] @@ -786,15 +793,15 @@ skip-if = ["os == 'android'"] # bug 1285441, android(bug 1232305) tags = "capturestream" ["test_bug895305.html"] -skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_bug919265.html"] -skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_bug1113600.html"] skip-if = [ - "os == 'win' && os_version == '10.0' && debug", # Bug 1713410 - "os == 'mac'", # Bug 1198168 + "win10_2009 && debug", # Bug 1713410 + "win11_2009 && debug", # Bug 1713410 + "apple_catalina", # Bug 1198168 + "apple_silicon", # Bug 1198168 ] ["test_bug1120222.html"] diff --git a/dom/media/test/mochitest_compat.toml b/dom/media/test/mochitest_compat.toml index c477661a19..86f76f1464 100644 --- a/dom/media/test/mochitest_compat.toml +++ b/dom/media/test/mochitest_compat.toml @@ -90,6 +90,10 @@ support-files = [ "bipbop-cenc-videoinit.mp4^headers^", "bipbop-cenc-video-10s.mp4", "bipbop-cenc-video-10s.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop-clearkey-keyrotation-clear-lead-audio.mp4", "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", @@ -807,15 +811,12 @@ skip-if = ["true"] # bug 475110 - disabled since we don't play Wave files standa ["test_bug1431810_opus_downmix_to_mono.html"] ["test_can_play_type.html"] -skip-if = ["(android_version == '25' && debug)"] # android(bug 1232305) ["test_can_play_type_mpeg.html"] ["test_can_play_type_no_ogg.html"] -skip-if = ["(android_version == '25' && debug)"] # android(bug 1232305) ["test_can_play_type_ogg.html"] -skip-if = ["(android_version == '25' && debug)"] # android(bug 1232305) ["test_can_play_type_wave.html"] @@ -1000,10 +1001,12 @@ skip-if = ["true"] # see bug 1319725 ["test_readyState.html"] ["test_referer.html"] -skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_reset_src.html"] -skip-if = ["verify && debug && os == 'win'"] +skip-if = [ + "win10_2009 && debug && verify", + "win11_2009 && debug && verify", +] ["test_source.html"] diff --git a/dom/media/test/mochitest_eme.toml b/dom/media/test/mochitest_eme.toml index 696e0e947e..d7f39c3eb8 100644 --- a/dom/media/test/mochitest_eme.toml +++ b/dom/media/test/mochitest_eme.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", diff --git a/dom/media/test/mochitest_media_recorder.toml b/dom/media/test/mochitest_media_recorder.toml index 428f99601d..a4893d9cf4 100644 --- a/dom/media/test/mochitest_media_recorder.toml +++ b/dom/media/test/mochitest_media_recorder.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", diff --git a/dom/media/test/mochitest_seek.toml b/dom/media/test/mochitest_seek.toml index 0c90bd1bfb..d71aac775a 100644 --- a/dom/media/test/mochitest_seek.toml +++ b/dom/media/test/mochitest_seek.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", diff --git a/dom/media/test/mochitest_stream.toml b/dom/media/test/mochitest_stream.toml index 043f8471d4..0badfc52ab 100644 --- a/dom/media/test/mochitest_stream.toml +++ b/dom/media/test/mochitest_stream.toml @@ -84,6 +84,10 @@ support-files = [ "bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^", "bipbop-clearkey-keyrotation-clear-lead-video.mp4", "bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^", + "bipbop-clearkey-video-av1.mp4", + "bipbop-clearkey-video-av1.mp4^headers^", + "bipbop-clearkey-video-av1.webm", + "bipbop-clearkey-video-av1.webm^headers^", "bipbop_225w_175kbps.mp4", "bipbop_225w_175kbps.mp4^headers^", "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", diff --git a/dom/media/test/reftest/color_quads/reftest.list b/dom/media/test/reftest/color_quads/reftest.list index 6071733f76..63a538b78a 100644 --- a/dom/media/test/reftest/color_quads/reftest.list +++ b/dom/media/test/reftest/color_quads/reftest.list @@ -14,35 +14,35 @@ defaults pref(media.av1.enabled,true) # - # yuv420p -fuzzy(16-51,5234-5622) fuzzy-if(swgl,32-38,1600-91746) fuzzy-if(useDrawSnapshot,16-16,11600-11600) fuzzy-if(OSX,16-73,5212-5622) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm ../reftest_img.html?src=color_quads/720p.png -fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,0-35,0-1947) fuzzy-if(OSX&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm +fuzzy(16-51,5234-5622) fuzzy-if(swgl,32-38,1600-91746) fuzzy-if(useDrawSnapshot,16-16,11600-11600) fuzzy-if(cocoaWidget,16-73,5212-5622) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm ../reftest_img.html?src=color_quads/720p.png +fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(cocoaWidget,0-35,0-1947) fuzzy-if(cocoaWidget&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm fuzzy-if(winWidget,0-1,0-78) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm -skip-if(winWidget&&isCoverageBuild) fuzzy(0-16,75-1941) fuzzy-if(Android,28-255,273680-359920) fuzzy-if(OSX,30-32,187326-187407) fuzzy-if(appleSilicon,30-48,1835-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm -fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,0-35,0-1947) fuzzy-if(OSX&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm +skip-if(winWidget&&isCoverageBuild) fuzzy(0-16,75-1941) fuzzy-if(Android,28-255,273680-359920) fuzzy-if(cocoaWidget,30-32,187326-187407) fuzzy-if(appleSilicon,30-48,1835-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm +fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(cocoaWidget,0-35,0-1947) fuzzy-if(cocoaWidget&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm skip-if(Android) fuzzy(16-48,8107-8818) fuzzy-if(winWidget&&swgl,31-38,8240-184080) fuzzy-if(appleSilicon,33-38,8819-11705) fuzzy-if(useDrawSnapshot,20-20,187200-187200) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm ../reftest_img.html?src=color_quads/720p.png skip-if(Android) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm # On Windows & sw render, we noticed that the comparison image captured from AV1 is not equal to its displayed video frame, so we would need to compare other codecs directly to PNG file. That should be fixed in bug 1748540. -skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(OSX,0-16,0-1718) fuzzy-if(OSX&&swgl,0-20,0-2423) fuzzy-if(appleSilicon,0-16,0-1874) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm -skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(OSX,2-36,184281-187407) fuzzy-if(winWidget,0-21,0-360000) fuzzy-if(appleSilicon,36-49,187329-187407) fuzzy-if(useDrawSnapshot,0-1,0-10) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm -skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(OSX,0-16,0-1718) fuzzy-if(OSX&&swgl,0-20,0-2423) fuzzy-if(appleSilicon,0-16,0-1874) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm +skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(cocoaWidget,0-16,0-1718) fuzzy-if(cocoaWidget&&swgl,0-20,0-2423) fuzzy-if(appleSilicon,0-16,0-1874) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm +skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(cocoaWidget,2-36,184281-187407) fuzzy-if(winWidget,0-21,0-360000) fuzzy-if(appleSilicon,36-49,187329-187407) fuzzy-if(useDrawSnapshot,0-1,0-10) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm +skip-if(Android) skip-if(winWidget&&swgl) fuzzy-if(Android,255-255,273726-273726) fuzzy-if(cocoaWidget,0-16,0-1718) fuzzy-if(cocoaWidget&&swgl,0-20,0-2423) fuzzy-if(appleSilicon,0-16,0-1874) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm skip-if(Android) skip-if(!(winWidget&&swgl)) fuzzy(0-35,0-8506) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.webm ../reftest_img.html?src=color_quads/720p.png skip-if(Android) skip-if(!(winWidget&&swgl)) fuzzy(0-35,0-8506) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.vp9.mp4 ../reftest_img.html?src=color_quads/720p.png # - # yuv420p10 -skip-if(Android) fuzzy(33-49,1870-2579) fuzzy-if(swgl,34-52,180421-270528) fuzzy-if(useDrawSnapshot,16-16,183840-183840) fuzzy-if(OSX,60-74,270329-271024) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm ../reftest_img.html?src=color_quads/720p.png -skip-if(Android) fuzzy-if(OSX,0-12,0-187770) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm +skip-if(Android) fuzzy(33-49,1870-2579) fuzzy-if(swgl,34-52,180421-270528) fuzzy-if(useDrawSnapshot,16-16,183840-183840) fuzzy-if(cocoaWidget,60-74,270329-271024) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm ../reftest_img.html?src=color_quads/720p.png +skip-if(Android) fuzzy-if(cocoaWidget,0-12,0-187770) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm skip-if(Android) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm #[2] skip-if(Android) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm -skip-if(Android) fuzzy-if(OSX,0-12,0-187770) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm +skip-if(Android) fuzzy-if(cocoaWidget,0-12,0-187770) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p10.av1.webm -skip-if(Android) fuzzy(33-49,174620-270059) fuzzy-if(swgl&&!winWidget,36-52,11553-11555) fuzzy-if(swgl&&winWidget,36-52,11554-187200) fuzzy-if(swgl&&OSX,34-50,11465-270059) fuzzy-if(useDrawSnapshot,20-20,186800-186800) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm ../reftest_img.html?src=color_quads/720p.png -skip-if(Android) fuzzy-if(OSX,0-12,0-274122) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm +skip-if(Android) fuzzy(33-49,174620-270059) fuzzy-if(!winWidget&&swgl,36-52,11553-11555) fuzzy-if(winWidget&&swgl,36-52,11554-187200) fuzzy-if(swgl&&cocoaWidget,34-50,11465-270059) fuzzy-if(useDrawSnapshot,20-20,186800-186800) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm ../reftest_img.html?src=color_quads/720p.png +skip-if(Android) fuzzy-if(cocoaWidget,0-12,0-274122) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm skip-if(Android) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm #[2] skip-if(Android) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm -skip-if(Android) fuzzy-if(OSX,0-12,0-274122) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm +skip-if(Android) fuzzy-if(cocoaWidget,0-12,0-274122) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p10.av1.webm # Android is really broken in a variety of ways for p10. #[2]: yuv420p10 broken in h264.mp4: https://bugzilla.mozilla.org/show_bug.cgi?id=1711812 diff --git a/dom/media/test/reftest/reftest.list b/dom/media/test/reftest/reftest.list index 6bbd7b9a53..0f709a35ee 100644 --- a/dom/media/test/reftest/reftest.list +++ b/dom/media/test/reftest/reftest.list @@ -1,12 +1,12 @@ -skip-if(Android) fuzzy-if(OSX,0-80,0-76800) fuzzy-if(appleSilicon,0-80,0-76800) fuzzy-if(winWidget,0-63,0-76799) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-70,0-2032) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html -skip-if(Android) fuzzy-if(OSX,0-87,0-76797) fuzzy-if(appleSilicon,0-87,0-76797) fuzzy-if(winWidget,0-60,0-76797) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-60,0-6070) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html -skip-if(Android) skip-if(cocoaWidget) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-57,0-4282) fuzzy-if(OSX,55-80,4173-4417) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html -skip-if(Android) fuzzy-if(OSX,0-25,0-175921) fuzzy-if(appleSilicon,34-34,40100-40100) fuzzy-if(winWidget,0-71,0-179198) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html +skip-if(Android) fuzzy-if(cocoaWidget,0-80,0-76800) fuzzy-if(appleSilicon,0-80,0-76800) fuzzy-if(winWidget,0-63,0-76799) fuzzy-if(gtkWidget,0-70,0-2032) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html +skip-if(Android) fuzzy-if(cocoaWidget,0-87,0-76797) fuzzy-if(appleSilicon,0-87,0-76797) fuzzy-if(winWidget,0-60,0-76797) fuzzy-if(gtkWidget,0-60,0-6070) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html +skip-if(Android) skip-if(cocoaWidget) skip-if(winWidget) fuzzy-if(gtkWidget,0-57,0-4282) fuzzy-if(cocoaWidget,55-80,4173-4417) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html +skip-if(Android) fuzzy-if(cocoaWidget,0-25,0-175921) fuzzy-if(appleSilicon,34-34,40100-40100) fuzzy-if(winWidget,0-71,0-179198) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html # Bug 1758718 -skip-if(Android) skip-if(MinGW) skip-if(OSX) fuzzy(0-10,0-778236) == image-10bits-rendering-video.html image-10bits-rendering-ref.html -skip-if(Android) skip-if(MinGW) fuzzy(0-10,0-778536) fuzzy-if(appleSilicon,0-37,0-699614) == image-10bits-rendering-90-video.html image-10bits-rendering-90-ref.html +skip-if(Android) skip-if(cocoaWidget) fuzzy(0-10,0-778236) == image-10bits-rendering-video.html image-10bits-rendering-ref.html +skip-if(Android) fuzzy(0-10,0-778536) fuzzy-if(appleSilicon,0-37,0-699614) == image-10bits-rendering-90-video.html image-10bits-rendering-90-ref.html # Bug 1758718 -skip-if(Android) fuzzy(0-27,0-573106) skip-if(OSX) == image-10bits-rendering-720-video.html image-10bits-rendering-720-ref.html +skip-if(Android) fuzzy(0-27,0-573106) skip-if(cocoaWidget) == image-10bits-rendering-720-video.html image-10bits-rendering-720-ref.html skip-if(Android) fuzzy(0-31,0-573249) fuzzy-if(appleSilicon,0-37,0-543189) == image-10bits-rendering-720-90-video.html image-10bits-rendering-720-90-ref.html skip-if(Android) fuzzy(0-84,0-771156) fails-if(useDrawSnapshot) == uneven_frame_duration_video.html uneven_frame_duration_video-ref.html # Skip on Windows 7 as the resolution of the video is too high for test machines and will fail in the decoder. # Set media.dormant-on-pause-timeout-ms to avoid decoders becoming dormant and busting test, skip on android as test is too noisy and unstable diff --git a/dom/media/test/test_arraybuffer.html b/dom/media/test/test_arraybuffer.html index 9ef84c53dc..146d3ea5ac 100644 --- a/dom/media/test/test_arraybuffer.html +++ b/dom/media/test/test_arraybuffer.html @@ -49,14 +49,14 @@ function startTest(test, token) { events.forEach(function(e) { v.addEventListener(e, logEvent); }); - once(v, "stalled", function(e) { + once(v, "stalled", function() { // Resource fetch algorithm in local mode should never fire stalled event. // https://html.spec.whatwg.org/multipage/media.html#concept-media-load-resource ok(false, test.name + ": got stalled"); removeNodeAndSource(v); manager.finished(token); }); - once(v, "canplaythrough", function(e) { + once(v, "canplaythrough", function() { ok(true, test.name + ": got canplaythrough"); is(v.readyState, v.HAVE_ENOUGH_DATA, test.name + ": readyState is HAVE_ENOUGH_DATA"); removeNodeAndSource(v); diff --git a/dom/media/test/test_aspectratio_mp4.html b/dom/media/test/test_aspectratio_mp4.html index 5e01875439..959096e902 100644 --- a/dom/media/test/test_aspectratio_mp4.html +++ b/dom/media/test/test_aspectratio_mp4.html @@ -28,7 +28,7 @@ v.onloadedmetadata = function() { is(v.videoHeight, resource.height, "Intrinsic height should match video height"); SimpleTest.finish(); } -v.addEventListener("error", function(ev) { +v.addEventListener("error", function() { if (v.readyState < v.HAVE_METADATA) { info("Video element returns with readyState " + v.readyState + " error.code " + v.error.code); todo(false, "This platform doesn't support to retrieve MP4 metadata."); diff --git a/dom/media/test/test_background_video_resume_after_end_show_last_frame.html b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html index 68f7179e12..991de9ac98 100644 --- a/dom/media/test/test_background_video_resume_after_end_show_last_frame.html +++ b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html @@ -79,7 +79,7 @@ function waitUntilSeekToLastFrame(video) { ); } - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { video.seenEnded = false; video.addEventListener("ended", () => { video.seenEnded = true; diff --git a/dom/media/test/test_buffered.html b/dom/media/test/test_buffered.html index 86d8eec28a..85bbef2189 100644 --- a/dom/media/test/test_buffered.html +++ b/dom/media/test/test_buffered.html @@ -74,7 +74,7 @@ function fetch(url, fetched_callback) { xhr.open("GET", url, true); xhr.responseType = "blob"; - var loaded = function (event) { + var loaded = function () { if (xhr.status == 200 || xhr.status == 206) { ok(true, `${url}: Fetch succeeded, status=${xhr.status}`); // Request fulfilled. Note sometimes we get 206... Presumably because either diff --git a/dom/media/test/test_bug1431810_opus_downmix_to_mono.html b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html index 647ddf0489..2d81fba1c7 100644 --- a/dom/media/test/test_bug1431810_opus_downmix_to_mono.html +++ b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html @@ -116,14 +116,14 @@ function mediaElementWithPhaseInversionDisabled(audioContext, mediaElement, succ let ac = new AudioContext(); -function testPhaseInversion(mediaElement) { - return new Promise((accept, reject) => { +function testPhaseInversion() { + return new Promise((accept) => { mediaElementWithPhaseInversion(ac, a, accept); }); } -function testPhaseInversionDisabled(mediaElement) { - return new Promise((accept, reject) => { +function testPhaseInversionDisabled() { + return new Promise((accept) => { mediaElementWithPhaseInversionDisabled(ac, b, accept); }); } diff --git a/dom/media/test/test_cueless_webm_seek-1.html b/dom/media/test/test_cueless_webm_seek-1.html index db58a89665..dbc2933b92 100644 --- a/dom/media/test/test_cueless_webm_seek-1.html +++ b/dom/media/test/test_cueless_webm_seek-1.html @@ -101,7 +101,7 @@ function fetch(url, fetched_callback) { xhr.open("GET", url, true); xhr.responseType = "blob"; - var loaded = function (event) { + var loaded = function () { if (xhr.status == 200 || xhr.status == 206) { // Request fulfilled. Note sometimes we get 206... Presumably because either // httpd.js or Necko cached the result. diff --git a/dom/media/test/test_cueless_webm_seek-2.html b/dom/media/test/test_cueless_webm_seek-2.html index 720cc18399..f9fd29fcfb 100644 --- a/dom/media/test/test_cueless_webm_seek-2.html +++ b/dom/media/test/test_cueless_webm_seek-2.html @@ -91,7 +91,7 @@ function fetch(url, fetched_callback) { xhr.open("GET", url, true); xhr.responseType = "blob"; - var loaded = function (event) { + var loaded = function () { if (xhr.status == 200 || xhr.status == 206) { // Request fulfilled. Note sometimes we get 206... Presumably because either // httpd.js or Necko cached the result. diff --git a/dom/media/test/test_cueless_webm_seek-3.html b/dom/media/test/test_cueless_webm_seek-3.html index d6e3e50d7d..235066e6eb 100644 --- a/dom/media/test/test_cueless_webm_seek-3.html +++ b/dom/media/test/test_cueless_webm_seek-3.html @@ -85,7 +85,7 @@ function fetch(url, fetched_callback) { xhr.open("GET", url, true); xhr.responseType = "blob"; - var loaded = function (event) { + var loaded = function () { if (xhr.status == 200 || xhr.status == 206) { // Request fulfilled. Note sometimes we get 206... Presumably because either // httpd.js or Necko cached the result. diff --git a/dom/media/test/test_decode_error_crossorigin.html b/dom/media/test/test_decode_error_crossorigin.html index 24c1430a5b..8fa3db6c38 100644 --- a/dom/media/test/test_decode_error_crossorigin.html +++ b/dom/media/test/test_decode_error_crossorigin.html @@ -19,7 +19,7 @@ function startTest(test, token) { }; const v = document.createElement("video"); manager.started(token); - v.addEventListener("error", event => { + v.addEventListener("error", () => { if (v.readyState == v.HAVE_NOTHING) { is(v.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected code for a load error"); diff --git a/dom/media/test/test_delay_load.html b/dom/media/test/test_delay_load.html index 05877aa911..6c10e5b957 100644 --- a/dom/media/test/test_delay_load.html +++ b/dom/media/test/test_delay_load.html @@ -87,7 +87,7 @@ var w = window.open("", "testWindow", "width=400,height=400"); testWindows.push(w); v = createVideo(test.name, test.type, "4"); -v.onloadstart = function(e) { +v.onloadstart = function() { // Using a new window to do this is a bit annoying, but if we use an iframe here, // delaying of the iframe's load event might interfere with the firing of our load event // in some confusing way. So it's simpler just to use another window. diff --git a/dom/media/test/test_eme_detach_media_keys.html b/dom/media/test/test_eme_detach_media_keys.html index 69b812032a..5faa68d9c5 100644 --- a/dom/media/test/test_eme_detach_media_keys.html +++ b/dom/media/test/test_eme_detach_media_keys.html @@ -15,7 +15,7 @@ SimpleTest.waitForExplicitFinish(); function createAndSet() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var m; navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig) .then(function (access) { diff --git a/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html index e47fae5891..6911ac2da8 100644 --- a/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html +++ b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html @@ -72,7 +72,7 @@ function startTest(test, token) ok(false, " Failed to set original mediakeys back."); } - function onCanPlayAgain(ev) { + function onCanPlayAgain() { Promise.all([closeSessions()]) .then(() => { ok(true, " (ENCRYPTED) Playback can be resumed."); @@ -91,7 +91,7 @@ function startTest(test, token) v.currentTime = v.duration / 2; } - function onCanPlay(ev) { + function onCanPlay() { function onSetMediaKeysToNullOK() { ok(true, TimeStamp(token) + " Set MediaKeys to null. OK!"); diff --git a/dom/media/test/test_eme_getstatusforpolicy.html b/dom/media/test/test_eme_getstatusforpolicy.html index 2031d04df5..6e9503e33a 100644 --- a/dom/media/test/test_eme_getstatusforpolicy.html +++ b/dom/media/test/test_eme_getstatusforpolicy.html @@ -68,7 +68,7 @@ add_task(async function testGetStatusForPolicy() { "Should have set MediaKeys on media element"); let keyStatus = await video.mediaKeys.getStatusForPolicy({minHdcpVersion : result.minHdcpVersion}) - .catch(e => ok(false, "getStatusForPolicy failed!")); + .catch(() => ok(false, "getStatusForPolicy failed!")); info(`getStatusForPolicy for HDCP ${result.minHdcpVersion} : ${keyStatus}`); is(keyStatus, result.expectedResult, `Expected ${result.expectedResult}, got ${keyStatus}`); diff --git a/dom/media/test/test_eme_initDataTypes.html b/dom/media/test/test_eme_initDataTypes.html index 587e6fc161..a1e0c15182 100644 --- a/dom/media/test/test_eme_initDataTypes.html +++ b/dom/media/test/test_eme_initDataTypes.html @@ -93,7 +93,7 @@ function PrepareInitData(initDataType, initData) } function Test(test) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var configs = [{ initDataTypes: [test.initDataType], videoCapabilities: [{contentType: 'video/mp4' }], @@ -113,7 +113,7 @@ function Test(test) { var initData = PrepareInitData(test.initDataType, test.initData); return session.generateRequest(test.initDataType, initData); } - ).catch((x) => { + ).catch(() => { ok(!test.expectPass, "'" + test.name + "' expected to fail."); resolve(); }); diff --git a/dom/media/test/test_eme_mfcdm_generate_request.html b/dom/media/test/test_eme_mfcdm_generate_request.html index 57be325e36..b1b8396c0f 100644 --- a/dom/media/test/test_eme_mfcdm_generate_request.html +++ b/dom/media/test/test_eme_mfcdm_generate_request.html @@ -65,11 +65,11 @@ async function testKeySystemRequest(keySystem) { }]; info(`requestMediaKeySystemAccess for ${keySystem}`); let access = await navigator.requestMediaKeySystemAccess(keySystem, configs) - .catch(e => ok(false, `failed to create key system access`)); + .catch(() => ok(false, `failed to create key system access`)); info('creating media key'); let mediaKeys = await access.createMediaKeys() - .catch(e => ok(false, `failed to create media key`));; + .catch(() => ok(false, `failed to create media key`));; info(`creating a temporary media key session`); let session = mediaKeys.createSession(sessionType); @@ -84,7 +84,7 @@ async function testKeySystemRequest(keySystem) { await session.generateRequest( 'keyids', new TextEncoder().encode(fakeKID)) - .catch(e => ok(false, `failed to generate request`)); + .catch(() => ok(false, `failed to generate request`)); await messagePromise; } diff --git a/dom/media/test/test_eme_mfcdm_getstatusforpolicy.html b/dom/media/test/test_eme_mfcdm_getstatusforpolicy.html index 7e22be25d9..83cd740570 100644 --- a/dom/media/test/test_eme_mfcdm_getstatusforpolicy.html +++ b/dom/media/test/test_eme_mfcdm_getstatusforpolicy.html @@ -89,7 +89,7 @@ add_task(async function testGetStatusForPolicy() { "Should have set MediaKeys on media element"); let keyStatus = await video.mediaKeys.getStatusForPolicy({minHdcpVersion : result.minHdcpVersion}) - .catch(e => ok(false, "getStatusForPolicy failed!")); + .catch(() => ok(false, "getStatusForPolicy failed!")); info(`getStatusForPolicy for HDCP ${result.minHdcpVersion} : ${keyStatus}`); is(keyStatus, result.expectedResult, `Expected ${result.expectedResult}, got ${keyStatus}`); diff --git a/dom/media/test/test_eme_missing_pssh.html b/dom/media/test/test_eme_missing_pssh.html index 29f77d021a..cb94ffbf23 100644 --- a/dom/media/test/test_eme_missing_pssh.html +++ b/dom/media/test/test_eme_missing_pssh.html @@ -63,7 +63,7 @@ } function DownloadMedia(url, type, mediaSource) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var sourceBuffer = mediaSource.addSourceBuffer(type); fetchWithXHR(url, (response) => { once(sourceBuffer, "updateend", resolve); diff --git a/dom/media/test/test_eme_non_mse_fails.html b/dom/media/test/test_eme_non_mse_fails.html index 6ff17d59ff..e2719f244c 100644 --- a/dom/media/test/test_eme_non_mse_fails.html +++ b/dom/media/test/test_eme_non_mse_fails.html @@ -65,7 +65,7 @@ function TestSetSrc(test, token) manager.started(token); var v = document.createElement("video"); - v.addEventListener("error", function(err) { + v.addEventListener("error", function() { ok(true, token + " got error setting src on video element, as expected"); manager.finished(token); }); diff --git a/dom/media/test/test_eme_playback.html b/dom/media/test/test_eme_playback.html index bcfa058e34..478b7b0a25 100644 --- a/dom/media/test/test_eme_playback.html +++ b/dom/media/test/test_eme_playback.html @@ -109,7 +109,7 @@ function startTest(test, token) var gotEncrypted = 0; let finish = new EMEPromise; - v.addEventListener("encrypted", function(ev) { + v.addEventListener("encrypted", function() { gotEncrypted += 1; }); @@ -119,7 +119,7 @@ function startTest(test, token) is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content"); }); - v.addEventListener("ended", function(ev) { + v.addEventListener("ended", function() { ok(true, TimeStamp(token) + " got ended event"); is(gotEncrypted, test.sessionCount, diff --git a/dom/media/test/test_eme_protection_query.html b/dom/media/test/test_eme_protection_query.html index 8bf97d8100..a8a1c7b376 100644 --- a/dom/media/test/test_eme_protection_query.html +++ b/dom/media/test/test_eme_protection_query.html @@ -53,7 +53,7 @@ async function setupEme(video) { let session = video.mediaKeys.createSession(); video.onencrypted = async encryptedEvent => { - session.onmessage = messageEvent => { + session.onmessage = () => { // Handle license messages. Hard code the license because we always test // with the same file and we know what the license should be. const license = { diff --git a/dom/media/test/test_eme_pssh_in_moof.html b/dom/media/test/test_eme_pssh_in_moof.html index d1965be844..406600056e 100644 --- a/dom/media/test/test_eme_pssh_in_moof.html +++ b/dom/media/test/test_eme_pssh_in_moof.html @@ -61,7 +61,7 @@ // Specialized create media keys function, since the one in eme.js relies
// on listening for encrypted events, and we want to manage those
// ourselves within this test.
- async function createAndSetMediaKeys(video, test, token) {
+ async function createAndSetMediaKeys(video, test) {
function streamType(type) {
var x = test.tracks.find(o => o.name == type);
return x ? x.type : undefined;
diff --git a/dom/media/test/test_eme_requestKeySystemAccess.html b/dom/media/test/test_eme_requestKeySystemAccess.html index b044fe8c84..4014ef937a 100644 --- a/dom/media/test/test_eme_requestKeySystemAccess.html +++ b/dom/media/test/test_eme_requestKeySystemAccess.html @@ -20,7 +20,7 @@ function ValidateConfig(name, expected, observed) { is(observed.label, expected.label, name + " label should match"); if (expected.initDataTypes) { - ok(expected.initDataTypes.every((element, index, array) => observed.initDataTypes.includes(element)), name + " initDataTypes should match."); + ok(expected.initDataTypes.every((element) => observed.initDataTypes.includes(element)), name + " initDataTypes should match."); } if (expected.audioCapabilities) { @@ -45,7 +45,7 @@ function ValidateConfig(name, expected, observed) { function Test(test) { var name = "'" + test.name + "'"; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var p; if (test.options) { var keySystem = (test.keySystem !== undefined) ? test.keySystem : CLEARKEY_KEYSYSTEM; diff --git a/dom/media/test/test_eme_request_notifications.html b/dom/media/test/test_eme_request_notifications.html index 6c44f892a6..62ef28e57d 100644 --- a/dom/media/test/test_eme_request_notifications.html +++ b/dom/media/test/test_eme_request_notifications.html @@ -17,7 +17,7 @@ function SetPrefs(prefs) { } function observe() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var observer = function(subject, topic, data) { SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request"); resolve(JSON.parse(data).status); @@ -70,7 +70,7 @@ var tests = [ SimpleTest.waitForExplicitFinish(); tests - .reduce(function(p, c, i, array) { + .reduce(function(p, c) { return p.then(function() { return Test(c); }); diff --git a/dom/media/test/test_eme_sample_groups_playback.html b/dom/media/test/test_eme_sample_groups_playback.html index cef1e26b33..e1e09ad73d 100644 --- a/dom/media/test/test_eme_sample_groups_playback.html +++ b/dom/media/test/test_eme_sample_groups_playback.html @@ -81,7 +81,7 @@ }); var request = new TextEncoder().encode(json); session.generateRequest("keyids", request) - .then(e => { + .then(() => { Log(test.name, "Request license success"); }, reason => { Log("Request license failed! " + reason); @@ -90,7 +90,7 @@ } function DownloadMedia(url, type, mediaSource) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { var sourceBuffer = mediaSource.addSourceBuffer(type); fetchWithXHR(url, (response) => { once(sourceBuffer, "updateend", resolve); diff --git a/dom/media/test/test_eme_stream_capture_blocked_case1.html b/dom/media/test/test_eme_stream_capture_blocked_case1.html index 0fc8d28364..e6743b4f8a 100644 --- a/dom/media/test/test_eme_stream_capture_blocked_case1.html +++ b/dom/media/test/test_eme_stream_capture_blocked_case1.html @@ -35,7 +35,7 @@ function startTest(test, token) var context = new AudioContext(); context.createMediaElementSource(v1); - v1.addEventListener("loadeddata", function(ev) { + v1.addEventListener("loadeddata", function() { ok(false, TimeStamp(case1token) + " should never reach loadeddata, as setMediaKeys should fail"); }); diff --git a/dom/media/test/test_eme_stream_capture_blocked_case2.html b/dom/media/test/test_eme_stream_capture_blocked_case2.html index b60538caf0..6ee415c124 100644 --- a/dom/media/test/test_eme_stream_capture_blocked_case2.html +++ b/dom/media/test/test_eme_stream_capture_blocked_case2.html @@ -20,7 +20,7 @@ function startTest(test, token) var case2token = token + "_case2"; let v2 = document.createElement("video"); - v2.addEventListener("loadeddata", function(ev) { + v2.addEventListener("loadeddata", function() { ok(true, case2token + " should reach loadeddata"); var threw = false; try { diff --git a/dom/media/test/test_eme_stream_capture_blocked_case3.html b/dom/media/test/test_eme_stream_capture_blocked_case3.html index e25a900956..23a23542e9 100644 --- a/dom/media/test/test_eme_stream_capture_blocked_case3.html +++ b/dom/media/test/test_eme_stream_capture_blocked_case3.html @@ -19,7 +19,7 @@ function startTest(test, token) var case3token = token + "_case3"; let v3 = document.createElement("video"); - v3.addEventListener("loadeddata", function(ev) { + v3.addEventListener("loadeddata", function() { ok(true, TimeStamp(case3token) + " should reach loadeddata"); var threw = false; try { diff --git a/dom/media/test/test_eme_unsetMediaKeys_then_capture.html b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html index 3ecdc79dbf..eb20f2bee2 100644 --- a/dom/media/test/test_eme_unsetMediaKeys_then_capture.html +++ b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html @@ -36,11 +36,11 @@ function startTest(test, token) let finish = new EMEPromise; - function onVideoEnded(ev) { + function onVideoEnded() { ok(true, TimeStamp(token) + " (ENCRYPTED) content playback ended."); function playClearVideo() { - var p1 = once(v, 'loadeddata', (e) => { + var p1 = once(v, 'loadeddata', () => { ok(true, TimeStamp(token) + " Receiving event 'loadeddata' for (CLEAR) content."); let canvasElem = document.createElement('canvas'); document.body.appendChild(canvasElem); diff --git a/dom/media/test/test_eme_wideinve_l1_installation.html b/dom/media/test/test_eme_wideinve_l1_installation.html index fa6a0c350b..da1f1ab0f9 100644 --- a/dom/media/test/test_eme_wideinve_l1_installation.html +++ b/dom/media/test/test_eme_wideinve_l1_installation.html @@ -19,7 +19,7 @@ function SetPrefs(prefs) { } function observe() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var observer = function(subject, topic, data) { SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request"); resolve(JSON.parse(data).status); @@ -74,7 +74,7 @@ const tests = [ }, ]; -tests.reduce(function(p, c, i, array) { +tests.reduce(function(p, c) { return p.then(function() { return Test(c); }); diff --git a/dom/media/test/test_eme_wv_privacy.html b/dom/media/test/test_eme_wv_privacy.html index a050d2a528..e18d31a36f 100644 --- a/dom/media/test/test_eme_wv_privacy.html +++ b/dom/media/test/test_eme_wv_privacy.html @@ -12,7 +12,7 @@ <script class="testbody" type="text/javascript"> function Test() { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var configs = [{ initDataTypes: ['keyids'], videoCapabilities: [{contentType: 'video/mp4' }], diff --git a/dom/media/test/test_hls_player_independency.html b/dom/media/test/test_hls_player_independency.html index cea5c140ed..ed2d5cee52 100644 --- a/dom/media/test/test_hls_player_independency.html +++ b/dom/media/test/test_hls_player_independency.html @@ -27,12 +27,12 @@ function startTest() { var v4x3 = document.getElementById('player4x3'); var v16x9 = document.getElementById('player16x9'); - var p1 = once(v4x3, 'ended', function onended(e) { + var p1 = once(v4x3, 'ended', function onended() { is(v4x3.videoWidth, 400, "4x3 content, the width should be 400."); is(v4x3.videoHeight, 300, "4x3 content, the height should be 300."); }); - var p2 = once(v16x9, 'ended', function onended(e) { + var p2 = once(v16x9, 'ended', function onended() { is(v16x9.videoWidth, 416, "16x9 content, the width should be 416."); is(v16x9.videoHeight, 234, "16x9 content, the height should be 234."); }); diff --git a/dom/media/test/test_hw_video_decoding.html b/dom/media/test/test_hw_video_decoding.html index f93ab9b0a8..43ac27bf71 100644 --- a/dom/media/test/test_hw_video_decoding.html +++ b/dom/media/test/test_hw_video_decoding.html @@ -24,7 +24,10 @@ const PLATFORMS = { // file: "gizmo.webm", // decoder: "wmf VP9 codec hardware video decoder", // }, - // TODO : add AV1, see bug 1861501. + { + file: "av1.mp4", + decoder: "wmf AV1 codec hardware video decoder", + }, ], }, Darwin: { diff --git a/dom/media/test/test_invalid_reject.html b/dom/media/test/test_invalid_reject.html index 583847fe12..4023642632 100644 --- a/dom/media/test/test_invalid_reject.html +++ b/dom/media/test/test_invalid_reject.html @@ -18,7 +18,7 @@ function startTest(test, token) { manager.started(token); // Set up event handlers. Seeing any of these is a failure. - function badEvent(type) { return function(e) { + function badEvent(type) { return function() { ok(false, test.name + " should not fire '" + type + "' event"); }}; var events = [ @@ -31,7 +31,7 @@ function startTest(test, token) { }); // Seeing a decoder error is a success. - v.addEventListener("error", function onerror(e) { + v.addEventListener("error", function onerror() { if (v.readyState == v.HAVE_NOTHING) { is(v.error.code, v.error.MEDIA_ERR_SRC_NOT_SUPPORTED, "decoder should reject " + test.name); diff --git a/dom/media/test/test_invalid_reject_play.html b/dom/media/test/test_invalid_reject_play.html index 3e658f94b8..bbfbbc34cc 100644 --- a/dom/media/test/test_invalid_reject_play.html +++ b/dom/media/test/test_invalid_reject_play.html @@ -18,14 +18,14 @@ function startTest(test, token) { manager.started(token); // Seeing a decoder error is a success. - v.addEventListener("error", function onerror(e) { + v.addEventListener("error", function onerror() { is(v.error.code, v.error.MEDIA_ERR_DECODE, "decoder should reject " + test.name); v.removeEventListener("error", onerror); manager.finished(token); }); - v.addEventListener("ended", function onended(e) { + v.addEventListener("ended", function onended() { ok(false, "decoder should have rejected file before playback ended"); v.removeEventListener("ended", onended); manager.finished(token); diff --git a/dom/media/test/test_load.html b/dom/media/test/test_load.html index de8fd63948..a502a6b23a 100644 --- a/dom/media/test/test_load.html +++ b/dom/media/test/test_load.html @@ -18,7 +18,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=479859 <pre id="test"> <script type="text/javascript"> -function log(msg) { +function log() { //document.getElementById('log').innerHTML += "<p>" + msg + "</p>"; } @@ -89,7 +89,7 @@ function prependSource(src, type) { var gTests = [ { // Test 0: adding video to doc, then setting src should load implicitly. - create(src, type) { + create(src) { document.body.appendChild(gMedia); gMedia.src = src; }, @@ -126,7 +126,7 @@ var gTests = [ addSource("404a", type); var s2 = addSource("404b", type); s2.addEventListener("error", - function(e) { + function() { // Should awaken waiting load, causing successful load. addSource(src, type); }); @@ -143,7 +143,7 @@ var gTests = [ addSource("404a", type); var s2 = addSource("404b", type); s2.addEventListener("error", - function(e) { + function() { // Should awaken waiting load, causing successful load. if (!prepended) { prependSource(src, type); diff --git a/dom/media/test/test_load_source.html b/dom/media/test/test_load_source.html index 95a925b61f..0792bb924b 100644 --- a/dom/media/test/test_load_source.html +++ b/dom/media/test/test_load_source.html @@ -26,7 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=534571 var v = null; var s = null; -function finish(event) { +function finish() { ok(true, "Should have played both videos"); SimpleTest.finish(); } @@ -34,7 +34,7 @@ function finish(event) { var first = null; var second = null; -function ended(event) { +function ended() { s.type = second.type; s.src = second.name; v.removeEventListener("ended", ended); diff --git a/dom/media/test/test_media_selection.html b/dom/media/test/test_media_selection.html index 42f5a9bd43..33ecabfd58 100644 --- a/dom/media/test/test_media_selection.html +++ b/dom/media/test/test_media_selection.html @@ -32,7 +32,7 @@ function maketest(attach_media, name, type, check_metadata) { manager.finished(token); }); } else { - e.addEventListener('error', function onerror(event) { + e.addEventListener('error', function onerror() { is(errorRun, false, "error handler should run once only!"); errorRun = true; is(e.readyState, HTMLMediaElement.HAVE_NOTHING, @@ -46,7 +46,7 @@ function maketest(attach_media, name, type, check_metadata) { } } -function set_src(element, name, type) { +function set_src(element, name) { element.src = name; document.body.appendChild(element); } diff --git a/dom/media/test/test_mediarecorder_record_canvas_captureStream.html b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html index 0b6cd6dbb5..d80a06e8ff 100644 --- a/dom/media/test/test_mediarecorder_record_canvas_captureStream.html +++ b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html @@ -51,7 +51,7 @@ function startTest() { video.id = "recorded-video"; video.src = URL.createObjectURL(blob); video.play(); - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/media/test/test_mediarecorder_record_changing_video_resolution.html b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html index d6354ee5a1..b4e3a6a3fb 100644 --- a/dom/media/test/test_mediarecorder_record_changing_video_resolution.html +++ b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html @@ -70,7 +70,7 @@ function startTest() { video.src = URL.createObjectURL(blob); video.preload = "metadata"; - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/media/test/test_mediarecorder_record_downsize_resolution.html b/dom/media/test/test_mediarecorder_record_downsize_resolution.html index f9422a3897..5e2fbd13c1 100644 --- a/dom/media/test/test_mediarecorder_record_downsize_resolution.html +++ b/dom/media/test/test_mediarecorder_record_downsize_resolution.html @@ -64,7 +64,7 @@ function startTest() { var video = document.createElement("video"); video.id = "recorded-video"; video.src = URL.createObjectURL(blob); - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html index 961a9644b2..7c70ddf49d 100644 --- a/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html +++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html @@ -53,7 +53,7 @@ async function startTest() { // We'll stop recording upon the 1st blob being received if (dataAvailableCount === 1) { - mediaRecorder.onstop = function (event) { + mediaRecorder.onstop = function () { info('onstop fired'); if (!onDataAvailableFirst) { diff --git a/dom/media/test/test_mediarecorder_record_session.html b/dom/media/test/test_mediarecorder_record_session.html index 88795d82d0..a14d3c2819 100644 --- a/dom/media/test/test_mediarecorder_record_session.html +++ b/dom/media/test/test_mediarecorder_record_session.html @@ -44,7 +44,7 @@ function startTest(test, token) { } // data avaliable. - mediaRecorder.ondataavailable = function(evt) {} + mediaRecorder.ondataavailable = function() {} mediaRecorder.onerror = function(err) { ok(false, 'Unexpected error fired with:' + err); diff --git a/dom/media/test/test_mediarecorder_record_startstopstart.html b/dom/media/test/test_mediarecorder_record_startstopstart.html index b4cc62c709..2556072979 100644 --- a/dom/media/test/test_mediarecorder_record_startstopstart.html +++ b/dom/media/test/test_mediarecorder_record_startstopstart.html @@ -18,7 +18,7 @@ function startTest() { var stopCount = 0; var dataavailable = 0; var expectedMimeType = 'audio/ogg; codecs=opus'; - recorder.onstop = function (e) { + recorder.onstop = function () { info('onstop fired'); is(recorder.stream, dest.stream, 'Media recorder stream = element stream post recording'); @@ -45,7 +45,7 @@ function startTest() { } dataavailable++; } - recorder.onerror = function (e) { + recorder.onerror = function () { ok(false, 'it should execute normally without exception'); } recorder.onwarning = function() { diff --git a/dom/media/test/test_mediarecorder_record_timeslice.html b/dom/media/test/test_mediarecorder_record_timeslice.html index 3e547e77b4..b122c45df5 100644 --- a/dom/media/test/test_mediarecorder_record_timeslice.html +++ b/dom/media/test/test_mediarecorder_record_timeslice.html @@ -68,7 +68,7 @@ function startTest(test, token) { // We'll stop recording upon the 1st blob being received if (dataAvailableCount === 1) { - mediaRecorder.onstop = function (event) { + mediaRecorder.onstop = function () { info('onstop fired'); if (!onDataAvailableFirst) { diff --git a/dom/media/test/test_mediarecorder_record_upsize_resolution.html b/dom/media/test/test_mediarecorder_record_upsize_resolution.html index d02fd08e44..81a761479c 100644 --- a/dom/media/test/test_mediarecorder_record_upsize_resolution.html +++ b/dom/media/test/test_mediarecorder_record_upsize_resolution.html @@ -64,7 +64,7 @@ function startTest() { var video = document.createElement("video"); video.id = "recorded-video"; video.src = URL.createObjectURL(blob); - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/media/test/test_mediatrack_consuming_mediaresource.html b/dom/media/test/test_mediatrack_consuming_mediaresource.html index 515df5c053..052b7cb667 100644 --- a/dom/media/test/test_mediatrack_consuming_mediaresource.html +++ b/dom/media/test/test_mediatrack_consuming_mediaresource.html @@ -29,27 +29,27 @@ function startTest(test, token) { isnot(element.videoTracks, undefined, 'HTMLMediaElement::VideoTracks() property should be available.'); - element.audioTracks.onaddtrack = function(e) { + element.audioTracks.onaddtrack = function() { audioOnaddtrack++; } - element.audioTracks.onremovetrack = function(e) { + element.audioTracks.onremovetrack = function() { audioOnremovetrack++; } - element.audioTracks.onchange = function(e) { + element.audioTracks.onchange = function() { audioOnchange++; } - element.videoTracks.onaddtrack = function(e) { + element.videoTracks.onaddtrack = function() { videoOnaddtrack++; } - element.videoTracks.onremovetrack = function(e) { + element.videoTracks.onremovetrack = function() { videoOnremovetrack++; } - element.videoTracks.onchange = function(e) { + element.videoTracks.onchange = function() { videoOnchange++; } diff --git a/dom/media/test/test_mediatrack_consuming_mediastream.html b/dom/media/test/test_mediatrack_consuming_mediastream.html index b930ca4fdc..79464ef28d 100644 --- a/dom/media/test/test_mediatrack_consuming_mediastream.html +++ b/dom/media/test/test_mediatrack_consuming_mediastream.html @@ -28,19 +28,19 @@ async function startTest() { return; } - element.audioTracks.onaddtrack = function(e) { + element.audioTracks.onaddtrack = function() { audioOnaddtrack++; }; - element.audioTracks.onchange = function(e) { + element.audioTracks.onchange = function() { audioOnchange++; }; - element.videoTracks.onaddtrack = function(e) { + element.videoTracks.onaddtrack = function() { videoOnaddtrack++; }; - element.videoTracks.onchange = function(e) { + element.videoTracks.onchange = function() { videoOnchange++; }; diff --git a/dom/media/test/test_mediatrack_replay_from_end.html b/dom/media/test/test_mediatrack_replay_from_end.html index 16b0cbeb97..143ae42668 100644 --- a/dom/media/test/test_mediatrack_replay_from_end.html +++ b/dom/media/test/test_mediatrack_replay_from_end.html @@ -34,19 +34,19 @@ function startTest(test, token) { let isPlaying = false; let steps = 0; - element.audioTracks.onaddtrack = function(e) { + element.audioTracks.onaddtrack = function() { audioOnaddtrack++; } - element.audioTracks.onremovetrack = function(e) { + element.audioTracks.onremovetrack = function() { audioOnremovetrack++; } - element.videoTracks.onaddtrack = function(e) { + element.videoTracks.onaddtrack = function() { videoOnaddtrack++; } - element.videoTracks.onremovetrack = function(e) { + element.videoTracks.onremovetrack = function() { videoOnremovetrack++; } diff --git a/dom/media/test/test_midflight_redirect_blocked.html b/dom/media/test/test_midflight_redirect_blocked.html index ea85673b45..24c5a96bd3 100644 --- a/dom/media/test/test_midflight_redirect_blocked.html +++ b/dom/media/test/test_midflight_redirect_blocked.html @@ -12,7 +12,7 @@ <script class="testbody" type='application/javascript'> function testIfLoadsToMetadata(test, useCors) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { var elemType = getMajorMimeType(test.type); var element = document.createElement(elemType); diff --git a/dom/media/test/test_mixed_principals.html b/dom/media/test/test_mixed_principals.html index c1deaef697..88523dabc7 100644 --- a/dom/media/test/test_mixed_principals.html +++ b/dom/media/test/test_mixed_principals.html @@ -46,7 +46,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=489415 } function runTest(origin, shouldReadBackOnLoad) { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { // Load will redirect mid-flight, which will be detected and should error, // and we should no longer be able to readback. var video = document.createElement("video"); diff --git a/dom/media/test/test_new_audio.html b/dom/media/test/test_new_audio.html index e1f8964f73..5e94bf9055 100644 --- a/dom/media/test/test_new_audio.html +++ b/dom/media/test/test_new_audio.html @@ -33,7 +33,7 @@ function startTest(test, token) { a.autoplay = true; document.body.appendChild(a); a.addEventListener("ended", - function(e){ + function(){ ok(true, "[" + a.src + "]We should get to the end. Oh look we did."); a.remove(); manager.finished(token); diff --git a/dom/media/test/test_periodic_timeupdate.html b/dom/media/test/test_periodic_timeupdate.html index 5d40c7e38a..9933e3d5dc 100644 --- a/dom/media/test/test_periodic_timeupdate.html +++ b/dom/media/test/test_periodic_timeupdate.html @@ -36,7 +36,7 @@ add_task(async function testPeriodicTimeupdateShouldOnlyBeDispatchedOnceWithin25 info(`seeking ends`); video._ignoreEvents = false; } - function checkTimeupdate(event) { + function checkTimeupdate() { // When reaching to the end, video would perform a seek to the start // position where one mandatory `timeupdate` would be dispatched. if (video._ignoreEvents) { diff --git a/dom/media/test/test_play_twice.html b/dom/media/test/test_play_twice.html index e94f28a031..ebe6e323b6 100644 --- a/dom/media/test/test_play_twice.html +++ b/dom/media/test/test_play_twice.html @@ -25,7 +25,7 @@ function startTest(test, token) { checkMetadata(t.name, v, test); }}(test, video); - var noLoad = function(t, v) { return function() { + var noLoad = function(t) { return function() { ok(false, t.name + " should not fire 'load' event"); }}(test, video); diff --git a/dom/media/test/test_playback.html b/dom/media/test/test_playback.html index 5e28861e93..2f16020059 100644 --- a/dom/media/test/test_playback.html +++ b/dom/media/test/test_playback.html @@ -35,7 +35,7 @@ function startTest(test, token) { checkMetadata(t.name, v, t); }}(test, video); - var noLoad = function(t, v) { return function() { + var noLoad = function(t) { return function() { ok(false, t.name + " should not fire 'load' event"); }}(test, video); diff --git a/dom/media/test/test_playback_errors.html b/dom/media/test/test_playback_errors.html index 7b3f046099..73d250b5aa 100644 --- a/dom/media/test/test_playback_errors.html +++ b/dom/media/test/test_playback_errors.html @@ -24,7 +24,7 @@ function startTest(test, token) { v.remove(); manager.finished(token); } - var checkError = function(t, v) { return function(evt) { + var checkError = function(t, v) { return function() { v._errorCount++; is(v._errorCount, 1, t.name + " only one error fired"); endedTest(v); diff --git a/dom/media/test/test_playback_hls.html b/dom/media/test/test_playback_hls.html index 7d5c777fc1..0d160e9264 100644 --- a/dom/media/test/test_playback_hls.html +++ b/dom/media/test/test_playback_hls.html @@ -34,7 +34,7 @@ function startTest(test, token) { checkMetadata(t.name, v, t); }}(test, video); - var noLoad = function(t, v) { return function() { + var noLoad = function(t) { return function() { ok(false, t.name + " should not fire 'load' event"); }}(test, video); diff --git a/dom/media/test/test_preload_actions.html b/dom/media/test/test_preload_actions.html index d027147c71..bc1e94d2a9 100644 --- a/dom/media/test/test_preload_actions.html +++ b/dom/media/test/test_preload_actions.html @@ -79,8 +79,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); v.src = test.name; document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none. @@ -105,8 +105,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "metadata"; - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("loadeddata", this.loadeddata); v.src = test.name; document.body.appendChild(v); // Causes implicit load, which will be halted after @@ -128,8 +128,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "auto"; - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("canplaythrough", this.canplaythrough); v.src = test.name; // Causes implicit load. document.body.appendChild(v); @@ -163,8 +163,8 @@ var tests = [ v._gotLoadedMetaData = false; v._gotSuspend = false; v.preload = "none"; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); v.addEventListener("ended", this.ended); v.src = test.name; @@ -190,8 +190,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource. v.src = test.name; // Load should start, and halt at preload:none. @@ -216,8 +216,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource. var s = document.createElement("source"); @@ -256,14 +256,14 @@ var tests = [ v._gotLoadedMetaData = false; v.preload = "none"; v._gotErrorEvent = false; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); v.addEventListener("ended", this.ended); var s1 = document.createElement("source"); s1.src = "not-a-real-file.404" s1.type = test.type; - s1.addEventListener("error", function(e){v._gotErrorEvent = true;}); + s1.addEventListener("error", function(){v._gotErrorEvent = true;}); v.appendChild(s1); var s2 = document.createElement("source"); s2.src = test.name; @@ -288,8 +288,8 @@ var tests = [ setup(v) { v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadstart", function(e){v.preload = "metadata";}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v.preload = "metadata";}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("loadeddata", this.loadeddata); v.src = test.name; // Causes implicit load. document.body.appendChild(v); @@ -333,8 +333,8 @@ var tests = [ setup(v) { v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadstart", function(e){v.preload = "auto";}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v.preload = "auto";}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("canplaythrough", this.canplaythrough); v.src = test.name; // Causes implicit load. document.body.appendChild(v); @@ -356,8 +356,8 @@ var tests = [ setup(v) { v._gotLoadedMetaData = false; v.preload = "none"; - v.addEventListener("loadstart", function(e){v.preload = "metadata";}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v.preload = "metadata";}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("loadeddata", this.loadeddata); v.src = test.name; // Causes implicit load. document.body.appendChild(v); @@ -384,8 +384,8 @@ var tests = [ v.preload = "auto"; v.src = test.name; v.preload = "none"; - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); v.addEventListener("suspend", this.suspend); document.body.appendChild(v); // Causes implicit load, should load according to preload none document.createElement("source"); @@ -415,8 +415,8 @@ var tests = [ v._gotLoadStart = false; v._gotLoadedMetaData = false; v.preload = "metadata"; - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("ended", this.ended); v.addEventListener("loadeddata", this.loadeddata); v.src = test.name; @@ -439,8 +439,8 @@ var tests = [ v._gotLoadedMetaData = false; v.preload = "none"; v.autoplay = true; - v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); - v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(){v._gotLoadedMetaData = true;}); v.addEventListener("ended", this.ended); v.src = test.name; // Causes implicit load. document.body.appendChild(v); diff --git a/dom/media/test/test_preload_suspend.html b/dom/media/test/test_preload_suspend.html index 7f1146360f..b715a58dc8 100644 --- a/dom/media/test/test_preload_suspend.html +++ b/dom/media/test/test_preload_suspend.html @@ -85,7 +85,7 @@ var tests = [ } ]; -function startTest(test, token) { +function startTest(test) { var v = document.createElement("video"); v.name = test.name; var key = Math.random(); diff --git a/dom/media/test/test_reset_events_async.html b/dom/media/test/test_reset_events_async.html index 482ec55986..98010cd475 100644 --- a/dom/media/test/test_reset_events_async.html +++ b/dom/media/test/test_reset_events_async.html @@ -23,10 +23,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=975270 is(a.networkState, HTMLMediaElement.NETWORK_EMPTY, "Shouldn't be loading"); - a.addEventListener("abort", function(e) { a._abort++; }); - a.addEventListener("emptied", function(e) { a._emptied++; }); + a.addEventListener("abort", function() { a._abort++; }); + a.addEventListener("emptied", function() { a._emptied++; }); a.addEventListener("loadedmetadata", - function(e) { + function() { is(a._abort, 0, "Should not have received 'abort' before 'loadedmetadata"); is(a._emptied, 0, "Should not have received 'emptied' before 'loadedmetadata"); diff --git a/dom/media/test/test_seek-10.html b/dom/media/test/test_seek-10.html index 6c02384ed4..63bb47f52a 100644 --- a/dom/media/test/test_seek-10.html +++ b/dom/media/test/test_seek-10.html @@ -31,7 +31,7 @@ function startTest() { v.currentTime = v.duration * 0.9; } -function done(evt) { +function done() { ok(true, "We don't acutally test anything..."); finish(); } diff --git a/dom/media/test/test_seekToNextFrame.html b/dom/media/test/test_seekToNextFrame.html index 755d06e622..fc00d91389 100644 --- a/dom/media/test/test_seekToNextFrame.html +++ b/dom/media/test/test_seekToNextFrame.html @@ -46,7 +46,7 @@ function startTest(test, token) { ); } - var onLoadedmetadata = function(t, v) { return function() { + var onLoadedmetadata = function() { return function() { callSeekToNextFrame(); }}(test, video); diff --git a/dom/media/test/test_temporary_file_blob_video_plays.html b/dom/media/test/test_temporary_file_blob_video_plays.html index 87f6b3c4e6..09ff377c43 100644 --- a/dom/media/test/test_temporary_file_blob_video_plays.html +++ b/dom/media/test/test_temporary_file_blob_video_plays.html @@ -51,7 +51,7 @@ function startTest() { video.id = "recorded-video"; video.src = URL.createObjectURL(blob); video.play(); - video.onerror = err => { + video.onerror = () => { ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); SimpleTest.finish(); }; diff --git a/dom/media/test/test_video_dimensions.html b/dom/media/test/test_video_dimensions.html index 4d9c2a185f..facd504ac5 100644 --- a/dom/media/test/test_video_dimensions.html +++ b/dom/media/test/test_video_dimensions.html @@ -65,7 +65,7 @@ var startTest = function(test, token) { manager.finished(token); } }; - var setupElement = function(v, id) { + var setupElement = function(v) { v.durationchange = false; v.ondurationchange = ondurationchange; v.resize = false; diff --git a/dom/media/test/test_video_low_power_telemetry.html b/dom/media/test/test_video_low_power_telemetry.html index be609f7ceb..25a5ce21a6 100644 --- a/dom/media/test/test_video_low_power_telemetry.html +++ b/dom/media/test/test_video_low_power_telemetry.html @@ -124,7 +124,7 @@ steps we run before each video. We keep track of which test we are running with } } - async function doPostTest(v) { + async function doPostTest() { info(`Test ${testIndex} attempting to retrieve telemetry.`); let snap = await retrieveSnapshotAndClearTelemetry(); ok(snap, `Test ${testIndex} should have telemetry.`); diff --git a/dom/media/utils/TelemetryProbesReporter.cpp b/dom/media/utils/TelemetryProbesReporter.cpp index a2854812ba..8c5614c048 100644 --- a/dom/media/utils/TelemetryProbesReporter.cpp +++ b/dom/media/utils/TelemetryProbesReporter.cpp @@ -292,6 +292,52 @@ void TelemetryProbesReporter::OnDecodeResumed() { mOwner->DispatchAsyncTestingEvent(u"mozvideodecodesuspendedpaused"_ns); } +void TelemetryProbesReporter::OntFirstFrameLoaded( + const TimeDuration& aLoadedFirstFrameTime, bool aIsMSE, + bool aIsExternalEngineStateMachine) { + const MediaInfo& info = mOwner->GetMediaInfo(); + MOZ_ASSERT(info.HasVideo()); + nsCString resolution; + DetermineResolutionForTelemetry(info, resolution); + + glean::media_playback::FirstFrameLoadedExtra extraData; + extraData.firstFrameLoadedTime = Some(aLoadedFirstFrameTime.ToMilliseconds()); + if (!aIsMSE && !aIsExternalEngineStateMachine) { + extraData.playbackType = Some("Non-MSE playback"_ns); + } else if (aIsMSE && !aIsExternalEngineStateMachine) { + extraData.playbackType = !mOwner->IsEncrypted() ? Some("MSE playback"_ns) + : Some("EME playback"_ns); + } else if (!aIsMSE && aIsExternalEngineStateMachine) { + extraData.playbackType = Some("Non-MSE media-engine playback"_ns); + } else if (aIsMSE && aIsExternalEngineStateMachine) { + extraData.playbackType = !mOwner->IsEncrypted() + ? Some("MSE media-engine playback"_ns) + : Some("EME media-engine playback"_ns); + } else { + extraData.playbackType = Some("ERROR TYPE"_ns); + MOZ_ASSERT(false, "Unexpected playback type!"); + } + extraData.videoCodec = Some(info.mVideo.mMimeType); + extraData.resolution = Some(resolution); + if (const auto keySystem = mOwner->GetKeySystem()) { + extraData.keySystem = Some(NS_ConvertUTF16toUTF8(*keySystem)); + } + + if (MOZ_LOG_TEST(gTelemetryProbesReporterLog, LogLevel::Debug)) { + nsPrintfCString logMessage{ + "Media_Playabck First_Frame_Loaded event, time(ms)=%f, " + "playback-type=%s, videoCodec=%s, resolution=%s", + aLoadedFirstFrameTime.ToMilliseconds(), extraData.playbackType->get(), + extraData.videoCodec->get(), extraData.resolution->get()}; + if (const auto keySystem = mOwner->GetKeySystem()) { + logMessage.Append(nsPrintfCString{ + ", keySystem=%s", NS_ConvertUTF16toUTF8(*keySystem).get()}); + } + LOG("%s", logMessage.get()); + } + glean::media_playback::first_frame_loaded.Record(Some(extraData)); +} + void TelemetryProbesReporter::OnShutdown() { AssertOnMainThreadAndNotShutdown(); LOG("Shutdown"); diff --git a/dom/media/utils/TelemetryProbesReporter.h b/dom/media/utils/TelemetryProbesReporter.h index 51ae2f12bb..be81e8022c 100644 --- a/dom/media/utils/TelemetryProbesReporter.h +++ b/dom/media/utils/TelemetryProbesReporter.h @@ -66,6 +66,8 @@ class TelemetryProbesReporter final { void OnMutedChanged(bool aMuted); void OnDecodeSuspended(); void OnDecodeResumed(); + void OntFirstFrameLoaded(const TimeDuration& aLoadedFirstFrameTime, + bool aIsMSE, bool aIsExternalEngineStateMachine); double GetTotalVideoPlayTimeInSeconds() const; double GetTotalVideoHDRPlayTimeInSeconds() const; diff --git a/dom/media/webaudio/FFTBlock.cpp b/dom/media/webaudio/FFTBlock.cpp index 6789ca0264..79fb934a00 100644 --- a/dom/media/webaudio/FFTBlock.cpp +++ b/dom/media/webaudio/FFTBlock.cpp @@ -30,15 +30,14 @@ #include "FFTBlock.h" +#include "FFVPXRuntimeLinker.h" #include <complex> namespace mozilla { -typedef std::complex<double> Complex; +FFmpegFFTFuncs FFTBlock::sFFTFuncs = {}; -#ifdef MOZ_LIBAV_FFT -FFmpegRDFTFuncs FFTBlock::sRDFTFuncs; -#endif +using Complex = std::complex<double>; static double fdlibm_cabs(const Complex& z) { return fdlibm_hypot(real(z), imag(z)); @@ -51,16 +50,15 @@ static double fdlibm_carg(const Complex& z) { FFTBlock* FFTBlock::CreateInterpolatedBlock(const FFTBlock& block0, const FFTBlock& block1, double interp) { - FFTBlock* newBlock = new FFTBlock(block0.FFTSize()); + uint32_t fftSize = block0.FFTSize(); + FFTBlock* newBlock = new FFTBlock(fftSize, 1.0f / AssertedCast<float>(fftSize)); newBlock->InterpolateFrequencyComponents(block0, block1, interp); // In the time-domain, the 2nd half of the response must be zero, to avoid // circular convolution aliasing... - int fftSize = newBlock->FFTSize(); AlignedTArray<float> buffer(fftSize); - newBlock->GetInverseWithoutScaling(buffer.Elements()); - AudioBufferInPlaceScale(buffer.Elements(), 1.0f / fftSize, fftSize / 2); + newBlock->GetInverse(buffer.Elements()); PodZero(buffer.Elements() + fftSize / 2, fftSize / 2); // Put back into frequency domain. diff --git a/dom/media/webaudio/FFTBlock.h b/dom/media/webaudio/FFTBlock.h index 6af882f9af..ecce39d7b1 100644 --- a/dom/media/webaudio/FFTBlock.h +++ b/dom/media/webaudio/FFTBlock.h @@ -7,31 +7,17 @@ #ifndef FFTBlock_h_ #define FFTBlock_h_ -#ifdef BUILD_ARM_NEON -# include <cmath> -# include "mozilla/arm.h" -# include "dl/sp/api/omxSP.h" -#endif - #include "AlignedTArray.h" #include "AudioNodeEngine.h" -#if defined(MOZ_LIBAV_FFT) -# include "FFmpegRDFTTypes.h" -# include "FFVPXRuntimeLinker.h" -#else -# include "kiss_fft/kiss_fftr.h" -#endif +#include "FFVPXRuntimeLinker.h" +#include "ffvpx/tx.h" namespace mozilla { // This class defines an FFT block, loosely modeled after Blink's FFTFrame // class to make sharing code with Blink easy. -// Currently it's implemented on top of KissFFT on all platforms. class FFTBlock final { union ComplexU { -#if !defined(MOZ_LIBAV_FFT) - kiss_fft_cpx c; -#endif float f[2]; struct { float r; @@ -41,28 +27,13 @@ class FFTBlock final { public: static void MainThreadInit() { -#ifdef MOZ_LIBAV_FFT FFVPXRuntimeLinker::Init(); - if (!sRDFTFuncs.init) { - FFVPXRuntimeLinker::GetRDFTFuncs(&sRDFTFuncs); + if (!sFFTFuncs.init) { + FFVPXRuntimeLinker::GetFFTFuncs(&sFFTFuncs); } -#endif } - - explicit FFTBlock(uint32_t aFFTSize) -#if defined(MOZ_LIBAV_FFT) - : mAvRDFT(nullptr), - mAvIRDFT(nullptr) -#else - : mKissFFT(nullptr), - mKissIFFT(nullptr) -# ifdef BUILD_ARM_NEON - , - mOmxFFT(nullptr), - mOmxIFFT(nullptr) -# endif -#endif - { + explicit FFTBlock(uint32_t aFFTSize, float aInverseScaling = 1.0f) + : mInverseScaling(aInverseScaling) { MOZ_COUNT_CTOR(FFTBlock); SetFFTSize(aFFTSize); } @@ -83,63 +54,33 @@ class FFTBlock final { return; } -#if defined(MOZ_LIBAV_FFT) - PodCopy(mOutputBuffer.Elements()->f, aData, mFFTSize); - sRDFTFuncs.calc(mAvRDFT, mOutputBuffer.Elements()->f); - // Recover packed Nyquist. - mOutputBuffer[mFFTSize / 2].r = mOutputBuffer[0].i; - mOutputBuffer[0].i = 0.0f; -#else -# ifdef BUILD_ARM_NEON - if (mozilla::supports_neon()) { - omxSP_FFTFwd_RToCCS_F32_Sfs(aData, mOutputBuffer.Elements()->f, mOmxFFT); - } else -# endif - { - kiss_fftr(mKissFFT, aData, &(mOutputBuffer.Elements()->c)); - } + mFn(mTxCtx, mOutputBuffer.Elements()->f, const_cast<float*>(aData), + 2 * sizeof(float)); +#ifdef DEBUG + mInversePerformed = false; #endif } - // Inverse-transform internal data and store the resulting FFTSize() - // points in aDataOut. - void GetInverse(float* aDataOut) { - GetInverseWithoutScaling(aDataOut); - AudioBufferInPlaceScale(aDataOut, 1.0f / mFFTSize, mFFTSize); - } - // Inverse-transform internal frequency data and store the resulting // FFTSize() points in |aDataOut|. If frequency data has not already been // scaled, then the output will need scaling by 1/FFTSize(). - void GetInverseWithoutScaling(float* aDataOut) { + void GetInverse(float* aDataOut) { if (!EnsureIFFT()) { std::fill_n(aDataOut, mFFTSize, 0.0f); return; }; - -#if defined(MOZ_LIBAV_FFT) - { - // Even though this function doesn't scale, the libav forward transform - // gives a value that needs scaling by 2 in order for things to turn out - // similar to how we expect from kissfft/openmax. - AudioBufferCopyWithScale(mOutputBuffer.Elements()->f, 2.0f, aDataOut, - mFFTSize); - aDataOut[1] = 2.0f * mOutputBuffer[mFFTSize / 2].r; // Packed Nyquist - sRDFTFuncs.calc(mAvIRDFT, aDataOut); - } -#else -# ifdef BUILD_ARM_NEON - if (mozilla::supports_neon()) { - omxSP_FFTInv_CCSToR_F32_Sfs_unscaled(mOutputBuffer.Elements()->f, - aDataOut, mOmxIFFT); - } else -# endif - { - kiss_fftri(mKissIFFT, &(mOutputBuffer.Elements()->c), aDataOut); - } + // When performing an inverse transform, tx overwrites the input. This + // asserts that forward / inverse transforms are interleaved to avoid having + // to keep the input around. + MOZ_ASSERT(!mInversePerformed); + mIFn(mITxCtx, aDataOut, mOutputBuffer.Elements()->f, 2 * sizeof(float)); +#ifdef DEBUG + mInversePerformed = true; #endif } void Multiply(const FFTBlock& aFrame) { + MOZ_ASSERT(!mInversePerformed); + uint32_t halfSize = mFFTSize / 2; // DFTs are not packed. MOZ_ASSERT(mOutputBuffer[0].i == 0); @@ -161,8 +102,8 @@ class FFTBlock final { MOZ_ASSERT(dataSize <= FFTSize()); AlignedTArray<float> paddedData; paddedData.SetLength(FFTSize()); - AudioBufferCopyWithScale(aData, 1.0f / FFTSize(), paddedData.Elements(), - dataSize); + AudioBufferCopyWithScale(aData, 1.0f / AssertedCast<float>(FFTSize()), + paddedData.Elements(), dataSize); PodZero(paddedData.Elements() + dataSize, mFFTSize - dataSize); PerformFFT(paddedData.Elements()); } @@ -180,46 +121,35 @@ class FFTBlock final { double ExtractAverageGroupDelay(); uint32_t FFTSize() const { return mFFTSize; } - float RealData(uint32_t aIndex) const { return mOutputBuffer[aIndex].r; } - float& RealData(uint32_t aIndex) { return mOutputBuffer[aIndex].r; } - float ImagData(uint32_t aIndex) const { return mOutputBuffer[aIndex].i; } - float& ImagData(uint32_t aIndex) { return mOutputBuffer[aIndex].i; } + float RealData(uint32_t aIndex) const { + MOZ_ASSERT(!mInversePerformed); + return mOutputBuffer[aIndex].r; + } + float& RealData(uint32_t aIndex) { + MOZ_ASSERT(!mInversePerformed); + return mOutputBuffer[aIndex].r; + } + float ImagData(uint32_t aIndex) const { + MOZ_ASSERT(!mInversePerformed); + return mOutputBuffer[aIndex].i; + } + float& ImagData(uint32_t aIndex) { + MOZ_ASSERT(!mInversePerformed); + return mOutputBuffer[aIndex].i; + } size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t amount = 0; -#if defined(MOZ_LIBAV_FFT) - auto ComputedSizeOfContextIfSet = [this](void* aContext) -> size_t { - if (!aContext) { - return 0; - } - // RDFTContext is only forward declared in public headers, but this is - // an estimate based on a value of 231 seen requested from - // _aligned_alloc on Win64. Don't use malloc_usable_size() because the - // context pointer is not necessarily from malloc. - size_t amount = 232; - // Add size of allocations performed in ff_fft_init(). - // The maximum FFT size used is 32768 = 2^15 and so revtab32 is not - // allocated. - MOZ_ASSERT(mFFTSize <= 32768); - amount += mFFTSize * (sizeof(uint16_t) + 2 * sizeof(float)); - - return amount; - }; + // malloc_usable_size can't be used here because the pointer isn't + // necessarily from malloc. This value has been manually checked. + if (mTxCtx) { + amount += 711; + } + if (mTxCtx) { + amount += 711; + } - amount += ComputedSizeOfContextIfSet(mAvRDFT); - amount += ComputedSizeOfContextIfSet(mAvIRDFT); -#else -# ifdef BUILD_ARM_NEON - amount += aMallocSizeOf(mOmxFFT); - amount += aMallocSizeOf(mOmxIFFT); -# endif -# ifdef USE_SIMD -# error kiss fft uses malloc only when USE_SIMD is not defined -# endif - amount += aMallocSizeOf(mKissFFT); - amount += aMallocSizeOf(mKissIFFT); -#endif amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf); return amount; } @@ -228,119 +158,67 @@ class FFTBlock final { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } - private: FFTBlock(const FFTBlock& other) = delete; void operator=(const FFTBlock& other) = delete; + private: bool EnsureFFT() { -#if defined(MOZ_LIBAV_FFT) - if (!mAvRDFT) { - if (!sRDFTFuncs.init) { + if (!mTxCtx) { + if (!sFFTFuncs.init) { return false; } - - mAvRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), DFT_R2C); - } -#else -# ifdef BUILD_ARM_NEON - if (mozilla::supports_neon()) { - if (!mOmxFFT) { - mOmxFFT = createOmxFFT(mFFTSize); - } - } else -# endif - { - if (!mKissFFT) { - mKissFFT = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr); - } + // Forward transform is always unscaled for our purpose. + float scale = 1.0f; + int rv = sFFTFuncs.init(&mTxCtx, &mFn, AV_TX_FLOAT_RDFT, 0 /* forward */, + AssertedCast<int>(mFFTSize), &scale, 0); + MOZ_ASSERT(!rv, "av_tx_init: invalid parameters (forward)"); + return !rv; } -#endif return true; } - bool EnsureIFFT() { -#if defined(MOZ_LIBAV_FFT) - if (!mAvIRDFT) { - if (!sRDFTFuncs.init) { + if (!mITxCtx) { + if (!sFFTFuncs.init) { return false; } - - mAvIRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), IDFT_C2R); - } -#else -# ifdef BUILD_ARM_NEON - if (mozilla::supports_neon()) { - if (!mOmxIFFT) { - mOmxIFFT = createOmxFFT(mFFTSize); - } - } else -# endif - { - if (!mKissIFFT) { - mKissIFFT = kiss_fftr_alloc(mFFTSize, 1, nullptr, nullptr); - } + int rv = + sFFTFuncs.init(&mITxCtx, &mIFn, AV_TX_FLOAT_RDFT, 1 /* inverse */, + AssertedCast<int>(mFFTSize), &mInverseScaling, 0); + MOZ_ASSERT(!rv, "av_tx_init: invalid parameters (inverse)"); + return !rv; } -#endif return true; } - -#ifdef BUILD_ARM_NEON - static OMXFFTSpec_R_F32* createOmxFFT(uint32_t aFFTSize) { - MOZ_ASSERT((aFFTSize & (aFFTSize - 1)) == 0); - OMX_INT bufSize; - OMX_INT order = FloorLog2(aFFTSize); - MOZ_ASSERT(aFFTSize >> order == 1); - OMXResult status = omxSP_FFTGetBufSize_R_F32(order, &bufSize); - if (status == OMX_Sts_NoErr) { - OMXFFTSpec_R_F32* context = - static_cast<OMXFFTSpec_R_F32*>(malloc(bufSize)); - if (omxSP_FFTInit_R_F32(context, order) != OMX_Sts_NoErr) { - return nullptr; - } - return context; - } - return nullptr; - } -#endif - void Clear() { -#if defined(MOZ_LIBAV_FFT) - if (mAvRDFT) { - sRDFTFuncs.end(mAvRDFT); - mAvRDFT = nullptr; + if (mTxCtx) { + sFFTFuncs.uninit(&mTxCtx); + mTxCtx = nullptr; + mFn = nullptr; } - if (mAvIRDFT) { - sRDFTFuncs.end(mAvIRDFT); - mAvIRDFT = nullptr; + if (mITxCtx) { + sFFTFuncs.uninit(&mITxCtx); + mITxCtx = nullptr; + mIFn = nullptr; } -#else -# ifdef BUILD_ARM_NEON - free(mOmxFFT); - free(mOmxIFFT); - mOmxFFT = mOmxIFFT = nullptr; -# endif - free(mKissFFT); - free(mKissIFFT); - mKissFFT = mKissIFFT = nullptr; -#endif } void AddConstantGroupDelay(double sampleFrameDelay); void InterpolateFrequencyComponents(const FFTBlock& block0, const FFTBlock& block1, double interp); -#if defined(MOZ_LIBAV_FFT) - static FFmpegRDFTFuncs sRDFTFuncs; - RDFTContext* mAvRDFT; - RDFTContext* mAvIRDFT; -#else - kiss_fftr_cfg mKissFFT; - kiss_fftr_cfg mKissIFFT; -# ifdef BUILD_ARM_NEON - OMXFFTSpec_R_F32* mOmxFFT; - OMXFFTSpec_R_F32* mOmxIFFT; -# endif -#endif + static FFmpegFFTFuncs sFFTFuncs; + // Context and function pointer for forward transform + AVTXContext* mTxCtx{}; + av_tx_fn mFn{}; + // Context and function pointer for inverse transform + AVTXContext* mITxCtx{}; + av_tx_fn mIFn{}; AlignedTArray<ComplexU> mOutputBuffer; - uint32_t mFFTSize; + uint32_t mFFTSize{}; + // A scaling that is performed when doing an inverse transform. The forward + // transform is always unscaled. + float mInverseScaling; +#ifdef DEBUG + bool mInversePerformed = false; +#endif }; } // namespace mozilla diff --git a/dom/media/webaudio/OscillatorNode.cpp b/dom/media/webaudio/OscillatorNode.cpp index 70592f6e96..65a928f126 100644 --- a/dom/media/webaudio/OscillatorNode.cpp +++ b/dom/media/webaudio/OscillatorNode.cpp @@ -329,10 +329,8 @@ class OscillatorNodeEngine final : public AudioNodeEngine { case OscillatorType::Custom: ComputeCustom(output, start, end, frequency, detune); break; - case OscillatorType::EndGuard_: - MOZ_ASSERT_UNREACHABLE("end guard"); - // Avoid `default:` so that `-Wswitch` catches missing enumerators - // at compile time. + // Avoid `default:` so that `-Wswitch` catches missing enumerators at + // compile time. }; } diff --git a/dom/media/webaudio/blink/FFTConvolver.cpp b/dom/media/webaudio/blink/FFTConvolver.cpp index 2ade9031ce..f9b456a0d4 100644 --- a/dom/media/webaudio/blink/FFTConvolver.cpp +++ b/dom/media/webaudio/blink/FFTConvolver.cpp @@ -85,7 +85,7 @@ const float* FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP) { // The input buffer is now filled (get frequency-domain version) m_frame.PerformFFT(m_inputBuffer.Elements()); m_frame.Multiply(*fftKernel); - m_frame.GetInverseWithoutScaling(m_outputBuffer.Elements()); + m_frame.GetInverse(m_outputBuffer.Elements()); // Overlap-add 1st half from previous time AudioBufferAddWithScale(m_lastOverlapBuffer.Elements(), 1.0f, diff --git a/dom/media/webaudio/blink/HRTFKernel.cpp b/dom/media/webaudio/blink/HRTFKernel.cpp index ecaa846a66..96a53609f2 100644 --- a/dom/media/webaudio/blink/HRTFKernel.cpp +++ b/dom/media/webaudio/blink/HRTFKernel.cpp @@ -38,7 +38,7 @@ static float extractAverageGroupDelay(float* impulseP, size_t length) { // Check for power-of-2. MOZ_ASSERT(length && (length & (length - 1)) == 0); - FFTBlock estimationFrame(length); + FFTBlock estimationFrame(length, 1.f / length); estimationFrame.PerformFFT(impulseP); float frameDelay = diff --git a/dom/media/webaudio/blink/PeriodicWave.cpp b/dom/media/webaudio/blink/PeriodicWave.cpp index 6b1d173008..4ed8829928 100644 --- a/dom/media/webaudio/blink/PeriodicWave.cpp +++ b/dom/media/webaudio/blink/PeriodicWave.cpp @@ -266,7 +266,7 @@ void PeriodicWave::createBandLimitedTables(float fundamentalFrequency, // Apply an inverse FFT to generate the time-domain table data. float* data = m_bandLimitedTables[rangeIndex]->Elements(); - frame.GetInverseWithoutScaling(data); + frame.GetInverse(data); // For the first range (which has the highest power), calculate // its peak value then compute normalization scale. diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build index 2e82d3daa8..3ee8c0aa76 100644 --- a/dom/media/webaudio/moz.build +++ b/dom/media/webaudio/moz.build @@ -130,8 +130,6 @@ if CONFIG["TARGET_CPU"] == "aarch64" or CONFIG["BUILD_ARM_NEON"]: LOCAL_INCLUDES += ["/third_party/xsimd/include"] SOURCES += ["AudioNodeEngineNEON.cpp"] SOURCES["AudioNodeEngineNEON.cpp"].flags += CONFIG["NEON_FLAGS"] - if CONFIG["BUILD_ARM_NEON"]: - LOCAL_INCLUDES += ["/media/openmax_dl/dl/api/"] # Are we targeting x86 or x64? If so, build SSEX files. if CONFIG["INTEL_ARCHITECTURE"]: diff --git a/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js index 2a5a4bff89..439dbec0c4 100644 --- a/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js +++ b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js @@ -1,3 +1,3 @@ -onmessage = function (event) { +onmessage = function () { postMessage("Pong"); }; diff --git a/dom/media/webaudio/test/test_OfflineAudioContext.html b/dom/media/webaudio/test/test_OfflineAudioContext.html index d9403566ae..6d8a907542 100644 --- a/dom/media/webaudio/test/test_OfflineAudioContext.html +++ b/dom/media/webaudio/test/test_OfflineAudioContext.html @@ -92,7 +92,7 @@ addLoadEvent(function() { ctx.startRendering().then(function() { ok(false, "Promise should not resolve when startRendering is called a second time on an OfflineAudioContext") finish(); - }).catch(function(err) { + }).catch(function() { ok(true, "Promise should reject when startRendering is called a second time on an OfflineAudioContext") finish(); }); @@ -106,7 +106,7 @@ addLoadEvent(function() { ctx.startRendering().then(function(b) { is(renderedBuffer, null, "The promise callback should be called first."); setOrCompareRenderedBuffer(b); - }).catch(function (error) { + }).catch(function () { ok(false, "The promise from OfflineAudioContext.startRendering should never be rejected"); }); }); diff --git a/dom/media/webaudio/test/test_WebAudioMemoryReporting.html b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html index 693e558304..027e3bcc56 100644 --- a/dom/media/webaudio/test/test_WebAudioMemoryReporting.html +++ b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html @@ -29,7 +29,7 @@ for (var i = 0; i < nodeTypes.length; ++i) { } } -var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) { +var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount) { if (aPath in usages) { usages[aPath] += aAmount; } diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html index 0411b74ce5..e8549af37d 100644 --- a/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html @@ -34,7 +34,7 @@ addLoadEvent(function() { var source = context.createBufferSource(); - source.onended = function(e) { + source.onended = function() { // The timing at which the audioprocess and ended listeners are called can // change, hence the fuzzy equal here. var errorRatio = samplesFromSource / (0.5 * context.sampleRate); diff --git a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html index 36cf8f720c..62bdd3fd54 100644 --- a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html +++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html @@ -72,10 +72,10 @@ function tryLegalOpeerationsOnClosedContext(ctx) { }); }); loadFile("ting-44.1k-1ch.ogg", function(buf) { - ctx.decodeAudioData(buf).then(function(decodedBuf) { + ctx.decodeAudioData(buf).then(function() { ok(true, "decodeAudioData on a closed context should work, it did.") finish(); - }).catch(function(e){ + }).catch(function(){ ok(false, "decodeAudioData on a closed context should work, it did not"); finish(); }); @@ -103,7 +103,7 @@ function testMultiContextOutput() { silentBuffersInARow = 0; - sp2.onaudioprocess = function(e) { + sp2.onaudioprocess = function() { ac1.suspend().then(function() { is(ac1.state, "suspended", "ac1 is suspended"); sp2.onaudioprocess = checkSilence; @@ -350,7 +350,7 @@ function testOfflineAudioContext() { o.onstatechange = beforeStartRendering; - o.startRendering().then(function(buffer) { + o.startRendering().then(function() { finishedRendering = true; }); } diff --git a/dom/media/webaudio/test/test_bug1056032.html b/dom/media/webaudio/test/test_bug1056032.html index ba38267e19..77427ee5f0 100644 --- a/dom/media/webaudio/test/test_bug1056032.html +++ b/dom/media/webaudio/test/test_bug1056032.html @@ -19,7 +19,7 @@ addLoadEvent(function() { xhr.responseType = "arraybuffer"; xhr.onload = function() { var context = new AudioContext(); - context.decodeAudioData(xhr.response, function(b) { + context.decodeAudioData(xhr.response, function() { ok(true, "We can decode an mp3 using decodeAudioData"); SimpleTest.finish(); }, function() { diff --git a/dom/media/webaudio/test/test_bug867174.html b/dom/media/webaudio/test/test_bug867174.html index e949bcec41..dd66c77303 100644 --- a/dom/media/webaudio/test/test_bug867174.html +++ b/dom/media/webaudio/test/test_bug867174.html @@ -21,7 +21,7 @@ addLoadEvent(function() { sp.connect(ctx.destination); source.start(0); - sp.onaudioprocess = function(e) { + sp.onaudioprocess = function() { // Now set the buffer source.buffer = buffer; diff --git a/dom/media/webaudio/test/test_convolverNodeNormalization.html b/dom/media/webaudio/test/test_convolverNodeNormalization.html index 24cb7d1670..b55dbba0c2 100644 --- a/dom/media/webaudio/test/test_convolverNodeNormalization.html +++ b/dom/media/webaudio/test/test_convolverNodeNormalization.html @@ -13,7 +13,7 @@ const LENGTH = 12800; // tolerate 16-bit math. const EPSILON = 1.0 / Math.pow(2, 15); -function test_normalization_via_response_concat(delayIndex) +function test_normalization_via_response_concat() { var context = new OfflineAudioContext(1, LENGTH, sampleRate); diff --git a/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html b/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html index e7c6d2db0c..cd86c0a1d3 100644 --- a/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html +++ b/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html @@ -19,7 +19,7 @@ var testDecodeAudioDataOnDetachedBuffer = function(buffer) { is(buffer.byteLength, 0, "Buffer should be detached"); // call decodeAudioData on detached buffer - context.decodeAudioData(buffer).then(function(b) { + context.decodeAudioData(buffer).then(function() { ok(false, "We should not be able to decode the detached buffer but we do"); SimpleTest.finish(); }, function(r) { diff --git a/dom/media/webaudio/test/test_decodeAudioDataPromise.html b/dom/media/webaudio/test/test_decodeAudioDataPromise.html index 139a686db1..a686c275cd 100644 --- a/dom/media/webaudio/test/test_decodeAudioDataPromise.html +++ b/dom/media/webaudio/test/test_decodeAudioDataPromise.html @@ -27,7 +27,7 @@ var ac = new AudioContext(); expectNoException(function() { var p = ac.decodeAudioData(" "); ok(p instanceof Promise, "AudioContext.decodeAudioData should return a Promise"); - p.then(function(data) { + p.then(function() { ok(false, "Promise should not resolve with an invalid source buffer."); finish(); }).catch(function(e) { diff --git a/dom/media/webaudio/test/test_decodeAudioError.html b/dom/media/webaudio/test/test_decodeAudioError.html index f18b971ac4..b6dd6ff74b 100644 --- a/dom/media/webaudio/test/test_decodeAudioError.html +++ b/dom/media/webaudio/test/test_decodeAudioError.html @@ -34,11 +34,11 @@ function errorExpectedWithFile(file, errorMsg) { xhr.open("GET", file, true); xhr.responseType = "arraybuffer"; xhr.onload = function() { - ctx.decodeAudioData(xhr.response, buffer => { + ctx.decodeAudioData(xhr.response, () => { ok(false, "You should not be able to decode that"); finish(); }, e => test(e)) - .then(buffer => { + .then(() => { ok(false, "You should not be able to decode that"); finish(); }) diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNode.html b/dom/media/webaudio/test/test_dynamicsCompressorNode.html index 05b6887a53..8f197aa32f 100644 --- a/dom/media/webaudio/test/test_dynamicsCompressorNode.html +++ b/dom/media/webaudio/test/test_dynamicsCompressorNode.html @@ -48,7 +48,7 @@ addLoadEvent(function() { osc.start(); var iteration = 0; - sp.onaudioprocess = function(e) { + sp.onaudioprocess = function() { if (iteration > 10) { ok(compressor.reduction < 0, "Feeding a full-scale sine to a compressor should result in an db" + diff --git a/dom/media/webaudio/test/test_event_listener_leaks.html b/dom/media/webaudio/test/test_event_listener_leaks.html index a3bcc9259e..f76e1d3e55 100644 --- a/dom/media/webaudio/test/test_event_listener_leaks.html +++ b/dom/media/webaudio/test/test_event_listener_leaks.html @@ -18,7 +18,7 @@ // exercise the leak condition. async function useAudioContext(contentWindow) { let ctx = new contentWindow.AudioContext(); - ctx.onstatechange = e => { + ctx.onstatechange = () => { contentWindow.stateChangeCount += 1; }; diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html index 7920af9f7b..84d41df50e 100644 --- a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html @@ -50,7 +50,7 @@ function waitForAudio(analysisFunction, cancelPromise) { }); } -async function test(sourceNode) { +async function test() { try { await analyser.connect(context.destination); diff --git a/dom/media/webaudio/test/test_pannerNodeTail.html b/dom/media/webaudio/test/test_pannerNodeTail.html index 7035780bf2..bb60fe05da 100644 --- a/dom/media/webaudio/test/test_pannerNodeTail.html +++ b/dom/media/webaudio/test/test_pannerNodeTail.html @@ -153,7 +153,7 @@ function startTest() { } source.buffer = buffer; source.start(0); - source.onended = function(e) { + source.onended = function() { gotEnded = true; }; diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html index fb45895380..0de8818d82 100644 --- a/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html +++ b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html @@ -15,7 +15,7 @@ addLoadEvent(function() { var context = new AudioContext(); var sp = context.createScriptProcessor(2048, 2, 2); - sp.onaudioprocess = function(e) { + sp.onaudioprocess = function() { ok(false, "Should not call onaudioprocess if the node is not connected."); sp.onaudioprocess = null; SimpleTest.finish(); diff --git a/dom/media/webaudio/test/webaudio.js b/dom/media/webaudio/test/webaudio.js index 049e0e5af3..100c71f320 100644 --- a/dom/media/webaudio/test/webaudio.js +++ b/dom/media/webaudio/test/webaudio.js @@ -42,7 +42,7 @@ function expectRejectedPromise(that, func, exceptionName) { ok(promise instanceof Promise, "Expect a Promise"); promise - .then(function (res) { + .then(function () { ok(false, "Promise resolved when it should have been rejected."); }) .catch(function (err) { diff --git a/dom/media/webcodecs/AudioData.cpp b/dom/media/webcodecs/AudioData.cpp new file mode 100644 index 0000000000..0b21798be8 --- /dev/null +++ b/dom/media/webcodecs/AudioData.cpp @@ -0,0 +1,731 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/AudioData.h" +#include "mozilla/dom/AudioDataBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "nsStringFwd.h" + +#include <utility> + +#include "AudioSampleFormat.h" +#include "WebCodecsUtils.h" +#include "js/StructuredClone.h" +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" + +extern mozilla::LazyLogModule gWebCodecsLog; + +namespace mozilla::dom { + +#ifdef LOG_INTERNAL +# undef LOG_INTERNAL +#endif // LOG_INTERNAL +#define LOG_INTERNAL(level, msg, ...) \ + MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__)) + +#ifdef LOGD +# undef LOGD +#endif // LOGD +#define LOGD(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__) + +#ifdef LOGE +# undef LOGE +#endif // LOGE +#define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__) + +// Only needed for refcounted objects. +// +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AudioData) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioData) + tmp->CloseIfNeeded(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioData) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioData) +// AudioData should be released as soon as its refcount drops to zero, +// without waiting for async deletion by the cycle collector, since it may hold +// a large-size PCM buffer. +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(AudioData, CloseIfNeeded()) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioData) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* + * W3C Webcodecs AudioData implementation + */ + +AudioData::AudioData(nsIGlobalObject* aParent, + const AudioDataSerializedData& aData) + : mParent(aParent), + mTimestamp(aData.mTimestamp), + mNumberOfChannels(aData.mNumberOfChannels), + mNumberOfFrames(aData.mNumberOfFrames), + mSampleRate(aData.mSampleRate), + mAudioSampleFormat(aData.mAudioSampleFormat), + // The resource is not copied, but referenced + mResource(aData.mResource) { + MOZ_ASSERT(mParent); + MOZ_ASSERT(mResource, + "Resource should always be present then receiving a transfer."); +} + +AudioData::AudioData(const AudioData& aOther) + : mParent(aOther.mParent), + mTimestamp(aOther.mTimestamp), + mNumberOfChannels(aOther.mNumberOfChannels), + mNumberOfFrames(aOther.mNumberOfFrames), + mSampleRate(aOther.mSampleRate), + mAudioSampleFormat(aOther.mAudioSampleFormat), + // The resource is not copied, but referenced + mResource(aOther.mResource) { + MOZ_ASSERT(mParent); +} + +Result<already_AddRefed<AudioDataResource>, nsresult> +AudioDataResource::Construct( + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aInit) { + FallibleTArray<uint8_t> copied; + uint8_t* rv = ProcessTypedArraysFixed( + aInit, [&](const Span<uint8_t>& aData) -> uint8_t* { + return copied.AppendElements(aData.Elements(), aData.Length(), + fallible); + }); + if (!rv) { + LOGE("AudioDataResource::Ctor: OOM"); + return Err(NS_ERROR_OUT_OF_MEMORY); + } + return MakeAndAddRef<AudioDataResource>(std::move(copied)); +} + +AudioData::AudioData( + nsIGlobalObject* aParent, + already_AddRefed<mozilla::dom::AudioDataResource> aResource, + const AudioDataInit& aInit) + : mParent(aParent), + mTimestamp(aInit.mTimestamp), + mNumberOfChannels(aInit.mNumberOfChannels), + mNumberOfFrames(aInit.mNumberOfFrames), + mSampleRate(aInit.mSampleRate), + mAudioSampleFormat(Some(aInit.mFormat)), + mResource(std::move(aResource)) { + MOZ_ASSERT(mParent); +} + +AudioData::AudioData( + nsIGlobalObject* aParent, + already_AddRefed<mozilla::dom::AudioDataResource> aResource, + int64_t aTimestamp, uint32_t aNumberOfChannels, uint32_t aNumberOfFrames, + float aSampleRate, AudioSampleFormat aAudioSampleFormat) + : mParent(aParent), + mTimestamp(aTimestamp), + mNumberOfChannels(aNumberOfChannels), + mNumberOfFrames(aNumberOfFrames), + mSampleRate(aSampleRate), + mAudioSampleFormat(Some(aAudioSampleFormat)), + mResource(aResource) { + MOZ_ASSERT(mParent); +} + +nsIGlobalObject* AudioData::GetParentObject() const { + AssertIsOnOwningThread(); + + return mParent.get(); +} + +JSObject* AudioData::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + AssertIsOnOwningThread(); + + return AudioData_Binding::Wrap(aCx, this, aGivenProto); +} + +uint32_t BytesPerSamples(const mozilla::dom::AudioSampleFormat& aFormat) { + switch (aFormat) { + case AudioSampleFormat::U8: + case AudioSampleFormat::U8_planar: + return sizeof(uint8_t); + case AudioSampleFormat::S16: + case AudioSampleFormat::S16_planar: + return sizeof(int16_t); + case AudioSampleFormat::S32: + case AudioSampleFormat::F32: + case AudioSampleFormat::S32_planar: + case AudioSampleFormat::F32_planar: + return sizeof(float); + default: + MOZ_ASSERT_UNREACHABLE("wrong enum value"); + } + return 0; +} + +Result<Ok, nsCString> IsValidAudioDataInit(const AudioDataInit& aInit) { + if (aInit.mSampleRate <= 0.0) { + auto msg = nsLiteralCString("sampleRate must be positive"); + LOGD("%s", msg.get()); + return Err(msg); + } + if (aInit.mNumberOfFrames == 0) { + auto msg = nsLiteralCString("mNumberOfFrames must be positive"); + LOGD("%s", msg.get()); + return Err(msg); + } + if (aInit.mNumberOfChannels == 0) { + auto msg = nsLiteralCString("mNumberOfChannels must be positive"); + LOGD("%s", msg.get()); + return Err(msg); + } + + uint64_t totalSamples = aInit.mNumberOfFrames * aInit.mNumberOfChannels; + uint32_t bytesPerSamples = BytesPerSamples(aInit.mFormat); + uint64_t totalSize = totalSamples * bytesPerSamples; + uint64_t arraySizeBytes = ProcessTypedArraysFixed( + aInit.mData, [&](const Span<uint8_t>& aData) -> uint64_t { + return aData.LengthBytes(); + }); + if (arraySizeBytes < totalSize) { + auto msg = + nsPrintfCString("Array of size %" PRIu64 + " not big enough, should be at least %" PRIu64 " bytes", + arraySizeBytes, totalSize); + LOGD("%s", msg.get()); + return Err(msg); + } + return Ok(); +} + +const char* FormatToString(AudioSampleFormat aFormat) { + switch (aFormat) { + case AudioSampleFormat::U8: + return "u8"; + case AudioSampleFormat::S16: + return "s16"; + case AudioSampleFormat::S32: + return "s32"; + case AudioSampleFormat::F32: + return "f32"; + case AudioSampleFormat::U8_planar: + return "u8-planar"; + case AudioSampleFormat::S16_planar: + return "s16-planar"; + case AudioSampleFormat::S32_planar: + return "s32-planar"; + case AudioSampleFormat::F32_planar: + return "f32-planar"; + default: + MOZ_ASSERT_UNREACHABLE("wrong enum value"); + } + return "unsupported"; +} + +/* static */ +already_AddRefed<AudioData> AudioData::Constructor(const GlobalObject& aGlobal, + const AudioDataInit& aInit, + ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + LOGD("[%p] AudioData(fmt: %s, rate: %f, ch: %" PRIu32 ", ts: %" PRId64 ")", + global.get(), FormatToString(aInit.mFormat), aInit.mSampleRate, + aInit.mNumberOfChannels, aInit.mTimestamp); + if (!global) { + LOGE("Global unavailable"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + nsString errorMessage; + auto rv = IsValidAudioDataInit(aInit); + if (rv.isErr()) { + LOGD("AudioData::Constructor failure (IsValidAudioDataInit)"); + aRv.ThrowTypeError(rv.inspectErr()); + return nullptr; + } + auto resource = AudioDataResource::Construct(aInit.mData); + if (resource.isErr()) { + LOGD("AudioData::Constructor failure (OOM)"); + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + return MakeAndAddRef<mozilla::dom::AudioData>(global, resource.unwrap(), + aInit); +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-format +Nullable<mozilla::dom::AudioSampleFormat> AudioData::GetFormat() const { + AssertIsOnOwningThread(); + return MaybeToNullable(mAudioSampleFormat); +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-samplerate +float AudioData::SampleRate() const { + AssertIsOnOwningThread(); + return mSampleRate; +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-numberofframes +uint32_t AudioData::NumberOfFrames() const { + AssertIsOnOwningThread(); + return mNumberOfFrames; +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-numberofchannels +uint32_t AudioData::NumberOfChannels() const { + AssertIsOnOwningThread(); + return mNumberOfChannels; +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-duration +uint64_t AudioData::Duration() const { + AssertIsOnOwningThread(); + // The spec isn't clear in which direction to convert to integer. + // https://github.com/w3c/webcodecs/issues/726 + return static_cast<uint64_t>( + static_cast<float>(USECS_PER_S * mNumberOfFrames) / mSampleRate); +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-timestamp +int64_t AudioData::Timestamp() const { + AssertIsOnOwningThread(); + return mTimestamp; +} + +struct CopyToSpec { + CopyToSpec(uint32_t aFrameCount, uint32_t aFrameOffset, uint32_t mPlaneIndex, + AudioSampleFormat aFormat) + : mFrameCount(aFrameCount), + mFrameOffset(aFrameOffset), + mPlaneIndex(mPlaneIndex), + mFormat(aFormat) {} + + const uint32_t mFrameCount; + const uint32_t mFrameOffset; + const uint32_t mPlaneIndex; + const AudioSampleFormat mFormat; +}; + +bool IsInterleaved(const AudioSampleFormat& aFormat) { + switch (aFormat) { + case AudioSampleFormat::U8: + case AudioSampleFormat::S16: + case AudioSampleFormat::S32: + case AudioSampleFormat::F32: + return true; + case AudioSampleFormat::U8_planar: + case AudioSampleFormat::S16_planar: + case AudioSampleFormat::S32_planar: + case AudioSampleFormat::F32_planar: + return false; + }; + MOZ_ASSERT_UNREACHABLE("Invalid enum value"); + return false; +} + +size_t AudioData::ComputeCopyElementCount( + const AudioDataCopyToOptions& aOptions, ErrorResult& aRv) { + // https://w3c.github.io/webcodecs/#compute-copy-element-count + // 1, 2 + auto destFormat = mAudioSampleFormat; + if (aOptions.mFormat.WasPassed()) { + destFormat = OptionalToMaybe(aOptions.mFormat); + } + // 3, 4 + MOZ_ASSERT(destFormat.isSome()); + if (IsInterleaved(destFormat.value())) { + if (aOptions.mPlaneIndex > 0) { + auto msg = "Interleaved format, but plane index > 0"_ns; + LOGD("%s", msg.get()); + aRv.ThrowRangeError(msg); + return 0; + } + } else { + if (aOptions.mPlaneIndex >= mNumberOfChannels) { + auto msg = nsPrintfCString( + "Plane index %" PRIu32 + " greater or equal than the number of channels %" PRIu32, + aOptions.mPlaneIndex, mNumberOfChannels); + LOGD("%s", msg.get()); + aRv.ThrowRangeError(msg); + return 0; + } + } + // 5 -- conversion between all formats supported + // 6 -- all planes have the same number of frames, always + uint64_t frameCount = mNumberOfFrames; + // 7 + if (aOptions.mFrameOffset >= frameCount) { + auto msg = nsPrintfCString("Frame offset of %" PRIu32 + " greater or equal than frame count %" PRIu64, + aOptions.mFrameOffset, frameCount); + LOGD("%s", msg.get()); + aRv.ThrowRangeError(msg); + return 0; + } + // 8, 9 + uint64_t copyFrameCount = frameCount - aOptions.mFrameOffset; + if (aOptions.mFrameCount.WasPassed()) { + if (aOptions.mFrameCount.Value() > copyFrameCount) { + auto msg = nsPrintfCString( + "Passed copy frame count of %" PRIu32 + " greater than available source frames for copy of %" PRIu64, + aOptions.mFrameCount.Value(), copyFrameCount); + LOGD("%s", msg.get()); + aRv.ThrowRangeError(msg); + return 0; + } + copyFrameCount = aOptions.mFrameCount.Value(); + } + // 10, 11 + uint64_t elementCount = copyFrameCount; + if (IsInterleaved(destFormat.value())) { + elementCount *= mNumberOfChannels; + } + return elementCount; +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-allocationsize +// This method returns an int, that can be zero in case of success or error. +// Caller should check aRv to determine success or error. +uint32_t AudioData::AllocationSize(const AudioDataCopyToOptions& aOptions, + ErrorResult& aRv) { + AssertIsOnOwningThread(); + if (!mResource) { + auto msg = "allocationSize called on detached AudioData"_ns; + LOGD("%s", msg.get()); + aRv.ThrowInvalidStateError(msg); + return 0; + } + size_t copyElementCount = ComputeCopyElementCount(aOptions, aRv); + if (aRv.Failed()) { + LOGD("AudioData::AllocationSize failure"); + // ComputeCopyElementCount has set the exception type. + return 0; + } + Maybe<mozilla::dom::AudioSampleFormat> destFormat = mAudioSampleFormat; + if (aOptions.mFormat.WasPassed()) { + destFormat = OptionalToMaybe(aOptions.mFormat); + } + if (destFormat.isNothing()) { + auto msg = "AudioData has an unknown format"_ns; + LOGD("%s", msg.get()); + // See https://github.com/w3c/webcodecs/issues/727 -- it isn't clear yet + // what to do here + aRv.ThrowRangeError(msg); + return 0; + } + CheckedInt<size_t> bytesPerSample = BytesPerSamples(destFormat.ref()); + + auto res = bytesPerSample * copyElementCount; + if (res.isValid()) { + return res.value(); + } + aRv.ThrowRangeError("Allocation size too large"); + return 0; +} + +template <typename S, typename D> +void CopySamples(Span<S> aSource, Span<D> aDest, uint32_t aSourceChannelCount, + const AudioSampleFormat aSourceFormat, + const CopyToSpec& aCopyToSpec) { + if (IsInterleaved(aSourceFormat) && IsInterleaved(aCopyToSpec.mFormat)) { + MOZ_ASSERT(aCopyToSpec.mPlaneIndex == 0); + MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount); + MOZ_ASSERT(aSource.Length() - aCopyToSpec.mFrameOffset >= + aCopyToSpec.mFrameCount); + // This turns into a regular memcpy if the types are in fact equal + ConvertAudioSamples(aSource.data() + aCopyToSpec.mFrameOffset, aDest.data(), + aCopyToSpec.mFrameCount * aSourceChannelCount); + return; + } + if (IsInterleaved(aSourceFormat) && !IsInterleaved(aCopyToSpec.mFormat)) { + DebugOnly<size_t> sourceFrameCount = aSource.Length() / aSourceChannelCount; + MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount); + MOZ_ASSERT(aSource.Length() - aCopyToSpec.mFrameOffset >= + aCopyToSpec.mFrameCount); + // Interleaved to planar -- only copy samples of the correct channel to the + // destination + size_t readIndex = aCopyToSpec.mFrameOffset * aSourceChannelCount + + aCopyToSpec.mPlaneIndex; + for (size_t i = 0; i < aCopyToSpec.mFrameCount; i++) { + aDest[i] = ConvertAudioSample<D>(aSource[readIndex]); + readIndex += aSourceChannelCount; + } + return; + } + + if (!IsInterleaved(aSourceFormat) && IsInterleaved(aCopyToSpec.mFormat)) { + MOZ_CRASH("This should never be hit -- current spec doesn't support it"); + // Planar to interleaved -- copy of all channels of the source into the + // destination buffer. + MOZ_ASSERT(aCopyToSpec.mPlaneIndex == 0); + MOZ_ASSERT(aDest.Length() >= aCopyToSpec.mFrameCount * aSourceChannelCount); + MOZ_ASSERT(aSource.Length() - + aCopyToSpec.mFrameOffset * aSourceChannelCount >= + aCopyToSpec.mFrameCount * aSourceChannelCount); + size_t writeIndex = 0; + // Scan the source linearly and put each sample at the right position in the + // destination interleaved buffer. + size_t readIndex = 0; + for (size_t channel = 0; channel < aSourceChannelCount; channel++) { + writeIndex = channel; + for (size_t i = 0; i < aCopyToSpec.mFrameCount; i++) { + aDest[writeIndex] = ConvertAudioSample<D>(aSource[readIndex]); + readIndex++; + writeIndex += aSourceChannelCount; + } + } + return; + } + if (!IsInterleaved(aSourceFormat) && !IsInterleaved(aCopyToSpec.mFormat)) { + // Planar to Planar / convert + copy from the right index in the source. + size_t offset = + aCopyToSpec.mPlaneIndex * aSource.Length() / aSourceChannelCount; + MOZ_ASSERT(aDest.Length() >= aSource.Length() / aSourceChannelCount - + aCopyToSpec.mFrameOffset); + for (uint32_t i = 0; i < aCopyToSpec.mFrameCount; i++) { + aDest[i] = + ConvertAudioSample<D>(aSource[offset + aCopyToSpec.mFrameOffset + i]); + } + } +} + +nsCString AudioData::ToString() const { + if (!mResource) { + return nsCString("AudioData[detached]"); + } + return nsPrintfCString("AudioData[%zu bytes %s %fHz %" PRIu32 "x%" PRIu32 + "ch]", + mResource->Data().LengthBytes(), + FormatToString(mAudioSampleFormat.value()), + mSampleRate, mNumberOfFrames, mNumberOfChannels); +} + +nsCString CopyToToString(size_t aDestBufSize, + const AudioDataCopyToOptions& aOptions) { + return nsPrintfCString( + "AudioDataCopyToOptions[data: %zu bytes %s frame count:%" PRIu32 + " frame offset: %" PRIu32 " plane: %" PRIu32 "]", + aDestBufSize, + aOptions.mFormat.WasPassed() ? FormatToString(aOptions.mFormat.Value()) + : "null", + aOptions.mFrameCount.WasPassed() ? aOptions.mFrameCount.Value() : 0, + aOptions.mFrameOffset, aOptions.mPlaneIndex); +} + +using DataSpanType = + Variant<Span<uint8_t>, Span<int16_t>, Span<int32_t>, Span<float>>; + +DataSpanType GetDataSpan(Span<uint8_t> aSpan, const AudioSampleFormat aFormat) { + const size_t Length = aSpan.Length() / BytesPerSamples(aFormat); + // TODO: Check size so Span can be reasonably constructed? + switch (aFormat) { + case AudioSampleFormat::U8: + case AudioSampleFormat::U8_planar: + return AsVariant(aSpan); + case AudioSampleFormat::S16: + case AudioSampleFormat::S16_planar: + return AsVariant(Span(reinterpret_cast<int16_t*>(aSpan.data()), Length)); + case AudioSampleFormat::S32: + case AudioSampleFormat::S32_planar: + return AsVariant(Span(reinterpret_cast<int32_t*>(aSpan.data()), Length)); + case AudioSampleFormat::F32: + case AudioSampleFormat::F32_planar: + return AsVariant(Span(reinterpret_cast<float*>(aSpan.data()), Length)); + } + MOZ_ASSERT_UNREACHABLE("Invalid enum value"); + return AsVariant(aSpan); +} + +void CopySamples(DataSpanType& aSource, DataSpanType& aDest, + uint32_t aSourceChannelCount, + const AudioSampleFormat aSourceFormat, + const CopyToSpec& aCopyToSpec) { + aSource.match([&](auto& src) { + aDest.match([&](auto& dst) { + CopySamples(src, dst, aSourceChannelCount, aSourceFormat, aCopyToSpec); + }); + }); +} + +void DoCopy(Span<uint8_t> aSource, Span<uint8_t> aDest, + const uint32_t aSourceChannelCount, + const AudioSampleFormat aSourceFormat, + const CopyToSpec& aCopyToSpec) { + DataSpanType source = GetDataSpan(aSource, aSourceFormat); + DataSpanType dest = GetDataSpan(aDest, aCopyToSpec.mFormat); + CopySamples(source, dest, aSourceChannelCount, aSourceFormat, aCopyToSpec); +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-copyto +void AudioData::CopyTo( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDestination, + const AudioDataCopyToOptions& aOptions, ErrorResult& aRv) { + AssertIsOnOwningThread(); + + size_t destLength = ProcessTypedArraysFixed( + aDestination, [&](const Span<uint8_t>& aData) -> size_t { + return aData.LengthBytes(); + }); + + LOGD("AudioData::CopyTo %s -> %s", ToString().get(), + CopyToToString(destLength, aOptions).get()); + + if (!mResource) { + auto msg = "copyTo called on closed AudioData"_ns; + LOGD("%s", msg.get()); + aRv.ThrowInvalidStateError(msg); + return; + } + + uint64_t copyElementCount = ComputeCopyElementCount(aOptions, aRv); + if (aRv.Failed()) { + LOGD("AudioData::CopyTo failed in ComputeCopyElementCount"); + return; + } + auto destFormat = mAudioSampleFormat; + if (aOptions.mFormat.WasPassed()) { + destFormat = OptionalToMaybe(aOptions.mFormat); + } + + uint32_t bytesPerSample = BytesPerSamples(destFormat.value()); + CheckedInt<uint32_t> copyLength = bytesPerSample; + copyLength *= copyElementCount; + if (copyLength.value() > destLength) { + auto msg = nsPrintfCString( + "destination buffer of length %zu too small for copying %" PRIu64 + " elements", + destLength, bytesPerSample * copyElementCount); + LOGD("%s", msg.get()); + aRv.ThrowRangeError(msg); + return; + } + + uint32_t framesToCopy = mNumberOfFrames - aOptions.mFrameOffset; + if (aOptions.mFrameCount.WasPassed()) { + framesToCopy = aOptions.mFrameCount.Value(); + } + + CopyToSpec copyToSpec(framesToCopy, aOptions.mFrameOffset, + aOptions.mPlaneIndex, destFormat.value()); + + // Now a couple layers of macros to type the pointers and perform the actual + // copy. + ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) { + DoCopy(mResource->Data(), aData, mNumberOfChannels, + mAudioSampleFormat.value(), copyToSpec); + }); +} + +// https://w3c.github.io/webcodecs/#dom-audiodata-clone +already_AddRefed<AudioData> AudioData::Clone(ErrorResult& aRv) { + AssertIsOnOwningThread(); + + if (!mResource) { + auto msg = "No media resource in the AudioData now"_ns; + LOGD("%s", msg.get()); + aRv.ThrowInvalidStateError(msg); + return nullptr; + } + + return MakeAndAddRef<AudioData>(*this); +} + +// https://w3c.github.io/webcodecs/#close-audiodata +void AudioData::Close() { + AssertIsOnOwningThread(); + + mResource = nullptr; + mSampleRate = 0; + mNumberOfFrames = 0; + mNumberOfChannels = 0; + mAudioSampleFormat = Nothing(); +} + +// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A1 +/* static */ +JSObject* AudioData::ReadStructuredClone(JSContext* aCx, + nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader, + const AudioDataSerializedData& aData) { + JS::Rooted<JS::Value> value(aCx, JS::NullValue()); + // To avoid a rooting hazard error from returning a raw JSObject* before + // running the RefPtr destructor, RefPtr needs to be destructed before + // returning the raw JSObject*, which is why the RefPtr<AudioData> is created + // in the scope below. Otherwise, the static analysis infers the RefPtr cannot + // be safely destructed while the unrooted return JSObject* is on the stack. + { + RefPtr<AudioData> frame = MakeAndAddRef<AudioData>(aGlobal, aData); + if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) { + LOGE("GetOrCreateDOMReflect failure"); + return nullptr; + } + } + return value.toObjectOrNull(); +} + +// https://w3c.github.io/webcodecs/#ref-for-audiodata%E2%91%A2%E2%91%A2 +bool AudioData::WriteStructuredClone(JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) const { + AssertIsOnOwningThread(); + + // AudioData closed + if (!mResource) { + LOGD("AudioData was already close in WriteStructuredClone"); + return false; + } + const uint32_t index = aHolder->AudioData().Length(); + // https://github.com/w3c/webcodecs/issues/717 + // For now, serialization is only allowed in the same address space, it's OK + // to send a refptr here instead of copying the backing buffer. + aHolder->AudioData().AppendElement(AudioDataSerializedData(*this)); + + return !NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_AUDIODATA, index)); +} + +// https://w3c.github.io/webcodecs/#ref-for-transfer-steps +UniquePtr<AudioData::TransferredData> AudioData::Transfer() { + AssertIsOnOwningThread(); + + if (!mResource) { + // Closed + LOGD("AudioData was already close in Transfer"); + return nullptr; + } + + // This adds a ref to the resource + auto serialized = MakeUnique<AudioDataSerializedData>(*this); + // This removes the ref to the resource, effectively transfering the backing + // storage. + Close(); + return serialized; +} + +// https://w3c.github.io/webcodecs/#ref-for-transfer-receiving-steps +/* static */ +already_AddRefed<AudioData> AudioData::FromTransferred(nsIGlobalObject* aGlobal, + TransferredData* aData) { + MOZ_ASSERT(aData); + + return MakeAndAddRef<AudioData>(aGlobal, *aData); +} + +void AudioData::CloseIfNeeded() { + if (mResource) { + mResource = nullptr; + } +} + +#undef LOGD +#undef LOGE +#undef LOG_INTERNAL + +} // namespace mozilla::dom diff --git a/dom/media/webcodecs/AudioData.h b/dom/media/webcodecs/AudioData.h new file mode 100644 index 0000000000..4ae69a225a --- /dev/null +++ b/dom/media/webcodecs/AudioData.h @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_AudioData_h +#define mozilla_dom_AudioData_h + +#include "MediaData.h" +#include "WebCodecsUtils.h" +#include "js/TypeDecls.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Span.h" +#include "mozilla/dom/AudioDataBinding.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; +class nsIURI; + +namespace mozilla::dom { + +class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer; +class OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer; +class Promise; +struct AudioDataBufferInit; +struct AudioDataCopyToOptions; +struct AudioDataInit; + +} // namespace mozilla::dom + +namespace mozilla::dom { + +class AudioData; +class AudioDataResource; +struct AudioDataSerializedData; + +class AudioData final : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AudioData) + + public: + AudioData(nsIGlobalObject* aParent, const AudioDataSerializedData& aData); + AudioData(nsIGlobalObject* aParent, + already_AddRefed<AudioDataResource> aResource, + const AudioDataInit& aInit); + AudioData(nsIGlobalObject* aParent, + already_AddRefed<mozilla::dom::AudioDataResource> aResource, + int64_t aTimestamp, uint32_t aNumberOfChannels, + uint32_t aNumberOfFrames, float aSampleRate, + AudioSampleFormat aAudioSampleFormat); + AudioData(const AudioData& aOther); + + protected: + ~AudioData() = default; + + public: + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<AudioData> Constructor(const GlobalObject& aGlobal, + const AudioDataInit& aInit, + ErrorResult& aRv); + + Nullable<mozilla::dom::AudioSampleFormat> GetFormat() const; + + float SampleRate() const; + + uint32_t NumberOfFrames() const; + + uint32_t NumberOfChannels() const; + + uint64_t Duration() const; // microseconds + + int64_t Timestamp() const; // microseconds + + uint32_t AllocationSize(const AudioDataCopyToOptions& aOptions, + ErrorResult& aRv); + + void CopyTo( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDestination, + const AudioDataCopyToOptions& aOptions, ErrorResult& aRv); + + already_AddRefed<AudioData> Clone(ErrorResult& aRv); + + void Close(); + + // [Serializable] implementations: {Read, Write}StructuredClone + static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader, + const AudioDataSerializedData& aData); + + bool WriteStructuredClone(JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) const; + + // [Transferable] implementations: Transfer, FromTransferred + using TransferredData = AudioDataSerializedData; + + UniquePtr<TransferredData> Transfer(); + + static already_AddRefed<AudioData> FromTransferred(nsIGlobalObject* aGlobal, + TransferredData* aData); + + private: + size_t ComputeCopyElementCount(const AudioDataCopyToOptions& aOptions, + ErrorResult& aRv); + + nsCString ToString() const; + // AudioData can run on either main thread or worker thread. + void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(AudioData); } + void CloseIfNeeded(); + + nsCOMPtr<nsIGlobalObject> mParent; + + friend struct AudioDataSerializedData; + + int64_t mTimestamp; + uint32_t mNumberOfChannels; + uint32_t mNumberOfFrames; + float mSampleRate; + Maybe<AudioSampleFormat> mAudioSampleFormat; + RefPtr<mozilla::dom::AudioDataResource> mResource; +}; + +class AudioDataResource final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataResource); + explicit AudioDataResource(FallibleTArray<uint8_t>&& aData) + : mData(std::move(aData)) {} + + explicit AudioDataResource() : mData() {} + + static AudioDataResource* Create(const Span<uint8_t>& aData) { + AudioDataResource* resource = new AudioDataResource(); + if (!resource->mData.AppendElements(aData, mozilla::fallible_t())) { + return nullptr; + } + return resource; + } + + static Result<already_AddRefed<AudioDataResource>, nsresult> Construct( + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aInit); + + Span<uint8_t> Data() { return Span(mData.Elements(), mData.Length()); }; + + private: + ~AudioDataResource() = default; + // It's always possible for the allocation to fail -- the size is + // controled by script. + FallibleTArray<uint8_t> mData; +}; + +struct AudioDataSerializedData { + explicit AudioDataSerializedData(const AudioData& aFrom) + : mTimestamp(aFrom.Timestamp()), + mNumberOfChannels(aFrom.NumberOfChannels()), + mNumberOfFrames(aFrom.NumberOfFrames()), + mSampleRate(aFrom.SampleRate()), + mAudioSampleFormat(NullableToMaybe(aFrom.GetFormat())), + mResource(aFrom.mResource) {} + int64_t mTimestamp{}; + uint32_t mNumberOfChannels{}; + uint32_t mNumberOfFrames{}; + float mSampleRate{}; + Maybe<AudioSampleFormat> mAudioSampleFormat; + RefPtr<AudioDataResource> mResource; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_AudioData_h diff --git a/dom/media/webcodecs/AudioDecoder.cpp b/dom/media/webcodecs/AudioDecoder.cpp new file mode 100644 index 0000000000..6b554dcacf --- /dev/null +++ b/dom/media/webcodecs/AudioDecoder.cpp @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/AudioDecoder.h" +#include "mozilla/dom/AudioDecoderBinding.h" + +#include "DecoderTraits.h" +#include "MediaContainerType.h" +#include "MediaData.h" +#include "VideoUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/Try.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/AudioDataBinding.h" +#include "mozilla/dom/EncodedAudioChunk.h" +#include "mozilla/dom/EncodedAudioChunkBinding.h" +#include "mozilla/dom/ImageUtils.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WebCodecsUtils.h" +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" + +extern mozilla::LazyLogModule gWebCodecsLog; + +namespace mozilla::dom { + +#ifdef LOG_INTERNAL +# undef LOG_INTERNAL +#endif // LOG_INTERNAL +#define LOG_INTERNAL(level, msg, ...) \ + MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__)) + +#ifdef LOG +# undef LOG +#endif // LOG +#define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__) + +#ifdef LOGW +# undef LOGW +#endif // LOGW +#define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__) + +#ifdef LOGE +# undef LOGE +#endif // LOGE +#define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__) + +#ifdef LOGV +# undef LOGV +#endif // LOGV +#define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDecoder, DOMEventTargetHelper, + mErrorCallback, mOutputCallback) +NS_IMPL_ADDREF_INHERITED(AudioDecoder, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(AudioDecoder, DOMEventTargetHelper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioDecoder) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +/* + * Below are helper classes + */ + +AudioDecoderConfigInternal::AudioDecoderConfigInternal( + const nsAString& aCodec, uint32_t aSampleRate, uint32_t aNumberOfChannels, + Maybe<RefPtr<MediaByteBuffer>>&& aDescription) + : mCodec(aCodec), + mSampleRate(aSampleRate), + mNumberOfChannels(aNumberOfChannels), + mDescription(std::move(aDescription)) {} + +/*static*/ +UniquePtr<AudioDecoderConfigInternal> AudioDecoderConfigInternal::Create( + const AudioDecoderConfig& aConfig) { + nsCString errorMessage; + if (!AudioDecoderTraits::Validate(aConfig, errorMessage)) { + LOGE("Failed to create AudioDecoderConfigInternal: %s", errorMessage.get()); + return nullptr; + } + + Maybe<RefPtr<MediaByteBuffer>> description; + if (aConfig.mDescription.WasPassed()) { + auto rv = GetExtraDataFromArrayBuffer(aConfig.mDescription.Value()); + if (rv.isErr()) { // Invalid description data. + nsCString error; + GetErrorName(rv.unwrapErr(), error); + LOGE( + "Failed to create AudioDecoderConfigInternal due to invalid " + "description data. Error: %s", + error.get()); + return nullptr; + } + description.emplace(rv.unwrap()); + } + + return UniquePtr<AudioDecoderConfigInternal>(new AudioDecoderConfigInternal( + aConfig.mCodec, aConfig.mSampleRate, aConfig.mNumberOfChannels, + std::move(description))); +} + +/* + * The followings are helpers for AudioDecoder methods + */ + +struct AudioMIMECreateParam { + explicit AudioMIMECreateParam(const AudioDecoderConfigInternal& aConfig) + : mParsedCodec(ParseCodecString(aConfig.mCodec).valueOr(EmptyString())) {} + explicit AudioMIMECreateParam(const AudioDecoderConfig& aConfig) + : mParsedCodec(ParseCodecString(aConfig.mCodec).valueOr(EmptyString())) {} + + const nsString mParsedCodec; +}; + +// Map between WebCodecs pcm types as strings and codec numbers +// All other codecs +nsCString ConvertCodecName(const nsCString& aContainer, + const nsCString& aCodec) { + if (!aContainer.EqualsLiteral("x-wav")) { + return aCodec; + } + if (aCodec.EqualsLiteral("ulaw")) { + return nsCString("7"); + } + if (aCodec.EqualsLiteral("alaw")) { + return nsCString("6"); + } + if (aCodec.Find("f32")) { + return nsCString("3"); + } + // Linear PCM + return nsCString("1"); +} + +static nsTArray<nsCString> GuessMIMETypes(const AudioMIMECreateParam& aParam) { + nsCString codec = NS_ConvertUTF16toUTF8(aParam.mParsedCodec); + nsTArray<nsCString> types; + for (const nsCString& container : GuessContainers(aParam.mParsedCodec)) { + codec = ConvertCodecName(container, codec); + nsPrintfCString mime("audio/%s; codecs=%s", container.get(), codec.get()); + types.AppendElement(mime); + } + return types; +} + +static bool IsSupportedAudioCodec(const nsAString& aCodec) { + LOG("IsSupportedAudioCodec: %s", NS_ConvertUTF16toUTF8(aCodec).get()); + return aCodec.EqualsLiteral("flac") || aCodec.EqualsLiteral("mp3") || + IsAACCodecString(aCodec) || aCodec.EqualsLiteral("opus") || + aCodec.EqualsLiteral("ulaw") || aCodec.EqualsLiteral("alaw") || + aCodec.EqualsLiteral("pcm-u8") || aCodec.EqualsLiteral("pcm-s16") || + aCodec.EqualsLiteral("pcm-s24") || aCodec.EqualsLiteral("pcm-s32") || + aCodec.EqualsLiteral("pcm-f32"); +} + +// https://w3c.github.io/webcodecs/#check-configuration-support +template <typename Config> +static bool CanDecodeAudio(const Config& aConfig) { + auto param = AudioMIMECreateParam(aConfig); + if (!IsSupportedAudioCodec(param.mParsedCodec)) { + return false; + } + if (IsOnAndroid() && IsAACCodecString(param.mParsedCodec)) { + return false; + } + // TODO: Instead of calling CanHandleContainerType with the guessed the + // containers, DecoderTraits should provide an API to tell if a codec is + // decodable or not. + for (const nsCString& mime : GuessMIMETypes(param)) { + if (Maybe<MediaContainerType> containerType = + MakeMediaExtendedMIMEType(mime)) { + if (DecoderTraits::CanHandleContainerType( + *containerType, nullptr /* DecoderDoctorDiagnostics */) != + CANPLAY_NO) { + return true; + } + } + } + return false; +} + +static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo( + const AudioDecoderConfigInternal& aConfig) { + // TODO: Instead of calling GetTracksInfo with the guessed containers, + // DecoderTraits should provide an API to create the TrackInfo directly. + for (const nsCString& mime : GuessMIMETypes(AudioMIMECreateParam(aConfig))) { + if (Maybe<MediaContainerType> containerType = + MakeMediaExtendedMIMEType(mime)) { + if (nsTArray<UniquePtr<TrackInfo>> tracks = + DecoderTraits::GetTracksInfo(*containerType); + !tracks.IsEmpty()) { + return tracks; + } + } + } + return {}; +} + +static Result<Ok, nsresult> CloneConfiguration( + RootedDictionary<AudioDecoderConfig>& aDest, JSContext* aCx, + const AudioDecoderConfig& aConfig, ErrorResult& aRv) { + aDest.mCodec = aConfig.mCodec; + if (aConfig.mDescription.WasPassed()) { + aDest.mDescription.Construct(); + MOZ_TRY(CloneBuffer(aCx, aDest.mDescription.Value(), + aConfig.mDescription.Value(), aRv)); + } + + aDest.mNumberOfChannels = aConfig.mNumberOfChannels; + aDest.mSampleRate = aConfig.mSampleRate; + + return Ok(); +} + +// https://w3c.github.io/webcodecs/#create-a-audiodata +static RefPtr<AudioData> CreateAudioData(nsIGlobalObject* aGlobalObject, + mozilla::AudioData* aData) { + MOZ_ASSERT(aGlobalObject); + MOZ_ASSERT(aData); + + auto buf = aData->MoveableData(); + // TODO: Ensure buf.Length() is a multiple of aData->mChannels and put it into + // AssertedCast<uint32_t> (sinze return type of buf.Length() is size_t). + uint32_t frames = buf.Length() / aData->mChannels; + RefPtr<AudioDataResource> resource = AudioDataResource::Create(Span{ + reinterpret_cast<uint8_t*>(buf.Data()), buf.Length() * sizeof(float)}); + return MakeRefPtr<AudioData>(aGlobalObject, resource.forget(), + aData->mTime.ToMicroseconds(), aData->mChannels, + frames, AssertedCast<float>(aData->mRate), + mozilla::dom::AudioSampleFormat::F32); +} + +/* static */ +bool AudioDecoderTraits::IsSupported( + const AudioDecoderConfigInternal& aConfig) { + return CanDecodeAudio(aConfig); +} + +/* static */ +Result<UniquePtr<TrackInfo>, nsresult> AudioDecoderTraits::CreateTrackInfo( + const AudioDecoderConfigInternal& aConfig) { + LOG("Create a AudioInfo from %s config", + NS_ConvertUTF16toUTF8(aConfig.mCodec).get()); + + nsTArray<UniquePtr<TrackInfo>> tracks = GetTracksInfo(aConfig); + if (tracks.Length() != 1 || tracks[0]->GetType() != TrackInfo::kAudioTrack) { + LOGE("Failed to get TrackInfo"); + return Err(NS_ERROR_INVALID_ARG); + } + + UniquePtr<TrackInfo> track(std::move(tracks[0])); + AudioInfo* ai = track->GetAsAudioInfo(); + if (!ai) { + LOGE("Failed to get AudioInfo"); + return Err(NS_ERROR_INVALID_ARG); + } + + if (aConfig.mDescription.isSome()) { + RefPtr<MediaByteBuffer> buf; + buf = aConfig.mDescription.value(); + if (buf) { + LOG("The given config has %zu bytes of description data", buf->Length()); + ai->mCodecSpecificConfig = + AudioCodecSpecificVariant{AudioCodecSpecificBinaryBlob{buf}}; + } + } + + ai->mChannels = aConfig.mNumberOfChannels; + ai->mRate = aConfig.mSampleRate; + + LOG("Created AudioInfo %s (%" PRIu32 "ch %" PRIu32 + "Hz - with extra-data: %s)", + NS_ConvertUTF16toUTF8(aConfig.mCodec).get(), ai->mChannels, ai->mChannels, + aConfig.mDescription.isSome() ? "yes" : "no"); + + return track; +} + +// https://w3c.github.io/webcodecs/#valid-audiodecoderconfig +/* static */ +bool AudioDecoderTraits::Validate(const AudioDecoderConfig& aConfig, + nsCString& aErrorMessage) { + Maybe<nsString> codec = ParseCodecString(aConfig.mCodec); + if (!codec || codec->IsEmpty()) { + LOGE("Validating AudioDecoderConfig: invalid codec string"); + + aErrorMessage.AppendPrintf("Invalid codec string %s", + NS_ConvertUTF16toUTF8(aConfig.mCodec).get()); + return false; + } + + LOG("Validating AudioDecoderConfig: codec: %s %uch %uHz %s extradata", + NS_ConvertUTF16toUTF8(codec.value()).get(), aConfig.mNumberOfChannels, + aConfig.mSampleRate, aConfig.mDescription.WasPassed() ? "w/" : "no"); + + if (aConfig.mNumberOfChannels == 0) { + aErrorMessage.AppendPrintf("Invalid number of channels of %u", + aConfig.mNumberOfChannels); + return false; + } + + if (aConfig.mSampleRate == 0) { + aErrorMessage.AppendPrintf("Invalid sample-rate of %u", + aConfig.mNumberOfChannels); + return false; + } + + bool detached = + aConfig.mDescription.WasPassed() && + (aConfig.mDescription.Value().IsArrayBuffer() + ? JS::ArrayBuffer::fromObject( + aConfig.mDescription.Value().GetAsArrayBuffer().Obj()) + .isDetached() + : JS::ArrayBufferView::fromObject( + aConfig.mDescription.Value().GetAsArrayBufferView().Obj()) + .isDetached()); + + if (detached) { + LOGE("description is detached."); + return false; + } + + return true; +} + +/* static */ +UniquePtr<AudioDecoderConfigInternal> AudioDecoderTraits::CreateConfigInternal( + const AudioDecoderConfig& aConfig) { + return AudioDecoderConfigInternal::Create(aConfig); +} + +/* static */ +bool AudioDecoderTraits::IsKeyChunk(const EncodedAudioChunk& aInput) { + return aInput.Type() == EncodedAudioChunkType::Key; +} + +/* static */ +UniquePtr<EncodedAudioChunkData> AudioDecoderTraits::CreateInputInternal( + const EncodedAudioChunk& aInput) { + return aInput.Clone(); +} + +/* + * Below are AudioDecoder implementation + */ + +AudioDecoder::AudioDecoder(nsIGlobalObject* aParent, + RefPtr<WebCodecsErrorCallback>&& aErrorCallback, + RefPtr<AudioDataOutputCallback>&& aOutputCallback) + : DecoderTemplate(aParent, std::move(aErrorCallback), + std::move(aOutputCallback)) { + MOZ_ASSERT(mErrorCallback); + MOZ_ASSERT(mOutputCallback); + LOG("AudioDecoder %p ctor", this); +} + +AudioDecoder::~AudioDecoder() { + LOG("AudioDecoder %p dtor", this); + Unused << ResetInternal(NS_ERROR_DOM_ABORT_ERR); +} + +JSObject* AudioDecoder::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + AssertIsOnOwningThread(); + + return AudioDecoder_Binding::Wrap(aCx, this, aGivenProto); +} + +// https://w3c.github.io/webcodecs/#dom-audiodecoder-audiodecoder +/* static */ +already_AddRefed<AudioDecoder> AudioDecoder::Constructor( + const GlobalObject& aGlobal, const AudioDecoderInit& aInit, + ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + return MakeAndAddRef<AudioDecoder>( + global.get(), RefPtr<WebCodecsErrorCallback>(aInit.mError), + RefPtr<AudioDataOutputCallback>(aInit.mOutput)); +} + +// https://w3c.github.io/webcodecs/#dom-audiodecoder-isconfigsupported +/* static */ +already_AddRefed<Promise> AudioDecoder::IsConfigSupported( + const GlobalObject& aGlobal, const AudioDecoderConfig& aConfig, + ErrorResult& aRv) { + LOG("AudioDecoder::IsConfigSupported, config: %s", + NS_ConvertUTF16toUTF8(aConfig.mCodec).get()); + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Promise> p = Promise::Create(global.get(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return p.forget(); + } + + nsCString errorMessage; + if (!AudioDecoderTraits::Validate(aConfig, errorMessage)) { + p->MaybeRejectWithTypeError(errorMessage); + return p.forget(); + } + + RootedDictionary<AudioDecoderConfig> config(aGlobal.Context()); + auto r = CloneConfiguration(config, aGlobal.Context(), aConfig, aRv); + if (r.isErr()) { + // This can only be an OOM: all members to clone are known to be valid + // because this is check by ::Validate above. + MOZ_ASSERT(r.inspectErr() == NS_ERROR_OUT_OF_MEMORY && + aRv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)); + return p.forget(); + } + + bool canDecode = CanDecodeAudio(config); + RootedDictionary<AudioDecoderSupport> s(aGlobal.Context()); + s.mConfig.Construct(std::move(config)); + s.mSupported.Construct(canDecode); + + p->MaybeResolve(s); + return p.forget(); +} + +already_AddRefed<MediaRawData> AudioDecoder::InputDataToMediaRawData( + UniquePtr<EncodedAudioChunkData>&& aData, TrackInfo& aInfo, + const AudioDecoderConfigInternal& aConfig) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aInfo.GetAsAudioInfo()); + + if (!aData) { + LOGE("No data for conversion"); + return nullptr; + } + + RefPtr<MediaRawData> sample = aData->TakeData(); + if (!sample) { + LOGE("Take no data for conversion"); + return nullptr; + } + + LOGV( + "EncodedAudioChunkData %p converted to %zu-byte MediaRawData - time: " + "%" PRIi64 "us, timecode: %" PRIi64 "us, duration: %" PRIi64 + "us, key-frame: %s", + aData.get(), sample->Size(), sample->mTime.ToMicroseconds(), + sample->mTimecode.ToMicroseconds(), sample->mDuration.ToMicroseconds(), + sample->mKeyframe ? "yes" : "no"); + + return sample.forget(); +} + +nsTArray<RefPtr<AudioData>> AudioDecoder::DecodedDataToOutputType( + nsIGlobalObject* aGlobalObject, const nsTArray<RefPtr<MediaData>>&& aData, + AudioDecoderConfigInternal& aConfig) { + AssertIsOnOwningThread(); + + nsTArray<RefPtr<AudioData>> frames; + for (const RefPtr<MediaData>& data : aData) { + MOZ_RELEASE_ASSERT(data->mType == MediaData::Type::AUDIO_DATA); + RefPtr<mozilla::AudioData> d(data->As<mozilla::AudioData>()); + frames.AppendElement(CreateAudioData(aGlobalObject, d.get())); + } + return frames; +} + +#undef LOG +#undef LOGW +#undef LOGE +#undef LOGV +#undef LOG_INTERNAL + +} // namespace mozilla::dom diff --git a/dom/media/webcodecs/AudioDecoder.h b/dom/media/webcodecs/AudioDecoder.h new file mode 100644 index 0000000000..54fad68f55 --- /dev/null +++ b/dom/media/webcodecs/AudioDecoder.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_AudioDecoder_h +#define mozilla_dom_AudioDecoder_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/AudioData.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/DecoderTemplate.h" +#include "mozilla/dom/DecoderTypes.h" +#include "mozilla/dom/RootedDictionary.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { + +namespace dom { + +class AudioDataOutputCallback; +class EncodedAudioChunk; +class EncodedAudioChunkData; +class EventHandlerNonNull; +class GlobalObject; +class Promise; +class WebCodecsErrorCallback; +struct AudioDecoderConfig; +struct AudioDecoderInit; + +} // namespace dom + +} // namespace mozilla + +namespace mozilla::dom { + +class AudioDecoder final : public DecoderTemplate<AudioDecoderTraits> { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDecoder, DOMEventTargetHelper) + + public: + AudioDecoder(nsIGlobalObject* aParent, + RefPtr<WebCodecsErrorCallback>&& aErrorCallback, + RefPtr<AudioDataOutputCallback>&& aOutputCallback); + + protected: + ~AudioDecoder(); + + public: + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<AudioDecoder> Constructor( + const GlobalObject& aGlobal, const AudioDecoderInit& aInit, + ErrorResult& aRv); + + static already_AddRefed<Promise> IsConfigSupported( + const GlobalObject& aGlobal, const AudioDecoderConfig& aConfig, + ErrorResult& aRv); + + protected: + virtual already_AddRefed<MediaRawData> InputDataToMediaRawData( + UniquePtr<EncodedAudioChunkData>&& aData, TrackInfo& aInfo, + const AudioDecoderConfigInternal& aConfig) override; + + virtual nsTArray<RefPtr<AudioData>> DecodedDataToOutputType( + nsIGlobalObject* aGlobalObject, const nsTArray<RefPtr<MediaData>>&& aData, + AudioDecoderConfigInternal& aConfig) override; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_AudioDecoder_h diff --git a/dom/media/webcodecs/DecoderAgent.cpp b/dom/media/webcodecs/DecoderAgent.cpp index 5c63e27d48..095852c01d 100644 --- a/dom/media/webcodecs/DecoderAgent.cpp +++ b/dom/media/webcodecs/DecoderAgent.cpp @@ -6,8 +6,6 @@ #include "DecoderAgent.h" -#include <atomic> - #include "ImageContainer.h" #include "MediaDataDecoderProxy.h" #include "PDMFactory.h" diff --git a/dom/media/webcodecs/DecoderTemplate.cpp b/dom/media/webcodecs/DecoderTemplate.cpp index 0fa25a208b..4d1c310737 100644 --- a/dom/media/webcodecs/DecoderTemplate.cpp +++ b/dom/media/webcodecs/DecoderTemplate.cpp @@ -139,8 +139,8 @@ void DecoderTemplate<DecoderType>::Configure(const ConfigType& aConfig, nsCString errorMessage; if (!DecoderType::Validate(aConfig, errorMessage)) { - aRv.ThrowTypeError( - nsPrintfCString("config is invalid: %s", errorMessage.get())); + LOG("Configure: Validate error: %s", errorMessage.get()); + aRv.ThrowTypeError(errorMessage); return; } @@ -322,13 +322,13 @@ void DecoderTemplate<DecoderType>::OutputDecodedData( MOZ_ASSERT(mState == CodecState::Configured); MOZ_ASSERT(mActiveConfig); - nsTArray<RefPtr<VideoFrame>> frames = DecodedDataToOutputType( + nsTArray<RefPtr<OutputType>> frames = DecodedDataToOutputType( GetParentObject(), std::move(aData), *mActiveConfig); - RefPtr<VideoFrameOutputCallback> cb(mOutputCallback); - for (RefPtr<VideoFrame>& frame : frames) { + RefPtr<OutputCallbackType> cb(mOutputCallback); + for (RefPtr<OutputType>& frame : frames) { LOG("Outputing decoded data: ts: %" PRId64, frame->Timestamp()); - RefPtr<VideoFrame> f = frame; - cb->Call((VideoFrame&)(*f)); + RefPtr<OutputType> f = frame; + cb->Call((OutputType&)(*f)); } } @@ -881,6 +881,7 @@ void DecoderTemplate<DecoderType>::DestroyDecoderAgentIfAny() { } template class DecoderTemplate<VideoDecoderTraits>; +template class DecoderTemplate<AudioDecoderTraits>; #undef LOG #undef LOGW diff --git a/dom/media/webcodecs/DecoderTypes.h b/dom/media/webcodecs/DecoderTypes.h index 56aa82046f..339a164f70 100644 --- a/dom/media/webcodecs/DecoderTypes.h +++ b/dom/media/webcodecs/DecoderTypes.h @@ -9,6 +9,9 @@ #include "MediaData.h" #include "mozilla/Maybe.h" +#include "mozilla/dom/AudioData.h" +#include "mozilla/dom/AudioDecoderBinding.h" +#include "mozilla/dom/EncodedAudioChunk.h" #include "mozilla/dom/EncodedVideoChunk.h" #include "mozilla/dom/VideoColorSpaceBinding.h" #include "mozilla/dom/VideoDecoderBinding.h" @@ -58,17 +61,17 @@ class VideoDecoderConfigInternal { bool Equals(const VideoDecoderConfigInternal& aOther) const { if (mDescription.isSome() != aOther.mDescription.isSome()) { - return false; + return false; } if (mDescription.isSome() && aOther.mDescription.isSome()) { - auto lhs = mDescription.value(); - auto rhs = aOther.mDescription.value(); - if (lhs->Length() != rhs->Length()) { - return false; - } - if (!ArrayEqual(lhs->Elements(), rhs->Elements(), lhs->Length())) { - return false; - } + auto lhs = mDescription.value(); + auto rhs = aOther.mDescription.value(); + if (lhs->Length() != rhs->Length()) { + return false; + } + if (!ArrayEqual(lhs->Elements(), rhs->Elements(), lhs->Length())) { + return false; + } } return mCodec.Equals(aOther.mCodec) && mCodedHeight == aOther.mCodedHeight && @@ -111,6 +114,49 @@ class VideoDecoderTraits { const InputType& aInput); }; +class AudioDecoderConfigInternal { + public: + static UniquePtr<AudioDecoderConfigInternal> Create( + const AudioDecoderConfig& aConfig); + ~AudioDecoderConfigInternal() = default; + + nsString mCodec; + uint32_t mSampleRate; + uint32_t mNumberOfChannels; + Maybe<RefPtr<MediaByteBuffer>> mDescription; + // Compilation fix, should be abstracted by DecoderAgent since those are not + // supported + HardwareAcceleration mHardwareAcceleration = + HardwareAcceleration::No_preference; + Maybe<bool> mOptimizeForLatency; + + private: + AudioDecoderConfigInternal(const nsAString& aCodec, uint32_t aSampleRate, + uint32_t aNumberOfChannels, + Maybe<RefPtr<MediaByteBuffer>>&& aDescription); +}; + +class AudioDecoderTraits { + public: + static constexpr nsLiteralCString Name = "AudioDecoder"_ns; + using ConfigType = AudioDecoderConfig; + using ConfigTypeInternal = AudioDecoderConfigInternal; + using InputType = EncodedAudioChunk; + using InputTypeInternal = EncodedAudioChunkData; + using OutputType = AudioData; + using OutputCallbackType = AudioDataOutputCallback; + + static bool IsSupported(const ConfigTypeInternal& aConfig); + static Result<UniquePtr<TrackInfo>, nsresult> CreateTrackInfo( + const ConfigTypeInternal& aConfig); + static bool Validate(const ConfigType& aConfig, nsCString& aErrorMessage); + static UniquePtr<ConfigTypeInternal> CreateConfigInternal( + const ConfigType& aConfig); + static bool IsKeyChunk(const InputType& aInput); + static UniquePtr<InputTypeInternal> CreateInputInternal( + const InputType& aInput); +}; + } // namespace dom } // namespace mozilla diff --git a/dom/media/webcodecs/EncodedAudioChunk.cpp b/dom/media/webcodecs/EncodedAudioChunk.cpp new file mode 100644 index 0000000000..1cbb2ff9bd --- /dev/null +++ b/dom/media/webcodecs/EncodedAudioChunk.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/EncodedAudioChunk.h" +#include "mozilla/dom/EncodedAudioChunkBinding.h" + +#include <utility> + +#include "MediaData.h" +#include "TimeUnits.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Logging.h" +#include "mozilla/PodOperations.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/WebCodecsUtils.h" + +extern mozilla::LazyLogModule gWebCodecsLog; +using mozilla::media::TimeUnit; + +namespace mozilla::dom { + +#ifdef LOG_INTERNAL +# undef LOG_INTERNAL +#endif // LOG_INTERNAL +#define LOG_INTERNAL(level, msg, ...) \ + MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__)) + +#ifdef LOGW +# undef LOGW +#endif // LOGW +#define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__) + +#ifdef LOGE +# undef LOGE +#endif // LOGE +#define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__) + +// Only needed for refcounted objects. +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(EncodedAudioChunk, mParent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(EncodedAudioChunk) +NS_IMPL_CYCLE_COLLECTING_RELEASE(EncodedAudioChunk) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EncodedAudioChunk) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +EncodedAudioChunkData::EncodedAudioChunkData( + already_AddRefed<MediaAlignedByteBuffer> aBuffer, + const EncodedAudioChunkType& aType, int64_t aTimestamp, + Maybe<uint64_t>&& aDuration) + : mBuffer(aBuffer), + mType(aType), + mTimestamp(aTimestamp), + mDuration(aDuration) { + MOZ_ASSERT(mBuffer); + MOZ_ASSERT(mBuffer->Length() == mBuffer->Size()); + MOZ_ASSERT(mBuffer->Length() <= + static_cast<size_t>(std::numeric_limits<uint32_t>::max())); +} + +UniquePtr<EncodedAudioChunkData> EncodedAudioChunkData::Clone() const { + if (!mBuffer) { + LOGE("No buffer in EncodedAudioChunkData %p to clone!", this); + return nullptr; + } + + // Since EncodedAudioChunkData can be zero-sized, cloning a zero-sized chunk + // is allowed. + if (mBuffer->Size() == 0) { + LOGW("Cloning an empty EncodedAudioChunkData %p", this); + } + + auto buffer = + MakeRefPtr<MediaAlignedByteBuffer>(mBuffer->Data(), mBuffer->Length()); + if (!buffer || buffer->Size() != mBuffer->Size()) { + LOGE("OOM to copy EncodedAudioChunkData %p", this); + return nullptr; + } + + return MakeUnique<EncodedAudioChunkData>(buffer.forget(), mType, mTimestamp, + Maybe<uint64_t>(mDuration)); +} + +already_AddRefed<MediaRawData> EncodedAudioChunkData::TakeData() { + if (!mBuffer || !(*mBuffer)) { + LOGE("EncodedAudioChunkData %p has no data!", this); + return nullptr; + } + + RefPtr<MediaRawData> sample(new MediaRawData(std::move(*mBuffer))); + sample->mKeyframe = mType == EncodedAudioChunkType::Key; + sample->mTime = TimeUnit::FromMicroseconds(mTimestamp); + sample->mTimecode = TimeUnit::FromMicroseconds(mTimestamp); + + if (mDuration) { + CheckedInt64 duration(*mDuration); + if (!duration.isValid()) { + LOGE("EncodedAudioChunkData %p 's duration exceeds TimeUnit's limit", + this); + return nullptr; + } + sample->mDuration = TimeUnit::FromMicroseconds(duration.value()); + } + + return sample.forget(); +} + +EncodedAudioChunk::EncodedAudioChunk( + nsIGlobalObject* aParent, already_AddRefed<MediaAlignedByteBuffer> aBuffer, + const EncodedAudioChunkType& aType, int64_t aTimestamp, + Maybe<uint64_t>&& aDuration) + : EncodedAudioChunkData(std::move(aBuffer), aType, aTimestamp, + std::move(aDuration)), + mParent(aParent) {} + +EncodedAudioChunk::EncodedAudioChunk(nsIGlobalObject* aParent, + const EncodedAudioChunkData& aData) + : EncodedAudioChunkData(aData), mParent(aParent) {} + +nsIGlobalObject* EncodedAudioChunk::GetParentObject() const { + AssertIsOnOwningThread(); + + return mParent.get(); +} + +JSObject* EncodedAudioChunk::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + AssertIsOnOwningThread(); + + return EncodedAudioChunk_Binding::Wrap(aCx, this, aGivenProto); +} + +// https://w3c.github.io/webcodecs/#encodedaudiochunk-constructors +/* static */ +already_AddRefed<EncodedAudioChunk> EncodedAudioChunk::Constructor( + const GlobalObject& aGlobal, const EncodedAudioChunkInit& aInit, + ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + auto buffer = ProcessTypedArrays( + aInit.mData, + [&](const Span<uint8_t>& aData, + JS::AutoCheckCannotGC&&) -> RefPtr<MediaAlignedByteBuffer> { + // Make sure it's in uint32_t's range. + CheckedUint32 byteLength(aData.Length()); + if (!byteLength.isValid()) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return nullptr; + } + if (aData.Length() == 0) { + LOGW("Buffer for constructing EncodedAudioChunk is empty!"); + } + RefPtr<MediaAlignedByteBuffer> buf = MakeRefPtr<MediaAlignedByteBuffer>( + aData.Elements(), aData.Length()); + + // Instead of checking *buf, size comparision is used to allow + // constructing a zero-sized EncodedAudioChunk. + if (!buf || buf->Size() != aData.Length()) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + return buf; + }); + + RefPtr<EncodedAudioChunk> chunk(new EncodedAudioChunk( + global, buffer.forget(), aInit.mType, aInit.mTimestamp, + OptionalToMaybe(aInit.mDuration))); + return aRv.Failed() ? nullptr : chunk.forget(); +} + +EncodedAudioChunkType EncodedAudioChunk::Type() const { + AssertIsOnOwningThread(); + + return mType; +} + +int64_t EncodedAudioChunk::Timestamp() const { + AssertIsOnOwningThread(); + + return mTimestamp; +} + +Nullable<uint64_t> EncodedAudioChunk::GetDuration() const { + AssertIsOnOwningThread(); + return MaybeToNullable(mDuration); +} + +uint32_t EncodedAudioChunk::ByteLength() const { + AssertIsOnOwningThread(); + MOZ_ASSERT(mBuffer); + + return static_cast<uint32_t>(mBuffer->Length()); +} + +// https://w3c.github.io/webcodecs/#dom-encodedaudiochunk-copyto +void EncodedAudioChunk::CopyTo( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDestination, + ErrorResult& aRv) { + AssertIsOnOwningThread(); + + ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) { + if (mBuffer->Size() > aData.size_bytes()) { + aRv.ThrowTypeError( + "Destination ArrayBuffer smaller than source EncodedAudioChunk"); + return; + } + + PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size()); + }); +} + +// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps +/* static */ +JSObject* EncodedAudioChunk::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader, + const EncodedAudioChunkData& aData) { + JS::Rooted<JS::Value> value(aCx, JS::NullValue()); + // To avoid a rooting hazard error from returning a raw JSObject* before + // running the RefPtr destructor, RefPtr needs to be destructed before + // returning the raw JSObject*, which is why the RefPtr<EncodedAudioChunk> is + // created in the scope below. Otherwise, the static analysis infers the + // RefPtr cannot be safely destructed while the unrooted return JSObject* is + // on the stack. + { + auto frame = MakeRefPtr<EncodedAudioChunk>(aGlobal, aData); + if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) { + return nullptr; + } + } + return value.toObjectOrNull(); +} + +// https://w3c.github.io/webcodecs/#ref-for-serialization-steps +bool EncodedAudioChunk::WriteStructuredClone( + JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) const { + AssertIsOnOwningThread(); + + // Indexing the chunk and send the index to the receiver. + const uint32_t index = + static_cast<uint32_t>(aHolder->EncodedAudioChunks().Length()); + // The serialization is limited to the same process scope so it's ok to + // serialize a reference instead of a copy. + aHolder->EncodedAudioChunks().AppendElement(EncodedAudioChunkData(*this)); + return !NS_WARN_IF( + !JS_WriteUint32Pair(aWriter, SCTAG_DOM_ENCODEDAUDIOCHUNK, index)); +} + +#undef LOGW +#undef LOGE +#undef LOG_INTERNAL + +} // namespace mozilla::dom diff --git a/dom/media/webcodecs/EncodedAudioChunk.h b/dom/media/webcodecs/EncodedAudioChunk.h new file mode 100644 index 0000000000..53c059495a --- /dev/null +++ b/dom/media/webcodecs/EncodedAudioChunk.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_EncodedAudioChunk_h +#define mozilla_dom_EncodedAudioChunk_h + +#include "js/TypeDecls.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { + +class MediaAlignedByteBuffer; +class MediaRawData; + +namespace dom { + +class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer; +class OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer; +class StructuredCloneHolder; + +enum class EncodedAudioChunkType : uint8_t; +struct EncodedAudioChunkInit; + +} // namespace dom +} // namespace mozilla + +namespace mozilla::dom { + +class EncodedAudioChunkData { + public: + EncodedAudioChunkData(already_AddRefed<MediaAlignedByteBuffer> aBuffer, + const EncodedAudioChunkType& aType, int64_t aTimestamp, + Maybe<uint64_t>&& aDuration); + EncodedAudioChunkData(const EncodedAudioChunkData& aData) = default; + ~EncodedAudioChunkData() = default; + + UniquePtr<EncodedAudioChunkData> Clone() const; + already_AddRefed<MediaRawData> TakeData(); + + protected: + // mBuffer's byte length is guaranteed to be smaller than UINT32_MAX. + RefPtr<MediaAlignedByteBuffer> mBuffer; + EncodedAudioChunkType mType; + int64_t mTimestamp; + Maybe<uint64_t> mDuration; +}; + +class EncodedAudioChunk final : public EncodedAudioChunkData, + public nsISupports, + public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(EncodedAudioChunk) + + public: + EncodedAudioChunk(nsIGlobalObject* aParent, + already_AddRefed<MediaAlignedByteBuffer> aBuffer, + const EncodedAudioChunkType& aType, int64_t aTimestamp, + Maybe<uint64_t>&& aDuration); + + EncodedAudioChunk(nsIGlobalObject* aParent, + const EncodedAudioChunkData& aData); + + protected: + ~EncodedAudioChunk() = default; + + public: + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<EncodedAudioChunk> Constructor( + const GlobalObject& aGlobal, const EncodedAudioChunkInit& aInit, + ErrorResult& aRv); + + EncodedAudioChunkType Type() const; + + int64_t Timestamp() const; + + Nullable<uint64_t> GetDuration() const; + + uint32_t ByteLength() const; + + void CopyTo( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDestination, + ErrorResult& aRv); + + // [Serializable] implementations: {Read, Write}StructuredClone + static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader, + const EncodedAudioChunkData& aData); + + bool WriteStructuredClone(JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) const; + + private: + // EncodedAudioChunk can run on either main thread or worker thread. + void AssertIsOnOwningThread() const { + NS_ASSERT_OWNINGTHREAD(EncodedAudioChunk); + } + + nsCOMPtr<nsIGlobalObject> mParent; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_EncodedAudioChunk_h diff --git a/dom/media/webcodecs/VideoDecoder.cpp b/dom/media/webcodecs/VideoDecoder.cpp index 47ca5bb459..18855e5cea 100644 --- a/dom/media/webcodecs/VideoDecoder.cpp +++ b/dom/media/webcodecs/VideoDecoder.cpp @@ -15,11 +15,8 @@ #include "MediaData.h" #include "VideoUtils.h" #include "mozilla/Assertions.h" -#include "mozilla/CheckedInt.h" -#include "mozilla/DebugOnly.h" #include "mozilla/Logging.h" #include "mozilla/Maybe.h" -#include "mozilla/StaticPrefs_dom.h" #include "mozilla/Try.h" #include "mozilla/Unused.h" #include "mozilla/dom/EncodedVideoChunk.h" @@ -31,7 +28,6 @@ #include "mozilla/dom/WebCodecsUtils.h" #include "nsPrintfCString.h" #include "nsReadableUtils.h" -#include "nsThreadUtils.h" #ifdef XP_MACOSX # include "MacIOSurfaceImage.h" @@ -97,9 +93,6 @@ VideoColorSpaceInit VideoColorSpaceInternal::ToColorSpaceInit() const { return init; }; -static Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraData( - const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer); - VideoDecoderConfigInternal::VideoDecoderConfigInternal( const nsAString& aCodec, Maybe<uint32_t>&& aCodedHeight, Maybe<uint32_t>&& aCodedWidth, Maybe<VideoColorSpaceInternal>&& aColorSpace, @@ -129,7 +122,7 @@ UniquePtr<VideoDecoderConfigInternal> VideoDecoderConfigInternal::Create( Maybe<RefPtr<MediaByteBuffer>> description; if (aConfig.mDescription.WasPassed()) { - auto rv = GetExtraData(aConfig.mDescription.Value()); + auto rv = GetExtraDataFromArrayBuffer(aConfig.mDescription.Value()); if (rv.isErr()) { // Invalid description data. LOGE( "Failed to create VideoDecoderConfigInternal due to invalid " @@ -168,12 +161,10 @@ nsString VideoDecoderConfigInternal::ToString() const { if (mColorSpace.isSome()) { rv.AppendPrintf("colorspace %s", "todo"); } - if (mDescription.isSome()) { + if (mDescription.isSome() && mDescription.value()) { rv.AppendPrintf("extradata: %zu bytes", mDescription.value()->Length()); } - rv.AppendPrintf( - "hw accel: %s", - HardwareAccelerationValues::GetString(mHardwareAcceleration).data()); + rv.AppendPrintf("hw accel: %s", GetEnumString(mHardwareAcceleration).get()); if (mOptimizeForLatency.isSome()) { rv.AppendPrintf("optimize for latency: %s", mOptimizeForLatency.value() ? "true" : "false"); @@ -217,24 +208,6 @@ static nsTArray<nsCString> GuessMIMETypes(const MIMECreateParam& aParam) { return types; } -static bool IsSupportedCodec(const nsAString& aCodec) { - // H265 is unsupported. - if (!IsAV1CodecString(aCodec) && !IsVP9CodecString(aCodec) && - !IsVP8CodecString(aCodec) && !IsH264CodecString(aCodec)) { - return false; - } - - // Gecko allows codec string starts with vp9 or av1 but Webcodecs requires to - // starts with av01 and vp09. - // https://www.w3.org/TR/webcodecs-codec-registry/#video-codec-registry - if (StringBeginsWith(aCodec, u"vp9"_ns) || - StringBeginsWith(aCodec, u"av1"_ns)) { - return false; - } - - return true; -} - // https://w3c.github.io/webcodecs/#check-configuration-support template <typename Config> static bool CanDecode(const Config& aConfig) { @@ -243,12 +216,7 @@ static bool CanDecode(const Config& aConfig) { if (IsOnAndroid()) { return false; } - if (!IsSupportedCodec(param.mParsedCodec)) { - return false; - } - if (IsOnMacOS() && IsH264CodecString(param.mParsedCodec) && - !StaticPrefs::dom_media_webcodecs_force_osx_h264_enabled()) { - // This will be fixed in Bug 1846796. + if (!IsSupportedVideoCodec(param.mParsedCodec)) { return false; } // TODO: Instead of calling CanHandleContainerType with the guessed the @@ -284,18 +252,9 @@ static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo( return {}; } -static Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraData( - const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) { - RefPtr<MediaByteBuffer> data = MakeRefPtr<MediaByteBuffer>(); - if (!AppendTypedArrayDataTo(aBuffer, *data)) { - return Err(NS_ERROR_OUT_OF_MEMORY); - } - return data->Length() > 0 ? data : nullptr; -} - static Result<Ok, nsresult> CloneConfiguration( RootedDictionary<VideoDecoderConfig>& aDest, JSContext* aCx, - const VideoDecoderConfig& aConfig) { + const VideoDecoderConfig& aConfig, ErrorResult& aRv) { DebugOnly<nsCString> str; MOZ_ASSERT(VideoDecoderTraits::Validate(aConfig, str)); @@ -312,7 +271,7 @@ static Result<Ok, nsresult> CloneConfiguration( if (aConfig.mDescription.WasPassed()) { aDest.mDescription.Construct(); MOZ_TRY(CloneBuffer(aCx, aDest.mDescription.Value(), - aConfig.mDescription.Value())); + aConfig.mDescription.Value(), aRv)); } if (aConfig.mDisplayAspectHeight.WasPassed()) { aDest.mDisplayAspectHeight.Construct(aConfig.mDisplayAspectHeight.Value()); @@ -334,8 +293,10 @@ static Maybe<VideoPixelFormat> GuessPixelFormat(layers::Image* aImage) { // DMABUFSurfaceImage? if (aImage->AsPlanarYCbCrImage() || aImage->AsNVImage()) { const ImageUtils imageUtils(aImage); + Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat(); Maybe<VideoPixelFormat> f = - ImageBitmapFormatToVideoPixelFormat(imageUtils.GetFormat()); + format.isSome() ? ImageBitmapFormatToVideoPixelFormat(format.value()) + : Nothing(); // ImageBitmapFormat cannot distinguish YUV420 or YUV420A. bool hasAlpha = aImage->AsPlanarYCbCrImage() && @@ -391,8 +352,6 @@ static VideoColorSpaceInternal GuessColorSpace( // Make an educated guess based on the coefficients. colorSpace.mPrimaries = colorSpace.mMatrix.map([](const auto& aMatrix) { switch (aMatrix) { - case VideoMatrixCoefficients::EndGuard_: - MOZ_CRASH("This should not happen"); case VideoMatrixCoefficients::Bt2020_ncl: return VideoColorPrimaries::Bt2020; case VideoMatrixCoefficients::Rgb: @@ -529,9 +488,6 @@ static VideoColorSpaceInternal GuessColorSpace(layers::Image* aImage) { case VideoMatrixCoefficients::Bt2020_ncl: colorSpace.mPrimaries = Some(VideoColorPrimaries::Bt2020); break; - case VideoMatrixCoefficients::EndGuard_: - MOZ_ASSERT_UNREACHABLE("bad enum value"); - break; }; } } @@ -794,6 +750,21 @@ bool VideoDecoderTraits::Validate(const VideoDecoderConfig& aConfig, return false; } + bool detached = + aConfig.mDescription.WasPassed() && + (aConfig.mDescription.Value().IsArrayBuffer() + ? JS::ArrayBuffer::fromObject( + aConfig.mDescription.Value().GetAsArrayBuffer().Obj()) + .isDetached() + : JS::ArrayBufferView::fromObject( + aConfig.mDescription.Value().GetAsArrayBufferView().Obj()) + .isDetached()); + + if (detached) { + LOGE("description is detached."); + return false; + } + return true; } @@ -828,9 +799,7 @@ VideoDecoder::VideoDecoder(nsIGlobalObject* aParent, LOG("VideoDecoder %p ctor", this); } -VideoDecoder::~VideoDecoder() { - LOG("VideoDecoder %p dtor", this); -} +VideoDecoder::~VideoDecoder() { LOG("VideoDecoder %p dtor", this); } JSObject* VideoDecoder::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { @@ -877,21 +846,17 @@ already_AddRefed<Promise> VideoDecoder::IsConfigSupported( nsCString errorMessage; if (!VideoDecoderTraits::Validate(aConfig, errorMessage)) { p->MaybeRejectWithTypeError(nsPrintfCString( - "VideoDecoderConfig is invalid: %s", errorMessage.get())); + "IsConfigSupported: config is invalid: %s", errorMessage.get())); return p.forget(); } - // TODO: Move the following works to another thread to unblock the current - // thread, as what spec suggests. - RootedDictionary<VideoDecoderConfig> config(aGlobal.Context()); - auto r = CloneConfiguration(config, aGlobal.Context(), aConfig); + auto r = CloneConfiguration(config, aGlobal.Context(), aConfig, aRv); if (r.isErr()) { - nsresult e = r.unwrapErr(); - LOGE("Failed to clone VideoDecoderConfig. Error: 0x%08" PRIx32, - static_cast<uint32_t>(e)); - p->MaybeRejectWithTypeError("Failed to clone VideoDecoderConfig"); - aRv.Throw(e); + // This can only be an OOM: all members to clone are known to be valid + // because this is check by ::Validate above. + MOZ_ASSERT(r.inspectErr() == NS_ERROR_OUT_OF_MEMORY && + aRv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)); return p.forget(); } diff --git a/dom/media/webcodecs/VideoEncoder.cpp b/dom/media/webcodecs/VideoEncoder.cpp index 0e71417cb0..f593f70c77 100644 --- a/dom/media/webcodecs/VideoEncoder.cpp +++ b/dom/media/webcodecs/VideoEncoder.cpp @@ -101,8 +101,7 @@ VideoEncoderConfigInternal::VideoEncoderConfigInternal( mBitrateMode(aConfig.mBitrateMode), mLatencyMode(aConfig.mLatencyMode), mContentHint(aConfig.mContentHint), - mAvc(aConfig.mAvc) { -} + mAvc(aConfig.mAvc) {} VideoEncoderConfigInternal::VideoEncoderConfigInternal( const VideoEncoderConfig& aConfig) @@ -119,8 +118,7 @@ VideoEncoderConfigInternal::VideoEncoderConfigInternal( mBitrateMode(aConfig.mBitrateMode), mLatencyMode(aConfig.mLatencyMode), mContentHint(OptionalToMaybe(aConfig.mContentHint)), - mAvc(OptionalToMaybe(aConfig.mAvc)) { -} + mAvc(OptionalToMaybe(aConfig.mAvc)) {} nsString VideoEncoderConfigInternal::ToString() const { nsString rv; @@ -137,26 +135,20 @@ nsString VideoEncoderConfigInternal::ToString() const { if (mFramerate.isSome()) { rv.AppendPrintf(", %lfHz", mFramerate.value()); } - rv.AppendPrintf( - " hw: %s", - HardwareAccelerationValues::GetString(mHardwareAcceleration).data()); - rv.AppendPrintf(", alpha: %s", AlphaOptionValues::GetString(mAlpha).data()); + rv.AppendPrintf(" hw: %s", GetEnumString(mHardwareAcceleration).get()); + rv.AppendPrintf(", alpha: %s", GetEnumString(mAlpha).get()); if (mScalabilityMode.isSome()) { rv.AppendPrintf(", scalability mode: %s", NS_ConvertUTF16toUTF8(mScalabilityMode.value()).get()); } - rv.AppendPrintf( - ", bitrate mode: %s", - VideoEncoderBitrateModeValues::GetString(mBitrateMode).data()); - rv.AppendPrintf(", latency mode: %s", - LatencyModeValues::GetString(mLatencyMode).data()); + rv.AppendPrintf(", bitrate mode: %s", GetEnumString(mBitrateMode).get()); + rv.AppendPrintf(", latency mode: %s", GetEnumString(mLatencyMode).get()); if (mContentHint.isSome()) { rv.AppendPrintf(", content hint: %s", NS_ConvertUTF16toUTF8(mContentHint.value()).get()); } if (mAvc.isSome()) { - rv.AppendPrintf(", avc-specific: %s", - AvcBitstreamFormatValues::GetString(mAvc->mFormat).data()); + rv.AppendPrintf(", avc-specific: %s", GetEnumString(mAvc->mFormat).get()); } return rv; @@ -238,43 +230,57 @@ EncoderConfig VideoEncoderConfigInternal::ToEncoderConfig() const { if (ExtractH264CodecDetails(mCodec, profile, constraints, level)) { if (profile == H264_PROFILE_BASE || profile == H264_PROFILE_MAIN || profile == H264_PROFILE_EXTENDED || profile == H264_PROFILE_HIGH) { - specific.emplace( - H264Specific(static_cast<H264_PROFILE>(profile), static_cast<H264_LEVEL>(level), format)); + specific.emplace(H264Specific(static_cast<H264_PROFILE>(profile), + static_cast<H264_LEVEL>(level), format)); } } } - // Only for vp9, not vp8 - if (codecType == CodecType::VP9) { - uint8_t profile, level, bitdepth, chromasubsampling; - mozilla::VideoColorSpace colorspace; - DebugOnly<bool> rv = ExtractVPXCodecDetails( - mCodec, profile, level, bitdepth, chromasubsampling, colorspace); -#ifdef DEBUG - if (!rv) { - LOGE("Error extracting VPX codec details, non fatal"); - } -#endif - specific.emplace(VP9Specific()); - } + uint8_t numTemporalLayers = 1; MediaDataEncoder::ScalabilityMode scalabilityMode; if (mScalabilityMode) { if (mScalabilityMode->EqualsLiteral("L1T2")) { scalabilityMode = MediaDataEncoder::ScalabilityMode::L1T2; + numTemporalLayers = 2; } else if (mScalabilityMode->EqualsLiteral("L1T3")) { scalabilityMode = MediaDataEncoder::ScalabilityMode::L1T3; + numTemporalLayers = 3; } else { scalabilityMode = MediaDataEncoder::ScalabilityMode::None; } } else { scalabilityMode = MediaDataEncoder::ScalabilityMode::None; } - return EncoderConfig( - codecType, {mWidth, mHeight}, usage, ImageBitmapFormat::RGBA32, ImageBitmapFormat::RGBA32, - AssertedCast<uint8_t>(mFramerate.refOr(0.f)), 0, mBitrate.refOr(0), - mBitrateMode == VideoEncoderBitrateMode::Constant - ? MediaDataEncoder::BitrateMode::Constant - : MediaDataEncoder::BitrateMode::Variable, - hwPref, scalabilityMode, specific); + // Only for vp9, not vp8 + if (codecType == CodecType::VP9) { + uint8_t profile, level, bitdepth, chromasubsampling; + mozilla::VideoColorSpace colorspace; + DebugOnly<bool> rv = ExtractVPXCodecDetails( + mCodec, profile, level, bitdepth, chromasubsampling, colorspace); +#ifdef DEBUG + if (!rv) { + LOGE("Error extracting VPX codec details, non fatal"); + } +#endif + specific.emplace(VP9Specific( + VPXComplexity::Normal, /* Complexity */ + true, /* Resilience */ + numTemporalLayers, /* Number of temporal layers */ + true, /* Denoising */ + false, /* Auto resize */ + false, /* Frame dropping */ + true, /* Adaptive Qp */ + 1, /* Number of spatial layers */ + false /* Flexible */ + )); + } + return EncoderConfig(codecType, {mWidth, mHeight}, usage, + ImageBitmapFormat::RGBA32, ImageBitmapFormat::RGBA32, + AssertedCast<uint8_t>(mFramerate.refOr(0.f)), 0, + mBitrate.refOr(0), + mBitrateMode == VideoEncoderBitrateMode::Constant + ? MediaDataEncoder::BitrateMode::Constant + : MediaDataEncoder::BitrateMode::Variable, + hwPref, scalabilityMode, specific); } already_AddRefed<WebCodecsConfigurationChangeList> VideoEncoderConfigInternal::Diff( @@ -326,27 +332,6 @@ VideoEncoderConfigInternal::Diff( return list.forget(); } -/* - * The followings are helpers for VideoEncoder methods - */ -static bool IsEncodeSupportedCodec(const nsAString& aCodec) { - LOG("IsEncodeSupported: %s", NS_ConvertUTF16toUTF8(aCodec).get()); - if (!IsVP9CodecString(aCodec) && !IsVP8CodecString(aCodec) && - !IsH264CodecString(aCodec) && !IsAV1CodecString(aCodec)) { - return false; - } - - // Gecko allows codec string starts with vp9 or av1 but Webcodecs requires to - // starts with av01 and vp09. - // https://www.w3.org/TR/webcodecs-codec-registry/#video-codec-registry - if (StringBeginsWith(aCodec, u"vp9"_ns) || - StringBeginsWith(aCodec, u"av1"_ns)) { - return false; - } - - return true; -} - // https://w3c.github.io/webcodecs/#check-configuration-support static bool CanEncode(const RefPtr<VideoEncoderConfigInternal>& aConfig) { auto parsedCodecString = @@ -355,7 +340,7 @@ static bool CanEncode(const RefPtr<VideoEncoderConfigInternal>& aConfig) { if (IsOnAndroid()) { return false; } - if (!IsEncodeSupportedCodec(parsedCodecString)) { + if (!IsSupportedVideoCodec(parsedCodecString)) { return false; } diff --git a/dom/media/webcodecs/VideoFrame.cpp b/dom/media/webcodecs/VideoFrame.cpp index 602bc95c29..bea5611e39 100644 --- a/dom/media/webcodecs/VideoFrame.cpp +++ b/dom/media/webcodecs/VideoFrame.cpp @@ -598,8 +598,6 @@ static bool IsYUVFormat(const VideoPixelFormat& aFormat) { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return false; - case VideoPixelFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE("unsupported format"); } return false; } @@ -641,8 +639,6 @@ static VideoColorSpaceInit PickColorSpace( colorSpace.mPrimaries.SetValue(VideoColorPrimaries::Bt709); colorSpace.mTransfer.SetValue(VideoTransferCharacteristics::Iec61966_2_1); break; - case VideoPixelFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE("unsupported format"); } return colorSpace; @@ -881,8 +877,6 @@ static Result<RefPtr<layers::Image>, nsCString> CreateImageFromBuffer( case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return CreateRGBAImageFromBuffer(aFormat, aSize, aBuffer); - case VideoPixelFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE("unsupported format"); } return Err(nsCString("Invalid image format")); } @@ -1390,8 +1384,9 @@ already_AddRefed<VideoFrame> VideoFrame::Constructor( } const ImageUtils imageUtils(image); + Maybe<dom::ImageBitmapFormat> f = imageUtils.GetFormat(); Maybe<VideoPixelFormat> format = - ImageBitmapFormatToVideoPixelFormat(imageUtils.GetFormat()); + f.isSome() ? ImageBitmapFormatToVideoPixelFormat(f.value()) : Nothing(); // TODO: Retrive/infer the duration, and colorspace. auto r = InitializeFrameFromOtherFrame( @@ -1789,15 +1784,13 @@ nsCString VideoFrame::ToString() const { return rv; } - rv.AppendPrintf( - "VideoFrame ts: %" PRId64 - ", %s, coded[%dx%d] visible[%dx%d], display[%dx%d] color: %s", - mTimestamp, - dom::VideoPixelFormatValues::GetString(mResource->mFormat->PixelFormat()) - .data(), - mCodedSize.width, mCodedSize.height, mVisibleRect.width, - mVisibleRect.height, mDisplaySize.width, mDisplaySize.height, - ColorSpaceInitToString(mColorSpace).get()); + rv.AppendPrintf("VideoFrame ts: %" PRId64 + ", %s, coded[%dx%d] visible[%dx%d], display[%dx%d] color: %s", + mTimestamp, + dom::GetEnumString(mResource->mFormat->PixelFormat()).get(), + mCodedSize.width, mCodedSize.height, mVisibleRect.width, + mVisibleRect.height, mDisplaySize.width, mDisplaySize.height, + ColorSpaceInitToString(mColorSpace).get()); if (mDuration) { rv.AppendPrintf(" dur: %" PRId64, mDuration.value()); @@ -2032,8 +2025,6 @@ gfx::SurfaceFormat VideoFrame::Format::ToSurfaceFormat() const { case VideoPixelFormat::BGRX: format = gfx::SurfaceFormat::B8G8R8X8; break; - case VideoPixelFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE("unsupported format"); } return format; } @@ -2056,8 +2047,6 @@ void VideoFrame::Format::MakeOpaque() { case VideoPixelFormat::RGBX: case VideoPixelFormat::BGRX: return; - case VideoPixelFormat::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported format"); } @@ -2077,8 +2066,6 @@ nsTArray<VideoFrame::Format::Plane> VideoFrame::Format::Planes() const { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return {Plane::RGBA}; - case VideoPixelFormat::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported format"); return {}; @@ -2125,8 +2112,6 @@ uint32_t VideoFrame::Format::SampleBytes(const Plane& aPlane) const { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return 4; // 8 bits/sample, 32 bits/pixel - case VideoPixelFormat::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported format"); return 0; @@ -2154,7 +2139,6 @@ gfx::IntSize VideoFrame::Format::SampleSize(const Plane& aPlane) const { case VideoPixelFormat::RGBX: case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: - case VideoPixelFormat::EndGuard_: MOZ_ASSERT_UNREACHABLE("invalid format"); return {0, 0}; } @@ -2177,8 +2161,6 @@ bool VideoFrame::Format::IsValidSize(const gfx::IntSize& aSize) const { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return true; - case VideoPixelFormat::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported format"); return false; @@ -2205,8 +2187,6 @@ size_t VideoFrame::Format::SampleCount(const gfx::IntSize& aSize) const { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return (count * 4).value(); - case VideoPixelFormat::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported format"); @@ -2252,8 +2232,6 @@ uint32_t VideoFrame::Resource::Stride(const Format::Plane& aPlane) const { case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: return (width * mFormat->SampleBytes(aPlane)).value(); - case VideoPixelFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE("invalid format"); } return 0; case Format::Plane::U: // and UV @@ -2269,7 +2247,6 @@ uint32_t VideoFrame::Resource::Stride(const Format::Plane& aPlane) const { case VideoPixelFormat::RGBX: case VideoPixelFormat::BGRA: case VideoPixelFormat::BGRX: - case VideoPixelFormat::EndGuard_: MOZ_ASSERT_UNREACHABLE("invalid format"); } return 0; diff --git a/dom/media/webcodecs/WebCodecsUtils.cpp b/dom/media/webcodecs/WebCodecsUtils.cpp index 1e03f616db..3507aba440 100644 --- a/dom/media/webcodecs/WebCodecsUtils.cpp +++ b/dom/media/webcodecs/WebCodecsUtils.cpp @@ -37,7 +37,7 @@ std::atomic<WebCodecsId> sNextId = 0; namespace mozilla::dom { /* - * The followings are helpers for VideoDecoder methods + * The followings are helpers for AudioDecoder and VideoDecoder methods */ nsTArray<nsCString> GuessContainers(const nsAString& aCodec) { @@ -57,6 +57,29 @@ nsTArray<nsCString> GuessContainers(const nsAString& aCodec) { return {"mp4"_ns, "3gpp"_ns, "3gpp2"_ns, "3gp2"_ns}; } + if (IsAACCodecString(aCodec)) { + return {"adts"_ns, "mp4"_ns}; + } + + if (aCodec.EqualsLiteral("vorbis") || aCodec.EqualsLiteral("opus")) { + return {"ogg"_ns}; + } + + if (aCodec.EqualsLiteral("flac")) { + return {"flac"_ns}; + } + + if (aCodec.EqualsLiteral("mp3")) { + return {"mp3"_ns}; + } + + if (aCodec.EqualsLiteral("ulaw") || aCodec.EqualsLiteral("alaw") || + aCodec.EqualsLiteral("pcm-u8") || aCodec.EqualsLiteral("pcm-s16") || + aCodec.EqualsLiteral("pcm-s24") || aCodec.EqualsLiteral("pcm-s32") || + aCodec.EqualsLiteral("pcm-f32")) { + return {"x-wav"_ns}; + } + return {}; } @@ -106,7 +129,8 @@ static std::tuple<JS::ArrayBufferOrView, size_t, size_t> GetArrayBufferInfo( Result<Ok, nsresult> CloneBuffer( JSContext* aCx, OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDest, - const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aSrc) { + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aSrc, + ErrorResult& aRv) { std::tuple<JS::ArrayBufferOrView, size_t, size_t> info = GetArrayBufferInfo(aCx, aSrc); JS::Rooted<JS::ArrayBufferOrView> abov(aCx); @@ -121,6 +145,8 @@ Result<Ok, nsresult> CloneBuffer( JS::Rooted<JSObject*> cloned(aCx, JS::ArrayBufferClone(aCx, obj, offset, len)); if (NS_WARN_IF(!cloned)) { + aRv.MightThrowJSException(); + aRv.StealExceptionFromJSContext(aCx); return Err(NS_ERROR_OUT_OF_MEMORY); } @@ -151,8 +177,6 @@ gfx::YUVColorSpace ToColorSpace(VideoMatrixCoefficients aMatrix) { return gfx::YUVColorSpace::BT601; case VideoMatrixCoefficients::Bt2020_ncl: return gfx::YUVColorSpace::BT2020; - case VideoMatrixCoefficients::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported VideoMatrixCoefficients"); return gfx::YUVColorSpace::Default; @@ -171,8 +195,7 @@ gfx::TransferFunction ToTransferFunction( case VideoTransferCharacteristics::Hlg: return gfx::TransferFunction::HLG; case VideoTransferCharacteristics::Linear: - case VideoTransferCharacteristics::EndGuard_: - break; + return gfx::TransferFunction::Default; } MOZ_ASSERT_UNREACHABLE("unsupported VideoTransferCharacteristics"); return gfx::TransferFunction::Default; @@ -190,8 +213,6 @@ gfx::ColorSpace2 ToPrimaries(VideoColorPrimaries aPrimaries) { return gfx::ColorSpace2::BT2020; case VideoColorPrimaries::Smpte432: return gfx::ColorSpace2::DISPLAY_P3; - case VideoColorPrimaries::EndGuard_: - break; } MOZ_ASSERT_UNREACHABLE("unsupported VideoTransferCharacteristics"); return gfx::ColorSpace2::UNKNOWN; @@ -300,13 +321,6 @@ Maybe<VideoPixelFormat> ImageBitmapFormatToVideoPixelFormat( return Nothing(); } -Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraDataFromArrayBuffer( - const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) { - RefPtr<MediaByteBuffer> data = MakeRefPtr<MediaByteBuffer>(); - Unused << AppendTypedArrayDataTo(aBuffer, *data); - return data->Length() > 0 ? data : nullptr; -} - bool IsOnAndroid() { #if defined(ANDROID) return true; @@ -364,15 +378,13 @@ struct ConfigurationChangeToString { } nsCString operator()( const HardwareAccelerationChange& aHardwareAccelerationChange) { - return nsPrintfCString("HW acceleration: %s", - dom::HardwareAccelerationValues::GetString( - aHardwareAccelerationChange.get()) - .data()); + return nsPrintfCString( + "HW acceleration: %s", + dom::GetEnumString(aHardwareAccelerationChange.get()).get()); } nsCString operator()(const AlphaChange& aAlphaChange) { - return nsPrintfCString( - "Alpha: %s", - dom::AlphaOptionValues::GetString(aAlphaChange.get()).data()); + return nsPrintfCString("Alpha: %s", + dom::GetEnumString(aAlphaChange.get()).get()); } nsCString operator()(const ScalabilityModeChange& aScalabilityModeChange) { if (aScalabilityModeChange.get().isNothing()) { @@ -383,15 +395,12 @@ struct ConfigurationChangeToString { NS_ConvertUTF16toUTF8(aScalabilityModeChange.get().value()).get()); } nsCString operator()(const BitrateModeChange& aBitrateModeChange) { - return nsPrintfCString( - "Bitrate mode: %s", - dom::VideoEncoderBitrateModeValues::GetString(aBitrateModeChange.get()) - .data()); + return nsPrintfCString("Bitrate mode: %s", + dom::GetEnumString(aBitrateModeChange.get()).get()); } nsCString operator()(const LatencyModeChange& aLatencyModeChange) { - return nsPrintfCString( - "Latency mode: %s", - dom::LatencyModeValues::GetString(aLatencyModeChange.get()).data()); + return nsPrintfCString("Latency mode: %s", + dom::GetEnumString(aLatencyModeChange.get()).get()); } nsCString operator()(const ContentHintChange& aContentHintChange) { return nsPrintfCString("Content hint: %s", @@ -489,9 +498,6 @@ WebCodecsConfigurationChangeList::ToPEMChangeList() const { return rv.forget(); } -#define ENUM_TO_STRING(enumType, enumValue) \ - enumType##Values::GetString(enumValue).data() - nsCString ColorSpaceInitToString( const dom::VideoColorSpaceInit& aColorSpaceInit) { nsCString rv("VideoColorSpace"); @@ -502,18 +508,15 @@ nsCString ColorSpaceInitToString( } if (!aColorSpaceInit.mMatrix.IsNull()) { rv.AppendPrintf(" matrix: %s", - ENUM_TO_STRING(dom::VideoMatrixCoefficients, - aColorSpaceInit.mMatrix.Value())); + GetEnumString(aColorSpaceInit.mMatrix.Value()).get()); } if (!aColorSpaceInit.mTransfer.IsNull()) { rv.AppendPrintf(" transfer: %s", - ENUM_TO_STRING(dom::VideoTransferCharacteristics, - aColorSpaceInit.mTransfer.Value())); + GetEnumString(aColorSpaceInit.mTransfer.Value()).get()); } if (!aColorSpaceInit.mPrimaries.IsNull()) { rv.AppendPrintf(" primaries: %s", - ENUM_TO_STRING(dom::VideoColorPrimaries, - aColorSpaceInit.mPrimaries.Value())); + GetEnumString(aColorSpaceInit.mPrimaries.Value()).get()); } return rv; @@ -575,4 +578,32 @@ nsString ConfigToString(const VideoDecoderConfig& aConfig) { return internal->ToString(); } -}; // namespace mozilla::dom +Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraDataFromArrayBuffer( + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) { + RefPtr<MediaByteBuffer> data = MakeRefPtr<MediaByteBuffer>(); + if (!AppendTypedArrayDataTo(aBuffer, *data)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + return data->Length() > 0 ? data : nullptr; +} + +bool IsSupportedVideoCodec(const nsAString& aCodec) { + LOG("IsSupportedVideoCodec: %s", NS_ConvertUTF16toUTF8(aCodec).get()); + // The only codec string accepted for vp8 is "vp8" + if (!IsVP9CodecString(aCodec) && !IsH264CodecString(aCodec) && + !IsAV1CodecString(aCodec) && !aCodec.EqualsLiteral("vp8")) { + return false; + } + + // Gecko allows codec string starts with vp9 or av1 but Webcodecs requires to + // starts with av01 and vp09. + // https://w3c.github.io/webcodecs/codec_registry.html. + if (StringBeginsWith(aCodec, u"vp9"_ns) || + StringBeginsWith(aCodec, u"av1"_ns)) { + return false; + } + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/media/webcodecs/WebCodecsUtils.h b/dom/media/webcodecs/WebCodecsUtils.h index 7c0e6b6bbc..196c57421d 100644 --- a/dom/media/webcodecs/WebCodecsUtils.h +++ b/dom/media/webcodecs/WebCodecsUtils.h @@ -8,6 +8,7 @@ #define MOZILLA_DOM_WEBCODECS_WEBCODECSUTILS_H #include "ErrorList.h" +#include "MediaData.h" #include "js/TypeDecls.h" #include "mozilla/Maybe.h" #include "mozilla/MozPromise.h" @@ -86,7 +87,11 @@ Nullable<T> MaybeToNullable(const Maybe<T>& aOptional) { Result<Ok, nsresult> CloneBuffer( JSContext* aCx, OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDest, - const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aSrc); + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aSrc, + ErrorResult& aRv); + +Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraDataFromArrayBuffer( + const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer); /* * The following are utilities to convert between VideoColorSpace values to @@ -232,6 +237,8 @@ Maybe<CodecType> CodecStringToCodecType(const nsAString& aCodecString); nsString ConfigToString(const VideoDecoderConfig& aConfig); +bool IsSupportedVideoCodec(const nsAString& aCodec); + } // namespace dom } // namespace mozilla diff --git a/dom/media/webcodecs/moz.build b/dom/media/webcodecs/moz.build index 267a822286..ddb5aad5cb 100644 --- a/dom/media/webcodecs/moz.build +++ b/dom/media/webcodecs/moz.build @@ -21,8 +21,11 @@ EXPORTS.mozilla += [ ] EXPORTS.mozilla.dom += [ + "AudioData.h", + "AudioDecoder.h", "DecoderTemplate.h", "DecoderTypes.h", + "EncodedAudioChunk.h", "EncodedVideoChunk.h", "EncoderAgent.h", "EncoderTemplate.h", @@ -35,8 +38,11 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ + "AudioData.cpp", + "AudioDecoder.cpp", "DecoderAgent.cpp", "DecoderTemplate.cpp", + "EncodedAudioChunk.cpp", "EncodedVideoChunk.cpp", "EncoderAgent.cpp", "EncoderTemplate.cpp", diff --git a/dom/media/webrtc/MediaEngineFake.cpp b/dom/media/webrtc/MediaEngineFake.cpp index 8c69ec5e47..b14d80ffbb 100644 --- a/dom/media/webrtc/MediaEngineFake.cpp +++ b/dom/media/webrtc/MediaEngineFake.cpp @@ -136,10 +136,8 @@ MediaEngineFakeVideoSource::MediaEngineFakeVideoSource() mSettings->mHeight.Construct( int32_t(MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT)); mSettings->mFrameRate.Construct(double(MediaEnginePrefs::DEFAULT_VIDEO_FPS)); - mSettings->mFacingMode.Construct( - NS_ConvertASCIItoUTF16(dom::VideoFacingModeEnumValues::strings - [uint8_t(VideoFacingModeEnum::Environment)] - .value)); + mSettings->mFacingMode.Construct(NS_ConvertASCIItoUTF16( + dom::GetEnumString(VideoFacingModeEnum::Environment))); } nsString MediaEngineFakeVideoSource::GetGroupId() { diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp index a6648b68c7..dbb5e7ff70 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -108,8 +108,7 @@ MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource( Maybe<VideoFacingModeEnum> facingMode = GetFacingMode(mMediaDevice->mRawName); if (facingMode.isSome()) { - NS_ConvertASCIItoUTF16 facingString( - dom::VideoFacingModeEnumValues::GetString(*facingMode)); + NS_ConvertASCIItoUTF16 facingString(dom::GetEnumString(*facingMode)); mSettings->mFacingMode.Construct(facingString); mFacingMode.emplace(facingString); } diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp index ee45bb0317..9d778d411d 100644 --- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -105,9 +105,9 @@ nsresult MediaEngineWebRTCMicrophoneSource::EvaluateSettings( prefs.mChannels = c.mChannelCount.Get(std::min(prefs.mChannels, maxChannels)); prefs.mChannels = std::max(1, std::min(prefs.mChannels, maxChannels)); - LOG("Audio config: agc: %d, noise: %d, channels: %d", - prefs.mAgcOn ? prefs.mAgc : -1, prefs.mNoiseOn ? prefs.mNoise : -1, - prefs.mChannels); + LOG("Mic source %p Audio config: aec: %s, agc: %s, noise: %s, channels: %d", + this, prefs.mAecOn ? "on" : "off", prefs.mAgcOn ? "on" : "off", + prefs.mNoiseOn ? "on" : "off", prefs.mChannels); *aOutPrefs = prefs; diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h index 61e0ed85ea..a948bf1499 100644 --- a/dom/media/webrtc/MediaTrackConstraints.h +++ b/dom/media/webrtc/MediaTrackConstraints.h @@ -20,17 +20,6 @@ namespace mozilla { class LocalMediaDevice; class MediaDevice; -template <class EnumValuesStrings, class Enum> -static Enum StringToEnum(const EnumValuesStrings& aStrings, - const nsAString& aValue, Enum aDefaultValue) { - for (size_t i = 0; aStrings[i].value; i++) { - if (aValue.EqualsASCII(aStrings[i].value)) { - return Enum(i); - } - } - return aDefaultValue; -} - // Helper classes for orthogonal constraints without interdependencies. // Instead of constraining values, constrain the constraints themselves. class NormalizedConstraintSet { diff --git a/dom/media/webrtc/MediaTransportChild.h b/dom/media/webrtc/MediaTransportChild.h index ef08fc55aa..0f9f5e8714 100644 --- a/dom/media/webrtc/MediaTransportChild.h +++ b/dom/media/webrtc/MediaTransportChild.h @@ -21,8 +21,10 @@ class MediaTransportChild : public dom::PMediaTransportChild { mozilla::ipc::IPCResult RecvOnCandidate(const string& transportId, const CandidateInfo& candidateInfo); mozilla::ipc::IPCResult RecvOnAlpnNegotiated(const string& alpn); - mozilla::ipc::IPCResult RecvOnGatheringStateChange(const int& state); - mozilla::ipc::IPCResult RecvOnConnectionStateChange(const int& state); + mozilla::ipc::IPCResult RecvOnGatheringStateChange(const string& transportId, + const int& state); + mozilla::ipc::IPCResult RecvOnConnectionStateChange(const string& transportId, + const int& state); mozilla::ipc::IPCResult RecvOnPacketReceived(const string& transportId, const MediaPacket& packet); mozilla::ipc::IPCResult RecvOnEncryptedSending(const string& transportId, diff --git a/dom/media/webrtc/PMediaTransport.ipdl b/dom/media/webrtc/PMediaTransport.ipdl index e7501ed814..ae6580f768 100644 --- a/dom/media/webrtc/PMediaTransport.ipdl +++ b/dom/media/webrtc/PMediaTransport.ipdl @@ -88,8 +88,8 @@ parent: child: async OnCandidate(string transportId, CandidateInfo candidateInfo); async OnAlpnNegotiated(string alpn); - async OnGatheringStateChange(int state); - async OnConnectionStateChange(int state); + async OnGatheringStateChange(string transportId, int state); + async OnConnectionStateChange(string transportId, int state); async OnPacketReceived(string transportId, MediaPacket packet); async OnEncryptedSending(string transportId, MediaPacket packet); async OnStateChange(string transportId, int state); diff --git a/dom/media/webrtc/WebrtcGlobal.h b/dom/media/webrtc/WebrtcGlobal.h index d610ee5d10..e9ec2ced51 100644 --- a/dom/media/webrtc/WebrtcGlobal.h +++ b/dom/media/webrtc/WebrtcGlobal.h @@ -8,6 +8,7 @@ #include "WebrtcIPCTraits.h" #include "ipc/EnumSerializer.h" #include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/RTCDataChannelBinding.h" #include "mozilla/dom/RTCStatsReportBinding.h" @@ -62,30 +63,22 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::RTCStatsType> - : public ContiguousEnumSerializer<mozilla::dom::RTCStatsType, - mozilla::dom::RTCStatsType::Codec, - mozilla::dom::RTCStatsType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RTCStatsType> {}; template <> struct ParamTraits<mozilla::dom::RTCStatsIceCandidatePairState> - : public ContiguousEnumSerializer< - mozilla::dom::RTCStatsIceCandidatePairState, - mozilla::dom::RTCStatsIceCandidatePairState::Frozen, - mozilla::dom::RTCStatsIceCandidatePairState::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RTCStatsIceCandidatePairState> {}; template <> struct ParamTraits<mozilla::dom::RTCIceCandidateType> - : public ContiguousEnumSerializer< - mozilla::dom::RTCIceCandidateType, - mozilla::dom::RTCIceCandidateType::Host, - mozilla::dom::RTCIceCandidateType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RTCIceCandidateType> {}; template <> struct ParamTraits<mozilla::dom::RTCBundlePolicy> - : public ContiguousEnumSerializer< - mozilla::dom::RTCBundlePolicy, - mozilla::dom::RTCBundlePolicy::Balanced, - mozilla::dom::RTCBundlePolicy::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RTCBundlePolicy> { +}; DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServerInternal, mUrls, mCredentialProvided, mUserNameProvided); @@ -218,10 +211,8 @@ DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCDataChannelStats, mId, template <> struct ParamTraits<mozilla::dom::RTCDataChannelState> - : public ContiguousEnumSerializer< - mozilla::dom::RTCDataChannelState, - mozilla::dom::RTCDataChannelState::Connecting, - mozilla::dom::RTCDataChannelState::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RTCDataChannelState> {}; DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCCodecStats, mTimestamp, mType, mId, mPayloadType, mCodecType, @@ -230,9 +221,7 @@ DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCCodecStats, mTimestamp, template <> struct ParamTraits<mozilla::dom::RTCCodecType> - : public ContiguousEnumSerializer<mozilla::dom::RTCCodecType, - mozilla::dom::RTCCodecType::Encode, - mozilla::dom::RTCCodecType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::RTCCodecType> {}; } // namespace IPC #endif // _WEBRTC_GLOBAL_H_ diff --git a/dom/media/webrtc/WebrtcIPCTraits.h b/dom/media/webrtc/WebrtcIPCTraits.h index b076745608..5b526da67f 100644 --- a/dom/media/webrtc/WebrtcIPCTraits.h +++ b/dom/media/webrtc/WebrtcIPCTraits.h @@ -9,6 +9,7 @@ #include "ipc/IPCMessageUtils.h" #include "ipc/IPCMessageUtilsSpecializations.h" #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/RTCConfigurationBinding.h" #include "mozilla/media/webrtc/WebrtcGlobal.h" #include "mozilla/dom/CandidateInfo.h" @@ -62,17 +63,15 @@ struct ParamTraits<mozilla::dom::OwningStringOrStringSequence> { } }; -template <typename T> -struct WebidlEnumSerializer - : public ContiguousEnumSerializer<T, T(0), T::EndGuard_> {}; - template <> struct ParamTraits<mozilla::dom::RTCIceCredentialType> - : public WebidlEnumSerializer<mozilla::dom::RTCIceCredentialType> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RTCIceCredentialType> {}; template <> struct ParamTraits<mozilla::dom::RTCIceTransportPolicy> - : public WebidlEnumSerializer<mozilla::dom::RTCIceTransportPolicy> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::RTCIceTransportPolicy> {}; DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServer, mCredential, mCredentialType, mUrl, mUrls, mUsername) diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp index 3ee95f36f6..cf95ef41d6 100644 --- a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp @@ -156,9 +156,9 @@ class MediaTransportHandlerSTS : public MediaTransportHandler, using MediaTransportHandler::OnRtcpStateChange; using MediaTransportHandler::OnStateChange; - void OnGatheringStateChange(NrIceCtx* aIceCtx, - NrIceCtx::GatheringState aState); - void OnConnectionStateChange(NrIceCtx* aIceCtx, + void OnGatheringStateChange(const std::string& aTransportId, + NrIceMediaStream::GatheringState aState); + void OnConnectionStateChange(NrIceMediaStream* aIceStream, NrIceCtx::ConnectionState aState); void OnCandidateFound(NrIceMediaStream* aStream, const std::string& aCandidate, @@ -589,8 +589,6 @@ void MediaTransportHandlerSTS::CreateIceCtx(const std::string& aName) { __func__); } - mIceCtx->SignalGatheringStateChange.connect( - this, &MediaTransportHandlerSTS::OnGatheringStateChange); mIceCtx->SignalConnectionStateChange.connect( this, &MediaTransportHandlerSTS::OnConnectionStateChange); @@ -784,6 +782,8 @@ void MediaTransportHandlerSTS::EnsureProvisionalTransport( stream->SignalCandidate.connect( this, &MediaTransportHandlerSTS::OnCandidateFound); + stream->SignalGatheringStateChange.connect( + this, &MediaTransportHandlerSTS::OnGatheringStateChange); } // Begins an ICE restart if this stream has a different ufrag/pwd @@ -1181,31 +1181,31 @@ void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) { } void MediaTransportHandler::OnGatheringStateChange( - dom::RTCIceGatheringState aState) { + const std::string& aTransportId, dom::RTCIceGathererState aState) { if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { mCallbackThread->Dispatch( // This is being called from sigslot, which does not hold a strong ref. WrapRunnable(this, &MediaTransportHandler::OnGatheringStateChange, - aState), + aTransportId, aState), NS_DISPATCH_NORMAL); return; } - SignalGatheringStateChange(aState); + SignalGatheringStateChange(aTransportId, aState); } void MediaTransportHandler::OnConnectionStateChange( - dom::RTCIceConnectionState aState) { + const std::string& aTransportId, dom::RTCIceTransportState aState) { if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { mCallbackThread->Dispatch( // This is being called from sigslot, which does not hold a strong ref. WrapRunnable(this, &MediaTransportHandler::OnConnectionStateChange, - aState), + aTransportId, aState), NS_DISPATCH_NORMAL); return; } - SignalConnectionStateChange(aState); + SignalConnectionStateChange(aTransportId, aState); } void MediaTransportHandler::OnPacketReceived(const std::string& aTransportId, @@ -1583,48 +1583,48 @@ RefPtr<TransportFlow> MediaTransportHandlerSTS::CreateTransportFlow( return flow; } -static mozilla::dom::RTCIceGatheringState toDomIceGatheringState( - NrIceCtx::GatheringState aState) { +static mozilla::dom::RTCIceGathererState toDomIceGathererState( + NrIceMediaStream::GatheringState aState) { switch (aState) { - case NrIceCtx::ICE_CTX_GATHER_INIT: - return dom::RTCIceGatheringState::New; - case NrIceCtx::ICE_CTX_GATHER_STARTED: - return dom::RTCIceGatheringState::Gathering; - case NrIceCtx::ICE_CTX_GATHER_COMPLETE: - return dom::RTCIceGatheringState::Complete; + case NrIceMediaStream::ICE_STREAM_GATHER_INIT: + return dom::RTCIceGathererState::New; + case NrIceMediaStream::ICE_STREAM_GATHER_STARTED: + return dom::RTCIceGathererState::Gathering; + case NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE: + return dom::RTCIceGathererState::Complete; } MOZ_CRASH(); } void MediaTransportHandlerSTS::OnGatheringStateChange( - NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) { - OnGatheringStateChange(toDomIceGatheringState(aState)); + const std::string& aTransportId, NrIceMediaStream::GatheringState aState) { + OnGatheringStateChange(aTransportId, toDomIceGathererState(aState)); } -static mozilla::dom::RTCIceConnectionState toDomIceConnectionState( +static mozilla::dom::RTCIceTransportState toDomIceTransportState( NrIceCtx::ConnectionState aState) { switch (aState) { case NrIceCtx::ICE_CTX_INIT: - return dom::RTCIceConnectionState::New; + return dom::RTCIceTransportState::New; case NrIceCtx::ICE_CTX_CHECKING: - return dom::RTCIceConnectionState::Checking; + return dom::RTCIceTransportState::Checking; case NrIceCtx::ICE_CTX_CONNECTED: - return dom::RTCIceConnectionState::Connected; + return dom::RTCIceTransportState::Connected; case NrIceCtx::ICE_CTX_COMPLETED: - return dom::RTCIceConnectionState::Completed; + return dom::RTCIceTransportState::Completed; case NrIceCtx::ICE_CTX_FAILED: - return dom::RTCIceConnectionState::Failed; + return dom::RTCIceTransportState::Failed; case NrIceCtx::ICE_CTX_DISCONNECTED: - return dom::RTCIceConnectionState::Disconnected; + return dom::RTCIceTransportState::Disconnected; case NrIceCtx::ICE_CTX_CLOSED: - return dom::RTCIceConnectionState::Closed; + return dom::RTCIceTransportState::Closed; } MOZ_CRASH(); } void MediaTransportHandlerSTS::OnConnectionStateChange( - NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) { - OnConnectionStateChange(toDomIceConnectionState(aState)); + NrIceMediaStream* aIceStream, NrIceCtx::ConnectionState aState) { + OnConnectionStateChange(aIceStream->GetId(), toDomIceTransportState(aState)); } // The stuff below here will eventually go into the MediaTransportChild class diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h index a776cb6fd7..100eff019e 100644 --- a/dom/media/webrtc/jsapi/MediaTransportHandler.h +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h @@ -12,7 +12,8 @@ #include "transport/dtlsidentity.h" // For DtlsDigest #include "mozilla/dom/RTCPeerConnectionBinding.h" #include "mozilla/dom/RTCConfigurationBinding.h" -#include "transport/nricectx.h" // Need some enums +#include "mozilla/dom/RTCIceTransportBinding.h" // RTCIceTransportState +#include "transport/nricectx.h" // Need some enums #include "common/CandidateInfo.h" #include "transport/nr_socket_proxy_config.h" #include "RTCStatsReport.h" @@ -122,8 +123,10 @@ class MediaTransportHandler { sigslot::signal2<const std::string&, const CandidateInfo&> SignalCandidate; sigslot::signal2<const std::string&, bool> SignalAlpnNegotiated; - sigslot::signal1<dom::RTCIceGatheringState> SignalGatheringStateChange; - sigslot::signal1<dom::RTCIceConnectionState> SignalConnectionStateChange; + sigslot::signal2<const std::string&, dom::RTCIceGathererState> + SignalGatheringStateChange; + sigslot::signal2<const std::string&, dom::RTCIceTransportState> + SignalConnectionStateChange; sigslot::signal2<const std::string&, const MediaPacket&> SignalPacketReceived; sigslot::signal2<const std::string&, const MediaPacket&> @@ -142,8 +145,10 @@ class MediaTransportHandler { void OnCandidate(const std::string& aTransportId, const CandidateInfo& aCandidateInfo); void OnAlpnNegotiated(const std::string& aAlpn); - void OnGatheringStateChange(dom::RTCIceGatheringState aState); - void OnConnectionStateChange(dom::RTCIceConnectionState aState); + void OnGatheringStateChange(const std::string& aTransportId, + dom::RTCIceGathererState aState); + void OnConnectionStateChange(const std::string& aTransportId, + dom::RTCIceTransportState aState); void OnPacketReceived(const std::string& aTransportId, const MediaPacket& aPacket); void OnEncryptedSending(const std::string& aTransportId, diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp index 847e9fd3de..4d13ae0ac0 100644 --- a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp @@ -406,21 +406,21 @@ mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated( } mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange( - const int& state) { + const string& transportId, const int& state) { MutexAutoLock lock(mMutex); if (mUser) { - mUser->OnGatheringStateChange( - static_cast<dom::RTCIceGatheringState>(state)); + mUser->OnGatheringStateChange(transportId, + static_cast<dom::RTCIceGathererState>(state)); } return ipc::IPCResult::Ok(); } mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange( - const int& state) { + const string& transportId, const int& state) { MutexAutoLock lock(mMutex); if (mUser) { mUser->OnConnectionStateChange( - static_cast<dom::RTCIceConnectionState>(state)); + transportId, static_cast<dom::RTCIceTransportState>(state)); } return ipc::IPCResult::Ok(); } diff --git a/dom/media/webrtc/jsapi/MediaTransportParent.cpp b/dom/media/webrtc/jsapi/MediaTransportParent.cpp index 31d0b9f30f..4bbd75b41c 100644 --- a/dom/media/webrtc/jsapi/MediaTransportParent.cpp +++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp @@ -49,14 +49,16 @@ class MediaTransportParent::Impl : public sigslot::has_slots<> { NS_ENSURE_TRUE_VOID(mParent->SendOnAlpnNegotiated(aAlpn)); } - void OnGatheringStateChange(dom::RTCIceGatheringState aState) { - NS_ENSURE_TRUE_VOID( - mParent->SendOnGatheringStateChange(static_cast<int>(aState))); + void OnGatheringStateChange(const std::string& aTransportId, + dom::RTCIceGathererState aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnGatheringStateChange( + aTransportId, static_cast<int>(aState))); } - void OnConnectionStateChange(dom::RTCIceConnectionState aState) { - NS_ENSURE_TRUE_VOID( - mParent->SendOnConnectionStateChange(static_cast<int>(aState))); + void OnConnectionStateChange(const std::string& aTransportId, + dom::RTCIceTransportState aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnConnectionStateChange( + aTransportId, static_cast<int>(aState))); } void OnPacketReceived(const std::string& aTransportId, diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp index 985100153a..9afa7e5dd2 100644 --- a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp @@ -85,6 +85,7 @@ #include "mozilla/dom/RTCCertificate.h" #include "mozilla/dom/RTCSctpTransportBinding.h" // RTCSctpTransportState #include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState +#include "mozilla/dom/RTCIceTransportBinding.h" // RTCIceTransportState #include "mozilla/dom/RTCRtpReceiverBinding.h" #include "mozilla/dom/RTCRtpSenderBinding.h" #include "mozilla/dom/RTCStatsReportBinding.h" @@ -249,19 +250,41 @@ void PeerConnectionAutoTimer::UnregisterConnection(bool aContainedAV) { bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; } +// There is not presently an implementation of these for nsTHashMap :( +inline void ImplCycleCollectionUnlink( + PeerConnectionImpl::RTCDtlsTransportMap& aMap) { + for (auto& tableEntry : aMap) { + ImplCycleCollectionUnlink(*tableEntry.GetModifiableData()); + } + aMap.Clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + PeerConnectionImpl::RTCDtlsTransportMap& aMap, const char* aName, + uint32_t aFlags = 0) { + for (auto& tableEntry : aMap) { + ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(), + aName, aFlags); + } +} + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl) tmp->Close(); tmp->BreakCycles(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mPCObserver, mWindow, mCertificate, - mSTSThread, mReceiveStreams, mOperations, - mSctpTransport, mKungFuDeathGrip) + NS_IMPL_CYCLE_COLLECTION_UNLINK( + mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams, + mOperations, mTransportIdToRTCDtlsTransport, mSctpTransport, + mLastStableSctpTransport, mLastStableSctpDtlsTransport, mKungFuDeathGrip) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl) NS_IMPL_CYCLE_COLLECTION_TRAVERSE( mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams, - mOperations, mTransceivers, mSctpTransport, mKungFuDeathGrip) + mOperations, mTransceivers, mTransportIdToRTCDtlsTransport, + mSctpTransport, mLastStableSctpTransport, mLastStableSctpDtlsTransport, + mKungFuDeathGrip) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl) @@ -849,6 +872,7 @@ nsresult PeerConnectionImpl::GetDatachannelParameters( if (!datachannelTransceiver || !datachannelTransceiver->mTransport.mComponents || + !datachannelTransceiver->mTransport.mDtls || !datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) { return NS_ERROR_FAILURE; } @@ -1061,7 +1085,7 @@ bool PeerConnectionImpl::CreatedSender(const dom::RTCRtpSender& aSender) const { return aSender.IsMyPc(this); } -nsresult PeerConnectionImpl::InitializeDataChannel() { +nsresult PeerConnectionImpl::MaybeInitializeDataChannel() { PC_AUTO_ENTER_API_CALL(false); CSFLogDebug(LOGTAG, "%s", __FUNCTION__); @@ -1440,6 +1464,16 @@ void PeerConnectionImpl::UpdateNegotiationNeeded() { })); } +RefPtr<dom::RTCRtpTransceiver> PeerConnectionImpl::GetTransceiver( + const std::string& aTransceiverId) { + for (const auto& transceiver : mTransceivers) { + if (transceiver->GetJsepTransceiverId() == aTransceiverId) { + return transceiver; + } + } + return nullptr; +} + void PeerConnectionImpl::NotifyDataChannel( already_AddRefed<DataChannel> aChannel) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); @@ -1637,7 +1671,6 @@ JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) { return kJsepSdpAnswer; case dom::RTCSdpType::Rollback: return kJsepSdpRollback; - case dom::RTCSdpType::EndGuard_:; } MOZ_CRASH("Nonexistent dom::RTCSdpType"); @@ -1989,10 +2022,14 @@ nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) { void PeerConnectionImpl::OnDtlsStateChange(const std::string& aTransportId, TransportLayer::State aState) { - auto it = mTransportIdToRTCDtlsTransport.find(aTransportId); - if (it != mTransportIdToRTCDtlsTransport.end()) { - it->second->UpdateState(aState); + nsCString key(aTransportId.data(), aTransportId.size()); + RefPtr<RTCDtlsTransport> dtlsTransport = + mTransportIdToRTCDtlsTransport.Get(key); + if (!dtlsTransport) { + return; } + + dtlsTransport->UpdateState(aState); // Whenever the state of an RTCDtlsTransport changes or when the [[IsClosed]] // slot turns true, the user agent MUST update the connection state by // queueing a task that runs the following steps: @@ -2024,9 +2061,9 @@ RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const { // Would use a bitset, but that requires lots of static_cast<size_t> // Oh well. std::set<RTCDtlsTransportState> statesFound; - for (const auto& [id, dtlsTransport] : mTransportIdToRTCDtlsTransport) { - Unused << id; - statesFound.insert(dtlsTransport->State()); + std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports()); + for (const auto& transport : transports) { + statesFound.insert(transport->State()); } // failed The previous state doesn't apply, and either @@ -2075,9 +2112,9 @@ RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const { bool PeerConnectionImpl::UpdateConnectionState() { auto newState = GetNewConnectionState(); if (newState != mConnectionState) { - CSFLogDebug(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__, - static_cast<int>(mConnectionState), static_cast<int>(newState), - this); + CSFLogInfo(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__, + static_cast<int>(mConnectionState), static_cast<int>(newState), + this); mConnectionState = newState; if (mConnectionState != RTCPeerConnectionState::Closed) { return true; @@ -2569,7 +2606,7 @@ PeerConnectionImpl::Close() { transceiver->Close(); } - mTransportIdToRTCDtlsTransport.clear(); + mTransportIdToRTCDtlsTransport.Clear(); mQueuedIceCtxOperations.clear(); @@ -2964,18 +3001,25 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( InvalidateLastReturnedParameters(); } + if (aSdpType == dom::RTCSdpType::Offer && + mSignalingState == RTCSignalingState::Stable) { + // If description is of type "offer" and + // connection.[[SignalingState]] is "stable" then for each + // transceiver in connection's set of transceivers, run the following + // steps: + SaveStateForRollback(); + } + // Section 4.4.1.5 Set the RTCSessionDescription: if (aSdpType == dom::RTCSdpType::Rollback) { // - step 4.5.10, type is rollback - RollbackRTCDtlsTransports(); + RestoreStateForRollback(); } else if (!(aRemote && aSdpType == dom::RTCSdpType::Offer)) { // - step 4.5.9 type is not rollback // - step 4.5.9.1 when remote is false // - step 4.5.9.2.13 when remote is true, type answer or pranswer // More simply: not rollback, and not for remote offers. - bool markAsStable = aSdpType == dom::RTCSdpType::Offer && - mSignalingState == RTCSignalingState::Stable; - UpdateRTCDtlsTransports(markAsStable); + UpdateRTCDtlsTransports(); } // Did we just apply a local description? @@ -2992,11 +3036,6 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( } if (mJsepSession->GetState() == kJsepStateStable) { - if (aSdpType != dom::RTCSdpType::Rollback) { - // We need this initted for UpdateTransports - InitializeDataChannel(); - } - // If we're rolling back a local offer, we might need to remove some // transports, and stomp some MediaPipeline setup, but nothing further // needs to be done. @@ -3071,6 +3110,10 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( // Spec does not actually tell us to do this, but that is probably a // spec bug. // https://github.com/w3c/webrtc-pc/issues/2817 + bool gatheringStateChanged = UpdateIceGatheringState(); + + bool iceConnectionStateChanged = UpdateIceConnectionState(); + bool connectionStateChanged = UpdateConnectionState(); // This only gets populated for remote descriptions @@ -3104,6 +3147,16 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( pcObserver->OnStateChange(PCObserverStateType::SignalingState, jrv); } + if (gatheringStateChanged) { + pcObserver->OnStateChange(PCObserverStateType::IceGatheringState, + jrv); + } + + if (iceConnectionStateChanged) { + pcObserver->OnStateChange(PCObserverStateType::IceConnectionState, + jrv); + } + if (connectionStateChanged) { pcObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv); } @@ -3290,61 +3343,162 @@ void PeerConnectionImpl::SendLocalIceCandidateToContent( } void PeerConnectionImpl::IceConnectionStateChange( - dom::RTCIceConnectionState domState) { + const std::string& aTransportId, dom::RTCIceTransportState domState) { + // If connection.[[IsClosed]] is true, abort these steps. PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); - CSFLogDebug(LOGTAG, "%s: %d -> %d", __FUNCTION__, - static_cast<int>(mIceConnectionState), - static_cast<int>(domState)); + CSFLogDebug(LOGTAG, "IceConnectionStateChange: %s %d (%p)", + aTransportId.c_str(), static_cast<int>(domState), this); - if (domState == mIceConnectionState) { - // no work to be done since the states are the same. - // this can happen during ICE rollback situations. + // Let transport be the RTCIceTransport whose state is changing. + nsCString key(aTransportId.data(), aTransportId.size()); + RefPtr<RTCDtlsTransport> dtlsTransport = + mTransportIdToRTCDtlsTransport.Get(key); + if (!dtlsTransport) { return; } + RefPtr<RTCIceTransport> transport = dtlsTransport->IceTransport(); - mIceConnectionState = domState; + if (domState == RTCIceTransportState::Closed) { + mTransportIdToRTCDtlsTransport.Remove(key); + } - // Would be nice if we had a means of converting one of these dom enums - // to a string that wasn't almost as much text as this switch statement... - switch (mIceConnectionState) { - case RTCIceConnectionState::New: - STAMP_TIMECARD(mTimeCard, "Ice state: new"); - break; - case RTCIceConnectionState::Checking: - // For telemetry - mIceStartTime = TimeStamp::Now(); - STAMP_TIMECARD(mTimeCard, "Ice state: checking"); - break; - case RTCIceConnectionState::Connected: - STAMP_TIMECARD(mTimeCard, "Ice state: connected"); - StartCallTelem(); - break; - case RTCIceConnectionState::Completed: - STAMP_TIMECARD(mTimeCard, "Ice state: completed"); - break; - case RTCIceConnectionState::Failed: - STAMP_TIMECARD(mTimeCard, "Ice state: failed"); - break; - case RTCIceConnectionState::Disconnected: - STAMP_TIMECARD(mTimeCard, "Ice state: disconnected"); - break; - case RTCIceConnectionState::Closed: - STAMP_TIMECARD(mTimeCard, "Ice state: closed"); - break; - default: - MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!"); + // Let selectedCandidatePairChanged be false. + // TODO(bug 1307994) + + // Let transportIceConnectionStateChanged be false. + bool transportIceConnectionStateChanged = false; + + // Let connectionIceConnectionStateChanged be false. + bool connectionIceConnectionStateChanged = false; + + // Let connectionStateChanged be false. + bool connectionStateChanged = false; + + if (transport->State() == domState) { + return; + } + + // If transport's RTCIceTransportState was changed, run the following steps: + + // Set transport.[[IceTransportState]] to the new indicated + // RTCIceTransportState. + transport->SetState(domState); + + // Set transportIceConnectionStateChanged to true. + transportIceConnectionStateChanged = true; + + // Set connection.[[IceConnectionState]] to the value of deriving a new state + // value as described by the RTCIceConnectionState enum. + if (UpdateIceConnectionState()) { + // If connection.[[IceConnectionState]] changed in the previous step, set + // connectionIceConnectionStateChanged to true. + connectionIceConnectionStateChanged = true; + } + + // Set connection.[[ConnectionState]] to the value of deriving a new state + // value as described by the RTCPeerConnectionState enum. + if (UpdateConnectionState()) { + // If connection.[[ConnectionState]] changed in the previous step, set + // connectionStateChanged to true. + connectionStateChanged = true; + } + + // If selectedCandidatePairChanged is true, fire an event named + // selectedcandidatepairchange at transport. + // TODO(bug 1307994) + + // If transportIceConnectionStateChanged is true, fire an event named + // statechange at transport. + if (transportIceConnectionStateChanged) { + transport->FireStateChangeEvent(); } - bool connectionStateChanged = UpdateConnectionState(); WrappableJSErrorResult rv; RefPtr<PeerConnectionObserver> pcObserver(mPCObserver); - pcObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv); + + // If connectionIceConnectionStateChanged is true, fire an event named + // iceconnectionstatechange at connection. + if (connectionIceConnectionStateChanged) { + pcObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv); + } + + // If connectionStateChanged is true, fire an event named + // connectionstatechange at connection. if (connectionStateChanged) { pcObserver->OnStateChange(PCObserverStateType::ConnectionState, rv); } } +RTCIceConnectionState PeerConnectionImpl::GetNewIceConnectionState() const { + // closed The RTCPeerConnection object's [[IsClosed]] slot is true. + if (IsClosed()) { + return RTCIceConnectionState::Closed; + } + + // Would use a bitset, but that requires lots of static_cast<size_t> + // Oh well. + std::set<RTCIceTransportState> statesFound; + std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports()); + for (const auto& transport : transports) { + RefPtr<dom::RTCIceTransport> iceTransport = transport->IceTransport(); + CSFLogWarn(LOGTAG, "GetNewIceConnectionState: %p %d", iceTransport.get(), + static_cast<int>(iceTransport->State())); + statesFound.insert(iceTransport->State()); + } + + // failed None of the previous states apply and any RTCIceTransports are + // in the "failed" state. + if (statesFound.count(RTCIceTransportState::Failed)) { + return RTCIceConnectionState::Failed; + } + + // disconnected None of the previous states apply and any + // RTCIceTransports are in the "disconnected" state. + if (statesFound.count(RTCIceTransportState::Disconnected)) { + return RTCIceConnectionState::Disconnected; + } + + // new None of the previous states apply and all RTCIceTransports are + // in the "new" or "closed" state, or there are no transports. + if (!statesFound.count(RTCIceTransportState::Checking) && + !statesFound.count(RTCIceTransportState::Completed) && + !statesFound.count(RTCIceTransportState::Connected)) { + return RTCIceConnectionState::New; + } + + // checking None of the previous states apply and any RTCIceTransports are + // in the "new" or "checking" state. + if (statesFound.count(RTCIceTransportState::New) || + statesFound.count(RTCIceTransportState::Checking)) { + return RTCIceConnectionState::Checking; + } + + // completed None of the previous states apply and all RTCIceTransports are + // in the "completed" or "closed" state. + if (!statesFound.count(RTCIceTransportState::Connected)) { + return RTCIceConnectionState::Completed; + } + + // connected None of the previous states apply. + return RTCIceConnectionState::Connected; +} + +bool PeerConnectionImpl::UpdateIceConnectionState() { + auto newState = GetNewIceConnectionState(); + if (newState != mIceConnectionState) { + CSFLogInfo(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__, + static_cast<int>(mIceConnectionState), + static_cast<int>(newState), this); + mIceConnectionState = newState; + if (mIceConnectionState != RTCIceConnectionState::Closed) { + return true; + } + } + + return false; +} + void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId, const CandidateInfo& aCandidateInfo) { if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) { @@ -3378,18 +3532,82 @@ void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId, } void PeerConnectionImpl::IceGatheringStateChange( - dom::RTCIceGatheringState state) { + const std::string& aTransportId, dom::RTCIceGathererState state) { + // If connection.[[IsClosed]] is true, abort these steps. PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); - CSFLogDebug(LOGTAG, "%s %d", __FUNCTION__, static_cast<int>(state)); - if (mIceGatheringState == state) { + CSFLogWarn(LOGTAG, "IceGatheringStateChange: %s %d (%p)", + aTransportId.c_str(), static_cast<int>(state), this); + + // Let transport be the RTCIceTransport for which candidate gathering + // began/finished. + nsCString key(aTransportId.data(), aTransportId.size()); + RefPtr<RTCDtlsTransport> dtlsTransport = + mTransportIdToRTCDtlsTransport.Get(key); + if (!dtlsTransport) { + return; + } + RefPtr<RTCIceTransport> transport = dtlsTransport->IceTransport(); + + if (transport->GatheringState() == state) { return; } - mIceGatheringState = state; + // Set transport.[[IceGathererState]] to gathering. + // or + // Set transport.[[IceGathererState]] to complete. + transport->SetGatheringState(state); + + // Set connection.[[IceGatheringState]] to the value of deriving a new state + // value as described by the RTCIceGatheringState enum. + // + // Let connectionIceGatheringStateChanged be true if + // connection.[[IceGatheringState]] changed in the previous step, otherwise + // false. + bool gatheringStateChanged = UpdateIceGatheringState(); + + // Do not read or modify state beyond this point. + + // Fire an event named gatheringstatechange at transport. + transport->FireGatheringStateChangeEvent(); + + // If connectionIceGatheringStateChanged is true, fire an event named + // icegatheringstatechange at connection. + if (gatheringStateChanged) { + // NOTE: If we're in the "complete" case, our JS code will fire a null + // icecandidate event after firing the icegatheringstatechange event. + // Fire an event named icecandidate using the RTCPeerConnectionIceEvent + // interface with the candidate attribute set to null at connection. + JSErrorResult rv; + mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv); + } +} + +bool PeerConnectionImpl::UpdateIceGatheringState() { + // If connection.[[IsClosed]] is true, abort these steps. + if (IsClosed()) { + return false; + } + + // Let newState be the value of deriving a new state value as + // described by the RTCIceGatheringState enum. + auto newState = GetNewIceGatheringState(); + + // If connection.[[IceGatheringState]] is equal to newState, abort + // these steps. + if (newState == mIceGatheringState) { + return false; + } + + CSFLogInfo(LOGTAG, "UpdateIceGatheringState: %d -> %d (%p)", + static_cast<int>(mIceGatheringState), static_cast<int>(newState), + this); + // Set connection.[[IceGatheringState]] to newState. + mIceGatheringState = newState; - // Would be nice if we had a means of converting one of these dom enums - // to a string that wasn't almost as much text as this switch statement... + // Would be nice if we had a means of converting one of these dom + // enums to a string that wasn't almost as much text as this switch + // statement... switch (mIceGatheringState) { case RTCIceGatheringState::New: STAMP_TIMECARD(mTimeCard, "Ice gathering state: new"); @@ -3404,8 +3622,42 @@ void PeerConnectionImpl::IceGatheringStateChange( MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!"); } - JSErrorResult rv; - mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv); + return true; +} + +RTCIceGatheringState PeerConnectionImpl::GetNewIceGatheringState() const { + // new Any of the RTCIceTransports are in the "new" gathering state + // and none of the transports are in the "gathering" state, or there are no + // transports. + + // NOTE! This derives the RTCIce**Gathering**State from the individual + // RTCIce**Gatherer**State of the transports. These are different enums. + // But they have exactly the same values, in the same order. + // ¯\_(ツ)_/¯ + bool foundComplete = false; + std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports()); + for (const auto& transport : transports) { + RefPtr<dom::RTCIceTransport> iceTransport = transport->IceTransport(); + switch (iceTransport->GatheringState()) { + case RTCIceGathererState::New: + break; + case RTCIceGathererState::Gathering: + // gathering Any of the RTCIceTransports are in the "gathering" + // state. + return RTCIceGatheringState::Gathering; + case RTCIceGathererState::Complete: + foundComplete = true; + break; + } + } + + if (!foundComplete) { + return RTCIceGatheringState::New; + } + + // This could change depending on the outcome in + // https://github.com/w3c/webrtc-pc/issues/2914 + return RTCIceGatheringState::Complete; } void PeerConnectionImpl::UpdateDefaultCandidate( @@ -3878,11 +4130,8 @@ void PeerConnectionImpl::StunAddrsHandler::OnStunAddrsAvailable( pcw.impl()->mStunAddrs = addrs.Clone(); pcw.impl()->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; pcw.impl()->FlushIceCtxOperationQueueIfReady(); - // If parent process returns 0 STUN addresses, change ICE connection - // state to failed. - if (!pcw.impl()->mStunAddrs.Length()) { - pcw.impl()->IceConnectionStateChange(dom::RTCIceConnectionState::Failed); - } + // If this fails, ICE cannot succeed, but we need to still go through the + // motions. } void PeerConnectionImpl::InitLocalAddrs() { @@ -3952,109 +4201,120 @@ void PeerConnectionImpl::EnsureTransports(const JsepSession& aSession) { GatherIfReady(); } -void PeerConnectionImpl::UpdateRTCDtlsTransports(bool aMarkAsStable) { +void PeerConnectionImpl::UpdateRTCDtlsTransports() { + // We use mDataConnection below, make sure it is initted if necessary + MaybeInitializeDataChannel(); + + // Make sure that the SCTP transport is unset if we do not see a DataChannel. + // We'll restore this if we do see a DataChannel. + RefPtr<dom::RTCSctpTransport> oldSctp = mSctpTransport.forget(); + mJsepSession->ForEachTransceiver( - [this, self = RefPtr<PeerConnectionImpl>(this)]( - const JsepTransceiver& jsepTransceiver) { + [this, self = RefPtr<PeerConnectionImpl>(this), + oldSctp](const JsepTransceiver& jsepTransceiver) { std::string transportId = jsepTransceiver.mTransport.mTransportId; - if (transportId.empty()) { - return; + RefPtr<dom::RTCDtlsTransport> dtlsTransport; + if (!transportId.empty()) { + nsCString key(transportId.data(), transportId.size()); + dtlsTransport = mTransportIdToRTCDtlsTransport.GetOrInsertNew( + key, GetParentObject()); } - if (!mTransportIdToRTCDtlsTransport.count(transportId)) { - mTransportIdToRTCDtlsTransport.emplace( - transportId, new RTCDtlsTransport(GetParentObject())); + + if (jsepTransceiver.GetMediaType() == SdpMediaSection::kApplication) { + // Spec says we only update the RTCSctpTransport when negotiation + // completes. This is probably a spec bug. + // https://github.com/w3c/webrtc-pc/issues/2898 + if (!dtlsTransport || !mDataConnection) { + return; + } + + // Why on earth does the spec use a floating point for this? + double maxMessageSize = + static_cast<double>(mDataConnection->GetMaxMessageSize()); + Nullable<uint16_t> maxChannels; + + if (!oldSctp) { + mSctpTransport = new RTCSctpTransport( + GetParentObject(), *dtlsTransport, maxMessageSize, maxChannels); + } else { + // Restore the SCTP transport we had before this function was called + oldSctp->SetTransport(*dtlsTransport); + oldSctp->SetMaxMessageSize(maxMessageSize); + oldSctp->SetMaxChannels(maxChannels); + mSctpTransport = oldSctp; + } + } else { + RefPtr<dom::RTCRtpTransceiver> domTransceiver = + GetTransceiver(jsepTransceiver.GetUuid()); + if (domTransceiver) { + domTransceiver->SetDtlsTransport(dtlsTransport); + } } }); +} - for (auto& transceiver : mTransceivers) { - std::string transportId = transceiver->GetTransportId(); - if (transportId.empty()) { - continue; - } - if (mTransportIdToRTCDtlsTransport.count(transportId)) { - transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId], - aMarkAsStable); - } +void PeerConnectionImpl::SaveStateForRollback() { + // This could change depending on the outcome in + // https://github.com/w3c/webrtc-pc/issues/2899 + if (mSctpTransport) { + // We have to save both of these things, because the DTLS transport could + // change without the SCTP transport changing. + mLastStableSctpTransport = mSctpTransport; + mLastStableSctpDtlsTransport = mSctpTransport->Transport(); + } else { + mLastStableSctpTransport = nullptr; + mLastStableSctpDtlsTransport = nullptr; } - // Spec says we only update the RTCSctpTransport when negotiation completes + for (auto& transceiver : mTransceivers) { + transceiver->SaveStateForRollback(); + } } -void PeerConnectionImpl::RollbackRTCDtlsTransports() { +void PeerConnectionImpl::RestoreStateForRollback() { for (auto& transceiver : mTransceivers) { transceiver->RollbackToStableDtlsTransport(); } + + mSctpTransport = mLastStableSctpTransport; + if (mSctpTransport) { + mSctpTransport->SetTransport(*mLastStableSctpDtlsTransport); + } } -void PeerConnectionImpl::RemoveRTCDtlsTransportsExcept( - const std::set<std::string>& aTransportIds) { - for (auto iter = mTransportIdToRTCDtlsTransport.begin(); - iter != mTransportIdToRTCDtlsTransport.end();) { - if (!aTransportIds.count(iter->first)) { - iter = mTransportIdToRTCDtlsTransport.erase(iter); - } else { - ++iter; +std::set<RefPtr<dom::RTCDtlsTransport>> +PeerConnectionImpl::GetActiveTransports() const { + std::set<RefPtr<dom::RTCDtlsTransport>> result; + for (const auto& transceiver : mTransceivers) { + if (transceiver->GetDtlsTransport()) { + result.insert(transceiver->GetDtlsTransport()); } } + + if (mSctpTransport && mSctpTransport->Transport()) { + result.insert(mSctpTransport->Transport()); + } + return result; } nsresult PeerConnectionImpl::UpdateTransports(const JsepSession& aSession, const bool forceIceTcp) { std::set<std::string> finalTransports; - Maybe<std::string> sctpTransport; mJsepSession->ForEachTransceiver( [&, this, self = RefPtr<PeerConnectionImpl>(this)]( const JsepTransceiver& transceiver) { - if (transceiver.GetMediaType() == SdpMediaSection::kApplication && - transceiver.HasTransport()) { - sctpTransport = Some(transceiver.mTransport.mTransportId); - } - if (transceiver.HasOwnTransport()) { finalTransports.insert(transceiver.mTransport.mTransportId); UpdateTransport(transceiver, forceIceTcp); } }); - // clean up the unused RTCDtlsTransports - RemoveRTCDtlsTransportsExcept(finalTransports); - mTransportHandler->RemoveTransportsExcept(finalTransports); for (const auto& transceiverImpl : mTransceivers) { transceiverImpl->UpdateTransport(); } - if (sctpTransport.isSome()) { - auto it = mTransportIdToRTCDtlsTransport.find(*sctpTransport); - if (it == mTransportIdToRTCDtlsTransport.end()) { - // What? - MOZ_ASSERT(false); - return NS_ERROR_FAILURE; - } - if (!mDataConnection) { - // What? - MOZ_ASSERT(false); - return NS_ERROR_FAILURE; - } - RefPtr<RTCDtlsTransport> dtlsTransport = it->second; - // Why on earth does the spec use a floating point for this? - double maxMessageSize = - static_cast<double>(mDataConnection->GetMaxMessageSize()); - Nullable<uint16_t> maxChannels; - - if (!mSctpTransport) { - mSctpTransport = new RTCSctpTransport(GetParentObject(), *dtlsTransport, - maxMessageSize, maxChannels); - } else { - mSctpTransport->SetTransport(*dtlsTransport); - mSctpTransport->SetMaxMessageSize(maxMessageSize); - mSctpTransport->SetMaxChannels(maxChannels); - } - } else { - mSctpTransport = nullptr; - } - return NS_OK; } @@ -4141,6 +4401,9 @@ nsresult PeerConnectionImpl::UpdateMediaPipelines() { void PeerConnectionImpl::StartIceChecks(const JsepSession& aSession) { MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mJsepSession->GetState() == kJsepStateStable); + + auto transports = GetActiveTransports(); if (!mCanRegisterMDNSHostnamesDirectly) { for (auto& pair : mMDNSHostnamesToRegister) { @@ -4472,32 +4735,31 @@ std::string PeerConnectionImpl::GetTransportIdMatchingSendTrack( } void PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s( - dom::RTCIceGatheringState aState) { + const std::string& aTransportId, dom::RTCIceGathererState aState) { ASSERT_ON_THREAD(mSTSThread); - GetMainThreadSerialEventTarget()->Dispatch( NS_NewRunnableFunction(__func__, - [handle = mHandle, aState] { + [handle = mHandle, aTransportId, aState] { PeerConnectionWrapper wrapper(handle); if (wrapper.impl()) { wrapper.impl()->IceGatheringStateChange( - aState); + aTransportId, aState); } }), NS_DISPATCH_NORMAL); } void PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s( - dom::RTCIceConnectionState aState) { + const std::string& aTransportId, dom::RTCIceTransportState aState) { ASSERT_ON_THREAD(mSTSThread); GetMainThreadSerialEventTarget()->Dispatch( NS_NewRunnableFunction(__func__, - [handle = mHandle, aState] { + [handle = mHandle, aTransportId, aState] { PeerConnectionWrapper wrapper(handle); if (wrapper.impl()) { wrapper.impl()->IceConnectionStateChange( - aState); + aTransportId, aState); } }), NS_DISPATCH_NORMAL); diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h index 085658b206..d7b54ad721 100644 --- a/dom/media/webrtc/jsapi/PeerConnectionImpl.h +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h @@ -16,6 +16,7 @@ #include "nsPIDOMWindow.h" #include "nsIUUIDGenerator.h" #include "nsIThread.h" +#include "nsTHashSet.h" #include "mozilla/Mutex.h" #include "mozilla/Attributes.h" @@ -217,8 +218,10 @@ class PeerConnectionImpl final virtual const std::string& GetName(); // ICE events - void IceConnectionStateChange(dom::RTCIceConnectionState state); - void IceGatheringStateChange(dom::RTCIceGatheringState state); + void IceConnectionStateChange(const std::string& aTransportId, + dom::RTCIceTransportState state); + void IceGatheringStateChange(const std::string& aTransportId, + dom::RTCIceGathererState state); void OnCandidateFound(const std::string& aTransportId, const CandidateInfo& aCandidateInfo); void UpdateDefaultCandidate(const std::string& defaultAddr, @@ -411,7 +414,7 @@ class PeerConnectionImpl final void RecordEndOfCallTelemetry(); - nsresult InitializeDataChannel(); + nsresult MaybeInitializeDataChannel(); NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, CreateDataChannel, ErrorResult& rv, const nsAString& aLabel, @@ -481,6 +484,9 @@ class PeerConnectionImpl final aTransceiversOut = mTransceivers.Clone(); } + RefPtr<dom::RTCRtpTransceiver> GetTransceiver( + const std::string& aTransceiverId); + // Gets the RTC Signaling State of the JSEP session dom::RTCSignalingState GetSignalingState() const; @@ -499,6 +505,12 @@ class PeerConnectionImpl final dom::RTCPeerConnectionState GetNewConnectionState() const; // Returns whether we need to fire a state change event bool UpdateConnectionState(); + dom::RTCIceConnectionState GetNewIceConnectionState() const; + // Returns whether we need to fire a state change event + bool UpdateIceConnectionState(); + dom::RTCIceGatheringState GetNewIceGatheringState() const; + // Returns whether we need to fire a state change event + bool UpdateIceGatheringState(); // initialize telemetry for when calls start void StartCallTelem(); @@ -585,6 +597,9 @@ class PeerConnectionImpl final void BreakCycles(); + using RTCDtlsTransportMap = + nsTHashMap<nsCStringHashKey, RefPtr<dom::RTCDtlsTransport>>; + private: virtual ~PeerConnectionImpl(); PeerConnectionImpl(const PeerConnectionImpl& rhs); @@ -805,10 +820,10 @@ class PeerConnectionImpl final // Ensure ICE transports exist that we might need when offer/answer concludes void EnsureTransports(const JsepSession& aSession); - void UpdateRTCDtlsTransports(bool aMarkAsStable); - void RollbackRTCDtlsTransports(); - void RemoveRTCDtlsTransportsExcept( - const std::set<std::string>& aTransportIds); + void UpdateRTCDtlsTransports(); + void SaveStateForRollback(); + void RestoreStateForRollback(); + std::set<RefPtr<dom::RTCDtlsTransport>> GetActiveTransports() const; // Activate ICE transports at the conclusion of offer/answer, // or when rollback occurs. @@ -861,9 +876,12 @@ class PeerConnectionImpl final std::set<std::pair<std::string, std::string>> mLocalIceCredentialsToReplace; nsTArray<RefPtr<dom::RTCRtpTransceiver>> mTransceivers; - std::map<std::string, RefPtr<dom::RTCDtlsTransport>> - mTransportIdToRTCDtlsTransport; + RTCDtlsTransportMap mTransportIdToRTCDtlsTransport; RefPtr<dom::RTCSctpTransport> mSctpTransport; + // This is similar to [[LastStableStateSender/ReceiverTransport]], but for + // DataChannel. + RefPtr<dom::RTCSctpTransport> mLastStableSctpTransport; + RefPtr<dom::RTCDtlsTransport> mLastStableSctpDtlsTransport; // Used whenever we need to dispatch a runnable to STS to tweak something // on our ICE ctx, but are not ready to do so at the moment (eg; we are @@ -924,8 +942,10 @@ class PeerConnectionImpl final void ConnectSignals(); // ICE events - void IceGatheringStateChange_s(dom::RTCIceGatheringState aState); - void IceConnectionStateChange_s(dom::RTCIceConnectionState aState); + void IceGatheringStateChange_s(const std::string& aTransportId, + dom::RTCIceGathererState aState); + void IceConnectionStateChange_s(const std::string& aTransportId, + dom::RTCIceTransportState aState); void OnCandidateFound_s(const std::string& aTransportId, const CandidateInfo& aCandidateInfo); void AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested); diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp index 243f06d2f1..83e0aeee82 100644 --- a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp @@ -9,7 +9,8 @@ namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper, + mIceTransport) NS_IMPL_ADDREF_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) @@ -19,7 +20,9 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDtlsTransport) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) RTCDtlsTransport::RTCDtlsTransport(nsPIDOMWindowInner* aWindow) - : DOMEventTargetHelper(aWindow), mState(RTCDtlsTransportState::New) {} + : DOMEventTargetHelper(aWindow), + mState(RTCDtlsTransportState::New), + mIceTransport(new RTCIceTransport(aWindow)) {} JSObject* RTCDtlsTransport::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.h b/dom/media/webrtc/jsapi/RTCDtlsTransport.h index 3800502154..3e744b5112 100644 --- a/dom/media/webrtc/jsapi/RTCDtlsTransport.h +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.h @@ -6,6 +6,7 @@ #define _RTCDtlsTransport_h_ #include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/RTCIceTransport.h" #include "mozilla/RefPtr.h" #include "js/RootingAPI.h" #include "transport/transportlayer.h" @@ -30,6 +31,7 @@ class RTCDtlsTransport : public DOMEventTargetHelper { JS::Handle<JSObject*> aGivenProto) override; IMPL_EVENT_HANDLER(statechange) RTCDtlsTransportState State() const { return mState; } + RefPtr<RTCIceTransport> IceTransport() { return mIceTransport; } void UpdateStateNoEvent(TransportLayer::State aState); void UpdateState(TransportLayer::State aState); @@ -38,6 +40,7 @@ class RTCDtlsTransport : public DOMEventTargetHelper { virtual ~RTCDtlsTransport() = default; RTCDtlsTransportState mState; + RefPtr<RTCIceTransport> mIceTransport; }; } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCIceTransport.cpp b/dom/media/webrtc/jsapi/RTCIceTransport.cpp new file mode 100644 index 0000000000..4c5e6eef4f --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCIceTransport.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RTCIceTransport.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/RTCIceTransportBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCIceTransport, DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(RTCIceTransport, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCIceTransport, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCIceTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +RTCIceTransport::RTCIceTransport(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow), + mState(RTCIceTransportState::New), + mGatheringState(RTCIceGathererState::New) {} + +JSObject* RTCIceTransport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCIceTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCIceTransport::SetState(RTCIceTransportState aState) { mState = aState; } + +void RTCIceTransport::SetGatheringState(RTCIceGathererState aState) { + mGatheringState = aState; +} + +void RTCIceTransport::FireStateChangeEvent() { + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init); + + DispatchTrustedEvent(event); +} + +void RTCIceTransport::FireGatheringStateChangeEvent() { + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + RefPtr<Event> event = + Event::Constructor(this, u"gatheringstatechange"_ns, init); + + DispatchTrustedEvent(event); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCIceTransport.h b/dom/media/webrtc/jsapi/RTCIceTransport.h new file mode 100644 index 0000000000..931aa66bac --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCIceTransport.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCICETRANSPORT_H_ +#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCICETRANSPORT_H_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "transport/transportlayer.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +enum class RTCIceTransportState : uint8_t; +enum class RTCIceGathererState : uint8_t; + +class RTCIceTransport : public DOMEventTargetHelper { + public: + explicit RTCIceTransport(nsPIDOMWindowInner* aWindow); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCIceTransport, + DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + IMPL_EVENT_HANDLER(statechange) + IMPL_EVENT_HANDLER(gatheringstatechange) + RTCIceTransportState State() const { return mState; } + RTCIceGathererState GatheringState() const { return mGatheringState; } + + void SetState(RTCIceTransportState aState); + void SetGatheringState(RTCIceGathererState aState); + + void FireStateChangeEvent(); + void FireGatheringStateChangeEvent(); + + private: + virtual ~RTCIceTransport() = default; + + RTCIceTransportState mState; + RTCIceGathererState mGatheringState; +}; + +} // namespace mozilla::dom +#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCICETRANSPORT_H_ diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp index efe83fb782..96db9fc312 100644 --- a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp @@ -709,9 +709,9 @@ void RTCRtpReceiver::UpdateTransport() { // Add unique payload types as a last-ditch fallback auto uniquePts = GetJsepTransceiver() .mRecvTrack.GetNegotiatedDetails() - ->GetUniquePayloadTypes(); + ->GetUniqueReceivePayloadTypes(); for (unsigned char& uniquePt : uniquePts) { - filter->AddUniquePT(uniquePt); + filter->AddUniqueReceivePT(uniquePt); } } diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp index 4ba0491521..da198c62e4 100644 --- a/dom/media/webrtc/jsapi/RTCRtpSender.cpp +++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp @@ -1502,7 +1502,6 @@ Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() { case dom::MediaSourceEnum::Microphone: case dom::MediaSourceEnum::AudioCapture: - case dom::MediaSourceEnum::EndGuard_: MOZ_ASSERT(false); break; } diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp index b219619f87..6b7b456e46 100644 --- a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp @@ -239,7 +239,6 @@ SdpDirectionAttribute::Direction ToSdpDirection( case dom::RTCRtpTransceiverDirection::Inactive: case dom::RTCRtpTransceiverDirection::Stopped: return SdpDirectionAttribute::Direction::kInactive; - case dom::RTCRtpTransceiverDirection::EndGuard_:; } MOZ_CRASH("Invalid transceiver direction!"); } @@ -279,12 +278,13 @@ void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit, mDirection = aInit.mDirection; } -void RTCRtpTransceiver::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, - bool aStable) { +void RTCRtpTransceiver::SetDtlsTransport( + dom::RTCDtlsTransport* aDtlsTransport) { mDtlsTransport = aDtlsTransport; - if (aStable) { - mLastStableDtlsTransport = mDtlsTransport; - } +} + +void RTCRtpTransceiver::SaveStateForRollback() { + mLastStableDtlsTransport = mDtlsTransport; } void RTCRtpTransceiver::RollbackToStableDtlsTransport() { @@ -366,12 +366,17 @@ void RTCRtpTransceiver::InitConduitControl() { } void RTCRtpTransceiver::Close() { - // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia -> - // PCMedia::SelfDestruct. Satisfies step 7 of + // Called via PCImpl::Close + // Satisfies steps 7 and 9 of // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close + // No events are fired for this. mShutdown = true; if (mDtlsTransport) { mDtlsTransport->UpdateStateNoEvent(TransportLayer::TS_CLOSED); + // Might not be set if we're cycle-collecting + if (mDtlsTransport->IceTransport()) { + mDtlsTransport->IceTransport()->SetState(RTCIceTransportState::Closed); + } } StopImpl(); } @@ -401,9 +406,9 @@ void RTCRtpTransceiver::Unlink() { // TODO: Only called from one place in PeerConnectionImpl, synchronously, when // the JSEP engine has successfully completed an offer/answer exchange. This is // a bit squirrely, since identity validation happens asynchronously in -// PeerConnection.jsm. This probably needs to happen once all the "in parallel" -// steps have succeeded, but before we queue the task for JS observable state -// updates. +// PeerConnection.sys.mjs. This probably needs to happen once all the "in +// parallel" steps have succeeded, but before we queue the task for JS +// observable state updates. nsresult RTCRtpTransceiver::UpdateTransport() { if (!mHasTransport) { return NS_OK; @@ -439,9 +444,9 @@ void RTCRtpTransceiver::ResetSync() { mSyncGroup = std::string(); } // TODO: Only called from one place in PeerConnectionImpl, synchronously, when // the JSEP engine has successfully completed an offer/answer exchange. This is // a bit squirrely, since identity validation happens asynchronously in -// PeerConnection.jsm. This probably needs to happen once all the "in parallel" -// steps have succeeded, but before we queue the task for JS observable state -// updates. +// PeerConnection.sys.mjs. This probably needs to happen once all the "in +// parallel" steps have succeeded, but before we queue the task for JS +// observable state updates. nsresult RTCRtpTransceiver::SyncWithMatchingVideoConduits( nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers) { if (mStopped) { diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.h b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h index afa6142d35..4fa7ef157c 100644 --- a/dom/media/webrtc/jsapi/RTCRtpTransceiver.h +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h @@ -119,7 +119,8 @@ class RTCRtpTransceiver : public nsISupports, public nsWrapperCache { void SyncFromJsep(const JsepSession& aSession); std::string GetMidAscii() const; - void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport, bool aStable); + void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport); + void SaveStateForRollback(); void RollbackToStableDtlsTransport(); std::string GetTransportId() const { diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build index 78a6241cd6..05920fc151 100644 --- a/dom/media/webrtc/jsapi/moz.build +++ b/dom/media/webrtc/jsapi/moz.build @@ -31,6 +31,7 @@ UNIFIED_SOURCES += [ "RTCEncodedAudioFrame.cpp", "RTCEncodedFrameBase.cpp", "RTCEncodedVideoFrame.cpp", + "RTCIceTransport.cpp", "RTCRtpReceiver.cpp", "RTCRtpScriptTransform.cpp", "RTCRtpScriptTransformer.cpp", @@ -50,6 +51,7 @@ EXPORTS.mozilla.dom += [ "RTCEncodedAudioFrame.h", "RTCEncodedFrameBase.h", "RTCEncodedVideoFrame.h", + "RTCIceTransport.h", "RTCRtpReceiver.h", "RTCRtpScriptTransform.h", "RTCRtpScriptTransformer.h", diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.cpp b/dom/media/webrtc/jsep/JsepSessionImpl.cpp index 50def6eb6c..c091b6d618 100644 --- a/dom/media/webrtc/jsep/JsepSessionImpl.cpp +++ b/dom/media/webrtc/jsep/JsepSessionImpl.cpp @@ -1121,11 +1121,17 @@ nsresult JsepSessionImpl::HandleNegotiatedSession( CopyBundleTransports(); - std::vector<JsepTrack*> remoteTracks; + std::vector<JsepTrack*> receiveTracks; for (auto& transceiver : mTransceivers) { - remoteTracks.push_back(&transceiver.mRecvTrack); + // Do not count payload types for non-active recv tracks as duplicates. If + // we receive an RTP packet with a payload type that is used by both a + // sendrecv and a sendonly m-section, there is no ambiguity; it is for the + // sendrecv m-section. + if (transceiver.mRecvTrack.GetActive()) { + receiveTracks.push_back(&transceiver.mRecvTrack); + } } - JsepTrack::SetUniquePayloadTypes(remoteTracks); + JsepTrack::SetUniqueReceivePayloadTypes(receiveTracks); mNegotiations++; diff --git a/dom/media/webrtc/jsep/JsepTrack.cpp b/dom/media/webrtc/jsep/JsepTrack.cpp index 59c038cfc0..2498200ab2 100644 --- a/dom/media/webrtc/jsep/JsepTrack.cpp +++ b/dom/media/webrtc/jsep/JsepTrack.cpp @@ -662,7 +662,7 @@ nsresult JsepTrack::Negotiate(const SdpMediaSection& answer, // works, however, if that payload type appeared in only one m-section. // We figure that out here. /* static */ -void JsepTrack::SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks) { +void JsepTrack::SetUniqueReceivePayloadTypes(std::vector<JsepTrack*>& tracks) { // Maps to track details if no other track contains the payload type, // otherwise maps to nullptr. std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap; @@ -697,7 +697,7 @@ void JsepTrack::SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks) { auto trackDetails = ptAndDetails.second; if (trackDetails) { - trackDetails->mUniquePayloadTypes.push_back( + trackDetails->mUniqueReceivePayloadTypes.push_back( static_cast<uint8_t>(uniquePt)); } } diff --git a/dom/media/webrtc/jsep/JsepTrack.h b/dom/media/webrtc/jsep/JsepTrack.h index d11735f43a..74a0d2396c 100644 --- a/dom/media/webrtc/jsep/JsepTrack.h +++ b/dom/media/webrtc/jsep/JsepTrack.h @@ -31,7 +31,7 @@ class JsepTrackNegotiatedDetails { JsepTrackNegotiatedDetails(const JsepTrackNegotiatedDetails& orig) : mExtmap(orig.mExtmap), - mUniquePayloadTypes(orig.mUniquePayloadTypes), + mUniqueReceivePayloadTypes(orig.mUniqueReceivePayloadTypes), mTias(orig.mTias), mRtpRtcpConf(orig.mRtpRtcpConf) { for (const auto& encoding : orig.mEncodings) { @@ -71,8 +71,8 @@ class JsepTrackNegotiatedDetails { } } - std::vector<uint8_t> GetUniquePayloadTypes() const { - return mUniquePayloadTypes; + std::vector<uint8_t> GetUniqueReceivePayloadTypes() const { + return mUniqueReceivePayloadTypes; } uint32_t GetTias() const { return mTias; } @@ -83,7 +83,7 @@ class JsepTrackNegotiatedDetails { friend class JsepTrack; std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap; - std::vector<uint8_t> mUniquePayloadTypes; + std::vector<uint8_t> mUniqueReceivePayloadTypes; std::vector<UniquePtr<JsepTrackEncoding>> mEncodings; uint32_t mTias; // bits per second RtpRtcpConfig mRtpRtcpConf; @@ -214,7 +214,7 @@ class JsepTrack { virtual nsresult Negotiate(const SdpMediaSection& answer, const SdpMediaSection& remote, const SdpMediaSection& local); - static void SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks); + static void SetUniqueReceivePayloadTypes(std::vector<JsepTrack*>& tracks); virtual void GetNegotiatedPayloadTypes( std::vector<uint16_t>* payloadTypes) const; diff --git a/dom/media/webrtc/metrics.yaml b/dom/media/webrtc/metrics.yaml index da3d077f2d..aea5cf17fb 100644 --- a/dom/media/webrtc/metrics.yaml +++ b/dom/media/webrtc/metrics.yaml @@ -18,14 +18,16 @@ rtcrtpsender: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 count_setparameters_compat: type: counter @@ -35,14 +37,16 @@ rtcrtpsender: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 used_sendencodings: type: rate @@ -53,14 +57,16 @@ rtcrtpsender: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 rtcrtpsender.setparameters: warn_no_getparameters: @@ -74,14 +80,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 blame_no_getparameters: type: labeled_counter @@ -92,13 +100,15 @@ rtcrtpsender.setparameters: enough call to `getParameters`) Collected only on EARLY_BETA_OR_EARLIER. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - web_activity notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 warn_length_changed: type: rate @@ -110,14 +120,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 blame_length_changed: type: labeled_counter @@ -128,13 +140,15 @@ rtcrtpsender.setparameters: EARLY_BETA_OR_EARLIER. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - web_activity notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 warn_no_transactionid: type: rate @@ -146,14 +160,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 blame_no_transactionid: type: labeled_counter @@ -163,13 +179,15 @@ rtcrtpsender.setparameters: by the eTLD+1 of the site. Collected only on EARLY_BETA_OR_EARLIER. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - web_activity notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_length_changed: type: rate @@ -181,14 +199,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_rid_changed: type: rate @@ -201,14 +221,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_no_getparameters: type: rate @@ -220,14 +242,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_no_transactionid: type: rate @@ -238,14 +262,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_stale_transactionid: type: rate @@ -256,14 +282,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_no_encodings: type: rate @@ -277,14 +305,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 fail_other: type: rate @@ -295,14 +325,16 @@ rtcrtpsender.setparameters: bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881403 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 codec.stats: ulpfec_negotiated: @@ -312,13 +344,15 @@ codec.stats: on the first negotiation for each video transceiver. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 labels: - negotiated - not_negotiated @@ -329,13 +363,15 @@ codec.stats: Count how many other fec options are being offered. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 video_preferred_codec: type: labeled_counter @@ -343,13 +379,15 @@ codec.stats: Counts the preferred video codec being signaled to us to identify preferred video codec. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 audio_preferred_codec: type: labeled_counter @@ -357,10 +395,12 @@ codec.stats: Counts the preferred audio codec being signaled to us to identify preferred audio codec. bugs: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1858213 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881396 data_sensitivity: - technical notification_emails: - webrtc-telemetry-alerts@mozilla.com - expires: 126 + expires: 132 diff --git a/dom/media/webrtc/tests/mochitests/head.js b/dom/media/webrtc/tests/mochitests/head.js index 9a4e5682a3..1fd559217a 100644 --- a/dom/media/webrtc/tests/mochitests/head.js +++ b/dom/media/webrtc/tests/mochitests/head.js @@ -424,6 +424,7 @@ function setupEnvironment() { ["media.peerconnection.nat_simulator.block_udp", false], ["media.peerconnection.nat_simulator.redirect_address", ""], ["media.peerconnection.nat_simulator.redirect_targets", ""], + ["media.peerconnection.treat_warnings_as_errors", true], ], }; diff --git a/dom/media/webrtc/tests/mochitests/iceTestUtils.js b/dom/media/webrtc/tests/mochitests/iceTestUtils.js index d4d1f5c4b4..9e76e3f7df 100644 --- a/dom/media/webrtc/tests/mochitests/iceTestUtils.js +++ b/dom/media/webrtc/tests/mochitests/iceTestUtils.js @@ -300,3 +300,129 @@ async function checkNoRelay(iceServers) { ); pc.close(); } + +function gatheringStateReached(object, state) { + if (object instanceof RTCIceTransport) { + return new Promise(r => + object.addEventListener("gatheringstatechange", function listener() { + if (object.gatheringState == state) { + object.removeEventListener("gatheringstatechange", listener); + r(state); + } + }) + ); + } else if (object instanceof RTCPeerConnection) { + return new Promise(r => + object.addEventListener("icegatheringstatechange", function listener() { + if (object.iceGatheringState == state) { + object.removeEventListener("icegatheringstatechange", listener); + r(state); + } + }) + ); + } else { + throw "First parameter is neither an RTCIceTransport nor an RTCPeerConnection"; + } +} + +function nextGatheringState(object) { + if (object instanceof RTCIceTransport) { + return new Promise(resolve => + object.addEventListener( + "gatheringstatechange", + () => resolve(object.gatheringState), + { once: true } + ) + ); + } else if (object instanceof RTCPeerConnection) { + return new Promise(resolve => + object.addEventListener( + "icegatheringstatechange", + () => resolve(object.iceGatheringState), + { once: true } + ) + ); + } else { + throw "First parameter is neither an RTCIceTransport nor an RTCPeerConnection"; + } +} + +function emptyCandidate(pc) { + return new Promise(r => + pc.addEventListener("icecandidate", function listener(e) { + if (e.candidate && e.candidate.candidate == "") { + pc.removeEventListener("icecandidate", listener); + r(e); + } + }) + ); +} + +function nullCandidate(pc) { + return new Promise(r => + pc.addEventListener("icecandidate", function listener(e) { + if (!e.candidate) { + pc.removeEventListener("icecandidate", listener); + r(e); + } + }) + ); +} + +function connectionStateReached(object, state) { + if (object instanceof RTCIceTransport || object instanceof RTCDtlsTransport) { + return new Promise(resolve => + object.addEventListener("statechange", function listener() { + if (object.state == state) { + object.removeEventListener("statechange", listener); + resolve(state); + } + }) + ); + } else if (object instanceof RTCPeerConnection) { + return new Promise(resolve => + object.addEventListener("connectionstatechange", function listener() { + if (object.connectionState == state) { + object.removeEventListener("connectionstatechange", listener); + resolve(state); + } + }) + ); + } else { + throw "First parameter is neither an RTCIceTransport, an RTCDtlsTransport, nor an RTCPeerConnection"; + } +} + +function nextConnectionState(object) { + if (object instanceof RTCIceTransport || object instanceof RTCDtlsTransport) { + return new Promise(resolve => + object.addEventListener("statechange", () => resolve(object.state), { + once: true, + }) + ); + } else if (object instanceof RTCPeerConnection) { + return new Promise(resolve => + object.addEventListener( + "connectionstatechange", + () => resolve(object.connectionState), + { once: true } + ) + ); + } else { + throw "First parameter is neither an RTCIceTransport, an RTCDtlsTransport, nor an RTCPeerConnection"; + } +} + +function nextIceConnectionState(pc) { + if (pc instanceof RTCPeerConnection) { + return new Promise(resolve => + pc.addEventListener( + "iceconnectionstatechange", + () => resolve(pc.iceConnectionState), + { once: true } + ) + ); + } else { + throw "First parameter is not an RTCPeerConnection"; + } +} diff --git a/dom/media/webrtc/tests/mochitests/pc.js b/dom/media/webrtc/tests/mochitests/pc.js index 73e1b2c2f0..5778c61392 100644 --- a/dom/media/webrtc/tests/mochitests/pc.js +++ b/dom/media/webrtc/tests/mochitests/pc.js @@ -12,8 +12,8 @@ const iceStateTransitions = { checking: ["new", "connected", "failed", "closed"], //Note: do we need to // allow 'completed' in // here as well? - connected: ["new", "completed", "disconnected", "closed"], - completed: ["new", "disconnected", "closed"], + connected: ["new", "checking", "completed", "disconnected", "closed"], + completed: ["new", "checking", "disconnected", "closed"], disconnected: ["new", "connected", "completed", "failed", "closed"], failed: ["new", "disconnected", "closed"], closed: [], @@ -367,9 +367,7 @@ PeerConnectionTest.prototype.createDataChannel = function (options) { PeerConnectionTest.prototype.createAnswer = function (peer) { return peer.createAnswer().then(answer => { // make a copy so this does not get updated with ICE candidates - this.originalAnswer = new RTCSessionDescription( - JSON.parse(JSON.stringify(answer)) - ); + this.originalAnswer = JSON.parse(JSON.stringify(answer)); return answer; }); }; @@ -384,9 +382,7 @@ PeerConnectionTest.prototype.createAnswer = function (peer) { PeerConnectionTest.prototype.createOffer = function (peer) { return peer.createOffer().then(offer => { // make a copy so this does not get updated with ICE candidates - this.originalOffer = new RTCSessionDescription( - JSON.parse(JSON.stringify(offer)) - ); + this.originalOffer = JSON.parse(JSON.stringify(offer)); return offer; }); }; @@ -1399,10 +1395,6 @@ PeerConnectionWrapper.prototype = { }); }, - isTrackOnPC(track) { - return !!this.getStreamForRecvTrack(track); - }, - allExpectedTracksAreObserved(expected, observed) { return Object.keys(expected).every(trackId => observed[trackId]); }, @@ -1459,7 +1451,10 @@ PeerConnectionWrapper.prototype = { setupTrackEventHandler() { this._pc.addEventListener("track", ({ track, streams }) => { info(`${this}: 'ontrack' event fired for ${track.id}`); - ok(this.isTrackOnPC(track), `Found track ${track.id}`); + ok( + this._pc.getReceivers().some(r => r.track == track), + `Found track ${track.id}` + ); let gratuitousEvent = true; let streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_RTCIceTransport.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_RTCIceTransport.html new file mode 100644 index 0000000000..893d1aad5b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_RTCIceTransport.html @@ -0,0 +1,197 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="application/javascript" src="pc.js"></script> + <script type="application/javascript" src="iceTestUtils.js"></script> + <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script></head> +<body> +<pre id="test"> +<script type="application/javascript"> + createHTML({ + bug: "1811912", + title: "Tests for RTCIceTransport" + }); + + const tests = [ + async function iceRestartNewGathersFirst() { + await pushPrefs( + ['media.peerconnection.nat_simulator.block_udp', true]); + // We block UDP, and configure a fake UDP STUN server; this should result + // in gathering taking a while, because we'll just be sending UDP packets + // off to be eaten by the NAT simulator. + const pc1 = new RTCPeerConnection({ iceServers: [{ + urls: ['stun:192.168.1.1'] + }] }); + const pc2 = new RTCPeerConnection(); + + const transceiver = pc1.addTransceiver('audio'); + await pc1.setLocalDescription(); + const {iceTransport} = transceiver.sender.transport; + is(await nextGatheringState(iceTransport), 'gathering'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + pc1.setConfiguration({iceServers: []}); + await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true})); + is(iceTransport.gatheringState, 'gathering'); + + const gatheringDonePromise = nextGatheringState(iceTransport); + // The empty candidate should fire for the new underlying transport + // (created by the ICE restart), but there should be no gathering state + // change yet. (Spec says that the RTCIceTransport object is the same + // here, even thougb there's actually a new transport) + await emptyCandidate(pc1); + + // New transport is done gathering, old should not be. + let result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 100))]); + is(result, undefined, 'Gathering should not complete yet'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + // We might or might not have an empty candidate for the generation we + // just abandoned, depending on how the spec shakes out. + + // Completing negotiation should result in a transition to complete very + // quickly + result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 100))]); + is(result, 'complete', 'Gathering should complete soon after the ICE restart is complete'); + pc1.close(); + pc2.close(); + await SpecialPowers.popPrefEnv(); + }, + + async function iceRestartNewGathersFirstThenRollback() { + await pushPrefs( + ['media.peerconnection.nat_simulator.block_udp', true]); + // This should result in gathering taking a while + const pc1 = new RTCPeerConnection({ iceServers: [{ + urls: ['stun:192.168.1.1'] + }] }); + const pc2 = new RTCPeerConnection(); + + const transceiver = pc1.addTransceiver('audio'); + await pc1.setLocalDescription(); + const {iceTransport} = transceiver.sender.transport; + is(await nextGatheringState(iceTransport), 'gathering'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + pc1.setConfiguration({iceServers: []}); + await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true})); + is(iceTransport.gatheringState, 'gathering'); + + const gatheringDonePromise = nextGatheringState(iceTransport); + // This should fire for the new transport, but there should be no + // gathering state change yet. + await emptyCandidate(pc1); + + // Rollback should abandon the new transport, and we should remain in + // 'gathering' + await pc1.setLocalDescription({type: 'rollback', sdp: ''}); + + let result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 1000))]); + is(result, undefined, 'Gathering should not complete'); + pc1.close(); + pc2.close(); + await SpecialPowers.popPrefEnv(); + }, + + async function iceRestartOldGathersFirst() { + await pushPrefs( + ['media.peerconnection.nat_simulator.block_udp', true]); + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + + const transceiver = pc1.addTransceiver('audio'); + await pc1.setLocalDescription(); + const {iceTransport} = transceiver.sender.transport; + is(await nextGatheringState(iceTransport), 'gathering'); + is(await nextGatheringState(iceTransport), 'complete'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + // This should result in gathering taking a while + pc1.setConfiguration({ iceServers: [{ + urls: ['stun:192.168.1.1'] + }] }); + await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true})); + + is(await nextGatheringState(iceTransport), 'gathering'); + + const gatheringDonePromise = nextGatheringState(iceTransport); + let result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 100))]); + is(result, undefined, 'Gathering should not complete'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 100))]); + is(result, undefined, 'Gathering should not complete'); + pc1.close(); + pc2.close(); + await SpecialPowers.popPrefEnv(); + }, + + async function iceRestartOldGathersFirstThenRollback() { + await pushPrefs( + ['media.peerconnection.nat_simulator.block_udp', true]); + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + + const transceiver = pc1.addTransceiver('audio'); + await pc1.setLocalDescription(); + const {iceTransport} = transceiver.sender.transport; + is(await nextGatheringState(iceTransport), 'gathering'); + is(await nextGatheringState(iceTransport), 'complete'); + + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(); + await pc1.setRemoteDescription(pc2.localDescription); + + // This should result in gathering taking a while + pc1.setConfiguration({ iceServers: [{ + urls: ['stun:192.168.1.1'] + }] }); + await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true})); + + is(await nextGatheringState(iceTransport), 'gathering', 'ICE restart should put us back in gathering'); + + const gatheringDonePromise = nextGatheringState(iceTransport); + is(iceTransport.gatheringState, 'gathering'); + await pc1.setLocalDescription({type: 'rollback', sdp: ''}); + const result = await Promise.race([gatheringDonePromise, new Promise(r => setTimeout(r, 200))]); + is(iceTransport.gatheringState, 'complete', 'Rollback of ICE restart to transport that was already done gathering should result in a transition back to "complete"'); + is(result, 'complete', 'Rollback that brings the gathering state back to complete should result in a gatheringstatechange event'); + pc1.close(); + pc2.close(); + await SpecialPowers.popPrefEnv(); + }, + ]; + + runNetworkTest(async () => { + for (const test of tests) { + info(`Running test: ${test.name}`); + try { + await test(); + } catch (e) { + ok(false, `Caught ${e.name}: ${e.message} ${e.stack}`); + } + info(`Done running test: ${test.name}`); + // Make sure we don't build up a pile of GC work, and also get PCImpl to + // print their timecards. + await new Promise(r => SpecialPowers.exactGC(r)); + } + }, { useIceServer: true }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html index 180abc075a..7f3f9f57ad 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html @@ -24,6 +24,8 @@ if (!("mediaDevices" in navigator)) { ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'], ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'], ['media.peerconnection.nat_simulator.block_tcp', true], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); options.expectedLocalCandidateType = "srflx"; options.expectedRemoteCandidateType = "relay"; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html index 7bb51764bd..387341151f 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html @@ -26,6 +26,8 @@ if (!("mediaDevices" in navigator)) { ['media.peerconnection.nat_simulator.block_tcp', false], ['media.peerconnection.nat_simulator.block_tls', true], ['media.peerconnection.ice.loopback', true], + // The above sets up 5+ ICE servers which triggers a warning + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); options.expectedLocalCandidateType = "relay-tcp"; options.expectedRemoteCandidateType = "relay-tcp"; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html index 7446401f87..2ac886c156 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html @@ -25,6 +25,8 @@ if (!("mediaDevices" in navigator)) { ['media.peerconnection.nat_simulator.block_udp', true], ['media.peerconnection.nat_simulator.block_tcp', true], ['media.peerconnection.ice.loopback', true], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); options.expectedLocalCandidateType = "relay-tls"; options.expectedRemoteCandidateType = "relay-tls"; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html index 78fa8bcb2c..69fc2af80a 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html @@ -24,6 +24,8 @@ if (!("mediaDevices" in navigator)) { ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'], ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'], ['media.peerconnection.nat_simulator.block_tcp', true], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); options.expectedLocalCandidateType = "srflx"; options.expectedRemoteCandidateType = "srflx"; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html index 297121cd94..4175419d44 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html @@ -25,6 +25,8 @@ if (!("mediaDevices" in navigator)) { ['media.peerconnection.nat_simulator.block_udp', true], ['media.peerconnection.nat_simulator.error_code_for_drop', 3 /*R_INTERNAL*/], ['media.peerconnection.nat_simulator.block_tls', true], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); options.expectedLocalCandidateType = "relay-tcp"; options.expectedRemoteCandidateType = "relay-tcp"; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html index ced57ff8a3..313d76441b 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html @@ -14,7 +14,10 @@ createHTML({ runNetworkTest(async () => { await pushPrefs( // Enable mDNS, since there are some checks we want to run with that - ['media.peerconnection.ice.obfuscate_host_addresses', true]); + ['media.peerconnection.ice.obfuscate_host_addresses', true], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], + ); const offerer = new RTCPeerConnection({iceServers: iceServersArray, iceTransportPolicy: 'relay'}); const answerer = new RTCPeerConnection({iceServers: iceServersArray}); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html index 5cd168af8a..5efb4a17d2 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html @@ -25,7 +25,7 @@ var makePC = (config, expected_error) => { exception = e; } is((exception? exception.name : "success"), expected_error || "success", - "RTCPeerConnection(" + JSON.stringify(config) + ")"); + "RTCPeerConnection(" + JSON.stringify(config) + ") " + exception?.message); }; // The order of properties in objects is not guaranteed in JavaScript, so this @@ -39,8 +39,8 @@ var toComparable = o => }, {}); // This is a test of the iceServers parsing code + readable errors -runNetworkTest(() => { - var exception = null; +runNetworkTest(async () => { + let exception = null; try { new RTCPeerConnection().close(); @@ -56,26 +56,37 @@ runNetworkTest(() => { { urls:"stun:127.0.0.1" }, { urls:"stun:localhost", foo:"" }, { urls: ["stun:127.0.0.1", "stun:localhost"] }, - { urls:"stuns:localhost", foo:"" }, + ]}); + makePC({ iceServers: [ { urls:"turn:[::1]:3478", username:"p", credential:"p" }, { urls:"turn:[::1]:3478", username:"", credential:"" }, { urls:"turns:[::1]:3478", username:"", credential:"" }, + ]}); + makePC({ iceServers: [ { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" }, { urls: ["turn:[::1]:3478", "turn:localhost"], username:"p", credential:"p" }, { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" }, - { url:"stun:localhost", foo:"" }, - { url:"turn:localhost", username:"p", credential:"p" } ]}); - makePC({ iceServers: [{ urls:"http:0.0.0.0" }] }, "SyntaxError"); try { - new RTCPeerConnection({ iceServers: [{ url:"http:0.0.0.0" }] }).close(); + new RTCPeerConnection({ iceServers: [{ urls:"http:0.0.0.0" }] }).close(); } catch (e) { ok(e.message.indexOf("http") > 0, "RTCPeerConnection() constructor has readable exceptions"); } + const push = prefs => SpecialPowers.pushPrefEnv(prefs); + + // Remaining tests trigger warnings + await push({ set: [['media.peerconnection.treat_warnings_as_errors', false]] }); + + makePC({ iceServers: [ + { urls:"stuns:localhost", foo:"" }, + { url:"stun:localhost", foo:"" }, + { url:"turn:localhost", username:"p", credential:"p" } + ]}); + // Test getConfiguration const config = { bundlePolicy: "max-bundle", @@ -98,41 +109,35 @@ runNetworkTest(() => { JSON.stringify(toComparable(config)), "getConfiguration"); pc.close(); - var push = prefs => SpecialPowers.pushPrefEnv(prefs); - - return Promise.resolve() // This set of tests are setting the about:config User preferences for default // ice servers and checking the outputs when RTCPeerConnection() is // invoked. See Bug 1167922 for more information. - .then(() => push({ set: [['media.peerconnection.default_iceservers', ""]] }) - .then(() => makePC()) - .then(() => push({ set: [['media.peerconnection.default_iceservers', "k"]] })) - .then(() => makePC()) - .then(() => push({ set: [['media.peerconnection.default_iceservers', "[{\"urls\": [\"stun:stun.services.mozilla.com\"]}]"]] })) - .then(() => makePC())) + await push({ set: [['media.peerconnection.default_iceservers', ""]] }); + makePC(); + await push({ set: [['media.peerconnection.default_iceservers', "k"]] }); + makePC(); + await push({ set: [['media.peerconnection.default_iceservers', + "[{\"urls\": [\"stun:stun.services.mozilla.com\"]}]"]]}); + makePC(); // This set of tests check that warnings work. See Bug 1254839 for more. - .then(() => { - let promise = new Promise(resolve => { - SpecialPowers.registerConsoleListener(msg => { - if (msg.message.includes("onaddstream")) { - SpecialPowers.postConsoleSentinel(); - resolve(msg.message); - } - }); + const warning = await new Promise(resolve => { + SpecialPowers.registerConsoleListener(msg => { + if (msg.message.includes("onaddstream")) { + SpecialPowers.postConsoleSentinel(); + resolve(msg.message); + } }); lineNumberAndFunction.func(); - return promise; - }).then(warning => { - is(warning.split('"')[1], - "WebRTC: onaddstream is deprecated! Use peerConnection.ontrack instead.", - "warning logged"); - var remainder = warning.split('"').slice(2).join('"'); - info(remainder); - ok(remainder.includes('file: "' + window.location + '"'), - "warning has this file"); - ok(remainder.includes('line: ' + lineNumberAndFunction.line), - "warning has correct line number"); - }); + }); + is(warning.split('"')[1], + "WebRTC: onaddstream is deprecated! Use peerConnection.ontrack instead.", + "warning logged"); + const remainder = warning.split('"').slice(2).join('"'); + info(remainder); + ok(remainder.includes('file: "' + window.location + '"'), + "warning has this file"); + ok(remainder.includes('line: ' + lineNumberAndFunction.line), + "warning has correct line number"); }); </script> </pre> diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html index 4c890e4400..ffd7a1b0c1 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html @@ -1,4 +1,4 @@ -<!DOCTYPE HTML> +<!DOCTYPE HTML> <html> <head> <script type="application/javascript" src="pc.js"></script> @@ -50,22 +50,27 @@ pc2.onicecandidate = e => { .catch(generateErrorCallback()); }; -var v1, v2; -var delivered = new Promise(resolve => { - pc2.onaddstream = e => { - v2.srcObject = e.stream; - resolve(e.stream); - }; -}); +runNetworkTest(async function() { + // Tests trigger warnings + await SpecialPowers.pushPrefEnv({ + set: [['media.peerconnection.treat_warnings_as_errors', false]] + }); + + const v1 = createMediaElement('video', 'v1'); + const v2 = createMediaElement('video', 'v2'); + + const delivered = new Promise(resolve => { + pc2.onaddstream = e => { + v2.srcObject = e.stream; + resolve(e.stream); + }; + }); -runNetworkTest(function() { - v1 = createMediaElement('video', 'v1'); - v2 = createMediaElement('video', 'v2'); var canPlayThrough = new Promise(resolve => v2.canplaythrough = resolve); is(v2.currentTime, 0, "v2.currentTime is zero at outset"); // not testing legacy gUM here - return navigator.mediaDevices.getUserMedia({ video: true, audio: true }) + await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(stream => pc1.addStream(v1.srcObject = stream)) .then(() => pcall(pc1, pc1.createOffer)) .then(offer => pcall(pc1, pc1.setLocalDescription, offer)) diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html index 5bdc8cc029..8c9470e7f2 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html @@ -32,7 +32,7 @@ // Rolling back should shut down gathering function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) { - return test.pcRemote.endOfTrickleIce; + is(test.pcRemote._pc.iceGatheringState, "new"); }, function PC_REMOTE_SETUP_ICE_HANDLER(test) { diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html index 7cd695ff54..589d8ed6ad 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html @@ -46,10 +46,14 @@ runNetworkTest(() => { { urls:"turn:[::1]:5349", username:"p", credential:"p" }, { urls:"turn:[::1]:3478", username:"p", credential:"p" }, { urls:"turn:[::1]", username:"p", credential:"p" }, + ]}); + makePC({ iceServers: [ { urls:"turn:localhost:53?transport=udp", username:"p", credential:"p" }, { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" }, { urls:"turn:localhost:53?transport=tcp", username:"p", credential:"p" }, { urls:"turn:localhost:3478?transport=tcp", username:"p", credential:"p" }, + ]}); + makePC({ iceServers: [ { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" }, { urls:"stun:localhost", foo:"" } ]}); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html index d94bb084b7..add3882a4d 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html @@ -21,12 +21,14 @@ function PC_LOCAL_SET_OFFER_OPTION(test) { test.setOfferOptions({ iceRestart: true }); }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); + // Make sure we don't get the end of gathering racing against the + // setting of the new offer + function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) { + return test.pcLocal.endOfTrickleIce; + }, + function PC_LOCAL_SETUP_ICE_HANDLER(test) { + test.pcLocal.setupIceCandidateHandler(test); }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ] ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html index 6bbf9440fc..3307258ef9 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html @@ -31,10 +31,6 @@ test.chain.replaceAfter('PC_REMOTE_CREATE_ANSWER', [ - function PC_LOCAL_EXPECT_ICE_CONNECTED(test) { - test.pcLocal.iceCheckingIceRollbackExpected = true; - }, - function PC_REMOTE_ROLLBACK(test) { return test.setRemoteDescription(test.pcRemote, { type: "rollback" }, STABLE); @@ -58,12 +54,6 @@ return test.pcLocal.endOfTrickleIce; }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); - }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ], 1 // Replaces after second PC_REMOTE_CREATE_ANSWER ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html index f5f9a1f220..a7adb7c3e9 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html @@ -33,9 +33,6 @@ HAVE_LOCAL_OFFER); }); }, - function PC_LOCAL_EXPECT_ICE_CONNECTED(test) { - test.pcLocal.iceCheckingIceRollbackExpected = true; - }, function PC_LOCAL_WAIT_FOR_GATHERING(test) { return new Promise(r => { test.pcLocal._pc.addEventListener("icegatheringstatechange", () => { @@ -54,12 +51,6 @@ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) { return test.pcLocal.endOfTrickleIce; }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); - }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ] ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html index 134fa97cc0..ef40d4039c 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html @@ -23,12 +23,14 @@ function PC_LOCAL_SET_OFFER_OPTION(test) { test.setOfferOptions({ iceRestart: true }); }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); + // Make sure we don't get the end of gathering racing against the + // setting of the new offer + function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) { + return test.pcLocal.endOfTrickleIce; + }, + function PC_LOCAL_SETUP_ICE_HANDLER(test) { + test.pcLocal.setupIceCandidateHandler(test); }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ] ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html index 06a3a3c980..85a3fcc45d 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html @@ -24,12 +24,14 @@ function PC_LOCAL_SET_OFFER_OPTION(test) { test.setOfferOptions({ iceRestart: true }); }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); + // Make sure we don't get the end of gathering racing against the + // setting of the new offer + function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) { + return test.pcLocal.endOfTrickleIce; + }, + function PC_LOCAL_SETUP_ICE_HANDLER(test) { + test.pcLocal.setupIceCandidateHandler(test); }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ] ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html index 5d4780211a..80fd1090bc 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html @@ -23,12 +23,15 @@ function PC_LOCAL_SET_OFFER_OPTION(test) { test.setOfferOptions({ iceRestart: true }); }, - function PC_LOCAL_EXPECT_ICE_CHECKING(test) { - test.pcLocal.expectIceChecking(); + // Make sure we don't get the end of gathering racing against the + // setting of the new offer + function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) { + return test.pcLocal.endOfTrickleIce; + }, + // Expect gathering to start again + function PC_LOCAL_SETUP_ICE_HANDLER(test) { + test.pcLocal.setupIceCandidateHandler(test); }, - function PC_REMOTE_EXPECT_ICE_CHECKING(test) { - test.pcRemote.expectIceChecking(); - } ] ); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_sillyCodecPriorities.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_sillyCodecPriorities.html index c8354356b0..aa0644ebbb 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_sillyCodecPriorities.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_sillyCodecPriorities.html @@ -15,12 +15,12 @@ visible: true }); - function makeCodecTopPriority(sdp, codec) { + function makeCodecTopPriority({type, sdp}, codec) { const ptToMove = sdputils.findCodecId(sdp, codec); - return sdp.replace( + return {type, sdp: sdp.replace( // m=video port type pts ptToMove more-pts? new RegExp(`(m=video [^ ]+ [^ ]+)(.*)( ${ptToMove})( [^ ]+)?`, "g"), - '$1$3$2$4'); + '$1$3$2$4')}; } function isCodecFirst(sdp, codec) { @@ -34,8 +34,7 @@ const stream = await navigator.mediaDevices.getUserMedia({ video: true }); const sender = pc1.addTrack(stream.getTracks()[0]); await pc1.setLocalDescription(); - let mungedOffer = pc1.localDescription; - mungedOffer.sdp = makeCodecTopPriority(mungedOffer.sdp, codec); + const mungedOffer = makeCodecTopPriority(pc1.localDescription, codec); await pc2.setRemoteDescription(mungedOffer); await pc2.setLocalDescription(); await pc1.setRemoteDescription(pc2.localDescription); @@ -51,8 +50,7 @@ await pc1.setLocalDescription(); await pc2.setRemoteDescription(pc1.localDescription); await pc2.setLocalDescription(); - let mungedAnswer = pc2.localDescription; - mungedAnswer.sdp = makeCodecTopPriority(mungedAnswer.sdp, codec); + const mungedAnswer = makeCodecTopPriority(pc2.localDescription, codec); await pc1.setRemoteDescription(mungedAnswer); is(isCodecFirst((await pc1.createOffer()).sdp, codec), !isPseudoCodec, "Top-priority codecs should come first in reoffers, unless they are pseudo codecs (eg; ulpfec)"); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html index cdc328fd2b..5146d423a6 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html @@ -23,6 +23,8 @@ if (!("mediaDevices" in navigator)) { await pushPrefs( ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'], ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'], + // The above triggers warning about 5 ICE servers + ['media.peerconnection.treat_warnings_as_errors', false], ['media.getusermedia.insecure.enabled', true]); const test = new PeerConnectionTest(options); makeOffererNonTrickle(test.chain); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html index 75f0d12463..8627590b65 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html @@ -98,10 +98,10 @@ runNetworkTest(async () => { //info("Original OFFER: " + JSON.stringify(offer)); offer.sdp = sdputils.removeBundle(offer.sdp); //info("OFFER w/o BUNDLE: " + JSON.stringify(offer)); - const offerAudio = new RTCSessionDescription(JSON.parse(JSON.stringify(offer))); + const offerAudio = JSON.parse(JSON.stringify(offer)); offerAudio.sdp = offerAudio.sdp.replace('m=video 9', 'm=video 0'); //info("offerAudio: " + JSON.stringify(offerAudio)); - const offerVideo = new RTCSessionDescription(JSON.parse(JSON.stringify(offer))); + const offerVideo = JSON.parse(JSON.stringify(offer)); offerVideo.sdp = offerVideo.sdp.replace('m=audio 9', 'm=audio 0'); //info("offerVideo: " + JSON.stringify(offerVideo)); diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html index c476c60161..342b72a5e1 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html @@ -11,7 +11,12 @@ title: "Throw in PeerConnection callbacks" }); -runNetworkTest(function () { +runNetworkTest(async function () { + // Tests trigger warnings + await SpecialPowers.pushPrefEnv({ + set: [['media.peerconnection.treat_warnings_as_errors', false]] + }); + let finish; const onfinished = new Promise(r => finish = async () => { window.onerror = oldOnError; diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html index f685f7c99a..8c12a50942 100644 --- a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html @@ -8,49 +8,70 @@ <script type="application/javascript"> createHTML({ bug: "1264479", - title: "PeerConnection verify current and pending descriptions" + title: "PeerConnection verify current and pending descriptions in legacy mode" }); - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); + // Mostly covered by WPT tests these days, except for in our legacy mode - var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed); - pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback()); - pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback()); + const isSimilarDescription = (d1, d2, msg) => { + isnot(d1, d2, msg + " — not the same object"); + is(d1.type, d2.type, msg + " — but same type"); + is(d1.sdp, d2.sdp, msg + " — and same sdp"); + } + runNetworkTest(async () => { + await SpecialPowers.pushPrefEnv({ + set: [['media.peerconnection.description.legacy.enabled', true]] + }); + + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + + const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed); + pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback()); + pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback()); - runNetworkTest(function() { const v1 = createMediaElement('video', 'v1'); const v2 = createMediaElement('video', 'v2'); - return navigator.mediaDevices.getUserMedia({ video: true, audio: true }) - .then(stream => (v1.srcObject = stream).getTracks().forEach(t => pc1.addTrack(t, stream))) - .then(() => pc1.createOffer({})) // check that createOffer accepts arg. - .then(offer => pc1.setLocalDescription(offer)) - .then(() => { - ok(!pc1.currentLocalDescription, "pc1 currentLocalDescription is empty"); - ok(pc1.pendingLocalDescription, "pc1 pendingLocalDescription is set"); - ok(pc1.localDescription, "pc1 localDescription is set"); - }) - .then(() => pc2.setRemoteDescription(pc1.localDescription)) - .then(() => { - ok(!pc2.currentRemoteDescription, "pc2 currentRemoteDescription is empty"); - ok(pc2.pendingRemoteDescription, "pc2 pendingRemoteDescription is set"); - ok(pc2.remoteDescription, "pc2 remoteDescription is set"); - }) - .then(() => pc2.createAnswer({})) // check that createAnswer accepts arg. - .then(answer => pc2.setLocalDescription(answer)) - .then(() => { - ok(pc2.currentLocalDescription, "pc2 currentLocalDescription is set"); - ok(!pc2.pendingLocalDescription, "pc2 pendingLocalDescription is empty"); - ok(pc2.localDescription, "pc2 localDescription is set"); - }) - .then(() => pc1.setRemoteDescription(pc2.localDescription)) - .then(() => { - ok(pc1.currentRemoteDescription, "pc1 currentRemoteDescription is set"); - ok(!pc1.pendingRemoteDescription, "pc1 pendingRemoteDescription is empty"); - ok(pc1.remoteDescription, "pc1 remoteDescription is set"); + const stream = await navigator.mediaDevices.getUserMedia({ + video: true, audio: true }); + v1.srcObject = stream; + for (const track of stream.getTracks()) { + pc1.addTrack(track, stream); + } + await pc1.setLocalDescription(); + is(pc1.currentLocalDescription, null, "pc1 currentLocalDescription is null"); + ok(pc1.pendingLocalDescription, "pc1 pendingLocalDescription is set"); + ok(pc1.localDescription, "pc1 localDescription is set"); + isSimilarDescription(pc1.pendingLocalDescription, pc1.pendingLocalDescription, "pendingLocalDescription"); + isSimilarDescription(pc1.localDescription, pc1.localDescription, "localDescription"); + isSimilarDescription(pc1.localDescription, pc1.pendingLocalDescription, "local and pending"); + + await pc2.setRemoteDescription(pc1.localDescription); + is(pc2.currentRemoteDescription, null, "pc2 currentRemoteDescription is null"); + ok(pc2.pendingRemoteDescription, "pc2 pendingRemoteDescription is set"); + ok(pc2.remoteDescription, "pc2 remoteDescription is set"); + isSimilarDescription(pc2.pendingRemoteDescription, pc2.pendingRemoteDescription, "pendingRemoteDescription"); + isSimilarDescription(pc2.remoteDescription, pc2.remoteDescription, "remoteDescription"); + isSimilarDescription(pc2.remoteDescription, pc2.pendingRemoteDescription, "remote and pending"); + + await pc2.setLocalDescription(); + ok(pc2.currentLocalDescription, "pc2 currentLocalDescription is set"); + is(pc2.pendingLocalDescription, null, "pc2 pendingLocalDescription is null"); + ok(pc2.localDescription, "pc2 localDescription is set"); + isSimilarDescription(pc2.currentLocalDescription, pc2.currentLocalDescription, "currentLocalDescription"); + isSimilarDescription(pc2.localDescription, pc2.localDescription, "localDescription"); + isSimilarDescription(pc2.localDescription, pc2.currentLocalDescription, "local and current"); + + await pc1.setRemoteDescription(pc2.localDescription); + ok(pc1.currentRemoteDescription, "pc1 currentRemoteDescription is set"); + is(pc1.pendingRemoteDescription, null, "pc1 pendingRemoteDescription is null"); + ok(pc1.remoteDescription, "pc1 remoteDescription is set"); + isSimilarDescription(pc1.currentRemoteDescription, pc1.currentRemoteDescription, "currentRemoteDescription"); + isSimilarDescription(pc1.remoteDescription, pc1.remoteDescription, "remoteDescription"); + isSimilarDescription(pc1.remoteDescription, pc1.currentRemoteDescription, "remote and current"); }); </script> </pre> diff --git a/dom/media/webrtc/third_party_build/default_config_env b/dom/media/webrtc/third_party_build/default_config_env index af8b77bba6..7013520a30 100644 --- a/dom/media/webrtc/third_party_build/default_config_env +++ b/dom/media/webrtc/third_party_build/default_config_env @@ -5,41 +5,41 @@ export MOZ_LIBWEBRTC_SRC=$STATE_DIR/moz-libwebrtc # The previous fast-forward bug number is used for some error messaging. -export MOZ_PRIOR_FASTFORWARD_BUG="1867099" +export MOZ_PRIOR_FASTFORWARD_BUG="1871981" # Fast-forwarding each Chromium version of libwebrtc should be done # under a separate bugzilla bug. This bug number is used when crafting # the commit summary as each upstream commit is vendored into the # mercurial repository. The bug used for the v106 fast-forward was # 1800920. -export MOZ_FASTFORWARD_BUG="1871981" +export MOZ_FASTFORWARD_BUG="1876843" # MOZ_NEXT_LIBWEBRTC_MILESTONE and MOZ_NEXT_FIREFOX_REL_TARGET are # not used during fast-forward processing, but facilitate generating this # default config. To generate an default config for the next update, run # bash dom/media/webrtc/third_party_build/update_default_config_env.sh -export MOZ_NEXT_LIBWEBRTC_MILESTONE=120 -export MOZ_NEXT_FIREFOX_REL_TARGET=124 +export MOZ_NEXT_LIBWEBRTC_MILESTONE=121 +export MOZ_NEXT_FIREFOX_REL_TARGET=125 # For Chromium release branches, see: # https://chromiumdash.appspot.com/branches -# Chromium's v119 release branch was 6045. This is used to pre-stack +# Chromium's v120 release branch was 6099. This is used to pre-stack # the previous release branch's commits onto the appropriate base commit # (the first common commit between trunk and the release branch). -export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6045" +export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6099" -# New target release branch for v120 is branch-heads/6099. This is used +# New target release branch for v121 is branch-heads/6167. This is used # to calculate the next upstream commit. -export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6099" +export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6167" # For local development 'mozpatches' is fine for a branch name, but when # pushing the patch stack to github, it should be named something like -# 'moz-mods-chr120-for-rel124'. +# 'moz-mods-chr121-for-rel125'. export MOZ_LIBWEBRTC_BRANCH="mozpatches" # After elm has been merged to mozilla-central, the patch stack in # moz-libwebrtc should be pushed to github. The script # push_official_branch.sh uses this branch name when pushing to the # public repo. -export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr120-for-rel124" +export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr121-for-rel125" diff --git a/dom/media/webrtc/third_party_build/filter_git_changes.py b/dom/media/webrtc/third_party_build/filter_git_changes.py index ee6cdbcbd9..86358b0b01 100644 --- a/dom/media/webrtc/third_party_build/filter_git_changes.py +++ b/dom/media/webrtc/third_party_build/filter_git_changes.py @@ -48,7 +48,7 @@ def filter_git_changes(github_path, commit_sha, diff_filter): # out the excluded directory paths (note the lack of trailing '$' # in the regex). regex_excludes = "|".join( - ["^(M|A|D|R\d\d\d)\t{}".format(i) for i in exclude_dir_list] + ["^(M|A|D|R\\d\\d\\d)\t{}".format(i) for i in exclude_dir_list] ) files_not_excluded = [ path for path in changed_files if not re.findall(regex_excludes, path) diff --git a/dom/media/webrtc/third_party_build/prep_repo.sh b/dom/media/webrtc/third_party_build/prep_repo.sh index b601ebc808..8cd9ff6816 100644 --- a/dom/media/webrtc/third_party_build/prep_repo.sh +++ b/dom/media/webrtc/third_party_build/prep_repo.sh @@ -69,6 +69,12 @@ rm -f *.patch CHERRY_PICK_BASE=`git merge-base branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM master` echo "common commit: $CHERRY_PICK_BASE" +# find the last upstream commit used by the previous update, so we don't +# accidentally grab release branch commits that were added after we started +# the previous update. +LAST_UPSTREAM_COMMIT_SHA=`tail -1 $CURRENT_DIR/third_party/libwebrtc/README.moz-ff-commit` +echo "previous update's last commit: $LAST_UPSTREAM_COMMIT_SHA" + # create a new branch at the common commit and checkout the new branch ERROR_HELP=$" Unable to create branch '$MOZ_LIBWEBRTC_BRANCH'. This probably means @@ -89,7 +95,7 @@ git checkout $MOZ_LIBWEBRTC_BRANCH rm -f $TMP_DIR/*.patch $TMP_DIR/*.patch.bak # grab the patches for all the commits in chrome's release branch for libwebrtc -git format-patch -o $TMP_DIR -k $CHERRY_PICK_BASE..branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM +git format-patch -o $TMP_DIR -k $CHERRY_PICK_BASE..$LAST_UPSTREAM_COMMIT_SHA # tweak the release branch commit summaries to show they were cherry picked sed -i.bak -e "/^Subject: / s/^Subject: /Subject: (cherry-pick-branch-heads\/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM) /" $TMP_DIR/*.patch git am $TMP_DIR/*.patch # applies to branch mozpatches diff --git a/dom/media/webrtc/third_party_build/save_patch_stack.py b/dom/media/webrtc/third_party_build/save_patch_stack.py index 9b080ac0ff..907b90f593 100644 --- a/dom/media/webrtc/third_party_build/save_patch_stack.py +++ b/dom/media/webrtc/third_party_build/save_patch_stack.py @@ -52,7 +52,7 @@ def save_patch_stack( # remove the commit summary from the file name patches_to_rename = os.listdir(patch_directory) for file in patches_to_rename: - shortened_name = re.sub("^(\d\d\d\d)-.*\.patch", "\\1.patch", file) + shortened_name = re.sub(r"^(\d\d\d\d)-.*\.patch", "\\1.patch", file) os.rename( os.path.join(patch_directory, file), os.path.join(patch_directory, shortened_name), diff --git a/dom/media/webrtc/third_party_build/verify_vendoring.sh b/dom/media/webrtc/third_party_build/verify_vendoring.sh index 869a2c8c67..008ab0d5db 100644 --- a/dom/media/webrtc/third_party_build/verify_vendoring.sh +++ b/dom/media/webrtc/third_party_build/verify_vendoring.sh @@ -17,12 +17,12 @@ echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC" echo "MOZ_LIBWEBRTC_BRANCH: $MOZ_LIBWEBRTC_BRANCH" echo "MOZ_FASTFORWARD_BUG: $MOZ_FASTFORWARD_BUG" -TIP_SHA=`hg id -r tip | awk '{ print $1; }'` -echo "TIP_SHA: $TIP_SHA" +CURRENT_SHA=`hg id -r . | awk '{ print $1; }'` +echo "CURRENT_SHA: $CURRENT_SHA" # we grab the entire firstline description for convenient logging -LAST_PATCHSTACK_UPDATE_COMMIT=`hg log --template "{node|short} {desc|firstline}\n" \ - --include "third_party/libwebrtc/moz-patch-stack/*.patch" | head -1` +LAST_PATCHSTACK_UPDATE_COMMIT=`hg log -r ::. --template "{node|short} {desc|firstline}\n" \ + --include "third_party/libwebrtc/moz-patch-stack/*.patch" | tail -1` echo "LAST_PATCHSTACK_UPDATE_COMMIT: $LAST_PATCHSTACK_UPDATE_COMMIT" LAST_PATCHSTACK_UPDATE_COMMIT_SHA=`echo $LAST_PATCHSTACK_UPDATE_COMMIT \ @@ -31,7 +31,7 @@ echo "LAST_PATCHSTACK_UPDATE_COMMIT_SHA: $LAST_PATCHSTACK_UPDATE_COMMIT_SHA" # grab the oldest, non "Vendor from libwebrtc" line OLDEST_CANDIDATE_COMMIT=`hg log --template "{node|short} {desc|firstline}\n" \ - -r $LAST_PATCHSTACK_UPDATE_COMMIT_SHA::tip \ + -r $LAST_PATCHSTACK_UPDATE_COMMIT_SHA::. \ | grep -v "Vendor libwebrtc from" | head -1` echo "OLDEST_CANDIDATE_COMMIT: $OLDEST_CANDIDATE_COMMIT" @@ -39,9 +39,9 @@ OLDEST_CANDIDATE_SHA=`echo $OLDEST_CANDIDATE_COMMIT \ | awk '{ print $1; }'` echo "OLDEST_CANDIDATE_SHA: $OLDEST_CANDIDATE_SHA" -EXTRACT_COMMIT_RANGE="{start-commit-sha}::tip" -if [ "x$TIP_SHA" != "x$OLDEST_CANDIDATE_SHA" ]; then - EXTRACT_COMMIT_RANGE="$OLDEST_CANDIDATE_SHA::tip" +EXTRACT_COMMIT_RANGE="{start-commit-sha}::." +if [ "x$CURRENT_SHA" != "x$OLDEST_CANDIDATE_SHA" ]; then + EXTRACT_COMMIT_RANGE="$OLDEST_CANDIDATE_SHA::." echo "EXTRACT_COMMIT_RANGE: $EXTRACT_COMMIT_RANGE" fi diff --git a/dom/media/webrtc/transport/nricectx.cpp b/dom/media/webrtc/transport/nricectx.cpp index f30c2734c2..7c71c0ab06 100644 --- a/dom/media/webrtc/transport/nricectx.cpp +++ b/dom/media/webrtc/transport/nricectx.cpp @@ -257,14 +257,14 @@ nsresult NrIceTurnServer::ToNicerTurnStruct(nr_ice_turn_server* server) const { } NrIceCtx::NrIceCtx(const std::string& name) - : connection_state_(ICE_CTX_INIT), - gathering_state_(ICE_CTX_GATHER_INIT), - name_(name), + : name_(name), ice_controlling_set_(false), ctx_(nullptr), peer_(nullptr), ice_handler_vtbl_(nullptr), ice_handler_(nullptr), + ice_gather_handler_vtbl_(nullptr), + ice_gather_handler_(nullptr), trickle_(true), config_(), nat_(nullptr), @@ -343,13 +343,10 @@ void NrIceCtx::DestroyStream(const std::string& id) { auto it = streams_.find(id); if (it != streams_.end()) { auto preexisting_stream = it->second; + SignalConnectionStateChange(preexisting_stream, ICE_CTX_CLOSED); streams_.erase(it); preexisting_stream->Close(); } - - if (streams_.empty()) { - SetGatheringState(ICE_CTX_GATHER_INIT); - } } // Handler callbacks @@ -377,6 +374,7 @@ int NrIceCtx::stream_ready(void* obj, nr_ice_media_stream* stream) { MOZ_ASSERT(s); s->Ready(stream); + ctx->SignalConnectionStateChange(s, ICE_CTX_CONNECTED); return 0; } @@ -393,44 +391,101 @@ int NrIceCtx::stream_failed(void* obj, nr_ice_media_stream* stream) { // Streams which do not exist should never fail. MOZ_ASSERT(s); - ctx->SetConnectionState(ICE_CTX_FAILED); + if (!ctx->dumped_rlog_) { + // Do this at most once per ctx + ctx->dumped_rlog_ = true; + MOZ_MTLOG(ML_INFO, + "NrIceCtx(" << ctx->name_ << "): dumping r_log ringbuffer... "); + std::deque<std::string> logs; + RLogConnector::GetInstance()->GetAny(0, &logs); + for (auto& log : logs) { + MOZ_MTLOG(ML_INFO, log); + } + } + s->Failed(); + ctx->SignalConnectionStateChange(s, ICE_CTX_FAILED); return 0; } -int NrIceCtx::ice_checking(void* obj, nr_ice_peer_ctx* pctx) { - MOZ_MTLOG(ML_DEBUG, "ice_checking called"); +int NrIceCtx::stream_checking(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_checking called"); + MOZ_ASSERT(!stream->local_stream); + MOZ_ASSERT(!stream->obsolete); // Get the ICE ctx NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); - ctx->SetConnectionState(ICE_CTX_CHECKING); + MOZ_ASSERT(s); + if (!s->AnyGenerationIsConnected()) { + // the checking state only applies if we aren't connected + ctx->SignalConnectionStateChange(s, ICE_CTX_CHECKING); + } return 0; } -int NrIceCtx::ice_connected(void* obj, nr_ice_peer_ctx* pctx) { - MOZ_MTLOG(ML_DEBUG, "ice_connected called"); +int NrIceCtx::stream_disconnected(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_disconnected called"); + MOZ_ASSERT(!stream->local_stream); + MOZ_ASSERT(!stream->obsolete); // Get the ICE ctx NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); - // This is called even on failed contexts. - if (ctx->connection_state() != ICE_CTX_FAILED) { - ctx->SetConnectionState(ICE_CTX_CONNECTED); - } + MOZ_ASSERT(s); + ctx->SignalConnectionStateChange(s, ICE_CTX_DISCONNECTED); return 0; } -int NrIceCtx::ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) { - MOZ_MTLOG(ML_DEBUG, "ice_disconnected called"); +int NrIceCtx::stream_gathering(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_gathering called"); + MOZ_ASSERT(!stream->local_stream); + MOZ_ASSERT(!stream->obsolete); + + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); + + MOZ_ASSERT(s); + + s->OnGatheringStarted(stream); + return 0; +} + +int NrIceCtx::stream_gathered(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_gathered called"); + MOZ_ASSERT(!stream->local_stream); // Get the ICE ctx NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); - ctx->SetConnectionState(ICE_CTX_DISCONNECTED); + // We get this callback for destroyed streams in some cases + if (s) { + s->OnGatheringComplete(stream); + } + return 0; +} + +int NrIceCtx::ice_checking(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_checking called"); + // We don't use this; we react to the stream-specific callbacks instead + return 0; +} + +int NrIceCtx::ice_connected(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_connected called"); + // We don't use this; we react to the stream-specific callbacks instead + return 0; +} +int NrIceCtx::ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_disconnected called"); + // We don't use this; we react to the stream-specific callbacks instead return 0; } @@ -466,7 +521,6 @@ void NrIceCtx::trickle_cb(void* arg, nr_ice_ctx* ice_ctx, } if (!candidate) { - s->SignalCandidate(s, "", stream->ufrag, "", ""); return; } @@ -587,11 +641,20 @@ void NrIceCtx::SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) { } bool NrIceCtx::Initialize() { + // Create the gather handler objects + ice_gather_handler_vtbl_ = new nr_ice_gather_handler_vtbl(); + ice_gather_handler_vtbl_->stream_gathering = &NrIceCtx::stream_gathering; + ice_gather_handler_vtbl_->stream_gathered = &NrIceCtx::stream_gathered; + ice_gather_handler_ = new nr_ice_gather_handler(); + ice_gather_handler_->vtbl = ice_gather_handler_vtbl_; + ice_gather_handler_->obj = this; + // Create the ICE context int r; UINT4 flags = NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION; - r = nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ctx_); + r = nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, + ice_gather_handler_, &ctx_); if (r) { MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name_ << "'"); @@ -634,6 +697,16 @@ bool NrIceCtx::Initialize() { ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair; ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready; ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed; + ice_handler_vtbl_->stream_checking = &NrIceCtx::stream_checking; + ice_handler_vtbl_->stream_disconnected = &NrIceCtx::stream_disconnected; + // stream_gathering and stream_gathered do not go here, since those are tied + // to the _local_ nr_ice_media_stream in nICEr. nICEr allows a local + // nr_ice_media_stream (which has a single set of candidates, and therefore a + // single gathering state) to be associated with multiple remote + // nr_ice_media_streams (which each have their own ICE connection state) + // because it allows forking. We never encounter forking, so these will be + // one-to-one in practice, but the architecture in nICEr means we have to set + // up these callbacks on the nr_ice_ctx, not the nr_ice_peer_ctx. ice_handler_vtbl_->ice_connected = &NrIceCtx::ice_connected; ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd; ice_handler_vtbl_->ice_checking = &NrIceCtx::ice_checking; @@ -720,8 +793,13 @@ NrIceStats NrIceCtx::Destroy() { delete ice_handler_vtbl_; delete ice_handler_; + delete ice_gather_handler_vtbl_; + delete ice_gather_handler_; + ice_handler_vtbl_ = nullptr; ice_handler_ = nullptr; + ice_gather_handler_vtbl_ = nullptr; + ice_gather_handler_ = nullptr; proxy_config_ = nullptr; streams_.clear(); @@ -854,15 +932,10 @@ nsresult NrIceCtx::StartGathering(bool default_route_only, // finished. int r = nr_ice_gather(ctx_, &NrIceCtx::gather_cb, this); - if (!r) { - SetGatheringState(ICE_CTX_GATHER_COMPLETE); - } else if (r == R_WOULDBLOCK) { - SetGatheringState(ICE_CTX_GATHER_STARTED); - } else { - SetGatheringState(ICE_CTX_GATHER_COMPLETE); + if (r && r != R_WOULDBLOCK) { MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't gather ICE candidates for '" << name_ << "', error=" << r); - SetConnectionState(ICE_CTX_FAILED); + SignalAllStreamsFailed(); return NS_ERROR_FAILURE; } @@ -940,7 +1013,7 @@ nsresult NrIceCtx::StartChecks() { r = nr_ice_peer_ctx_pair_candidates(peer_); if (r) { MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't pair candidates on " << name_); - SetConnectionState(ICE_CTX_FAILED); + SignalAllStreamsFailed(); return NS_ERROR_FAILURE; } @@ -952,7 +1025,7 @@ nsresult NrIceCtx::StartChecks() { } else { MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't start peer checks on " << name_); - SetConnectionState(ICE_CTX_FAILED); + SignalAllStreamsFailed(); return NS_ERROR_FAILURE; } } @@ -961,18 +1034,21 @@ nsresult NrIceCtx::StartChecks() { } void NrIceCtx::gather_cb(NR_SOCKET s, int h, void* arg) { - NrIceCtx* ctx = static_cast<NrIceCtx*>(arg); + MOZ_MTLOG(ML_DEBUG, "gather_cb called"); + // We don't use this; we react to the stream-specific callbacks instead +} - ctx->SetGatheringState(ICE_CTX_GATHER_COMPLETE); +void NrIceCtx::SignalAllStreamsFailed() { + for (auto& [id, stream] : streams_) { + Unused << id; + stream->Failed(); + SignalConnectionStateChange(stream, ICE_CTX_FAILED); + } } void NrIceCtx::UpdateNetworkState(bool online) { MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): updating network state to " << (online ? "online" : "offline")); - if (connection_state_ == ICE_CTX_CLOSED) { - return; - } - if (online) { nr_ice_peer_ctx_refresh_consent_all_streams(peer_); } else { @@ -980,36 +1056,6 @@ void NrIceCtx::UpdateNetworkState(bool online) { } } -void NrIceCtx::SetConnectionState(ConnectionState state) { - if (state == connection_state_) return; - - MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): state " << connection_state_ - << "->" << state); - connection_state_ = state; - - if (connection_state_ == ICE_CTX_FAILED) { - MOZ_MTLOG(ML_INFO, - "NrIceCtx(" << name_ << "): dumping r_log ringbuffer... "); - std::deque<std::string> logs; - RLogConnector::GetInstance()->GetAny(0, &logs); - for (auto& log : logs) { - MOZ_MTLOG(ML_INFO, log); - } - } - - SignalConnectionStateChange(this, state); -} - -void NrIceCtx::SetGatheringState(GatheringState state) { - if (state == gathering_state_) return; - - MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << name_ << "): gathering state " - << gathering_state_ << "->" << state); - gathering_state_ = state; - - SignalGatheringStateChange(this, state); -} - void NrIceCtx::GenerateObfuscatedAddress(nr_ice_candidate* candidate, std::string* mdns_address, std::string* actual_address) { diff --git a/dom/media/webrtc/transport/nricectx.h b/dom/media/webrtc/transport/nricectx.h index a0a0b5b772..01ad6b5dbd 100644 --- a/dom/media/webrtc/transport/nricectx.h +++ b/dom/media/webrtc/transport/nricectx.h @@ -74,6 +74,8 @@ typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx; typedef struct nr_ice_media_stream_ nr_ice_media_stream; typedef struct nr_ice_handler_ nr_ice_handler; typedef struct nr_ice_handler_vtbl_ nr_ice_handler_vtbl; +typedef struct nr_ice_gather_handler_ nr_ice_gather_handler; +typedef struct nr_ice_gather_handler_vtbl_ nr_ice_gather_handler_vtbl; typedef struct nr_ice_candidate_ nr_ice_candidate; typedef struct nr_ice_cand_pair_ nr_ice_cand_pair; typedef struct nr_ice_stun_server_ nr_ice_stun_server; @@ -200,12 +202,6 @@ class NrIceCtx { ICE_CTX_CLOSED }; - enum GatheringState { - ICE_CTX_GATHER_INIT, - ICE_CTX_GATHER_STARTED, - ICE_CTX_GATHER_COMPLETE - }; - enum Controlling { ICE_CONTROLLING, ICE_CONTROLLED }; enum Policy { ICE_POLICY_RELAY, ICE_POLICY_NO_HOST, ICE_POLICY_ALL }; @@ -294,12 +290,6 @@ class NrIceCtx { // The name of the ctx const std::string& name() const { return name_; } - // Current state - ConnectionState connection_state() const { return connection_state_; } - - // Current state - GatheringState gathering_state() const { return gathering_state_; } - // Get the global attributes std::vector<std::string> GetGlobalAttributes(); @@ -351,9 +341,7 @@ class NrIceCtx { // Signals to indicate events. API users can (and should) // register for these. - sigslot::signal2<NrIceCtx*, NrIceCtx::GatheringState> - SignalGatheringStateChange; - sigslot::signal2<NrIceCtx*, NrIceCtx::ConnectionState> + sigslot::signal2<NrIceMediaStream*, NrIceCtx::ConnectionState> SignalConnectionStateChange; // The thread to direct method calls to @@ -375,7 +363,11 @@ class NrIceCtx { static int select_pair(void* obj, nr_ice_media_stream* stream, int component_id, nr_ice_cand_pair** potentials, int potential_ct); + static int stream_gathering(void* obj, nr_ice_media_stream* stream); + static int stream_gathered(void* obj, nr_ice_media_stream* stream); + static int stream_checking(void* obj, nr_ice_media_stream* stream); static int stream_ready(void* obj, nr_ice_media_stream* stream); + static int stream_disconnected(void* obj, nr_ice_media_stream* stream); static int stream_failed(void* obj, nr_ice_media_stream* stream); static int ice_checking(void* obj, nr_ice_peer_ctx* pctx); static int ice_connected(void* obj, nr_ice_peer_ctx* pctx); @@ -387,28 +379,25 @@ class NrIceCtx { nr_ice_media_stream* stream, int component_id, nr_ice_candidate* candidate); + void SignalAllStreamsFailed(); + // Find a media stream by stream ptr. Gross RefPtr<NrIceMediaStream> FindStream(nr_ice_media_stream* stream); - // Set the state - void SetConnectionState(ConnectionState state); - - // Set the state - void SetGatheringState(GatheringState state); - void GenerateObfuscatedAddress(nr_ice_candidate* candidate, std::string* mdns_address, std::string* actual_address); - ConnectionState connection_state_; - GatheringState gathering_state_; + bool dumped_rlog_ = false; const std::string name_; bool ice_controlling_set_; std::map<std::string, RefPtr<NrIceMediaStream>> streams_; nr_ice_ctx* ctx_; nr_ice_peer_ctx* peer_; - nr_ice_handler_vtbl* ice_handler_vtbl_; // Must be pointer - nr_ice_handler* ice_handler_; // Must be pointer + nr_ice_handler_vtbl* ice_handler_vtbl_; // Must be pointer + nr_ice_handler* ice_handler_; // Must be pointer + nr_ice_gather_handler_vtbl* ice_gather_handler_vtbl_; // Must be pointer + nr_ice_gather_handler* ice_gather_handler_; // Must be pointer bool trickle_; nsCOMPtr<nsIEventTarget> sts_target_; // The thread to run on Config config_; diff --git a/dom/media/webrtc/transport/nricemediastream.cpp b/dom/media/webrtc/transport/nricemediastream.cpp index 426aee230e..a3a0c147c9 100644 --- a/dom/media/webrtc/transport/nricemediastream.cpp +++ b/dom/media/webrtc/transport/nricemediastream.cpp @@ -204,11 +204,17 @@ nsresult NrIceMediaStream::ConnectToPeer( MOZ_ASSERT(stream_); if (Matches(old_stream_, ufrag, pwd)) { + bool wasGathering = !AllGenerationsDoneGathering(); // (We swap before we close so we never have stream_ == nullptr) MOZ_MTLOG(ML_DEBUG, "Rolling back to old stream ufrag=" << ufrag << " " << name_); std::swap(stream_, old_stream_); CloseStream(&old_stream_); + if (wasGathering && AllGenerationsDoneGathering()) { + // Special case; we do not need to send another empty candidate, but we + // do need to handle the transition from gathering to complete. + SignalGatheringStateChange(GetId(), ICE_STREAM_GATHER_COMPLETE); + } } else if (old_stream_) { // Right now we wait for ICE to complete before closing the old stream. // It might be worth it to close it sooner, but we don't want to close it @@ -273,6 +279,10 @@ nsresult NrIceMediaStream::SetIceCredentials(const std::string& ufrag, } state_ = ICE_CONNECTING; + + MOZ_MTLOG(ML_WARNING, + "SetIceCredentials new=" << stream_ << " old=" << old_stream_); + return NS_OK; } @@ -661,6 +671,21 @@ void NrIceMediaStream::Failed() { } } +void NrIceMediaStream::OnGatheringStarted(nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_WARNING, "OnGatheringStarted called for " << stream); + SignalGatheringStateChange(GetId(), ICE_STREAM_GATHER_STARTED); +} + +void NrIceMediaStream::OnGatheringComplete(nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_WARNING, "OnGatheringComplete called for " << stream); + // Spec says to queue two separate tasks; one for the empty candidate, and + // the next for the state change. + SignalCandidate(this, "", stream->ufrag, "", ""); + if (AllGenerationsDoneGathering()) { + SignalGatheringStateChange(GetId(), ICE_STREAM_GATHER_COMPLETE); + } +} + void NrIceMediaStream::Close() { MOZ_MTLOG(ML_DEBUG, "Marking stream closed '" << name_ << "'"); state_ = ICE_CLOSED; @@ -709,4 +734,34 @@ nr_ice_media_stream* NrIceMediaStream::GetStreamForRemoteUfrag( return nullptr; } +bool NrIceMediaStream::AllGenerationsDoneGathering() const { + if (stream_ && !nr_ice_media_stream_is_done_gathering(stream_)) { + return false; + } + if (old_stream_ && !nr_ice_media_stream_is_done_gathering(old_stream_)) { + return false; + } + return true; +} + +bool NrIceMediaStream::AnyGenerationIsConnected() const { + nr_ice_media_stream* peer_stream = nullptr; + + if (stream_ && + !nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream)) { + if (peer_stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED && + !peer_stream->disconnected) { + return true; + } + } + + if (old_stream_ && + !nr_ice_peer_ctx_find_pstream(ctx_->peer(), old_stream_, &peer_stream)) { + if (peer_stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED && + !peer_stream->disconnected) { + return true; + } + } + return false; +} } // namespace mozilla diff --git a/dom/media/webrtc/transport/nricemediastream.h b/dom/media/webrtc/transport/nricemediastream.h index 406373573c..d434dd34dd 100644 --- a/dom/media/webrtc/transport/nricemediastream.h +++ b/dom/media/webrtc/transport/nricemediastream.h @@ -126,6 +126,12 @@ struct NrIceCandidatePair { class NrIceMediaStream { public: + enum GatheringState { + ICE_STREAM_GATHER_INIT, + ICE_STREAM_GATHER_STARTED, + ICE_STREAM_GATHER_COMPLETE + }; + NrIceMediaStream(NrIceCtx* ctx, const std::string& id, const std::string& name, size_t components); @@ -182,6 +188,9 @@ class NrIceMediaStream { void Ready(nr_ice_media_stream* stream); void Failed(); + void OnGatheringStarted(nr_ice_media_stream* stream); + void OnGatheringComplete(nr_ice_media_stream* stream); + // Close the stream. Called by the NrIceCtx. // Different from the destructor because other people // might be holding RefPtrs but we want those writes to fail once @@ -192,9 +201,14 @@ class NrIceMediaStream { // the candidate belongs to. const std::string& GetId() const { return id_; } + bool AllGenerationsDoneGathering() const; + bool AnyGenerationIsConnected() const; + sigslot::signal5<NrIceMediaStream*, const std::string&, const std::string&, const std::string&, const std::string&> SignalCandidate; // A new ICE candidate: + sigslot::signal2<const std::string&, NrIceMediaStream::GatheringState> + SignalGatheringStateChange; sigslot::signal1<NrIceMediaStream*> SignalReady; // Candidate pair ready. sigslot::signal1<NrIceMediaStream*> SignalFailed; // Candidate pair failed. diff --git a/dom/media/webrtc/transport/test/ice_unittest.cpp b/dom/media/webrtc/transport/test/ice_unittest.cpp index 4d097fafa3..50febb3cdd 100644 --- a/dom/media/webrtc/transport/test/ice_unittest.cpp +++ b/dom/media/webrtc/transport/test/ice_unittest.cpp @@ -304,6 +304,8 @@ class SchedulableTrickleCandidate { } void Schedule(unsigned int ms) { + std::cerr << "Scheduling " << Candidate() << " in " << ms << "ms" + << std::endl; test_utils_->SyncDispatchToSTS( WrapRunnable(this, &SchedulableTrickleCandidate::Schedule_s, ms)); } @@ -355,10 +357,7 @@ class IceTestPeer : public sigslot::has_slots<> { offerer_(offerer), stream_counter_(0), shutting_down_(false), - gathering_complete_(false), ready_ct_(0), - ice_connected_(false), - ice_failed_(false), ice_reached_checking_(false), received_(0), sent_(0), @@ -372,8 +371,6 @@ class IceTestPeer : public sigslot::has_slots<> { simulate_ice_lite_(false), nat_(new TestNat), test_utils_(utils) { - ice_ctx_->SignalGatheringStateChange.connect( - this, &IceTestPeer::GatheringStateChange); ice_ctx_->SignalConnectionStateChange.connect( this, &IceTestPeer::ConnectionStateChange); @@ -426,6 +423,10 @@ class IceTestPeer : public sigslot::has_slots<> { stream->SignalReady.connect(this, &IceTestPeer::StreamReady); stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed); stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived); + stream->SignalGatheringStateChange.connect( + this, &IceTestPeer::GatheringStateChange); + mConnectionStates[id] = NrIceCtx::ICE_CTX_INIT; + mGatheringStates[id] = NrIceMediaStream::ICE_STREAM_GATHER_INIT; } void AddStream(int components) { @@ -434,7 +435,10 @@ class IceTestPeer : public sigslot::has_slots<> { } void RemoveStream_s(size_t index) { - ice_ctx_->DestroyStream(MakeTransportId(index)); + const std::string id = MakeTransportId(index); + ice_ctx_->DestroyStream(id); + mConnectionStates.erase(id); + mGatheringStates.erase(id); } void RemoveStream(size_t index) { @@ -650,7 +654,15 @@ class IceTestPeer : public sigslot::has_slots<> { return host_net; } - bool gathering_complete() { return gathering_complete_; } + bool gathering_complete() { + for (const auto& [id, state] : mGatheringStates) { + Unused << id; + if (state != NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE) { + return false; + } + } + return true; + } int ready_ct() { return ready_ct_; } bool is_ready_s(size_t index) { auto media_stream = GetStream_s(index); @@ -666,8 +678,33 @@ class IceTestPeer : public sigslot::has_slots<> { WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream)); return result; } - bool ice_connected() { return ice_connected_; } - bool ice_failed() { return ice_failed_; } + bool ice_connected() { + for (const auto& [id, state] : mConnectionStates) { + if (state != NrIceCtx::ICE_CTX_CONNECTED) { + return false; + } + } + return true; + } + bool ice_failed() { + for (const auto& [id, state] : mConnectionStates) { + if (state == NrIceCtx::ICE_CTX_FAILED) { + return true; + } + } + return false; + } + bool ice_checking() { + if (ice_failed() || ice_connected()) { + return false; + } + for (const auto& [id, state] : mConnectionStates) { + if (state == NrIceCtx::ICE_CTX_CHECKING) { + return true; + } + } + return false; + } bool ice_reached_checking() { return ice_reached_checking_; } size_t received() { return received_; } size_t sent() { return sent_; } @@ -680,13 +717,16 @@ class IceTestPeer : public sigslot::has_slots<> { void RestartIce_s() { for (auto& stream : ice_ctx_->GetStreams()) { SetIceCredentials_s(*stream); + mConnectionStates[stream->GetId()] = NrIceCtx::ICE_CTX_INIT; + mGatheringStates[stream->GetId()] = + NrIceMediaStream::ICE_STREAM_GATHER_INIT; } // take care of some local bookkeeping ready_ct_ = 0; - gathering_complete_ = false; - ice_connected_ = false; - ice_failed_ = false; - ice_reached_checking_ = false; + // We do not unset ice_reached_checking_ here, since we do not expect + // ICE to return to checking in an ICE restart, because the ICE stack + // continues using the old streams (which are probably connected) until the + // new ones are connected. remote_ = nullptr; } @@ -709,9 +749,6 @@ class IceTestPeer : public sigslot::has_slots<> { remote_ = remote; trickle_mode_ = trickle_mode; - ice_connected_ = false; - ice_failed_ = false; - ice_reached_checking_ = false; res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes()); ASSERT_FALSE(remote->simulate_ice_lite_ && (ice_ctx_->GetControlling() == NrIceCtx::ICE_CONTROLLED)); @@ -793,8 +830,11 @@ class IceTestPeer : public sigslot::has_slots<> { auto stream = GetStream_s(index); if (!stream) { // stream might have gone away before the trickle timer popped + std::cerr << "Trickle candidate has no stream: " << index << std::endl; return NS_OK; } + std::cerr << "Trickle candidate for " << index << " (" << stream->GetId() + << "):" << candidate << std::endl; return stream->ParseTrickleCandidate(candidate, ufrag, ""); } @@ -940,16 +980,18 @@ class IceTestPeer : public sigslot::has_slots<> { } // Handle events - void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) { + void GatheringStateChange(const std::string& aTransportId, + NrIceMediaStream::GatheringState state) { if (shutting_down_) { return; } - if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) { + mGatheringStates[aTransportId] = state; + + if (!gathering_complete()) { return; } std::cerr << name_ << " Gathering complete" << std::endl; - gathering_complete_ = true; std::cerr << name_ << " ATTRIBUTES:" << std::endl; for (const auto& stream : ice_ctx_->GetStreams()) { @@ -973,9 +1015,9 @@ class IceTestPeer : public sigslot::has_slots<> { if (candidate.empty()) { return; } - std::cerr << "Candidate for stream " << stream->name() + std::cerr << "Candidate for stream " << stream->GetId() << " initialized: " << candidate << std::endl; - candidates_[stream->name()].push_back(candidate); + candidates_[stream->GetId()].push_back(candidate); // If we are connected, then try to trickle to the other side. if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) { @@ -990,7 +1032,7 @@ class IceTestPeer : public sigslot::has_slots<> { return; } } - ADD_FAILURE() << "No matching stream found for " << stream; + ADD_FAILURE() << "No matching stream found for " << stream->GetId(); } } @@ -1133,32 +1175,45 @@ class IceTestPeer : public sigslot::has_slots<> { DumpCandidatePairs_s(stream); } - void ConnectionStateChange(NrIceCtx* ctx, NrIceCtx::ConnectionState state) { - (void)ctx; + void ConnectionStateChange(NrIceMediaStream* stream, + NrIceCtx::ConnectionState state) { + mConnectionStates[stream->GetId()] = state; + if (ice_checking()) { + ice_reached_checking_ = true; + } + switch (state) { case NrIceCtx::ICE_CTX_INIT: break; case NrIceCtx::ICE_CTX_CHECKING: - std::cerr << name_ << " ICE reached checking" << std::endl; - ice_reached_checking_ = true; + std::cerr << name_ << " ICE reached checking (" << stream->GetId() + << ")" << std::endl; + MOZ_ASSERT(ice_reached_checking_); break; case NrIceCtx::ICE_CTX_CONNECTED: - std::cerr << name_ << " ICE connected" << std::endl; - ice_connected_ = true; + std::cerr << name_ << " ICE reached connected (" << stream->GetId() + << ")" << std::endl; + MOZ_ASSERT(ice_reached_checking_); break; case NrIceCtx::ICE_CTX_COMPLETED: - std::cerr << name_ << " ICE completed" << std::endl; + std::cerr << name_ << " ICE reached completed (" << stream->GetId() + << ")" << std::endl; + MOZ_ASSERT(ice_reached_checking_); break; case NrIceCtx::ICE_CTX_FAILED: - std::cerr << name_ << " ICE failed" << std::endl; - ice_failed_ = true; + std::cerr << name_ << " ICE reached failed (" << stream->GetId() << ")" + << std::endl; + MOZ_ASSERT(ice_reached_checking_); break; case NrIceCtx::ICE_CTX_DISCONNECTED: - std::cerr << name_ << " ICE disconnected" << std::endl; - ice_connected_ = false; + std::cerr << name_ << " ICE reached disconnected (" << stream->GetId() + << ")" << std::endl; + MOZ_ASSERT(ice_reached_checking_); + break; + case NrIceCtx::ICE_CTX_CLOSED: + std::cerr << name_ << " ICE reached closed (" << stream->GetId() << ")" + << std::endl; break; - default: - MOZ_CRASH(); } } @@ -1326,10 +1381,9 @@ class IceTestPeer : public sigslot::has_slots<> { std::map<std::string, std::pair<std::string, std::string>> mOldIceCredentials; size_t stream_counter_; bool shutting_down_; - bool gathering_complete_; + std::map<std::string, NrIceCtx::ConnectionState> mConnectionStates; + std::map<std::string, NrIceMediaStream::GatheringState> mGatheringStates; int ready_ct_; - bool ice_connected_; - bool ice_failed_; bool ice_reached_checking_; size_t received_; size_t sent_; @@ -1686,10 +1740,8 @@ class WebRtcIceConnectTest : public StunTest { TrickleMode mode = TRICKLE_NONE) { ASSERT_TRUE(caller->ready_ct() == 0); ASSERT_TRUE(caller->ice_connected() == 0); - ASSERT_TRUE(caller->ice_reached_checking() == 0); ASSERT_TRUE(callee->ready_ct() == 0); ASSERT_TRUE(callee->ice_connected() == 0); - ASSERT_TRUE(callee->ice_reached_checking() == 0); // IceTestPeer::Connect grabs attributes from the first arg, and // gives them to |this|, meaning that callee->Connect(caller, ...) @@ -3361,6 +3413,8 @@ TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamDuringICE) { RealisticTrickleDelay(p1_->ControlTrickle(0)); RealisticTrickleDelay(p2_->ControlTrickle(0)); AddStream(1); + ASSERT_TRUE(Gather()); + ConnectTrickle(); RealisticTrickleDelay(p1_->ControlTrickle(1)); RealisticTrickleDelay(p2_->ControlTrickle(1)); WaitForConnected(1000); diff --git a/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp index b55b05f10c..20754f033b 100644 --- a/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp +++ b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp @@ -73,7 +73,8 @@ class IcePeer { peer_ctx_(nullptr), nat_(nat), test_utils_(test_utils) { - nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ice_ctx_); + nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, nullptr, + &ice_ctx_); if (nat_) { nr_socket_factory* factory; diff --git a/dom/media/webrtc/transport/test/transport_unittests.cpp b/dom/media/webrtc/transport/test/transport_unittests.cpp index 7729151ade..0e9702ced8 100644 --- a/dom/media/webrtc/transport/test/transport_unittests.cpp +++ b/dom/media/webrtc/transport/test/transport_unittests.cpp @@ -586,16 +586,15 @@ class TransportTestPeer : public sigslot::has_slots<> { void InitIce() { nsresult res; - // Attach our slots - ice_ctx_->SignalGatheringStateChange.connect( - this, &TransportTestPeer::GatheringStateChange); - char name[100]; snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(), (int)streams_.size()); // Create the media stream RefPtr<NrIceMediaStream> stream = ice_ctx_->CreateStream(name, name, 1); + // Attach our slots + stream->SignalGatheringStateChange.connect( + this, &TransportTestPeer::GatheringStateChange); ASSERT_TRUE(stream != nullptr); stream->SetIceCredentials("ufrag", "pass"); @@ -639,9 +638,12 @@ class TransportTestPeer : public sigslot::has_slots<> { << std::endl; } - void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) { - (void)ctx; - if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) { + void GatheringStateChange(const std::string& aTransportId, + NrIceMediaStream::GatheringState state) { + // We only use one stream, no need to check whether all streams are done + // gathering. + Unused << aTransportId; + if (state == NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE) { GatheringComplete(); } } diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c index 0d498845a4..b428264e5a 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c @@ -325,8 +325,9 @@ int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out) #endif /* USE_TURN */ #define MAXADDRS 100 /* Ridiculously high */ -int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) - { + int nr_ice_ctx_create(char* label, UINT4 flags, + nr_ice_gather_handler* gather_handler, + nr_ice_ctx** ctxp) { nr_ice_ctx *ctx=0; int r,_status; @@ -341,6 +342,8 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) if(!(ctx->label=r_strdup(label))) ABORT(R_NO_MEMORY); + ctx->gather_handler = gather_handler; + /* Get the STUN servers */ if(r=NR_reg_get_child_count(NR_ICE_REG_STUN_SRV_PRFX, (unsigned int *)&ctx->stun_server_ct_cfg)||ctx->stun_server_ct_cfg==0) { @@ -442,7 +445,7 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) int i; nr_ice_stun_id *id1,*id2; - ctx->done_cb = 0; + ctx->gather_done_cb = 0; ctx->trickle_cb = 0; STAILQ_FOREACH_SAFE(s1, &ctx->streams, entry, s2){ @@ -452,6 +455,8 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) RFREE(ctx->label); + ctx->gather_handler = 0; + RFREE(ctx->stun_servers_cfg); RFREE(ctx->local_addrs); @@ -539,20 +544,26 @@ void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg) } } - if (nr_ice_media_stream_is_done_gathering(stream) && - ctx->trickle_cb) { - ctx->trickle_cb(ctx->trickle_cb_arg, ctx, stream, component_id, NULL); + if (nr_ice_media_stream_is_done_gathering(stream)) { + if (ctx->gather_handler && ctx->gather_handler->vtbl->stream_gathered) { + ctx->gather_handler->vtbl->stream_gathered(ctx->gather_handler->obj, + stream); + } + if (ctx->trickle_cb) { + ctx->trickle_cb(ctx->trickle_cb_arg, ctx, stream, component_id, NULL); + } } if(ctx->uninitialized_candidates==0){ + assert(nr_ice_media_stream_is_done_gathering(stream)); r_log(LOG_ICE, LOG_INFO, "ICE(%s): All candidates initialized", ctx->label); - if (ctx->done_cb) { - ctx->done_cb(0,0,ctx->cb_arg); - } - else { + if (ctx->gather_done_cb) { + ctx->gather_done_cb(0, 0, ctx->cb_arg); + } else { r_log(LOG_ICE, LOG_INFO, - "ICE(%s): No done_cb. We were probably destroyed.", ctx->label); + "ICE(%s): No gather_done_cb. We were probably destroyed.", + ctx->label); } } else { @@ -850,8 +861,7 @@ int nr_ice_set_target_for_default_local_address_lookup(nr_ice_ctx *ctx, const ch return(_status); } -int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg) - { + int nr_ice_gather(nr_ice_ctx* ctx, NR_async_cb gather_done_cb, void* cb_arg) { int r,_status; nr_ice_media_stream *stream; nr_local_addr stun_addrs[MAXADDRS]; @@ -872,7 +882,7 @@ int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg) } r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Initializing candidates",ctx->label); - ctx->done_cb=done_cb; + ctx->gather_done_cb = gather_done_cb; ctx->cb_arg=cb_arg; /* Initialize all the media stream/component pairs */ diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h index 8b3081f567..4039c741ec 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h @@ -97,9 +97,23 @@ typedef struct nr_ice_stats_ { UINT2 turn_438s; } nr_ice_stats; +typedef struct nr_ice_gather_handler_vtbl_ { + /* This media stream is gathering */ + int (*stream_gathering)(void* obj, nr_ice_media_stream* stream); + + /* This media stream has finished gathering */ + int (*stream_gathered)(void* obj, nr_ice_media_stream* stream); +} nr_ice_gather_handler_vtbl; + +typedef struct nr_ice_gather_handler_ { + void* obj; + nr_ice_gather_handler_vtbl* vtbl; +} nr_ice_gather_handler; + struct nr_ice_ctx_ { UINT4 flags; char *label; + nr_ice_gather_handler* gather_handler; UINT4 Ta; @@ -129,7 +143,7 @@ struct nr_ice_ctx_ { nr_ice_peer_ctx_head peers; nr_ice_stun_id_head ids; - NR_async_cb done_cb; + NR_async_cb gather_done_cb; void *cb_arg; nr_ice_trickle_candidate_cb trickle_cb; @@ -141,7 +155,8 @@ struct nr_ice_ctx_ { nr_transport_addr *target_for_default_local_address_lookup; }; -int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp); +int nr_ice_ctx_create(char* label, UINT4 flags, + nr_ice_gather_handler* gather_handler, nr_ice_ctx** ctxp); int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char* ufrag, char* pwd, nr_ice_ctx **ctxp); #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION (1) #define NR_ICE_CTX_FLAGS_LITE (1<<1) @@ -156,7 +171,7 @@ void nr_ice_ctx_remove_flags(nr_ice_ctx *ctx, UINT4 flags); void nr_ice_ctx_destroy(nr_ice_ctx** ctxp); int nr_ice_set_local_addresses(nr_ice_ctx *ctx, nr_local_addr* stun_addrs, int stun_addr_ct); int nr_ice_set_target_for_default_local_address_lookup(nr_ice_ctx *ctx, const char *target_ip, UINT2 target_port); -int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg); +int nr_ice_gather(nr_ice_ctx* ctx, NR_async_cb gather_done_cb, void* cb_arg); int nr_ice_add_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand); void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg); int nr_ice_add_media_stream(nr_ice_ctx *ctx,const char *label,const char *ufrag,const char *pwd,int components, nr_ice_media_stream **streamp); diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h index 5a0690adad..ab3e41ef2d 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h @@ -56,9 +56,15 @@ int component_id, nr_ice_cand_pair **potentials,int potential_ct); */ int (*stream_ready)(void *obj, nr_ice_media_stream *stream); + /* This media stream is checking */ + int (*stream_checking)(void* obj, nr_ice_media_stream* stream); + /* This media stream has failed */ int (*stream_failed)(void *obj, nr_ice_media_stream *stream); + /* This media stream has disconnected */ + int (*stream_disconnected)(void* obj, nr_ice_media_stream* stream); + /* ICE is connected for this peer ctx */ int (*ice_connected)(void *obj, nr_ice_peer_ctx *pctx); diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c index 62bfbad629..90e278bedb 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c @@ -80,6 +80,7 @@ int nr_ice_media_stream_create(nr_ice_ctx *ctx,const char *label,const char *ufr stream->component_ct=components; stream->ice_state = NR_ICE_MEDIA_STREAM_UNPAIRED; stream->obsolete = 0; + stream->actually_started_checking = 0; stream->r2l_user = 0; stream->l2r_user = 0; stream->flags = ctx->flags; @@ -177,8 +178,20 @@ int nr_ice_media_stream_initialize(nr_ice_ctx *ctx, nr_ice_media_stream *stream) comp=STAILQ_NEXT(comp,entry); } + if (!nr_ice_media_stream_is_done_gathering(stream) && ctx->gather_handler && + ctx->gather_handler->vtbl->stream_gathering) { + ctx->gather_handler->vtbl->stream_gathering(ctx->gather_handler->obj, + stream); + } + _status=0; abort: + if (_status) { + if (ctx->gather_handler && ctx->gather_handler->vtbl->stream_gathered) { + ctx->gather_handler->vtbl->stream_gathered(ctx->gather_handler->obj, + stream); + } + } return(_status); } @@ -413,6 +426,19 @@ static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg) if(pair){ nr_ice_candidate_pair_start(pair->pctx,pair); /* Ignore failures */ + + /* stream->ice_state goes to checking when we decide that it is ok to + * start checking, which can happen before we get remote candidates. We + * want to fire this event when we _actually_ start sending checks. */ + if (!stream->actually_started_checking) { + stream->actually_started_checking = 1; + if (stream->pctx->handler && + stream->pctx->handler->vtbl->stream_checking) { + stream->pctx->handler->vtbl->stream_checking( + stream->pctx->handler->obj, stream->local_stream); + } + } + NR_ASYNC_TIMER_SET(timer_val,nr_ice_media_stream_check_timer_cb,cb_arg,&stream->timer); } else { @@ -729,9 +755,21 @@ void nr_ice_media_stream_set_disconnected(nr_ice_media_stream *stream, int disco if (disconnected == NR_ICE_MEDIA_STREAM_DISCONNECTED) { if (!stream->local_stream->obsolete) { + if (stream->pctx->handler && + stream->pctx->handler->vtbl->stream_disconnected) { + stream->pctx->handler->vtbl->stream_disconnected( + stream->pctx->handler->obj, stream->local_stream); + } nr_ice_peer_ctx_disconnected(stream->pctx); } } else { + if (!stream->local_stream->obsolete) { + if (stream->pctx->handler && + stream->pctx->handler->vtbl->stream_ready) { + stream->pctx->handler->vtbl->stream_ready(stream->pctx->handler->obj, + stream->local_stream); + } + } nr_ice_peer_ctx_check_if_connected(stream->pctx); } } diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h index 99f906c100..3da20e3b5e 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h @@ -72,6 +72,7 @@ struct nr_ice_media_stream_ { * processing. If this stream is connected already, traffic can continue to * flow for a limited time while the new stream gets ready. */ int obsolete; + int actually_started_checking; #define NR_ICE_MEDIA_STREAM_UNPAIRED 1 #define NR_ICE_MEDIA_STREAM_CHECKS_FROZEN 2 diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c index 0bf97eb984..bfa1fcf44b 100644 --- a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c +++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c @@ -671,6 +671,12 @@ void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx) void nr_ice_peer_ctx_disconnected(nr_ice_peer_ctx *pctx) { + if (pctx->connected_cb_timer) { + /* Whoops, never mind */ + NR_async_timer_cancel(pctx->connected_cb_timer); + pctx->connected_cb_timer = 0; + } + if (pctx->reported_connected && pctx->handler && pctx->handler->vtbl->ice_disconnected) { diff --git a/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp b/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp index fc1a51dd0d..275e1bf45a 100644 --- a/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp +++ b/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp @@ -85,23 +85,27 @@ bool MediaPipelineFilter::Filter(const webrtc::RTPHeader& header) { // Remote SSRC based filtering // - if (remote_ssrc_set_.count(header.ssrc)) { + if (!remote_ssrc_set_.empty()) { + if (remote_ssrc_set_.count(header.ssrc)) { + DEBUG_LOG( + ("MediaPipelineFilter SSRC: %u matched remote SSRC set." + " passing packet", + header.ssrc)); + return true; + } DEBUG_LOG( - ("MediaPipelineFilter SSRC: %u matched remote SSRC set." - " passing packet", - header.ssrc)); - return true; + ("MediaPipelineFilter SSRC: %u did not match any of %zu" + " remote SSRCS.", + header.ssrc, remote_ssrc_set_.size())); + return false; } - DEBUG_LOG( - ("MediaPipelineFilter SSRC: %u did not match any of %zu" - " remote SSRCS.", - header.ssrc, remote_ssrc_set_.size())); // - // PT, payload type, last ditch effort filtering + // PT, payload type, last ditch effort filtering. We only try this if we do + // not have any ssrcs configured (either by learning them, or negotiation). // - if (payload_type_set_.count(header.payloadType)) { + if (receive_payload_type_set_.count(header.payloadType)) { DEBUG_LOG( ("MediaPipelineFilter payload-type: %u matched %zu" " unique payload type. learning ssrc. passing packet", @@ -114,7 +118,7 @@ bool MediaPipelineFilter::Filter(const webrtc::RTPHeader& header) { DEBUG_LOG( ("MediaPipelineFilter payload-type: %u did not match any of %zu" " unique payload-types.", - header.payloadType, payload_type_set_.size())); + header.payloadType, receive_payload_type_set_.size())); DEBUG_LOG( ("MediaPipelineFilter packet failed to match any criteria." " ignoring packet")); @@ -125,8 +129,8 @@ void MediaPipelineFilter::AddRemoteSSRC(uint32_t ssrc) { remote_ssrc_set_.insert(ssrc); } -void MediaPipelineFilter::AddUniquePT(uint8_t payload_type) { - payload_type_set_.insert(payload_type); +void MediaPipelineFilter::AddUniqueReceivePT(uint8_t payload_type) { + receive_payload_type_set_.insert(payload_type); } void MediaPipelineFilter::Update(const MediaPipelineFilter& filter_update) { @@ -143,7 +147,7 @@ void MediaPipelineFilter::Update(const MediaPipelineFilter& filter_update) { mRemoteMid = filter_update.mRemoteMid; mRemoteMidBindings = filter_update.mRemoteMidBindings; } - payload_type_set_ = filter_update.payload_type_set_; + receive_payload_type_set_ = filter_update.receive_payload_type_set_; // Use extmapping from new filter mExtMap = filter_update.mExtMap; diff --git a/dom/media/webrtc/transportbridge/MediaPipelineFilter.h b/dom/media/webrtc/transportbridge/MediaPipelineFilter.h index d6bb7abd43..62d4b63ab8 100644 --- a/dom/media/webrtc/transportbridge/MediaPipelineFilter.h +++ b/dom/media/webrtc/transportbridge/MediaPipelineFilter.h @@ -66,7 +66,7 @@ class MediaPipelineFilter { void SetRemoteMediaStreamId(const Maybe<std::string>& aMid); // When a payload type id is unique to our media section, add it here. - void AddUniquePT(uint8_t payload_type); + void AddUniqueReceivePT(uint8_t payload_type); void Update(const MediaPipelineFilter& filter_update); @@ -76,7 +76,7 @@ class MediaPipelineFilter { // The number of filters we manage here is quite small, so I am optimizing // for readability. std::set<uint32_t> remote_ssrc_set_; - std::set<uint8_t> payload_type_set_; + std::set<uint8_t> receive_payload_type_set_; Maybe<std::string> mRemoteMid; std::set<uint32_t> mRemoteMidBindings; // RID extension can be set by tests and is sticky, the rest of diff --git a/dom/media/webspeech/recognition/SpeechRecognition.cpp b/dom/media/webspeech/recognition/SpeechRecognition.cpp index 75d1ba7709..7239a88237 100644 --- a/dom/media/webspeech/recognition/SpeechRecognition.cpp +++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp @@ -437,12 +437,13 @@ uint32_t SpeechRecognition::ProcessAudioSegment(AudioSegment* aSegment, // we need to call the nsISpeechRecognitionService::ProcessAudioSegment // in a separate thread so that any eventual encoding or pre-processing // of the audio does not block the main thread - nsresult rv = mEncodeTaskQueue->Dispatch( - NewRunnableMethod<StoreCopyPassByPtr<AudioSegment>, TrackRate>( - "nsISpeechRecognitionService::ProcessAudioSegment", - mRecognitionService, - &nsISpeechRecognitionService::ProcessAudioSegment, - std::move(*aSegment), aTrackRate)); + nsresult rv = mEncodeTaskQueue->Dispatch(NS_NewRunnableFunction( + "nsISpeechRecognitionService::ProcessAudioSegment", + [=, service = mRecognitionService, + segment = std::move(*aSegment)]() mutable { + service->ProcessAudioSegment(&segment, aTrackRate); + })); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return samples; diff --git a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl index 1898bf68c1..8192eff045 100644 --- a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl +++ b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl @@ -65,18 +65,6 @@ interface nsISynthVoiceRegistry : nsISupports }; %{C++ -#define NS_SYNTHVOICEREGISTRY_CID \ - { /* {7090524d-5574-4492-a77f-d8d558ced59d} */ \ - 0x7090524d, \ - 0x5574, \ - 0x4492, \ - { 0xa7, 0x7f, 0xd8, 0xd5, 0x58, 0xce, 0xd5, 0x9d } \ - } - #define NS_SYNTHVOICEREGISTRY_CONTRACTID \ "@mozilla.org/synth-voice-registry;1" - -#define NS_SYNTHVOICEREGISTRY_CLASSNAME \ - "Speech Synthesis Voice Registry" - %} diff --git a/dom/media/webvtt/WebVTTListener.cpp b/dom/media/webvtt/WebVTTListener.cpp index 3f8d99a8f3..b0691491e3 100644 --- a/dom/media/webvtt/WebVTTListener.cpp +++ b/dom/media/webvtt/WebVTTListener.cpp @@ -65,7 +65,7 @@ nsresult WebVTTListener::LoadResource() { if (IsCanceled()) { return NS_OK; } - // Exit if we failed to create the WebVTTParserWrapper (vtt.jsm) + // Exit if we failed to create the WebVTTParserWrapper (vtt.sys.mjs) NS_ENSURE_SUCCESS(mParserWrapperError, mParserWrapperError); mElement->SetReadyState(TextTrackReadyState::Loading); diff --git a/dom/media/webvtt/test/mochitest/manifest.js b/dom/media/webvtt/test/mochitest/manifest.js index 91c481feb9..dc72b55ffa 100644 --- a/dom/media/webvtt/test/mochitest/manifest.js +++ b/dom/media/webvtt/test/mochitest/manifest.js @@ -11,7 +11,7 @@ function removeNodeAndSource(n) { } function once(target, name, cb) { - var p = new Promise(function (resolve, reject) { + var p = new Promise(function (resolve) { target.addEventListener( name, function () { diff --git a/dom/media/webvtt/test/mochitest/mochitest.toml b/dom/media/webvtt/test/mochitest/mochitest.toml index 6ec6042407..6fd4c9e7ba 100644 --- a/dom/media/webvtt/test/mochitest/mochitest.toml +++ b/dom/media/webvtt/test/mochitest/mochitest.toml @@ -18,15 +18,14 @@ support-files = [ ["test_bug883173.html"] skip-if = [ - "android_version == '25' && debug", # android(bug 1232305) - "os == 'linux' && (asan || tsan || debug)", # Bug 1829511 + "os == 'linux' && os_version == '18.04' && asan", # Bug 1829511 + "os == 'linux' && os_version == '18.04' && tsan", # Bug 1829511 + "os == 'linux' && os_version == '18.04' && debug", # Bug 1829511 ] ["test_bug895091.html"] -skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_bug957847.html"] -skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_bug1018933.html"] @@ -39,10 +38,7 @@ skip-if = ["android_version == '25' && debug"] # android(bug 1232305) ["test_texttrack_cors_preload_none.html"] ["test_texttrack_mode_change_during_loading.html"] -skip-if = [ - "true", - "os == 'android'", # Bug 1636572, android(bug 1562021) -] +skip-if = ["true"] ["test_texttrack_moz.html"] diff --git a/dom/media/webvtt/update-webvtt.js b/dom/media/webvtt/update-webvtt.js index 20a3e2669f..8350cff5dc 100644 --- a/dom/media/webvtt/update-webvtt.js +++ b/dom/media/webvtt/update-webvtt.js @@ -6,7 +6,7 @@ var gift = require("gift"), fs = require("fs"), argv = require("optimist") .usage( - "Update vtt.jsm with the latest from a vtt.js directory.\nUsage:" + + "Update vtt.sys.mjs with the latest from a vtt.js directory.\nUsage:" + " $0 -d [dir]" ) .demand("d") @@ -22,7 +22,7 @@ var gift = require("gift"), .options("w", { alias: "write", describe: "Path to file to write to.", - default: "./vtt.jsm", + default: "./vtt.sys.mjs", }).argv; var repo = gift(argv.d); diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp index 23c54cde25..5c07311427 100644 --- a/dom/messagechannel/MessagePort.cpp +++ b/dom/messagechannel/MessagePort.cpp @@ -204,7 +204,8 @@ already_AddRefed<MessagePort> MessagePort::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv) { MOZ_ASSERT(aGlobal); - RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateUnshippedEntangled); + RefPtr<MessagePort> mp = + new MessagePort(aGlobal, eStateInitializingUnshippedEntangled); mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */, false /* Neutered */, aRv); return mp.forget(); @@ -223,11 +224,16 @@ already_AddRefed<MessagePort> MessagePort::Create( return mp.forget(); } -void MessagePort::UnshippedEntangle(MessagePort* aEntangledPort) { +void MessagePort::UnshippedEntangle(RefPtr<MessagePort>& aEntangledPort) { MOZ_DIAGNOSTIC_ASSERT(aEntangledPort); MOZ_DIAGNOSTIC_ASSERT(!mUnshippedEntangledPort); - mUnshippedEntangledPort = aEntangledPort; + if (mState == eStateInitializingUnshippedEntangled) { + mUnshippedEntangledPort = aEntangledPort; + mState = eStateUnshippedEntangled; + } else { + MOZ_ASSERT_UNREACHABLE("Should not have been called."); + } } void MessagePort::Initialize(const nsID& aUUID, const nsID& aDestinationUUID, @@ -251,7 +257,7 @@ void MessagePort::Initialize(const nsID& aUUID, const nsID& aDestinationUUID, return; } } else { - MOZ_ASSERT(mState == eStateUnshippedEntangled); + MOZ_ASSERT(mState == eStateInitializingUnshippedEntangled); } // The port has to keep itself alive until it's entangled. @@ -332,6 +338,12 @@ void MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, return; } + if (mState == eStateInitializingUnshippedEntangled) { + MOZ_ASSERT_UNREACHABLE( + "Should be eStateUnshippedEntangled or eStateDisentangled by now."); + return; + } + // If we are unshipped we are connected to the other port on the same thread. if (mState == eStateUnshippedEntangled) { MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort); @@ -390,6 +402,11 @@ void MessagePort::Dispatch() { } switch (mState) { + case eStateInitializingUnshippedEntangled: + MOZ_ASSERT_UNREACHABLE( + "Should be eStateUnshippedEntangled or eStateDisentangled by now."); + break; + case eStateUnshippedEntangled: // Everything is fine here. We have messages because the other // port populates our queue directly. @@ -459,6 +476,17 @@ void MessagePort::CloseInternal(bool aSoftly) { // now invalid. mRefMessageBodyService->ForgetPort(mIdentifier->uuid()); + if (mState == eStateInitializingUnshippedEntangled) { + // We can end up here if we failed to create our WorkerRef. Our Create + // method will end up returning an error and MessageChannel will not bother + // creating our counterpart port or calling UnshippedEntangle (and we + // assert on this). + mState = eStateDisentangledForClose; + + UpdateMustKeepAlive(); + return; + } + if (mState == eStateUnshippedEntangled) { MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort); diff --git a/dom/messagechannel/MessagePort.h b/dom/messagechannel/MessagePort.h index b0365e6b81..2ed4fdabaa 100644 --- a/dom/messagechannel/MessagePort.h +++ b/dom/messagechannel/MessagePort.h @@ -110,7 +110,7 @@ class MessagePort final : public DOMEventTargetHelper { // Non WebIDL methods - void UnshippedEntangle(MessagePort* aEntangledPort); + void UnshippedEntangle(RefPtr<MessagePort>& aEntangledPort); bool CanBeCloned() const { return !mHasBeenTransferredOrClosed; } @@ -127,6 +127,10 @@ class MessagePort final : public DOMEventTargetHelper { private: enum State { + // The plan is to be eStateUnshippedEntangled once we are told about our + // unshipped entangled counterpart. + eStateInitializingUnshippedEntangled, + // When a port is created by a MessageChannel it is entangled with the // other. They both run on the same thread, same event loop and the // messages are added to the queues without using PBackground actors. diff --git a/dom/messagechannel/tests/mm_messageChannel.js b/dom/messagechannel/tests/mm_messageChannel.js index 7acd5df530..8cc104e748 100644 --- a/dom/messagechannel/tests/mm_messageChannel.js +++ b/dom/messagechannel/tests/mm_messageChannel.js @@ -71,6 +71,6 @@ function emptyTest(msg) { sendAsyncMessage("EmptyTest:FinishPrepare", { message: "OK" }); } -function notTransferableTest(msg) { +function notTransferableTest() { sendAsyncMessage("NotTransferableTest:FinishPrepare", { message: "OK" }); } diff --git a/dom/messagechannel/tests/mm_messageChannelParent.js b/dom/messagechannel/tests/mm_messageChannelParent.js index a5595722a2..f103a1746b 100644 --- a/dom/messagechannel/tests/mm_messageChannelParent.js +++ b/dom/messagechannel/tests/mm_messageChannelParent.js @@ -72,7 +72,7 @@ function close_test(finish) { is(msg.data.message, "OK", ""); ok(port, ""); - port.onmessage = message => { + port.onmessage = () => { ok(false, "Port is alive."); finish(); }; @@ -92,7 +92,7 @@ function close_test(finish) { function empty_transferable(finish) { ok(mm, "empty_transferable"); - let finishPrepare = msg => { + let finishPrepare = () => { ok(true, "Same basic test."); mm.removeMessageListener("EmptyTest:FinishPrepare", finishPrepare); finish(); @@ -106,7 +106,7 @@ function empty_transferable(finish) { function not_transferable(finish) { ok(mm, "not_transferable"); - let finishPrepare = msg => { + let finishPrepare = () => { ok(true, "Same basic test."); finish(); }; @@ -118,7 +118,7 @@ function not_transferable(finish) { /* * Test preparation */ -function finishLoad(msg) { +function finishLoad() { run_tests(); } diff --git a/dom/messagechannel/tests/test_event_listener_leaks.html b/dom/messagechannel/tests/test_event_listener_leaks.html index 93ed5215d3..579491dab3 100644 --- a/dom/messagechannel/tests/test_event_listener_leaks.html +++ b/dom/messagechannel/tests/test_event_listener_leaks.html @@ -23,7 +23,7 @@ async function useMessagePort(contentWindow) { mc.port1.postMessage("foo"); await new Promise(resolve => { - mc.port2.onmessage = e => { + mc.port2.onmessage = () => { contentWindow.messageCount += 1; resolve(); }; diff --git a/dom/messagechannel/tests/test_messageChannel.xhtml b/dom/messagechannel/tests/test_messageChannel.xhtml index e51359d67d..cac2a4f066 100644 --- a/dom/messagechannel/tests/test_messageChannel.xhtml +++ b/dom/messagechannel/tests/test_messageChannel.xhtml @@ -17,7 +17,7 @@ var channel = new MessageChannel(); ok(channel, "MessageChannel is created"); - channel.port1.onmessage = function(evt) { + channel.port1.onmessage = function() { ok(true, "message received!"); SimpleTest.finish(); } diff --git a/dom/messagechannel/tests/test_messageChannel_start.html b/dom/messagechannel/tests/test_messageChannel_start.html index 4bc49cc0d6..f9e86da385 100644 --- a/dom/messagechannel/tests/test_messageChannel_start.html +++ b/dom/messagechannel/tests/test_messageChannel_start.html @@ -36,12 +36,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 var events = 2; - a.port1.onmessage = function(evt) { + a.port1.onmessage = function() { ok(true, "This method should be called"); if (!--events) runTests(); } - a.port2.onmessage = function(evt) { + a.port2.onmessage = function() { ok(true, "This method should be called"); if (!--events) runTests(); } @@ -55,11 +55,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 a.port2.postMessage(43); ok(true, "MessagePort{1,2}.postmessage() invoked"); - a.port1.addEventListener('message', function(evt) { + a.port1.addEventListener('message', function() { ok(false, "This method should not be called"); }); - a.port2.addEventListener('message', function(evt) { + a.port2.addEventListener('message', function() { ok(false, "This method should not be called"); }); @@ -76,12 +76,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 var events = 2; - a.port1.addEventListener('message', function(evt) { + a.port1.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); - a.port2.addEventListener('message', function(evt) { + a.port2.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); @@ -100,12 +100,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 var events = 1; - a.port1.addEventListener('message', function(evt) { + a.port1.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); - a.port2.addEventListener('message', function(evt) { + a.port2.addEventListener('message', function() { ok(false, "This method should not be called"); }); @@ -122,11 +122,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 var events = 1; - a.port1.addEventListener('message', function(evt) { + a.port1.addEventListener('message', function() { ok(false, "This method should not be called"); }); - a.port2.addEventListener('message', function(evt) { + a.port2.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); @@ -144,12 +144,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 setTimeout(function() { var events = 2; - a.port1.onmessage = function(evt) { + a.port1.onmessage = function() { ok(true, "This method should be called"); if (!--events) runTests(); } - a.port2.onmessage = function(evt) { + a.port2.onmessage = function() { ok(true, "This method should be called"); if (!--events) runTests(); } @@ -167,13 +167,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 var events = 2; a.port1.start(); - a.port1.addEventListener('message', function(evt) { + a.port1.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); a.port2.start(); - a.port2.addEventListener('message', function(evt) { + a.port2.addEventListener('message', function() { ok(true, "This method should be called"); if (!--events) runTests(); }); @@ -191,17 +191,17 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 addEventListener('message', testOnMessageCloneCb, false); function testOnMessageCloneCb(event) { - a.port1.onmessage = function(evt) { + a.port1.onmessage = function() { ok(true, "This method should be called"); testOnMessageCloneFinish(); } - event.data.onmessage = function(evt) { + event.data.onmessage = function() { ok(true, "This method should be called"); testOnMessageCloneFinish(); } - a.port2.onmessage = function(evt) { + a.port2.onmessage = function() { ok(false, "This method should not be called"); } } diff --git a/dom/messagechannel/tests/test_removedWindow.html b/dom/messagechannel/tests/test_removedWindow.html index b660000fd8..0413a7afeb 100644 --- a/dom/messagechannel/tests/test_removedWindow.html +++ b/dom/messagechannel/tests/test_removedWindow.html @@ -30,13 +30,13 @@ async function runTest() { // This should silently fail. pre.port1.postMessage(42); - pre.port2.onmessage = e => { + pre.port2.onmessage = () => { ok(false, "No messages should be received!"); } // This should silently fail. post.port1.postMessage(42); - post.port2.onmessage = e => { + post.port2.onmessage = () => { ok(false, "No messages should be received!"); } @@ -45,7 +45,7 @@ async function runTest() { let mc = new MessageChannel(); mc.port1.postMessage(42); - mc.port2.onmessage = e => { + mc.port2.onmessage = () => { ok(true, "Ready to complete the test"); SimpleTest.finish(); } diff --git a/dom/midi/MIDIPort.cpp b/dom/midi/MIDIPort.cpp index 3d75bf2d63..94d7280c73 100644 --- a/dom/midi/MIDIPort.cpp +++ b/dom/midi/MIDIPort.cpp @@ -104,14 +104,14 @@ bool MIDIPort::Initialize(const MIDIPortInfo& aPortInfo, bool aSysexEnabled, mPortHolder.Init(port.forget()); LOG("MIDIPort::Initialize (%s, %s)", NS_ConvertUTF16toUTF8(Port()->Name()).get(), - MIDIPortTypeValues::strings[uint32_t(Port()->Type())].value); + GetEnumString(Port()->Type()).get()); return true; } void MIDIPort::UnsetIPCPort() { LOG("MIDIPort::UnsetIPCPort (%s, %s)", NS_ConvertUTF16toUTF8(Port()->Name()).get(), - MIDIPortTypeValues::strings[uint32_t(Port()->Type())].value); + GetEnumString(Port()->Type()).get()); mPortHolder.Clear(); } diff --git a/dom/midi/tests/MIDITestUtils.js b/dom/midi/tests/MIDITestUtils.js index 779a961991..b6d6fa301c 100644 --- a/dom/midi/tests/MIDITestUtils.js +++ b/dom/midi/tests/MIDITestUtils.js @@ -1,7 +1,7 @@ var MIDITestUtils = { permissionSetup: allow => { let permPromiseRes; - let permPromise = new Promise((res, rej) => { + let permPromise = new Promise(res => { permPromiseRes = res; }); SpecialPowers.pushPrefEnv( diff --git a/dom/midi/tests/browser_midi_permission_gated.js b/dom/midi/tests/browser_midi_permission_gated.js index 2bdce51a2d..d82ea87d6e 100644 --- a/dom/midi/tests/browser_midi_permission_gated.js +++ b/dom/midi/tests/browser_midi_permission_gated.js @@ -779,7 +779,7 @@ async function waitForNotification(notificationId) { let observerPromise; if (notificationId !== "addon-webext-permissions") { observerPromise = new Promise(resolve => { - Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + Services.obs.addObserver(function observer(aSubject, aTopic) { // Ignore the progress notification unless that is the notification we want if ( notificationId != PROGRESS_NOTIFICATION && diff --git a/dom/midi/tests/browser_refresh_port_list.js b/dom/midi/tests/browser_refresh_port_list.js index 152b067254..7e2adffe5f 100644 --- a/dom/midi/tests/browser_refresh_port_list.js +++ b/dom/midi/tests/browser_refresh_port_list.js @@ -4,37 +4,37 @@ const EXAMPLE_ORG_URL = "https://example.org/browser/dom/midi/tests/"; const PAGE = "refresh_port_list.html"; -async function get_access(browser) { +async function get_access() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { return content.wrappedJSObject.get_access(); }); } -async function reset_access(browser) { +async function reset_access() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { return content.wrappedJSObject.reset_access(); }); } -async function get_num_ports(browser) { +async function get_num_ports() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { return content.wrappedJSObject.get_num_ports(); }); } -async function add_port(browser) { +async function add_port() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { return content.wrappedJSObject.add_port(); }); } -async function remove_port(browser) { +async function remove_port() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { return content.wrappedJSObject.remove_port(); }); } -async function force_refresh(browser) { +async function force_refresh() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { return content.wrappedJSObject.force_refresh(); }); diff --git a/dom/midi/tests/test_midi_device_enumeration.html b/dom/midi/tests/test_midi_device_enumeration.html index 1dab1c8cf7..48fe0c7d69 100644 --- a/dom/midi/tests/test_midi_device_enumeration.html +++ b/dom/midi/tests/test_midi_device_enumeration.html @@ -16,7 +16,7 @@ is(await props[prop], obj[prop], type + " property value " + prop + " is " + props[prop]); } }; - let failOnCall = (event) => { + let failOnCall = () => { ok(false, "No connect/state events should be received on startup!"); }; async function runTests () { diff --git a/dom/midi/tests/test_midi_device_explicit_open_close.html b/dom/midi/tests/test_midi_device_explicit_open_close.html index d3ed910a55..2fa682db5d 100644 --- a/dom/midi/tests/test_midi_device_explicit_open_close.html +++ b/dom/midi/tests/test_midi_device_explicit_open_close.html @@ -26,8 +26,8 @@ let input = access.inputs.get(await MIDITestUtils.inputInfo.id); let portEventRes; let accessEventRes; - let portEventPromise = new Promise((resolve, reject) => { portEventRes = resolve; }); - let accessEventPromise = new Promise((resolve, reject) => { accessEventRes = resolve; }); + let portEventPromise = new Promise((resolve) => { portEventRes = resolve; }); + let accessEventPromise = new Promise((resolve) => { accessEventRes = resolve; }); let shouldClose = false; let checkPort = (event) => { ok(input === event.port, "input port object and event port object are same object"); @@ -77,8 +77,8 @@ out_access.removeEventListener("statechange", outputEventHandler); } ok(true, "Starting MIDI port closing test"); - portEventPromise = new Promise((resolve, reject) => { portEventRes = resolve; }); - accessEventPromise = new Promise((resolve, reject) => { accessEventRes = resolve; }); + portEventPromise = new Promise((resolve) => { portEventRes = resolve; }); + accessEventPromise = new Promise((resolve) => { accessEventRes = resolve; }); input.addEventListener("statechange", inputEventHandler); access.addEventListener("statechange", accessEventHandler); shouldClose = true; diff --git a/dom/midi/tests/test_midi_device_pending.html b/dom/midi/tests/test_midi_device_pending.html index 2e6bd08420..d81bbaf894 100644 --- a/dom/midi/tests/test_midi_device_pending.html +++ b/dom/midi/tests/test_midi_device_pending.html @@ -25,8 +25,8 @@ let portPromise; function resetPromises() { - accessPromise = new Promise((res, rej) => { accessRes = res; }); - portPromise = new Promise((res, rej) => { portRes = res; }); + accessPromise = new Promise((res) => { accessRes = res; }); + portPromise = new Promise((res) => { portRes = res; }); } function accessStateChangeHandler(event) { diff --git a/dom/moz.build b/dom/moz.build index 737a427005..f781bd3708 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -12,7 +12,6 @@ JAR_MANIFESTS += ["jar.mn"] interfaces = [ "base", - "html", "events", "sidebar", "xul", @@ -32,7 +31,6 @@ DIRS += [ "base", "bindings", "battery", - "browser-element", "cache", "canvas", "webgpu", diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.js b/dom/network/tests/test_tcpsocket_client_and_server_basics.js index bbb91e410f..32b290a949 100644 --- a/dom/network/tests/test_tcpsocket_client_and_server_basics.js +++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js @@ -129,7 +129,7 @@ function listenForEventsOnSocket(socket, socketType) { } dump("(" + socketType + " waiting for event)\n"); - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { pendingResolve = resolve; }); }, @@ -149,7 +149,7 @@ function listenForEventsOnSocket(socket, socketType) { return promise; } dump("(" + socketType + " waiting for " + length + " bytes)\n"); - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { pendingResolve = resolve; wantDataLength = length; }); @@ -159,7 +159,7 @@ function listenForEventsOnSocket(socket, socketType) { throw new Error("only one wait allowed at a time."); } - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { pendingResolve = resolve; // we may receive no data before getting close, in which case we want to // return an empty array @@ -177,7 +177,7 @@ function listenForEventsOnSocket(socket, socketType) { * to add the event listener during the connection. */ function waitForConnection(listeningServer) { - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { // Because of the event model of sockets, we can't use the // listenForEventsOnSocket mechanism; we need to hook up listeners during // the connect event. diff --git a/dom/network/tests/test_udpsocket.html b/dom/network/tests/test_udpsocket.html index 1ca42f2432..f465c1727f 100644 --- a/dom/network/tests/test_udpsocket.html +++ b/dom/network/tests/test_udpsocket.html @@ -74,7 +74,7 @@ function testSendString(socket) { socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); @@ -89,7 +89,7 @@ function testSendArrayBuffer(socket) { socket.send(DATA_ARRAY_BUFFER, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); ok(is_same_buffer(msg.data, DATA_ARRAY_BUFFER), 'expected same buffer data'); @@ -103,7 +103,7 @@ function testSendArrayBufferView(socket) { socket.send(TYPED_DATA_ARRAY, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); ok(is_same_buffer(msg.data, TYPED_DATA_ARRAY), 'expected same buffer data'); @@ -118,7 +118,7 @@ function testSendBlob(socket) { let blob = new Blob([HELLO_WORLD], {type : 'text/plain'}); socket.send(blob, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); @@ -133,7 +133,7 @@ function testSendBigArray(socket) { socket.send(BIG_TYPED_ARRAY, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { let byteReceived = 0; socket.addEventListener('message', function recv_callback(msg) { let byteBegin = byteReceived; @@ -154,7 +154,7 @@ function testSendBigBlob(socket) { let blob = new Blob([BIG_TYPED_ARRAY]); socket.send(blob, '127.0.0.1', socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { let byteReceived = 0; socket.addEventListener('message', function recv_callback(msg) { let byteBegin = byteReceived; @@ -185,7 +185,7 @@ function testUDPOptions(socket) { return remoteSocket.opened.then(function() { remoteSocket.send(HELLO_WORLD); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); is(msg.remotePort, remoteSocket.localPort, 'expected packet from ' + remoteSocket.localPort); @@ -224,7 +224,7 @@ function testMulticast() { return socket.opened.then(function() { socket.send(HELLO_WORLD, MCAST_ADDRESS, socket.localPort); - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { socket.addEventListener('message', function(msg) { let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); @@ -344,7 +344,7 @@ async function testBFCache() { socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort); await new Promise(resolve => { - function recv_again_callback(message) { + function recv_again_callback() { socket.removeEventListener('message', recv_again_callback); ok(false, 'should not receive packet after page unload'); } diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 39ba7b23ff..fb5b0ea9a3 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -714,7 +714,12 @@ Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID, } } -nsresult Notification::Init() { +nsresult Notification::MaybeObserveWindowFrozenOrDestroyed() { + // NOTE: Non-persistent notifications can also be opened from workers, but we + // don't care and nobody else cares. And it's not clear whether we even should + // do this for window at all, see + // https://github.com/whatwg/notifications/issues/204. + // TODO: Somehow extend GlobalTeardownObserver to deal with FROZEN_TOPIC? if (!mWorkerPrivate) { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); @@ -776,6 +781,10 @@ already_AddRefed<Notification> Notification::Constructor( if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + if (NS_WARN_IF( + NS_FAILED(notification->MaybeObserveWindowFrozenOrDestroyed()))) { + return nullptr; + } // This is be ok since we are on the worker thread where this function will // run to completion before the Notification has a chance to go away. @@ -915,8 +924,6 @@ already_AddRefed<Notification> Notification::CreateInternal( aGlobal, id, aTitle, aOptions.mBody, aOptions.mDir, aOptions.mLang, aOptions.mTag, aOptions.mIcon, aOptions.mRequireInteraction, silent, std::move(vibrate), aOptions.mMozbehavior); - rv = notification->Init(); - NS_ENSURE_SUCCESS(rv, nullptr); return notification.forget(); } diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h index 4ffa69cf49..61f4a8f1c3 100644 --- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -245,7 +245,8 @@ class Notification : public DOMEventTargetHelper, nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv); - nsresult Init(); + // Triggers CloseInternal for non-persistent notifications if window goes away + nsresult MaybeObserveWindowFrozenOrDestroyed(); bool IsInPrivateBrowsing(); void ShowInternal(); void CloseInternal(bool aContextClosed = false); diff --git a/dom/notification/NotificationStorage.sys.mjs b/dom/notification/NotificationStorage.sys.mjs index 132771e088..46c9e2485c 100644 --- a/dom/notification/NotificationStorage.sys.mjs +++ b/dom/notification/NotificationStorage.sys.mjs @@ -42,7 +42,7 @@ NotificationStorage.prototype = { } }, - observe(aSubject, aTopic, aData) { + observe(aSubject, aTopic) { if (DEBUG) { debug("Topic: " + aTopic); } diff --git a/dom/notification/new/NotificationDB.sys.mjs b/dom/notification/new/NotificationDB.sys.mjs index b607fdd234..d7e1d998c1 100644 --- a/dom/notification/new/NotificationDB.sys.mjs +++ b/dom/notification/new/NotificationDB.sys.mjs @@ -10,6 +10,7 @@ function debug(s) { const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", KeyValueService: "resource://gre/modules/kvstore.sys.mjs", }); @@ -37,6 +38,11 @@ var NotificationDB = { // of the load via its resolution. _loadPromise: null, + // A promise that resolves once the ongoing task queue has been drained. + // The value will be reset when the queue starts again. + _queueDrainedPromise: null, + _queueDrainedPromiseResolve: null, + init() { if (this._shutdownInProgress) { return; @@ -47,6 +53,13 @@ var NotificationDB = { Services.obs.addObserver(this, "xpcom-shutdown"); this.registerListeners(); + + // This assumes that nothing will queue a new task at profile-change-teardown phase, + // potentially replacing the _queueDrainedPromise if there was no existing task run. + lazy.AsyncShutdown.profileChangeTeardown.addBlocker( + "NotificationDB: Need to make sure that all notification messages are processed", + () => this._queueDrainedPromise + ); }, registerListeners() { @@ -61,7 +74,7 @@ var NotificationDB = { } }, - observe(aSubject, aTopic, aData) { + observe(aSubject, aTopic) { if (DEBUG) { debug("Topic: " + aTopic); } @@ -261,6 +274,9 @@ var NotificationDB = { debug("Task queue was not running, starting now..."); } this.runNextTask(); + this._queueDrainedPromise = new Promise(resolve => { + this._queueDrainedPromiseResolve = resolve; + }); } return promise; @@ -272,6 +288,13 @@ var NotificationDB = { debug("No more tasks to run, queue depleted"); } this.runningTask = null; + if (this._queueDrainedPromiseResolve) { + this._queueDrainedPromiseResolve(); + } else if (DEBUG) { + debug( + "_queueDrainedPromiseResolve was null somehow, no promise to resolve" + ); + } return; } this.runningTask = this.tasks.shift(); diff --git a/dom/notification/old/NotificationDB.sys.mjs b/dom/notification/old/NotificationDB.sys.mjs index 79a9965628..7de9734450 100644 --- a/dom/notification/old/NotificationDB.sys.mjs +++ b/dom/notification/old/NotificationDB.sys.mjs @@ -7,6 +7,12 @@ function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); } +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", +}); + const NOTIFICATION_STORE_DIR = PathUtils.profileDir; const NOTIFICATION_STORE_PATH = PathUtils.join( NOTIFICATION_STORE_DIR, @@ -23,6 +29,11 @@ var NotificationDB = { // Ensure we won't call init() while xpcom-shutdown is performed _shutdownInProgress: false, + // A promise that resolves once the ongoing task queue has been drained. + // The value will be reset when the queue starts again. + _queueDrainedPromise: null, + _queueDrainedPromiseResolve: null, + init() { if (this._shutdownInProgress) { return; @@ -37,6 +48,13 @@ var NotificationDB = { Services.obs.addObserver(this, "xpcom-shutdown"); this.registerListeners(); + + // This assumes that nothing will queue a new task at profile-change-teardown phase, + // potentially replacing the _queueDrainedPromise if there was no existing task run. + lazy.AsyncShutdown.profileChangeTeardown.addBlocker( + "NotificationDB: Need to make sure that all notification messages are processed", + () => this._queueDrainedPromise + ); }, registerListeners() { @@ -51,7 +69,7 @@ var NotificationDB = { } }, - observe(aSubject, aTopic, aData) { + observe(aSubject, aTopic) { if (DEBUG) { debug("Topic: " + aTopic); } @@ -114,7 +132,7 @@ var NotificationDB = { }, // If read failed, we assume we have no notifications to load. - reason => { + () => { this.loaded = true; return this.createStore(); } @@ -251,6 +269,9 @@ var NotificationDB = { debug("Task queue was not running, starting now..."); } this.runNextTask(); + this._queueDrainedPromise = new Promise(resolve => { + this._queueDrainedPromiseResolve = resolve; + }); } return promise; @@ -262,6 +283,13 @@ var NotificationDB = { debug("No more tasks to run, queue depleted"); } this.runningTask = null; + if (this._queueDrainedPromiseResolve) { + this._queueDrainedPromiseResolve(); + } else if (DEBUG) { + debug( + "_queueDrainedPromiseResolve was null somehow, no promise to resolve" + ); + } return; } this.runningTask = this.tasks.shift(); diff --git a/dom/notification/test/chrome/test_notification_system_principal.xhtml b/dom/notification/test/chrome/test_notification_system_principal.xhtml index 0700d59338..c563a817a8 100644 --- a/dom/notification/test/chrome/test_notification_system_principal.xhtml +++ b/dom/notification/test/chrome/test_notification_system_principal.xhtml @@ -23,14 +23,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=874090 const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; var mockAlertsService = { - showAlert(alert, alertListener) { + showAlert() { ok(true, "System principal was granted permission and is able to call showAlert."); unregisterMock(); SimpleTest.finish(); }, - showAlertNotification(imageUrl, title, text, textClickable, - cookie, alertListener, name, dir, lang, data) { + showAlertNotification() { this.showAlert(); }, diff --git a/dom/notification/test/mochitest/NotificationTest.js b/dom/notification/test/mochitest/NotificationTest.js index 400ff56253..8593125389 100644 --- a/dom/notification/test/mochitest/NotificationTest.js +++ b/dom/notification/test/mochitest/NotificationTest.js @@ -75,7 +75,7 @@ var NotificationTest = (function () { ); }, - clickNotification(notification) { + clickNotification() { // TODO: how?? }, diff --git a/dom/notification/test/mochitest/test_notification_basics.html b/dom/notification/test/mochitest/test_notification_basics.html index 3dde839a96..ba2bd09174 100644 --- a/dom/notification/test/mochitest/test_notification_basics.html +++ b/dom/notification/test/mochitest/test_notification_basics.html @@ -68,7 +68,7 @@ Notification.requestPermission({}) .then(_ => { ok(false, "Non callable arg to requestPermission should reject promise"); - }, err => { + }, () => { ok(true, "Non callable arg to requestPermission should reject promise"); }) .then(done); diff --git a/dom/notification/test/mochitest/test_notification_tag.html b/dom/notification/test/mochitest/test_notification_tag.html index f4fc72bbe3..d2bc15418e 100644 --- a/dom/notification/test/mochitest/test_notification_tag.html +++ b/dom/notification/test/mochitest/test_notification_tag.html @@ -32,7 +32,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=782211 var notificationsCreated = []; const mockAlertsService = { - showAlert(alert, alertListener) { + showAlert(alert) { notificationsCreated.push(alert.name); if (notificationsCreated.length == 3) { // notifications created by the test1 origin @@ -78,10 +78,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=782211 textClickable, cookie, alertListener, - name, - dir, - lang, - data + name ) { this.showAlert({ name }); }, diff --git a/dom/notification/test/unit/test_notificationdb.js b/dom/notification/test/unit/test_notificationdb.js index 33397f87f3..b6ca8bd79c 100644 --- a/dom/notification/test/unit/test_notificationdb.js +++ b/dom/notification/test/unit/test_notificationdb.js @@ -112,7 +112,7 @@ add_test(function test_send_two_get_one() { }; let msgSaveReply = "Notification:Save:Return:OK"; - let msgSaveHandler = function (message) { + let msgSaveHandler = function () { calls += 1; if (calls === 2) { addAndSend("Notification:GetAll", msgGetReply, msgGetHandler, { diff --git a/dom/origin-trials/tests/mochitest/common.js b/dom/origin-trials/tests/mochitest/common.js index a38a975ea7..3b3e4ae3f9 100644 --- a/dom/origin-trials/tests/mochitest/common.js +++ b/dom/origin-trials/tests/mochitest/common.js @@ -32,7 +32,7 @@ if ( workerReply(this.port); } - process(inputs, outputs, parameters) { + process() { // Do nothing, output silence return true; } diff --git a/dom/payments/PaymentRequestManager.cpp b/dom/payments/PaymentRequestManager.cpp index 32ccac145a..1f21ff1bab 100644 --- a/dom/payments/PaymentRequestManager.cpp +++ b/dom/payments/PaymentRequestManager.cpp @@ -220,8 +220,7 @@ void ConvertDetailsUpdate(JSContext* aCx, const PaymentDetailsUpdate& aDetails, void ConvertOptions(const PaymentOptions& aOptions, IPCPaymentOptions& aIPCOption) { - NS_ConvertASCIItoUTF16 shippingType( - PaymentShippingTypeValues::GetString(aOptions.mShippingType)); + NS_ConvertASCIItoUTF16 shippingType(GetEnumString(aOptions.mShippingType)); aIPCOption = IPCPaymentOptions(aOptions.mRequestPayerName, aOptions.mRequestPayerEmail, aOptions.mRequestPayerPhone, aOptions.mRequestShipping, @@ -548,8 +547,7 @@ void PaymentRequestManager::CompletePayment(PaymentRequest* aRequest, if (aTimedOut) { completeStatusString.AssignLiteral("timeout"); } else { - completeStatusString.AssignASCII( - PaymentCompleteValues::GetString(aComplete)); + completeStatusString.AssignASCII(GetEnumString(aComplete)); } nsAutoString requestId; diff --git a/dom/performance/LargestContentfulPaint.h b/dom/performance/LargestContentfulPaint.h index 0193cd858a..0eceabb1be 100644 --- a/dom/performance/LargestContentfulPaint.h +++ b/dom/performance/LargestContentfulPaint.h @@ -73,16 +73,6 @@ struct LCPImageEntryKey { return element == otherElement && imgRequest == otherImgRequest; } - bool Equals(const Element* aElement, - const imgRequestProxy* aImgRequestProxy) const { - Element* element = GetElement(); - if (!element || !mImageRequestProxy) { - return false; - } - - return element == aElement && mImageRequestProxy == mImageRequestProxy; - } - nsWeakPtr mElement; WeakPtr<PreloaderBase> mImageRequestProxy; diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index ecbc3b4c68..ce2557100d 100644 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -8,6 +8,11 @@ #include <sstream> +#if defined(XP_LINUX) +# include <fcntl.h> +# include <sys/mman.h> +#endif + #include "ETWTools.h" #include "GeckoProfiler.h" #include "nsRFPService.h" @@ -593,18 +598,66 @@ std::pair<TimeStamp, TimeStamp> Performance::GetTimeStampsForMarker( return std::make_pair(startTimeStamp, endTimeStamp); } +static FILE* MaybeOpenMarkerFile() { + if (!getenv("MOZ_USE_PERFORMANCE_MARKER_FILE")) { + return nullptr; + } + +#ifdef XP_LINUX + // We treat marker files similar to Jitdump files (see PerfSpewer.cpp) and + // mmap them if needed. + int fd = open(GetMarkerFilename().c_str(), O_CREAT | O_TRUNC | O_RDWR, 0666); + FILE* markerFile = fdopen(fd, "w+"); + + if (!markerFile) { + return nullptr; + } + + // On Linux and Android, we need to mmap the file so that the path makes it + // into the perf.data file or into samply. + // On non-Android, make the mapping executable, otherwise the MMAP event may + // not be recorded by perf (see perf_event_open mmap_data). + // But on Android, don't make the mapping executable, because doing so can + // make the mmap call fail on some Android devices. It's also not required on + // Android because simpleperf sets mmap_data = 1 for unrelated reasons (it + // wants to know about vdex files for Java JIT profiling, see + // SetRecordNotExecutableMaps). + int protection = PROT_READ; +# ifndef ANDROID + protection |= PROT_EXEC; +# endif + + // Mmap just the first page - that's enough to ensure the path makes it into + // the recording. + long page_size = sysconf(_SC_PAGESIZE); + void* mmap_address = mmap(nullptr, page_size, protection, MAP_PRIVATE, fd, 0); + if (mmap_address == MAP_FAILED) { + fclose(markerFile); + return nullptr; + } + return markerFile; +#else + // On macOS, we just need to `open` or `fopen` the marker file, and samply + // will know its path because it hooks those functions - no mmap needed. + // On Windows, there's no need to use MOZ_USE_PERFORMANCE_MARKER_FILE because + // we have ETW trace events for UserTiming measures. Still, we want this code + // to compile successfully on Windows, so we use fopen rather than + // open+fdopen. + return fopen(GetMarkerFilename().c_str(), "w+"); +#endif +} + // This emits markers to an external marker-[pid].txt file for use by an // external profiler like samply or etw-gecko void Performance::MaybeEmitExternalProfilerMarker( const nsAString& aName, Maybe<const PerformanceMeasureOptions&> aOptions, Maybe<const nsAString&> aStartMark, const Optional<nsAString>& aEndMark) { - static FILE* markerFile = getenv("MOZ_USE_PERFORMANCE_MARKER_FILE") - ? fopen(GetMarkerFilename().c_str(), "w+") - : nullptr; + static FILE* markerFile = MaybeOpenMarkerFile(); if (!markerFile) { return; } +#if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX) ErrorResult rv; auto [startTimeStamp, endTimeStamp] = GetTimeStampsForMarker(aStartMark, aEndMark, aOptions, rv); @@ -612,6 +665,7 @@ void Performance::MaybeEmitExternalProfilerMarker( if (NS_WARN_IF(rv.Failed())) { return; } +#endif #ifdef XP_LINUX uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot(); diff --git a/dom/performance/PerformanceTiming.cpp b/dom/performance/PerformanceTiming.cpp index 546783d686..d4505899cc 100644 --- a/dom/performance/PerformanceTiming.cpp +++ b/dom/performance/PerformanceTiming.cpp @@ -6,9 +6,9 @@ #include "PerformanceTiming.h" #include "mozilla/BasePrincipal.h" -#include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/StaticPrefs_dom.h" -#include "mozilla/Telemetry.h" +#include "mozilla/dom/PerformanceTimingBinding.h" +#include "mozilla/glean/GleanMetrics.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIHttpChannel.h" diff --git a/dom/permission/PermissionUtils.cpp b/dom/permission/PermissionUtils.cpp index 26e3ec0157..604fa01167 100644 --- a/dom/permission/PermissionUtils.cpp +++ b/dom/permission/PermissionUtils.cpp @@ -24,7 +24,7 @@ static const nsLiteralCString kPermissionTypes[] = { // clang-format on }; -const size_t kPermissionNameCount = PermissionNameValues::Count; +const size_t kPermissionNameCount = ContiguousEnumSize<PermissionName>::value; static_assert(MOZ_ARRAY_LENGTH(kPermissionTypes) == kPermissionNameCount, "kPermissionTypes and PermissionName count should match"); diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index fb4989c43d..3abb517ac7 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -213,59 +213,6 @@ already_AddRefed<Promise> Promise::All( return CreateFromExisting(global, result, aPropagateUserInteraction); } -void Promise::Then(JSContext* aCx, - // aCalleeGlobal may not be in the compartment of aCx, when - // called over Xrays. - JS::Handle<JSObject*> aCalleeGlobal, - AnyCallback* aResolveCallback, AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) { - NS_ASSERT_OWNINGTHREAD(Promise); - - // Let's hope this does the right thing with Xrays... Ensure everything is - // just in the caller compartment; that ought to do the trick. In theory we - // should consider aCalleeGlobal, but in practice our only caller is - // DOMRequest::Then, which is not working with a Promise subclass, so things - // should be OK. - JS::Rooted<JSObject*> promise(aCx, PromiseObj()); - if (!promise) { - // This promise is no-op, so do nothing. - return; - } - - if (!JS_WrapObject(aCx, &promise)) { - aRv.NoteJSContextException(aCx); - return; - } - - JS::Rooted<JSObject*> resolveCallback(aCx); - if (aResolveCallback) { - resolveCallback = aResolveCallback->CallbackOrNull(); - if (!JS_WrapObject(aCx, &resolveCallback)) { - aRv.NoteJSContextException(aCx); - return; - } - } - - JS::Rooted<JSObject*> rejectCallback(aCx); - if (aRejectCallback) { - rejectCallback = aRejectCallback->CallbackOrNull(); - if (!JS_WrapObject(aCx, &rejectCallback)) { - aRv.NoteJSContextException(aCx); - return; - } - } - - JS::Rooted<JSObject*> retval(aCx); - retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback, - rejectCallback); - if (!retval) { - aRv.NoteJSContextException(aCx); - return; - } - - aRetval.setObject(*retval); -} - static void SettlePromise(Promise* aSettlingPromise, Promise* aCallbackPromise, ErrorResult& aRv) { if (!aSettlingPromise) { diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index 76c657d5a6..f2b70a95cd 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -255,13 +255,6 @@ class Promise : public SupportsWeakPtr { PropagateUserInteraction aPropagateUserInteraction = eDontPropagateUserInteraction); - void Then(JSContext* aCx, - // aCalleeGlobal may not be in the compartment of aCx, when called - // over Xrays. - JS::Handle<JSObject*> aCalleeGlobal, AnyCallback* aResolveCallback, - AnyCallback* aRejectCallback, JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv); - template <typename Callback, typename... Args> using IsHandlerCallback = std::is_same<already_AddRefed<Promise>, diff --git a/dom/promise/tests/promise_uncatchable_exception.js b/dom/promise/tests/promise_uncatchable_exception.js index eafc9e5448..1b1f62e774 100644 --- a/dom/promise/tests/promise_uncatchable_exception.js +++ b/dom/promise/tests/promise_uncatchable_exception.js @@ -2,10 +2,10 @@ postMessage("Done", "*"); -var p = new Promise(function (resolve, reject) { +var p = new Promise(function () { TestFunctions.throwUncatchableException(); ok(false, "Shouldn't get here!"); -}).catch(function (exception) { +}).catch(function () { ok(false, "Shouldn't get here!"); }); ok(false, "Shouldn't get here!"); diff --git a/dom/promise/tests/test_bug883683.html b/dom/promise/tests/test_bug883683.html index 1b31e32330..b2f776fe53 100644 --- a/dom/promise/tests/test_bug883683.html +++ b/dom/promise/tests/test_bug883683.html @@ -23,10 +23,10 @@ function runTest() { [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise)); ok(true, "No leaks with resolve?"); - [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); }); + [{}, {}, {}, {}, {}].reduce(function(a) { return new Promise(function() { throw a; }); }); ok(true, "No leaks with exception?"); - [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); }); + [{}, {}, {}, {}, {}].reduce(function() { return new Promise(function() { }); }); ok(true, "No leaks with empty promise?"); SimpleTest.finish(); diff --git a/dom/promise/tests/test_on_promise_settled_duplicates.html b/dom/promise/tests/test_on_promise_settled_duplicates.html index e11f4eaa60..324c63b74f 100644 --- a/dom/promise/tests/test_on_promise_settled_duplicates.html +++ b/dom/promise/tests/test_on_promise_settled_duplicates.html @@ -38,7 +38,7 @@ Bug 1084065 - Test that Debugger.prototype.onPromiseResolved doesn't get dupes. seen.add(wp); }; - var promise = new Promise(function(fulfill, reject) { + var promise = new Promise(function(fulfill) { fulfill(1); fulfill(2); fulfill(3); diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html index 7c724daf51..95126bd6ba 100644 --- a/dom/promise/tests/test_promise.html +++ b/dom/promise/tests/test_promise.html @@ -56,7 +56,7 @@ function promiseReject() { var promise = new Promise(function(resolve, reject) { reject(42); }); - promise.then(function(what) { + promise.then(function() { ok(false, "Then - resolveCb has been called"); runTest(); }, function(what) { @@ -70,7 +70,7 @@ function promiseRejectNoHandler() { // This test only checks that the code that reports unhandled errors in the // Promises implementation does not crash or leak. - new Promise(function(res, rej) { + new Promise(function() { // eslint-disable-next-line no-undef noSuchMethod(); }); @@ -81,7 +81,7 @@ function promiseRejectNoArg() { var promise = new Promise(function(resolve, reject) { reject(); }); - promise.then(function(what) { + promise.then(function() { ok(false, "Then - resolveCb has been called"); runTest(); }, function(what) { @@ -92,11 +92,11 @@ function promiseRejectNoArg() { } function promiseException() { - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function() { // eslint-disable-next-line no-throw-literal throw 42; }); - promise.then(function(what) { + promise.then(function() { ok(false, "Then - resolveCb has been called"); runTest(); }, function(what) { @@ -108,10 +108,10 @@ function promiseException() { function promiseGC() { var resolve; - var promise = new Promise(function(r1, r2) { + var promise = new Promise(function(r1) { resolve = r1; }); - promise.then(function(what) { + promise.then(function() { ok(true, "Then - promise is still alive"); runTest(); }); @@ -199,7 +199,7 @@ function promiseAsync_SyncXHR() { function promiseDoubleThen() { var steps = 0; - var promise = new Promise(function(r1, r2) { + var promise = new Promise(function(r1) { r1(42); }); @@ -207,7 +207,7 @@ function promiseDoubleThen() { ok(true, "Then.resolve has been called"); is(what, 42, "Value == 42"); steps++; - }, function(what) { + }, function() { ok(false, "Then.reject has been called"); }); @@ -216,28 +216,28 @@ function promiseDoubleThen() { is(steps, 1, "Then.resolve - step == 1"); is(what, 42, "Value == 42"); runTest(); - }, function(what) { + }, function() { ok(false, "Then.reject has been called"); }); } function promiseThenException() { - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(42); }); - promise.then(function(what) { + promise.then(function() { ok(true, "Then.resolve has been called"); // eslint-disable-next-line no-throw-literal throw "booh"; - }).catch(function(e) { + }).catch(function() { ok(true, "window.onerror has been called!"); runTest(); }); } function promiseThenCatchThen() { - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(42); }); @@ -245,7 +245,7 @@ function promiseThenCatchThen() { ok(true, "Then.resolve has been called"); is(what, 42, "Value == 42"); return what + 1; - }, function(what) { + }, function() { ok(false, "Then.reject has been called"); }); @@ -255,7 +255,7 @@ function promiseThenCatchThen() { ok(true, "Then.resolve has been called"); is(what, 43, "Value == 43"); return what + 1; - }, function(what) { + }, function() { ok(false, "Then.reject has been called"); }).catch(function() { ok(false, "Catch has been called"); @@ -263,13 +263,13 @@ function promiseThenCatchThen() { ok(true, "Then.resolve has been called"); is(what, 44, "Value == 44"); runTest(); - }, function(what) { + }, function() { ok(false, "Then.reject has been called"); }); } function promiseThenNoArg() { - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(42); }); @@ -318,7 +318,7 @@ function promiseRejectThenCatchThen() { reject(42); }); - var promise2 = promise.then(function(what) { + var promise2 = promise.then(function() { ok(false, "Then.resolve has been called"); }, function(what) { ok(true, "Then.reject has been called"); @@ -332,7 +332,7 @@ function promiseRejectThenCatchThen() { ok(true, "Then.resolve has been called"); is(what, 43, "Value == 43"); return what + 1; - }).catch(function(what) { + }).catch(function() { ok(false, "Catch has been called"); }).then(function(what) { ok(true, "Then.resolve has been called"); @@ -366,7 +366,7 @@ function promiseRejectThenCatchExceptionThen() { reject(42); }); - promise.then(function(what) { + promise.then(function() { ok(false, "Then.resolve has been called"); }, function(what) { ok(true, "Then.reject has been called"); @@ -386,7 +386,7 @@ function promiseRejectThenCatchExceptionThen() { function promiseThenCatchOrderingResolve() { var global = 0; - var f = new Promise(function(r1, r2) { + var f = new Promise(function(r1) { r1(42); }); @@ -446,8 +446,8 @@ function promiseCatchNoArg() { } function promiseNestedPromise() { - new Promise(function(resolve, reject) { - resolve(new Promise(function(res, rej) { + new Promise(function(resolve) { + resolve(new Promise(function(res) { ok(true, "Nested promise is executed"); res(42); })); @@ -458,8 +458,8 @@ function promiseNestedPromise() { } function promiseNestedNestedPromise() { - new Promise(function(resolve, reject) { - resolve(new Promise(function(res, rej) { + new Promise(function(resolve) { + resolve(new Promise(function(res) { ok(true, "Nested promise is executed"); res(42); }).then(function(what) { return what + 1; })); @@ -471,7 +471,7 @@ function promiseNestedNestedPromise() { function promiseWrongNestedPromise() { new Promise(function(resolve, reject) { - resolve(new Promise(function(r, r2) { + resolve(new Promise(function(r) { ok(true, "Nested promise is executed"); r(42); })); @@ -479,16 +479,16 @@ function promiseWrongNestedPromise() { }).then(function(value) { is(value, 42, "Nested promise is executed and then == 42"); runTest(); - }, function(value) { + }, function() { ok(false, "This is wrong"); }); } function promiseLoop() { - new Promise(function(resolve, reject) { - resolve(new Promise(function(res, rej) { + new Promise(function(resolve) { + resolve(new Promise(function(res) { ok(true, "Nested promise is executed"); - res(new Promise(function(resInner, rejInner) { + res(new Promise(function(resInner) { ok(true, "Nested nested promise is executed"); resInner(42); })); @@ -496,14 +496,14 @@ function promiseLoop() { }).then(function(value) { is(value, 42, "Nested nested promise is executed and then == 42"); runTest(); - }, function(value) { + }, function() { ok(false, "This is wrong"); }); } function promiseStaticReject() { var promise = Promise.reject(42); - promise.then(function(what) { + promise.then(function() { ok(false, "This should not be called"); }, function(what) { is(what, 42, "Value == 42"); @@ -522,7 +522,7 @@ function promiseStaticResolve() { } function promiseResolveNestedPromise() { - var promise = Promise.resolve(new Promise(function(r, r2) { + var promise = Promise.resolve(new Promise(function(r) { ok(true, "Nested promise is executed"); r(42); }, function() { @@ -538,21 +538,21 @@ function promiseResolveNestedPromise() { function promiseSimpleThenableResolve() { var thenable = { then(resolve) { resolve(5); } }; - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(thenable); }); promise.then(function(v) { ok(v === 5, "promiseSimpleThenableResolve"); runTest(); - }, function(e) { + }, function() { ok(false, "promiseSimpleThenableResolve: Should not reject"); }); } function promiseSimpleThenableReject() { var thenable = { then(resolve, reject) { reject(5); } }; - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(thenable); }); @@ -574,7 +574,7 @@ function promiseThenableThrowsBeforeCallback() { }}; var promise = Promise.resolve(thenable); - promise.then(function(v) { + promise.then(function() { ok(false, "promiseThenableThrowsBeforeCallback: Should've rejected"); runTest(); }, function(e) { @@ -593,7 +593,7 @@ function promiseThenableThrowsAfterCallback() { promise.then(function(v) { ok(v === 5, "promiseThenableThrowsAfterCallback"); runTest(); - }, function(e) { + }, function() { ok(false, "promiseThenableThrowsAfterCallback: Should've resolved"); runTest(); }); @@ -606,7 +606,7 @@ function promiseThenableRejectThenResolve() { }}; var promise = Promise.resolve(thenable); - promise.then(function(v) { + promise.then(function() { ok(false, "promiseThenableRejectThenResolve should have rejected"); runTest(); }, function(e) { @@ -617,7 +617,7 @@ function promiseThenableRejectThenResolve() { function promiseWithThenReplaced() { // Ensure that we call the 'then' on the promise and not the internal then. - var promise = new Promise(function(resolve, reject) { + var promise = new Promise(function(resolve) { resolve(5); }); @@ -627,7 +627,7 @@ function promiseWithThenReplaced() { }; var promise2 = Promise.resolve(promise); - promise2.then(function(v) { + promise2.then(function() { ok(false, "promiseWithThenReplaced: Should've rejected"); runTest(); }, function(e) { @@ -646,7 +646,7 @@ function promiseStrictHandlers() { } function promiseStrictExecutorThisArg() { - new Promise(function(resolve, reject) { + new Promise(function() { "use strict"; ok(this === undefined, "thisArg should be undefined."); runTest(); @@ -667,12 +667,12 @@ function promiseResolveArray() { } function promiseResolveThenable() { - var p = Promise.resolve({ then(onFulfill, onReject) { onFulfill(2); } }); + var p = Promise.resolve({ then(onFulfill) { onFulfill(2); } }); ok(p instanceof Promise, "Should cast to a Promise."); p.then(function(v) { is(v, 2, "Should resolve to 2."); runTest(); - }, function(e) { + }, function() { ok(false, "promiseResolveThenable should've resolved"); runTest(); }); @@ -748,7 +748,7 @@ function promiseTestAsyncThenableResolution() { // Bug 1062323 function promiseWrapperAsyncResolution() { - var p = new Promise(function(resolve, reject) { + var p = new Promise(function(resolve) { resolve(); }); diff --git a/dom/promise/tests/test_promise_uncatchable_exception.html b/dom/promise/tests/test_promise_uncatchable_exception.html index 2bb6f1fe17..b8e4bdb103 100644 --- a/dom/promise/tests/test_promise_uncatchable_exception.html +++ b/dom/promise/tests/test_promise_uncatchable_exception.html @@ -16,7 +16,7 @@ <pre id="test"> <script type="application/javascript"> -onmessage = function(evt) { +onmessage = function() { ok(true, "finished"); SimpleTest.finish(); }; diff --git a/dom/promise/tests/test_promise_utils.html b/dom/promise/tests/test_promise_utils.html index b20d909351..44777dd965 100644 --- a/dom/promise/tests/test_promise_utils.html +++ b/dom/promise/tests/test_promise_utils.html @@ -69,7 +69,7 @@ function promiseAllIterable() { is(values[1], 1, "Array values should match."); is(values[2], undefined, "Array values should match."); runTest(); - }, function(e) { + }, function() { ok(false, "Promise.all shouldn't fail when an iterable is passed."); runTest(); }); @@ -125,7 +125,7 @@ function promiseAllRejectFails() { ]; var p = Promise.all(arr); - p.then(function(values) { + p.then(function() { ok(false, "Promise.all shouldn't resolve when iterable has rejected Promises."); runTest(); }, function(e) { @@ -140,7 +140,7 @@ function promiseAllCastError() { throw new ReferenceError("placeholder for nonexistent function call"); } }]); ok(p instanceof Promise, "Should cast to a Promise."); - p.then(function(v) { + p.then(function() { ok(false, "promiseAllCastError: should've rejected."); runTest(); }, function(e) { @@ -161,7 +161,7 @@ function promiseAllEnumerable() { } is(count, 3, "Resolved array from Promise.all should be enumerable"); runTest(); - }, function(e) { + }, function() { ok(false, "promiseAllEnumerable: should've resolved."); runTest(); }); @@ -230,7 +230,7 @@ function promiseRaceIterable() { Promise.race(participants()).then(function(winner) { is(winner, 10, "Winner should be the one that finished earlier."); runTest(); - }, function(e) { + }, function() { ok(false, "Promise.race shouldn't throw when an iterable is passed!"); runTest(); }); @@ -257,7 +257,7 @@ function promiseRaceReject() { function promiseRaceThrow() { var p = Promise.race([ - new Promise(function(resolve) { + new Promise(function() { throw new ReferenceError("placeholder for nonexistent function call"); }), new Promise(function(resolve) { diff --git a/dom/promise/tests/test_promise_xrays.html b/dom/promise/tests/test_promise_xrays.html index 8559dbb2a4..782251d332 100644 --- a/dom/promise/tests/test_promise_xrays.html +++ b/dom/promise/tests/test_promise_xrays.html @@ -44,24 +44,24 @@ function testHaveXray() { } function testConstructor1() { - var p = new win.Promise(function(resolve, reject) { resolve(win.Promise.resolve(5)); }); + var p = new win.Promise(function(resolve) { resolve(win.Promise.resolve(5)); }); p.then( function(arg) { is(arg, 5, "Content Promise constructor resolved with content promise should work"); }, - function(e) { + function() { ok(false, "Content Promise constructor resolved with content promise should not fail"); } ).then(nextTest); } function testConstructor2() { - var p = new win.Promise(function(resolve, reject) { resolve(Promise.resolve(5)); }); + var p = new win.Promise(function(resolve) { resolve(Promise.resolve(5)); }); p.then( function(arg) { is(arg, 5, "Content Promise constructor resolved with chrome promise should work"); }, - function(e) { + function() { ok(false, "Content Promise constructor resolved with chrome promise should not fail"); } ).then(nextTest); @@ -223,20 +223,20 @@ function testResolve3() { function(arg) { is(arg, 5, "Promise.resolve with chrome Promise should work"); }, - function(e) { + function() { ok(false, "Promise.resolve with chrome Promise should not fail"); } ).then(nextTest); } function testResolve4() { - var p = new win.Promise((res, rej) => {}); + var p = new win.Promise(() => {}); Cu.getJSTestingFunctions().resolvePromise(p, 42); p.then( function(arg) { is(arg, 42, "Resolving an Xray to a promise with TestingFunctions resolvePromise should work"); }, - function(e) { + function() { ok(false, "Resolving an Xray to a promise with TestingFunctions resolvePromise should not fail"); } ).then(nextTest); @@ -246,7 +246,7 @@ function testReject1() { var p = win.Promise.reject(5); ok(p instanceof win.Promise, "Promise.reject should return a promise"); p.then( - function(arg) { + function() { ok(false, "Promise should be rejected"); }, function(e) { @@ -256,10 +256,10 @@ function testReject1() { } function testReject2() { - var p = new win.Promise((res, rej) => {}); + var p = new win.Promise(() => {}); Cu.getJSTestingFunctions().rejectPromise(p, 42); p.then( - function(arg) { + function() { ok(false, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should trigger catch handler"); }, function(e) { @@ -277,7 +277,7 @@ function testThen1() { function(arg) { is(arg, 25, "Promise.then should work"); }, - function(e) { + function() { ok(false, "Promise.then should not fail"); } ).then(nextTest); @@ -292,7 +292,7 @@ function testThen2() { function(arg) { is(arg, 25, "Promise.then resolved with chrome promise should work"); }, - function(e) { + function() { ok(false, "Promise.then resolved with chrome promise should not fail"); } ).then(nextTest); @@ -308,7 +308,7 @@ function testCatch1() { function(arg) { is(arg, 25, "Promise.catch should work"); }, - function(e) { + function() { ok(false, "Promise.catch should not fail"); } ).then(nextTest); diff --git a/dom/promise/tests/test_resolve.html b/dom/promise/tests/test_resolve.html index 7e4745b47a..04101c94d3 100644 --- a/dom/promise/tests/test_resolve.html +++ b/dom/promise/tests/test_resolve.html @@ -44,7 +44,7 @@ function runTest() { var test = tests.pop(); - new Promise(function(resolve, reject) { + new Promise(function(resolve) { resolve(test); }).then(function(what) { ok(test === what, "What is: " + what); diff --git a/dom/promise/tests/test_webassembly_compile.html b/dom/promise/tests/test_webassembly_compile.html index 351f0f4ae4..5a1e669e0a 100644 --- a/dom/promise/tests/test_webassembly_compile.html +++ b/dom/promise/tests/test_webassembly_compile.html @@ -207,7 +207,7 @@ function compileStreamingDoubleUseFail() { }) .then( () => ok(false, "should have failed on second use"), - err => { ok(true, "failed on second use"); runTest(); } + () => { ok(true, "failed on second use"); runTest(); } ); }); } diff --git a/dom/promise/tests/unit/test_monitor_uncaught.js b/dom/promise/tests/unit/test_monitor_uncaught.js index afa328cb97..043e6dac8f 100644 --- a/dom/promise/tests/unit/test_monitor_uncaught.js +++ b/dom/promise/tests/unit/test_monitor_uncaught.js @@ -23,8 +23,8 @@ add_task(async function test_globals() { }); add_task(async function test_promiseID() { - let p1 = new Promise(resolve => {}); - let p2 = new Promise(resolve => {}); + let p1 = new Promise(() => {}); + let p2 = new Promise(() => {}); let p3 = p2.catch(null); let promise = [p1, p2, p3]; @@ -111,7 +111,7 @@ add_task(async function test_observe_uncaught() { let onConsumed = new CallbackResults("onConsumed"); let observer = { - onLeftUncaught(promise, data) { + onLeftUncaught(promise) { onLeftUncaught.observe(promise); }, onConsumed(promise) { @@ -121,7 +121,7 @@ add_task(async function test_observe_uncaught() { let resolveLater = function (delay = 20) { // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - return new Promise((resolve, reject) => setTimeout(resolve, delay)); + return new Promise(resolve => setTimeout(resolve, delay)); }; let rejectLater = function (delay = 20) { // eslint-disable-next-line mozilla/no-arbitrary-setTimeout diff --git a/dom/promise/tests/unit/test_promise_job_across_sandbox.js b/dom/promise/tests/unit/test_promise_job_across_sandbox.js index ff1d1575e3..d98947cccc 100644 --- a/dom/promise/tests/unit/test_promise_job_across_sandbox.js +++ b/dom/promise/tests/unit/test_promise_job_across_sandbox.js @@ -114,7 +114,7 @@ add_task(async function testThenableJob() { const p = new Promise(resolve => { // Create a bound function where its realm is privileged realm, and // its target is from sandbox realm. - sandbox.then = function (onFulfilled, onRejected) { + sandbox.then = function () { resolve(10); }; }); @@ -138,7 +138,7 @@ add_task(async function testThenableJobNuked() { const sandbox = createSandbox(); let called = false; - sandbox.then = function (onFulfilled, onRejected) { + sandbox.then = function () { called = true; }; @@ -175,6 +175,7 @@ add_task(async function testThenableJobAccessError() { sandbox.thenable = { get then() { accessed = true; + return undefined; }, }; diff --git a/dom/push/Push.sys.mjs b/dom/push/Push.sys.mjs index 07f763040f..2e55d1dc89 100644 --- a/dom/push/Push.sys.mjs +++ b/dom/push/Push.sys.mjs @@ -4,8 +4,6 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; -import { DOMRequestIpcHelper } from "resource://gre/modules/DOMRequestHelper.sys.mjs"; - const lazy = {}; ChromeUtils.defineLazyGetter(lazy, "console", () => { @@ -25,37 +23,37 @@ XPCOMUtils.defineLazyServiceGetter( "nsIPushService" ); -const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}"); - /** * The Push component runs in the child process and exposes the Push API * to the web application. The PushService running in the parent process is the * one actually performing all operations. */ -export function Push() { - lazy.console.debug("Push()"); -} - -Push.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - - contractID: "@mozilla.org/push/PushManager;1", - - classID: PUSH_CID, - - QueryInterface: ChromeUtils.generateQI([ - "nsIDOMGlobalPropertyInitializer", - "nsISupportsWeakReference", - "nsIObserver", - ]), +export class Push { + constructor() { + lazy.console.debug("Push()"); + } + + get contractID() { + return "@mozilla.org/push/PushManager;1"; + } + + get classID() { + return Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}"); + } + + get QueryInterface() { + return ChromeUtils.generateQI([ + "nsIDOMGlobalPropertyInitializer", + "nsISupportsWeakReference", + "nsIObserver", + ]); + } init(win) { lazy.console.debug("init()"); this._window = win; - this.initDOMRequestHelper(win); - // Get the client principal from the window. This won't be null because the // service worker should be available when accessing the push manager. this._principal = win.clientPrincipal; @@ -70,11 +68,11 @@ Push.prototype = { // Accessing the top-level document might fails if cross-origin this._topLevelPrincipal = undefined; } - }, + } __init(scope) { this._scope = scope; - }, + } askPermission() { lazy.console.debug("askPermission()"); @@ -82,7 +80,7 @@ Push.prototype = { let hasValidTransientUserGestureActivation = this._window.document.hasValidTransientUserGestureActivation; - return this.createPromise((resolve, reject) => { + return new this._window.Promise((resolve, reject) => { // Test permission before requesting to support GeckoView: // * GeckoViewPermissionChild wants to return early when requested without user activation // before doing actual permission check: @@ -90,7 +88,7 @@ Push.prototype = { // which is partly because: // * GeckoView test runner has no real permission check but just returns VALUE_ALLOW. // https://searchfox.org/mozilla-central/rev/6e5b9a5a1edab13a1b2e2e90944b6e06b4d8149c/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java#108-123 - if (this._testPermission() === Ci.nsIPermissionManager.ALLOW_ACTION) { + if (this.#testPermission() === Ci.nsIPermissionManager.ALLOW_ACTION) { resolve(); return; } @@ -111,42 +109,45 @@ Push.prototype = { return; } - this._requestPermission( + this.#requestPermission( hasValidTransientUserGestureActivation, resolve, permissionDenied ); }); - }, + } subscribe(options) { lazy.console.debug("subscribe()", this._scope); - return this.askPermission().then(() => - this.createPromise((resolve, reject) => { - let callback = new PushSubscriptionCallback(this, resolve, reject); - - if (!options || options.applicationServerKey === null) { - lazy.PushService.subscribe(this._scope, this._principal, callback); - return; - } - - let keyView = this._normalizeAppServerKey(options.applicationServerKey); - if (keyView.byteLength === 0) { - callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR); - return; - } - lazy.PushService.subscribeWithKey( - this._scope, - this._principal, - keyView, - callback - ); - }) + return this.askPermission().then( + () => + new this._window.Promise((resolve, reject) => { + let callback = new PushSubscriptionCallback(this, resolve, reject); + + if (!options || options.applicationServerKey === null) { + lazy.PushService.subscribe(this._scope, this._principal, callback); + return; + } + + let keyView = this.#normalizeAppServerKey( + options.applicationServerKey + ); + if (keyView.byteLength === 0) { + callback.rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR); + return; + } + lazy.PushService.subscribeWithKey( + this._scope, + this._principal, + keyView, + callback + ); + }) ); - }, + } - _normalizeAppServerKey(appServerKey) { + #normalizeAppServerKey(appServerKey) { let key; if (typeof appServerKey == "string") { try { @@ -169,25 +170,25 @@ Push.prototype = { key = appServerKey; } return new this._window.Uint8Array(key); - }, + } getSubscription() { lazy.console.debug("getSubscription()", this._scope); - return this.createPromise((resolve, reject) => { + return new this._window.Promise((resolve, reject) => { let callback = new PushSubscriptionCallback(this, resolve, reject); lazy.PushService.getSubscription(this._scope, this._principal, callback); }); - }, + } permissionState() { lazy.console.debug("permissionState()", this._scope); - return this.createPromise((resolve, reject) => { + return new this._window.Promise((resolve, reject) => { let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION; try { - permission = this._testPermission(); + permission = this.#testPermission(); } catch (e) { reject(); return; @@ -201,9 +202,9 @@ Push.prototype = { } resolve(pushPermissionStatus); }); - }, + } - _testPermission() { + #testPermission() { let permission = Services.perms.testExactPermissionFromPrincipal( this._principal, "desktop-notification" @@ -217,9 +218,9 @@ Push.prototype = { } } catch (e) {} return permission; - }, + } - _requestPermission( + #requestPermission( hasValidTransientUserGestureActivation, allowCallback, cancelCallback @@ -251,22 +252,24 @@ Push.prototype = { // remoting if needed. let windowUtils = this._window.windowUtils; windowUtils.askPermission(request); - }, -}; - -function PushSubscriptionCallback(pushManager, resolve, reject) { - this.pushManager = pushManager; - this.resolve = resolve; - this.reject = reject; + } } -PushSubscriptionCallback.prototype = { - QueryInterface: ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]), +class PushSubscriptionCallback { + constructor(pushManager, resolve, reject) { + this.pushManager = pushManager; + this.resolve = resolve; + this.reject = reject; + } + + get QueryInterface() { + return ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]); + } onPushSubscription(ok, subscription) { let { pushManager } = this; if (!Components.isSuccessCode(ok)) { - this._rejectWithError(ok); + this.rejectWithError(ok); return; } @@ -275,24 +278,24 @@ PushSubscriptionCallback.prototype = { return; } - let p256dhKey = this._getKey(subscription, "p256dh"); - let authSecret = this._getKey(subscription, "auth"); + let p256dhKey = this.#getKey(subscription, "p256dh"); + let authSecret = this.#getKey(subscription, "auth"); let options = { endpoint: subscription.endpoint, scope: pushManager._scope, p256dhKey, authSecret, }; - let appServerKey = this._getKey(subscription, "appServer"); + let appServerKey = this.#getKey(subscription, "appServer"); if (appServerKey) { // Avoid passing null keys to work around bug 1256449. options.appServerKey = appServerKey; } let sub = new pushManager._window.PushSubscription(options); this.resolve(sub); - }, + } - _getKey(subscription, name) { + #getKey(subscription, name) { let rawKey = Cu.cloneInto( subscription.getKey(name), this.pushManager._window @@ -305,9 +308,9 @@ PushSubscriptionCallback.prototype = { let keyView = new this.pushManager._window.Uint8Array(key); keyView.set(rawKey); return key; - }, + } - _rejectWithError(result) { + rejectWithError(result) { let error; switch (result) { case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR: @@ -331,5 +334,5 @@ PushSubscriptionCallback.prototype = { ); } this.reject(error); - }, -}; + } +} diff --git a/dom/push/PushComponents.sys.mjs b/dom/push/PushComponents.sys.mjs index 0ad0505851..4a2bfc1279 100644 --- a/dom/push/PushComponents.sys.mjs +++ b/dom/push/PushComponents.sys.mjs @@ -40,7 +40,7 @@ const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified"; * similar to the Push DOM API, but does not require service workers. * * Push service methods may be called from the parent or content process. The - * parent process implementation loads `PushService.jsm` at app startup, and + * parent process implementation loads `PushService.sys.mjs` at app startup, and * calls its methods directly. The content implementation forwards calls to * the parent Push service via IPC. * @@ -78,7 +78,7 @@ PushServiceBase.prototype = { return this._messages.includes(message.name); }, - observe(subject, topic, data) { + observe(subject, topic) { if (topic === "android-push-service") { // Load PushService immediately. this.ensureReady(); @@ -102,7 +102,7 @@ PushServiceBase.prototype = { /** * The parent process implementation of `nsIPushService`. This version loads - * `PushService.jsm` at startup and calls its methods directly. It also + * `PushService.sys.mjs` at startup and calls its methods directly. It also * receives and responds to requests from the content process. */ let parentInstance; @@ -165,7 +165,7 @@ Object.assign(PushServiceParent.prototype, { result => { callback.onUnsubscribe(Cr.NS_OK, result); }, - error => { + () => { callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false); } ) @@ -192,10 +192,10 @@ Object.assign(PushServiceParent.prototype, { domain, }) .then( - result => { + () => { callback.onClear(Cr.NS_OK); }, - error => { + () => { callback.onClear(Cr.NS_ERROR_FAILURE); } ) diff --git a/dom/push/PushCrypto.sys.mjs b/dom/push/PushCrypto.sys.mjs index 384901f925..fab1dc4762 100644 --- a/dom/push/PushCrypto.sys.mjs +++ b/dom/push/PushCrypto.sys.mjs @@ -377,7 +377,7 @@ class Decoder { * @param {BufferSource} ikm The ECDH shared secret. * @returns {Array} A `[gcmBits, nonce]` tuple. */ - async deriveKeyAndNonce(ikm) { + async deriveKeyAndNonce() { throw new Error("Missing `deriveKeyAndNonce` implementation"); } @@ -408,7 +408,7 @@ class Decoder { * @param {Uint8Array} chunk The decrypted block with padding. * @returns {Uint8Array} The block with padding removed. */ - unpadChunk(chunk, last) { + unpadChunk() { throw new Error("Missing `unpadChunk` implementation"); } diff --git a/dom/push/PushDB.sys.mjs b/dom/push/PushDB.sys.mjs index d6eab52040..aa35093200 100644 --- a/dom/push/PushDB.sys.mjs +++ b/dom/push/PushDB.sys.mjs @@ -46,7 +46,7 @@ PushDB.prototype = { ); }, - upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) { + upgradeSchema(aTransaction, aDb, aOldVersion) { if (aOldVersion <= 3) { // XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old // registrations away without even informing the app. @@ -419,7 +419,7 @@ PushDB.prototype = { } function putRecord() { let req = aStore.put(newRecord); - req.onsuccess = aEvent => { + req.onsuccess = () => { lazy.console.debug( "update: Update successful", aKeyID, diff --git a/dom/push/PushService.sys.mjs b/dom/push/PushService.sys.mjs index 7314e3df54..1add65992f 100644 --- a/dom/push/PushService.sys.mjs +++ b/dom/push/PushService.sys.mjs @@ -623,7 +623,7 @@ export var PushService = { this._db.close(); this._db = null; }, - err => { + () => { this._db.close(); this._db = null; } @@ -1162,17 +1162,15 @@ export var PushService = { let keyPromise; if (aPageRecord.appServerKey && aPageRecord.appServerKey.length) { let keyView = new Uint8Array(aPageRecord.appServerKey); - keyPromise = lazy.PushCrypto.validateAppServerKey(keyView).catch( - error => { - // Normalize Web Crypto exceptions. `nsIPushService` will forward the - // error result to the DOM API implementation in `PushManager.cpp` or - // `Push.js`, which will convert it to the correct `DOMException`. - throw errorWithResult( - "Invalid app server key", - Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR - ); - } - ); + keyPromise = lazy.PushCrypto.validateAppServerKey(keyView).catch(() => { + // Normalize Web Crypto exceptions. `nsIPushService` will forward the + // error result to the DOM API implementation in `PushManager.cpp` or + // `Push.js`, which will convert it to the correct `DOMException`. + throw errorWithResult( + "Invalid app server key", + Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR + ); + }); } else { keyPromise = Promise.resolve(null); } diff --git a/dom/push/PushServiceHttp2.sys.mjs b/dom/push/PushServiceHttp2.sys.mjs index 76ac85d7b1..838071f701 100644 --- a/dom/push/PushServiceHttp2.sys.mjs +++ b/dom/push/PushServiceHttp2.sys.mjs @@ -52,7 +52,7 @@ PushSubscriptionListener.prototype = { return this.QueryInterface(aIID); }, - onStartRequest(aRequest) { + onStartRequest() { lazy.console.debug("PushSubscriptionListener: onStartRequest()"); // We do not do anything here. }, @@ -174,7 +174,7 @@ var PushServiceDelete = function (resolve, reject) { }; PushServiceDelete.prototype = { - onStartRequest(aRequest) {}, + onStartRequest() {}, onDataAvailable(aRequest, aStream, aOffset, aCount) { // Nobody should send data, but just to be sure, otherwise necko will @@ -218,9 +218,9 @@ var SubscriptionListener = function ( }; SubscriptionListener.prototype = { - onStartRequest(aRequest) {}, + onStartRequest() {}, - onDataAvailable(aRequest, aStream, aOffset, aCount) {}, + onDataAvailable() {}, onStopRequest(aRequest, aStatus) { lazy.console.debug("SubscriptionListener: onStopRequest()"); @@ -418,12 +418,12 @@ export var PushServiceHttp2 = { return this._mainPushService !== null; }, - async connect(broadcastListeners) { + async connect() { let subscriptions = await this._mainPushService.getAllUnexpired(); this.startConnections(subscriptions); }, - async sendSubscribeBroadcast(serviceId, version) { + async sendSubscribeBroadcast() { // Not implemented yet }, @@ -737,7 +737,7 @@ export var PushServiceHttp2 = { .catch(console.error); } }, - error => { + () => { if (this._mainPushService) { this._mainPushService .dropRegistrationAndNotifyApp(aSubscriptionUri) diff --git a/dom/push/PushServiceWebSocket.sys.mjs b/dom/push/PushServiceWebSocket.sys.mjs index 72d8791a96..59e8f403f1 100644 --- a/dom/push/PushServiceWebSocket.sys.mjs +++ b/dom/push/PushServiceWebSocket.sys.mjs @@ -80,11 +80,11 @@ PushWebSocketListener.prototype = { this._pushService._wsOnStop(context, statusCode); }, - onAcknowledge(context, size) { + onAcknowledge() { // EMPTY }, - onBinaryMessageAvailable(context, message) { + onBinaryMessageAvailable() { // EMPTY }, @@ -971,7 +971,7 @@ export var PushServiceWebSocket = { // Otherwise, we're still setting up. If we don't have a request queue, // make one now. if (!this._notifyRequestQueue) { - let promise = new Promise((resolve, reject) => { + let promise = new Promise(resolve => { this._notifyRequestQueue = resolve; }); this._enqueue(_ => promise); @@ -1037,7 +1037,7 @@ export var PushServiceWebSocket = { }, // begin Push protocol handshake - _wsOnStart(context) { + _wsOnStart() { lazy.console.debug("wsOnStart()"); if (this._currentState != STATE_WAITING_FOR_WS_START) { diff --git a/dom/push/test/lifetime_worker.js b/dom/push/test/lifetime_worker.js index 02c09d966e..251a4a2bee 100644 --- a/dom/push/test/lifetime_worker.js +++ b/dom/push/test/lifetime_worker.js @@ -20,7 +20,7 @@ self.onfetch = function (event) { state = "update"; } else if (event.request.url.includes("wait")) { event.respondWith( - new Promise(function (res, rej) { + new Promise(function (res) { if (resolvePromiseCallback) { dump("ERROR: service worker was already waiting on a promise.\n"); } @@ -50,7 +50,7 @@ self.onmessage = function (event) { state = event.data; if (state === "wait") { event.waitUntil( - new Promise(function (res, rej) { + new Promise(function (res) { if (resolvePromiseCallback) { dump("ERROR: service worker was already waiting on a promise.\n"); } diff --git a/dom/push/test/mockpushserviceparent.js b/dom/push/test/mockpushserviceparent.js index 08d93f3aaf..7553b81876 100644 --- a/dom/push/test/mockpushserviceparent.js +++ b/dom/push/test/mockpushserviceparent.js @@ -85,7 +85,7 @@ addMessageListener("socket-setup", function () { }); }); -addMessageListener("socket-teardown", function (msg) { +addMessageListener("socket-teardown", function () { pushService .restoreServiceBackend() .then(_ => { diff --git a/dom/push/test/test_data.html b/dom/push/test/test_data.html index a2f043b7d9..0fad066cb7 100644 --- a/dom/push/test/test_data.html +++ b/dom/push/test/test_data.html @@ -154,7 +154,7 @@ http://creativecommons.org/licenses/publicdomain/ is(message.data.text, "Hi! \ud83d\udc40", "Wrong text for message with emoji"); var text = await new Promise((resolve, reject) => { var reader = new FileReader(); - reader.onloadend = event => { + reader.onloadend = () => { if (reader.error) { reject(reader.error); } else { diff --git a/dom/push/test/test_has_permissions.html b/dom/push/test/test_has_permissions.html index 0bef8fe19f..b66a9d4746 100644 --- a/dom/push/test/test_has_permissions.html +++ b/dom/push/test/test_has_permissions.html @@ -23,7 +23,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } diff --git a/dom/push/test/test_multiple_register.html b/dom/push/test/test_multiple_register.html index 3a963b7cd4..8073e0ccbf 100644 --- a/dom/push/test/test_multiple_register.html +++ b/dom/push/test/test_multiple_register.html @@ -23,7 +23,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } @@ -46,12 +46,12 @@ http://creativecommons.org/licenses/publicdomain/ } function setupPushNotification(swr) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { swr.pushManager.subscribe().then( function(pushSubscription) { ok(true, "successful registered for push notification"); res({swr, pushSubscription}); - }, function(error) { + }, function() { ok(false, "could not register for push notification"); res(null); } @@ -61,12 +61,12 @@ http://creativecommons.org/licenses/publicdomain/ } function setupSecondEndpoint(result) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { result.swr.pushManager.subscribe().then( function(pushSubscription) { ok(result.pushSubscription.endpoint == pushSubscription.endpoint, "setupSecondEndpoint - Got the same endpoint back."); res(result); - }, function(error) { + }, function() { ok(false, "could not register for push notification"); res(null); } @@ -76,12 +76,12 @@ http://creativecommons.org/licenses/publicdomain/ } function getEndpointExpectNull(swr) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { swr.pushManager.getSubscription().then( function(pushSubscription) { ok(pushSubscription == null, "getEndpoint should return null when app not subscribed."); res(swr); - }, function(error) { + }, function() { ok(false, "could not register for push notification"); res(null); } @@ -91,13 +91,13 @@ http://creativecommons.org/licenses/publicdomain/ } function getEndpoint(result) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { result.swr.pushManager.getSubscription().then( function(pushSubscription) { ok(result.pushSubscription.endpoint == pushSubscription.endpoint, "getEndpoint - Got the same endpoint back."); res(pushSubscription); - }, function(error) { + }, function() { ok(false, "could not register for push notification"); res(null); } diff --git a/dom/push/test/test_multiple_register_different_scope.html b/dom/push/test/test_multiple_register_different_scope.html index b7c5bf1414..34cef0b015 100644 --- a/dom/push/test/test_multiple_register_different_scope.html +++ b/dom/push/test/test_multiple_register_different_scope.html @@ -26,7 +26,7 @@ http://creativecommons.org/licenses/publicdomain/ var scopeA = "./a/"; var scopeB = "./b/"; - function debug(str) { + function debug() { // console.log(str + "\n"); } diff --git a/dom/push/test/test_multiple_register_during_service_activation.html b/dom/push/test/test_multiple_register_during_service_activation.html index be043a523e..fcce74affb 100644 --- a/dom/push/test/test_multiple_register_during_service_activation.html +++ b/dom/push/test/test_multiple_register_during_service_activation.html @@ -25,7 +25,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } diff --git a/dom/push/test/test_permissions.html b/dom/push/test/test_permissions.html index 442cfe4a09..4562175b5c 100644 --- a/dom/push/test/test_permissions.html +++ b/dom/push/test/test_permissions.html @@ -23,7 +23,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } diff --git a/dom/push/test/test_register.html b/dom/push/test/test_register.html index 541e4a2d8d..c5800d328c 100644 --- a/dom/push/test/test_register.html +++ b/dom/push/test/test_register.html @@ -23,7 +23,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } diff --git a/dom/push/test/test_register_key.html b/dom/push/test/test_register_key.html index b3e7570770..ba216d24dc 100644 --- a/dom/push/test/test_register_key.html +++ b/dom/push/test/test_register_key.html @@ -58,7 +58,7 @@ http://creativecommons.org/licenses/publicdomain/ }; }, - registration(pageRecord) { + registration() { return { endpoint: "https://example.com/push/subWithKey", appServerKey: testKey, diff --git a/dom/push/test/test_serviceworker_lifetime.html b/dom/push/test/test_serviceworker_lifetime.html index 30f191a119..c8098e1728 100644 --- a/dom/push/test/test_serviceworker_lifetime.html +++ b/dom/push/test/test_serviceworker_lifetime.html @@ -42,7 +42,7 @@ } function waitForActiveServiceWorker(ctx) { - return waitForActive(ctx.registration).then(function(result) { + return waitForActive(ctx.registration).then(function() { ok(ctx.registration.active, "Service Worker is active"); return ctx; }); @@ -57,13 +57,13 @@ } function registerPushNotification(ctx) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { ctx.registration.pushManager.subscribe().then( function(pushSubscription) { ok(true, "successful registered for push notification"); ctx.subscription = pushSubscription; res(ctx); - }, function(error) { + }, function() { ok(false, "could not register for push notification"); res(ctx); }); @@ -103,7 +103,7 @@ } function createIframe(ctx) { - var p = new Promise(function(res, rej) { + var p = new Promise(function(res) { var iframe = document.createElement("iframe"); // This file doesn't exist, the service worker will give us an empty // document. @@ -120,7 +120,7 @@ function closeIframe(ctx) { ctx.iframe.remove(); - return new Promise(function(res, rej) { + return new Promise(function(res) { // XXXcatalinb: give the worker more time to "notice" it stopped // controlling documents ctx.iframe = null; @@ -135,7 +135,7 @@ this.navigator.serviceWorker.onmessage = null; resolve(); } - return new Promise(function(res, rej) { + return new Promise(function(res) { contentWindow.navigator.serviceWorker.onmessage = checkMessage.bind(contentWindow, expected, res); }); @@ -148,7 +148,7 @@ return p; } - function pushEvent(ctx, expected_state, new_state) { + function pushEvent(ctx, expected_state) { var expected = {type: "push", state: expected_state}; var p = waitAndCheckMessage(ctx.iframe.contentWindow, expected); sendPushToPushServer(ctx.subscription.endpoint); @@ -184,9 +184,9 @@ return function(ctx) { cancelShutdownObserver(ctx); - ctx.observer_promise = new Promise(function(res, rej) { + ctx.observer_promise = new Promise(function(res) { ctx.observer = { - observe(subject, topic, data) { + observe(subject, topic) { ok((topic == shutdownTopic) && expectingEvent, "Service worker was terminated."); this.remove(ctx); }, @@ -217,7 +217,7 @@ function subTest(test) { return function(ctx) { - return new Promise(function(res, rej) { + return new Promise(function(res) { function run() { test.steps(ctx).catch(function(e) { ok(false, "Some test failed with error: " + e); @@ -322,7 +322,7 @@ ["dom.serviceWorkers.idle_extended_timeout", 0], ], steps(context) { - return new Promise(function(res, rej) { + return new Promise(function(res) { context.iframe.contentWindow.navigator.serviceWorker.controller.postMessage("ping"); res(context); }); diff --git a/dom/push/test/test_try_registering_offline_disabled.html b/dom/push/test/test_try_registering_offline_disabled.html index d993e73e60..db65c79ce6 100644 --- a/dom/push/test/test_try_registering_offline_disabled.html +++ b/dom/push/test/test_try_registering_offline_disabled.html @@ -23,7 +23,7 @@ http://creativecommons.org/licenses/publicdomain/ </pre> <script class="testbody" type="text/javascript"> - function debug(str) { + function debug() { // console.log(str + "\n"); } @@ -54,12 +54,12 @@ http://creativecommons.org/licenses/publicdomain/ } function subscribeFail(swr) { - return new Promise((res, rej) => { + return new Promise((res) => { swr.pushManager.subscribe() - .then(sub => { + .then(() => { ok(false, "successful registered for push notification"); throw new Error("Should fail"); - }, err => { + }, () => { ok(true, "could not register for push notification"); res(swr); }); @@ -118,7 +118,7 @@ http://creativecommons.org/licenses/publicdomain/ }; function changeOfflineState(offline) { - return new Promise(function(res, rej) { + return new Promise(function(res) { // eslint-disable-next-line mozilla/use-services const obsService = SpecialPowers.Cc["@mozilla.org/observer-service;1"] .getService(SpecialPowers.Ci.nsIObserverService); diff --git a/dom/push/test/test_utils.js b/dom/push/test/test_utils.js index 0214318d09..5ecda2bd96 100644 --- a/dom/push/test/test_utils.js +++ b/dom/push/test/test_utils.js @@ -98,7 +98,7 @@ class MockWebSocket { this._isActive = false; } - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -131,7 +131,7 @@ class MockWebSocket { ); } - onAck(request) { + onAck() { // Do nothing. } @@ -222,7 +222,7 @@ function setupPrefsAndMockSocket(mockSocket) { } function injectControlledFrame(target = document.body) { - return new Promise(function (res, rej) { + return new Promise(function (res) { var iframe = document.createElement("iframe"); iframe.src = "/tests/dom/push/test/frame.html"; @@ -266,7 +266,7 @@ function waitForActive(swr) { resolve(swr); return; } - sw.addEventListener("statechange", function onStateChange(evt) { + sw.addEventListener("statechange", function onStateChange() { if (sw.state === "activated") { sw.removeEventListener("statechange", onStateChange); resolve(swr); diff --git a/dom/push/test/worker.js b/dom/push/test/worker.js index bcdbf0e0ad..f576ea3cf0 100644 --- a/dom/push/test/worker.js +++ b/dom/push/test/worker.js @@ -82,7 +82,7 @@ function handlePush(event) { } var testHandlers = { - publicKey(data) { + publicKey() { return self.registration.pushManager .getSubscription() .then(subscription => ({ @@ -116,7 +116,7 @@ var testHandlers = { }); }, - denySubscribe(data) { + denySubscribe() { return self.registration.pushManager .getSubscription() .then(subscription => { diff --git a/dom/push/test/xpcshell/broadcast_handler.sys.mjs b/dom/push/test/xpcshell/broadcast_handler.sys.mjs index eecf220a6f..f1f5a0fa89 100644 --- a/dom/push/test/xpcshell/broadcast_handler.sys.mjs +++ b/dom/push/test/xpcshell/broadcast_handler.sys.mjs @@ -2,7 +2,7 @@ export var broadcastHandler = { reset() { this.notifications = []; - this.wasNotified = new Promise((resolve, reject) => { + this.wasNotified = new Promise(resolve => { this.receivedBroadcastMessage = function () { resolve(); this.notifications.push(Array.from(arguments)); diff --git a/dom/push/test/xpcshell/head.js b/dom/push/test/xpcshell/head.js index da50ee3c5c..926d523201 100644 --- a/dom/push/test/xpcshell/head.js +++ b/dom/push/test/xpcshell/head.js @@ -46,7 +46,7 @@ var isParent = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; // Stop and clean up after the PushService. -Services.obs.addObserver(function observe(subject, topic, data) { +Services.obs.addObserver(function observe(subject, topic) { Services.obs.removeObserver(observe, topic); PushService.uninit(); // Occasionally, `profile-change-teardown` and `xpcom-shutdown` will fire @@ -107,7 +107,7 @@ function waterfall(...callbacks) { * @returns {Promise} A promise that fulfills when the notification is fired. */ function promiseObserverNotification(topic, matchFunc) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { Services.obs.addObserver(function observe(subject, aTopic, data) { let matches = typeof matchFunc != "function" || matchFunc(subject, data); if (!matches) { @@ -305,7 +305,7 @@ MockWebSocket.prototype = { this._handleMessage(msg); }, - close(code, reason) { + close() { waterfall(() => this._listener.onStop(this._context, Cr.NS_OK)); }, @@ -413,7 +413,7 @@ var setUpServiceInParent = async function (service, db) { }), makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_broadcast_success.js b/dom/push/test/xpcshell/test_broadcast_success.js index 16f586081b..1f4c8b4a42 100644 --- a/dom/push/test/xpcshell/test_broadcast_success.js +++ b/dom/push/test/xpcshell/test_broadcast_success.js @@ -189,7 +189,7 @@ add_task(async function test_handle_hello_broadcasts() { ); }, - onBroadcastSubscribe(data) {}, + onBroadcastSubscribe() {}, }); }, }); @@ -239,7 +239,7 @@ add_task(async function test_broadcast_context() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(data) {}, + onHello() {}, }); }, }); diff --git a/dom/push/test/xpcshell/test_clearAll_successful.js b/dom/push/test/xpcshell/test_clearAll_successful.js index a638fffaaf..7a919c3b23 100644 --- a/dom/push/test/xpcshell/test_clearAll_successful.js +++ b/dom/push/test/xpcshell/test_clearAll_successful.js @@ -7,7 +7,7 @@ var db; var unregisterDefers = {}; var userAgentID = "4ce480ef-55b2-4f83-924c-dcd35ab978b4"; -function promiseUnregister(keyID, code) { +function promiseUnregister(keyID) { return new Promise(r => (unregisterDefers[keyID] = r)); } @@ -41,7 +41,7 @@ add_task(async function setup() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_clear_forgetAboutSite.js b/dom/push/test/xpcshell/test_clear_forgetAboutSite.js index 27ae57af25..5bb385969e 100644 --- a/dom/push/test/xpcshell/test_clear_forgetAboutSite.js +++ b/dom/push/test/xpcshell/test_clear_forgetAboutSite.js @@ -79,7 +79,7 @@ add_task(async function setup() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_clear_origin_data.js b/dom/push/test/xpcshell/test_clear_origin_data.js index 7c743148a6..fe42031df3 100644 --- a/dom/push/test/xpcshell/test_clear_origin_data.js +++ b/dom/push/test/xpcshell/test_clear_origin_data.js @@ -59,11 +59,6 @@ add_task(async function test_webapps_cleardata() { originAttributes: {}, clearIf: { inIsolatedMozBrowser: false }, }, - { - scope: "https://example.org/1", - originAttributes: { inIsolatedMozBrowser: true }, - clearIf: {}, - }, ]; let unregisterDone; diff --git a/dom/push/test/xpcshell/test_drop_expired.js b/dom/push/test/xpcshell/test_drop_expired.js index 823049c21f..b933919516 100644 --- a/dom/push/test/xpcshell/test_drop_expired.js +++ b/dom/push/test/xpcshell/test_drop_expired.js @@ -111,7 +111,7 @@ add_task(async function setUp() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_notification_duplicate.js b/dom/push/test/xpcshell/test_notification_duplicate.js index 9812b63149..039437060f 100644 --- a/dom/push/test/xpcshell/test_notification_duplicate.js +++ b/dom/push/test/xpcshell/test_notification_duplicate.js @@ -116,7 +116,7 @@ add_task(async function test_notification_duplicate() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_notification_error.js b/dom/push/test/xpcshell/test_notification_error.js index 21ab7ab94f..f50b58e1ef 100644 --- a/dom/push/test/xpcshell/test_notification_error.js +++ b/dom/push/test/xpcshell/test_notification_error.js @@ -75,7 +75,7 @@ add_task(async function test_notification_error() { }), makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_notification_incomplete.js b/dom/push/test/xpcshell/test_notification_incomplete.js index 48aba51132..ee94b44316 100644 --- a/dom/push/test/xpcshell/test_notification_incomplete.js +++ b/dom/push/test/xpcshell/test_notification_incomplete.js @@ -56,7 +56,7 @@ add_task(async function test_notification_incomplete() { await db.put(record); } - function observeMessage(subject, topic, data) { + function observeMessage() { ok(false, "Should not deliver malformed updates"); } registerCleanupFunction(() => @@ -79,7 +79,7 @@ add_task(async function test_notification_incomplete() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_notification_version_string.js b/dom/push/test/xpcshell/test_notification_version_string.js index 7aaaee5269..f0c10c8fc9 100644 --- a/dom/push/test/xpcshell/test_notification_version_string.js +++ b/dom/push/test/xpcshell/test_notification_version_string.js @@ -39,7 +39,7 @@ add_task(async function test_notification_version_string() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_permissions.js b/dom/push/test/xpcshell/test_permissions.js index 1b3e3282bb..c9e027e3d8 100644 --- a/dom/push/test/xpcshell/test_permissions.js +++ b/dom/push/test/xpcshell/test_permissions.js @@ -117,7 +117,7 @@ add_task(async function setUp() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -149,7 +149,7 @@ add_task(async function setUp() { }) ); }, - onACK(request) {}, + onACK() {}, }); }, }); diff --git a/dom/push/test/xpcshell/test_quota_exceeded.js b/dom/push/test/xpcshell/test_quota_exceeded.js index f8365aa888..d802871550 100644 --- a/dom/push/test/xpcshell/test_quota_exceeded.js +++ b/dom/push/test/xpcshell/test_quota_exceeded.js @@ -77,7 +77,7 @@ add_task(async function test_expiration_origin_threshold() { let updates = 0; let notifyPromise = promiseObserverNotification( PushServiceComponent.pushTopic, - (subject, data) => { + () => { updates++; return updates == 6; } @@ -91,7 +91,7 @@ add_task(async function test_expiration_origin_threshold() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -140,7 +140,7 @@ add_task(async function test_expiration_origin_threshold() { }, // We expect to receive acks, but don't care about their // contents. - onACK(request) {}, + onACK() {}, }); }, }); diff --git a/dom/push/test/xpcshell/test_quota_observer.js b/dom/push/test/xpcshell/test_quota_observer.js index 447f509967..aafc5fa17e 100644 --- a/dom/push/test/xpcshell/test_quota_observer.js +++ b/dom/push/test/xpcshell/test_quota_observer.js @@ -77,7 +77,7 @@ add_task(async function test_expiration_history_observer() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -106,7 +106,7 @@ add_task(async function test_expiration_history_observer() { equal(request.code, 201, "Expected quota exceeded unregister reason"); unregisterDone(); }, - onACK(request) {}, + onACK() {}, }); }, }); diff --git a/dom/push/test/xpcshell/test_quota_with_notification.js b/dom/push/test/xpcshell/test_quota_with_notification.js index d2e6d7cae8..015033c505 100644 --- a/dom/push/test/xpcshell/test_quota_with_notification.js +++ b/dom/push/test/xpcshell/test_quota_with_notification.js @@ -50,7 +50,7 @@ add_task(async function test_expiration_origin_threshold() { let updates = 0; let notifyPromise = promiseObserverNotification( PushServiceComponent.pushTopic, - (subject, data) => { + () => { updates++; return updates == numMessages; } @@ -59,7 +59,7 @@ add_task(async function test_expiration_origin_threshold() { let modifications = 0; let modifiedPromise = promiseObserverNotification( PushServiceComponent.subscriptionModifiedTopic, - (subject, data) => { + () => { // Each subscription should be modified twice: once to update the message // count and last push time, and the second time to update the quota. modifications++; @@ -67,7 +67,7 @@ add_task(async function test_expiration_origin_threshold() { } ); - let updateQuotaPromise = new Promise((resolve, reject) => { + let updateQuotaPromise = new Promise(resolve => { let quotaUpdateCount = 0; PushService._updateQuotaTestCallback = function () { quotaUpdateCount++; @@ -82,7 +82,7 @@ add_task(async function test_expiration_origin_threshold() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -107,12 +107,12 @@ add_task(async function test_expiration_origin_threshold() { ); } }, - onUnregister(request) { + onUnregister() { ok(false, "Channel should not be unregistered."); }, // We expect to receive acks, but don't care about their // contents. - onACK(request) {}, + onACK() {}, }); }, }); diff --git a/dom/push/test/xpcshell/test_reconnect_retry.js b/dom/push/test/xpcshell/test_reconnect_retry.js index 7ff3740ee8..9350781e8e 100644 --- a/dom/push/test/xpcshell/test_reconnect_retry.js +++ b/dom/push/test/xpcshell/test_reconnect_retry.js @@ -25,7 +25,7 @@ add_task(async function test_reconnect_retry() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_case.js b/dom/push/test/xpcshell/test_register_case.js index 1ae6f127f3..f70d2f3f84 100644 --- a/dom/push/test/xpcshell/test_register_case.js +++ b/dom/push/test/xpcshell/test_register_case.js @@ -22,7 +22,7 @@ add_task(async function test_register_case() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "HELLO", diff --git a/dom/push/test/xpcshell/test_register_flush.js b/dom/push/test/xpcshell/test_register_flush.js index 2c12ecb9ba..132f2d029d 100644 --- a/dom/push/test/xpcshell/test_register_flush.js +++ b/dom/push/test/xpcshell/test_register_flush.js @@ -42,7 +42,7 @@ add_task(async function test_register_flush() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_invalid_channel.js b/dom/push/test/xpcshell/test_register_invalid_channel.js index 0f1b6cf1b1..93b91ab29a 100644 --- a/dom/push/test/xpcshell/test_register_invalid_channel.js +++ b/dom/push/test/xpcshell/test_register_invalid_channel.js @@ -24,7 +24,7 @@ add_task(async function test_register_invalid_channel() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -33,7 +33,7 @@ add_task(async function test_register_invalid_channel() { }) ); }, - onRegister(request) { + onRegister() { this.serverSendMsg( JSON.stringify({ messageType: "register", diff --git a/dom/push/test/xpcshell/test_register_invalid_endpoint.js b/dom/push/test/xpcshell/test_register_invalid_endpoint.js index 64398b97f8..1f4f6e0023 100644 --- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js +++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js @@ -24,7 +24,7 @@ add_task(async function test_register_invalid_endpoint() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -33,7 +33,7 @@ add_task(async function test_register_invalid_endpoint() { }) ); }, - onRegister(request) { + onRegister() { this.serverSendMsg( JSON.stringify({ messageType: "register", diff --git a/dom/push/test/xpcshell/test_register_invalid_json.js b/dom/push/test/xpcshell/test_register_invalid_json.js index d7a19e789c..38030f19a6 100644 --- a/dom/push/test/xpcshell/test_register_invalid_json.js +++ b/dom/push/test/xpcshell/test_register_invalid_json.js @@ -25,7 +25,7 @@ add_task(async function test_register_invalid_json() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_no_id.js b/dom/push/test/xpcshell/test_register_no_id.js index 763350b11e..e58b29b60e 100644 --- a/dom/push/test/xpcshell/test_register_no_id.js +++ b/dom/push/test/xpcshell/test_register_no_id.js @@ -26,7 +26,7 @@ add_task(async function test_register_no_id() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_request_queue.js b/dom/push/test/xpcshell/test_register_request_queue.js index 6d7928c52a..fbe44cb7ce 100644 --- a/dom/push/test/xpcshell/test_register_request_queue.js +++ b/dom/push/test/xpcshell/test_register_request_queue.js @@ -21,7 +21,7 @@ add_task(async function test_register_request_queue() { let onHello; let helloPromise = new Promise( resolve => - (onHello = after(2, function onHelloReceived(request) { + (onHello = after(2, function onHelloReceived() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_rollback.js b/dom/push/test/xpcshell/test_register_rollback.js index 9a0233aca8..6ace051a9e 100644 --- a/dom/push/test/xpcshell/test_register_rollback.js +++ b/dom/push/test/xpcshell/test_register_rollback.js @@ -30,7 +30,7 @@ add_task(async function test_register_rollback() { PushService.init({ serverURI: "wss://push.example.org/", db: makeStub(db, { - put(prev, record) { + put() { return Promise.reject("universe has imploded"); }, }), diff --git a/dom/push/test/xpcshell/test_register_wrong_id.js b/dom/push/test/xpcshell/test_register_wrong_id.js index 674ce9f9ff..06dc73ee4f 100644 --- a/dom/push/test/xpcshell/test_register_wrong_id.js +++ b/dom/push/test/xpcshell/test_register_wrong_id.js @@ -28,7 +28,7 @@ add_task(async function test_register_wrong_id() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_register_wrong_type.js b/dom/push/test/xpcshell/test_register_wrong_type.js index b3b9aaa927..f1fcf582bc 100644 --- a/dom/push/test/xpcshell/test_register_wrong_type.js +++ b/dom/push/test/xpcshell/test_register_wrong_type.js @@ -24,7 +24,7 @@ add_task(async function test_register_wrong_type() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -34,7 +34,7 @@ add_task(async function test_register_wrong_type() { ); helloDone(); }, - onRegister(request) { + onRegister() { registers++; this.serverSendMsg( JSON.stringify({ diff --git a/dom/push/test/xpcshell/test_registration_error.js b/dom/push/test/xpcshell/test_registration_error.js index cba22ddc1c..6e826181fe 100644 --- a/dom/push/test/xpcshell/test_registration_error.js +++ b/dom/push/test/xpcshell/test_registration_error.js @@ -20,7 +20,7 @@ add_task(async function test_registrations_error() { PushService.init({ serverURI: "wss://push.example.org/", db: makeStub(db, { - getByIdentifiers(prev, scope) { + getByIdentifiers() { return Promise.reject("Database error"); }, }), diff --git a/dom/push/test/xpcshell/test_retry_ws.js b/dom/push/test/xpcshell/test_retry_ws.js index 19df72acd3..87f9e7dba8 100644 --- a/dom/push/test/xpcshell/test_retry_ws.js +++ b/dom/push/test/xpcshell/test_retry_ws.js @@ -33,7 +33,7 @@ add_task(async function test_ws_retry() { // Use a mock timer to avoid waiting for the backoff interval. let reconnects = 0; PushServiceWebSocket._backoffTimer = { - init(observer, delay, type) { + init(observer, delay) { reconnects++; ok( delay >= 5 && delay <= 2000, @@ -51,7 +51,7 @@ add_task(async function test_ws_retry() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { if (reconnects == 10) { this.serverSendMsg( JSON.stringify({ diff --git a/dom/push/test/xpcshell/test_service_child.js b/dom/push/test/xpcshell/test_service_child.js index a22f1f4d73..7e44cadd7c 100644 --- a/dom/push/test/xpcshell/test_service_child.js +++ b/dom/push/test/xpcshell/test_service_child.js @@ -108,7 +108,7 @@ add_test(function test_subscribeWithKey_success() { } ); }, - error => { + () => { ok(false, "Error generating app server key"); done(); } @@ -139,7 +139,7 @@ add_test(function test_subscribeWithKey_conflict() { } ); }, - error => { + () => { ok(false, "Error generating different app server key"); done(); } diff --git a/dom/push/test/xpcshell/test_unregister_empty_scope.js b/dom/push/test/xpcshell/test_unregister_empty_scope.js index b5b6109ddb..6663f7c6ad 100644 --- a/dom/push/test/xpcshell/test_unregister_empty_scope.js +++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js @@ -16,7 +16,7 @@ add_task(async function test_unregister_empty_scope() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_unregister_error.js b/dom/push/test/xpcshell/test_unregister_error.js index bc56dc49b4..4273379eda 100644 --- a/dom/push/test/xpcshell/test_unregister_error.js +++ b/dom/push/test/xpcshell/test_unregister_error.js @@ -32,7 +32,7 @@ add_task(async function test_unregister_error() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_unregister_invalid_json.js b/dom/push/test/xpcshell/test_unregister_invalid_json.js index fa709c8eae..7cb7d0bada 100644 --- a/dom/push/test/xpcshell/test_unregister_invalid_json.js +++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js @@ -51,7 +51,7 @@ add_task(async function test_unregister_invalid_json() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", @@ -61,7 +61,7 @@ add_task(async function test_unregister_invalid_json() { }) ); }, - onUnregister(request) { + onUnregister() { this.serverSendMsg(");alert(1);("); unregisterDone(); }, diff --git a/dom/push/test/xpcshell/test_unregister_not_found.js b/dom/push/test/xpcshell/test_unregister_not_found.js index a7693f3cf5..8694add393 100644 --- a/dom/push/test/xpcshell/test_unregister_not_found.js +++ b/dom/push/test/xpcshell/test_unregister_not_found.js @@ -14,7 +14,7 @@ add_task(async function test_unregister_not_found() { serverURI: "wss://push.example.org/", makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/push/test/xpcshell/test_unregister_success.js b/dom/push/test/xpcshell/test_unregister_success.js index ccd0d31495..7064acd7d8 100644 --- a/dom/push/test/xpcshell/test_unregister_success.js +++ b/dom/push/test/xpcshell/test_unregister_success.js @@ -35,7 +35,7 @@ add_task(async function test_unregister_success() { db, makeWebSocket(uri) { return new MockWebSocket(uri, { - onHello(request) { + onHello() { this.serverSendMsg( JSON.stringify({ messageType: "hello", diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp index 3f8a1a316b..451d55a450 100644 --- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -82,6 +82,7 @@ #include "mozilla/Variant.h" #include "mozilla/dom/FileSystemQuotaClientFactory.h" #include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/dom/LocalStorageCommon.h" #include "mozilla/dom/StorageDBUpdater.h" #include "mozilla/dom/cache/QuotaClient.h" @@ -108,6 +109,7 @@ #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/ExtensionProtocolHandler.h" +#include "mozilla/StorageOriginAttributes.h" #include "nsAppDirectoryServiceDefs.h" #include "nsBaseHashtable.h" #include "nsCOMPtr.h" @@ -1377,6 +1379,9 @@ void InitializeQuotaManager() { RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler = net::ExtensionProtocolHandler::GetSingleton(); QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler)); + + IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate(); + QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr)); } QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize())); @@ -2267,7 +2272,7 @@ void QuotaManager::Shutdown() { quotaManager->mQuotaManagerShutdownSteps.get()); } - CrashReporter::AnnotateCrashReport( + CrashReporter::RecordAnnotationNSCString( CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation); MOZ_CRASH("Quota manager shutdown timed out"); @@ -2372,7 +2377,7 @@ void QuotaManager::Shutdown() { // Body of the function - ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContext, + ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContextTainted, "dom::quota::QuotaManager::Shutdown"_ns}; // This must be called before `flagShutdownStarted`, it would fail otherwise. @@ -2707,6 +2712,10 @@ nsresult QuotaManager::LoadQuota() { fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin; + const auto extraInfo = + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, + fullOriginMetadata.mStorageOrigin}; + fullOriginMetadata.mIsPrivate = false; QM_TRY_INSPECT(const auto& clientUsagesText, @@ -3203,7 +3212,7 @@ nsresult QuotaManager::CreateDirectoryMetadata( const OriginMetadata& aOriginMetadata) { AssertIsOnIOThread(); - OriginAttributes groupAttributes; + StorageOriginAttributes groupAttributes; nsCString groupNoSuffix; QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup, @@ -3211,11 +3220,11 @@ nsresult QuotaManager::CreateDirectoryMetadata( NS_ERROR_FAILURE); nsCString groupPrefix; - GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix); + GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix); nsCString group = groupPrefix + groupNoSuffix; - OriginAttributes originAttributes; + StorageOriginAttributes originAttributes; nsCString originNoSuffix; QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin, @@ -3223,7 +3232,7 @@ nsresult QuotaManager::CreateDirectoryMetadata( NS_ERROR_FAILURE); nsCString originPrefix; - GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix); + GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix); nsCString origin = originPrefix + originNoSuffix; @@ -3588,6 +3597,10 @@ nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType, MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType); + const auto extraInfo = ScopedLogExtraInfo{ + ScopedLogExtraInfo::kTagStorageOriginTainted, + metadata.mStorageOrigin}; + // FIXME(tt): The check for origin name consistency can // be removed once we have an upgrade to traverse origin // directories and check through the directory metadata @@ -3679,6 +3692,10 @@ nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType, QM_TRY(([&]() -> Result<Ok, nsresult> { QM_TRY(([&directory, &info, this, aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> { + const auto extraInfo = ScopedLogExtraInfo{ + ScopedLogExtraInfo::kTagStorageOriginTainted, + info.mFullOriginMetadata.mStorageOrigin}; + const auto originDirName = MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin); @@ -3728,6 +3745,10 @@ nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType, nsIFile* aDirectory) { AssertIsOnIOThread(); + // The ScopedLogExtraInfo is not set here on purpose, so the callers can + // decide if they want to set it. The extra info can be set sooner this way + // as well. + const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT; // We need to initialize directories of all clients if they exists and also @@ -5211,6 +5232,10 @@ QuotaManager::EnsurePersistentOriginIsInitialized( const auto innerFunc = [&aOriginMetadata, this](const auto& firstInitializationAttempt) -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> { + const auto extraInfo = + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, + aOriginMetadata.mStorageOrigin}; + QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata)); if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) { @@ -7612,7 +7637,7 @@ Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData( MOZ_ASSERT(originalSuffix[0] == '^'); if (!URLParams::Parse( - Substring(originalSuffix, 1, originalSuffix.Length() - 1), + Substring(originalSuffix, 1, originalSuffix.Length() - 1), true, [](const nsAString& aName, const nsAString& aValue) { if (aName.EqualsLiteral("appId")) { return false; diff --git a/dom/quota/CachingDatabaseConnection.cpp b/dom/quota/CachingDatabaseConnection.cpp index 504c0b5ded..dfdf0f71da 100644 --- a/dom/quota/CachingDatabaseConnection.cpp +++ b/dom/quota/CachingDatabaseConnection.cpp @@ -14,16 +14,16 @@ namespace mozilla::dom::quota { CachingDatabaseConnection::CachingDatabaseConnection( MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection) : -#ifdef CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP - mOwningThread{nsAutoOwningThread{}}, +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + mOwningEventTarget{nsAutoOwningEventTarget{}}, #endif mStorageConnection(std::move(aStorageConnection)) { } void CachingDatabaseConnection::LazyInit( MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection) { -#ifdef CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP - mOwningThread.init(); +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + mOwningEventTarget.init(); #endif mStorageConnection.init(std::move(aStorageConnection)); } @@ -40,8 +40,8 @@ CachingDatabaseConnection::GetCachedStatement(const nsACString& aQuery) { auto stmt, mCachedStatements.TryLookupOrInsertWith( aQuery, [&]() -> Result<nsCOMPtr<mozIStorageStatement>, nsresult> { - const auto extraInfo = - ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, aQuery}; + const auto extraInfo = ScopedLogExtraInfo{ + ScopedLogExtraInfo::kTagQueryTainted, aQuery}; QM_TRY_RETURN( MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( diff --git a/dom/quota/CachingDatabaseConnection.h b/dom/quota/CachingDatabaseConnection.h index ea94035a34..c9d7dbdd6b 100644 --- a/dom/quota/CachingDatabaseConnection.h +++ b/dom/quota/CachingDatabaseConnection.h @@ -14,6 +14,7 @@ #include "nscore.h" #include "nsHashKeys.h" #include "nsInterfaceHashtable.h" +#include "nsISupportsImpl.h" #include "nsString.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" @@ -55,7 +56,7 @@ class CachingDatabaseConnection { BorrowedStatement(NotNull<mozIStorageStatement*> aStatement, const nsACString& aQuery) : mozStorageStatementScoper(aStatement), - mExtraInfo{ScopedLogExtraInfo::kTagQuery, aQuery} {} + mExtraInfo{ScopedLogExtraInfo::kTagQueryTainted, aQuery} {} ScopedLogExtraInfo mExtraInfo; #else @@ -67,8 +68,9 @@ class CachingDatabaseConnection { class LazyStatement; void AssertIsOnConnectionThread() const { -#ifdef CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP - mOwningThread->AssertOwnership("CachingDatabaseConnection not thread-safe"); +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + mOwningEventTarget->AssertOwnership( + "CachingDatabaseConnection not thread-safe"); #endif } @@ -125,8 +127,8 @@ class CachingDatabaseConnection { void Close(); private: -#ifdef CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP - LazyInitializedOnce<const nsAutoOwningThread> mOwningThread; +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + LazyInitializedOnce<const nsAutoOwningEventTarget> mOwningEventTarget; #endif LazyInitializedOnceEarlyDestructible< diff --git a/dom/quota/Config.h b/dom/quota/Config.h index 3ec16ab95d..10025883e1 100644 --- a/dom/quota/Config.h +++ b/dom/quota/Config.h @@ -33,14 +33,4 @@ # define QM_COLLECTING_OPERATION_TELEMETRY #endif -/** - * The thread ownership checks in CachingDatabaseConnection assumes that the - * object lives on a single thread, not any serial event target. - * Defining CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP restores the checks. - * See bug 1858989. - */ -#if 0 -# define CACHING_DB_CONNECTION_CHECK_THREAD_OWNERSHIP 1 -#endif - #endif // DOM_QUOTA_CONFIG_H_ diff --git a/dom/quota/OriginParser.cpp b/dom/quota/OriginParser.cpp index 0335b1e2c0..96e2da56fc 100644 --- a/dom/quota/OriginParser.cpp +++ b/dom/quota/OriginParser.cpp @@ -216,11 +216,7 @@ void OriginParser::HandleToken(const nsDependentCSubstring& aToken) { return; } - if (aToken.First() == 't') { - mInIsolatedMozBrowser = true; - } else if (aToken.First() == 'f') { - mInIsolatedMozBrowser = false; - } else { + if ((aToken.First() != 't') && (aToken.First() != 'f')) { QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!", nsCString(aToken).get()); diff --git a/dom/quota/OriginParser.h b/dom/quota/OriginParser.h index cc4d0d1a11..f4fecf1c0a 100644 --- a/dom/quota/OriginParser.h +++ b/dom/quota/OriginParser.h @@ -65,7 +65,6 @@ class MOZ_STACK_CLASS OriginParser final { SchemeType mSchemeType; State mState; - bool mInIsolatedMozBrowser; bool mUniversalFileOrigin; bool mMaybeDriveLetter; bool mError; @@ -80,7 +79,6 @@ class MOZ_STACK_CLASS OriginParser final { mTokenizer(aOrigin, '+'), mSchemeType(eNone), mState(eExpectingAppIdOrScheme), - mInIsolatedMozBrowser(false), mUniversalFileOrigin(false), mMaybeDriveLetter(false), mError(false), diff --git a/dom/quota/QuotaCommon.cpp b/dom/quota/QuotaCommon.cpp index e2df8a1082..71b6186d00 100644 --- a/dom/quota/QuotaCommon.cpp +++ b/dom/quota/QuotaCommon.cpp @@ -374,14 +374,15 @@ void LogError(const nsACString& aExpr, const Maybe<nsresult> aMaybeRv, return; } - nsAutoCString context; + const Tainted<nsCString>* contextTaintedPtr = nullptr; # ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - if (const auto contextIt = extraInfoMap.find(ScopedLogExtraInfo::kTagContext); + if (const auto contextIt = + extraInfoMap.find(ScopedLogExtraInfo::kTagContextTainted); contextIt != extraInfoMap.cend()) { - context = *contextIt->second; + contextTaintedPtr = contextIt->second; } # endif @@ -444,37 +445,46 @@ void LogError(const nsACString& aExpr, const Maybe<nsresult> aMaybeRv, } # endif - nsAutoCString extraInfosString; + auto extraInfosStringTainted = Tainted<nsAutoCString>([&] { + nsAutoCString extraInfosString; - if (!rvCode.IsEmpty()) { - extraInfosString.Append(" failed with resultCode "_ns + rvCode); - } + if (!rvCode.IsEmpty()) { + extraInfosString.Append(" failed with resultCode "_ns + rvCode); + } - if (!rvName.IsEmpty()) { - extraInfosString.Append(", resultName "_ns + rvName); - } + if (!rvName.IsEmpty()) { + extraInfosString.Append(", resultName "_ns + rvName); + } # ifdef QM_ERROR_STACKS_ENABLED - if (!frameIdString.IsEmpty()) { - extraInfosString.Append(", frameId "_ns + frameIdString); - } + if (!frameIdString.IsEmpty()) { + extraInfosString.Append(", frameId "_ns + frameIdString); + } - if (!stackIdString.IsEmpty()) { - extraInfosString.Append(", stackId "_ns + stackIdString); - } + if (!stackIdString.IsEmpty()) { + extraInfosString.Append(", stackId "_ns + stackIdString); + } - if (!processIdString.IsEmpty()) { - extraInfosString.Append(", processId "_ns + processIdString); - } + if (!processIdString.IsEmpty()) { + extraInfosString.Append(", processId "_ns + processIdString); + } # endif # ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED - for (const auto& item : extraInfoMap) { - extraInfosString.Append(", "_ns + nsDependentCString(item.first) + " "_ns + - *item.second); - } + for (const auto& item : extraInfoMap) { + const auto& valueTainted = *item.second; + + extraInfosString.Append( + ", "_ns + nsDependentCString(item.first) + " "_ns + + MOZ_NO_VALIDATE(valueTainted, + "It's okay to append any `extraInfoMap` value to " + "`extraInfosString`.")); + } # endif + return extraInfosString; + }()); + const auto sourceFileRelativePath = detail::MakeSourceFileRelativePath(aSourceFilePath); @@ -482,9 +492,14 @@ void LogError(const nsACString& aExpr, const Maybe<nsresult> aMaybeRv, NS_DebugBreak( NS_DEBUG_WARNING, nsAutoCString("QM_TRY failure ("_ns + severityString + ")"_ns).get(), - (extraInfosString.IsEmpty() ? nsPromiseFlatCString(aExpr) - : static_cast<const nsCString&>(nsAutoCString( - aExpr + extraInfosString))) + (MOZ_NO_VALIDATE(extraInfosStringTainted, + "It's okay to check if `extraInfosString` is empty.") + .IsEmpty() + ? nsPromiseFlatCString(aExpr) + : static_cast<const nsCString&>(nsAutoCString( + aExpr + MOZ_NO_VALIDATE(extraInfosStringTainted, + "It's okay to log `extraInfosString` " + "to stdout/console.")))) .get(), nsPromiseFlatCString(sourceFileRelativePath).get(), aSourceFileLine); # endif @@ -496,13 +511,16 @@ void LogError(const nsACString& aExpr, const Maybe<nsresult> aMaybeRv, // reporting (instead of the browsing console). // Another option is to keep the current check and rely on MOZ_LOG reporting // in future once that's available. - if (!context.IsEmpty()) { + if (contextTaintedPtr) { nsCOMPtr<nsIConsoleService> console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (console) { NS_ConvertUTF8toUTF16 message( "QM_TRY failure ("_ns + severityString + ")"_ns + ": '"_ns + aExpr + - extraInfosString + "', file "_ns + sourceFileRelativePath + ":"_ns + + MOZ_NO_VALIDATE( + extraInfosStringTainted, + "It's okay to log `extraInfosString` to the browser console.") + + "', file "_ns + sourceFileRelativePath + ":"_ns + IntToCString(aSourceFileLine)); // The concatenation above results in a message like: @@ -517,14 +535,34 @@ void LogError(const nsACString& aExpr, const Maybe<nsresult> aMaybeRv, # endif # ifdef QM_LOG_ERROR_TO_TELEMETRY_ENABLED - if (!context.IsEmpty()) { + // The context tag is special because it's used to enable logging to + // telemetry (besides carrying information). Other tags (like query) don't + // enable logging to telemetry. + + if (contextTaintedPtr) { + const auto& contextTainted = *contextTaintedPtr; + + // Do NOT CHANGE this if you don't know what you're doing. + + // `extraInfoString` is not included in the telemetry event on purpose + // since it can contain sensitive information. + // For now, we don't include aExpr in the telemetry event. It might help to // match locations across versions, but they might be large. + + // New extra entries (with potentially sensitive content) can't be easily + // (accidentally) added because they would have to be added to Events.yaml + // under "dom.quota.try" which would require a data review. + auto extra = Some([&] { auto res = CopyableTArray<EventExtraEntry>{}; res.SetCapacity(9); - res.AppendElement(EventExtraEntry{"context"_ns, nsCString{context}}); + res.AppendElement(EventExtraEntry{ + "context"_ns, + MOZ_NO_VALIDATE( + contextTainted, + "Context has been data-reviewed for telemetry transmission.")}); # ifdef QM_ERROR_STACKS_ENABLED if (!frameIdString.IsEmpty()) { diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h index b672150b96..74855dd16b 100644 --- a/dom/quota/QuotaCommon.h +++ b/dom/quota/QuotaCommon.h @@ -1712,8 +1712,8 @@ auto ExecuteInitialization( const auto maybeScopedLogExtraInfo = firstInitializationAttempt.Recorded() ? Nothing{} - : Some(ScopedLogExtraInfo{ScopedLogExtraInfo::kTagContext, - aContext}); + : Some(ScopedLogExtraInfo{ + ScopedLogExtraInfo::kTagContextTainted, aContext}); #endif return std::forward<Func>(aFunc)(firstInitializationAttempt); diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h index 42e5de961a..354977166a 100644 --- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -727,7 +727,8 @@ class QuotaManager final : public BackgroundThreadObject { nsCOMPtr<mozIStorageConnection> mStorageConnection; - EnumeratedArray<Client::Type, Client::TYPE_MAX, nsCString> mShutdownSteps; + EnumeratedArray<Client::Type, nsCString, size_t(Client::TYPE_MAX)> + mShutdownSteps; LazyInitializedOnce<const TimeStamp> mShutdownStartedAt; // Accesses to mQuotaManagerShutdownSteps must be protected by mQuotaMutex. diff --git a/dom/quota/ScopedLogExtraInfo.cpp b/dom/quota/ScopedLogExtraInfo.cpp index e9ddf84a8d..1c38c58f88 100644 --- a/dom/quota/ScopedLogExtraInfo.cpp +++ b/dom/quota/ScopedLogExtraInfo.cpp @@ -9,19 +9,27 @@ namespace mozilla::dom::quota { #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED -MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sQueryValue; -MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sContextValue; +MOZ_THREAD_LOCAL(const Tainted<nsCString>*) +ScopedLogExtraInfo::sQueryValueTainted; +MOZ_THREAD_LOCAL(const Tainted<nsCString>*) +ScopedLogExtraInfo::sContextValueTainted; +MOZ_THREAD_LOCAL(const Tainted<nsCString>*) +ScopedLogExtraInfo::sStorageOriginValueTainted; /* static */ auto ScopedLogExtraInfo::FindSlot(const char* aTag) { // XXX For now, don't use a real map but just allow the known tag values. - if (aTag == kTagQuery) { - return &sQueryValue; + if (aTag == kTagQueryTainted) { + return &sQueryValueTainted; } - if (aTag == kTagContext) { - return &sContextValue; + if (aTag == kTagContextTainted) { + return &sContextValueTainted; + } + + if (aTag == kTagStorageOriginTainted) { + return &sStorageOriginValueTainted; } MOZ_CRASH("Unknown tag!"); @@ -51,20 +59,25 @@ ScopedLogExtraInfo::GetExtraInfoMap() { // the caller(s). ScopedLogExtraInfoMap map; - if (sQueryValue.get()) { - map.emplace(kTagQuery, sQueryValue.get()); + if (sQueryValueTainted.get()) { + map.emplace(kTagQueryTainted, sQueryValueTainted.get()); + } + + if (sContextValueTainted.get()) { + map.emplace(kTagContextTainted, sContextValueTainted.get()); } - if (sContextValue.get()) { - map.emplace(kTagContext, sContextValue.get()); + if (sStorageOriginValueTainted.get()) { + map.emplace(kTagStorageOriginTainted, sStorageOriginValueTainted.get()); } return map; } /* static */ void ScopedLogExtraInfo::Initialize() { - MOZ_ALWAYS_TRUE(sQueryValue.init()); - MOZ_ALWAYS_TRUE(sContextValue.init()); + MOZ_ALWAYS_TRUE(sQueryValueTainted.init()); + MOZ_ALWAYS_TRUE(sContextValueTainted.init()); + MOZ_ALWAYS_TRUE(sStorageOriginValueTainted.init()); } void ScopedLogExtraInfo::AddInfo() { diff --git a/dom/quota/ScopedLogExtraInfo.h b/dom/quota/ScopedLogExtraInfo.h index 54b8bccb3f..5dc881a3b1 100644 --- a/dom/quota/ScopedLogExtraInfo.h +++ b/dom/quota/ScopedLogExtraInfo.h @@ -12,6 +12,7 @@ #include <map> #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" +#include "mozilla/Tainting.h" #include "mozilla/ThreadLocal.h" #include "nsString.h" #include "nsXULAppAPI.h" @@ -19,8 +20,12 @@ namespace mozilla::dom::quota { struct MOZ_STACK_CLASS ScopedLogExtraInfo { - static constexpr const char kTagQuery[] = "query"; - static constexpr const char kTagContext[] = "context"; + static constexpr const char kTagQueryTainted[] = "query"; + static constexpr const char kTagContextTainted[] = "context"; + // Using the storage origin (instead of normal origin) on purpose to store + // the masked origin (uuid based) on the stack for origins partitioned for + // private browsing. + static constexpr const char kTagStorageOriginTainted[] = "storage-origin"; #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED private: @@ -41,18 +46,20 @@ struct MOZ_STACK_CLASS ScopedLogExtraInfo { ScopedLogExtraInfo(const ScopedLogExtraInfo&) = delete; ScopedLogExtraInfo& operator=(const ScopedLogExtraInfo&) = delete; - using ScopedLogExtraInfoMap = std::map<const char*, const nsACString*>; + using ScopedLogExtraInfoMap = + std::map<const char*, const Tainted<nsCString>*>; static ScopedLogExtraInfoMap GetExtraInfoMap(); static void Initialize(); private: const char* mTag; - const nsACString* mPreviousValue; - nsCString mCurrentValue; + const Tainted<nsCString>* mPreviousValue; + Tainted<nsCString> mCurrentValue; - static MOZ_THREAD_LOCAL(const nsACString*) sQueryValue; - static MOZ_THREAD_LOCAL(const nsACString*) sContextValue; + static MOZ_THREAD_LOCAL(const Tainted<nsCString>*) sQueryValueTainted; + static MOZ_THREAD_LOCAL(const Tainted<nsCString>*) sContextValueTainted; + static MOZ_THREAD_LOCAL(const Tainted<nsCString>*) sStorageOriginValueTainted; void AddInfo(); #else diff --git a/dom/quota/SerializationHelpers.h b/dom/quota/SerializationHelpers.h index 50c6c6a5e6..7eb4773964 100644 --- a/dom/quota/SerializationHelpers.h +++ b/dom/quota/SerializationHelpers.h @@ -64,7 +64,6 @@ struct ParamTraits<mozilla::OriginAttributesPattern> { static void Write(MessageWriter* aWriter, const paramType& aParam) { WriteParam(aWriter, aParam.mFirstPartyDomain); - WriteParam(aWriter, aParam.mInIsolatedMozBrowser); WriteParam(aWriter, aParam.mPrivateBrowsingId); WriteParam(aWriter, aParam.mUserContextId); WriteParam(aWriter, aParam.mGeckoViewSessionContextId); @@ -72,7 +71,6 @@ struct ParamTraits<mozilla::OriginAttributesPattern> { static bool Read(MessageReader* aReader, paramType* aResult) { return ReadParam(aReader, &aResult->mFirstPartyDomain) && - ReadParam(aReader, &aResult->mInIsolatedMozBrowser) && ReadParam(aReader, &aResult->mPrivateBrowsingId) && ReadParam(aReader, &aResult->mUserContextId) && ReadParam(aReader, &aResult->mGeckoViewSessionContextId); diff --git a/dom/quota/StorageOriginAttributes.cpp b/dom/quota/StorageOriginAttributes.cpp index 7d8fd9c802..bcdf47bce8 100644 --- a/dom/quota/StorageOriginAttributes.cpp +++ b/dom/quota/StorageOriginAttributes.cpp @@ -6,10 +6,58 @@ #include "StorageOriginAttributes.h" +#include "nsString.h" #include "nsURLHelper.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/quota/QuotaManager.h" namespace mozilla { +void StorageOriginAttributes::CreateSuffix(nsACString& aStr) const { + nsCString str1; + + URLParams params; + nsAutoString value; + + if (mInIsolatedMozBrowser) { + params.Set(u"inBrowser"_ns, u"1"_ns); + } + + str1.Truncate(); + + params.Serialize(value, true); + if (!value.IsEmpty()) { + str1.AppendLiteral("^"); + str1.Append(NS_ConvertUTF16toUTF8(value)); + } + + // Make sure that the string don't contain characters that would get replaced + // with the plus character by quota manager, potentially causing ambiguity. + MOZ_ASSERT(str1.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == + kNotFound); + + // Let OriginAttributes::CreateSuffix serialize other origin attributes. + nsCString str2; + mOriginAttributes.CreateSuffix(str2); + + aStr.Truncate(); + + if (str1.IsEmpty()) { + aStr.Append(str2); + return; + } + + if (str2.IsEmpty()) { + aStr.Append(str1); + return; + } + + // If both strings are not empty, we need to combine them. + aStr.Append(str1); + aStr.Append('&'); + aStr.Append(Substring(str2, 1, str2.Length() - 1)); +} + bool StorageOriginAttributes::PopulateFromSuffix(const nsACString& aStr) { if (aStr.IsEmpty()) { return true; @@ -20,7 +68,7 @@ bool StorageOriginAttributes::PopulateFromSuffix(const nsACString& aStr) { } bool ok = - URLParams::Parse(Substring(aStr, 1, aStr.Length() - 1), + URLParams::Parse(Substring(aStr, 1, aStr.Length() - 1), true, [this](const nsAString& aName, const nsAString& aValue) { if (aName.EqualsLiteral("inBrowser")) { if (!aValue.EqualsLiteral("1")) { diff --git a/dom/quota/StorageOriginAttributes.h b/dom/quota/StorageOriginAttributes.h index 5d8f416b38..b68fdc804b 100644 --- a/dom/quota/StorageOriginAttributes.h +++ b/dom/quota/StorageOriginAttributes.h @@ -15,12 +15,32 @@ namespace mozilla { // in OriginAttributes class anymore. class StorageOriginAttributes { public: + StorageOriginAttributes() : mInIsolatedMozBrowser(false) {} + + explicit StorageOriginAttributes(bool aInIsolatedMozBrowser) + : mInIsolatedMozBrowser(aInIsolatedMozBrowser) {} + bool InIsolatedMozBrowser() const { return mInIsolatedMozBrowser; } uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; } // New getters can be added here incrementally. + void SetInIsolatedMozBrowser(bool aInIsolatedMozBrowser) { + mInIsolatedMozBrowser = aInIsolatedMozBrowser; + } + + void SetUserContextId(uint32_t aUserContextId) { + mOriginAttributes.mUserContextId = aUserContextId; + } + + // New setters can be added here incrementally. + + // Serializes/Deserializes non-default values into the suffix format, i.e. + // |^key1=value1&key2=value2|. If there are no non-default attributes, this + // returns an empty string + void CreateSuffix(nsACString& aStr) const; + [[nodiscard]] bool PopulateFromSuffix(const nsACString& aStr); // Populates the attributes from a string like @@ -31,7 +51,7 @@ class StorageOriginAttributes { private: OriginAttributes mOriginAttributes; - bool mInIsolatedMozBrowser = false; + bool mInIsolatedMozBrowser; }; } // namespace mozilla diff --git a/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp index 00a3393844..395246c1fb 100644 --- a/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp +++ b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp @@ -16,19 +16,23 @@ TEST(DOM_Quota_ScopedLogExtraInfo, AddAndRemove) { const auto extraInfo = - ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text}; + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQueryTainted, text}; #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); + const auto& queryValueTainted = + *extraInfoMap.at(ScopedLogExtraInfo::kTagQueryTainted); + + EXPECT_EQ(text, MOZ_NO_VALIDATE(queryValueTainted, + "It's ok to use query value in tests.")); #endif } #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery)); + EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQueryTainted)); #endif } @@ -39,27 +43,38 @@ TEST(DOM_Quota_ScopedLogExtraInfo, Nested) { const auto extraInfo = - ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text}; + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQueryTainted, text}; { const auto extraInfo = - ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, nestedText}; + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQueryTainted, nestedText}; #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - EXPECT_EQ(nestedText, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); + + const auto& queryValueTainted = + *extraInfoMap.at(ScopedLogExtraInfo::kTagQueryTainted); + + EXPECT_EQ(nestedText, + MOZ_NO_VALIDATE(queryValueTainted, + "It's ok to use query value in tests.")); #endif } #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); + + const auto& queryValueTainted = + *extraInfoMap.at(ScopedLogExtraInfo::kTagQueryTainted); + + EXPECT_EQ(text, MOZ_NO_VALIDATE(queryValueTainted, + "It's ok to use query value in tests.")); #endif } #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); - EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery)); + EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQueryTainted)); #endif } diff --git a/dom/quota/test/gtest/TestStorageOriginAttributes.cpp b/dom/quota/test/gtest/TestStorageOriginAttributes.cpp index 4529e1d53b..f7a8387305 100644 --- a/dom/quota/test/gtest/TestStorageOriginAttributes.cpp +++ b/dom/quota/test/gtest/TestStorageOriginAttributes.cpp @@ -9,6 +9,33 @@ namespace mozilla::dom::quota::test { +TEST(DOM_Quota_StorageOriginAttributes, Constructor_Default) +{ + { + StorageOriginAttributes originAttributes; + + ASSERT_FALSE(originAttributes.InIsolatedMozBrowser()); + ASSERT_EQ(originAttributes.UserContextId(), 0u); + } +} + +TEST(DOM_Quota_StorageOriginAttributes, Constructor_InIsolatedMozbrowser) +{ + { + StorageOriginAttributes originAttributes(/* aInIsolatedMozBrowser */ false); + + ASSERT_FALSE(originAttributes.InIsolatedMozBrowser()); + ASSERT_EQ(originAttributes.UserContextId(), 0u); + } + + { + StorageOriginAttributes originAttributes(/* aInIsolatedMozBrowser */ true); + + ASSERT_TRUE(originAttributes.InIsolatedMozBrowser()); + ASSERT_EQ(originAttributes.UserContextId(), 0u); + } +} + TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_NoOriginAttributes) { { @@ -162,4 +189,52 @@ TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_Mixed_Invalid) } } +TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_NoOriginAttributes) +{ + { + StorageOriginAttributes originAttributes; + nsCString suffix; + originAttributes.CreateSuffix(suffix); + + ASSERT_TRUE(suffix.IsEmpty()); + } +} + +TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_InIsolatedMozbrowser) +{ + { + StorageOriginAttributes originAttributes; + originAttributes.SetInIsolatedMozBrowser(true); + nsCString suffix; + originAttributes.CreateSuffix(suffix); + + ASSERT_TRUE(suffix.Equals("^inBrowser=1"_ns)); + } +} + +TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_UserContextId) +{ + { + StorageOriginAttributes originAttributes; + originAttributes.SetUserContextId(42); + nsCString suffix; + originAttributes.CreateSuffix(suffix); + + ASSERT_TRUE(suffix.Equals("^userContextId=42"_ns)); + } +} + +TEST(DOM_Quota_StorageOriginAttributes, CreateSuffix_Mixed) +{ + { + StorageOriginAttributes originAttributes; + originAttributes.SetInIsolatedMozBrowser(true); + originAttributes.SetUserContextId(42); + nsCString suffix; + originAttributes.CreateSuffix(suffix); + + ASSERT_TRUE(suffix.Equals("^inBrowser=1&userContextId=42"_ns)); + } +} + } // namespace mozilla::dom::quota::test diff --git a/dom/quota/test/modules/system/worker/ModuleLoader.js b/dom/quota/test/modules/system/worker/ModuleLoader.js index b79f3ff5b9..5354c26e34 100644 --- a/dom/quota/test/modules/system/worker/ModuleLoader.js +++ b/dom/quota/test/modules/system/worker/ModuleLoader.js @@ -3,7 +3,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -function ModuleLoader(base, depth, proto) { +function ModuleLoader(base, depth) { const modules = {}; const require = async function (id) { diff --git a/dom/quota/test/xpcshell/telemetry/test_dom_quota_try.js b/dom/quota/test/xpcshell/telemetry/test_dom_quota_try.js new file mode 100644 index 0000000000..28bb2d63b4 --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/test_dom_quota_try.js @@ -0,0 +1,180 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +// This is a list of all extra keys that are exposed to telemetry. Please only +// add things to this list with great care and proper code review from relevant +// module owner/peers and proper data review from data stewards. +const allowedExtraKeys = [ + "context", + "frame_id", + "process_id", + "result", + "seq", + "severity", + "source_file", + "source_line", + "stack_id", +]; + +const originSchemes = [ + "http", + "https", + "ftp", + "ws", + "wss", + "gopher", + "blob", + "file", + "data", +]; + +const testcases = [ + // Test temporary storage initialization with and without a broken origin + // directory. + { + async setup(expectedInitResult) { + Services.prefs.setBoolPref("dom.quotaManager.loadQuotaFromCache", false); + + let request = init(); + await requestFinished(request); + + request = initTemporaryStorage(); + await requestFinished(request); + + request = initTemporaryOrigin( + "default", + getPrincipal("https://example.com") + ); + await requestFinished(request); + + const usageFile = getRelativeFile( + "storage/default/https+++example.com/ls/usage" + ); + + if (!expectedInitResult) { + usageFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } else { + usageFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + } + + // XXX It would be nice to have a method which shuts down temporary + // storage only. Now we have to shut down entire storage (including + // temporary storage) and then initialize storage again. + request = reset(); + await requestFinished(request); + + request = init(); + await requestFinished(request); + }, + initFunction: initTemporaryStorage, + getExpectedNumberOfEvents() { + if (AppConstants.EARLY_BETA_OR_EARLIER || AppConstants.DEBUG) { + if (AppConstants.NIGHTLY_BUILD) { + return { + initFailure: 9, + initSuccess: 0, + }; + } + + return { + initFailure: 14, + initSuccess: 0, + }; + } + + return { + initFailure: 0, + initSuccess: 0, + }; + }, + async cleanup() { + const request = clear(); + await requestFinished(request); + + Services.prefs.setBoolPref("dom.quotaManager.loadQuotaFromCache", true); + }, + }, +]; + +function verifyEvents(expectedNumberOfEvents) { + const events = TelemetryTestUtils.getEvents({ + category: "dom.quota.try", + method: "error", + }); + + is( + events.length, + expectedNumberOfEvents, + "The number of events must match the expected number of events" + ); + + for (const event of events) { + for (const extraKey in event.extra) { + ok( + allowedExtraKeys.includes(extraKey), + `The extra key ${extraKey} must be in the allow list` + ); + + const extraValue = event.extra[extraKey]; + + // These are extra paranoia checks to ensure extra values don't contain + // origin like strings. + for (const suffix of ["://", "+++"]) { + ok( + originSchemes.every( + originScheme => !extraValue.includes(originScheme + suffix) + ), + `The extra value ${extraValue} must not contain origin like strings` + ); + } + } + } +} + +async function testSteps() { + for (const testcase of testcases) { + for (const expectedInitResult of [false, true]) { + // Clear all events. + Services.telemetry.clearEvents(); + + info( + `Verifying the events when the initialization ` + + `${expectedInitResult ? "succeeds" : "fails"}` + ); + + await testcase.setup(expectedInitResult); + + const msg = `Should ${expectedInitResult ? "not " : ""} have thrown`; + + let request = testcase.initFunction(); + try { + await requestFinished(request); + ok(expectedInitResult, msg); + } catch (ex) { + ok(!expectedInitResult, msg); + } + + const expectedNumberOfEventsObject = testcase.getExpectedNumberOfEvents + ? testcase.getExpectedNumberOfEvents() + : testcase.expectedNumberOfEvents; + + const expectedNumberOfEvents = expectedInitResult + ? expectedNumberOfEventsObject.initSuccess + : expectedNumberOfEventsObject.initFailure; + + verifyEvents(expectedNumberOfEvents); + + await testcase.cleanup(); + } + } +} diff --git a/dom/quota/test/xpcshell/telemetry/xpcshell.toml b/dom/quota/test/xpcshell/telemetry/xpcshell.toml index 949ef3cb06..36a176db2a 100644 --- a/dom/quota/test/xpcshell/telemetry/xpcshell.toml +++ b/dom/quota/test/xpcshell/telemetry/xpcshell.toml @@ -13,5 +13,8 @@ support-files = [ "version2_2_profile.zip", ] +["test_dom_quota_try.js"] +skip-if = ["os == 'android' || appname == 'thunderbird'"] + ["test_qm_first_initialization_attempt.js"] skip-if = ["appname == 'thunderbird'"] diff --git a/dom/reporting/tests/browser_cleanup.js b/dom/reporting/tests/browser_cleanup.js index e50b8db1da..06e7893fd0 100644 --- a/dom/reporting/tests/browser_cleanup.js +++ b/dom/reporting/tests/browser_cleanup.js @@ -57,7 +57,7 @@ add_task(async function () { ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data"); await new Promise(resolve => { - Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () => resolve() ); }); @@ -89,7 +89,7 @@ add_task(async function () { ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data"); await new Promise(resolve => { - Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_REPORTS, value => + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_REPORTS, () => resolve() ); }); @@ -125,7 +125,7 @@ add_task(async function () { TEST_HOST, true, Ci.nsIClearDataService.CLEAR_REPORTS, - value => resolve() + () => resolve() ); }); @@ -269,7 +269,7 @@ add_task(async function () { add_task(async function () { info("Cleaning up."); await new Promise(resolve => { - Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () => resolve() ); }); diff --git a/dom/reporting/tests/common_deprecated.js b/dom/reporting/tests/common_deprecated.js index 8f8415fcf3..f3ff1028be 100644 --- a/dom/reporting/tests/common_deprecated.js +++ b/dom/reporting/tests/common_deprecated.js @@ -1,5 +1,7 @@ let testingInterface; +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // eslint-disable-next-line no-unused-vars function test_deprecatedInterface() { info("Testing DeprecatedTestingInterface report"); @@ -34,7 +36,7 @@ function test_deprecatedInterface() { .replace("worker_deprecated.js", "common_deprecated.js"), "We have a sourceFile" ); - is(report.body.lineNumber, 48, "We have a lineNumber"); + is(report.body.lineNumber, 50, "We have a lineNumber"); is(report.body.columnNumber, 24, "We have a columnNumber"); obs.disconnect(); @@ -86,7 +88,7 @@ function test_deprecatedMethod() { .replace("worker_deprecated.js", "common_deprecated.js"), "We have a sourceFile" ); - is(report.body.lineNumber, 100, "We have a lineNumber"); + is(report.body.lineNumber, 102, "We have a lineNumber"); is(report.body.columnNumber, 22, "We have a columnNumber"); obs.disconnect(); @@ -122,7 +124,7 @@ function test_deprecatedMethodWithDataURI() { }; </script>`; - return new Promise((resolve, reject) => { + return new Promise(resolve => { window.open(uri); window.addEventListener("message", e => { is(e.data, "passed", "The data URI is truncated"); @@ -167,7 +169,7 @@ function test_deprecatedAttribute() { .replace("worker_deprecated.js", "common_deprecated.js"), "We have a sourceFile" ); - is(report.body.lineNumber, 181, "We have a lineNumber"); + is(report.body.lineNumber, 183, "We have a lineNumber"); is(report.body.columnNumber, 8, "We have a columnNumber"); obs.disconnect(); diff --git a/dom/reporting/tests/iframe_delivering.html b/dom/reporting/tests/iframe_delivering.html index e8c5e9e3a4..0eafd3ca8a 100644 --- a/dom/reporting/tests/iframe_delivering.html +++ b/dom/reporting/tests/iframe_delivering.html @@ -78,7 +78,7 @@ fetch("delivering.sjs?task=header") .then(_ => { return new Promise(resolve => { let w = new Worker("worker_delivering.js"); - w.onmessage = e => resolve(); + w.onmessage = () => resolve(); }); }) .then(_ => { diff --git a/dom/reporting/tests/worker_delivering.js b/dom/reporting/tests/worker_delivering.js index 539bcd231c..708a7c0e28 100644 --- a/dom/reporting/tests/worker_delivering.js +++ b/dom/reporting/tests/worker_delivering.js @@ -1,5 +1,5 @@ fetch("delivering.sjs?task=header&worker=true") .then(r => r.text()) - .then(text => { + .then(() => { postMessage("All good!"); }); diff --git a/dom/reporting/tests/worker_deprecated.js b/dom/reporting/tests/worker_deprecated.js index f6b57896f6..934efaa57a 100644 --- a/dom/reporting/tests/worker_deprecated.js +++ b/dom/reporting/tests/worker_deprecated.js @@ -1,4 +1,5 @@ /* eslint-disable no-undef */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ // eslint-disable-next-line no-unused-vars function ok(a, msg) { diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 43f718ab64..4fa08e074d 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -12,21 +12,16 @@ #include "mozilla/Assertions.h" #include "mozilla/dom/FetchPriority.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/dom/RequestBinding.h" -#include "nsIChildChannel.h" -#include "zlib.h" #include "prsystem.h" -#include "jsapi.h" -#include "jsfriendapi.h" -#include "js/Array.h" // JS::GetArrayLength #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::OwningDecodeOptions, JS::DelazificationOption #include "js/ContextOptions.h" // JS::ContextOptionsRef #include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate -#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/loader/ScriptLoadRequest.h" #include "ScriptCompression.h" #include "js/loader/LoadedScript.h" @@ -34,8 +29,6 @@ #include "js/MemoryFunctions.h" #include "js/Modules.h" #include "js/PropertyAndElement.h" // JS_DefineProperty -#include "js/Realm.h" -#include "js/SourceText.h" #include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult #include "js/Utility.h" #include "xpcpublic.h" @@ -54,7 +47,6 @@ #include "mozilla/dom/WindowContext.h" #include "mozilla/Mutex.h" // mozilla::Mutex #include "mozilla/net/UrlClassifierFeatureFactory.h" -#include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_javascript.h" #include "mozilla/StaticPrefs_network.h" @@ -80,16 +72,12 @@ #include "nsUnicharUtils.h" #include "nsError.h" #include "nsThreadUtils.h" -#include "nsDocShellCID.h" #include "nsIContentSecurityPolicy.h" #include "mozilla/Logging.h" #include "nsCRT.h" #include "nsContentCreatorFunctions.h" #include "nsProxyRelease.h" -#include "nsSandboxFlags.h" -#include "nsContentTypeParser.h" #include "nsINetworkPredictor.h" -#include "nsMimeTypes.h" #include "mozilla/ConsoleReportCollector.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/EventQueue.h" @@ -103,14 +91,12 @@ #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" -#include "mozilla/Unused.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "nsIScriptError.h" #include "nsIAsyncOutputStream.h" #include "js/loader/ModuleLoaderBase.h" #include "mozilla/Maybe.h" -using JS::SourceText; using namespace JS::loader; using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT; @@ -668,53 +654,35 @@ static void AdjustPriorityAndClassOfServiceForLinkPreloadScripts( nsIChannel* aChannel, ScriptLoadRequest* aRequest) { MOZ_ASSERT(aRequest->GetScriptLoadContext()->IsLinkPreloadScript()); + // Put it to the group that is not blocked by leaders and doesn't block + // follower at the same time. + // Giving it a much higher priority will make this request be processed + // ahead of other Unblocked requests, but with the same weight as + // Leaders. This will make us behave similar way for both http2 and http1. + ScriptLoadContext::PrioritizeAsPreload(aChannel); + if (!StaticPrefs::network_fetchpriority_enabled()) { - // Put it to the group that is not blocked by leaders and doesn't block - // follower at the same time. - // Giving it a much higher priority will make this request be processed - // ahead of other Unblocked requests, but with the same weight as - // Leaders. This will make us behave similar way for both http2 and http1. - ScriptLoadContext::PrioritizeAsPreload(aChannel); return; } - if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) { - cos->AddClassFlags(nsIClassOfService::Unblocked); - } - if (nsCOMPtr<nsISupportsPriority> supportsPriority = do_QueryInterface(aChannel)) { LOG(("Is <link rel=[module]preload")); - const RequestPriority fetchPriority = aRequest->FetchPriority(); + const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority()); // The spec defines the priority to be set in an implementation defined // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>). - // For web-compatibility, the fetch priority mapping from - // <https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority> - // is taken. - const int32_t supportsPriorityValue = [&]() { - switch (fetchPriority) { - case RequestPriority::Auto: { - return nsISupportsPriority::PRIORITY_HIGH; - } - case RequestPriority::High: { - return nsISupportsPriority::PRIORITY_HIGH; - } - case RequestPriority::Low: { - return nsISupportsPriority::PRIORITY_LOW; - } - default: { - MOZ_ASSERT_UNREACHABLE(); - return nsISupportsPriority::PRIORITY_NORMAL; - } - } - }(); - - LogPriorityMapping(ScriptLoader::gScriptLoaderLog, - ToFetchPriority(fetchPriority), supportsPriorityValue); - - supportsPriority->SetPriority(supportsPriorityValue); + // See corresponding preferences in StaticPrefList.yaml for more context. + const int32_t supportsPriorityDelta = + FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_script, fetchPriority); + supportsPriority->AdjustPriority(supportsPriorityDelta); +#ifdef DEBUG + int32_t adjustedPriority; + supportsPriority->GetPriority(&adjustedPriority); + LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority, + adjustedPriority); +#endif } } @@ -729,50 +697,40 @@ void AdjustPriorityForNonLinkPreloadScripts(nsIChannel* aChannel, if (nsCOMPtr<nsISupportsPriority> supportsPriority = do_QueryInterface(aChannel)) { LOG(("Is not <link rel=[module]preload")); - const RequestPriority fetchPriority = aRequest->FetchPriority(); + const auto fetchPriority = ToFetchPriority(aRequest->FetchPriority()); // The spec defines the priority to be set in an implementation defined // manner (<https://fetch.spec.whatwg.org/#concept-fetch>, step 15 and // <https://html.spec.whatwg.org/#concept-script-fetch-options-fetch-priority>). - // <testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests-data.js> - // provides more context for the priority mapping. - const int32_t supportsPriorityValue = [&]() { - switch (fetchPriority) { - case RequestPriority::Auto: { - if (aRequest->IsModuleRequest()) { - return nsISupportsPriority::PRIORITY_HIGH; - } - - const ScriptLoadContext* scriptLoadContext = - aRequest->GetScriptLoadContext(); - if (scriptLoadContext->IsAsyncScript() || - scriptLoadContext->IsDeferredScript()) { - return nsISupportsPriority::PRIORITY_LOW; - } - - if (scriptLoadContext->mScriptFromHead) { - return nsISupportsPriority::PRIORITY_HIGH; - } - - return nsISupportsPriority::PRIORITY_NORMAL; - } - case RequestPriority::Low: { - return nsISupportsPriority::PRIORITY_LOW; - } - case RequestPriority::High: { - return nsISupportsPriority::PRIORITY_HIGH; - } - default: { - MOZ_ASSERT_UNREACHABLE(); - return nsISupportsPriority::PRIORITY_NORMAL; - } + // See corresponding preferences in StaticPrefList.yaml for more context. + const int32_t supportsPriorityDelta = [&]() { + const ScriptLoadContext* scriptLoadContext = + aRequest->GetScriptLoadContext(); + if (aRequest->IsModuleRequest()) { + return FETCH_PRIORITY_ADJUSTMENT_FOR(module_script, fetchPriority); + } + + if (scriptLoadContext->IsAsyncScript() || + scriptLoadContext->IsDeferredScript()) { + return FETCH_PRIORITY_ADJUSTMENT_FOR(async_or_defer_script, + fetchPriority); + } + + if (scriptLoadContext->mScriptFromHead) { + return FETCH_PRIORITY_ADJUSTMENT_FOR(script_in_head, fetchPriority); } + + return FETCH_PRIORITY_ADJUSTMENT_FOR(other_script, fetchPriority); }(); - if (supportsPriorityValue) { - LogPriorityMapping(ScriptLoader::gScriptLoaderLog, - ToFetchPriority(fetchPriority), supportsPriorityValue); - supportsPriority->SetPriority(supportsPriorityValue); + if (supportsPriorityDelta) { + supportsPriority->AdjustPriority(supportsPriorityDelta); +#ifdef DEBUG + int32_t adjustedPriority; + supportsPriority->GetPriority(&adjustedPriority); + LogPriorityMapping(ScriptLoader::gScriptLoaderLog, fetchPriority, + adjustedPriority); +#endif } } } @@ -1933,14 +1891,13 @@ class ScriptOrModuleCompileTask final : public CompileOrDecodeTask { JS::SetNativeStackQuota(mFrontendContext, JS::ThreadStackQuotaForSize(stackSize)); - JS::CompilationStorage compileStorage; auto compile = [&](auto& source) { if constexpr (target == CompilationTarget::Script) { return JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, - source, compileStorage); + source); } return JS::CompileModuleScriptToStencil(mFrontendContext, mOptions, - source, compileStorage); + source); }; return mMaybeSource.mapNonEmpty(compile); } @@ -2004,7 +1961,6 @@ class ScriptDecodeTask final : public CompileOrDecodeTask { already_AddRefed<JS::Stencil> Decode() { // NOTE: JS::DecodeStencil doesn't need the stack quota. - JS::CompilationStorage compileStorage; RefPtr<JS::Stencil> stencil; mResult = JS::DecodeStencil(mFrontendContext, mDecodeOptions, mRange, getter_AddRefs(stencil)); diff --git a/dom/script/moz.build b/dom/script/moz.build index 17fa695342..2b4ab40c9a 100644 --- a/dom/script/moz.build +++ b/dom/script/moz.build @@ -7,14 +7,9 @@ with Files("**"): BUG_COMPONENT = ("Core", "DOM: Core & HTML") -XPIDL_SOURCES += [ - "nsIScriptLoaderObserver.idl", -] - -XPIDL_MODULE = "dom" - EXPORTS += [ "nsIScriptElement.h", + "nsIScriptLoaderObserver.h", ] EXPORTS.mozilla.dom += [ diff --git a/dom/script/nsIScriptLoaderObserver.h b/dom/script/nsIScriptLoaderObserver.h new file mode 100644 index 0000000000..449812c6e3 --- /dev/null +++ b/dom/script/nsIScriptLoaderObserver.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_DOM_SCRIPT_NSISCRIPTLOADEROBSERVER_H_ +#define MOZILLA_DOM_SCRIPT_NSISCRIPTLOADEROBSERVER_H_ + +#include "nsISupports.h" +#include "js/GCAnnotations.h" + +class nsIScriptElement; +class nsIURI; + +#define NS_ISCRIPTLOADEROBSERVER_IID \ + { \ + 0x7b787204, 0x76fb, 0x4764, { \ + 0x96, 0xf1, 0xfb, 0x7a, 0x66, 0x6d, 0xb4, 0xf4 \ + } \ + } + +class NS_NO_VTABLE nsIScriptLoaderObserver : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTLOADEROBSERVER_IID) + + /** + * The script is available for evaluation. For inline scripts, this + * method will be called synchronously. For externally loaded scripts, + * this method will be called when the load completes. + * + * @param aResult A result code representing the result of loading + * a script. If this is a failure code, script evaluation + * will not occur. + * @param aElement The element being processed. + * @param aIsInline Is this an inline classic script (as opposed to an + * externally loaded classic script or module script)? + * @param aURI What is the URI of the script (the document URI if + * it is inline). + * @param aLineNo At what line does the script appear (generally 1 + * if it is a loaded script). + */ + JS_HAZ_CAN_RUN_SCRIPT NS_IMETHOD ScriptAvailable(nsresult aResult, + nsIScriptElement* aElement, + bool aIsInlineClassicScript, + nsIURI* aURI, + uint32_t aLineNo) = 0; + + /** + * The script has been evaluated. + * + * @param aResult A result code representing the success or failure of + * the script evaluation. + * @param aElement The element being processed. + * @param aIsInline Is this an inline script or externally loaded? + */ + JS_HAZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT NS_IMETHOD ScriptEvaluated( + nsresult aResult, nsIScriptElement* aElement, bool aIsInline) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptLoaderObserver, + NS_ISCRIPTLOADEROBSERVER_IID) + +#define NS_DECL_NSISCRIPTLOADEROBSERVER \ + NS_IMETHOD ScriptAvailable(nsresult aResult, nsIScriptElement* aElement, \ + bool aIsInlineClassicScript, nsIURI* aURI, \ + uint32_t aLineNo) override; \ + MOZ_CAN_RUN_SCRIPT NS_IMETHOD ScriptEvaluated( \ + nsresult aResult, nsIScriptElement* aElement, bool aIsInline) override; + +#endif // MOZILLA_DOM_SCRIPT_NSISCRIPTLOADEROBSERVER_H_ diff --git a/dom/script/nsIScriptLoaderObserver.idl b/dom/script/nsIScriptLoaderObserver.idl deleted file mode 100644 index ceeb79cb39..0000000000 --- a/dom/script/nsIScriptLoaderObserver.idl +++ /dev/null @@ -1,48 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIScriptElement; -interface nsIURI; - -[scriptable, uuid(7b787204-76fb-4764-96f1-fb7a666db4f4)] -interface nsIScriptLoaderObserver : nsISupports { - - /** - * The script is available for evaluation. For inline scripts, this - * method will be called synchronously. For externally loaded scripts, - * this method will be called when the load completes. - * - * @param aResult A result code representing the result of loading - * a script. If this is a failure code, script evaluation - * will not occur. - * @param aElement The element being processed. - * @param aIsInline Is this an inline classic script (as opposed to an - * externally loaded classic script or module script)? - * @param aURI What is the URI of the script (the document URI if - * it is inline). - * @param aLineNo At what line does the script appear (generally 1 - * if it is a loaded script). - */ - void scriptAvailable(in nsresult aResult, - in nsIScriptElement aElement, - in boolean aIsInlineClassicScript, - in nsIURI aURI, - in uint32_t aLineNo); - - /** - * The script has been evaluated. - * - * @param aResult A result code representing the success or failure of - * the script evaluation. - * @param aElement The element being processed. - * @param aIsInline Is this an inline script or externally loaded? - */ - [can_run_script] - void scriptEvaluated(in nsresult aResult, - in nsIScriptElement aElement, - in boolean aIsInline); -}; diff --git a/dom/security/ReferrerInfo.cpp b/dom/security/ReferrerInfo.cpp index 70cdddb8ab..565d4d3284 100644 --- a/dom/security/ReferrerInfo.cpp +++ b/dom/security/ReferrerInfo.cpp @@ -131,16 +131,9 @@ ReferrerPolicy ReferrerPolicyFromToken(const nsAString& aContent, } } - // Supported tokes - ReferrerPolicyValues, are generated from - // ReferrerPolicy.webidl - for (uint8_t i = 0; ReferrerPolicyValues::strings[i].value; i++) { - if (lowerContent.EqualsASCII(ReferrerPolicyValues::strings[i].value)) { - return static_cast<enum ReferrerPolicy>(i); - } - } - - // Return no referrer policy (empty string) if none of the previous match - return ReferrerPolicy::_empty; + // Return no referrer policy (empty string) if it's not a valid enum value. + return StringToEnum<ReferrerPolicy>(lowerContent) + .valueOr(ReferrerPolicy::_empty); } // static @@ -187,18 +180,6 @@ ReferrerPolicy ReferrerInfo::ReferrerPolicyFromHeaderString( return referrerPolicy; } -// static -const char* ReferrerInfo::ReferrerPolicyToString(ReferrerPolicyEnum aPolicy) { - uint8_t index = static_cast<uint8_t>(aPolicy); - uint8_t referrerPolicyCount = ArrayLength(ReferrerPolicyValues::strings); - MOZ_ASSERT(index < referrerPolicyCount); - if (index >= referrerPolicyCount) { - return ""; - } - - return ReferrerPolicyValues::strings[index].value; -} - /* static */ uint32_t ReferrerInfo::GetUserReferrerSendingPolicy() { return clamped<uint32_t>( @@ -831,11 +812,8 @@ bool ReferrerInfo::ShouldIgnoreLessRestrictedPolicies( nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, true); - uint32_t idx = static_cast<uint32_t>(aPolicy); - AutoTArray<nsString, 2> params = { - NS_ConvertUTF8toUTF16( - nsDependentCString(ReferrerPolicyValues::strings[idx].value)), + NS_ConvertUTF8toUTF16(GetEnumString(aPolicy)), NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())}; LogMessageToConsole(aChannel, "ReferrerPolicyDisallowRelaxingMessage", params); @@ -1051,7 +1029,7 @@ ReferrerInfo::GetReferrerPolicy( NS_IMETHODIMP ReferrerInfo::GetReferrerPolicyString(nsACString& aResult) { - aResult.AssignASCII(ReferrerPolicyToString(mPolicy)); + aResult.AssignASCII(GetEnumString(mPolicy)); return NS_OK; } @@ -1719,7 +1697,9 @@ void ReferrerInfo::RecordTelemetry(nsIHttpChannel* aChannel) { // requests and the rest 9 buckets are for cross-site requests. uint32_t telemetryOffset = IsCrossSiteRequest(aChannel) - ? static_cast<uint32_t>(ReferrerPolicy::EndGuard_) + ? UnderlyingValue( + MaxContiguousEnumValue<dom::ReferrerPolicy>::value) + + 1 : 0; Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT, diff --git a/dom/security/ReferrerInfo.h b/dom/security/ReferrerInfo.h index 78440a5a70..b62afbb934 100644 --- a/dom/security/ReferrerInfo.h +++ b/dom/security/ReferrerInfo.h @@ -258,13 +258,6 @@ class ReferrerInfo : public nsIReferrerInfo { static ReferrerPolicyEnum ReferrerPolicyFromHeaderString( const nsAString& aContent); - /* - * Helper function to convert ReferrerPolicy enum to string - * - * @param aPolicy referrer policy to convert. - */ - static const char* ReferrerPolicyToString(ReferrerPolicyEnum aPolicy); - /** * Hash function for this object */ diff --git a/dom/security/featurepolicy/test/mochitest/mochitest.toml b/dom/security/featurepolicy/test/mochitest/mochitest.toml index c621331596..b61b067b97 100644 --- a/dom/security/featurepolicy/test/mochitest/mochitest.toml +++ b/dom/security/featurepolicy/test/mochitest/mochitest.toml @@ -10,5 +10,7 @@ support-files = [ ["test_featureList.html"] +["test_initial_aboutblank.html"] + ["test_parser.html"] fail-if = ["xorigin"] diff --git a/dom/security/featurepolicy/test/mochitest/test_initial_aboutblank.html b/dom/security/featurepolicy/test/mochitest/test_initial_aboutblank.html new file mode 100644 index 0000000000..79d1bfd4c4 --- /dev/null +++ b/dom/security/featurepolicy/test/mochitest/test_initial_aboutblank.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test feature policy - Initial about:blank</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe name="parentIframe" allow="fullscreen 'none'" srcdoc=" + <iframe name='regular'></iframe> + <iframe name='transient'></iframe> + <script>transient.stop()</script> +"></iframe> + +<script type="text/javascript"> +add_task(function testAboutBlank() { + isDeeply(frames[0].transient.document.featurePolicy.allowedFeatures(), + frames[0].document.featurePolicy.allowedFeatures(), + "check allowed feature list for initial about:blank"); + isDeeply(frames[0].regular.document.featurePolicy.allowedFeatures(), + frames[0].document.featurePolicy.allowedFeatures(), + "check allowed feature list for real load about:blank"); +}); +</script> +</body> +</html> diff --git a/dom/security/metrics.yaml b/dom/security/metrics.yaml new file mode 100644 index 0000000000..02084407b1 --- /dev/null +++ b/dom/security/metrics.yaml @@ -0,0 +1,153 @@ +# 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/. + +# Adding a new metric? We have docs for that! +# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 +$tags: + - 'Core :: DOM: Security' + +httpsfirst: + upgraded: + type: counter + description: > + Counts how often a load is marked to be upgraded to HTTPS because of + HTTPS-First (`dom.security.https_first` enabled). + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + upgraded_schemeless: + type: counter + description: > + Counts how often a load is marked to be upgraded to HTTPS because of + schemeless HTTPS-First (`dom.security.https_first` disabled, but load + marked as schemeless). + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded: + type: counter + description: > + How many regular HTTPS-First (`dom.security.https_first` enabled) + upgrades get downgraded again. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_schemeless: + type: counter + description: > + How many schemeless HTTPS-First (`dom.security.https_first` disabled, but + load marked as schemeless) upgrades get downgraded again. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_on_timer: + type: rate + description: > + How many HTTPS-First (`dom.security.https_first` enabled) upgrades get + downgraded again because the HTTP request fired after 3s received a answer + faster than the HTTPS request. + denominator_metric: httpsfirst.downgraded + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_on_timer_schemeless: + type: rate + description: > + How many of schemeless HTTPS-First (`dom.security.https_first` disabled, + but load marked as schemeless) upgrades get downgraded again because the + HTTP request fired after 3s received a answer faster than the HTTPS + request + denominator_metric: httpsfirst.downgraded_schemeless + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgrade_time: + type: timing_distribution + description: > + If a HTTPS-First (`dom.security.https_first` enabled) upgrade isn't + successful, measures the timespan between the navigation start and the + downgrade. This is essentially the overhead caused by HTTPS-First if a + site does not support HTTPS. + time_unit: millisecond + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgrade_time_schemeless: + type: timing_distribution + description: > + If a schemeless HTTPS-First (`dom.security.https_first` disabled, but + load marked as schemeless) upgrade isn't successful, measures the + timespan between the navigation start and the downgrade. This is + essentially the overhead caused by HTTPS-First if a site does not support + HTTPS. + time_unit: millisecond + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never diff --git a/dom/security/moz.build b/dom/security/moz.build index 0f2aed6a1f..f63fa22db6 100644 --- a/dom/security/moz.build +++ b/dom/security/moz.build @@ -9,7 +9,7 @@ with Files("*"): TEST_DIRS += ["test"] -DIRS += ["featurepolicy", "sanitizer"] +DIRS += ["featurepolicy", "sanitizer", "trusted-types"] EXPORTS.mozilla.dom += [ "CSPEvalChecker.h", diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index aafd2b64f2..ed44304484 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -189,10 +189,11 @@ bool nsCSPContext::permitsInternal( bool permits = true; nsAutoString violatedDirective; + nsAutoString violatedDirectiveString; for (uint32_t p = 0; p < mPolicies.Length(); p++) { if (!mPolicies[p]->permits(aDir, aLoadInfo, aContentLocation, !!aOriginalURIIfRedirect, aSpecific, - violatedDirective)) { + violatedDirective, violatedDirectiveString)) { // If the policy is violated and not report-only, reject the load and // report to the console if (!mPolicies[p]->getReportOnlyFlag()) { @@ -200,12 +201,6 @@ bool nsCSPContext::permitsInternal( permits = false; } - // In CSP 3.0 the effective directive doesn't become the actually used - // directive in case of a fallback from e.g. script-src-elem to - // script-src or default-src. - nsAutoString effectiveDirective; - effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDir)); - // Callers should set |aSendViolationReports| to false if this is a // preload - the decision may be wrong due to the inability to get the // nonce, and will incorrectly fail the unit tests. @@ -226,9 +221,11 @@ bool nsCSPContext::permitsInternal( BlockedContentSource::eUnknown, /* a BlockedContentSource */ aOriginalURIIfRedirect, /* in case of redirect originalURI is not null */ - violatedDirective, effectiveDirective, p, /* policy index */ - u""_ns, /* no observer subject */ - spec, /* source file */ + violatedDirective, violatedDirectiveString, + aDir, // aViolatedDirective + p, // policy index + u""_ns, // no observer subject + spec, // source file false, // aReportSample (no sample) u""_ns, /* no script sample */ lineNumber, /* line number */ @@ -519,7 +516,8 @@ void nsCSPContext::reportInlineViolation( CSPDirective aDirective, Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener, const nsAString& aNonce, bool aReportSample, const nsAString& aSample, - const nsAString& aViolatedDirective, const nsAString& aEffectiveDirective, + const nsAString& aViolatedDirective, + const nsAString& aViolatedDirectiveString, CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that uint32_t aLineNumber, uint32_t aColumnNumber) { nsString observerSubject; @@ -564,14 +562,15 @@ void nsCSPContext::reportInlineViolation( BlockedContentSource::eInline, // aBlockedSource mSelfURI, // aOriginalURI aViolatedDirective, // aViolatedDirective - aEffectiveDirective, // aEffectiveDirective - aViolatedPolicyIndex, // aViolatedPolicyIndex - observerSubject, // aObserverSubject - sourceFile, // aSourceFile - aReportSample, // aReportSample - aSample, // aScriptSample - lineNumber, // aLineNum - columnNumber); // aColumnNum + aViolatedDirectiveString, + aEffectiveDirective, // aEffectiveDirective + aViolatedPolicyIndex, // aViolatedPolicyIndex + observerSubject, // aObserverSubject + sourceFile, // aSourceFile + aReportSample, // aReportSample + aSample, // aScriptSample + lineNumber, // aLineNum + columnNumber); // aColumnNum } NS_IMETHODIMP @@ -672,19 +671,16 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, bool aHasUnsafeHash, *outAllowsInline = false; } nsAutoString violatedDirective; + nsAutoString violatedDirectiveString; bool reportSample = false; - mPolicies[i]->getDirectiveStringAndReportSampleForContentType( - aDirective, violatedDirective, &reportSample); - - // In CSP 3.0 the effective directive doesn't become the actually used - // directive in case of a fallback from e.g. script-src-elem to - // script-src or default-src. - nsAutoString effectiveDirective; - effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDirective)); + mPolicies[i]->getViolatedDirectiveInformation( + aDirective, violatedDirective, violatedDirectiveString, + &reportSample); reportInlineViolation(aDirective, aTriggeringElement, aCSPEventListener, aNonce, reportSample, content, violatedDirective, - effectiveDirective, i, aLineNumber, aColumnNumber); + violatedDirectiveString, aDirective, i, aLineNumber, + aColumnNumber); } } @@ -747,15 +743,18 @@ nsCSPContext::LogViolationDetails( } nsAutoString violatedDirective; + nsAutoString violatedDirectiveString; bool reportSample = false; - mPolicies[p]->getDirectiveStringAndReportSampleForContentType( - SCRIPT_SRC_DIRECTIVE, violatedDirective, &reportSample); + mPolicies[p]->getViolatedDirectiveInformation( + SCRIPT_SRC_DIRECTIVE, violatedDirective, violatedDirectiveString, + &reportSample); - AsyncReportViolation(aTriggeringElement, aCSPEventListener, nullptr, - blockedContentSource, nullptr, violatedDirective, - u"script-src"_ns /* aEffectiveDirective */, p, - observerSubject, aSourceFile, reportSample, - aScriptSample, aLineNum, aColumnNum); + AsyncReportViolation( + aTriggeringElement, aCSPEventListener, nullptr, blockedContentSource, + nullptr, violatedDirective, violatedDirectiveString, + CSPDirective::SCRIPT_SRC_DIRECTIVE /* aEffectiveDirective */, p, + observerSubject, aSourceFile, reportSample, aScriptSample, aLineNum, + aColumnNum); } return NS_OK; } @@ -1375,10 +1374,12 @@ class CSPReportSenderRunnable final : public Runnable { nsIURI* aBlockedURI, nsCSPContext::BlockedContentSource aBlockedContentSource, nsIURI* aOriginalURI, uint32_t aViolatedPolicyIndex, bool aReportOnlyFlag, - const nsAString& aViolatedDirective, const nsAString& aEffectiveDirective, - const nsAString& aObserverSubject, const nsAString& aSourceFile, - bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum, - uint32_t aColumnNum, nsCSPContext* aCSPContext) + const nsAString& aViolatedDirective, + const nsAString& aViolatedDirectiveString, + const CSPDirective aEffectiveDirective, const nsAString& aObserverSubject, + const nsAString& aSourceFile, bool aReportSample, + const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum, + nsCSPContext* aCSPContext) : mozilla::Runnable("CSPReportSenderRunnable"), mTriggeringElement(aTriggeringElement), mCSPEventListener(aCSPEventListener), @@ -1389,6 +1390,7 @@ class CSPReportSenderRunnable final : public Runnable { mReportOnlyFlag(aReportOnlyFlag), mReportSample(aReportSample), mViolatedDirective(aViolatedDirective), + mViolatedDirectiveString(aViolatedDirectiveString), mEffectiveDirective(aEffectiveDirective), mSourceFile(aSourceFile), mScriptSample(aScriptSample), @@ -1436,16 +1438,18 @@ class CSPReportSenderRunnable final : public Runnable { NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); - nsresult rv; - // 0) prepare violation data mozilla::dom::SecurityPolicyViolationEventInit init; nsAutoCString blockedContentSource; BlockedContentSourceToString(mBlockedContentSource, blockedContentSource); - rv = mCSPContext->GatherSecurityPolicyViolationEventData( - mBlockedURI, blockedContentSource, mOriginalURI, mEffectiveDirective, + nsAutoString effectiveDirective; + effectiveDirective.AssignASCII( + CSP_CSPDirectiveToString(mEffectiveDirective)); + + nsresult rv = mCSPContext->GatherSecurityPolicyViolationEventData( + mBlockedURI, blockedContentSource, mOriginalURI, effectiveDirective, mViolatedPolicyIndex, mSourceFile, mReportSample ? mScriptSample : EmptyString(), mLineNum, mColumnNum, init); @@ -1464,32 +1468,7 @@ class CSPReportSenderRunnable final : public Runnable { mCSPContext->SendReports(init, mViolatedPolicyIndex); // 3) log to console (one per policy violation) - - if (mBlockedURI) { - mBlockedURI->GetSpec(blockedContentSource); - if (blockedContentSource.Length() > - nsCSPContext::ScriptSampleMaxLength()) { - bool isData = mBlockedURI->SchemeIs("data"); - if (NS_SUCCEEDED(rv) && isData && - blockedContentSource.Length() > - nsCSPContext::ScriptSampleMaxLength()) { - blockedContentSource.Truncate(nsCSPContext::ScriptSampleMaxLength()); - blockedContentSource.Append( - NS_ConvertUTF16toUTF8(nsContentUtils::GetLocalizedEllipsis())); - } - } - } - - if (blockedContentSource.Length() > 0) { - nsString blockedContentSource16 = - NS_ConvertUTF8toUTF16(blockedContentSource); - AutoTArray<nsString, 2> params = {mViolatedDirective, - blockedContentSource16}; - mCSPContext->logToConsole( - mReportOnlyFlag ? "CSPROViolationWithURI" : "CSPViolationWithURI", - params, mSourceFile, mScriptSample, mLineNum, mColumnNum, - nsIScriptError::errorFlag); - } + ReportToConsole(); // 4) fire violation event // A frame-ancestors violation has occurred, but we should not dispatch @@ -1503,6 +1482,104 @@ class CSPReportSenderRunnable final : public Runnable { } private: + void ReportToConsole() const { + NS_ConvertUTF8toUTF16 effectiveDirective( + CSP_CSPDirectiveToString(mEffectiveDirective)); + + switch (mBlockedContentSource) { + case nsCSPContext::BlockedContentSource::eInline: { + const char* errorName = nullptr; + if (mEffectiveDirective == CSPDirective::STYLE_SRC_ATTR_DIRECTIVE || + mEffectiveDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) { + errorName = mReportOnlyFlag ? "CSPROInlineStyleViolation" + : "CSPInlineStyleViolation"; + } else if (mEffectiveDirective == + CSPDirective::SCRIPT_SRC_ATTR_DIRECTIVE) { + errorName = mReportOnlyFlag ? "CSPROEventHandlerScriptViolation" + : "CSPEventHandlerScriptViolation"; + } else { + MOZ_ASSERT(mEffectiveDirective == + CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE); + errorName = mReportOnlyFlag ? "CSPROInlineScriptViolation" + : "CSPInlineScriptViolation"; + } + + AutoTArray<nsString, 2> params = {mViolatedDirectiveString, + effectiveDirective}; + mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample, + mLineNum, mColumnNum, + nsIScriptError::errorFlag); + break; + } + + case nsCSPContext::BlockedContentSource::eEval: { + AutoTArray<nsString, 2> params = {mViolatedDirectiveString, + effectiveDirective}; + mCSPContext->logToConsole(mReportOnlyFlag ? "CSPROEvalScriptViolation" + : "CSPEvalScriptViolation", + params, mSourceFile, mScriptSample, mLineNum, + mColumnNum, nsIScriptError::errorFlag); + break; + } + + case nsCSPContext::BlockedContentSource::eWasmEval: { + AutoTArray<nsString, 2> params = {mViolatedDirectiveString, + effectiveDirective}; + mCSPContext->logToConsole(mReportOnlyFlag + ? "CSPROWasmEvalScriptViolation" + : "CSPWasmEvalScriptViolation", + params, mSourceFile, mScriptSample, mLineNum, + mColumnNum, nsIScriptError::errorFlag); + break; + } + + case nsCSPContext::BlockedContentSource::eSelf: + case nsCSPContext::BlockedContentSource::eUnknown: { + nsAutoString source(u"<unknown>"_ns); + if (mBlockedURI) { + nsAutoCString uri; + mBlockedURI->GetSpec(uri); + + if (mBlockedURI->SchemeIs("data") && + uri.Length() > nsCSPContext::ScriptSampleMaxLength()) { + uri.Truncate(nsCSPContext::ScriptSampleMaxLength()); + uri.Append( + NS_ConvertUTF16toUTF8(nsContentUtils::GetLocalizedEllipsis())); + } + + if (!uri.IsEmpty()) { + CopyUTF8toUTF16(uri, source); + } + } + + const char* errorName = nullptr; + switch (mEffectiveDirective) { + case CSPDirective::STYLE_SRC_ELEM_DIRECTIVE: + errorName = + mReportOnlyFlag ? "CSPROStyleViolation" : "CSPStyleViolation"; + break; + case CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE: + errorName = + mReportOnlyFlag ? "CSPROScriptViolation" : "CSPScriptViolation"; + break; + case CSPDirective::WORKER_SRC_DIRECTIVE: + errorName = + mReportOnlyFlag ? "CSPROWorkerViolation" : "CSPWorkerViolation"; + break; + default: + errorName = mReportOnlyFlag ? "CSPROGenericViolation" + : "CSPGenericViolation"; + } + + AutoTArray<nsString, 3> params = {mViolatedDirectiveString, source, + effectiveDirective}; + mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample, + mLineNum, mColumnNum, + nsIScriptError::errorFlag); + } + } + } + RefPtr<Element> mTriggeringElement; nsCOMPtr<nsICSPEventListener> mCSPEventListener; nsCOMPtr<nsIURI> mBlockedURI; @@ -1512,7 +1589,8 @@ class CSPReportSenderRunnable final : public Runnable { bool mReportOnlyFlag; bool mReportSample; nsString mViolatedDirective; - nsString mEffectiveDirective; + nsString mViolatedDirectiveString; + CSPDirective mEffectiveDirective; nsCOMPtr<nsISupports> mObserverSubject; nsString mSourceFile; nsString mScriptSample; @@ -1554,7 +1632,8 @@ nsresult nsCSPContext::AsyncReportViolation( Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener, nsIURI* aBlockedURI, BlockedContentSource aBlockedContentSource, nsIURI* aOriginalURI, const nsAString& aViolatedDirective, - const nsAString& aEffectiveDirective, uint32_t aViolatedPolicyIndex, + const nsAString& aViolatedDirectiveString, + const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex, const nsAString& aObserverSubject, const nsAString& aSourceFile, bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum) { @@ -1565,8 +1644,8 @@ nsresult nsCSPContext::AsyncReportViolation( aTriggeringElement, aCSPEventListener, aBlockedURI, aBlockedContentSource, aOriginalURI, aViolatedPolicyIndex, mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(), aViolatedDirective, - aEffectiveDirective, aObserverSubject, aSourceFile, aReportSample, - aScriptSample, aLineNum, aColumnNum, this); + aViolatedDirectiveString, aEffectiveDirective, aObserverSubject, + aSourceFile, aReportSample, aScriptSample, aLineNum, aColumnNum, this); if (XRE_IsContentProcess()) { if (mEventTarget) { diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h index 0c1438e573..ae47e34cb3 100644 --- a/dom/security/nsCSPContext.h +++ b/dom/security/nsCSPContext.h @@ -126,10 +126,12 @@ class nsCSPContext : public nsIContentSecurityPolicy { mozilla::dom::Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener, nsIURI* aBlockedURI, BlockedContentSource aBlockedContentSource, nsIURI* aOriginalURI, - const nsAString& aViolatedDirective, const nsAString& aEffectiveDirective, - uint32_t aViolatedPolicyIndex, const nsAString& aObserverSubject, - const nsAString& aSourceFile, bool aReportSample, - const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum); + const nsAString& aViolatedDirective, + const nsAString& aViolatedDirectiveString, + const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex, + const nsAString& aObserverSubject, const nsAString& aSourceFile, + bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum, + uint32_t aColumnNum); // Hands off! Don't call this method unless you know what you // are doing. It's only supposed to be called from within @@ -168,7 +170,8 @@ class nsCSPContext : public nsIContentSecurityPolicy { const nsAString& aNonce, bool aReportSample, const nsAString& aSample, const nsAString& aViolatedDirective, - const nsAString& aEffectiveDirective, + const nsAString& aViolatedDirectiveString, + CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex, uint32_t aLineNumber, uint32_t aColumnNumber); diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index 50730b691b..11d09909f7 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -1569,7 +1569,8 @@ nsCSPPolicy::~nsCSPPolicy() { bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo, nsIURI* aUri, bool aWasRedirected, bool aSpecific, - nsAString& outViolatedDirective) const { + nsAString& outViolatedDirective, + nsAString& outViolatedDirectiveString) const { if (CSPUTILSLOGENABLED()) { CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s", aUri->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir), @@ -1578,6 +1579,7 @@ bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo, NS_ASSERTION(aUri, "permits needs an uri to perform the check!"); outViolatedDirective.Truncate(); + outViolatedDirectiveString.Truncate(); nsCSPDirective* defaultDir = nullptr; @@ -1589,6 +1591,7 @@ bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo, if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly, mUpgradeInsecDir)) { mDirectives[i]->getDirName(outViolatedDirective); + mDirectives[i]->toString(outViolatedDirectiveString); return false; } return true; @@ -1604,6 +1607,7 @@ bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo, if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly, mUpgradeInsecDir)) { defaultDir->getDirName(outViolatedDirective); + defaultDir->toString(outViolatedDirectiveString); return false; } return true; @@ -1692,43 +1696,22 @@ bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const { * The parameter outDirective is the equivalent of 'outViolatedDirective' * for the ::permits() function family. */ -void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType( - CSPDirective aDirective, nsAString& outDirective, - bool* aReportSample) const { - MOZ_ASSERT(aReportSample); +void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective, + nsAString& outDirective, + nsAString& outDirectiveString, + bool* aReportSample) const { *aReportSample = false; - - nsCSPDirective* defaultDir = nullptr; - for (uint32_t i = 0; i < mDirectives.Length(); i++) { - if (mDirectives[i]->isDefaultDirective()) { - defaultDir = mDirectives[i]; - continue; - } - if (mDirectives[i]->equals(aDirective)) { - mDirectives[i]->getDirName(outDirective); - *aReportSample = mDirectives[i]->hasReportSampleKeyword(); - return; - } - } - // if we haven't found a matching directive yet, - // the contentType must be restricted by the default directive - if (defaultDir) { - defaultDir->getDirName(outDirective); - *aReportSample = defaultDir->hasReportSampleKeyword(); + nsCSPDirective* directive = matchingOrDefaultDirective(aDirective); + if (!directive) { + MOZ_ASSERT_UNREACHABLE("Can not query violated directive"); + outDirective.AppendLiteral("couldNotQueryViolatedDirective"); + outDirective.Truncate(); return; } - NS_ASSERTION(false, "Can not query directive string for contentType!"); - outDirective.AppendLiteral("couldNotQueryViolatedDirective"); -} -void nsCSPPolicy::getDirectiveAsString(CSPDirective aDir, - nsAString& outDirective) const { - for (uint32_t i = 0; i < mDirectives.Length(); i++) { - if (mDirectives[i]->equals(aDir)) { - mDirectives[i]->toString(outDirective); - return; - } - } + directive->getDirName(outDirective); + directive->toString(outDirectiveString); + *aReportSample = directive->hasReportSampleKeyword(); } /* diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 2692681d03..eeccaf0c4a 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -619,7 +619,8 @@ class nsCSPPolicy { bool permits(CSPDirective aDirective, nsILoadInfo* aLoadInfo, nsIURI* aUri, bool aWasRedirected, bool aSpecific, - nsAString& outViolatedDirective) const; + nsAString& outViolatedDirective, + nsAString& outViolatedDirectiveString) const; bool allows(CSPDirective aDirective, enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; @@ -650,11 +651,10 @@ class nsCSPPolicy { void getReportURIs(nsTArray<nsString>& outReportURIs) const; - void getDirectiveStringAndReportSampleForContentType( - CSPDirective aDirective, nsAString& outDirective, - bool* aReportSample) const; - - void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const; + void getViolatedDirectiveInformation(CSPDirective aDirective, + nsAString& outDirective, + nsAString& outDirectiveString, + bool* aReportSample) const; uint32_t getSandboxFlags() const; diff --git a/dom/security/nsContentSecurityUtils.cpp b/dom/security/nsContentSecurityUtils.cpp index a483522499..01e9c6d5db 100644 --- a/dom/security/nsContentSecurityUtils.cpp +++ b/dom/security/nsContentSecurityUtils.cpp @@ -1365,6 +1365,7 @@ void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) { // preferences and downloads allow legacy inline scripts through hash src. MOZ_ASSERT(!foundScriptSrc || StringBeginsWith(aboutSpec, "about:preferences"_ns) || + StringBeginsWith(aboutSpec, "about:settings"_ns) || StringBeginsWith(aboutSpec, "about:downloads"_ns) || StringBeginsWith(aboutSpec, "about:asrouter"_ns) || StringBeginsWith(aboutSpec, "about:newtab"_ns) || @@ -1383,6 +1384,7 @@ void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) { // remote web resources MOZ_ASSERT(!foundWebScheme || StringBeginsWith(aboutSpec, "about:preferences"_ns) || + StringBeginsWith(aboutSpec, "about:settings"_ns) || StringBeginsWith(aboutSpec, "about:addons"_ns) || StringBeginsWith(aboutSpec, "about:newtab"_ns) || StringBeginsWith(aboutSpec, "about:debugging"_ns) || @@ -1411,6 +1413,7 @@ void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) { // Bug 1579160: Remove 'unsafe-inline' from style-src within // about:preferences "about:preferences"_ns, + "about:settings"_ns, // Bug 1571346: Remove 'unsafe-inline' from style-src within about:addons "about:addons"_ns, // Bug 1584485: Remove 'unsafe-inline' from style-src within: @@ -1553,7 +1556,7 @@ bool nsContentSecurityUtils::ValidateScriptFilename(JSContext* cx, // and this is the most reasonable. See 1727770 u"about:downloads"_ns, // We think this is the same problem as about:downloads - u"about:preferences"_ns, + u"about:preferences"_ns, u"about:settings"_ns, // Browser console will give a filename of 'debugger' See 1763943 // Sometimes it's 'debugger eager eval code', other times just 'debugger // eval code' @@ -1667,37 +1670,25 @@ long nsContentSecurityUtils::ClassifyDownload( nsCOMPtr<nsIURI> contentLocation; aChannel->GetURI(getter_AddRefs(contentLocation)); - nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal(); - if (!loadingPrincipal) { - loadingPrincipal = loadInfo->TriggeringPrincipal(); - } - // Creating a fake Loadinfo that is just used for the MCB check. - nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new mozilla::net::LoadInfo( - loadingPrincipal, loadInfo->TriggeringPrincipal(), nullptr, - nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, - nsIContentPolicy::TYPE_FETCH); - // Disable HTTPS-Only checks for that loadinfo. This is required because - // otherwise nsMixedContentBlocker::ShouldLoad would assume that the request - // is safe, because HTTPS-Only is handling it. - secCheckLoadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT); - - int16_t decission = nsIContentPolicy::ACCEPT; - nsMixedContentBlocker::ShouldLoad(false, // aHadInsecureImageRedirect - contentLocation, // aContentLocation, - secCheckLoadInfo, // aLoadinfo - false, // aReportError - &decission // aDecision - ); - Telemetry::Accumulate(mozilla::Telemetry::MIXED_CONTENT_DOWNLOADS, - decission != nsIContentPolicy::ACCEPT); - - if (StaticPrefs::dom_block_download_insecure() && - decission != nsIContentPolicy::ACCEPT) { - nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); - if (httpChannel) { - LogMessageToConsole(httpChannel, "MixedContentBlockedDownload"); + if (StaticPrefs::dom_block_download_insecure()) { + // If we are not dealing with a potentially trustworthy origin, or a URI + // that is safe to be loaded like e.g. data:, then we block the load. + bool isInsecureDownload = + !nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin( + contentLocation) && + !nsMixedContentBlocker::URISafeToBeLoadedInSecureContext( + contentLocation); + + Telemetry::Accumulate(mozilla::Telemetry::INSECURE_DOWNLOADS, + isInsecureDownload); + + if (isInsecureDownload) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + LogMessageToConsole(httpChannel, "BlockedInsecureDownload"); + } + return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE; } - return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE; } if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp index 2a3880ba70..535efaba4e 100644 --- a/dom/security/nsHTTPSOnlyUtils.cpp +++ b/dom/security/nsHTTPSOnlyUtils.cpp @@ -6,6 +6,8 @@ #include "mozilla/Components.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/NullPrincipal.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/net/DNS.h" @@ -438,7 +440,7 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, // We can upgrade the request - let's log to the console and set the status // so we know that we upgraded the request. if (aLoadInfo->GetWasSchemelessInput() && - mozilla::StaticPrefs::dom_security_https_first_schemeless()) { + !IsHttpsFirstModeEnabled(isPrivateWin)) { nsAutoCString urlCString; aURI->GetSpec(urlCString); NS_ConvertUTF8toUTF16 urlString(urlCString); @@ -447,6 +449,8 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params, nsIScriptError::warningFlag, aLoadInfo, aURI, true); + + mozilla::glean::httpsfirst::upgraded_schemeless.Add(); } else { nsAutoCString scheme; @@ -461,7 +465,12 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection" : "HTTPSOnlyUpgradeRequest", params, nsIScriptError::warningFlag, aLoadInfo, aURI, true); + + if (!isSpeculative) { + mozilla::glean::httpsfirst::upgraded.Add(); + } } + // Set flag so we know that we upgraded the request httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST; aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); @@ -470,9 +479,11 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, /* static */ already_AddRefed<nsIURI> -nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, - nsresult aStatus) { - nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); +nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest( + mozilla::net::DocumentLoadListener* aDocumentLoadListener, + nsresult aStatus) { + nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel(); + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); // Only downgrade if we this request was upgraded using HTTPS-First Mode if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) { @@ -488,7 +499,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, // to check each NS_OK for those errors. // Only downgrade an NS_OK status if it is an 4xx or 5xx error. if (NS_SUCCEEDED(aStatus)) { - nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); // If no httpChannel exists we have nothing to do here. if (!httpChannel) { return nullptr; @@ -532,7 +543,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, } nsCOMPtr<nsIURI> uri; - nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + nsresult rv = channel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, nullptr); nsAutoCString spec; @@ -584,6 +595,33 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, nsIScriptError::warningFlag, loadInfo, uri, true); + // Record telemety + nsDOMNavigationTiming* timing = aDocumentLoadListener->GetTiming(); + if (timing) { + mozilla::TimeStamp navigationStart = timing->GetNavigationStartTimeStamp(); + if (navigationStart) { + mozilla::TimeDuration duration = + mozilla::TimeStamp::Now() - navigationStart; + bool isPrivateWin = + loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + + if (loadInfo->GetWasSchemelessInput() && + !IsHttpsFirstModeEnabled(isPrivateWin)) { + mozilla::glean::httpsfirst::downgraded_schemeless.Add(); + if (timing) { + mozilla::glean::httpsfirst::downgrade_time_schemeless + .AccumulateRawDuration(duration); + } + } else { + mozilla::glean::httpsfirst::downgraded.Add(); + if (timing) { + mozilla::glean::httpsfirst::downgrade_time.AccumulateRawDuration( + duration); + } + } + } + } + return newURI.forget(); } @@ -954,6 +992,19 @@ TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) { nsresult httpsOnlyChannelStatus; httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus); if (httpsOnlyChannelStatus == NS_OK) { + bool isPrivateWin = + loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + if (!nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) { + // Record HTTPS-First Telemetry + if (loadInfo->GetWasSchemelessInput() && + !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) { + mozilla::glean::httpsfirst::downgraded_on_timer_schemeless + .AddToNumerator(); + } else { + mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator(); + } + } + httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL); } } diff --git a/dom/security/nsHTTPSOnlyUtils.h b/dom/security/nsHTTPSOnlyUtils.h index 73a5219082..7e36bfadbd 100644 --- a/dom/security/nsHTTPSOnlyUtils.h +++ b/dom/security/nsHTTPSOnlyUtils.h @@ -95,12 +95,13 @@ class nsHTTPSOnlyUtils { /** * Determines if the request was previously upgraded with HTTPS-First, creates * a downgraded URI and logs to console. - * @param aStatus Status code - * @param aChannel Failed channel - * @return URI with http-scheme or nullptr + * @param aStatus Status code + * @param aDocumentLoadListener Failed document load listener + * @return URI with http-scheme or nullptr */ static already_AddRefed<nsIURI> PotentiallyDowngradeHttpsFirstRequest( - nsIChannel* aChannel, nsresult aStatus); + mozilla::net::DocumentLoadListener* aDocumentLoadListener, + nsresult aStatus); /** * Checks if the error code is on a block-list of codes that are probably diff --git a/dom/security/test/csp/file_csp_error_messages.html b/dom/security/test/csp/file_csp_error_messages.html new file mode 100644 index 0000000000..65d26ac57e --- /dev/null +++ b/dom/security/test/csp/file_csp_error_messages.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="default-src 'nonce-abc';"> + <title></title> +</head> + +<!-- event handler --> +<body onload="alert('onload');"> + + <!-- img-src --> + <img src="image.png"> + + <!-- external script --> + <script src=script.js></script> + + <!-- inline script --> + <script> + alert("failure"); + </script> + + <script nonce="abc"> + /* worker-src */ + new Worker("/worker.js") + </script> + + <script nonce="abc"> + // eslint-disable-next-line no-eval + eval("hello world"); + </script> +</body> +</html>
\ No newline at end of file diff --git a/dom/security/test/csp/mochitest.toml b/dom/security/test/csp/mochitest.toml index 8d8c6c31f5..5dd9a14222 100644 --- a/dom/security/test/csp/mochitest.toml +++ b/dom/security/test/csp/mochitest.toml @@ -433,6 +433,9 @@ skip-if = [ ["test_connect-src.html"] +["test_csp_error_messages.html"] +support-files = ["file_csp_error_messages.html"] + ["test_csp_frame_ancestors_about_blank.html"] support-files = [ "file_csp_frame_ancestors_about_blank.html", diff --git a/dom/security/test/csp/test_csp_error_messages.html b/dom/security/test/csp/test_csp_error_messages.html new file mode 100644 index 0000000000..51be37e7c0 --- /dev/null +++ b/dom/security/test/csp/test_csp_error_messages.html @@ -0,0 +1,75 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Test some specialized CSP errors</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<iframe id="cspframe"></iframe> + +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function cleanup() { + SpecialPowers.postConsoleSentinel(); + SimpleTest.finish(); +}; + +let errors = []; +function add(name) { + ok(!errors.includes(name), `duplicate error for ${name}`); + errors.push(name); +} + +SpecialPowers.registerConsoleListener(msg => { + if (!msg.errorMessage) { + return; + } + + let {errorMessage} = msg; + function contains(str) { + ok(errorMessage.includes(str), `error message contains "${str}"`); + } + + if (errorMessage.includes("(script-src-attr)")) { + contains("blocked an event handler"); + contains("from being executed"); + contains("Source: alert('onload');"); + add("event handler"); + } else if (errorMessage.includes("(img-src)")) { + contains("blocked the loading of a resource"); + contains("/image.png"); + add("image"); + } else if (errorMessage.includes("an inline script")) { + contains("(script-src-elem)"); + contains("from being executed"); + add("inline script"); + } else if (errorMessage.includes("a script")) { + contains("(script-src-elem)"); + contains("from being executed"); + contains("/script.js"); + add("script"); + } else if (errorMessage.includes("(worker-src)")) { + contains("(worker-src)"); + contains("from being executed"); + contains("/worker.js"); + add("worker"); + } else if (errorMessage.includes("a JavaScript eval")) { + contains("(script-src)"); + contains("from being executed"); + contains("Missing 'unsafe-eval'") + add("eval"); + } + + if (errors.length == 6) { + SimpleTest.executeSoon(cleanup); + } +}); + +document.getElementById('cspframe').src = 'file_csp_error_messages.html'; +</script> +</body> +</html> diff --git a/dom/security/test/general/browser.toml b/dom/security/test/general/browser.toml index 0f4ec5b224..c6d6b4bf79 100644 --- a/dom/security/test/general/browser.toml +++ b/dom/security/test/general/browser.toml @@ -48,6 +48,16 @@ support-files = [ "file_gpc_server.sjs", ] +["browser_test_http_download.js"] +skip-if = [ + "win11_2009", # Bug 1784764 + "os == 'linux' && !debug", +] +support-files = [ + "http_download_page.html", + "http_download_server.sjs" +] + ["browser_test_referrer_loadInOtherProcess.js"] ["browser_test_report_blocking.js"] diff --git a/dom/security/test/general/browser_restrict_privileged_about_script.js b/dom/security/test/general/browser_restrict_privileged_about_script.js index 0baa6e3d4d..7dfb6d691a 100644 --- a/dom/security/test/general/browser_restrict_privileged_about_script.js +++ b/dom/security/test/general/browser_restrict_privileged_about_script.js @@ -20,7 +20,7 @@ add_task(async function test_principal_click() { }); await BrowserTestUtils.withNewTab( "about:test-about-privileged-with-scripts", - async function (browser) { + async function () { // Wait for page to fully load info("Waiting for tab to be loaded.."); // let's look into the fully loaded about page diff --git a/dom/security/test/general/browser_test_data_download.js b/dom/security/test/general/browser_test_data_download.js index df5a8aeac4..9cebb97b30 100644 --- a/dom/security/test/general/browser_test_data_download.js +++ b/dom/security/test/general/browser_test_data_download.js @@ -22,13 +22,13 @@ function addWindowListener(aURL) { resolve(domwindow); }, domwindow); }, - onCloseWindow(aXULWindow) {}, + onCloseWindow() {}, }); }); } function waitDelay(delay) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { /* eslint-disable mozilla/no-arbitrary-setTimeout */ window.setTimeout(resolve, delay); }); diff --git a/dom/security/test/general/browser_test_data_text_csv.js b/dom/security/test/general/browser_test_data_text_csv.js index 9855ddce46..b6c9f46336 100644 --- a/dom/security/test/general/browser_test_data_text_csv.js +++ b/dom/security/test/general/browser_test_data_text_csv.js @@ -6,7 +6,7 @@ const kTestPath = getRootDirectory(gTestPath).replace( ); const kTestURI = kTestPath + "file_data_text_csv.html"; -function addWindowListener(aURL, aCallback) { +function addWindowListener(aURL) { return new Promise(resolve => { Services.wm.addListener({ onOpenWindow(aXULWindow) { @@ -22,7 +22,7 @@ function addWindowListener(aURL, aCallback) { resolve(domwindow); }, domwindow); }, - onCloseWindow(aXULWindow) {}, + onCloseWindow() {}, }); }); } diff --git a/dom/security/test/general/browser_test_http_download.js b/dom/security/test/general/browser_test_http_download.js new file mode 100644 index 0000000000..35e3fdfc4b --- /dev/null +++ b/dom/security/test/general/browser_test_http_download.js @@ -0,0 +1,275 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs", +}); + +const HandlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" +].getService(Ci.nsIHandlerService); + +const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + +// Using insecure HTTP URL for a test cases around HTTP downloads +let INSECURE_BASE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/" + ) + "http_download_page.html"; + +function promiseFocus() { + return new Promise(resolve => { + waitForFocus(resolve); + }); +} + +async function task_openPanel() { + await promiseFocus(); + + let promise = BrowserTestUtils.waitForPopupEvent( + DownloadsPanel.panel, + "shown" + ); + DownloadsPanel.showPanel(); + await promise; +} + +const downloadMonitoringView = { + _listeners: [], + onDownloadAdded(download) { + for (let listener of this._listeners) { + listener(download); + } + this._listeners = []; + }, + waitForDownload(listener) { + this._listeners.push(listener); + }, +}; + +/** + * Waits until a download is triggered. + * Unless the always_ask_before_handling_new_types pref is true, the download + * will simply be saved, so resolve when the view is notified of the new + * download. Otherwise, it waits until a prompt is shown, selects the choosen + * <action>, then accepts the dialog + * @param [action] Which action to select, either: + * "handleInternally", "save" or "open". + * @returns {Promise} Resolved once done. + */ + +function shouldTriggerDownload(action = "save") { + if ( + Services.prefs.getBoolPref( + "browser.download.always_ask_before_handling_new_types" + ) + ) { + return new Promise((resolve, reject) => { + Services.wm.addListener({ + onOpenWindow(xulWin) { + Services.wm.removeListener(this); + let win = xulWin.docShell.domWindow; + waitForFocus(() => { + if ( + win.location == + "chrome://mozapps/content/downloads/unknownContentType.xhtml" + ) { + let dialog = win.document.getElementById("unknownContentType"); + let button = dialog.getButton("accept"); + let actionRadio = win.document.getElementById(action); + actionRadio.click(); + button.disabled = false; + dialog.acceptDialog(); + resolve(); + } else { + reject(); + } + }, win); + }, + }); + }); + } + return new Promise(res => { + downloadMonitoringView.waitForDownload(res); + }); +} + +const CONSOLE_ERROR_MESSAGE = "We blocked a download that’s not secure"; + +function shouldConsoleError() { + // Waits until CONSOLE_ERROR_MESSAGE was logged + return new Promise((resolve, reject) => { + function listener(msgObj) { + let text = msgObj.message; + if (text.includes(CONSOLE_ERROR_MESSAGE)) { + Services.console.unregisterListener(listener); + resolve(); + } + } + Services.console.registerListener(listener); + }); +} + +async function resetDownloads() { + // Removes all downloads from the download List + const types = new Set(); + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + if (download.contentType) { + types.add(download.contentType); + } + publicList.remove(download); + await download.finalize(true); + } + + if (types.size) { + // reset handlers for the contentTypes of any files previously downloaded + for (let type of types) { + const mimeInfo = MIMEService.getFromTypeAndExtension(type, ""); + info("resetting handler for type: " + type); + HandlerService.remove(mimeInfo); + } + } +} + +function shouldNotifyDownloadUI() { + return new Promise(res => { + downloadMonitoringView.waitForDownload(async aDownload => { + let { error } = aDownload; + if ( + error.becauseBlockedByReputationCheck && + error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE + ) { + // It's an insecure Download, now Check that it has been cleaned up properly + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download target is not empty!`); + } + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download partFile was not cleaned up properly`); + } + // Assert that the Referrer is presnt + if (!aDownload.source.referrerInfo) { + throw new Error("The Blocked download is missing the ReferrerInfo"); + } + + res(aDownload); + } else { + ok(false, "No error for download that was expected to error!"); + } + }); + }); +} + +async function runTest(url, link, checkFunction, description) { + await SpecialPowers.pushPrefEnv({ + set: [["dom.block_download_insecure", true]], + }); + await resetDownloads(); + + let tab = BrowserTestUtils.addTab(gBrowser, url); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("Checking: " + description); + + let checkPromise = checkFunction(); + // Click the Link to trigger the download + SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { + content.document.getElementById(contentLink).click(); + }); + + await checkPromise; + + ok(true, description); + BrowserTestUtils.removeTab(tab); + + await SpecialPowers.popPrefEnv(); +} + +add_setup(async () => { + let list = await Downloads.getList(Downloads.ALL); + list.addView(downloadMonitoringView); + registerCleanupFunction(() => list.removeView(downloadMonitoringView)); +}); + +// Test Blocking +add_task(async function test_blocking() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + await runTest( + INSECURE_BASE_URL, + "http-link", + () => + Promise.all([ + shouldTriggerDownload(), + shouldNotifyDownloadUI(), + shouldConsoleError(), + ]), + "Insecure (HTTP) toplevel -> Insecure (HTTP) download should Error" + ); + await SpecialPowers.popPrefEnv(); + } +}); + +// Test Manual Unblocking +add_task(async function test_manual_unblocking() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + await runTest( + INSECURE_BASE_URL, + "http-link", + async () => { + let [, download] = await Promise.all([ + shouldTriggerDownload(), + shouldNotifyDownloadUI(), + ]); + await download.unblock(); + Assert.equal( + download.error, + null, + "There should be no error after unblocking" + ); + }, + "A blocked download should succeed to download after a manual unblock" + ); + await SpecialPowers.popPrefEnv(); + } +}); + +// Test Unblock Download Visible +add_task(async function test_unblock_download_visible() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + await promiseFocus(); + await runTest( + INSECURE_BASE_URL, + "http-link", + async () => { + let panelHasOpened = BrowserTestUtils.waitForPopupEvent( + DownloadsPanel.panel, + "shown" + ); + info("awaiting that the download is triggered and added to the list"); + await Promise.all([shouldTriggerDownload(), shouldNotifyDownloadUI()]); + info("awaiting that the Download list shows itself"); + await panelHasOpened; + DownloadsPanel.hidePanel(); + ok(true, "The Download Panel should have opened on blocked download"); + }, + "A blocked download should open the download panel" + ); + await SpecialPowers.popPrefEnv(); + } +}); diff --git a/dom/security/test/general/browser_test_report_blocking.js b/dom/security/test/general/browser_test_report_blocking.js index ebd7514097..ab66f1d836 100644 --- a/dom/security/test/general/browser_test_report_blocking.js +++ b/dom/security/test/general/browser_test_report_blocking.js @@ -108,7 +108,7 @@ async function testReporting(test) { return iframe.browsingContext; }); - await SpecialPowers.spawn(frameBC, [type], async obj => { + await SpecialPowers.spawn(frameBC, [type], async () => { // Wait until the reporting UI is visible. await ContentTaskUtils.waitForCondition(() => { let reportUI = content.document.getElementById("blockingErrorReporting"); diff --git a/dom/security/test/general/browser_test_toplevel_data_navigations.js b/dom/security/test/general/browser_test_toplevel_data_navigations.js index 0e006f1fd2..cf7c116eba 100644 --- a/dom/security/test/general/browser_test_toplevel_data_navigations.js +++ b/dom/security/test/general/browser_test_toplevel_data_navigations.js @@ -15,7 +15,7 @@ add_task(async function test_nav_data_uri() { await SpecialPowers.pushPrefEnv({ set: [["security.data_uri.block_toplevel_data_uri_navigations", true]], }); - await BrowserTestUtils.withNewTab(kDataURI, async function (browser) { + await BrowserTestUtils.withNewTab(kDataURI, async function () { await SpecialPowers.spawn( gBrowser.selectedBrowser, [{ kDataBody }], diff --git a/dom/security/test/general/browser_test_view_image_data_navigation.js b/dom/security/test/general/browser_test_view_image_data_navigation.js index 90aace1e3e..6e4173e343 100644 --- a/dom/security/test/general/browser_test_view_image_data_navigation.js +++ b/dom/security/test/general/browser_test_view_image_data_navigation.js @@ -8,7 +8,7 @@ add_task(async function test_principal_right_click_open_link_in_new_tab() { const TEST_PAGE = getRootDirectory(gTestPath) + "file_view_image_data_navigation.html"; - await BrowserTestUtils.withNewTab(TEST_PAGE, async function (browser) { + await BrowserTestUtils.withNewTab(TEST_PAGE, async function () { let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); // simulate right-click->view-image @@ -43,7 +43,7 @@ add_task(async function test_right_click_open_bg_image() { const TEST_PAGE = getRootDirectory(gTestPath) + "file_view_bg_image_data_navigation.html"; - await BrowserTestUtils.withNewTab(TEST_PAGE, async function (browser) { + await BrowserTestUtils.withNewTab(TEST_PAGE, async function () { let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); // simulate right-click->view-image diff --git a/dom/security/test/general/http_download_page.html b/dom/security/test/general/http_download_page.html new file mode 100644 index 0000000000..c5461eaed3 --- /dev/null +++ b/dom/security/test/general/http_download_page.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test for the download attribute</title> + </head> + <body> + hi + + <script> + const host = window.location.host; + const path = location.pathname.replace("http_download_page.html","http_download_server.sjs"); + + const insecureLink = document.createElement("a"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + insecureLink.href=`http://${host}/${path}`; + insecureLink.download="true"; + insecureLink.id="http-link"; + insecureLink.textContent="Not secure Link"; + + document.body.append(insecureLink); + </script> + </body> +</html> diff --git a/dom/security/test/general/http_download_server.sjs b/dom/security/test/general/http_download_server.sjs new file mode 100644 index 0000000000..e659df2f40 --- /dev/null +++ b/dom/security/test/general/http_download_server.sjs @@ -0,0 +1,20 @@ +// force the Browser to Show a Download Prompt + +function handleRequest(request, response) { + let type = "image/png"; + let filename = "hello.png"; + request.queryString.split("&").forEach(val => { + var [key, value] = val.split("="); + if (key == "type") { + type = value; + } + if (key == "name") { + filename = value; + } + }); + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Disposition", `attachment; filename=${filename}`); + response.setHeader("Content-Type", type); + response.write("🙈🙊🐵🙊"); +} diff --git a/dom/security/test/general/test_block_script_wrong_mime.html b/dom/security/test/general/test_block_script_wrong_mime.html index 93a4b9d220..7122363dfc 100644 --- a/dom/security/test/general/test_block_script_wrong_mime.html +++ b/dom/security/test/general/test_block_script_wrong_mime.html @@ -25,7 +25,7 @@ const MIMETypes = [ // <script src=""> function testScript([mime, shouldLoad]) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { let script = document.createElement("script"); script.onload = () => { document.body.removeChild(script); @@ -44,7 +44,7 @@ function testScript([mime, shouldLoad]) { // new Worker() function testWorker([mime, shouldLoad]) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker&mime="+mime); worker.onmessage = (event) => { ok(shouldLoad, `worker with mime '${mime}' should load`) @@ -62,7 +62,7 @@ function testWorker([mime, shouldLoad]) { // new Worker() with importScripts() function testWorkerImportScripts([mime, shouldLoad]) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker-import&mime="+mime); worker.onmessage = (event) => { ok(shouldLoad, `worker/importScripts with mime '${mime}' should load`) diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html index bbadacb218..1a1e6e8f8a 100644 --- a/dom/security/test/general/test_block_toplevel_data_navigation.html +++ b/dom/security/test/general/test_block_toplevel_data_navigation.html @@ -45,7 +45,7 @@ async function expectBlockedToplevelData() { } }; - function observer(subject, topic) { + function observer(subject) { if (!bcs.includes(subject.webProgress)) { bcs.push(subject.webProgress); subject.webProgress.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL); diff --git a/dom/security/test/general/test_bug1277803.xhtml b/dom/security/test/general/test_bug1277803.xhtml index 30cc82310b..8987219ed1 100644 --- a/dom/security/test/general/test_bug1277803.xhtml +++ b/dom/security/test/general/test_bug1277803.xhtml @@ -27,7 +27,7 @@ function runTest() { // Register our observer to intercept favicon requests. - function observer(aSubject, aTopic, aData) { + function observer(aSubject, aTopic) { // Make sure this is a favicon request. let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel); if (FAVICON_URI != httpChannel.URI.spec) { diff --git a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html index 24ec5dbdd9..d0d702d606 100644 --- a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html +++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html @@ -51,7 +51,7 @@ function createChromeScript() { return Ci.nsIContentPolicy.ACCEPT; }, - shouldProcess(contentLocation, loadInfo) { + shouldProcess() { return Ci.nsIContentPolicy.ACCEPT; } }; diff --git a/dom/security/test/general/test_meta_referrer.html b/dom/security/test/general/test_meta_referrer.html index f5e8b649f4..2871028869 100644 --- a/dom/security/test/general/test_meta_referrer.html +++ b/dom/security/test/general/test_meta_referrer.html @@ -24,7 +24,7 @@ function checkTestsDone() { var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ let counter = 0; - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com") || counter >= 2) { return; diff --git a/dom/security/test/general/test_same_site_cookies_subrequest.html b/dom/security/test/general/test_same_site_cookies_subrequest.html index 304dbafa9a..0975e49663 100644 --- a/dom/security/test/general/test_same_site_cookies_subrequest.html +++ b/dom/security/test/general/test_same_site_cookies_subrequest.html @@ -78,7 +78,7 @@ function checkResult(aCookieVal) { function setupQueryResultAndRunTest() { var myXHR = new XMLHttpRequest(); myXHR.open("GET", "file_same_site_cookies_subrequest.sjs?queryresult" + curTest); - myXHR.onload = function(e) { + myXHR.onload = function() { checkResult(myXHR.responseText); } myXHR.onerror = function(e) { diff --git a/dom/security/test/general/test_same_site_cookies_toplevel_nav.html b/dom/security/test/general/test_same_site_cookies_toplevel_nav.html index aba825916b..9ba625e4a3 100644 --- a/dom/security/test/general/test_same_site_cookies_toplevel_nav.html +++ b/dom/security/test/general/test_same_site_cookies_toplevel_nav.html @@ -83,7 +83,7 @@ function checkResult(aCookieVal) { function setupQueryResultAndRunTest() { var myXHR = new XMLHttpRequest(); myXHR.open("GET", "file_same_site_cookies_toplevel_nav.sjs?queryresult" + curTest); - myXHR.onload = function(e) { + myXHR.onload = function() { checkResult( myXHR.responseText); } myXHR.onerror = function(e) { diff --git a/dom/security/test/https-first/browser_download_attribute.js b/dom/security/test/https-first/browser_download_attribute.js index 8165add998..ea4558cc84 100644 --- a/dom/security/test/https-first/browser_download_attribute.js +++ b/dom/security/test/https-first/browser_download_attribute.js @@ -31,7 +31,7 @@ let msgCounter = 0; function shouldConsoleTryUpgradeAndError() { // Waits until CONSOLE_ERROR_MESSAGE was logged. // Checks if download was tried via http:// - return new Promise((resolve, reject) => { + return new Promise(resolve => { function listener(msgObj) { let text = msgObj.message; // Verify upgrade messages diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js index 733474dcc1..c4437f6051 100644 --- a/dom/security/test/https-first/browser_httpsfirst.js +++ b/dom/security/test/https-first/browser_httpsfirst.js @@ -31,6 +31,7 @@ add_task(async function () { await SpecialPowers.pushPrefEnv({ set: [["dom.security.https_first", false]], }); + Services.fog.testResetFOG(); await runPrefTest( "http://example.com", @@ -42,6 +43,23 @@ add_task(async function () { set: [["dom.security.https_first", true]], }); + for (const key of [ + "upgraded", + "upgradedSchemeless", + "downgraded", + "downgradedSchemeless", + "downgradedOnTimer", + "downgradedOnTimerSchemeless", + "downgradeTime", + "downgradeTimeSchemeless", + ]) { + is( + Glean.httpsfirst[key].testGetValue(), + null, + `No telemetry should have been recorded yet for ${key}` + ); + } + await runPrefTest( "http://example.com", "Should upgrade upgradeable website", @@ -71,4 +89,21 @@ add_task(async function () { "Should downgrade after timeout.", "http://" ); + + info("Checking expected telemetry"); + is(Glean.httpsfirst.upgraded.testGetValue(), 5); + is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgraded.testGetValue(), 3); + is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimer.testGetValue().numerator, 1); + is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null); + const downgradeSeconds = + Glean.httpsfirst.downgradeTime.testGetValue().sum / 1_000_000_000; + ok( + downgradeSeconds > 2 && downgradeSeconds < 30, + `Summed downgrade time should be above 2 and below 30 seconds (is ${downgradeSeconds.toFixed( + 2 + )}s)` + ); + is(null, Glean.httpsfirst.downgradeTimeSchemeless.testGetValue()); }); diff --git a/dom/security/test/https-first/browser_mixed_content_download.js b/dom/security/test/https-first/browser_mixed_content_download.js index 09ea64cea8..919470a78e 100644 --- a/dom/security/test/https-first/browser_mixed_content_download.js +++ b/dom/security/test/https-first/browser_mixed_content_download.js @@ -57,7 +57,7 @@ const DOWNLOAD_URL = // falls back since download is not available via https let msgCounter = 0; function shouldConsoleError() { - return new Promise((resolve, reject) => { + return new Promise(resolve => { function listener(msgObj) { let text = msgObj.message; if (text.includes(CONSOLE_UPGRADE_MESSAGE) && msgCounter == 0) { diff --git a/dom/security/test/https-first/browser_schemeless.js b/dom/security/test/https-first/browser_schemeless.js index 9687f15072..64b078983d 100644 --- a/dom/security/test/https-first/browser_schemeless.js +++ b/dom/security/test/https-first/browser_schemeless.js @@ -153,6 +153,7 @@ async function runTest(aInput, aDesc, aExpectedScheme) { add_task(async function () { requestLongerTimeout(10); + Services.fog.testResetFOG(); await SpecialPowers.pushPrefEnv({ set: [ @@ -183,9 +184,36 @@ add_task(async function () { "http" ); + for (const key of [ + "upgraded", + "upgradedSchemeless", + "downgraded", + "downgradedSchemeless", + "downgradedOnTimer", + "downgradedOnTimerSchemeless", + "downgradeTime", + "downgradeTimeSchemeless", + ]) { + is( + Glean.httpsfirst[key].testGetValue(), + null, + `No telemetry should have been recorded yet for ${key}` + ); + } + await runTest( "example.com", "Should upgrade upgradeable website without explicit scheme", "https" ); + + info("Checking expected telemetry"); + is(Glean.httpsfirst.upgraded.testGetValue(), null); + is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), 5); + is(Glean.httpsfirst.downgraded.testGetValue(), null); + is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimer.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradeTime.testGetValue(), null); + is(Glean.httpsfirst.downgradeTimeSchemeless.testGetValue(), null); }); diff --git a/dom/security/test/https-first/file_data_uri.html b/dom/security/test/https-first/file_data_uri.html index 69133e5079..e6d5744802 100644 --- a/dom/security/test/https-first/file_data_uri.html +++ b/dom/security/test/https-first/file_data_uri.html @@ -6,7 +6,7 @@ </head> <body> <script class="testbody" type="text/javascript"> - window.onload = (event) => { + window.onload = () => { let myLoc = window.location.href; window.opener.parent.postMessage({location: myLoc}, "*"); window.close(); diff --git a/dom/security/test/https-first/test_resource_upgrade.html b/dom/security/test/https-first/test_resource_upgrade.html index 66f65d9a04..275a6a23af 100644 --- a/dom/security/test/https-first/test_resource_upgrade.html +++ b/dom/security/test/https-first/test_resource_upgrade.html @@ -44,7 +44,7 @@ // returns after the server has received all the expected requests. var myXHR = new XMLHttpRequest(); myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult"); - myXHR.onload = function (e) { + myXHR.onload = function () { var results = myXHR.responseText.split(","); for (var index in results) { checkResult(results[index]); diff --git a/dom/security/test/https-only/browser_hsts_host.js b/dom/security/test/https-only/browser_hsts_host.js index 858c19865c..c612606f72 100644 --- a/dom/security/test/https-only/browser_hsts_host.js +++ b/dom/security/test/https-only/browser_hsts_host.js @@ -146,7 +146,7 @@ add_task(async function () { Services.obs.removeObserver(observer, "http-on-examine-response"); }); -function observer(subject, topic, state) { +function observer(subject, topic) { info("observer called with " + topic); if (topic == "http-on-examine-response") { onExamineResponse(subject); diff --git a/dom/security/test/https-only/browser_save_as.js b/dom/security/test/https-only/browser_save_as.js index 309dd69c79..fbfdf276a8 100644 --- a/dom/security/test/https-only/browser_save_as.js +++ b/dom/security/test/https-only/browser_save_as.js @@ -14,7 +14,7 @@ const TEST_PATH = "http://example.com/browser/dom/security/test/https-only/file_save_as.html"; let MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); const tempDir = createTemporarySaveDirectory(); MockFilePicker.displayDirectory = tempDir; @@ -78,7 +78,7 @@ function createPromiseForTransferComplete() { } function createPromiseForConsoleError(message) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { function listener(msgObj) { let text = msgObj.message; if (text.includes(message)) { @@ -155,7 +155,11 @@ async function setHttpsFirstAndOnlyPrefs(httpsFirst, httpsOnly) { add_task(async function testBaseline() { // Run with HTTPS-First and HTTPS-Only disabled await setHttpsFirstAndOnlyPrefs(false, false); - await runTest("#insecure-link", HTTP_LINK, undefined); + await runTest( + "#insecure-link", + HTTP_LINK, + "We blocked a download that’s not secure: “http://example.org/”." + ); await runTest("#secure-link", HTTPS_LINK, undefined); }); @@ -169,7 +173,7 @@ add_task(async function testHttpsFirst() { await runTest( "#insecure-link", HTTP_LINK, - "Blocked downloading insecure content “http://example.org/”." + "We blocked a download that’s not secure: “http://example.org/”." ); await runTest("#secure-link", HTTPS_LINK, undefined); }); @@ -181,7 +185,7 @@ add_task(async function testHttpsOnly() { await runTest( "#insecure-link", HTTP_LINK, - "Blocked downloading insecure content “http://example.org/”." + "We blocked a download that’s not secure: “http://example.org/”." ); await runTest("#secure-link", HTTPS_LINK, undefined); }); diff --git a/dom/security/test/https-only/browser_user_gesture.js b/dom/security/test/https-only/browser_user_gesture.js index e7d6a20318..9eec10bd88 100644 --- a/dom/security/test/https-only/browser_user_gesture.js +++ b/dom/security/test/https-only/browser_user_gesture.js @@ -23,7 +23,7 @@ add_task(async function () { // 1. Upgrade a page to https:// BrowserTestUtils.startLoadingURIString(browser, kTestURI); await loaded; - await ContentTask.spawn(browser, {}, async args => { + await ContentTask.spawn(browser, {}, async () => { ok( content.document.location.href.startsWith("https://"), "Should be https" diff --git a/dom/security/test/https-only/file_upgrade_insecure.html b/dom/security/test/https-only/file_upgrade_insecure.html index 346cfbeb9c..b2d3b89e3d 100644 --- a/dom/security/test/https-only/file_upgrade_insecure.html +++ b/dom/security/test/https-only/file_upgrade_insecure.html @@ -55,7 +55,7 @@ ); if (AppConstants.platform !== "android") { var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure"); - mySocket.onopen = function(e) { + mySocket.onopen = function() { if (mySocket.url.includes("wss://")) { window.parent.postMessage({result: "websocket-ok"}, "*"); } diff --git a/dom/security/test/https-only/file_websocket_exceptions_iframe.html b/dom/security/test/https-only/file_websocket_exceptions_iframe.html index 23c6af2d45..d8d0046119 100644 --- a/dom/security/test/https-only/file_websocket_exceptions_iframe.html +++ b/dom/security/test/https-only/file_websocket_exceptions_iframe.html @@ -5,17 +5,17 @@ <script> window.addEventListener("message", receiveMessage); -function receiveMessage(event) { +function receiveMessage() { window.removeEventListener("message", receiveMessage); var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure"); - mySocket.onopen = function(e) { + mySocket.onopen = function() { parent.dispatchEvent(new CustomEvent("WebSocketEnded", { detail: { url: mySocket.url, state: "onopen" } })); mySocket.close(); }; - mySocket.onerror = function(e) { + mySocket.onerror = function() { parent.dispatchEvent(new CustomEvent("WebSocketEnded", { detail: { url: mySocket.url, state: "onerror" } })); diff --git a/dom/security/test/https-only/test_redirect_upgrade.html b/dom/security/test/https-only/test_redirect_upgrade.html index 59f02f96d0..6cf96bd494 100644 --- a/dom/security/test/https-only/test_redirect_upgrade.html +++ b/dom/security/test/https-only/test_redirect_upgrade.html @@ -28,12 +28,12 @@ Test that 302 redirect requests get upgraded to https:// with HTTPS-Only Mode en // Make a request to a site (eg. https://file_redirect.sjs?301), which will redirect to http://file_redirect.sjs?check. // The response will either be secure-ok, if the request has been upgraded to https:// or secure-error if it didn't. myXHR.open("GET", `https://example.com/tests/dom/security/test/https-only/file_redirect.sjs?${currentCode}`); - myXHR.onload = (e) => { + myXHR.onload = () => { is(myXHR.responseText, "secure-ok", `a ${currentCode} redirect when posting violation report should be blocked`) testDone(); } // This should not happen - myXHR.onerror = (e) => { + myXHR.onerror = () => { ok(false, `Could not query results from server for ${currentCode}-redirect test (" + e.message + ")`); testDone(); } diff --git a/dom/security/test/https-only/test_resource_upgrade.html b/dom/security/test/https-only/test_resource_upgrade.html index 6584bad020..1be7fcc3dd 100644 --- a/dom/security/test/https-only/test_resource_upgrade.html +++ b/dom/security/test/https-only/test_resource_upgrade.html @@ -54,7 +54,7 @@ // returns after the server has received all the expected requests. var myXHR = new XMLHttpRequest(); myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult"); - myXHR.onload = function (e) { + myXHR.onload = function () { var results = myXHR.responseText.split(","); for (var index in results) { checkResult(results[index]); diff --git a/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js b/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js index ee350008aa..b103d83cd7 100644 --- a/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js +++ b/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js @@ -101,7 +101,7 @@ function shouldTriggerDownload(action = "save") { }); } -const CONSOLE_ERROR_MESSAGE = "Blocked downloading insecure content"; +const CONSOLE_ERROR_MESSAGE = "We blocked a download that’s not secure"; function shouldConsoleError() { // Waits until CONSOLE_ERROR_MESSAGE was logged diff --git a/dom/security/test/referrer-policy/browser.toml b/dom/security/test/referrer-policy/browser.toml index 325b6a3f49..a77046c85b 100644 --- a/dom/security/test/referrer-policy/browser.toml +++ b/dom/security/test/referrer-policy/browser.toml @@ -1,8 +1,8 @@ [DEFAULT] support-files = ["referrer_page.sjs"] -["browser_fragment_navigation.js"] -support-files = ["file_fragment_navigation.sjs"] +["browser_session_history.js"] +support-files = ["file_session_history.sjs"] ["browser_referrer_disallow_cross_site_relaxing.js"] diff --git a/dom/security/test/referrer-policy/browser_fragment_navigation.js b/dom/security/test/referrer-policy/browser_session_history.js index c3d5e62854..b480ce4ff0 100644 --- a/dom/security/test/referrer-policy/browser_fragment_navigation.js +++ b/dom/security/test/referrer-policy/browser_session_history.js @@ -4,7 +4,7 @@ "use strict"; const TEST_FILE = - "https://example.com/browser/dom/security/test/referrer-policy/file_fragment_navigation.sjs"; + "https://example.com/browser/dom/security/test/referrer-policy/file_session_history.sjs"; add_task(async function test_browser_navigation() { await BrowserTestUtils.withNewTab(TEST_FILE, async browser => { @@ -37,6 +37,21 @@ add_task(async function test_browser_navigation() { content.document.getElementById("ok"), "Page should load when checking referrer after fragment navigation and reload" ); + + info("Clicking on push_state button"); + content.document.getElementById("push_state").click(); + }); + + info("Reloading tab"); + loadPromise = BrowserTestUtils.browserLoaded(browser); + await BrowserTestUtils.reloadTab(gBrowser.selectedTab); + await loadPromise; + + await SpecialPowers.spawn(browser, [], () => { + ok( + content.document.getElementById("ok"), + "Page should load when checking referrer after history.pushState and reload" + ); }); }); }); diff --git a/dom/security/test/referrer-policy/file_fragment_navigation.sjs b/dom/security/test/referrer-policy/file_session_history.sjs index 5fb6f0d826..3d5f06b026 100644 --- a/dom/security/test/referrer-policy/file_fragment_navigation.sjs +++ b/dom/security/test/referrer-policy/file_session_history.sjs @@ -6,16 +6,22 @@ function handleRequest(request, response) { request.queryString === "check_referrer" && (!request.hasHeader("referer") || request.getHeader("referer") !== - "https://example.com/browser/dom/security/test/referrer-policy/file_fragment_navigation.sjs") + "https://example.com/browser/dom/security/test/referrer-policy/file_session_history.sjs") ) { response.setStatusLine(request.httpVersion, 400, "Bad Request"); response.write("Did not receive referrer"); } else { response.setHeader("Content-Type", "text/html"); response.write( - `<span id="ok">OK</span> -<a id="check_referrer" href="?check_referrer">check_referrer</a> -<a id="fragment" href="#fragment">fragment</a>` + `<span id="ok">OK</span> + <a id="check_referrer" href="?check_referrer">check_referrer</a> + <a id="fragment" href="#fragment">fragment</a> + <script> + function pushState(){ + history.pushState({}, "", location); + } + </script> + <button id="push_state" onclick="pushState();" >push_state</button>` ); } } diff --git a/dom/security/test/referrer-policy/referrer_helper.js b/dom/security/test/referrer-policy/referrer_helper.js index b892017eef..92593fa907 100644 --- a/dom/security/test/referrer-policy/referrer_helper.js +++ b/dom/security/test/referrer-policy/referrer_helper.js @@ -61,7 +61,7 @@ function checkIndividualResults(aTestname, aExpectedReferrer, aName) { ); advance(); }; - var onerror = xhr => { + var onerror = () => { ok(false, "Can't get results from the counter server."); SimpleTest.finish(); }; @@ -69,7 +69,7 @@ function checkIndividualResults(aTestname, aExpectedReferrer, aName) { } function resetState() { - doXHR(RESET_STATE, advance, function (xhr) { + doXHR(RESET_STATE, advance, function () { ok(false, "error in reset state"); SimpleTest.finish(); }); diff --git a/dom/security/test/referrer-policy/test_img_referrer.html b/dom/security/test/referrer-policy/test_img_referrer.html index fcc80929d2..5e3a2e6ddf 100644 --- a/dom/security/test/referrer-policy/test_img_referrer.html +++ b/dom/security/test/referrer-policy/test_img_referrer.html @@ -64,7 +64,7 @@ function checkIndividualResults(aTestname, aExpectedImg, aName) { advance(); }, - function(xhr) { + function() { ok(false, "Can't get results from the counter server."); SimpleTest.finish(); }); @@ -73,7 +73,7 @@ function checkIndividualResults(aTestname, aExpectedImg, aName) { function resetState() { doXHR('/tests/dom/security/test/referrer-policy/img_referrer_testserver.sjs?action=resetState', advance, - function(xhr) { + function() { ok(false, "error in reset state"); SimpleTest.finish(); }); diff --git a/dom/security/test/sec-fetch/browser_external_loads.js b/dom/security/test/sec-fetch/browser_external_loads.js index 0340b46899..070342e800 100644 --- a/dom/security/test/sec-fetch/browser_external_loads.js +++ b/dom/security/test/sec-fetch/browser_external_loads.js @@ -7,7 +7,7 @@ const TEST_PATH = getRootDirectory(gTestPath).replace( var gExpectedHeader = {}; -function checkSecFetchUser(subject, topic, data) { +function checkSecFetchUser(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com")) { return; @@ -45,7 +45,7 @@ add_task(async function external_load() { Services.obs.addObserver(checkSecFetchUser, "http-on-stop-request"); let headersChecked = new Promise(resolve => { - let reqStopped = async (subject, topic, data) => { + let reqStopped = async () => { Services.obs.removeObserver(reqStopped, "http-on-stop-request"); resolve(); }; diff --git a/dom/security/test/sec-fetch/browser_navigation.js b/dom/security/test/sec-fetch/browser_navigation.js index d203391356..2d51a7d1f5 100644 --- a/dom/security/test/sec-fetch/browser_navigation.js +++ b/dom/security/test/sec-fetch/browser_navigation.js @@ -10,7 +10,7 @@ async function setup() { waitForExplicitFinish(); } -function checkSecFetchUser(subject, topic, data) { +function checkSecFetchUser(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com/")) { return; diff --git a/dom/security/test/sec-fetch/test_iframe_history_manipulation.html b/dom/security/test/sec-fetch/test_iframe_history_manipulation.html index 5ec749bf4d..65d79fa530 100644 --- a/dom/security/test/sec-fetch/test_iframe_history_manipulation.html +++ b/dom/security/test/sec-fetch/test_iframe_history_manipulation.html @@ -18,7 +18,7 @@ let testFrame; var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); info("request observed: " + channel.URI.spec); if (!channel.URI.spec.startsWith("https://example.org")) { diff --git a/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html index 28eae80226..d05ae4df70 100644 --- a/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html +++ b/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html @@ -25,7 +25,7 @@ let testPassCounter = 0; var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { return; diff --git a/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html index adee5afe84..1a3fa85603 100644 --- a/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html +++ b/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html @@ -25,7 +25,7 @@ let testPassCounter = 0; var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { return; diff --git a/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html index b532baeb5e..1dd7f8864e 100644 --- a/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html +++ b/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html @@ -26,7 +26,7 @@ let testWindow; var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { return; diff --git a/dom/security/test/sec-fetch/test_trustworthy_loopback.html b/dom/security/test/sec-fetch/test_trustworthy_loopback.html index 95ecac17ed..3b44895e77 100644 --- a/dom/security/test/sec-fetch/test_trustworthy_loopback.html +++ b/dom/security/test/sec-fetch/test_trustworthy_loopback.html @@ -23,7 +23,7 @@ function checkTestsDone() { var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.includes("localhost") || channel.URI.spec.startsWith("http://localhost:9898/tests/dom/security/test/sec-fetch/file_trustworthy_loopback.html")) { diff --git a/dom/security/test/sec-fetch/test_websocket.html b/dom/security/test/sec-fetch/test_websocket.html index 5df0553a4f..4613419040 100644 --- a/dom/security/test/sec-fetch/test_websocket.html +++ b/dom/security/test/sec-fetch/test_websocket.html @@ -21,7 +21,7 @@ function checkTestsDone() { var script = SpecialPowers.loadChromeScript(() => { /* eslint-env mozilla/chrome-script */ - Services.obs.addObserver(function onExamResp(subject, topic, data) { + Services.obs.addObserver(function onExamResp(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_websocket")) { return; @@ -61,11 +61,11 @@ script.addMessageListener("test-end", () => { }); var wssSocket = new WebSocket("wss://example.com/tests/dom/security/test/sec-fetch/file_websocket"); -wssSocket.onopen = function(e) { +wssSocket.onopen = function() { ok(true, "sanity: wssSocket onopen"); checkTestsDone(); }; -wssSocket.onerror = function(e) { +wssSocket.onerror = function() { ok(false, "sanity: wssSocket onerror"); }; diff --git a/dom/security/test/unit/test_csp_reports.js b/dom/security/test/unit/test_csp_reports.js index 36da1a13e5..3d5a00b0f6 100644 --- a/dom/security/test/unit/test_csp_reports.js +++ b/dom/security/test/unit/test_csp_reports.js @@ -23,7 +23,7 @@ const REPORT_SERVER_URI = "http://localhost"; * or fails a test based on what it gets. */ function makeReportHandler(testpath, message, expectedJSON) { - return function (request, response) { + return function (request) { // we only like "POST" submissions for reports! if (request.method !== "POST") { do_throw("violation report should be a POST request"); diff --git a/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js index 26758d261d..1c5fdabf31 100644 --- a/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js +++ b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js @@ -45,11 +45,11 @@ var tests = [ function ChannelListener() {} ChannelListener.prototype = { - onStartRequest(request) {}, - onDataAvailable(request, stream, offset, count) { + onStartRequest() {}, + onDataAvailable() { do_throw("Should not get any data!"); }, - onStopRequest(request, status) { + onStopRequest(request) { var upgrade_insecure_header = false; try { if (request.getRequestHeader("Upgrade-Insecure-Requests")) { @@ -76,7 +76,7 @@ function setupChannel(aContentType) { return chan; } -function serverHandler(metadata, response) { +function serverHandler() { // no need to perform anything here } diff --git a/dom/security/test/unit/test_https_only_https_first_default_port.js b/dom/security/test/unit/test_https_only_https_first_default_port.js index bd4d6717eb..06ffb80eee 100644 --- a/dom/security/test/unit/test_https_only_https_first_default_port.js +++ b/dom/security/test/unit/test_https_only_https_first_default_port.js @@ -41,13 +41,13 @@ const TESTS = [ function ChannelListener() {} ChannelListener.prototype = { - onStartRequest(request) { + onStartRequest() { // dummy implementation }, - onDataAvailable(request, stream, offset, count) { + onDataAvailable() { do_throw("Should not get any data!"); }, - onStopRequest(request, status) { + onStopRequest(request) { var chan = request.QueryInterface(Ci.nsIChannel); let requestURL = chan.URI; Assert.equal( @@ -79,7 +79,7 @@ function setUpChannel() { return chan; } -function serverHandler(metadata, response) { +function serverHandler() { // dummy implementation } diff --git a/dom/security/test/unit/test_https_only_https_first_prefs.js b/dom/security/test/unit/test_https_only_https_first_prefs.js index 9c6ced1fcb..6c7e112d9b 100644 --- a/dom/security/test/unit/test_https_only_https_first_prefs.js +++ b/dom/security/test/unit/test_https_only_https_first_prefs.js @@ -272,10 +272,10 @@ ChannelListener.prototype = { var authHeader = httpChan.getRequestHeader("Authorization"); Assert.equal(authHeader, "Basic user:pass", curTest.description); }, - onDataAvailable(request, stream, offset, count) { + onDataAvailable() { do_throw("Should not get any data!"); }, - onStopRequest(request, status) { + onStopRequest(request) { var chan = request.QueryInterface(Ci.nsIChannel); let requestURL = chan.URI; Assert.equal( @@ -331,7 +331,7 @@ function setUpChannel() { return chan; } -function serverHandler(metadata, response) { +function serverHandler() { // dummy implementation } diff --git a/dom/security/trusted-types/TrustedHTML.cpp b/dom/security/trusted-types/TrustedHTML.cpp new file mode 100644 index 0000000000..005cab8c63 --- /dev/null +++ b/dom/security/trusted-types/TrustedHTML.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TrustedHTML.h" + +namespace mozilla::dom { + +IMPL_TRUSTED_TYPE_CLASS(TrustedHTML) + +} // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedHTML.h b/dom/security/trusted-types/TrustedHTML.h new file mode 100644 index 0000000000..68a642c83a --- /dev/null +++ b/dom/security/trusted-types/TrustedHTML.h @@ -0,0 +1,19 @@ +/* -*- 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 DOM_SECURITY_TRUSTED_TYPES_TRUSTEDHTML_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDHTML_H_ + +#include "mozilla/dom/TrustedTypeUtils.h" + +namespace mozilla::dom { + +// https://w3c.github.io/trusted-types/dist/spec/#trusted-html +DECL_TRUSTED_TYPE_CLASS(TrustedHTML) + +} // namespace mozilla::dom + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDHTML_H_ diff --git a/dom/security/trusted-types/TrustedScript.cpp b/dom/security/trusted-types/TrustedScript.cpp new file mode 100644 index 0000000000..496c53278a --- /dev/null +++ b/dom/security/trusted-types/TrustedScript.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TrustedScript.h" + +namespace mozilla::dom { + +IMPL_TRUSTED_TYPE_CLASS(TrustedScript) + +} // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedScript.h b/dom/security/trusted-types/TrustedScript.h new file mode 100644 index 0000000000..0b6e0965ed --- /dev/null +++ b/dom/security/trusted-types/TrustedScript.h @@ -0,0 +1,19 @@ +/* -*- 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 DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPT_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPT_H_ + +#include "mozilla/dom/TrustedTypeUtils.h" + +namespace mozilla::dom { + +// https://w3c.github.io/trusted-types/dist/spec/#trusted-script +DECL_TRUSTED_TYPE_CLASS(TrustedScript) + +} // namespace mozilla::dom + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPT_H_ diff --git a/dom/security/trusted-types/TrustedScriptURL.cpp b/dom/security/trusted-types/TrustedScriptURL.cpp new file mode 100644 index 0000000000..8de2448f91 --- /dev/null +++ b/dom/security/trusted-types/TrustedScriptURL.cpp @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TrustedScriptURL.h" + +namespace mozilla::dom { + +// https://w3c.github.io/trusted-types/dist/spec/#trused-script-url +IMPL_TRUSTED_TYPE_CLASS(TrustedScriptURL) + +} // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedScriptURL.h b/dom/security/trusted-types/TrustedScriptURL.h new file mode 100644 index 0000000000..6e71235cb9 --- /dev/null +++ b/dom/security/trusted-types/TrustedScriptURL.h @@ -0,0 +1,18 @@ +/* -*- 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 DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPTURL_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPTURL_H_ + +#include "mozilla/dom/TrustedTypeUtils.h" + +namespace mozilla::dom { + +DECL_TRUSTED_TYPE_CLASS(TrustedScriptURL) + +} // namespace mozilla::dom + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDSCRIPTURL_H_ diff --git a/dom/security/trusted-types/TrustedTypePolicy.cpp b/dom/security/trusted-types/TrustedTypePolicy.cpp new file mode 100644 index 0000000000..3c4e758ed0 --- /dev/null +++ b/dom/security/trusted-types/TrustedTypePolicy.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TrustedTypePolicy.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/TrustedTypePolicyFactory.h" +#include "mozilla/dom/TrustedTypesBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TrustedTypePolicy, mParentObject) + +JSObject* TrustedTypePolicy::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return TrustedTypePolicy_Binding::Wrap(aCx, this, aGivenProto); +} + +UniquePtr<TrustedHTML> TrustedTypePolicy::CreateHTML( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const { + // TODO: implement the spec. + return MakeUnique<TrustedHTML>(); +} + +UniquePtr<TrustedScript> TrustedTypePolicy::CreateScript( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const { + // TODO: implement the spec. + return MakeUnique<TrustedScript>(); +} + +UniquePtr<TrustedScriptURL> TrustedTypePolicy::CreateScriptURL( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const { + // TODO: implement the spec. + return MakeUnique<TrustedScriptURL>(); +} + +} // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypePolicy.h b/dom/security/trusted-types/TrustedTypePolicy.h new file mode 100644 index 0000000000..22d99947b3 --- /dev/null +++ b/dom/security/trusted-types/TrustedTypePolicy.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICY_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICY_H_ + +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/TrustedHTML.h" +#include "mozilla/dom/TrustedScript.h" +#include "mozilla/dom/TrustedScriptURL.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" +#include "nsWrapperCache.h" + +namespace mozilla::dom { + +class TrustedTypePolicyFactory; + +// https://w3c.github.io/trusted-types/dist/spec/#trusted-type-policy +class TrustedTypePolicy : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TrustedTypePolicy) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TrustedTypePolicy) + + explicit TrustedTypePolicy(TrustedTypePolicyFactory* aParentObject) + : mParentObject{aParentObject} {} + + // Required for Web IDL binding. + TrustedTypePolicyFactory* GetParentObject() const { return mParentObject; } + + // Required for Web IDL binding. + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // https://w3c.github.io/trusted-types/dist/spec/#trustedtypepolicy-name + void GetName(DOMString& aResult) const { + // TODO: impl. + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createhtml + UniquePtr<TrustedHTML> CreateHTML( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const; + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createscript + UniquePtr<TrustedScript> CreateScript( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const; + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicy-createscripturl + UniquePtr<TrustedScriptURL> CreateScriptURL( + JSContext* aJSContext, const nsAString& aInput, + const Sequence<JS::Value>& aArguments) const; + + private: + // Required because this class is ref-counted. + virtual ~TrustedTypePolicy() = default; + + RefPtr<TrustedTypePolicyFactory> mParentObject; +}; + +} // namespace mozilla::dom + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICY_H_ diff --git a/dom/security/trusted-types/TrustedTypePolicyFactory.cpp b/dom/security/trusted-types/TrustedTypePolicyFactory.cpp new file mode 100644 index 0000000000..448c51eb3b --- /dev/null +++ b/dom/security/trusted-types/TrustedTypePolicyFactory.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TrustedTypePolicyFactory.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/TrustedTypePolicy.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TrustedTypePolicyFactory, mGlobalObject) + +JSObject* TrustedTypePolicyFactory::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return TrustedTypePolicyFactory_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<TrustedTypePolicy> TrustedTypePolicyFactory::CreatePolicy( + const nsAString& aPolicyName, + const TrustedTypePolicyOptions& aPolicyOptions) { + // TODO: implement the spec. + return MakeRefPtr<TrustedTypePolicy>(this).forget(); +} + +UniquePtr<TrustedHTML> TrustedTypePolicyFactory::EmptyHTML() { + // Preserving the wrapper ensures: + // ``` + // const e = trustedTypes.emptyHTML; + // e === trustedTypes.emptyHTML; + // ``` + // which comes with the cost of keeping the factory, one per global, alive. + // An additional benefit is it saves the cost of re-instantiating potentially + // multiple emptyHML objects. Both, the JS- and the C++-objects. + dom::PreserveWrapper(this); + + return MakeUnique<TrustedHTML>(); +} + +UniquePtr<TrustedScript> TrustedTypePolicyFactory::EmptyScript() { + // See the explanation in `EmptyHTML()`. + dom::PreserveWrapper(this); + + return MakeUnique<TrustedScript>(); +} + +} // namespace mozilla::dom diff --git a/dom/security/trusted-types/TrustedTypePolicyFactory.h b/dom/security/trusted-types/TrustedTypePolicyFactory.h new file mode 100644 index 0000000000..fea5312cf8 --- /dev/null +++ b/dom/security/trusted-types/TrustedTypePolicyFactory.h @@ -0,0 +1,105 @@ +/* -*- 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 DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICYFACTORY_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICYFACTORY_H_ + +#include "js/TypeDecls.h" +#include "mozilla/dom/TrustedHTML.h" +#include "mozilla/dom/TrustedScript.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsIGlobalObject.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" +#include "nsWrapperCache.h" + +template <typename T> +struct already_AddRefed; + +class DOMString; + +namespace mozilla::dom { + +class TrustedTypePolicy; + +// https://w3c.github.io/trusted-types/dist/spec/#trusted-type-policy-factory +class TrustedTypePolicyFactory : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TrustedTypePolicyFactory) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TrustedTypePolicyFactory) + + explicit TrustedTypePolicyFactory(nsIGlobalObject* aGlobalObject) + : mGlobalObject{aGlobalObject} {} + + // Required for Web IDL binding. + nsIGlobalObject* GetParentObject() const { return mGlobalObject; } + + // Required for Web IDL binding. + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-createpolicy + already_AddRefed<TrustedTypePolicy> CreatePolicy( + const nsAString& aPolicyName, + const TrustedTypePolicyOptions& aPolicyOptions); + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-ishtml + bool IsHTML(JSContext* aJSContext, + const JS::Handle<JS::Value>& aValue) const { + // TODO: impl. + return false; + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-isscript + bool IsScript(JSContext* aJSContext, + const JS::Handle<JS::Value>& aValue) const { + // TODO: impl. + return false; + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-isscripturl + bool IsScriptURL(JSContext* aJSContext, + const JS::Handle<JS::Value>& aValue) const { + // TODO: impl. + return false; + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-emptyhtml + UniquePtr<TrustedHTML> EmptyHTML(); + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-emptyscript + UniquePtr<TrustedScript> EmptyScript(); + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-getattributetype + void GetAttributeType(const nsAString& aTagName, const nsAString& aAttribute, + const nsAString& aElementNs, const nsAString& aAttrNs, + DOMString& aResult) { + // TODO: impl. + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-getpropertytype + void GetPropertyType(const nsAString& aTagName, const nsAString& aProperty, + const nsAString& aElementNs, DOMString& aResult) { + // TODO: impl + } + + // https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-defaultpolicy + TrustedTypePolicy* GetDefaultPolicy() const { + // TODO: impl + return nullptr; + } + + private: + // Required because this class is ref-counted. + virtual ~TrustedTypePolicyFactory() = default; + + RefPtr<nsIGlobalObject> mGlobalObject; +}; + +} // namespace mozilla::dom + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEPOLICYFACTORY_H_ diff --git a/dom/security/trusted-types/TrustedTypeUtils.h b/dom/security/trusted-types/TrustedTypeUtils.h new file mode 100644 index 0000000000..90ffc50c38 --- /dev/null +++ b/dom/security/trusted-types/TrustedTypeUtils.h @@ -0,0 +1,35 @@ +/* -*- 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 DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEUTILS_H_ +#define DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEUTILS_H_ + +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "mozilla/dom/TrustedTypesBinding.h" +#include "nsStringFwd.h" + +#define DECL_TRUSTED_TYPE_CLASS(_class) \ + class _class : public mozilla::dom::NonRefcountedDOMObject { \ + public: \ + /* Required for Web IDL binding. */ \ + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, \ + JS::MutableHandle<JSObject*> aObject); \ + \ + void Stringify(nsAString& aResult) const { /* TODO: impl. */ \ + } \ + \ + void ToJSON(DOMString& aResult) const { /* TODO: impl. */ \ + } \ + }; + +#define IMPL_TRUSTED_TYPE_CLASS(_class) \ + bool _class::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, \ + JS::MutableHandle<JSObject*> aObject) { \ + return _class##_Binding::Wrap(aCx, this, aGivenProto, aObject); \ + } + +#endif // DOM_SECURITY_TRUSTED_TYPES_TRUSTEDTYPEUTILS_H_ diff --git a/dom/security/trusted-types/moz.build b/dom/security/trusted-types/moz.build new file mode 100644 index 0000000000..159a54ff02 --- /dev/null +++ b/dom/security/trusted-types/moz.build @@ -0,0 +1,27 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Security") + +EXPORTS.mozilla.dom += [ + "TrustedHTML.h", + "TrustedScript.h", + "TrustedScriptURL.h", + "TrustedTypePolicy.h", + "TrustedTypePolicyFactory.h", + "TrustedTypeUtils.h", +] + +UNIFIED_SOURCES += [ + "TrustedHTML.cpp", + "TrustedScript.cpp", + "TrustedScriptURL.cpp", + "TrustedTypePolicy.cpp", + "TrustedTypePolicyFactory.cpp", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/serializers/nsDOMSerializer.cpp b/dom/serializers/nsDOMSerializer.cpp index 98d92e64ce..468ea7a5e1 100644 --- a/dom/serializers/nsDOMSerializer.cpp +++ b/dom/serializers/nsDOMSerializer.cpp @@ -10,7 +10,6 @@ #include "mozilla/dom/Document.h" #include "nsIDocumentEncoder.h" #include "nsComponentManagerUtils.h" -#include "nsContentCID.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsINode.h" diff --git a/dom/serviceworkers/ServiceWorkerEvents.cpp b/dom/serviceworkers/ServiceWorkerEvents.cpp index 531e29e905..6027625021 100644 --- a/dom/serviceworkers/ServiceWorkerEvents.cpp +++ b/dom/serviceworkers/ServiceWorkerEvents.cpp @@ -624,8 +624,7 @@ void RespondWithHandler::ResolvedCallback(JSContext* aCx, if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { - NS_ConvertASCIItoUTF16 modeString( - RequestModeValues::GetString(mRequestMode)); + NS_ConvertASCIItoUTF16 modeString(GetEnumString(mRequestMode)); autoCancel.SetCancelMessage("BadOpaqueInterceptionRequestModeWithURL"_ns, mRequestURL, modeString); diff --git a/dom/serviceworkers/ServiceWorkerIPCUtils.h b/dom/serviceworkers/ServiceWorkerIPCUtils.h index bd867f877a..9931af94e7 100644 --- a/dom/serviceworkers/ServiceWorkerIPCUtils.h +++ b/dom/serviceworkers/ServiceWorkerIPCUtils.h @@ -11,6 +11,7 @@ // Undo X11/X.h's definition of None #undef None +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/ServiceWorkerBinding.h" #include "mozilla/dom/ServiceWorkerRegistrationBinding.h" @@ -18,17 +19,13 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::ServiceWorkerState> - : public ContiguousEnumSerializer< - mozilla::dom::ServiceWorkerState, - mozilla::dom::ServiceWorkerState::Parsed, - mozilla::dom::ServiceWorkerState::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::ServiceWorkerState> {}; template <> struct ParamTraits<mozilla::dom::ServiceWorkerUpdateViaCache> - : public ContiguousEnumSerializer< - mozilla::dom::ServiceWorkerUpdateViaCache, - mozilla::dom::ServiceWorkerUpdateViaCache::Imports, - mozilla::dom::ServiceWorkerUpdateViaCache::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::ServiceWorkerUpdateViaCache> {}; } // namespace IPC diff --git a/dom/serviceworkers/ServiceWorkerInfo.cpp b/dom/serviceworkers/ServiceWorkerInfo.cpp index 9998cfed6b..2689af6ee9 100644 --- a/dom/serviceworkers/ServiceWorkerInfo.cpp +++ b/dom/serviceworkers/ServiceWorkerInfo.cpp @@ -43,7 +43,7 @@ static_assert(nsIServiceWorkerInfo::STATE_REDUNDANT == "ServiceWorkerState enumeration value should match state values " "from nsIServiceWorkerInfo."); static_assert(nsIServiceWorkerInfo::STATE_UNKNOWN == - ServiceWorkerStateValues::Count, + ContiguousEnumSize<ServiceWorkerState>::value, "ServiceWorkerState enumeration value should match state values " "from nsIServiceWorkerInfo."); @@ -165,8 +165,6 @@ void ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) { // Any state can directly transition to redundant, but everything else is // ordered. if (aState != ServiceWorkerState::Redundant) { - MOZ_ASSERT_IF(State() == ServiceWorkerState::EndGuard_, - aState == ServiceWorkerState::Installing); MOZ_ASSERT_IF(State() == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed); MOZ_ASSERT_IF(State() == ServiceWorkerState::Installed, diff --git a/dom/serviceworkers/ServiceWorkerManager.cpp b/dom/serviceworkers/ServiceWorkerManager.cpp index b0dcfca893..842939d785 100644 --- a/dom/serviceworkers/ServiceWorkerManager.cpp +++ b/dom/serviceworkers/ServiceWorkerManager.cpp @@ -127,7 +127,7 @@ static_assert( static_cast<uint32_t>(RequestRedirect::Manual), "RequestRedirect enumeration value should make Necko Redirect mode value."); static_assert( - 3 == RequestRedirectValues::Count, + 3 == ContiguousEnumSize<RequestRedirect>::value, "RequestRedirect enumeration value should make Necko Redirect mode value."); static_assert( @@ -155,7 +155,7 @@ static_assert( static_cast<uint32_t>(RequestCache::Only_if_cached), "RequestCache enumeration value should match Necko Cache mode value."); static_assert( - 6 == RequestCacheValues::Count, + 6 == ContiguousEnumSize<RequestCache>::value, "RequestCache enumeration value should match Necko Cache mode value."); static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) == diff --git a/dom/serviceworkers/ServiceWorkerManagerParent.cpp b/dom/serviceworkers/ServiceWorkerManagerParent.cpp index 5ed0f4faa8..d2d0a64275 100644 --- a/dom/serviceworkers/ServiceWorkerManagerParent.cpp +++ b/dom/serviceworkers/ServiceWorkerManagerParent.cpp @@ -91,9 +91,9 @@ mozilla::ipc::IPCResult ServiceWorkerManagerParent::RecvPropagateUnregister( // one and only ServiceWorkerManager, but it is necessary for us to have run // the unregister call above because until Bug 1183245 is fixed, // nsIServiceWorkerManager.propagateUnregister() is a de facto API for - // clearing ServiceWorker registrations by Sanitizer.jsm via - // ServiceWorkerCleanUp.jsm, as well as devtools "unregister" affordance and - // the no-longer-relevant about:serviceworkers UI. + // clearing ServiceWorker registrations by Sanitizer.sys.mjs via + // ServiceWorkerCleanUp.sys.mjs, as well as devtools "unregister" affordance + // and the no-longer-relevant about:serviceworkers UI. return IPC_OK(); } diff --git a/dom/serviceworkers/ServiceWorkerOp.cpp b/dom/serviceworkers/ServiceWorkerOp.cpp index 8811404a0f..9c4fc569d1 100644 --- a/dom/serviceworkers/ServiceWorkerOp.cpp +++ b/dom/serviceworkers/ServiceWorkerOp.cpp @@ -1428,8 +1428,7 @@ void FetchEventOp::ResolvedCallback(JSContext* aCx, if (response->Type() == ResponseType::Opaque && requestMode != RequestMode::No_cors) { - NS_ConvertASCIItoUTF16 modeString( - RequestModeValues::GetString(requestMode)); + NS_ConvertASCIItoUTF16 modeString(GetEnumString(requestMode)); nsAutoString requestURL; GetRequestURL(requestURL); diff --git a/dom/serviceworkers/test/browser_download.js b/dom/serviceworkers/test/browser_download.js index 0c69a48d17..70622a2349 100644 --- a/dom/serviceworkers/test/browser_download.js +++ b/dom/serviceworkers/test/browser_download.js @@ -3,7 +3,7 @@ var gTestRoot = getRootDirectory(gTestPath).replace( "chrome://mochitests/content/", - "http://mochi.test:8888/" + "https://example.com/" ); function getFile(aFilename) { diff --git a/dom/serviceworkers/test/close_test.js b/dom/serviceworkers/test/close_test.js index 07f85617ef..0609782e5e 100644 --- a/dom/serviceworkers/test/close_test.js +++ b/dom/serviceworkers/test/close_test.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(v, msg) { client.postMessage({ status: "ok", result: !!v, message: msg }); } diff --git a/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js index c2e5d416e7..31a8165625 100644 --- a/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js +++ b/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // Cross origin request var prefix = "http://example.com/tests/dom/serviceworkers/test/eventsource/"; diff --git a/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js index 9cb8d2d61f..9cfca63db9 100644 --- a/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js +++ b/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + var prefix = "http://example.com/tests/dom/serviceworkers/test/eventsource/"; self.importScripts("eventsource_worker_helper.js"); diff --git a/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js index 5c8c75a161..84d406ac47 100644 --- a/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js +++ b/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // Cross origin request var prefix = "http://example.com/tests/dom/serviceworkers/test/eventsource/"; diff --git a/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js index 72780e2979..ec3f0309c2 100644 --- a/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js +++ b/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + self.importScripts("eventsource_worker_helper.js"); self.addEventListener("fetch", function (event) { diff --git a/dom/serviceworkers/test/gtest/TestReadWrite.cpp b/dom/serviceworkers/test/gtest/TestReadWrite.cpp index 823647d22e..66b966f9e2 100644 --- a/dom/serviceworkers/test/gtest/TestReadWrite.cpp +++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp @@ -219,7 +219,7 @@ TEST(ServiceWorkerRegistrar, TestReadData) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -280,30 +280,24 @@ TEST(ServiceWorkerRegistrar, TestWriteData) { RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest; - for (int i = 0; i < 2; ++i) { - ServiceWorkerRegistrationData reg; - - reg.scope() = nsPrintfCString("https://scope_write_%d.org", i); - reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i); - reg.currentWorkerHandlesFetch() = true; - reg.cacheName() = - NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i)); - reg.updateViaCache() = - nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; + ServiceWorkerRegistrationData reg; - reg.currentWorkerInstalledTime() = PR_Now(); - reg.currentWorkerActivatedTime() = PR_Now(); - reg.lastUpdateTime() = PR_Now(); + reg.scope() = "https://scope_write_0.org"_ns; + reg.currentWorkerURL() = "currentWorkerURL write 0"_ns; + reg.currentWorkerHandlesFetch() = true; + reg.cacheName() = u"cacheName write 0"_ns; + reg.updateViaCache() = + nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; - nsAutoCString spec; - spec.AppendPrintf("spec write %d", i); + reg.currentWorkerInstalledTime() = PR_Now(); + reg.currentWorkerActivatedTime() = PR_Now(); + reg.lastUpdateTime() = PR_Now(); - reg.principal() = mozilla::ipc::ContentPrincipalInfo( - mozilla::OriginAttributes(i % 2), spec, spec, mozilla::Nothing(), - spec); + const auto spec = "spec write 0"_ns; + reg.principal() = mozilla::ipc::ContentPrincipalInfo( + mozilla::OriginAttributes(), spec, spec, mozilla::Nothing(), spec); - swr->TestRegisterServiceWorker(reg); - } + swr->TestRegisterServiceWorker(reg); nsresult rv = swr->TestWriteData(); ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail"; @@ -314,47 +308,37 @@ TEST(ServiceWorkerRegistrar, TestWriteData) nsresult rv = swr->TestReadData(); ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail"; - const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData(); - ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found"; - - for (int i = 0; i < 2; ++i) { - nsAutoCString test; - - ASSERT_EQ(data[i].principal().type(), - mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); - const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal(); + const nsTArray<ServiceWorkerRegistrationData>& dataArr = swr->TestGetData(); + ASSERT_EQ((uint32_t)1, dataArr.Length()) << "1 entries should be found"; - mozilla::OriginAttributes attrs(i % 2); - nsAutoCString suffix, expectSuffix; - attrs.CreateSuffix(expectSuffix); - cInfo.attrs().CreateSuffix(suffix); + const auto& data = dataArr[0]; - ASSERT_STREQ(expectSuffix.get(), suffix.get()); + ASSERT_EQ(data.principal().type(), + mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); + const mozilla::ipc::ContentPrincipalInfo& cInfo = data.principal(); - test.AppendPrintf("https://scope_write_%d.org", i); - ASSERT_STREQ(test.get(), cInfo.spec().get()); + mozilla::OriginAttributes attrs; + nsAutoCString suffix, expectSuffix; + attrs.CreateSuffix(expectSuffix); + cInfo.attrs().CreateSuffix(suffix); - test.Truncate(); - test.AppendPrintf("https://scope_write_%d.org", i); - ASSERT_STREQ(test.get(), data[i].scope().get()); + ASSERT_STREQ(expectSuffix.get(), suffix.get()); - test.Truncate(); - test.AppendPrintf("currentWorkerURL write %d", i); - ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get()); + ASSERT_STREQ("https://scope_write_0.org", cInfo.spec().get()); + ASSERT_STREQ("https://scope_write_0.org", data.scope().get()); + ASSERT_STREQ("currentWorkerURL write 0", data.currentWorkerURL().get()); - ASSERT_EQ(true, data[i].currentWorkerHandlesFetch()); + ASSERT_EQ(true, data.currentWorkerHandlesFetch()); - test.Truncate(); - test.AppendPrintf("cacheName write %d", i); - ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get()); + ASSERT_STREQ("cacheName write 0", + NS_ConvertUTF16toUTF8(data.cacheName()).get()); - ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS, - data[i].updateViaCache()); + ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS, + data.updateViaCache()); - ASSERT_NE((int64_t)0, data[i].currentWorkerInstalledTime()); - ASSERT_NE((int64_t)0, data[i].currentWorkerActivatedTime()); - ASSERT_NE((int64_t)0, data[i].lastUpdateTime()); - } + ASSERT_NE((int64_t)0, data.currentWorkerInstalledTime()); + ASSERT_NE((int64_t)0, data.currentWorkerActivatedTime()); + ASSERT_NE((int64_t)0, data.lastUpdateTime()); } TEST(ServiceWorkerRegistrar, TestVersion2Migration) @@ -394,7 +378,7 @@ TEST(ServiceWorkerRegistrar, TestVersion2Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -464,7 +448,7 @@ TEST(ServiceWorkerRegistrar, TestVersion3Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -532,7 +516,7 @@ TEST(ServiceWorkerRegistrar, TestVersion4Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -604,7 +588,7 @@ TEST(ServiceWorkerRegistrar, TestVersion5Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -678,7 +662,7 @@ TEST(ServiceWorkerRegistrar, TestVersion6Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -765,7 +749,7 @@ TEST(ServiceWorkerRegistrar, TestVersion7Migration) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -850,7 +834,7 @@ TEST(ServiceWorkerRegistrar, TestDedupeRead) nsAutoCString suffix0; cInfo0.attrs().CreateSuffix(suffix0); - ASSERT_STREQ("^inBrowser=1", suffix0.get()); + ASSERT_STREQ("", suffix0.get()); ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get()); ASSERT_STREQ("https://scope_0.org", data[0].scope().get()); ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get()); @@ -903,8 +887,7 @@ TEST(ServiceWorkerRegistrar, TestDedupeWrite) spec.AppendPrintf("spec write dedupe/%d", i); reg.principal() = mozilla::ipc::ContentPrincipalInfo( - mozilla::OriginAttributes(false), spec, spec, mozilla::Nothing(), - spec); + mozilla::OriginAttributes(), spec, spec, mozilla::Nothing(), spec); swr->TestRegisterServiceWorker(reg); } @@ -926,7 +909,7 @@ TEST(ServiceWorkerRegistrar, TestDedupeWrite) mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); const mozilla::ipc::ContentPrincipalInfo& cInfo = data[0].principal(); - mozilla::OriginAttributes attrs(false); + mozilla::OriginAttributes attrs; nsAutoCString suffix, expectSuffix; attrs.CreateSuffix(expectSuffix); cInfo.attrs().CreateSuffix(suffix); diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.js b/dom/serviceworkers/test/test_serviceworker_interfaces.js index 1cf0896edf..c13c228d71 100644 --- a/dom/serviceworkers/test/test_serviceworker_interfaces.js +++ b/dom/serviceworkers/test/test_serviceworker_interfaces.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // This is a list of all interfaces that are exposed to workers. // Please only add things to this list with great care and proper review // from the associated module peers. @@ -173,8 +175,6 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! "DOMRectReadOnly", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "DOMRequest", disabled: true }, - // IMPORTANT: Do not change this list without review from a DOM peer! "DOMStringList", // IMPORTANT: Do not change this list without review from a DOM peer! "ErrorEvent", diff --git a/dom/smil/SMILAnimationController.cpp b/dom/smil/SMILAnimationController.cpp index 2c103b0f16..99abc07b58 100644 --- a/dom/smil/SMILAnimationController.cpp +++ b/dom/smil/SMILAnimationController.cpp @@ -51,8 +51,8 @@ SMILAnimationController::~SMILAnimationController() { NS_ASSERTION(mAnimationElementTable.IsEmpty(), "Animation controller shouldn't be tracking any animation" " elements when it dies"); - NS_ASSERTION(!mRegisteredWithRefreshDriver, - "Leaving stale entry in refresh driver's observer list"); + MOZ_RELEASE_ASSERT(!mRegisteredWithRefreshDriver, + "Leaving stale entry in refresh driver's observer list"); } void SMILAnimationController::Disconnect() { diff --git a/dom/storage/SessionStorageManager.cpp b/dom/storage/SessionStorageManager.cpp index 020083730a..a64d578426 100644 --- a/dom/storage/SessionStorageManager.cpp +++ b/dom/storage/SessionStorageManager.cpp @@ -26,6 +26,7 @@ #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundChild.h" +#include "nsIXULRuntime.h" #include "nsTHashMap.h" #include "nsThreadUtils.h" @@ -918,7 +919,7 @@ void BackgroundSessionStorageManager::SetCurrentBrowsingContextId( } void BackgroundSessionStorageManager::MaybeScheduleSessionStoreUpdate() { - if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + if (!SessionStorePlatformCollection()) { return; } diff --git a/dom/storage/StorageDBUpdater.cpp b/dom/storage/StorageDBUpdater.cpp index 7dec6425e2..255deddb4d 100644 --- a/dom/storage/StorageDBUpdater.cpp +++ b/dom/storage/StorageDBUpdater.cpp @@ -15,6 +15,7 @@ #include "mozilla/Tokenizer.h" #include "mozIStorageConnection.h" #include "mozStorageHelper.h" +#include "mozilla/StorageOriginAttributes.h" // Current version of the database schema #define CURRENT_SCHEMA_VERSION 2 @@ -131,8 +132,8 @@ class ExtractOriginData : protected mozilla::Tokenizer { } } } else { - OriginAttributes attrs(inIsolatedMozBrowser); - attrs.CreateSuffix(suffix); + StorageOriginAttributes originAttributes(inIsolatedMozBrowser); + originAttributes.CreateSuffix(suffix); } // Consume the rest of the input as "origin". diff --git a/dom/storage/StorageUtils.cpp b/dom/storage/StorageUtils.cpp index 39a69c3f08..cb7a74c73f 100644 --- a/dom/storage/StorageUtils.cpp +++ b/dom/storage/StorageUtils.cpp @@ -13,6 +13,7 @@ #include "nsIURI.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" +#include "mozilla/StorageOriginAttributes.h" namespace mozilla::dom::StorageUtils { @@ -61,16 +62,16 @@ nsCString Scheme0Scope(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) { nsCString result; - OriginAttributes oa; + StorageOriginAttributes oa; if (!aOriginSuffix.IsEmpty()) { DebugOnly<bool> success = oa.PopulateFromSuffix(aOriginSuffix); MOZ_ASSERT(success); } - if (oa.mInIsolatedMozBrowser) { + if (oa.InIsolatedMozBrowser()) { result.AppendInt(0); // This is the appId to be removed. result.Append(':'); - result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f'); + result.Append(oa.InIsolatedMozBrowser() ? 't' : 'f'); result.Append(':'); } @@ -80,7 +81,7 @@ nsCString Scheme0Scope(const nsACString& aOriginSuffix, // with originAttributes and originKey columns) so that switch between // schema 1 and 0 always works in both ways. nsAutoCString remaining; - oa.mInIsolatedMozBrowser = false; + oa.SetInIsolatedMozBrowser(false); oa.CreateSuffix(remaining); if (!remaining.IsEmpty()) { MOZ_ASSERT(!aOriginSuffix.IsEmpty()); diff --git a/dom/streams/test/xpcshell/bug-1387503-1.js b/dom/streams/test/xpcshell/bug-1387503-1.js index 777f614c44..0be334e230 100644 --- a/dom/streams/test/xpcshell/bug-1387503-1.js +++ b/dom/streams/test/xpcshell/bug-1387503-1.js @@ -18,8 +18,8 @@ add_task(async function test() { async function fn() { try { let stream = new ReadableStream({ - start(controller) {}, - pull(controller) { + start() {}, + pull() { // eslint-disable-next-line no-debugger debugger; }, diff --git a/dom/streams/test/xpcshell/bug-1773237.js b/dom/streams/test/xpcshell/bug-1773237.js index 0d0107fe85..2ce8d33439 100644 --- a/dom/streams/test/xpcshell/bug-1773237.js +++ b/dom/streams/test/xpcshell/bug-1773237.js @@ -7,5 +7,5 @@ var stream = new ReadableStream({ var response = new Response(stream); var text = response.text().then( () => {}, - e => {} + () => {} ); diff --git a/dom/streams/test/xpcshell/dom_stream_prototype_test.js b/dom/streams/test/xpcshell/dom_stream_prototype_test.js index b127368318..e3c59c675f 100644 --- a/dom/streams/test/xpcshell/dom_stream_prototype_test.js +++ b/dom/streams/test/xpcshell/dom_stream_prototype_test.js @@ -2,7 +2,7 @@ var log = []; const stream = new ReadableStream({ - start(controller) { + start() { log.push("started"); }, pull(controller) { diff --git a/dom/streams/test/xpcshell/response.js b/dom/streams/test/xpcshell/response.js index ead1be527f..884cbfea29 100644 --- a/dom/streams/test/xpcshell/response.js +++ b/dom/streams/test/xpcshell/response.js @@ -1,10 +1,10 @@ "use strict"; -add_task(async function (test) { +add_task(async function () { return new Response(new Blob([], { type: "text/plain" })).body.cancel(); }); -add_task(function (test) { +add_task(function () { var response = new Response( new Blob(["This is data"], { type: "text/plain" }) ); @@ -13,14 +13,14 @@ add_task(function (test) { return reader.cancel(); }); -add_task(function (test) { +add_task(function () { var response = new Response(new Blob(["T"], { type: "text/plain" })); var reader = response.body.getReader(); var closedPromise = reader.closed.then(function () { return reader.cancel(); }); - reader.read().then(function readMore({ done, value }) { + reader.read().then(function readMore({ done }) { if (!done) { return reader.read().then(readMore); } diff --git a/dom/streams/test/xpcshell/subclassing.js b/dom/streams/test/xpcshell/subclassing.js index b2b86cf353..8b6192b840 100644 --- a/dom/streams/test/xpcshell/subclassing.js +++ b/dom/streams/test/xpcshell/subclassing.js @@ -19,7 +19,7 @@ add_task(function subclass_helper() { // The base class constructor is called. let stream = new PartyStreamer({ // (The ReadableStream constructor calls this start method.) - start(c) { + start() { started = true; }, }); diff --git a/dom/svg/SVGAElement.cpp b/dom/svg/SVGAElement.cpp index 8df685e674..d27e8735ad 100644 --- a/dom/svg/SVGAElement.cpp +++ b/dom/svg/SVGAElement.cpp @@ -149,8 +149,8 @@ nsresult SVGAElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void SVGAElement::UnbindFromTree(bool aNullParent) { - SVGAElementBase::UnbindFromTree(aNullParent); +void SVGAElement::UnbindFromTree(UnbindContext& aContext) { + SVGAElementBase::UnbindFromTree(aContext); // Without removing the link state we risk a dangling pointer // in the mStyledLinks hashtable Link::UnbindFromTree(); diff --git a/dom/svg/SVGAElement.h b/dom/svg/SVGAElement.h index 994481a386..166a146436 100644 --- a/dom/svg/SVGAElement.h +++ b/dom/svg/SVGAElement.h @@ -48,7 +48,7 @@ class SVGAElement final : public SVGAElementBase, // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; int32_t TabIndexDefault() override; Focusable IsFocusableWithoutStyle(bool aWithMouse) override; diff --git a/dom/svg/SVGAnimationElement.cpp b/dom/svg/SVGAnimationElement.cpp index ee5ad40f25..fea2a2595e 100644 --- a/dom/svg/SVGAnimationElement.cpp +++ b/dom/svg/SVGAnimationElement.cpp @@ -157,7 +157,7 @@ nsresult SVGAnimationElement::BindToTree(BindContext& aContext, return NS_OK; } -void SVGAnimationElement::UnbindFromTree(bool aNullParent) { +void SVGAnimationElement::UnbindFromTree(UnbindContext& aContext) { SMILAnimationController* controller = OwnerDoc()->GetAnimationController(); if (controller) { controller->UnregisterAnimationElement(this); @@ -168,7 +168,7 @@ void SVGAnimationElement::UnbindFromTree(bool aNullParent) { AnimationNeedsResample(); - SVGAnimationElementBase::UnbindFromTree(aNullParent); + SVGAnimationElementBase::UnbindFromTree(aContext); } bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID, diff --git a/dom/svg/SVGAnimationElement.h b/dom/svg/SVGAnimationElement.h index 163aa08ea7..02602172a8 100644 --- a/dom/svg/SVGAnimationElement.h +++ b/dom/svg/SVGAnimationElement.h @@ -41,7 +41,7 @@ class SVGAnimationElement : public SVGAnimationElementBase, public SVGTests { // nsIContent specializations nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; // Element specializations bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, diff --git a/dom/svg/SVGFEImageElement.cpp b/dom/svg/SVGFEImageElement.cpp index acf1f821f5..f9c5f96758 100644 --- a/dom/svg/SVGFEImageElement.cpp +++ b/dom/svg/SVGFEImageElement.cpp @@ -170,9 +170,9 @@ nsresult SVGFEImageElement::BindToTree(BindContext& aContext, return rv; } -void SVGFEImageElement::UnbindFromTree(bool aNullParent) { - nsImageLoadingContent::UnbindFromTree(aNullParent); - SVGFEImageElementBase::UnbindFromTree(aNullParent); +void SVGFEImageElement::UnbindFromTree(UnbindContext& aContext) { + nsImageLoadingContent::UnbindFromTree(); + SVGFEImageElementBase::UnbindFromTree(aContext); } void SVGFEImageElement::DestroyContent() { diff --git a/dom/svg/SVGFEImageElement.h b/dom/svg/SVGFEImageElement.h index 8d79e155f3..41b28b83a9 100644 --- a/dom/svg/SVGFEImageElement.h +++ b/dom/svg/SVGFEImageElement.h @@ -70,7 +70,7 @@ class SVGFEImageElement final : public SVGFEImageElementBase, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aSubjectPrincipal, bool aNotify) override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void DestroyContent() override; NS_DECL_IMGINOTIFICATIONOBSERVER diff --git a/dom/svg/SVGImageElement.cpp b/dom/svg/SVGImageElement.cpp index 014303c912..264c03da09 100644 --- a/dom/svg/SVGImageElement.cpp +++ b/dom/svg/SVGImageElement.cpp @@ -260,9 +260,9 @@ nsresult SVGImageElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void SVGImageElement::UnbindFromTree(bool aNullParent) { - nsImageLoadingContent::UnbindFromTree(aNullParent); - SVGImageElementBase::UnbindFromTree(aNullParent); +void SVGImageElement::UnbindFromTree(UnbindContext& aContext) { + nsImageLoadingContent::UnbindFromTree(); + SVGImageElementBase::UnbindFromTree(aContext); } void SVGImageElement::DestroyContent() { diff --git a/dom/svg/SVGImageElement.h b/dom/svg/SVGImageElement.h index 880ba5d457..06770e2576 100644 --- a/dom/svg/SVGImageElement.h +++ b/dom/svg/SVGImageElement.h @@ -60,7 +60,7 @@ class SVGImageElement final : public SVGImageElementBase, nsIPrincipal* aSubjectPrincipal, bool aNotify) override; nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void DestroyContent() override; diff --git a/dom/svg/SVGMPathElement.cpp b/dom/svg/SVGMPathElement.cpp index 2695cc3aa1..8ff21f61cd 100644 --- a/dom/svg/SVGMPathElement.cpp +++ b/dom/svg/SVGMPathElement.cpp @@ -68,10 +68,10 @@ already_AddRefed<DOMSVGAnimatedString> SVGMPathElement::Href() { //---------------------------------------------------------------------- // nsIContent methods -void SVGMPathElement::UnbindFromTree(bool aNullParent) { +void SVGMPathElement::UnbindFromTree(UnbindContext& aContext) { mMPathObserver = nullptr; NotifyParentOfMpathChange(); - SVGMPathElementBase::UnbindFromTree(aNullParent); + SVGMPathElementBase::UnbindFromTree(aContext); } void SVGMPathElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, diff --git a/dom/svg/SVGMPathElement.h b/dom/svg/SVGMPathElement.h index 273ff45e93..bc02a27460 100644 --- a/dom/svg/SVGMPathElement.h +++ b/dom/svg/SVGMPathElement.h @@ -38,7 +38,7 @@ class SVGMPathElement final : public SVGMPathElementBase { // nsIContent interface nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp index 070bfa2bf0..f7282569f9 100644 --- a/dom/svg/SVGSVGElement.cpp +++ b/dom/svg/SVGSVGElement.cpp @@ -337,12 +337,12 @@ nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void SVGSVGElement::UnbindFromTree(bool aNullParent) { +void SVGSVGElement::UnbindFromTree(UnbindContext& aContext) { if (mTimedDocumentRoot) { mTimedDocumentRoot->SetParent(nullptr); } - SVGGraphicsElement::UnbindFromTree(aNullParent); + SVGGraphicsElement::UnbindFromTree(aContext); } SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList( diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h index 5c0cec383e..0e800e8bd1 100644 --- a/dom/svg/SVGSVGElement.h +++ b/dom/svg/SVGSVGElement.h @@ -128,7 +128,7 @@ class SVGSVGElement final : public SVGSVGElementBase { // SVGElement overrides nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; SVGAnimatedTransformList* GetAnimatedTransformList( uint32_t aFlags = 0) override; diff --git a/dom/svg/SVGStyleElement.cpp b/dom/svg/SVGStyleElement.cpp index 999c240b65..6eaa2cd6fd 100644 --- a/dom/svg/SVGStyleElement.cpp +++ b/dom/svg/SVGStyleElement.cpp @@ -69,10 +69,10 @@ nsresult SVGStyleElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void SVGStyleElement::UnbindFromTree(bool aNullParent) { +void SVGStyleElement::UnbindFromTree(UnbindContext& aContext) { nsCOMPtr<Document> oldDoc = GetUncomposedDoc(); ShadowRoot* oldShadow = GetContainingShadow(); - SVGStyleElementBase::UnbindFromTree(aNullParent); + SVGStyleElementBase::UnbindFromTree(aContext); Unused << UpdateStyleSheetInternal(oldDoc, oldShadow); } diff --git a/dom/svg/SVGStyleElement.h b/dom/svg/SVGStyleElement.h index b1b1850add..357beaf152 100644 --- a/dom/svg/SVGStyleElement.h +++ b/dom/svg/SVGStyleElement.h @@ -40,7 +40,7 @@ class SVGStyleElement final : public SVGStyleElementBase, // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; virtual void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, diff --git a/dom/svg/SVGTitleElement.cpp b/dom/svg/SVGTitleElement.cpp index f352383fa5..873499c40e 100644 --- a/dom/svg/SVGTitleElement.cpp +++ b/dom/svg/SVGTitleElement.cpp @@ -63,11 +63,11 @@ nsresult SVGTitleElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void SVGTitleElement::UnbindFromTree(bool aNullParent) { +void SVGTitleElement::UnbindFromTree(UnbindContext& aContext) { SendTitleChangeEvent(false); // Let this fall through. - SVGTitleElementBase::UnbindFromTree(aNullParent); + SVGTitleElementBase::UnbindFromTree(aContext); } void SVGTitleElement::DoneAddingChildren(bool aHaveNotified) { diff --git a/dom/svg/SVGTitleElement.h b/dom/svg/SVGTitleElement.h index 52f8bf886c..0141b84d54 100644 --- a/dom/svg/SVGTitleElement.h +++ b/dom/svg/SVGTitleElement.h @@ -45,7 +45,7 @@ class SVGTitleElement final : public SVGTitleElementBase, nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; void DoneAddingChildren(bool aHaveNotified) override; diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp index 5e671fc802..2db8649ba1 100644 --- a/dom/svg/SVGUseElement.cpp +++ b/dom/svg/SVGUseElement.cpp @@ -19,6 +19,7 @@ #include "mozilla/dom/SVGGraphicsElement.h" #include "mozilla/dom/SVGLengthBinding.h" #include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGSwitchElement.h" #include "mozilla/dom/SVGSymbolElement.h" #include "mozilla/dom/SVGUseElementBinding.h" #include "nsGkAtoms.h" @@ -166,8 +167,8 @@ nsresult SVGUseElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void SVGUseElement::UnbindFromTree(bool aNullParent) { - SVGUseElementBase::UnbindFromTree(aNullParent); +void SVGUseElement::UnbindFromTree(UnbindContext& aContext) { + SVGUseElementBase::UnbindFromTree(aContext); OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this); } @@ -251,7 +252,17 @@ static bool NodeCouldBeRendered(const nsINode& aNode) { if (const auto* symbol = SVGSymbolElement::FromNode(aNode)) { return symbol->CouldBeRendered(); } - // TODO: Do we have other cases we can optimize out easily? + if (const auto* svgGraphics = SVGGraphicsElement::FromNode(aNode)) { + if (!svgGraphics->PassesConditionalProcessingTests()) { + return false; + } + } + if (auto* svgSwitch = + SVGSwitchElement::FromNodeOrNull(aNode.GetParentNode())) { + if (&aNode != svgSwitch->GetActiveChild()) { + return false; + } + } return true; } diff --git a/dom/svg/SVGUseElement.h b/dom/svg/SVGUseElement.h index bd155ca0c3..3bdf3fc5bb 100644 --- a/dom/svg/SVGUseElement.h +++ b/dom/svg/SVGUseElement.h @@ -50,7 +50,7 @@ class SVGUseElement final : public SVGUseElementBase, NS_IMPL_FROMNODE_WITH_TAG(SVGUseElement, kNameSpaceID_SVG, use) nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent = true) override; + void UnbindFromTree(UnbindContext&) override; // interfaces: NS_DECL_ISUPPORTS_INHERITED diff --git a/dom/svg/test/test_SVGTransformListAddition.xhtml b/dom/svg/test/test_SVGTransformListAddition.xhtml index b3b908466c..ca0a7e23ac 100644 --- a/dom/svg/test/test_SVGTransformListAddition.xhtml +++ b/dom/svg/test/test_SVGTransformListAddition.xhtml @@ -40,7 +40,7 @@ function Transform(type, angle) { this.angle = angle; } -function main(g) { +function main() { var cases = [ new AdditionTestCase("Not additive", "translate(150 50)", diff --git a/dom/svg/test/test_bounds.html b/dom/svg/test/test_bounds.html index 0cf0001065..3b17c62d25 100644 --- a/dom/svg/test/test_bounds.html +++ b/dom/svg/test/test_bounds.html @@ -115,7 +115,7 @@ function runTest() { const sin45 = Math.sin(Math.PI / 4); - isfuzzy(text1Bounds.left, 24, 1, "text1.getBoundingClientRect().left"); + isfuzzy(text1Bounds.left, 23, 1, "text1.getBoundingClientRect().left"); is(text2Bounds.left, text1Bounds.left + 100, "text2.getBoundingClientRect().left"); is(text2Bounds.top, text1Bounds.top, "text2.getBoundingClientRect().top"); @@ -194,9 +194,9 @@ function runTest() { var text2aBounds = doc.getElementById("text2a").getBoundingClientRect(); - isfuzzy(text1aBounds.left, 82, 1, "text1a.getBoundingClientRect().left"); + isfuzzy(text1aBounds.left, 81, 1, "text1a.getBoundingClientRect().left"); is(text1aBounds.width, text1Bounds.width + 4, "text1a.getBoundingClientRect().width"); - is(text1bBounds.width, text1Bounds.width, "text1b.getBoundingClientRect().width"); + isfuzzy(text1bBounds.width, text1Bounds.width + 170, 1, "text1b.getBoundingClientRect().width"); isfuzzy(text1bBounds.height, 196, 5, "text1b.getBoundingClientRect().height"); is(text2aBounds.left, text1aBounds.left + 100 - 3, "text2a.getBoundingClientRect().left"); diff --git a/dom/svg/test/test_bug1426594.html b/dom/svg/test/test_bug1426594.html index ac975093c8..a3f830e9d0 100644 --- a/dom/svg/test/test_bug1426594.html +++ b/dom/svg/test/test_bug1426594.html @@ -12,10 +12,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1426594 function runTests() { let textElement = document.getElementById("textId"), - tspanElement = document.getElementById("tspanId"); + textClientRect = textElement.getBoundingClientRect(), + tspanClientRect = document.getElementById("tspanId").getBoundingClientRect(); - isfuzzy(textElement.getBoundingClientRect().width, tspanElement.getBoundingClientRect().width, 5); - isfuzzy(textElement.getBoundingClientRect().height, tspanElement.getBoundingClientRect().height, 5); + // TODO: tspan bounds should account for stroke, decorations and text-shadow + isfuzzy(textClientRect.width, tspanClientRect.width, 6, "unexpected width"); + isfuzzy(textClientRect.height, tspanClientRect.height, 6, "unexpected height"); SimpleTest.finish(); } diff --git a/dom/svg/test/test_fragments.html b/dom/svg/test/test_fragments.html index b12833e899..e7d0f29714 100644 --- a/dom/svg/test/test_fragments.html +++ b/dom/svg/test/test_fragments.html @@ -21,8 +21,7 @@ var svg = $("svg"); SimpleTest.waitForExplicitFinish(); -function Test(svgFragmentIdentifier, viewBoxString, - preserveAspectRatioString, zoomAndPanString) { +function Test(svgFragmentIdentifier) { this.svgFragmentIdentifier = svgFragmentIdentifier; } diff --git a/dom/svg/test/test_selectSubString.xhtml b/dom/svg/test/test_selectSubString.xhtml index 6755b65c56..66038237cb 100644 --- a/dom/svg/test/test_selectSubString.xhtml +++ b/dom/svg/test/test_selectSubString.xhtml @@ -39,7 +39,7 @@ function runTests() { } } - function expectNoThrow(charnum, nchars, expected) { + function expectNoThrow(charnum, nchars) { try { text.selectSubString(charnum, nchars); ok(true, diff --git a/dom/svg/test/test_text_selection.html b/dom/svg/test/test_text_selection.html index 7160461db9..2f275ac436 100644 --- a/dom/svg/test/test_text_selection.html +++ b/dom/svg/test/test_text_selection.html @@ -78,7 +78,7 @@ function testSelection() { deselect(); // Drag from left of the text to the right of the text to select it. - drag(90, 50, 110 + text[0].getComputedTextLength(), 50); + drag(101, 50, 99 + text[0].getComputedTextLength(), 50); selection_is("hello there", "selecting entire simple text by dragging around it"); deselect(); diff --git a/dom/system/IOUtils.cpp b/dom/system/IOUtils.cpp index 08e2173452..4b989c6c0c 100644 --- a/dom/system/IOUtils.cpp +++ b/dom/system/IOUtils.cpp @@ -79,17 +79,23 @@ # include "base/process_util.h" #endif -#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise) \ +#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise, _msg, ...) \ do { \ if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \ NS_FAILED(_rv)) { \ - (_promise)->MaybeRejectWithOperationError( \ - FormatErrorMessage(_rv, "Could not parse path (%s)", \ - NS_ConvertUTF16toUTF8(_path).get())); \ + (_promise)->MaybeRejectWithOperationError(FormatErrorMessage( \ + _rv, _msg ": could not parse path", ##__VA_ARGS__)); \ return; \ } \ } while (0) +#define IOUTILS_TRY_WITH_CONTEXT(_expr, _fmt, ...) \ + do { \ + if (nsresult _rv = (_expr); NS_FAILED(_rv)) { \ + return Err(IOUtils::IOError(_rv, _fmt, ##__VA_ARGS__)); \ + } \ + } while (0) + static constexpr auto SHUTDOWN_ERROR = "IOUtils: Shutting down and refusing additional I/O tasks"_ns; @@ -121,34 +127,32 @@ static bool IsNotDirectory(nsresult aResult) { /** * Formats an error message and appends the error name to the end. */ -template <typename... Args> -static nsCString FormatErrorMessage(nsresult aError, const char* const aMessage, - Args... aArgs) { - nsPrintfCString msg(aMessage, aArgs...); +static nsCString MOZ_FORMAT_PRINTF(2, 3) + FormatErrorMessage(nsresult aError, const char* const aFmt, ...) { + nsAutoCString errorName; + GetErrorName(aError, errorName); - if (const char* errName = GetStaticErrorName(aError)) { - msg.AppendPrintf(": %s", errName); - } else { - // In the exceptional case where there is no error name, print the literal - // integer value of the nsresult as an upper case hex value so it can be - // located easily in searchfox. - msg.AppendPrintf(": 0x%" PRIX32, static_cast<uint32_t>(aError)); - } + nsCString msg; + + va_list ap; + va_start(ap, aFmt); + msg.AppendVprintf(aFmt, ap); + va_end(ap); - return std::move(msg); + msg.AppendPrintf(" (%s)", errorName.get()); + + return msg; } static nsCString FormatErrorMessage(nsresult aError, - const char* const aMessage) { - const char* errName = GetStaticErrorName(aError); - if (errName) { - return nsPrintfCString("%s: %s", aMessage, errName); - } - // In the exceptional case where there is no error name, print the literal - // integer value of the nsresult as an upper case hex value so it can be - // located easily in searchfox. - return nsPrintfCString("%s: 0x%" PRIX32, aMessage, - static_cast<uint32_t>(aError)); + const nsCString& aMessage) { + nsAutoCString errorName; + GetErrorName(aError, errorName); + + nsCString msg(aMessage); + msg.AppendPrintf(" (%s)", errorName.get()); + + return msg; } [[nodiscard]] inline bool ToJSValue( @@ -183,99 +187,82 @@ static void ResolveJSPromise(Promise* aPromise, T&& aValue) { } static void RejectJSPromise(Promise* aPromise, const IOUtils::IOError& aError) { - const auto& errMsg = aError.Message(); + const auto errMsg = FormatErrorMessage(aError.Code(), aError.Message()); switch (aError.Code()) { case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK: - [[fallthrough]]; // to NS_ERROR_FILE_INVALID_PATH + [[fallthrough]]; case NS_ERROR_FILE_NOT_FOUND: - [[fallthrough]]; // to NS_ERROR_FILE_INVALID_PATH + [[fallthrough]]; case NS_ERROR_FILE_INVALID_PATH: - aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("File not found"_ns)); + [[fallthrough]]; + case NS_ERROR_NOT_AVAILABLE: + aPromise->MaybeRejectWithNotFoundError(errMsg); break; + case NS_ERROR_FILE_IS_LOCKED: - [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + [[fallthrough]]; case NS_ERROR_FILE_ACCESS_DENIED: - aPromise->MaybeRejectWithNotAllowedError( - errMsg.refOr("Access was denied to the target file"_ns)); + aPromise->MaybeRejectWithNotAllowedError(errMsg); break; + case NS_ERROR_FILE_TOO_BIG: - aPromise->MaybeRejectWithNotReadableError( - errMsg.refOr("Target file is too big"_ns)); - break; + [[fallthrough]]; case NS_ERROR_FILE_NO_DEVICE_SPACE: - aPromise->MaybeRejectWithNotReadableError( - errMsg.refOr("Target device is full"_ns)); + [[fallthrough]]; + case NS_ERROR_FILE_DEVICE_FAILURE: + [[fallthrough]]; + case NS_ERROR_FILE_FS_CORRUPTED: + [[fallthrough]]; + case NS_ERROR_FILE_CORRUPTED: + aPromise->MaybeRejectWithNotReadableError(errMsg); break; + case NS_ERROR_FILE_ALREADY_EXISTS: - aPromise->MaybeRejectWithNoModificationAllowedError( - errMsg.refOr("Target file already exists"_ns)); + aPromise->MaybeRejectWithNoModificationAllowedError(errMsg); break; + case NS_ERROR_FILE_COPY_OR_MOVE_FAILED: - aPromise->MaybeRejectWithOperationError( - errMsg.refOr("Failed to copy or move the target file"_ns)); + [[fallthrough]]; + case NS_ERROR_FILE_NAME_TOO_LONG: + [[fallthrough]]; + case NS_ERROR_FILE_UNRECOGNIZED_PATH: + [[fallthrough]]; + case NS_ERROR_FILE_DIR_NOT_EMPTY: + aPromise->MaybeRejectWithOperationError(errMsg); break; + case NS_ERROR_FILE_READ_ONLY: - aPromise->MaybeRejectWithReadOnlyError( - errMsg.refOr("Target file is read only"_ns)); + aPromise->MaybeRejectWithReadOnlyError(errMsg); break; + case NS_ERROR_FILE_NOT_DIRECTORY: - [[fallthrough]]; // to NS_ERROR_FILE_DESTINATION_NOT_DIR + [[fallthrough]]; case NS_ERROR_FILE_DESTINATION_NOT_DIR: - aPromise->MaybeRejectWithInvalidAccessError( - errMsg.refOr("Target file is not a directory"_ns)); - break; + [[fallthrough]]; case NS_ERROR_FILE_IS_DIRECTORY: - aPromise->MaybeRejectWithInvalidAccessError( - errMsg.refOr("Target file is a directory"_ns)); - break; + [[fallthrough]]; case NS_ERROR_FILE_UNKNOWN_TYPE: - aPromise->MaybeRejectWithInvalidAccessError( - errMsg.refOr("Target file is of unknown type"_ns)); - break; - case NS_ERROR_FILE_NAME_TOO_LONG: - aPromise->MaybeRejectWithOperationError( - errMsg.refOr("Target file path is too long"_ns)); - break; - case NS_ERROR_FILE_UNRECOGNIZED_PATH: - aPromise->MaybeRejectWithOperationError( - errMsg.refOr("Target file path is not recognized"_ns)); - break; - case NS_ERROR_FILE_DIR_NOT_EMPTY: - aPromise->MaybeRejectWithOperationError( - errMsg.refOr("Target directory is not empty"_ns)); - break; - case NS_ERROR_FILE_DEVICE_FAILURE: - [[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED - case NS_ERROR_FILE_FS_CORRUPTED: - aPromise->MaybeRejectWithNotReadableError( - errMsg.refOr("Target file system may be corrupt or unavailable"_ns)); - break; - case NS_ERROR_FILE_CORRUPTED: - aPromise->MaybeRejectWithNotReadableError( - errMsg.refOr("Target file could not be read and may be corrupt"_ns)); + aPromise->MaybeRejectWithInvalidAccessError(errMsg); break; + case NS_ERROR_ILLEGAL_INPUT: - [[fallthrough]]; // NS_ERROR_ILLEGAL_VALUE + [[fallthrough]]; case NS_ERROR_ILLEGAL_VALUE: - aPromise->MaybeRejectWithDataError( - errMsg.refOr("Argument is not allowed"_ns)); - break; - case NS_ERROR_NOT_AVAILABLE: - aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("Unavailable"_ns)); + aPromise->MaybeRejectWithDataError(errMsg); break; + case NS_ERROR_ABORT: - aPromise->MaybeRejectWithAbortError(errMsg.refOr("Operation aborted"_ns)); + aPromise->MaybeRejectWithAbortError(errMsg); break; + default: - aPromise->MaybeRejectWithUnknownError(FormatErrorMessage( - aError.Code(), errMsg.refOr("Unexpected error"_ns).get())); + aPromise->MaybeRejectWithUnknownError(errMsg); } } static void RejectShuttingDown(Promise* aPromise) { - RejectJSPromise(aPromise, - IOUtils::IOError(NS_ERROR_ABORT).WithMessage(SHUTDOWN_ERROR)); + RejectJSPromise(aPromise, IOUtils::IOError(NS_ERROR_ABORT, SHUTDOWN_ERROR)); } static bool AssertParentProcessWithCallerLocationImpl(GlobalObject& aGlobal, @@ -368,10 +355,20 @@ already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); Maybe<uint32_t> toRead = Nothing(); if (!aOptions.mMaxBytes.IsNull()) { + if (aOptions.mDecompress) { + RejectJSPromise( + promise, IOError(NS_ERROR_ILLEGAL_INPUT, + "Could not read `%s': the `maxBytes' and " + "`decompress' options are mutually exclusive", + file->HumanReadablePath().get())); + return; + } + if (aOptions.mMaxBytes.Value() == 0) { // Resolve with an empty buffer. nsTArray<uint8_t> arr(0); @@ -437,7 +434,8 @@ already_AddRefed<Promise> IOUtils::ReadUTF8(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<JsBuffer>( state->mEventQueue, promise, @@ -455,7 +453,8 @@ already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); RefPtr<StrongWorkerRef> workerRef; if (!NS_IsMainThread()) { @@ -476,8 +475,12 @@ already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal, file](JsBuffer&& aBuffer) { AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) { - promise->MaybeRejectWithUnknownError( - "Could not initialize JS API"); + RejectJSPromise( + promise, + IOError( + NS_ERROR_DOM_UNKNOWN_ERR, + "Could not read `%s': could not initialize JS API", + file->HumanReadablePath().get())); return; } JSContext* cx = jsapi.cx(); @@ -486,7 +489,12 @@ already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal, cx, IOUtils::JsBuffer::IntoString(cx, std::move(aBuffer))); if (!jsonStr) { - RejectJSPromise(promise, IOError(NS_ERROR_OUT_OF_MEMORY)); + RejectJSPromise( + promise, + IOError( + NS_ERROR_OUT_OF_MEMORY, + "Could not read `%s': failed to allocate buffer", + file->HumanReadablePath().get())); return; } @@ -497,13 +505,11 @@ already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal, JS_ClearPendingException(cx); promise->MaybeReject(exn); } else { - RejectJSPromise( - promise, - IOError(NS_ERROR_DOM_UNKNOWN_ERR) - .WithMessage( - "ParseJSON threw an uncatchable exception " - "while parsing file(%s)", - file->HumanReadablePath().get())); + RejectJSPromise(promise, + IOError(NS_ERROR_DOM_UNKNOWN_ERR, + "Could not read `%s': ParseJSON " + "threw an uncatchable exception", + file->HumanReadablePath().get())); } return; @@ -526,25 +532,31 @@ already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not write to `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); Maybe<Buffer<uint8_t>> buf = aData.CreateFromData<Buffer<uint8_t>>(); if (buf.isNothing()) { - promise->MaybeRejectWithOperationError( - "Out of memory: Could not allocate buffer while writing to file"); + promise->MaybeRejectWithOperationError(nsPrintfCString( + "Could not write to `%s': could not allocate buffer", + file->HumanReadablePath().get())); return; } - auto opts = InternalWriteOpts::FromBinding(aOptions); - if (opts.isErr()) { - RejectJSPromise(promise, opts.unwrapErr()); + auto result = InternalWriteOpts::FromBinding(aOptions); + if (result.isErr()) { + RejectJSPromise( + promise, + IOError::WithCause(result.unwrapErr(), "Could not write to `%s'", + file->HumanReadablePath().get())); return; } DispatchAndResolve<uint32_t>( state->mEventQueue, promise, [file = std::move(file), buf = buf.extract(), - opts = opts.unwrap()]() { return WriteSync(file, buf, opts); }); + opts = result.unwrap()]() { return WriteSync(file, buf, opts); }); }); } @@ -557,18 +569,23 @@ already_AddRefed<Promise> IOUtils::WriteUTF8(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not write to `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); - auto opts = InternalWriteOpts::FromBinding(aOptions); - if (opts.isErr()) { - RejectJSPromise(promise, opts.unwrapErr()); + auto result = InternalWriteOpts::FromBinding(aOptions); + if (result.isErr()) { + RejectJSPromise( + promise, + IOError::WithCause(result.unwrapErr(), "Could not write to `%s'", + file->HumanReadablePath().get())); return; } DispatchAndResolve<uint32_t>( state->mEventQueue, promise, [file = std::move(file), str = nsCString(aString), - opts = opts.unwrap()]() { + opts = result.unwrap()]() { return WriteSync(file, AsBytes(Span(str)), opts); }); }); @@ -583,18 +600,27 @@ already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not write to `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); - auto opts = InternalWriteOpts::FromBinding(aOptions); - if (opts.isErr()) { - RejectJSPromise(promise, opts.unwrapErr()); + auto result = InternalWriteOpts::FromBinding(aOptions); + if (result.isErr()) { + RejectJSPromise( + promise, + IOError::WithCause(result.unwrapErr(), "Could not write to `%s'", + file->HumanReadablePath().get())); return; } - if (opts.inspect().mMode == WriteMode::Append || - opts.inspect().mMode == WriteMode::AppendOrCreate) { + auto opts = result.unwrap(); + + if (opts.mMode == WriteMode::Append || + opts.mMode == WriteMode::AppendOrCreate) { promise->MaybeRejectWithNotSupportedError( - "IOUtils.writeJSON does not support appending to files."_ns); + nsPrintfCString("Could not write to `%s': IOUtils.writeJSON does " + "not support appending to files.", + file->HumanReadablePath().get())); return; } @@ -608,10 +634,9 @@ already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal, JS_ClearPendingException(cx); promise->MaybeReject(exn); } else { - RejectJSPromise( - promise, - IOError(NS_ERROR_DOM_UNKNOWN_ERR) - .WithMessage("Could not serialize object to JSON")); + RejectJSPromise(promise, + IOError(NS_ERROR_DOM_UNKNOWN_ERR, + "Could not serialize object to JSON"_ns)); } return; } @@ -619,10 +644,13 @@ already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal, DispatchAndResolve<uint32_t>( state->mEventQueue, promise, [file = std::move(file), string = std::move(string), - opts = opts.unwrap()]() -> Result<uint32_t, IOError> { + opts = std::move(opts)]() -> Result<uint32_t, IOError> { nsAutoCString utf8Str; if (!CopyUTF16toUTF8(string, utf8Str, fallible)) { - return Err(IOError(NS_ERROR_OUT_OF_MEMORY)); + return Err(IOError( + NS_ERROR_OUT_OF_MEMORY, + "Failed to write to `%s': could not allocate buffer", + file->HumanReadablePath().get())); } return WriteSync(file, AsBytes(Span(utf8Str)), opts); }); @@ -638,10 +666,16 @@ already_AddRefed<Promise> IOUtils::Move(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> sourceFile = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise); + REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise, + "Could not move `%s' to `%s'", + NS_ConvertUTF16toUTF8(aSourcePath).get(), + NS_ConvertUTF16toUTF8(aDestPath).get()); nsCOMPtr<nsIFile> destFile = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise); + REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise, + "Could not move `%s' to `%s'", + NS_ConvertUTF16toUTF8(aSourcePath).get(), + NS_ConvertUTF16toUTF8(aDestPath).get()); DispatchAndResolve<Ok>( state->mEventQueue, promise, @@ -660,7 +694,9 @@ already_AddRefed<Promise> IOUtils::Remove(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not remove `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<Ok>( state->mEventQueue, promise, @@ -679,7 +715,9 @@ already_AddRefed<Promise> IOUtils::MakeDirectory( return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not make directory `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<Ok>(state->mEventQueue, promise, [file = std::move(file), @@ -699,7 +737,8 @@ already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not stat `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<InternalFileInfo>( state->mEventQueue, promise, @@ -716,10 +755,16 @@ already_AddRefed<Promise> IOUtils::Copy(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> sourceFile = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise); + REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise, + "Could not copy `%s' to `%s'", + NS_ConvertUTF16toUTF8(aSourcePath).get(), + NS_ConvertUTF16toUTF8(aDestPath).get()); nsCOMPtr<nsIFile> destFile = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise); + REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise, + "Could not copy `%s' to `%s'", + NS_ConvertUTF16toUTF8(aSourcePath).get(), + NS_ConvertUTF16toUTF8(aDestPath).get()); DispatchAndResolve<Ok>( state->mEventQueue, promise, @@ -736,7 +781,7 @@ already_AddRefed<Promise> IOUtils::SetAccessTime( GlobalObject& aGlobal, const nsAString& aPath, const Optional<int64_t>& aAccess, ErrorResult& aError) { return SetTime(aGlobal, aPath, aAccess, &nsIFile::SetLastAccessedTime, - aError); + "access", aError); } /* static */ @@ -744,7 +789,7 @@ already_AddRefed<Promise> IOUtils::SetModificationTime( GlobalObject& aGlobal, const nsAString& aPath, const Optional<int64_t>& aModification, ErrorResult& aError) { return SetTime(aGlobal, aPath, aModification, &nsIFile::SetLastModifiedTime, - aError); + "modification", aError); } /* static */ @@ -752,11 +797,14 @@ already_AddRefed<Promise> IOUtils::SetTime(GlobalObject& aGlobal, const nsAString& aPath, const Optional<int64_t>& aNewTime, IOUtils::SetTimeFn aSetTimeFn, + const char* const aTimeKind, ErrorResult& aError) { return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not set %s time on `%s'", aTimeKind, + NS_ConvertUTF16toUTF8(aPath).get()); int64_t newTime = aNewTime.WasPassed() ? aNewTime.Value() : PR_Now() / PR_USEC_PER_MSEC; @@ -775,7 +823,9 @@ already_AddRefed<Promise> IOUtils::GetChildren( return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not get children of `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<nsTArray<nsString>>( state->mEventQueue, promise, @@ -800,7 +850,9 @@ already_AddRefed<Promise> IOUtils::SetPermissions(GlobalObject& aGlobal, #endif nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not set permissions on `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<Ok>( state->mEventQueue, promise, @@ -817,7 +869,9 @@ already_AddRefed<Promise> IOUtils::Exists(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not determine if `%s' exists", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<bool>( state->mEventQueue, promise, @@ -853,14 +907,21 @@ already_AddRefed<Promise> IOUtils::CreateUnique(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aParent, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aParent, promise, "Could not create unique %s in `%s'", + aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory", + NS_ConvertUTF16toUTF8(aParent).get()); if (nsresult rv = file->Append(aPrefix); NS_FAILED(rv)) { - RejectJSPromise(promise, - IOError(rv).WithMessage( - "Could not append prefix `%s' to parent `%s'", - NS_ConvertUTF16toUTF8(aPrefix).get(), - file->HumanReadablePath().get())); + RejectJSPromise( + promise, + IOError( + rv, + "Could not create unique %s: could not append prefix `%s' to " + "parent `%s'", + aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory", + NS_ConvertUTF16toUTF8(aPrefix).get(), + file->HumanReadablePath().get())); return; } @@ -881,14 +942,14 @@ already_AddRefed<Promise> IOUtils::ComputeHexDigest( return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { if (!nssInitialized) { - RejectJSPromise(promise, - IOError(NS_ERROR_UNEXPECTED) - .WithMessage("Could not initialize NSS")); + RejectJSPromise(promise, IOError(NS_ERROR_UNEXPECTED, + "Could not initialize NSS"_ns)); return; } nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not hash `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<nsCString>(state->mEventQueue, promise, [file = std::move(file), aAlgorithm]() { @@ -907,7 +968,10 @@ already_AddRefed<Promise> IOUtils::GetWindowsAttributes(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, + "Could not get Windows file attributes of " + "`%s'", + NS_ConvertUTF16toUTF8(aPath).get()); RefPtr<StrongWorkerRef> workerRef; if (!NS_IsMainThread()) { @@ -945,7 +1009,10 @@ already_AddRefed<Promise> IOUtils::SetWindowsAttributes( return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aPath, promise, + "Could not set Windows file attributes on `%s'", + NS_ConvertUTF16toUTF8(aPath).get()); uint32_t setAttrs = 0; uint32_t clearAttrs = 0; @@ -992,7 +1059,11 @@ already_AddRefed<Promise> IOUtils::HasMacXAttr(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aPath, promise, + "Could not read the extended attribute `%s' from `%s'", + PromiseFlatCString(aAttr).get(), + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<bool>( state->mEventQueue, promise, @@ -1010,7 +1081,11 @@ already_AddRefed<Promise> IOUtils::GetMacXAttr(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aPath, promise, + "Could not read extended attribute `%s' from `%s'", + PromiseFlatCString(aAttr).get(), + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<nsTArray<uint8_t>>( state->mEventQueue, promise, @@ -1029,16 +1104,21 @@ already_AddRefed<Promise> IOUtils::SetMacXAttr(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aPath, promise, + "Could not set the extended attribute `%s' on `%s'", + PromiseFlatCString(aAttr).get(), + NS_ConvertUTF16toUTF8(aPath).get()); nsTArray<uint8_t> value; if (!aValue.AppendDataTo(value)) { RejectJSPromise( - promise, - IOError(NS_ERROR_OUT_OF_MEMORY) - .WithMessage( - "Could not allocate buffer to set extended attribute")); + promise, IOError(NS_ERROR_OUT_OF_MEMORY, + "Could not set extended attribute `%s' on `%s': " + "could not allocate buffer", + PromiseFlatCString(aAttr).get(), + file->HumanReadablePath().get())); return; } @@ -1058,7 +1138,11 @@ already_AddRefed<Promise> IOUtils::DelMacXAttr(GlobalObject& aGlobal, return WithPromiseAndState( aGlobal, aError, [&](Promise* promise, auto& state) { nsCOMPtr<nsIFile> file = new nsLocalFile(); - REJECT_IF_INIT_PATH_FAILED(file, aPath, promise); + REJECT_IF_INIT_PATH_FAILED( + file, aPath, promise, + "Could not delete extended attribute `%s' on `%s'", + PromiseFlatCString(aAttr).get(), + NS_ConvertUTF16toUTF8(aPath).get()); DispatchAndResolve<Ok>( state->mEventQueue, promise, @@ -1086,8 +1170,10 @@ already_AddRefed<Promise> IOUtils::GetFile( nsCOMPtr<nsIFile> parent; if (nsresult rv = file->GetParent(getter_AddRefs(parent)); NS_FAILED(rv)) { - RejectJSPromise(promise, IOError(rv).WithMessage( - "Could not get parent directory")); + RejectJSPromise(promise, IOError(rv, + "Could not get nsIFile for `%s': " + "could not get parent directory", + file->HumanReadablePath().get())); return; } @@ -1153,19 +1239,16 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( nsIFile* aFile, const uint64_t aOffset, const Maybe<uint32_t> aMaxBytes, const bool aDecompress, IOUtils::BufferKind aBufferKind) { MOZ_ASSERT(!NS_IsMainThread()); - - if (aMaxBytes.isSome() && aDecompress) { - return Err( - IOError(NS_ERROR_ILLEGAL_INPUT) - .WithMessage( - "The `maxBytes` and `decompress` options are not compatible")); - } + // This is checked in IOUtils::Read. + MOZ_ASSERT(aMaxBytes.isNothing() || !aDecompress, + "maxBytes and decompress are mutually exclusive"); if (aOffset > static_cast<uint64_t>(INT64_MAX)) { - return Err(IOError(NS_ERROR_ILLEGAL_INPUT) - .WithMessage("Requested offset is too large (%" PRIu64 - " > %" PRId64 ")", - aOffset, INT64_MAX)); + return Err( + IOError(NS_ERROR_ILLEGAL_INPUT, + "Could not read `%s': requested offset is too large (%" PRIu64 + " > %" PRId64 ")", + aFile->HumanReadablePath().get(), aOffset, INT64_MAX)); } const int64_t offset = static_cast<int64_t>(aOffset); @@ -1174,8 +1257,12 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( if (nsresult rv = stream->Init(aFile, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not open the file at %s", - aFile->HumanReadablePath().get())); + if (IsFileNotFound(rv)) { + return Err(IOError(rv, "Could not open `%s': file does not exist", + aFile->HumanReadablePath().get())); + } + return Err( + IOError(rv, "Could not open `%s'", aFile->HumanReadablePath().get())); } uint32_t bufSize = 0; @@ -1187,9 +1274,10 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( int64_t rawStreamSize = -1; if (nsresult rv = stream->GetSize(&rawStreamSize); NS_FAILED(rv)) { - return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED) - .WithMessage("Could not get info for the file at %s", - aFile->HumanReadablePath().get())); + return Err( + IOError(NS_ERROR_FILE_ACCESS_DENIED, + "Could not open `%s': could not stat file or directory", + aFile->HumanReadablePath().get())); } MOZ_RELEASE_ASSERT(rawStreamSize >= 0); @@ -1198,10 +1286,9 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( bufSize = 0; } else { if (streamSize - offset > static_cast<int64_t>(UINT32_MAX)) { - return Err(IOError(NS_ERROR_FILE_TOO_BIG) - .WithMessage( - "Could not read the file at %s with offset %" PRIu32 - " because it is too large(size=%" PRIu64 " bytes)", + return Err(IOError(NS_ERROR_FILE_TOO_BIG, + "Could not read `%s' with offset %" PRIu64 + ": file is too large (%" PRIu64 " bytes)", aFile->HumanReadablePath().get(), offset, streamSize)); } @@ -1214,9 +1301,9 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( if (offset > 0) { if (nsresult rv = stream->Seek(PR_SEEK_SET, offset); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not seek to position %" PRId64 " in file %s", offset, - aFile->HumanReadablePath().get())); + return Err(IOError( + rv, "Could not read `%s': could not seek to position %" PRId64, + aFile->HumanReadablePath().get(), offset)); } } @@ -1225,7 +1312,8 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( if (bufSize > 0) { auto result = JsBuffer::Create(aBufferKind, bufSize); if (result.isErr()) { - return result.propagateErr(); + return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'", + aFile->HumanReadablePath().get())); } buffer = result.unwrap(); Span<char> toRead = buffer.BeginWriting(); @@ -1241,9 +1329,9 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( if (nsresult rv = stream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Encountered an unexpected error while reading file(%s)", - aFile->HumanReadablePath().get())); + return Err( + IOError(rv, "Could not read `%s': encountered an unexpected error", + aFile->HumanReadablePath().get())); } if (bytesRead == 0) { break; @@ -1257,7 +1345,13 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync( // Decompress the file contents, if required. if (aDecompress) { - return MozLZ4::Decompress(AsBytes(buffer.BeginReading()), aBufferKind); + auto result = + MozLZ4::Decompress(AsBytes(buffer.BeginReading()), aBufferKind); + if (result.isErr()) { + return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'", + aFile->HumanReadablePath().get())); + } + return result; } return std::move(buffer); @@ -1273,11 +1367,9 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadUTF8Sync( JsBuffer buffer = result.unwrap(); if (!IsUtf8(buffer.BeginReading())) { - return Err( - IOError(NS_ERROR_FILE_CORRUPTED) - .WithMessage( - "Could not read file(%s) because it is not UTF-8 encoded", - aFile->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_CORRUPTED, + "Could not read `%s': file is not UTF-8 encoded", + aFile->HumanReadablePath().get())); } return buffer; @@ -1293,14 +1385,16 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( nsIFile* tempFile = aOptions.mTmpFile; bool exists = false; - MOZ_TRY(aFile->Exists(&exists)); + IOUTILS_TRY_WITH_CONTEXT( + aFile->Exists(&exists), + "Could not write to `%s': could not stat file or directory", + aFile->HumanReadablePath().get()); if (exists && aOptions.mMode == WriteMode::Create) { - return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS) - .WithMessage("Refusing to overwrite the file at %s\n" - "Specify `mode: \"overwrite\"` to allow " - "overwriting the destination", - aFile->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS, + "Could not write to `%s': refusing to overwrite file, " + "`mode' is not \"overwrite\"", + aFile->HumanReadablePath().get())); } // If backupFile was specified, perform the backup as a move. @@ -1316,11 +1410,12 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( bool noOverwrite = aOptions.mMode == WriteMode::Create; - if (MoveSync(toMove, backupFile, noOverwrite).isErr()) { - return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED) - .WithMessage("Failed to backup the source file(%s) to %s", - aFile->HumanReadablePath().get(), - backupFile->HumanReadablePath().get())); + if (auto result = MoveSync(toMove, backupFile, noOverwrite); + result.isErr()) { + return Err(IOError::WithCause( + result.unwrapErr(), + "Could not write to `%s': failed to back up source file", + aFile->HumanReadablePath().get())); } } @@ -1369,11 +1464,13 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( nsTArray<uint8_t> compressed; Span<const char> bytes; if (aOptions.mCompress) { - auto rv = MozLZ4::Compress(aByteArray); - if (rv.isErr()) { - return rv.propagateErr(); + auto result = MozLZ4::Compress(aByteArray); + if (result.isErr()) { + return Err(IOError::WithCause(result.unwrapErr(), + "Could not write to `%s'", + writeFile->HumanReadablePath().get())); } - compressed = rv.unwrap(); + compressed = result.unwrap(); bytes = Span(reinterpret_cast<const char*>(compressed.Elements()), compressed.Length()); } else { @@ -1388,9 +1485,9 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( if (rv == nsresult::NS_ERROR_FILE_IS_DIRECTORY) { rv = NS_ERROR_FILE_ACCESS_DENIED; } - return Err( - IOError(rv).WithMessage("Could not open the file at %s for writing", - writeFile->HumanReadablePath().get())); + return Err(IOError( + rv, "Could not write to `%s': failed to open file for writing", + writeFile->HumanReadablePath().get())); } // nsFileRandomAccessStream::Write uses PR_Write under the hood, which @@ -1407,10 +1504,10 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( if (nsresult rv = stream->Write(pendingBytes.Elements(), chunkSize, &bytesWritten); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not write chunk (size = %" PRIu32 - ") to file %s. The file may be corrupt.", - chunkSize, writeFile->HumanReadablePath().get())); + return Err(IOError(rv, + "Could not write to `%s': failed to write chunk; " + "the file may be corrupt", + writeFile->HumanReadablePath().get())); } pendingBytes = pendingBytes.From(bytesWritten); totalWritten += bytesWritten; @@ -1433,8 +1530,9 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( bool isDir = false; if (nsresult rv = aFile->IsDirectory(&isDir); NS_FAILED(rv) && !IsFileNotFound(rv)) { - return Err(IOError(rv).WithMessage("Could not stat the file at %s", - aFile->HumanReadablePath().get())); + return Err(IOError( + rv, "Could not write to `%s': could not stat file or directory", + aFile->HumanReadablePath().get())); } // If we attempt to write to a directory *without* a temp file, we get a @@ -1445,20 +1543,19 @@ Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync( // inside the directory, which is not what we want. In this case, we are // just going to bail out early. if (isDir) { - return Err( - IOError(NS_ERROR_FILE_ACCESS_DENIED) - .WithMessage("Could not open the file at %s for writing", - aFile->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED, + "Could not write to `%s': file is a directory", + aFile->HumanReadablePath().get())); } } - if (MoveSync(writeFile, aFile, /* aNoOverwrite = */ false).isErr()) { - return Err( - IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED) - .WithMessage( - "Could not move temporary file(%s) to destination(%s)", - writeFile->HumanReadablePath().get(), - aFile->HumanReadablePath().get())); + if (auto result = MoveSync(writeFile, aFile, /* aNoOverwrite = */ false); + result.isErr()) { + return Err(IOError::WithCause( + result.unwrapErr(), + "Could not write to `%s': could not move overwite with temporary " + "file", + aFile->HumanReadablePath().get())); } } } @@ -1474,13 +1571,18 @@ Result<Ok, IOUtils::IOError> IOUtils::MoveSync(nsIFile* aSourceFile, // Ensure the source file exists before continuing. If it doesn't exist, // subsequent operations can fail in different ways on different platforms. bool srcExists = false; - MOZ_TRY(aSourceFile->Exists(&srcExists)); + IOUTILS_TRY_WITH_CONTEXT( + aSourceFile->Exists(&srcExists), + "Could not move `%s' to `%s': could not stat source file or directory", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get()); + if (!srcExists) { return Err( - IOError(NS_ERROR_FILE_NOT_FOUND) - .WithMessage( - "Could not move source file(%s) because it does not exist", - aSourceFile->HumanReadablePath().get())); + IOError(NS_ERROR_FILE_NOT_FOUND, + "Could not move `%s' to `%s': source file does not exist", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get())); } return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks, "move", aSourceFile, @@ -1497,28 +1599,35 @@ Result<Ok, IOUtils::IOError> IOUtils::CopySync(nsIFile* aSourceFile, // Ensure the source file exists before continuing. If it doesn't exist, // subsequent operations can fail in different ways on different platforms. bool srcExists; - MOZ_TRY(aSourceFile->Exists(&srcExists)); + IOUTILS_TRY_WITH_CONTEXT( + aSourceFile->Exists(&srcExists), + "Could not copy `%s' to `%s': could not stat source file or directory", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get()); + if (!srcExists) { return Err( - IOError(NS_ERROR_FILE_NOT_FOUND) - .WithMessage( - "Could not copy source file(%s) because it does not exist", - aSourceFile->HumanReadablePath().get())); + IOError(NS_ERROR_FILE_NOT_FOUND, + "Could not copy `%s' to `%s': source file does not exist", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get())); } // If source is a directory, fail immediately unless the recursive option is // true. bool srcIsDir = false; - MOZ_TRY(aSourceFile->IsDirectory(&srcIsDir)); + IOUTILS_TRY_WITH_CONTEXT( + aSourceFile->IsDirectory(&srcIsDir), + "Could not copy `%s' to `%s': could not stat source file or directory", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get()); + if (srcIsDir && !aRecursive) { - return Err( - IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED) - .WithMessage( - "Refused to copy source directory(%s) to the destination(%s)\n" - "Specify the `recursive: true` option to allow copying " - "directories", - aSourceFile->HumanReadablePath().get(), - aDestFile->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED, + "Refused to copy directory `%s' to `%s': `recursive' is " + "false\n", + aSourceFile->HumanReadablePath().get(), + aDestFile->HumanReadablePath().get())); } return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks, "copy", aSourceFile, @@ -1542,10 +1651,9 @@ Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod, if (NS_SUCCEEDED(rv) && destIsDir) { rv = (aSource->*aMethod)(aDest, u""_ns); if (NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not %s source file(%s) to destination directory(%s)", - aMethodName, aSource->HumanReadablePath().get(), - aDest->HumanReadablePath().get())); + return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName, + aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get())); } return Ok(); } @@ -1554,7 +1662,9 @@ Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod, if (!IsFileNotFound(rv)) { // It's ok if the dest file doesn't exist. Case 2 handles this below. // Bail out early for any other kind of error though. - return Err(IOError(rv)); + return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName, + aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get())); } destExists = false; } @@ -1564,37 +1674,41 @@ Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod, // If the destination exists and the source is not a regular file, // then this may fail. if (aNoOverwrite && destExists) { - return Err( - IOError(NS_ERROR_FILE_ALREADY_EXISTS) - .WithMessage( - "Could not %s source file(%s) to destination(%s) because the " - "destination already exists and overwrites are not allowed\n" - "Specify the `noOverwrite: false` option to mitigate this " - "error", - aMethodName, aSource->HumanReadablePath().get(), - aDest->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS, + "Could not %s `%s' to `%s': destination file exists and " + "`noOverwrite' is true", + aMethodName, aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get())); } if (destExists && !destIsDir) { // If the source file is a directory, but the target is a file, abort early. // Different implementations of |CopyTo| and |MoveTo| seem to handle this // error case differently (or not at all), so we explicitly handle it here. bool srcIsDir = false; - MOZ_TRY(aSource->IsDirectory(&srcIsDir)); + IOUTILS_TRY_WITH_CONTEXT( + aSource->IsDirectory(&srcIsDir), + "Could not %s `%s' to `%s': could not stat source file or directory", + aMethodName, aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get()); if (srcIsDir) { - return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR) - .WithMessage("Could not %s the source directory(%s) to " - "the destination(%s) because the destination " - "is not a directory", - aMethodName, - aSource->HumanReadablePath().get(), - aDest->HumanReadablePath().get())); + return Err(IOError( + NS_ERROR_FILE_DESTINATION_NOT_DIR, + "Could not %s directory `%s' to `%s': destination is not a directory", + aMethodName, aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get())); } } - nsCOMPtr<nsIFile> destDir; + // We would have already thrown if the path was zero-length. nsAutoString destName; - MOZ_TRY(aDest->GetLeafName(destName)); - MOZ_TRY(aDest->GetParent(getter_AddRefs(destDir))); + MOZ_ALWAYS_SUCCEEDS(aDest->GetLeafName(destName)); + + nsCOMPtr<nsIFile> destDir; + IOUTILS_TRY_WITH_CONTEXT( + aDest->GetParent(getter_AddRefs(destDir)), + "Could not %s `%s` to `%s': path `%s' does not have a parent", + aMethodName, aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get(), aDest->HumanReadablePath().get()); // We know `destName` is a file and therefore must have a parent directory. MOZ_RELEASE_ASSERT(destDir); @@ -1603,9 +1717,9 @@ Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod, // |MoveToFollowingLinks| will create it. rv = (aSource->*aMethod)(destDir, destName); if (NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not %s the source file(%s) to the destination(%s)", aMethodName, - aSource->HumanReadablePath().get(), aDest->HumanReadablePath().get())); + return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName, + aSource->HumanReadablePath().get(), + aDest->HumanReadablePath().get())); } return Ok(); } @@ -1625,32 +1739,35 @@ Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(nsIFile* aFile, return Ok(); } if (NS_FAILED(rv)) { - IOError err(rv); if (IsFileNotFound(rv)) { - return Err(err.WithMessage( - "Could not remove the file at %s because it does not exist.\n" - "Specify the `ignoreAbsent: true` option to mitigate this error", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not remove `%s': file does not exist", + aFile->HumanReadablePath().get())); } if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY) { - return Err(err.WithMessage( - "Could not remove the non-empty directory at %s.\n" - "Specify the `recursive: true` option to mitigate this error", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, + "Could not remove `%s': the directory is not empty", + aFile->HumanReadablePath().get())); } #ifdef XP_WIN if (rv == NS_ERROR_FILE_ACCESS_DENIED && aRetryReadonly) { - MOZ_TRY(SetWindowsAttributesSync(aFile, 0, FILE_ATTRIBUTE_READONLY)); + if (auto result = + SetWindowsAttributesSync(aFile, 0, FILE_ATTRIBUTE_READONLY); + result.isErr()) { + return Err(IOError::WithCause( + result.unwrapErr(), + "Could not remove `%s': could not clear readonly attribute", + aFile->HumanReadablePath().get())); + } return RemoveSync(aFile, aIgnoreAbsent, aRecursive, /* aRetryReadonly = */ false); } #endif - return Err(err.WithMessage("Could not remove the file at %s", - aFile->HumanReadablePath().get())); + return Err( + IOError(rv, "Could not remove `%s'", aFile->HumanReadablePath().get())); } return Ok(); } @@ -1663,7 +1780,10 @@ Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile, MOZ_ASSERT(!NS_IsMainThread()); nsCOMPtr<nsIFile> parent; - MOZ_TRY(aFile->GetParent(getter_AddRefs(parent))); + IOUTILS_TRY_WITH_CONTEXT( + aFile->GetParent(getter_AddRefs(parent)), + "Could not make directory `%s': could not get parent directory", + aFile->HumanReadablePath().get()); if (!parent) { // If we don't have a parent directory, we were called with a // root directory. If the directory doesn't already exist (e.g., asking @@ -1677,7 +1797,11 @@ Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile, // Otherwise, we fall through to `nsiFile::Create()` and let it fail there // instead. bool exists = false; - MOZ_TRY(aFile->Exists(&exists)); + IOUTILS_TRY_WITH_CONTEXT( + aFile->Exists(&exists), + "Could not make directory `%s': could not stat file or directory", + aFile->HumanReadablePath().get()); + if (exists) { return Ok(); } @@ -1692,13 +1816,16 @@ Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile, // an existing file, since trying to create a directory where a regular // file exists may be indicative of a logic error. bool isDirectory; - MOZ_TRY(aFile->IsDirectory(&isDirectory)); + IOUTILS_TRY_WITH_CONTEXT( + aFile->IsDirectory(&isDirectory), + "Could not make directory `%s': could not stat file or directory", + aFile->HumanReadablePath().get()); + if (!isDirectory) { - return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY) - .WithMessage("Could not create directory because the " - "target file(%s) exists " - "and is not a directory", - aFile->HumanReadablePath().get())); + return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY, + "Could not create directory `%s': file exists and " + "is not a directory", + aFile->HumanReadablePath().get())); } // The directory exists. // The caller may suppress this error. @@ -1706,14 +1833,12 @@ Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile, return Ok(); } // Otherwise, forward it. - return Err(IOError(rv).WithMessage( - "Could not create directory because it already exists at %s\n" - "Specify the `ignoreExisting: true` option to mitigate this " - "error", + return Err(IOError( + rv, "Could not create directory `%s': directory already exists", aFile->HumanReadablePath().get())); } - return Err(IOError(rv).WithMessage("Could not create directory at %s", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not create directory `%s'", + aFile->HumanReadablePath().get())); } return Ok(); } @@ -1731,26 +1856,27 @@ Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync( // Any subsequent errors are unexpected and will just be forwarded. nsresult rv = aFile->IsFile(&isRegular); if (NS_FAILED(rv)) { - IOError err(rv); if (IsFileNotFound(rv)) { - return Err( - err.WithMessage("Could not stat file(%s) because it does not exist", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not stat `%s': file does not exist", + aFile->HumanReadablePath().get())); } - return Err(err); + return Err( + IOError(rv, "Could not stat `%s'", aFile->HumanReadablePath().get())); } // Now we can populate the info object by querying the file. info.mType = FileType::Regular; if (!isRegular) { bool isDir = false; - MOZ_TRY(aFile->IsDirectory(&isDir)); + IOUTILS_TRY_WITH_CONTEXT(aFile->IsDirectory(&isDir), "Could not stat `%s'", + aFile->HumanReadablePath().get()); info.mType = isDir ? FileType::Directory : FileType::Other; } int64_t size = -1; if (info.mType == FileType::Regular) { - MOZ_TRY(aFile->GetFileSize(&size)); + IOUTILS_TRY_WITH_CONTEXT(aFile->GetFileSize(&size), "Could not stat `%s'", + aFile->HumanReadablePath().get()); } info.mSize = size; @@ -1759,18 +1885,27 @@ Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync( info.mCreationTime.emplace(static_cast<int64_t>(creationTime)); } else if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { // This field is only supported on some platforms. - return Err(IOError(rv)); + return Err( + IOError(rv, "Could not stat `%s'", aFile->HumanReadablePath().get())); } PRTime lastAccessed = 0; - MOZ_TRY(aFile->GetLastAccessedTime(&lastAccessed)); + IOUTILS_TRY_WITH_CONTEXT(aFile->GetLastAccessedTime(&lastAccessed), + "Could not stat `%s'", + aFile->HumanReadablePath().get()); + info.mLastAccessed = static_cast<int64_t>(lastAccessed); PRTime lastModified = 0; - MOZ_TRY(aFile->GetLastModifiedTime(&lastModified)); + IOUTILS_TRY_WITH_CONTEXT(aFile->GetLastModifiedTime(&lastModified), + "Could not stat `%s'", + aFile->HumanReadablePath().get()); + info.mLastModified = static_cast<int64_t>(lastModified); - MOZ_TRY(aFile->GetPermissions(&info.mPermissions)); + IOUTILS_TRY_WITH_CONTEXT(aFile->GetPermissions(&info.mPermissions), + "Could not stat `%s'", + aFile->HumanReadablePath().get()); return info; } @@ -1788,26 +1923,23 @@ Result<int64_t, IOUtils::IOError> IOUtils::SetTimeSync( // If it ever becomes possible to set a file time to 0, this check should be // removed, though this use case seems rare. if (aNewTime == 0) { - return Err( - IOError(NS_ERROR_ILLEGAL_VALUE) - .WithMessage( - "Refusing to set the modification time of file(%s) to 0.\n" - "To use the current system time, call `setModificationTime` " - "with no arguments", - aFile->HumanReadablePath().get())); + return Err(IOError( + NS_ERROR_ILLEGAL_VALUE, + "Refusing to set modification time of `%s' to 0: to use the current " + "system time, call `setModificationTime' with no arguments", + aFile->HumanReadablePath().get())); } nsresult rv = (aFile->*aSetTimeFn)(aNewTime); if (NS_FAILED(rv)) { - IOError err(rv); if (IsFileNotFound(rv)) { - return Err( - err.WithMessage("Could not set modification time of file(%s) " - "because it does not exist", - aFile->HumanReadablePath().get())); + return Err(IOError( + rv, "Could not set modification time of `%s': file does not exist", + aFile->HumanReadablePath().get())); } - return Err(err); + return Err(IOError(rv, "Could not set modification time of `%s'", + aFile->HumanReadablePath().get())); } return aNewTime; } @@ -1824,31 +1956,43 @@ Result<nsTArray<nsString>, IOUtils::IOError> IOUtils::GetChildrenSync( return children; } if (NS_FAILED(rv)) { - IOError err(rv); if (IsFileNotFound(rv)) { - return Err(err.WithMessage( - "Could not get children of file(%s) because it does not exist", + return Err(IOError( + rv, "Could not get children of `%s': directory does not exist", aFile->HumanReadablePath().get())); } if (IsNotDirectory(rv)) { - return Err(err.WithMessage( - "Could not get children of file(%s) because it is not a directory", - aFile->HumanReadablePath().get())); + return Err( + IOError(rv, "Could not get children of `%s': file is not a directory", + aFile->HumanReadablePath().get())); } - return Err(err); + return Err(IOError(rv, "Could not get children of `%s'", + aFile->HumanReadablePath().get())); } bool hasMoreElements = false; - MOZ_TRY(iter->HasMoreElements(&hasMoreElements)); + IOUTILS_TRY_WITH_CONTEXT( + iter->HasMoreElements(&hasMoreElements), + "Could not get children of `%s': could not iterate children", + aFile->HumanReadablePath().get()); + while (hasMoreElements) { nsCOMPtr<nsIFile> child; - MOZ_TRY(iter->GetNextFile(getter_AddRefs(child))); + IOUTILS_TRY_WITH_CONTEXT( + iter->GetNextFile(getter_AddRefs(child)), + "Could not get children of `%s': could not retrieve child file", + aFile->HumanReadablePath().get()); + if (child) { nsString path; - MOZ_TRY(child->GetPath(path)); + MOZ_ALWAYS_SUCCEEDS(child->GetPath(path)); children.AppendElement(path); } - MOZ_TRY(iter->HasMoreElements(&hasMoreElements)); + + IOUTILS_TRY_WITH_CONTEXT( + iter->HasMoreElements(&hasMoreElements), + "Could not get children of `%s': could not iterate children", + aFile->HumanReadablePath().get()); } return children; @@ -1859,7 +2003,10 @@ Result<Ok, IOUtils::IOError> IOUtils::SetPermissionsSync( nsIFile* aFile, const uint32_t aPermissions) { MOZ_ASSERT(!NS_IsMainThread()); - MOZ_TRY(aFile->SetPermissions(aPermissions)); + IOUTILS_TRY_WITH_CONTEXT(aFile->SetPermissions(aPermissions), + "Could not set permissions on `%s'", + aFile->HumanReadablePath().get()); + return Ok{}; } @@ -1868,7 +2015,8 @@ Result<bool, IOUtils::IOError> IOUtils::ExistsSync(nsIFile* aFile) { MOZ_ASSERT(!NS_IsMainThread()); bool exists = false; - MOZ_TRY(aFile->Exists(&exists)); + IOUTILS_TRY_WITH_CONTEXT(aFile->Exists(&exists), "Could not stat `%s'", + aFile->HumanReadablePath().get()); return exists; } @@ -1880,7 +2028,13 @@ Result<nsString, IOUtils::IOError> IOUtils::CreateUniqueSync( if (nsresult rv = aFile->CreateUnique(aFileType, aPermissions); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not create unique path")); + nsCOMPtr<nsIFile> aParent = nullptr; + MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(aParent))); + MOZ_RELEASE_ASSERT(aParent); + return Err( + IOError(rv, "Could not create unique %s in `%s'", + aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory", + aParent->HumanReadablePath().get())); } nsString path; @@ -1914,24 +2068,25 @@ Result<nsCString, IOUtils::IOError> IOUtils::ComputeHexDigestSync( Digest digest; if (nsresult rv = digest.Begin(alg); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not hash file at %s", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not hash `%s': could not create digest", + aFile->HumanReadablePath().get())); } RefPtr<nsIInputStream> stream; if (nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not open the file at %s", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not hash `%s': could not open for reading", + aFile->HumanReadablePath().get())); } char buffer[BUFFER_SIZE]; uint32_t read = 0; for (;;) { if (nsresult rv = stream->Read(buffer, BUFFER_SIZE, &read); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Encountered an unexpected error while reading file(%s)", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, + "Could not hash `%s': encountered an unexpected error " + "while reading file", + aFile->HumanReadablePath().get())); } if (read == 0) { break; @@ -1940,20 +2095,22 @@ Result<nsCString, IOUtils::IOError> IOUtils::ComputeHexDigestSync( if (nsresult rv = digest.Update(reinterpret_cast<unsigned char*>(buffer), read); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not hash file at %s", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not hash `%s': could not update digest", + aFile->HumanReadablePath().get())); } } AutoTArray<uint8_t, SHA512_LENGTH> rawDigest; if (nsresult rv = digest.End(rawDigest); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage("Could not hash file at %s", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not hash `%s': could not compute digest", + aFile->HumanReadablePath().get())); } nsCString hexDigest; if (!hexDigest.SetCapacity(2 * rawDigest.Length(), fallible)) { - return Err(IOError(NS_ERROR_OUT_OF_MEMORY)); + return Err(IOError(NS_ERROR_OUT_OF_MEMORY, + "Could not hash `%s': out of memory", + aFile->HumanReadablePath().get())); } const char HEX[] = "0123456789abcdef"; @@ -1977,9 +2134,8 @@ Result<uint32_t, IOUtils::IOError> IOUtils::GetWindowsAttributesSync( MOZ_ASSERT(file); if (nsresult rv = file->GetWindowsFileAttributes(&attrs); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not get Windows file attributes for the file at `%s'", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not get Windows file attributes for `%s'", + aFile->HumanReadablePath().get())); } return attrs; } @@ -1993,9 +2149,8 @@ Result<Ok, IOUtils::IOError> IOUtils::SetWindowsAttributesSync( if (nsresult rv = file->SetWindowsFileAttributes(aSetAttrs, aClearAttrs); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not set Windows file attributes for the file at `%s'", - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not set Windows file attributes for `%s'", + aFile->HumanReadablePath().get())); } return Ok{}; @@ -2013,9 +2168,8 @@ Result<bool, IOUtils::IOError> IOUtils::HasMacXAttrSync( bool hasAttr = false; if (nsresult rv = file->HasXAttr(aAttr, &hasAttr); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not read the extended attribute `%s' from the file `%s'", - aAttr.get(), aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not read extended attribute `%s' from `%s'", + aAttr.get(), aFile->HumanReadablePath().get())); } return hasAttr; @@ -2031,17 +2185,15 @@ Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::GetMacXAttrSync( nsTArray<uint8_t> value; if (nsresult rv = file->GetXAttr(aAttr, value); NS_FAILED(rv)) { - auto err = IOError(rv); - if (rv == NS_ERROR_NOT_AVAILABLE) { - return Err(err.WithMessage( - "The file `%s' does not have an extended attribute `%s'", - aFile->HumanReadablePath().get(), aAttr.get())); + return Err(IOError(rv, + "Could not get extended attribute `%s' from `%s': the " + "file does not have the attribute", + aAttr.get(), aFile->HumanReadablePath().get())); } - return Err(err.WithMessage( - "Could not read the extended attribute `%s' from the file `%s'", - aAttr.get(), aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not read extended attribute `%s' from `%s'", + aAttr.get(), aFile->HumanReadablePath().get())); } return value; @@ -2056,9 +2208,8 @@ Result<Ok, IOUtils::IOError> IOUtils::SetMacXAttrSync( MOZ_ASSERT(file); if (nsresult rv = file->SetXAttr(aAttr, aValue); NS_FAILED(rv)) { - return Err(IOError(rv).WithMessage( - "Could not set extended attribute `%s' on file `%s'", aAttr.get(), - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not set extended attribute `%s' on `%s'", + aAttr.get(), aFile->HumanReadablePath().get())); } return Ok{}; @@ -2073,17 +2224,15 @@ Result<Ok, IOUtils::IOError> IOUtils::DelMacXAttrSync(nsIFile* aFile, MOZ_ASSERT(file); if (nsresult rv = file->DelXAttr(aAttr); NS_FAILED(rv)) { - auto err = IOError(rv); - if (rv == NS_ERROR_NOT_AVAILABLE) { - return Err(err.WithMessage( - "The file `%s' does not have an extended attribute `%s'", - aFile->HumanReadablePath().get(), aAttr.get())); + return Err(IOError(rv, + "Could not delete extended attribute `%s' from " + "`%s': the file does not have the attribute", + aAttr.get(), aFile->HumanReadablePath().get())); } - return Err(IOError(rv).WithMessage( - "Could not delete extended attribute `%s' on file `%s'", aAttr.get(), - aFile->HumanReadablePath().get())); + return Err(IOError(rv, "Could not delete extended attribute `%s' from `%s'", + aAttr.get(), aFile->HumanReadablePath().get())); } return Ok{}; @@ -2374,8 +2523,8 @@ Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::MozLZ4::Compress( size_t worstCaseSize = Compression::LZ4::maxCompressedSize(aUncompressed.Length()) + HEADER_SIZE; if (!result.SetCapacity(worstCaseSize, fallible)) { - return Err(IOError(NS_ERROR_OUT_OF_MEMORY) - .WithMessage("Could not allocate buffer to compress data")); + return Err(IOError(NS_ERROR_OUT_OF_MEMORY, + "could not allocate buffer to compress data"_ns)); } result.AppendElements(Span(MAGIC_NUMBER.data(), MAGIC_NUMBER.size())); std::array<uint8_t, sizeof(uint32_t)> contentSizeBytes{}; @@ -2394,8 +2543,7 @@ Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::MozLZ4::Compress( aUncompressed.Length(), reinterpret_cast<char*>(result.Elements()) + HEADER_SIZE); if (!compressed) { - return Err( - IOError(NS_ERROR_UNEXPECTED).WithMessage("Could not compress data")); + return Err(IOError(NS_ERROR_UNEXPECTED, "could not compress data"_ns)); } result.SetLength(HEADER_SIZE + compressed); return result; @@ -2405,10 +2553,8 @@ Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::MozLZ4::Compress( Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::MozLZ4::Decompress( Span<const uint8_t> aFileContents, IOUtils::BufferKind aBufferKind) { if (aFileContents.LengthBytes() < HEADER_SIZE) { - return Err( - IOError(NS_ERROR_FILE_CORRUPTED) - .WithMessage( - "Could not decompress file because the buffer is too short")); + return Err(IOError(NS_ERROR_FILE_CORRUPTED, + "could not decompress file: buffer is too small"_ns)); } auto header = aFileContents.To(HEADER_SIZE); if (!std::equal(std::begin(MAGIC_NUMBER), std::end(MAGIC_NUMBER), @@ -2420,10 +2566,10 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::MozLZ4::Decompress( } magicStr.AppendPrintf("%02X", header.at(i)); - return Err(IOError(NS_ERROR_FILE_CORRUPTED) - .WithMessage("Could not decompress file because it has an " - "invalid LZ4 header (wrong magic number: '%s')", - magicStr.get())); + return Err(IOError(NS_ERROR_FILE_CORRUPTED, + "could not decompress file: invalid LZ4 header: wrong " + "magic number: `%s'", + magicStr.get())); } size_t numBytes = sizeof(uint32_t); Span<const uint8_t> sizeBytes = header.Last(numBytes); @@ -2435,7 +2581,9 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::MozLZ4::Decompress( auto contents = aFileContents.From(HEADER_SIZE); auto result = JsBuffer::Create(aBufferKind, expectedDecompressedSize); if (result.isErr()) { - return result.propagateErr(); + return Err(IOError::WithCause( + result.unwrapErr(), + "could not decompress file: could not allocate buffer"_ns)); } JsBuffer decompressed = result.unwrap(); @@ -2445,9 +2593,8 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::MozLZ4::Decompress( reinterpret_cast<char*>(decompressed.Elements()), expectedDecompressedSize, &actualSize)) { return Err( - IOError(NS_ERROR_FILE_CORRUPTED) - .WithMessage( - "Could not decompress file contents, the file may be corrupt")); + IOError(NS_ERROR_FILE_CORRUPTED, + "could not decompress file: the file may be corrupt"_ns)); } decompressed.SetLength(actualSize); return decompressed; @@ -2583,8 +2730,8 @@ IOUtils::InternalWriteOpts::FromBinding(const WriteOptions& aOptions) { if (nsresult rv = PathUtils::InitFileWithPath(opts.mBackupFile, aOptions.mBackupFile.Value()); NS_FAILED(rv)) { - return Err(IOUtils::IOError(rv).WithMessage( - "Could not parse path of backupFile (%s)", + return Err(IOUtils::IOError( + rv, "Could not parse path of backupFile `%s'", NS_ConvertUTF16toUTF8(aOptions.mBackupFile.Value()).get())); } } @@ -2594,8 +2741,8 @@ IOUtils::InternalWriteOpts::FromBinding(const WriteOptions& aOptions) { if (nsresult rv = PathUtils::InitFileWithPath(opts.mTmpFile, aOptions.mTmpPath.Value()); NS_FAILED(rv)) { - return Err(IOUtils::IOError(rv).WithMessage( - "Could not parse path of temp file (%s)", + return Err(IOUtils::IOError( + rv, "Could not parse path of temp file `%s'", NS_ConvertUTF16toUTF8(aOptions.mTmpPath.Value()).get())); } } @@ -2609,8 +2756,7 @@ Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::JsBuffer::Create( IOUtils::BufferKind aBufferKind, size_t aCapacity) { JsBuffer buffer(aBufferKind, aCapacity); if (aCapacity != 0 && !buffer.mBuffer) { - return Err(IOError(NS_ERROR_OUT_OF_MEMORY) - .WithMessage("Could not allocate buffer")); + return Err(IOError(NS_ERROR_OUT_OF_MEMORY, "Could not allocate buffer"_ns)); } return buffer; } @@ -2787,8 +2933,8 @@ void SyncReadFile::ReadBytesInto(const Uint8Array& aDestArray, } if (nsresult rv = mStream->Seek(PR_SEEK_SET, aOffset); NS_FAILED(rv)) { - return aRv.ThrowOperationError( - FormatErrorMessage(rv, "Could not seek to position %lld", aOffset)); + return aRv.ThrowOperationError(FormatErrorMessage( + rv, "Could not seek to position %" PRId64, aOffset)); } Span<char> toRead = AsWritableChars(aData); @@ -2805,7 +2951,8 @@ void SyncReadFile::ReadBytesInto(const Uint8Array& aDestArray, &bytesRead); NS_FAILED(rv)) { return aRv.ThrowOperationError(FormatErrorMessage( - rv, "Encountered an unexpected error while reading file stream")); + rv, + "Encountered an unexpected error while reading file stream"_ns)); } if (bytesRead == 0) { return aRv.ThrowOperationError( @@ -2896,3 +3043,4 @@ uint32_t IOUtils::LaunchProcess(GlobalObject& aGlobal, } // namespace mozilla::dom #undef REJECT_IF_INIT_PATH_FAILED +#undef IOUTILS_TRY_WITH_CONTEXT diff --git a/dom/system/IOUtils.h b/dom/system/IOUtils.h index 82ea30eaa8..6acfcbfb24 100644 --- a/dom/system/IOUtils.h +++ b/dom/system/IOUtils.h @@ -69,8 +69,8 @@ class IOUtils final { }; template <typename T> - using PhaseArray = - EnumeratedArray<IOUtils::ShutdownPhase, IOUtils::ShutdownPhase::Count, T>; + using PhaseArray = EnumeratedArray<IOUtils::ShutdownPhase, T, + size_t(IOUtils::ShutdownPhase::Count)>; static already_AddRefed<Promise> Read(GlobalObject& aGlobal, const nsAString& aPath, @@ -148,6 +148,7 @@ class IOUtils final { const nsAString& aPath, const Optional<int64_t>& aNewTime, SetTimeFn aSetTimeFn, + const char* const aTimeKind, ErrorResult& aError); public: @@ -653,23 +654,34 @@ class IOUtils::EventQueue final { */ class IOUtils::IOError { public: - MOZ_IMPLICIT IOError(nsresult aCode) : mCode(aCode), mMessage(Nothing()) {} - - /** - * Replaces the message associated with this error. - */ - template <typename... Args> - IOError WithMessage(const char* const aMessage, Args... aArgs) { - mMessage.emplace(nsPrintfCString(aMessage, aArgs...)); - return *this; + IOError(nsresult aCode, const nsCString& aMsg) + : mCode(aCode), mMessage(aMsg) {} + + IOError(nsresult aCode, const char* const aFmt, ...) MOZ_FORMAT_PRINTF(3, 4) + : mCode(aCode) { + va_list ap; + va_start(ap, aFmt); + mMessage.AppendVprintf(aFmt, ap); + va_end(ap); } - IOError WithMessage(const char* const aMessage) { - mMessage.emplace(nsCString(aMessage)); - return *this; + + static IOError WithCause(const IOError& aCause, const nsCString& aMsg) { + IOError e(aCause.mCode, aMsg); + e.mMessage.AppendPrintf(": %s", aCause.mMessage.get()); + return e; } - IOError WithMessage(const nsCString& aMessage) { - mMessage.emplace(aMessage); - return *this; + + static IOError WithCause(const IOError& aCause, const char* const aFmt, ...) + MOZ_FORMAT_PRINTF(2, 3) { + va_list ap; + va_start(ap, aFmt); + + IOError e(aCause.mCode, EmptyCString()); + e.mMessage.AppendVprintf(aFmt, ap); + e.mMessage.AppendPrintf(": %s", aCause.mMessage.get()); + + va_end(ap); + return e; } /** @@ -678,13 +690,13 @@ class IOUtils::IOError { nsresult Code() const { return mCode; } /** - * Maybe returns a message associated with this error. + * Returns the message associated with this error. */ - const Maybe<nsCString>& Message() const { return mMessage; } + const nsCString& Message() const { return mMessage; } private: nsresult mCode; - Maybe<nsCString> mMessage; + nsCString mMessage; }; /** diff --git a/dom/system/NetworkGeolocationProvider.sys.mjs b/dom/system/NetworkGeolocationProvider.sys.mjs index 1bee69a282..2e929c066f 100644 --- a/dom/system/NetworkGeolocationProvider.sys.mjs +++ b/dom/system/NetworkGeolocationProvider.sys.mjs @@ -391,7 +391,7 @@ NetworkGeolocationProvider.prototype = { } }, - notify(timer) { + notify() { this.onStatus(false, "wifi-timeout"); this.sendLocationRequest(null); }, diff --git a/dom/system/PathUtils.h b/dom/system/PathUtils.h index ff01ddfc1e..1870d92f23 100644 --- a/dom/system/PathUtils.h +++ b/dom/system/PathUtils.h @@ -241,7 +241,8 @@ class PathUtils::DirectoryCache final { void ResolveWithDirectory(Promise* aPromise, const Directory aRequestedDir); template <typename T> - using DirectoryArray = EnumeratedArray<Directory, Directory::Count, T>; + using DirectoryArray = + EnumeratedArray<Directory, T, size_t(Directory::Count)>; DirectoryArray<nsString> mDirectories; DirectoryArray<MozPromiseHolder<PopulateDirectoriesPromise>> mPromises; diff --git a/dom/system/tests/ioutils/file_ioutils_worker.js b/dom/system/tests/ioutils/file_ioutils_worker.js index e367eb4d99..f8cf286f8e 100644 --- a/dom/system/tests/ioutils/file_ioutils_worker.js +++ b/dom/system/tests/ioutils/file_ioutils_worker.js @@ -14,7 +14,7 @@ importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js"); importScripts("file_ioutils_test_fixtures.js"); -self.onmessage = async function (msg) { +self.onmessage = async function () { const tmpDir = await PathUtils.getTempDir(); // IOUtils functionality is the same when called from the main thread, or a diff --git a/dom/system/tests/ioutils/test_ioutils_copy_move.html b/dom/system/tests/ioutils/test_ioutils_copy_move.html index 408bb82f39..0761447bc6 100644 --- a/dom/system/tests/ioutils/test_ioutils_copy_move.html +++ b/dom/system/tests/ioutils/test_ioutils_copy_move.html @@ -24,7 +24,7 @@ info("Test moving a file to a relative destination"); await Assert.rejects( IOUtils.move(tmpFileName, dest), - /Could not parse path/, + /OperationError: Could not move `.*' to `.*': could not parse path \(NS_ERROR_FILE_UNRECOGNIZED_PATH\)/, "IOUtils::move only works with absolute paths" ); ok( @@ -56,7 +56,7 @@ // Test. await Assert.rejects( IOUtils.move(tmpFileName, destFileName, { noOverwrite: true }), - /Could not move source file\(.*\) to destination\(.*\) because the destination already exists and overwrites are not allowed/, + /Could not move `.*' to `.*': destination file exists and `noOverwrite' is true/, "IOUtils::move will refuse to move a file if overwrites are disabled" ); ok( @@ -161,7 +161,7 @@ // Test. await Assert.rejects( IOUtils.move(notExistsSrc, notExistsDest), - /Could not move source file\(.*\) because it does not exist/, + /NotFoundError: Could not move `.*' to `.*': source file does not exist/, "IOUtils::move throws if source file does not exist" ); ok( @@ -178,7 +178,7 @@ // Test. await Assert.rejects( IOUtils.move(srcDir, destFile), - /Could not move the source directory\(.*\) to the destination\(.*\) because the destination is not a directory/, + /InvalidAccessError: Could not move directory `.*' to `.*': destination is not a directory/, "IOUtils::move throws if try to move dir into an existing file" ); @@ -206,7 +206,7 @@ // Test. await Assert.rejects( IOUtils.copy(tmpFileName, destFileName, { noOverwrite: true }), - /Could not copy source file\(.*\) to destination\(.*\) because the destination already exists and overwrites are not allowed/, + /NoModificationAllowedError: Could not copy `.*' to `.*': destination file exists and `noOverwrite' is true/, "IOUtils::copy will refuse to copy to existing destination if overwrites are disabled" ); ok( @@ -311,7 +311,7 @@ // Test. await Assert.rejects( IOUtils.copy(notExistsSrc, notExistsDest), - /Could not copy source file\(.*\) because it does not exist/, + /NotFoundError: Could not copy `.*' to `.*': source file does not exist/, "IOUtils::copy throws if source file does not exist" ); ok( @@ -328,8 +328,8 @@ // Test. await Assert.rejects( IOUtils.copy(srcDir, destFile, { recursive: true }), - /Could not copy the source directory\(.*\) to the destination\(.*\) because the destination is not a directory/, - "IOUtils::copy throws if try to move dir into an existing file" + /InvalidAccessError: Could not copy directory `.*' to `.*': destination is not a directory/, + "IOUtils::copy throws when trying to move a directory into an existing file" ); ok(await fileHasTextContents(destFile, ""), "IOUtils::copy failure does not affect destination"); @@ -339,7 +339,7 @@ // Test. await Assert.rejects( IOUtils.copy(srcDir, notExistsDest, { recursive: false }), - /Refused to copy source directory\(.*\) to the destination\(.*\)/, + /OperationError: Refused to copy directory `.*' to `.*': `recursive' is false/, "IOUtils::copy throws if try to copy a directory with { recursive: false }" ); console.log(`${notExistsDest} exists?`, await IOUtils.exists(notExistsDest)) diff --git a/dom/system/tests/ioutils/test_ioutils_dir_iteration.html b/dom/system/tests/ioutils/test_ioutils_dir_iteration.html index 54168235b0..1e3653d876 100644 --- a/dom/system/tests/ioutils/test_ioutils_dir_iteration.html +++ b/dom/system/tests/ioutils/test_ioutils_dir_iteration.html @@ -21,7 +21,7 @@ await Assert.rejects( IOUtils.getChildren(notExists), - /Could not get children of file\(.*\) because it does not exist/, + /NotFoundError: Could not get children of `.*': directory does not exist/, "IOUtils::getChildren rejects if the file does not exist" ); ok(!await fileExists(notExists), `Expected ${notExists} not to exist`); @@ -31,7 +31,7 @@ let tmpFileName = PathUtils.join(PathUtils.tempDir, 'iterator_file.tmp'); await createFile(tmpFileName) await Assert.rejects(IOUtils.getChildren(tmpFileName), - /Could not get children of file\(.*\) because it is not a directory/, + /InvalidAccessError: Could not get children of `.*': file is not a directory/, "IOUtils::getChildren rejects if the file is not a dir" ); diff --git a/dom/system/tests/ioutils/test_ioutils_mac_xattr.html b/dom/system/tests/ioutils/test_ioutils_mac_xattr.html index 6af9b2e6f8..775490976e 100644 --- a/dom/system/tests/ioutils/test_ioutils_mac_xattr.html +++ b/dom/system/tests/ioutils/test_ioutils_mac_xattr.html @@ -36,7 +36,7 @@ info("Testing getting an attribute that does not exist"); await Assert.rejects( IOUtils.getMacXAttr(path, ATTR), - /NotFoundError: The file `.+' does not have an extended attribute/, + /NotFoundError: Could not get extended attribute `bogus.attr' from `.*': the file does not have the attribute/, "IOUtils::getMacXAttr rejects when the attribute does not exist" ); @@ -61,7 +61,7 @@ await IOUtils.delMacXAttr(path, ATTR); await Assert.rejects( IOUtils.getMacXAttr(path, ATTR), - /NotFoundError: The file `.+' does not have an extended attribute/, + /NotFoundError: Could not get extended attribute `bogus.attr' from `.*': the file does not have the attribute/, "IOUtils::delMacXAttr removes the attribute" ); @@ -73,7 +73,7 @@ info("Testing removing an attribute that does not exist"); await Assert.rejects( IOUtils.delMacXAttr(path, ATTR), - /NotFoundError: The file `.+' does not have an extended attribute/, + /NotFoundError: Could not delete extended attribute `bogus.attr' from `.*': the file does not have the attribute/, "IOUtils::delMacXAttr rejects when the attribute does not exist" ); diff --git a/dom/system/tests/ioutils/test_ioutils_mkdir.html b/dom/system/tests/ioutils/test_ioutils_mkdir.html index 6827b24cc6..e95873fd17 100644 --- a/dom/system/tests/ioutils/test_ioutils_mkdir.html +++ b/dom/system/tests/ioutils/test_ioutils_mkdir.html @@ -33,7 +33,7 @@ ); await Assert.rejects( IOUtils.makeDirectory(newDirectoryName, { ignoreExisting: false }), - /Could not create directory because it already exists at .*/, + /NoModificationAllowedError: Could not create directory `.*': directory already exists/, "IOUtils::makeDirectory can throw if the target dir exists" ) @@ -45,7 +45,7 @@ ); await Assert.rejects( IOUtils.makeDirectory(nestedDirName, { createAncestors: false }), - /Could not create directory at .*/, + /NotFoundError: Could not create directory `.*'/, "IOUtils::makeDirectory can fail if the target is missing parents" ); ok(!await IOUtils.exists(nestedDirName), `Expected ${nestedDirName} not to exist`); @@ -65,14 +65,14 @@ await Assert.rejects( IOUtils.makeDirectory(notADirFileName, { ignoreExisting: false }), - /Could not create directory because the target file\(.*\) exists and is not a directory/, + /InvalidAccessError: Could not create directory `.*': file exists and is not a directory/, "IOUtils::makeDirectory [ignoreExisting: false] throws when the target is an existing file" ); ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`); await Assert.rejects( IOUtils.makeDirectory(notADirFileName, { ignoreExisting: true }), - /Could not create directory because the target file\(.*\) exists and is not a directory/, + /InvalidAccessError: Could not create directory `.*': file exists and is not a directory/, "IOUtils::makeDirectory [ignoreExisting: true] throws when the target is an existing file" ); ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`); diff --git a/dom/system/tests/ioutils/test_ioutils_read_write.html b/dom/system/tests/ioutils/test_ioutils_read_write.html index 2243eb1eda..49232d464a 100644 --- a/dom/system/tests/ioutils/test_ioutils_read_write.html +++ b/dom/system/tests/ioutils/test_ioutils_read_write.html @@ -23,7 +23,7 @@ const doesNotExist = PathUtils.join(PathUtils.tempDir, "does_not_exist.tmp"); await Assert.rejects( IOUtils.read(doesNotExist), - /Could not open the file at .*/, + /NotFoundError: Could not open `.*': file does not exist/, "IOUtils::read rejects when file does not exist" ); }); @@ -46,7 +46,7 @@ IOUtils.write(tmpFileName, newContents, { mode: "create", }), - /Refusing to overwrite the file at */, + /NoModificationAllowedError: Could not write to `.*': refusing to overwrite file, `mode' is not "overwrite"/, "IOUtils::write rejects writing to existing file if overwrites are disabled" ); ok( @@ -285,7 +285,7 @@ info("Test writing a file at a relative destination"); await Assert.rejects( IOUtils.write(tmpFileName, bytes), - /Could not parse path/, + /OperationError: Could not write to `.*': could not parse path \(NS_ERROR_FILE_UNRECOGNIZED_PATH\)/, "IOUtils::write only works with absolute paths" ); }); @@ -296,8 +296,8 @@ info("Test reading a file at a relative destination"); await Assert.rejects( IOUtils.read(tmpFileName), - /Could not parse path/, - "IOUtils::write only works with absolute paths" + /OperationError: Could not read `.*': could not parse path \(NS_ERROR_FILE_UNRECOGNIZED_PATH\)/, + "IOUtils::read only works with absolute paths" ); }); @@ -348,7 +348,7 @@ is(bytesWritten, 64, "Expected to write 64 bytes"); await Assert.rejects( IOUtils.read(tmpFileName, { maxBytes: 4, decompress: true }), - /The `maxBytes` and `decompress` options are not compatible/, + /DataError: Could not read `.*': the `maxBytes' and `decompress' options are mutually exclusive/, "IOUtils::read rejects when maxBytes and decompress options are both used" ); @@ -364,17 +364,7 @@ await Assert.rejects( IOUtils.read(tmpFileName, { decompress: true }), - (actual) => { - is(actual.constructor, DOMException, - "rejection reason constructor for decompress with bad header"); - is(actual.name, "NotReadableError", - "rejection error name for decompress with bad header"); - ok(/Could not decompress file because it has an invalid LZ4 header \(wrong magic number: .*\)/ - .test(actual.message), - "rejection error message for decompress with bad header. Got " - + actual.message); - return true; - }, + /NotReadableError: Could not read `.*': could not decompress file: invalid LZ4 header: wrong magic number: `01 01 01 01 01 01 01 01 01 01 01 01' \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::read fails to decompress LZ4 data with a bad header" ); @@ -384,7 +374,7 @@ await Assert.rejects( IOUtils.read(tmpFileName, { decompress: true }), - /Could not decompress file because the buffer is too short/, + /NotReadableError: Could not read `.*': could not decompress file: buffer is too small \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::read fails to decompress LZ4 data with missing header" ); @@ -396,7 +386,7 @@ await Assert.rejects( IOUtils.read(tmpFileName, { decompress: true }), - /Could not decompress file contents, the file may be corrupt/, + /NotReadableError: Could not read `.*': could not decompress file: the file may be corrupt \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::read fails to read corrupt LZ4 contents with a correct header" ); @@ -411,11 +401,11 @@ await IOUtils.makeDirectory(fileName); await Assert.rejects( IOUtils.write(fileName, bytes), - /NotAllowedError: Could not open the file at .+ for writing/); + /NotAllowedError: Could not write to `.*': failed to open file for writing/); await Assert.rejects( IOUtils.write(fileName, bytes, { tmpPath }), - /NotAllowedError: Could not open the file at .+ for writing/); + /NotAllowedError: Could not write to `.*': file is a directory/); ok(!await IOUtils.exists(PathUtils.join(fileName, PathUtils.filename(tmpPath)))); }); @@ -509,7 +499,7 @@ await Assert.rejects( IOUtils.write(fileName, Uint8Array.of(5, 6, 7, 8, 9), { mode: "append" }), - /NotFoundError: Could not open the file at .*/ + /NotFoundError: Could not write to `.*': failed to open file for writing/ ); }); </script> diff --git a/dom/system/tests/ioutils/test_ioutils_read_write_json.html b/dom/system/tests/ioutils/test_ioutils_read_write_json.html index 0acb191e1b..a85a0c19a1 100644 --- a/dom/system/tests/ioutils/test_ioutils_read_write_json.html +++ b/dom/system/tests/ioutils/test_ioutils_read_write_json.html @@ -77,7 +77,7 @@ ok(!await IOUtils.exists(notExistsFilename), `${notExistsFilename} should not exist`); await Assert.rejects( IOUtils.readJSON(notExistsFilename), - /NotFoundError: Could not open the file at/, + /NotFoundError: Could not open `.*'/, "IOUtils::readJSON rejects when file does not exist" ); @@ -149,7 +149,7 @@ await Assert.rejects( IOUtils.writeJSON(filename, OBJECT, {mode: "append"}), - /NotSupportedError: IOUtils.writeJSON does not support appending to files/, + /NotSupportedError: Could not write to `.*': IOUtils.writeJSON does not support appending to files/, "IOUtils.writeJSON() cannot append" ); diff --git a/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html b/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html index cdea016732..e1a1864656 100644 --- a/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html +++ b/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html @@ -26,7 +26,7 @@ const doesNotExist = PathUtils.join(PathUtils.tempDir, "does_not_exist.tmp"); await Assert.rejects( IOUtils.readUTF8(doesNotExist), - /Could not open the file at .*/, + /NotFoundError: Could not open `.*'/, "IOUtils::readUTF8 rejects when file does not exist" ); @@ -38,7 +38,7 @@ await Assert.rejects( IOUtils.readUTF8(invalidUTF8File), - /Could not read file\(.*\) because it is not UTF-8 encoded/, + /NotReadableError: Could not read `.*': file is not UTF-8 encoded/, "IOUtils::readUTF8 will reject when reading a file that is not valid UTF-8" ); @@ -56,7 +56,7 @@ IOUtils.writeUTF8(tmpFileName, newContents, { mode: "create", }), - /Refusing to overwrite the file at */, + /NoModificationAllowedError: Could not write to `.*': refusing to overwrite file, `mode' is not "overwrite"/, "IOUtils::writeUTF8 rejects writing to existing file if overwrites are disabled" ); ok( @@ -233,7 +233,7 @@ info("Test writing a file at a relative destination"); await Assert.rejects( IOUtils.writeUTF8(tmpFileName, "foo"), - /Could not parse path/, + /OperationError: Could not write to `.*': could not parse path \(NS_ERROR_FILE_UNRECOGNIZED_PATH\)/, "IOUtils::writeUTF8 only works with absolute paths" ); }); @@ -244,7 +244,7 @@ info("Test reading a file at a relative destination"); await Assert.rejects( IOUtils.readUTF8(tmpFileName), - /Could not parse path/, + /OperationError: Could not read `.*': could not parse path \(NS_ERROR_FILE_UNRECOGNIZED_PATH\)/, "IOUtils::readUTF8 only works with absolute paths" ); }); @@ -309,7 +309,7 @@ await Assert.rejects( IOUtils.readUTF8(tmpFileName, { decompress: true }), - /Could not decompress file because it has an invalid LZ4 header \(wrong magic number: .*\)/, + /NotReadableError: Could not read `.*': could not decompress file: invalid LZ4 header: wrong magic number: `01 01 01 01 01 01 01 01 01 01 01 01' \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::readUTF8 fails to decompress LZ4 data with a bad header" ); @@ -319,7 +319,7 @@ await Assert.rejects( IOUtils.readUTF8(tmpFileName, { decompress: true }), - /Could not decompress file because the buffer is too short/, + /NotReadableError: Could not read `.*': could not decompress file: buffer is too small \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::readUTF8 fails to decompress LZ4 data with missing header" ); @@ -331,7 +331,7 @@ await Assert.rejects( IOUtils.readUTF8(tmpFileName, { decompress: true }), - /Could not decompress file contents, the file may be corrupt/, + /NotReadableError: Could not read `.*': could not decompress file: the file may be corrupt \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::readUTF8 fails to read corrupt LZ4 contents with a correct header" ); @@ -342,7 +342,7 @@ } await Assert.rejects( IOUtils.readUTF8(tmpFileName, { decompress: true }), - /Could not decompress file because the buffer is too short/, + /NotReadableError: Could not read `.*': could not decompress file: buffer is too small \(NS_ERROR_FILE_CORRUPTED\)/, "IOUtils::readUTF8 fails to decompress empty files" ); diff --git a/dom/system/tests/ioutils/test_ioutils_remove.html b/dom/system/tests/ioutils/test_ioutils_remove.html index f368fc09d3..1d860a7871 100644 --- a/dom/system/tests/ioutils/test_ioutils_remove.html +++ b/dom/system/tests/ioutils/test_ioutils_remove.html @@ -43,7 +43,7 @@ await Assert.rejects( IOUtils.remove(tmpFileName, { ignoreAbsent: false }), - /Could not remove the file at .* because it does not exist/, + /NotFoundError: Could not remove `.*': file does not exist/, "IOUtils::remove can throw an error when target file is missing" ); ok(!await fileExists(tmpFileName), `Expected file ${tmpFileName} not to exist`); @@ -69,7 +69,7 @@ await Assert.rejects( IOUtils.remove(tmpParentDir, { recursive: false }), - /Could not remove the non-empty directory at .*/, + /OperationError: Could not remove `.*': the directory is not empty/, "IOUtils::remove fails if non-recursively removing directory with contents" ); diff --git a/dom/system/tests/ioutils/test_ioutils_stat_set_modification_time.html b/dom/system/tests/ioutils/test_ioutils_stat_set_modification_time.html index e508817a41..8a5b521742 100644 --- a/dom/system/tests/ioutils/test_ioutils_stat_set_modification_time.html +++ b/dom/system/tests/ioutils/test_ioutils_stat_set_modification_time.html @@ -76,7 +76,7 @@ await Assert.rejects( IOUtils.stat(notExistsFile), - /Could not stat file\(.*\) because it does not exist/, + /NotFoundError: Could not stat `.*': file does not exist/, "IOUtils::stat throws if the target file does not exist" ); }); @@ -214,7 +214,7 @@ await Assert.rejects( IOUtils.setModificationTime(notExistsFile), - /Could not set modification time of file\(.*\) because it does not exist/, + /Could not set modification time of `.*': file does not exist/, "IOUtils::setModificationTime throws if the target file does not exist" ); @@ -224,7 +224,7 @@ await Assert.rejects( IOUtils.setModificationTime(tempFileName, 0), - /Refusing to set the modification time of file\(.*\) to 0/, + /DataError: Refusing to set modification time of `.*' to 0: to use the current system time, call `setModificationTime' with no arguments/, "IOUtils::setModificationTime cannot set the file modification time to Epoch" ); diff --git a/dom/system/tests/ioutils/test_ioutils_windows_file_attributes.html b/dom/system/tests/ioutils/test_ioutils_windows_file_attributes.html index a5b72bd078..00219fd6fb 100644 --- a/dom/system/tests/ioutils/test_ioutils_windows_file_attributes.html +++ b/dom/system/tests/ioutils/test_ioutils_windows_file_attributes.html @@ -48,7 +48,7 @@ await Assert.rejects( IOUtils.writeUTF8(filePath, "hello, world"), - /NotAllowedError: Could not open the file at .+ for writing/, + /NotAllowedError: Could not write to `.*': failed to open file for writing/, "IOUtils::writeUTF8 on a read-only file fails." ); diff --git a/dom/tests/browser/browser_ConsoleStorageAPITests.js b/dom/tests/browser/browser_ConsoleStorageAPITests.js index f28db48a91..ee3e9e3f52 100644 --- a/dom/tests/browser/browser_ConsoleStorageAPITests.js +++ b/dom/tests/browser/browser_ConsoleStorageAPITests.js @@ -28,14 +28,14 @@ add_task(async function () { var tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); var browser = gBrowser.selectedBrowser; - const windowId = await ContentTask.spawn(browser, null, async function (opt) { + const windowId = await ContentTask.spawn(browser, null, async function () { let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( Ci.nsIConsoleAPIStorage ); let observerPromise = new Promise(resolve => { let apiCallCount = 0; - function observe(aSubject) { + function observe() { apiCallCount++; info(`Received ${apiCallCount} console log events`); if (apiCallCount == 4) { diff --git a/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js index 38f85ef5b1..623e509702 100644 --- a/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js +++ b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js @@ -33,7 +33,7 @@ function test() { function doTest(aIsPrivateMode, aWindow, aCallback) { BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then( () => { - function observe(aSubject) { + function observe() { afterEvents = ConsoleAPIStorage.getEvents(innerID); is( beforeEvents.length == afterEvents.length - 1, diff --git a/dom/tests/browser/browser_bug1004814.js b/dom/tests/browser/browser_bug1004814.js index 789709a8d7..eb2272285f 100644 --- a/dom/tests/browser/browser_bug1004814.js +++ b/dom/tests/browser/browser_bug1004814.js @@ -8,7 +8,7 @@ add_task(async function () { "http://example.com/browser/dom/tests/browser/test_bug1004814.html"; await BrowserTestUtils.withNewTab(TEST_URI, async aBrowser => { - let duration = await SpecialPowers.spawn(aBrowser, [], function (opts) { + let duration = await SpecialPowers.spawn(aBrowser, [], function () { const ConsoleAPIStorage = Cc[ "@mozilla.org/consoleAPI-storage;1" ].getService(Ci.nsIConsoleAPIStorage); diff --git a/dom/tests/browser/browser_bug1236512.js b/dom/tests/browser/browser_bug1236512.js index 66d58ab132..c7d9f14a66 100644 --- a/dom/tests/browser/browser_bug1236512.js +++ b/dom/tests/browser/browser_bug1236512.js @@ -42,7 +42,7 @@ async function waitContentVisibilityChange(aIsHidden, aBrowser) { content.document, "visibilitychange", true /* capture */, - aEvent => { + () => { info( `visibilitychange: ${content.document.hidden} ${content.document.visibilityState}` ); @@ -71,7 +71,7 @@ add_task(async function () { winTest, "resize", false, - e => { + () => { return winTest.innerHeight <= 500 && winTest.innerWidth <= 500; } ); diff --git a/dom/tests/browser/browser_bug1238427.js b/dom/tests/browser/browser_bug1238427.js index 60cb383e03..6e599b7b56 100644 --- a/dom/tests/browser/browser_bug1238427.js +++ b/dom/tests/browser/browser_bug1238427.js @@ -31,7 +31,7 @@ add_task(async function () { gBrowser, url: TEST_URI, }, - function (browser) { + function () { /* ... */ } ); diff --git a/dom/tests/browser/browser_bug1563629.js b/dom/tests/browser/browser_bug1563629.js index afbf5970d0..b5637ac84d 100644 --- a/dom/tests/browser/browser_bug1563629.js +++ b/dom/tests/browser/browser_bug1563629.js @@ -10,7 +10,7 @@ const URL1 = `https://example.com/${PATH}`; const URL2 = `https://example.org/${PATH}`; function listenForCrash(win) { - function listener(event) { + function listener() { ok(false, "a crash occurred"); } diff --git a/dom/tests/browser/browser_form_associated_custom_elements_validity.js b/dom/tests/browser/browser_form_associated_custom_elements_validity.js index 3765405735..da7c1ed263 100644 --- a/dom/tests/browser/browser_form_associated_custom_elements_validity.js +++ b/dom/tests/browser/browser_form_associated_custom_elements_validity.js @@ -109,3 +109,50 @@ add_task(async function form_report_validity() { } ); }); + +add_task(async function no_validation_anchor() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `data:text/html,<my-control tabindex=0>custom elements</my-control>`, + }, + async function (aBrowser) { + let promisePopupShown = BrowserTestUtils.waitForEvent( + window, + "popupshown" + ); + + let message = "valueMissing message"; + await SpecialPowers.spawn(aBrowser, [message], function (aMessage) { + class MyControl extends content.HTMLElement { + static get formAssociated() { + return true; + } + constructor() { + super(); + let internals = this.attachInternals(); + internals.setValidity({ valueMissing: true }, aMessage); + internals.reportValidity(); + } + } + content.customElements.define("my-control", MyControl); + + let myControl = content.document.querySelector("my-control"); + content.customElements.upgrade(myControl); + }); + await promisePopupShown; + + let invalidFormPopup = + window.document.getElementById("invalid-form-popup"); + is(invalidFormPopup.state, "open", "invalid-form-popup should be opened"); + is(invalidFormPopup.firstChild.textContent, message, "check message"); + + let promisePopupHidden = BrowserTestUtils.waitForEvent( + invalidFormPopup, + "popuphidden" + ); + invalidFormPopup.hidePopup(); + await promisePopupHidden; + } + ); +}); diff --git a/dom/tests/browser/browser_localStorage_privatestorageevent.js b/dom/tests/browser/browser_localStorage_privatestorageevent.js index 7c81fadf2d..22b948a73d 100644 --- a/dom/tests/browser/browser_localStorage_privatestorageevent.js +++ b/dom/tests/browser/browser_localStorage_privatestorageevent.js @@ -37,43 +37,43 @@ add_task(async function () { var pubBrowser = gBrowser.getBrowserForTab(pubTab); // Check if pubWin can see privWin's storage events - await SpecialPowers.spawn(pubBrowser, [], function (opts) { + await SpecialPowers.spawn(pubBrowser, [], function () { content.window.gotStorageEvent = false; - content.window.addEventListener("storage", ev => { + content.window.addEventListener("storage", () => { content.window.gotStorageEvent = true; }); }); - await SpecialPowers.spawn(privBrowser, [], function (opts) { + await SpecialPowers.spawn(privBrowser, [], function () { content.window.localStorage.key = "ablooabloo"; }); - let pubSaw = await SpecialPowers.spawn(pubBrowser, [], function (opts) { + let pubSaw = await SpecialPowers.spawn(pubBrowser, [], function () { return content.window.gotStorageEvent; }); ok(!pubSaw, "pubWin shouldn't be able to see privWin's storage events"); - await SpecialPowers.spawn(privBrowser, [], function (opts) { + await SpecialPowers.spawn(privBrowser, [], function () { content.window.gotStorageEvent = false; - content.window.addEventListener("storage", ev => { + content.window.addEventListener("storage", () => { content.window.gotStorageEvent = true; }); }); // Check if privWin can see pubWin's storage events - await SpecialPowers.spawn(privBrowser, [], function (opts) { + await SpecialPowers.spawn(privBrowser, [], function () { content.window.gotStorageEvent = false; - content.window.addEventListener("storage", ev => { + content.window.addEventListener("storage", () => { content.window.gotStorageEvent = true; }); }); - await SpecialPowers.spawn(pubBrowser, [], function (opts) { + await SpecialPowers.spawn(pubBrowser, [], function () { content.window.localStorage.key = "ablooabloo"; }); - let privSaw = await SpecialPowers.spawn(privBrowser, [], function (opts) { + let privSaw = await SpecialPowers.spawn(privBrowser, [], function () { return content.window.gotStorageEvent; }); diff --git a/dom/tests/browser/browser_persist_cookies.js b/dom/tests/browser/browser_persist_cookies.js index 282ad22060..60a70d6e88 100644 --- a/dom/tests/browser/browser_persist_cookies.js +++ b/dom/tests/browser/browser_persist_cookies.js @@ -13,7 +13,7 @@ const TEST_PATH2 = getRootDirectory(gTestPath).replace( ); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); registerCleanupFunction(async function () { info("Running the cleanup code"); diff --git a/dom/tests/browser/browser_persist_cross_origin_iframe.js b/dom/tests/browser/browser_persist_cross_origin_iframe.js index 94a9a74af7..46457b6051 100644 --- a/dom/tests/browser/browser_persist_cross_origin_iframe.js +++ b/dom/tests/browser/browser_persist_cross_origin_iframe.js @@ -13,7 +13,7 @@ const TEST_PATH2 = getRootDirectory(gTestPath).replace( ); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); registerCleanupFunction(async function () { info("Running the cleanup code"); @@ -120,7 +120,7 @@ add_task(async function () { gTestDir = createTemporarySaveDirectory(); MockFilePicker.displayDirectory = gTestDir; - MockFilePicker.showCallback = function (fp) { + MockFilePicker.showCallback = function () { let destFile = gTestDir.clone(); destFile.append("first.html"); MockFilePicker.setFiles([destFile]); @@ -154,7 +154,7 @@ add_task(async function () { "second_files/image_data/dummy.png", ]); - MockFilePicker.showCallback = function (fp) { + MockFilePicker.showCallback = function () { let destFile = gTestDir.clone(); destFile.append("second.html"); MockFilePicker.setFiles([destFile]); @@ -176,7 +176,7 @@ add_task(async function () { "third_files/dummy.png", ]); - MockFilePicker.showCallback = function (fp) { + MockFilePicker.showCallback = function () { let destFile = gTestDir.clone(); destFile.append("third.html"); MockFilePicker.setFiles([destFile]); diff --git a/dom/tests/browser/browser_persist_image_accept.js b/dom/tests/browser/browser_persist_image_accept.js index b4648a51ec..21a2096b9d 100644 --- a/dom/tests/browser/browser_persist_image_accept.js +++ b/dom/tests/browser/browser_persist_image_accept.js @@ -9,7 +9,7 @@ const TEST_PATH = getRootDirectory(gTestPath).replace( ); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); registerCleanupFunction(async function () { info("Running the cleanup code"); @@ -100,7 +100,7 @@ add_task(async function test_image_download() { }); let httpOnModifyPromise = TestUtils.topicObserved( "http-on-modify-request", - (s, t, d) => { + s => { let channel = s.QueryInterface(Ci.nsIChannel); let uri = channel.URI && channel.URI.spec; if (!uri.endsWith("dummy.png")) { diff --git a/dom/tests/browser/browser_persist_mixed_content_image.js b/dom/tests/browser/browser_persist_mixed_content_image.js index d84934376d..6612e61ef1 100644 --- a/dom/tests/browser/browser_persist_mixed_content_image.js +++ b/dom/tests/browser/browser_persist_mixed_content_image.js @@ -9,7 +9,7 @@ const TEST_PATH = getRootDirectory(gTestPath).replace( ); var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); registerCleanupFunction(async function () { info("Running the cleanup code"); diff --git a/dom/tests/browser/browser_sessionStorage_navigation.js b/dom/tests/browser/browser_sessionStorage_navigation.js index 8598969dc8..ffe75b13fe 100644 --- a/dom/tests/browser/browser_sessionStorage_navigation.js +++ b/dom/tests/browser/browser_sessionStorage_navigation.js @@ -66,7 +66,7 @@ add_task(async function () { await SpecialPowers.spawn( browser, [ORIGIN2, key, value], - async (ORIGIN, key, value) => { + async (ORIGIN, key) => { is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); let value1 = content.window.sessionStorage.getItem(key); diff --git a/dom/tests/browser/browser_test_focus_after_modal_state.js b/dom/tests/browser/browser_test_focus_after_modal_state.js index 2193d8fdc4..9b3d989078 100644 --- a/dom/tests/browser/browser_test_focus_after_modal_state.js +++ b/dom/tests/browser/browser_test_focus_after_modal_state.js @@ -33,14 +33,14 @@ add_task(async function () { return new Promise(resolve => { let doc = content.document.getElementById("edit").contentDocument; - doc.addEventListener("focus", function (event) { + doc.addEventListener("focus", function () { focusOccurred = true; if (blurOccurred) { resolve(true); } }); - doc.addEventListener("blur", function (event) { + doc.addEventListener("blur", function () { blurOccurred = true; if (focusOccurred) { resolve(false); diff --git a/dom/tests/browser/browser_windowProxy_transplant.js b/dom/tests/browser/browser_windowProxy_transplant.js index 6b9e316968..8e6e0f8413 100644 --- a/dom/tests/browser/browser_windowProxy_transplant.js +++ b/dom/tests/browser/browser_windowProxy_transplant.js @@ -59,7 +59,7 @@ add_task(async function () { return new Promise(resolve => { iframe.addEventListener( "load", - event => { + () => { info("Got an iframe load event!"); resolve(); }, diff --git a/dom/tests/browser/create_webrtc_peer_connection.html b/dom/tests/browser/create_webrtc_peer_connection.html index ee993d4892..51ca043073 100644 --- a/dom/tests/browser/create_webrtc_peer_connection.html +++ b/dom/tests/browser/create_webrtc_peer_connection.html @@ -19,7 +19,7 @@ window.addEventListener("message", event => { } }); -window.addEventListener("DOMContentLoaded", function(ev) { +window.addEventListener("DOMContentLoaded", function() { document.getElementById("msg").innerText = location.host; }); </script> diff --git a/dom/tests/browser/file_postMessage_parent.html b/dom/tests/browser/file_postMessage_parent.html index f9aa63a8c7..e9cb8a1d34 100644 --- a/dom/tests/browser/file_postMessage_parent.html +++ b/dom/tests/browser/file_postMessage_parent.html @@ -5,7 +5,7 @@ var winID = SpecialPowers.wrap(this).windowGlobalChild.innerWindowId; var observer = { - observe(subject, topic) { + observe(subject) { var currID = SpecialPowers.wrap(subject).QueryInterface(SpecialPowers.Ci.nsISupportsPRUint64).data; if (currID != winID) { return; diff --git a/dom/tests/browser/geo_leak_test.html b/dom/tests/browser/geo_leak_test.html index fb3fabac40..8273174581 100644 --- a/dom/tests/browser/geo_leak_test.html +++ b/dom/tests/browser/geo_leak_test.html @@ -4,7 +4,7 @@ <title>Geolocation incomplete position leak test</title> <script type="text/javascript"> -function successCallback(position) {} +function successCallback() {} function errorCallback() {} function init() { diff --git a/dom/tests/browser/test-console-api.html b/dom/tests/browser/test-console-api.html index e8da7c311e..f5afdf96b2 100644 --- a/dom/tests/browser/test-console-api.html +++ b/dom/tests/browser/test-console-api.html @@ -54,7 +54,7 @@ } function nativeCallback() { - new Promise(function(resolve, reject) { resolve(42); }).then(console.log); + new Promise(function(resolve) { resolve(42); }).then(console.log); } function timeStamp(val) { diff --git a/dom/tests/browser/worker_bug1004814.js b/dom/tests/browser/worker_bug1004814.js index 4fb54da692..fa6701329a 100644 --- a/dom/tests/browser/worker_bug1004814.js +++ b/dom/tests/browser/worker_bug1004814.js @@ -1,4 +1,4 @@ -onmessage = function (evt) { +onmessage = function () { console.time("bug1004814"); setTimeout(function () { console.timeEnd("bug1004814"); diff --git a/dom/tests/mochitest/chrome/child_focus_frame.html b/dom/tests/mochitest/chrome/child_focus_frame.html index d7f0ff63cd..89e9b8aae1 100644 --- a/dom/tests/mochitest/chrome/child_focus_frame.html +++ b/dom/tests/mochitest/chrome/child_focus_frame.html @@ -43,8 +43,8 @@ <fieldset style="float: right;"> <legend id="legend">Options</legend> <input id="t28" type="radio" name="options" value="optionone">One - <label id="ad" accesskey="d">Label:<input id="t29" type="radio" name="options" value="optiontwo">Two</label> </fieldset> +<label id="ad" accesskey="d">Label:<input id="t29" type="radio" name="options2" value="optiontwo">Two</label> <div id="t30" tabindex="0">abc</div> <input id="o18" accesskey="u" disabled size="3"/> <label id="ag" accesskey="g" for="n6">L</label> diff --git a/dom/tests/mochitest/chrome/file_bug800817.xhtml b/dom/tests/mochitest/chrome/file_bug800817.xhtml index bcd64b3ca7..ea646a74a1 100644 --- a/dom/tests/mochitest/chrome/file_bug800817.xhtml +++ b/dom/tests/mochitest/chrome/file_bug800817.xhtml @@ -30,16 +30,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=800817 var b1 = document.getElementById("b1"); var b2 = document.getElementById("b2"); - var testMozBrowser = window.arguments[0].testMozBrowser; - if (testMozBrowser) { - b1.setAttribute("mozbrowser", "true"); - b2.setAttribute("mozbrowser", "true"); - } - - if (testMozBrowser) - window.arguments[0].info("Testing with mozbrowser=true"); - else - window.arguments[0].info("Testing without mozbrowser"); + window.arguments[0].info("Testing..."); b1.contentWindow.focus(); window.arguments[0].is(document.activeElement, b1, diff --git a/dom/tests/mochitest/chrome/test_bug800817.xhtml b/dom/tests/mochitest/chrome/test_bug800817.xhtml index fefaf2c155..76ea31a2fc 100644 --- a/dom/tests/mochitest/chrome/test_bug800817.xhtml +++ b/dom/tests/mochitest/chrome/test_bug800817.xhtml @@ -20,20 +20,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=800817 <![CDATA[ /** Test for Bug 800817 **/ - var testMozBrowser = false; function runTests() { - // Run a first round of tests for non-mozbrowser iframes. window.openDialog("file_bug800817.xhtml", "_blank", "chrome,width=600,height=550,noopener", window); } function finishedTests() { - if (!testMozBrowser) { - testMozBrowser = true; - // Run a second round of tests for mozbrowser iframes. - window.openDialog("file_bug800817.xhtml", "_blank", "chrome,width=600,height=550,noopener", window); - } else { - SimpleTest.finish(); - } + SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); diff --git a/dom/tests/mochitest/chrome/window_focus.xhtml b/dom/tests/mochitest/chrome/window_focus.xhtml index e4cfeb6560..a1f9838450 100644 --- a/dom/tests/mochitest/chrome/window_focus.xhtml +++ b/dom/tests/mochitest/chrome/window_focus.xhtml @@ -46,6 +46,9 @@ var gChildWindow = null; var gOldExpectedWindow = null; var gNewExpectedWindow = null; +var gCanTabMoveFocusToRootElement = + !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element"); + function is(l, r, n) { window.arguments[0].SimpleTest.is(l,r,n); } function ok(v, n) { window.arguments[0].SimpleTest.ok(v,n); } @@ -398,8 +401,18 @@ function startTest() gLastFocusMethod = fm.FLAG_BYKEY; if (gPartialTabbing) { - var partialTabList = ["t3", "t5", "t9", "t10", "t11", "t12", "t13", "t14", "t15", - "t16", "t19", "t20", "t21", "t22", "t26", "t27", "t28", "t29", "t30"]; + var partialTabList; + if (gCanTabMoveFocusToRootElement) { + partialTabList = ["t3", "t5", "t9", "t10", "t11", "t12", "t13", "t14", "t15", + "t16", "t19", "t20", "t21", "t22", "t26", "t27", "t28", "t29", "t30"]; + } else { + // !gCanTabMoveFocusToRootElement + // t13 is the <html> element in child_focus_frame.html, + // and it's not getting the focus + // when gCanTabMoveFocusToRootElement is false. + partialTabList = ["t3", "t5", "t9", "t10", "t11", "t12", "t14", "t15", + "t16", "t19", "t20", "t21", "t22", "t26", "t27", "t28", "t29", "t30"]; + } for (var idx = 0; idx < partialTabList.length; idx++) { expectFocusShift(pressTab, null, getById(partialTabList[idx]), true, "partial tab key " + partialTabList[idx]); } @@ -415,6 +428,13 @@ function startTest() else { // TAB key for (var idx = 1; idx <= kTabbableSteps; idx++) { + if (!gCanTabMoveFocusToRootElement) { + if (idx == kChildDocumentRootIndex) + // t13 is the <html> element in child_focus_frame.html, + // and it's not getting the focus + // when gCanTabMoveFocusToRootElement is false. + continue; + } expectFocusShift(pressTab, null, getById("t" + idx), true, "tab key t" + idx); } @@ -427,6 +447,14 @@ function startTest() // Shift+TAB key setFocusTo("o5", window); for (idx = kTabbableSteps; idx > 0; idx--) { + if (!gCanTabMoveFocusToRootElement) { + if (idx == kChildDocumentRootIndex) { + // t13 is the <html> element in child_focus_frame.html, + // and it's not getting the focus + // when gCanTabMoveFocusToRootElement is false. + continue; + } + } expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}), null, getById("t" + idx), true, "shift tab key t" + idx); } @@ -1158,29 +1186,38 @@ function framesetWindowLoaded(framesetWindow) gOldExpectedWindow = getTopWindow(framesetWindow); gMoveToFocusFrame = true; - for (var idx = 1; idx <= 8; idx++) { - gNewExpectedWindow = framesetWindow.frames[(idx - 1) >> 1]; - if (idx % 2) + let steps = gCanTabMoveFocusToRootElement ? 8 : 4; + for (var idx = 1; idx <= steps; idx++) { + let frameIndex = gCanTabMoveFocusToRootElement ? (idx - 1) >> 1 : idx - 1; + gNewExpectedWindow = framesetWindow.frames[frameIndex]; + if (gCanTabMoveFocusToRootElement) { + if (idx % 2) { + initEvents(gNewExpectedWindow); + } + } else { initEvents(gNewExpectedWindow); + } expectFocusShift(() => synthesizeKey("KEY_Tab", {}, framesetWindow), - gNewExpectedWindow, getById("f" + idx), true, "frameset tab key f" + idx); + gNewExpectedWindow, getById("f" + (gCanTabMoveFocusToRootElement ? idx : idx * 2)), true, "frameset tab key f" + idx); gMoveToFocusFrame = false; gOldExpectedWindow = gNewExpectedWindow; } gNewExpectedWindow = framesetWindow.frames[0]; expectFocusShift(() => synthesizeKey("KEY_Tab", {}, framesetWindow), - gNewExpectedWindow, getById("f1"), true, "frameset tab key wrap to start"); + gNewExpectedWindow, gCanTabMoveFocusToRootElement ? getById("f1") : getById("f2"), true, "frameset tab key wrap to start"); gOldExpectedWindow = gNewExpectedWindow; gNewExpectedWindow = framesetWindow.frames[3]; expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}, framesetWindow), gNewExpectedWindow, getById("f8"), true, "frameset shift tab key wrap to end"); - for (idx = 7; idx >= 1; idx--) { + steps = gCanTabMoveFocusToRootElement ? 7 : 3; + for (idx = steps; idx >= 1; idx--) { gOldExpectedWindow = gNewExpectedWindow; - gNewExpectedWindow = framesetWindow.frames[(idx - 1) >> 1]; + let frameIndex = gCanTabMoveFocusToRootElement ? (idx - 1) >> 1 : idx - 1; + gNewExpectedWindow = framesetWindow.frames[frameIndex]; expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}, framesetWindow), - gNewExpectedWindow, getById("f" + idx), true, "frameset shift tab key f" + idx); + gNewExpectedWindow, getById("f" + (gCanTabMoveFocusToRootElement ? idx : idx * 2)), true, "frameset shift tab key f" + idx); } // document shifting diff --git a/dom/tests/mochitest/general/mochitest.toml b/dom/tests/mochitest/general/mochitest.toml index b8ec30f510..06137a8dd9 100644 --- a/dom/tests/mochitest/general/mochitest.toml +++ b/dom/tests/mochitest/general/mochitest.toml @@ -194,7 +194,7 @@ skip-if = [ ["test_resizeby.html"] skip-if = [ "os == 'android'", # Window sizes cannot be controled on android; Windows - "os == 'linux' && bits == 64",# Bug 1604152 + "os == 'linux' && os_version == '18.04' && bits == 64",# Bug 1604152 ] ["test_resource_timing.html"] @@ -261,8 +261,10 @@ skip-if = [ ["test_toggling_performance_navigation_timing.html"] skip-if = [ - "os == 'win' && bits == 64 && !debug", # Bug 1730152 - "os == 'mac' && bits == 64 && !debug", # Bug 1730152 + "win10_2009 && bits == 64 && !debug", # Bug 1730152 + "win11_2009 && bits == 64 && !debug", # Bug 1730152 + "apple_catalina && !debug", # Bug 1730152 + "apple_silicon && !debug", # Bug 1730152 ] ["test_vibrator.html"] diff --git a/dom/tests/mochitest/general/test_focus_scrollchildframe.html b/dom/tests/mochitest/general/test_focus_scrollchildframe.html index 4b12462c45..5bb13002b8 100644 --- a/dom/tests/mochitest/general/test_focus_scrollchildframe.html +++ b/dom/tests/mochitest/general/test_focus_scrollchildframe.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML> <html> <head> - <title>Tests for for-of loops</title> + <title>Test for using TAB to move focus and scroll into view</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/EventUtils.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> @@ -11,10 +11,18 @@ <div id="content" style="display: none"></div> <script> function doTest() { + var canTabMoveFocusToRootElement = + !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element"); document.getElementById("button").focus(); + const iframe = document.querySelector("iframe"); is(window.scrollY, 0, "Scrolled position initially 0"); synthesizeKey("KEY_Tab"); - ok(window.scrollY > 200, "Scrolled child frame into view"); + is(document.activeElement, iframe, "Focus moved to the iframe"); + if (canTabMoveFocusToRootElement) { + ok(window.scrollY > 200, "Scrolled child frame into view"); + } else { + is(window.scrollY, 0, "Scrolled position remained the same"); + } SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js index 366a9203c7..fd15348532 100644 --- a/dom/tests/mochitest/general/test_interfaces.js +++ b/dom/tests/mochitest/general/test_interfaces.js @@ -182,6 +182,10 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "AudioContext", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioData", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioDecoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "AudioBufferSourceNode", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "AudioDestinationNode", insecureContext: true }, @@ -262,11 +266,7 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "ConstantSourceNode", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { - name: "ContentVisibilityAutoStateChangeEvent", - insecureContext: true, - nightly: true, - }, + { name: "ContentVisibilityAutoStateChangeEvent", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "ConvolverNode", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! @@ -390,8 +390,6 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DOMRectReadOnly", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "DOMRequest", insecureContext: true, disabled: true }, - // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DOMStringList", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DOMStringMap", insecureContext: true }, @@ -406,6 +404,8 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "ElementInternals", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EncodedAudioChunk", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "EncodedVideoChunk", insecureContext: true, nightly: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "ErrorEvent", insecureContext: true }, @@ -456,23 +456,23 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "GainNode", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "Gamepad", insecureContext: false }, + { name: "Gamepad", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadAxisMoveEvent", insecureContext: false }, + { name: "GamepadAxisMoveEvent", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadButtonEvent", insecureContext: false }, + { name: "GamepadButtonEvent", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadButton", insecureContext: false }, + { name: "GamepadButton", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadEvent", insecureContext: false }, + { name: "GamepadEvent", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadHapticActuator", insecureContext: false }, + { name: "GamepadHapticActuator", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadLightIndicator", insecureContext: false, disabled: true }, + { name: "GamepadLightIndicator", insecureContext: true, disabled: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadPose", insecureContext: false }, + { name: "GamepadPose", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "GamepadTouch", insecureContext: false, disabled: true }, + { name: "GamepadTouch", insecureContext: true, disabled: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "Geolocation", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! @@ -1074,6 +1074,8 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "RTCIceCandidate", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCIceTransport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "RTCPeerConnection", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "RTCPeerConnectionIceEvent", insecureContext: true }, @@ -1645,7 +1647,7 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "onbeforeprint", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "onbeforetoggle", insecureContext: true, nightly: true }, + { name: "onbeforetoggle", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "onbeforeunload", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! @@ -1663,8 +1665,12 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "onclose", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncontextlost", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "oncontextmenu", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncontextrestored", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "oncopy", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "oncuechange", insecureContext: true }, diff --git a/dom/tests/mochitest/webcomponents/mochitest.toml b/dom/tests/mochitest/webcomponents/mochitest.toml index 8b9dad41d5..e43a4d1ccd 100644 --- a/dom/tests/mochitest/webcomponents/mochitest.toml +++ b/dom/tests/mochitest/webcomponents/mochitest.toml @@ -64,8 +64,6 @@ support-files = [ ["test_event_retarget.html"] -["test_event_stopping.html"] - ["test_link_prefetch.html"] ["test_shadowdom_active_pseudo_class.html"] diff --git a/dom/tests/mochitest/webcomponents/test_event_stopping.html b/dom/tests/mochitest/webcomponents/test_event_stopping.html deleted file mode 100644 index c90988d869..0000000000 --- a/dom/tests/mochitest/webcomponents/test_event_stopping.html +++ /dev/null @@ -1,174 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=887541 ---> -<head> - <title>Test for event model in web components</title> - <script type="text/javascript" src="head.js"></script> - <script src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887541">Bug 887541</a> -<script> - -var els = SpecialPowers.Cc["@mozilla.org/eventlistenerservice;1"] - .getService(SpecialPowers.Ci.nsIEventListenerService); - -SimpleTest.waitForExplicitFinish(); -createIframe() - .then((aDocument) => { - function eventListener(e) { - eventChain.push(this); - } - - function isEventChain(actual, expected, msg) { - is(actual.length, expected.length, msg); - for (var i = 0; i < expected.length; i++) { - is(actual[i], expected[i], msg + " at " + i); - } - - if (actual.length) { - var chain = els.getEventTargetChainFor(actual[0], false); // Events should be dispatched on actual[0]. - ok(expected.length < chain.length, "There should be additional chrome event targets."); - } - } - - /* - * <div elemOne> ------ <shadow-root shadowOne> - * | - * <span elemTwo> - * | - * <span elemThree> - */ - - var elemOne = aDocument.createElement("div"); - var elemTwo = aDocument.createElement("span"); - var elemThree = aDocument.createElement("span"); - var shadowOne = elemOne.attachShadow({mode: "open"}); - - shadowOne.appendChild(elemTwo); - elemTwo.appendChild(elemThree); - - // Test stopping "abort" event. - - elemOne.addEventListener("abort", eventListener); - elemTwo.addEventListener("abort", eventListener); - elemThree.addEventListener("abort", eventListener); - shadowOne.addEventListener("abort", eventListener); - - var eventChain = []; - - var customEvent = new CustomEvent("abort", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that abort event is stopped at shadow root."); - - // Test stopping "error" event. - - elemOne.addEventListener("error", eventListener); - elemTwo.addEventListener("error", eventListener); - elemThree.addEventListener("error", eventListener); - shadowOne.addEventListener("error", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("error", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that error event is stopped at shadow root."); - - // Test stopping "select" event. - - elemOne.addEventListener("select", eventListener); - elemTwo.addEventListener("select", eventListener); - elemThree.addEventListener("select", eventListener); - shadowOne.addEventListener("select", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("select", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that select event is stopped at shadow root."); - - // Test stopping "change" event. - - elemOne.addEventListener("change", eventListener); - elemTwo.addEventListener("change", eventListener); - elemThree.addEventListener("change", eventListener); - shadowOne.addEventListener("change", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("change", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - - // Test stopping "reset" event. - - elemOne.addEventListener("reset", eventListener); - elemTwo.addEventListener("reset", eventListener); - elemThree.addEventListener("reset", eventListener); - shadowOne.addEventListener("reset", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("reset", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that reset event is stopped at shadow root."); - - // Test stopping "load" event. - - elemOne.addEventListener("load", eventListener); - elemTwo.addEventListener("load", eventListener); - elemThree.addEventListener("load", eventListener); - shadowOne.addEventListener("load", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("load", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that load event is stopped at shadow root."); - - // Test stopping "resize" event. - - elemOne.addEventListener("resize", eventListener); - elemTwo.addEventListener("resize", eventListener); - elemThree.addEventListener("resize", eventListener); - shadowOne.addEventListener("resize", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("resize", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that resize event is stopped at shadow root."); - - // Test stopping "scroll" event. - - elemOne.addEventListener("scroll", eventListener); - elemTwo.addEventListener("scroll", eventListener); - elemThree.addEventListener("scroll", eventListener); - shadowOne.addEventListener("scroll", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("scroll", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that scroll event is stopped at shadow root."); - - // Test stopping "selectstart" event. - - elemOne.addEventListener("selectstart", eventListener); - elemTwo.addEventListener("selectstart", eventListener); - elemThree.addEventListener("selectstart", eventListener); - shadowOne.addEventListener("selectstart", eventListener); - - eventChain = []; - - customEvent = new CustomEvent("selectstart", { "bubbles" : true }); - elemThree.dispatchEvent(customEvent); - isEventChain(eventChain, [elemThree, elemTwo, shadowOne], "Test that selectstart event is stopped at shadow root."); - - SimpleTest.finish(); - }); -</script> -</body> -</html> diff --git a/dom/tests/unit/test_Fetch.js b/dom/tests/unit/test_Fetch.js index 768cf671dd..1449a23854 100644 --- a/dom/tests/unit/test_Fetch.js +++ b/dom/tests/unit/test_Fetch.js @@ -216,7 +216,7 @@ add_test(function test_getTestFailedConnect() { do_test_pending(); // try a server that's not there fetch("http://localhost:4/should/fail") - .then(response => { + .then(() => { do_throw("Request should not succeed"); }) .catch(err => { @@ -230,7 +230,7 @@ add_test(function test_mozError() { do_test_pending(); // try a server that's not there fetch("http://localhost:4/should/fail", { mozErrors: true }) - .then(response => { + .then(() => { do_throw("Request should not succeed"); }) .catch(err => { @@ -245,7 +245,7 @@ add_test(function test_request_mozError() { // try a server that's not there const r = new Request("http://localhost:4/should/fail", { mozErrors: true }); fetch(r) - .then(response => { + .then(() => { do_throw("Request should not succeed"); }) .catch(err => { diff --git a/dom/tests/unit/test_PromiseDebugging.js b/dom/tests/unit/test_PromiseDebugging.js index 2262d60d61..6148b65c75 100644 --- a/dom/tests/unit/test_PromiseDebugging.js +++ b/dom/tests/unit/test_PromiseDebugging.js @@ -2,7 +2,7 @@ function run_test() { // Hack around Promise.jsm being stuck on my global Assert.equal(false, PromiseDebugging === undefined); var res; - var p = new Promise(function (resolve, reject) { + var p = new Promise(function (resolve) { res = resolve; }); var state = PromiseDebugging.getState(p); diff --git a/dom/tests/unit/test_geolocation_monitor.js b/dom/tests/unit/test_geolocation_monitor.js index f0fc1ad74b..7b47463882 100644 --- a/dom/tests/unit/test_geolocation_monitor.js +++ b/dom/tests/unit/test_geolocation_monitor.js @@ -56,7 +56,7 @@ function watchPosition() { let observer = { QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), - observe(subject, topic, data) { + observe(subject, topic) { Assert.equal(topic, "geolocation-position-events"); observer._countEvents++; observer._lastData = toJSON(subject); diff --git a/dom/tests/unit/test_geolocation_provider.js b/dom/tests/unit/test_geolocation_provider.js index 8c58cd39f5..e08887bfce 100644 --- a/dom/tests/unit/test_geolocation_provider.js +++ b/dom/tests/unit/test_geolocation_provider.js @@ -12,10 +12,10 @@ function terminate(succ) { geolocation.clearWatch(watchID); } -function successCallback(pos) { +function successCallback() { terminate(true); } -function errorCallback(pos) { +function errorCallback() { terminate(false); } diff --git a/dom/url/tests/browser_download_after_revoke.js b/dom/url/tests/browser_download_after_revoke.js index be8ad96e70..f145b36281 100644 --- a/dom/url/tests/browser_download_after_revoke.js +++ b/dom/url/tests/browser_download_after_revoke.js @@ -43,7 +43,7 @@ async function test() { domwindow.addEventListener("load", onModalLoad, true); }, - onCloseWindow(aXULWindow) {}, + onCloseWindow() {}, }; Services.wm.addListener(listener); diff --git a/dom/url/tests/protocol_worker.js b/dom/url/tests/protocol_worker.js index c038254a4c..81e761ee0e 100644 --- a/dom/url/tests/protocol_worker.js +++ b/dom/url/tests/protocol_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(a, msg) { postMessage({ type: "status", status: !!a, msg }); } diff --git a/dom/url/tests/urlSearchParams_commons.js b/dom/url/tests/urlSearchParams_commons.js index 3a1dcb2807..4a1308b94b 100644 --- a/dom/url/tests/urlSearchParams_commons.js +++ b/dom/url/tests/urlSearchParams_commons.js @@ -1,5 +1,9 @@ /* import-globals-from urlSearchParams_worker.js */ +// This file gets included into a worker which doesn't have any +// assertion methods besides `ok` and `is`. +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function testSimpleURLSearchParams() { var u = new URLSearchParams(); ok(u, "URLSearchParams created"); diff --git a/dom/url/tests/url_exceptions_worker.js b/dom/url/tests/url_exceptions_worker.js index c8d8494ac9..8bbd641136 100644 --- a/dom/url/tests/url_exceptions_worker.js +++ b/dom/url/tests/url_exceptions_worker.js @@ -2,7 +2,7 @@ function ok(a, msg) { postMessage({ type: "status", status: !!a, msg }); } -onmessage = function (event) { +onmessage = function () { // URL.href throws var url = new URL("http://www.example.com"); ok(url, "URL created"); diff --git a/dom/vr/XRSystem.cpp b/dom/vr/XRSystem.cpp index 09b9e07476..2dd66e41f4 100644 --- a/dom/vr/XRSystem.cpp +++ b/dom/vr/XRSystem.cpp @@ -122,8 +122,8 @@ already_AddRefed<Promise> XRSystem::IsSessionSupported(XRSessionMode aMode, } already_AddRefed<Promise> XRSystem::RequestSession( - JSContext* aCx, XRSessionMode aMode, const XRSessionInit& aOptions, - CallerType aCallerType, ErrorResult& aRv) { + XRSessionMode aMode, const XRSessionInit& aOptions, CallerType aCallerType, + ErrorResult& aRv) { nsCOMPtr<nsIGlobalObject> global = GetParentObject(); NS_ENSURE_TRUE(global, nullptr); @@ -166,49 +166,25 @@ already_AddRefed<Promise> XRSystem::RequestSession( requiredReferenceSpaceTypes.AppendElement(XRReferenceSpaceType::Local); } - BindingCallContext callCx(aCx, "XRSystem.requestSession"); - if (aOptions.mRequiredFeatures.WasPassed()) { - const Sequence<JS::Value>& arr = (aOptions.mRequiredFeatures.Value()); - for (const JS::Value& val : arr) { - if (!val.isNull() && !val.isUndefined()) { - bool bFound = false; - JS::Rooted<JS::Value> v(aCx, val); - int index = 0; - if (FindEnumStringIndex<false>( - callCx, v, XRReferenceSpaceTypeValues::strings, - "XRReferenceSpaceType", "Argument 2 of XR.requestSession", - &index)) { - if (index >= 0) { - requiredReferenceSpaceTypes.AppendElement( - static_cast<XRReferenceSpaceType>(index)); - bFound = true; - } - } - if (!bFound) { - promise->MaybeRejectWithNotSupportedError( - "A required feature for the XRSession is not available."); - return promise.forget(); - } + for (const nsString& val : aOptions.mRequiredFeatures.Value()) { + Maybe<XRReferenceSpaceType> type = + StringToEnum<XRReferenceSpaceType>(val); + if (type.isNothing()) { + promise->MaybeRejectWithNotSupportedError( + "A required feature for the XRSession is not available."); + return promise.forget(); } + requiredReferenceSpaceTypes.AppendElement(type.value()); } } if (aOptions.mOptionalFeatures.WasPassed()) { - const Sequence<JS::Value>& arr = (aOptions.mOptionalFeatures.Value()); - for (const JS::Value& val : arr) { - if (!val.isNull() && !val.isUndefined()) { - JS::Rooted<JS::Value> v(aCx, val); - int index = 0; - if (FindEnumStringIndex<false>( - callCx, v, XRReferenceSpaceTypeValues::strings, - "XRReferenceSpaceType", "Argument 2 of XR.requestSession", - &index)) { - if (index >= 0) { - optionalReferenceSpaceTypes.AppendElement( - static_cast<XRReferenceSpaceType>(index)); - } - } + for (const nsString& val : aOptions.mOptionalFeatures.Value()) { + Maybe<XRReferenceSpaceType> type = + StringToEnum<XRReferenceSpaceType>(val); + if (type.isSome()) { + optionalReferenceSpaceTypes.AppendElement(type.value()); } } } diff --git a/dom/vr/XRSystem.h b/dom/vr/XRSystem.h index 2a49dfb68f..860098e30c 100644 --- a/dom/vr/XRSystem.h +++ b/dom/vr/XRSystem.h @@ -120,7 +120,7 @@ class XRSystem final : public DOMEventTargetHelper, // WebIDL Members already_AddRefed<Promise> IsSessionSupported(XRSessionMode aMode, ErrorResult& aRv); - already_AddRefed<Promise> RequestSession(JSContext* aCx, XRSessionMode aMode, + already_AddRefed<Promise> RequestSession(XRSessionMode aMode, const XRSessionInit& aOptions, CallerType aCallerType, ErrorResult& aRv); diff --git a/dom/vr/test/reftest/reftest.list b/dom/vr/test/reftest/reftest.list index c1ca292c71..ac012495cb 100644 --- a/dom/vr/test/reftest/reftest.list +++ b/dom/vr/test/reftest/reftest.list @@ -2,9 +2,7 @@ # Please confirm there is no other VR display connected. Otherwise, VRPuppetDisplay can't be attached. defaults pref(dom.vr.enabled,true) pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,true) pref(dom.vr.require-gesture,false) pref(dom.vr.puppet.submitframe,1) pref(dom.vr.display.rafMaxDuration,200) pref(dom.vr.display.enumerate.interval,0) pref(dom.vr.controller.enumerate.interval,0) # WebVR Tests have been disabled as refactoring of gfxVRPuppet is landing. Dependencies for re-enabling these are tracked by meta bug 1555185. -# VR SubmitFrame is only implemented for D3D11.1 and MacOSX now. -# Our Windows 7 test machines don't support D3D11.1, so we run these tests on Windows 8+ only. -# skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated) == draw_rect.html wrapper.html?draw_rect.png +# skip-if((!winWidget&&release_or_beta)||Android||gtkWidget) == draw_rect.html wrapper.html?draw_rect.png # On MacOSX platform, getting different color interpolation result. # For lower resolution Mac hardware, we need to adjust it to fuzzy-if(cocoaWidget,0-1,0-1200). -# fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||cocoaWidget,0-1,0-600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated) == change_size.html wrapper.html?change_size.png +# fuzzy-if(winWidget||cocoaWidget,0-1,0-600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget) == change_size.html wrapper.html?change_size.png diff --git a/dom/webauthn/MacOSWebAuthnService.mm b/dom/webauthn/MacOSWebAuthnService.mm index 79b9030541..cec9600e57 100644 --- a/dom/webauthn/MacOSWebAuthnService.mm +++ b/dom/webauthn/MacOSWebAuthnService.mm @@ -545,6 +545,19 @@ MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId, "MacOSWebAuthnService::MakeCredential", [self = RefPtr{this}, browsingContextId(aBrowsingContextId), aArgs = nsCOMPtr{aArgs}, aPromise = nsCOMPtr{aPromise}]() { + // Bug 1884574 - The Reset() call above should have cancelled any + // transactions that were dispatched to the platform, the platform + // should have called didCompleteWithError, and didCompleteWithError + // should have rejected the pending promise. However, in some scenarios, + // the platform fails to call the callback, and this leads to a + // diagnostic assertion failure when we drop `mRegisterPromise`. Avoid + // this by aborting the transaction here. + if (self->mRegisterPromise) { + MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug, + ("MacOSAuthenticatorRequestDelegate::MakeCredential: " + "platform failed to call callback")); + self->AbortTransaction(NS_ERROR_DOM_ABORT_ERR); + } self->mRegisterPromise = aPromise; nsAutoString rpId; @@ -847,6 +860,14 @@ void MacOSWebAuthnService::DoGetAssertion( [self = RefPtr{this}, browsingContextId(aBrowsingContextId), aArgs, aPromise, aSelectedCredentialId = std::move(aSelectedCredentialId)]() mutable { + // Bug 1884574 - This AbortTransaction call is necessary. + // See comment in MacOSWebAuthnService::MakeCredential. + if (self->mSignPromise) { + MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug, + ("MacOSAuthenticatorRequestDelegate::DoGetAssertion: " + "platform failed to call callback")); + self->AbortTransaction(NS_ERROR_DOM_ABORT_ERR); + } self->mSignPromise = aPromise; nsAutoString rpId; diff --git a/dom/webauthn/moz.build b/dom/webauthn/moz.build index 5d84dc06e7..47309034c8 100644 --- a/dom/webauthn/moz.build +++ b/dom/webauthn/moz.build @@ -70,7 +70,7 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": "AndroidWebAuthnService.cpp", ] -if CONFIG["OS_ARCH"] == "Darwin": +if CONFIG["TARGET_OS"] == "OSX": UNIFIED_SOURCES += [ "MacOSWebAuthnService.mm", ] diff --git a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp index dea0b98023..78641582ce 100644 --- a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp +++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -29,7 +29,6 @@ #include "mozilla/Unused.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" -#include "nsContentCID.h" #include "nsCycleCollectionParticipant.h" #include "nsDOMAttributeMap.h" #include "nsFrameLoader.h" diff --git a/dom/webgpu/Adapter.cpp b/dom/webgpu/Adapter.cpp index 434ba7c6fa..7c0e72eac4 100644 --- a/dom/webgpu/Adapter.cpp +++ b/dom/webgpu/Adapter.cpp @@ -128,9 +128,6 @@ static Maybe<ffi::WGPUFeatures> ToWGPUFeatures( case dom::GPUFeatureName::Float32_filterable: return Some(WGPUFeatures_FLOAT32_FILTERABLE); - - case dom::GPUFeatureName::EndGuard_: - break; } MOZ_CRASH("Bad GPUFeatureName."); } @@ -141,11 +138,11 @@ static Maybe<ffi::WGPUFeatures> MakeFeatureBits( for (const auto& feature : aFeatures) { const auto bit = ToWGPUFeatures(feature); if (!bit) { - const auto featureStr = dom::GPUFeatureNameValues::GetString(feature); + const auto featureStr = dom::GetEnumString(feature); (void)featureStr; NS_WARNING( nsPrintfCString("Requested feature bit for '%s' is not implemented.", - featureStr.data()) + featureStr.get()) .get()); return Nothing(); } @@ -169,7 +166,7 @@ Adapter::Adapter(Instance* const aParent, WebGPUChild* const aBridge, auto ret = std::unordered_map<ffi::WGPUFeatures, dom::GPUFeatureName>{}; for (const auto feature : - MakeEnumeratedRange(dom::GPUFeatureName::EndGuard_)) { + dom::MakeWebIDLEnumeratedRange<dom::GPUFeatureName>()) { const auto bitForFeature = ToWGPUFeatures(feature); if (!bitForFeature) { // There are some features that don't have bits. @@ -363,12 +360,12 @@ already_AddRefed<dom::Promise> Adapter::RequestDevice( for (const auto requested : aDesc.mRequiredFeatures) { const bool supported = mFeatures->Features().count(requested); if (!supported) { - const auto fstr = dom::GPUFeatureNameValues::GetString(requested); + const auto fstr = dom::GetEnumString(requested); const auto astr = this->LabelOrId(); nsPrintfCString msg( "requestDevice: Feature '%s' requested must be supported by " "adapter %s", - fstr.data(), astr.get()); + fstr.get(), astr.get()); promise->MaybeRejectWithTypeError(msg); return; } diff --git a/dom/webgpu/CanvasContext.h b/dom/webgpu/CanvasContext.h index 58ef04e861..057287b26e 100644 --- a/dom/webgpu/CanvasContext.h +++ b/dom/webgpu/CanvasContext.h @@ -84,6 +84,8 @@ class CanvasContext final : public nsICanvasRenderingContextInternal, already_AddRefed<layers::FwdTransactionTracker> UseCompositableForwarder( layers::CompositableForwarder* aForwarder) override; + bool IsOffscreenCanvas() { return !!mOffscreenCanvas; } + public: void GetCanvas(dom::OwningHTMLCanvasElementOrOffscreenCanvas&) const; diff --git a/dom/webgpu/CommandBuffer.cpp b/dom/webgpu/CommandBuffer.cpp index ff9bbd8d5d..59023018ef 100644 --- a/dom/webgpu/CommandBuffer.cpp +++ b/dom/webgpu/CommandBuffer.cpp @@ -16,10 +16,13 @@ namespace mozilla::webgpu { GPU_IMPL_CYCLE_COLLECTION(CommandBuffer, mParent) GPU_IMPL_JS_WRAP(CommandBuffer) -CommandBuffer::CommandBuffer(Device* const aParent, RawId aId, - nsTArray<WeakPtr<CanvasContext>>&& aTargetContexts, - RefPtr<CommandEncoder>&& aEncoder) - : ChildOf(aParent), mId(aId), mTargetContexts(std::move(aTargetContexts)) { +CommandBuffer::CommandBuffer( + Device* const aParent, RawId aId, + nsTArray<WeakPtr<CanvasContext>>&& aPresentationContexts, + RefPtr<CommandEncoder>&& aEncoder) + : ChildOf(aParent), + mId(aId), + mPresentationContexts(std::move(aPresentationContexts)) { mEncoder = std::move(aEncoder); MOZ_RELEASE_ASSERT(aId); } @@ -33,9 +36,9 @@ Maybe<RawId> CommandBuffer::Commit() { return Nothing(); } mValid = false; - for (const auto& targetContext : mTargetContexts) { - if (targetContext) { - targetContext->MaybeQueueSwapChainPresent(); + for (const auto& presentationContext : mPresentationContexts) { + if (presentationContext) { + presentationContext->MaybeQueueSwapChainPresent(); } } return Some(mId); diff --git a/dom/webgpu/CommandBuffer.h b/dom/webgpu/CommandBuffer.h index b9c2495fb7..dff3e14d75 100644 --- a/dom/webgpu/CommandBuffer.h +++ b/dom/webgpu/CommandBuffer.h @@ -22,7 +22,7 @@ class CommandBuffer final : public ObjectBase, public ChildOf<Device> { GPU_DECL_JS_WRAP(CommandBuffer) CommandBuffer(Device* const aParent, RawId aId, - nsTArray<WeakPtr<CanvasContext>>&& aTargetContexts, + nsTArray<WeakPtr<CanvasContext>>&& aPresentationContexts, RefPtr<CommandEncoder>&& aEncoder); Maybe<RawId> Commit(); @@ -33,7 +33,7 @@ class CommandBuffer final : public ObjectBase, public ChildOf<Device> { void Cleanup(); const RawId mId; - const nsTArray<WeakPtr<CanvasContext>> mTargetContexts; + const nsTArray<WeakPtr<CanvasContext>> mPresentationContexts; // Command buffers and encoders share the same identity (this is a // simplifcation currently made by wgpu). To avoid dropping the same ID twice, // the wgpu resource lifetime is tied to the encoder which is held alive by diff --git a/dom/webgpu/CommandEncoder.cpp b/dom/webgpu/CommandEncoder.cpp index 15d95401d4..f254c9d8b9 100644 --- a/dom/webgpu/CommandEncoder.cpp +++ b/dom/webgpu/CommandEncoder.cpp @@ -3,6 +3,7 @@ * 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 "mozilla/dom/UnionTypes.h" #include "mozilla/dom/WebGPUBinding.h" #include "CommandEncoder.h" @@ -89,6 +90,14 @@ void CommandEncoder::Cleanup() { } } +void CommandEncoder::TrackPresentationContext(CanvasContext* aTargetContext) { + if (aTargetContext) { + if (!aTargetContext->IsOffscreenCanvas()) { + mPresentationContexts.AppendElement(aTargetContext); + } + } +} + void CommandEncoder::CopyBufferToBuffer(const Buffer& aSource, BufferAddress aSourceOffset, const Buffer& aDestination, @@ -121,10 +130,7 @@ void CommandEncoder::CopyBufferToTexture( ConvertExtent(aCopySize), ToFFI(&bb)); mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb)); - const auto& targetContext = aDestination.mTexture->mTargetContext; - if (targetContext) { - mTargetContexts.AppendElement(targetContext); - } + TrackPresentationContext(aDestination.mTexture->mTargetContext); } void CommandEncoder::CopyTextureToBuffer( const dom::GPUImageCopyTexture& aSource, @@ -156,10 +162,7 @@ void CommandEncoder::CopyTextureToTexture( ConvertExtent(aCopySize), ToFFI(&bb)); mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb)); - const auto& targetContext = aDestination.mTexture->mTargetContext; - if (targetContext) { - mTargetContexts.AppendElement(targetContext); - } + TrackPresentationContext(aDestination.mTexture->mTargetContext); } void CommandEncoder::ClearBuffer(const Buffer& aBuffer, const uint64_t aOffset, @@ -216,13 +219,9 @@ already_AddRefed<ComputePassEncoder> CommandEncoder::BeginComputePass( already_AddRefed<RenderPassEncoder> CommandEncoder::BeginRenderPass( const dom::GPURenderPassDescriptor& aDesc) { for (const auto& at : aDesc.mColorAttachments) { - auto* targetContext = at.mView->GetTargetContext(); - if (targetContext) { - mTargetContexts.AppendElement(targetContext); - } + TrackPresentationContext(at.mView->GetTargetContext()); if (at.mResolveTarget.WasPassed()) { - targetContext = at.mResolveTarget.Value().GetTargetContext(); - mTargetContexts.AppendElement(targetContext); + TrackPresentationContext(at.mResolveTarget.Value().GetTargetContext()); } } @@ -230,24 +229,24 @@ already_AddRefed<RenderPassEncoder> CommandEncoder::BeginRenderPass( return pass.forget(); } -void CommandEncoder::EndComputePass(ffi::WGPUComputePass& aPass) { +void CommandEncoder::EndComputePass(ffi::WGPURecordedComputePass& aPass) { if (!mBridge->IsOpen()) { return; } ipc::ByteBuf byteBuf; ffi::wgpu_compute_pass_finish(&aPass, ToFFI(&byteBuf)); - mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(byteBuf)); + mBridge->SendComputePass(mId, mParent->mId, std::move(byteBuf)); } -void CommandEncoder::EndRenderPass(ffi::WGPURenderPass& aPass) { +void CommandEncoder::EndRenderPass(ffi::WGPURecordedRenderPass& aPass) { if (!mBridge->IsOpen()) { return; } ipc::ByteBuf byteBuf; ffi::wgpu_render_pass_finish(&aPass, ToFFI(&byteBuf)); - mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(byteBuf)); + mBridge->SendRenderPass(mId, mParent->mId, std::move(byteBuf)); } already_AddRefed<CommandBuffer> CommandEncoder::Finish( @@ -263,7 +262,7 @@ already_AddRefed<CommandBuffer> CommandEncoder::Finish( RefPtr<CommandEncoder> me(this); RefPtr<CommandBuffer> comb = new CommandBuffer( - mParent, mId, std::move(mTargetContexts), std::move(me)); + mParent, mId, std::move(mPresentationContexts), std::move(me)); return comb.forget(); } diff --git a/dom/webgpu/CommandEncoder.h b/dom/webgpu/CommandEncoder.h index 52b10a5b2e..99a2fb15b4 100644 --- a/dom/webgpu/CommandEncoder.h +++ b/dom/webgpu/CommandEncoder.h @@ -32,7 +32,7 @@ using GPUExtent3D = RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict; namespace webgpu { namespace ffi { struct WGPUComputePass; -struct WGPURenderPass; +struct WGPURecordedRenderPass; struct WGPUImageDataLayout; struct WGPUImageCopyTexture_TextureId; struct WGPUExtent3d; @@ -67,13 +67,15 @@ class CommandEncoder final : public ObjectBase, public ChildOf<Device> { void Cleanup(); RefPtr<WebGPUChild> mBridge; - nsTArray<WeakPtr<CanvasContext>> mTargetContexts; + nsTArray<WeakPtr<CanvasContext>> mPresentationContexts; + + void TrackPresentationContext(CanvasContext* aTargetContext); public: const auto& GetDevice() const { return mParent; }; - void EndComputePass(ffi::WGPUComputePass& aPass); - void EndRenderPass(ffi::WGPURenderPass& aPass); + void EndComputePass(ffi::WGPURecordedComputePass& aPass); + void EndRenderPass(ffi::WGPURecordedRenderPass& aPass); void CopyBufferToBuffer(const Buffer& aSource, BufferAddress aSourceOffset, const Buffer& aDestination, diff --git a/dom/webgpu/ComputePassEncoder.cpp b/dom/webgpu/ComputePassEncoder.cpp index 2820a575e8..190bbf00a7 100644 --- a/dom/webgpu/ComputePassEncoder.cpp +++ b/dom/webgpu/ComputePassEncoder.cpp @@ -17,13 +17,13 @@ GPU_IMPL_CYCLE_COLLECTION(ComputePassEncoder, mParent, mUsedBindGroups, mUsedPipelines) GPU_IMPL_JS_WRAP(ComputePassEncoder) -void ffiWGPUComputePassDeleter::operator()(ffi::WGPUComputePass* raw) { +void ffiWGPUComputePassDeleter::operator()(ffi::WGPURecordedComputePass* raw) { if (raw) { ffi::wgpu_compute_pass_destroy(raw); } } -ffi::WGPUComputePass* BeginComputePass( +ffi::WGPURecordedComputePass* BeginComputePass( RawId aEncoderId, const dom::GPUComputePassDescriptor& aDesc) { MOZ_RELEASE_ASSERT(aEncoderId); ffi::WGPUComputePassDescriptor desc = {}; @@ -31,7 +31,7 @@ ffi::WGPUComputePass* BeginComputePass( webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); - return ffi::wgpu_command_encoder_begin_compute_pass(aEncoderId, &desc); + return ffi::wgpu_command_encoder_begin_compute_pass(&desc); } ComputePassEncoder::ComputePassEncoder( @@ -49,16 +49,16 @@ void ComputePassEncoder::SetBindGroup( const dom::Sequence<uint32_t>& aDynamicOffsets) { if (mValid) { mUsedBindGroups.AppendElement(&aBindGroup); - ffi::wgpu_compute_pass_set_bind_group(mPass.get(), aSlot, aBindGroup.mId, - aDynamicOffsets.Elements(), - aDynamicOffsets.Length()); + ffi::wgpu_recorded_compute_pass_set_bind_group( + mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(), + aDynamicOffsets.Length()); } } void ComputePassEncoder::SetPipeline(const ComputePipeline& aPipeline) { if (mValid) { mUsedPipelines.AppendElement(&aPipeline); - ffi::wgpu_compute_pass_set_pipeline(mPass.get(), aPipeline.mId); + ffi::wgpu_recorded_compute_pass_set_pipeline(mPass.get(), aPipeline.mId); } } @@ -66,7 +66,7 @@ void ComputePassEncoder::DispatchWorkgroups(uint32_t workgroupCountX, uint32_t workgroupCountY, uint32_t workgroupCountZ) { if (mValid) { - ffi::wgpu_compute_pass_dispatch_workgroups( + ffi::wgpu_recorded_compute_pass_dispatch_workgroups( mPass.get(), workgroupCountX, workgroupCountY, workgroupCountZ); } } @@ -74,7 +74,7 @@ void ComputePassEncoder::DispatchWorkgroups(uint32_t workgroupCountX, void ComputePassEncoder::DispatchWorkgroupsIndirect( const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) { if (mValid) { - ffi::wgpu_compute_pass_dispatch_workgroups_indirect( + ffi::wgpu_recorded_compute_pass_dispatch_workgroups_indirect( mPass.get(), aIndirectBuffer.mId, aIndirectOffset); } } @@ -82,18 +82,20 @@ void ComputePassEncoder::DispatchWorkgroupsIndirect( void ComputePassEncoder::PushDebugGroup(const nsAString& aString) { if (mValid) { const NS_ConvertUTF16toUTF8 utf8(aString); - ffi::wgpu_compute_pass_push_debug_group(mPass.get(), utf8.get(), 0); + ffi::wgpu_recorded_compute_pass_push_debug_group(mPass.get(), utf8.get(), + 0); } } void ComputePassEncoder::PopDebugGroup() { if (mValid) { - ffi::wgpu_compute_pass_pop_debug_group(mPass.get()); + ffi::wgpu_recorded_compute_pass_pop_debug_group(mPass.get()); } } void ComputePassEncoder::InsertDebugMarker(const nsAString& aString) { if (mValid) { const NS_ConvertUTF16toUTF8 utf8(aString); - ffi::wgpu_compute_pass_insert_debug_marker(mPass.get(), utf8.get(), 0); + ffi::wgpu_recorded_compute_pass_insert_debug_marker(mPass.get(), utf8.get(), + 0); } } diff --git a/dom/webgpu/ComputePassEncoder.h b/dom/webgpu/ComputePassEncoder.h index 8160a09e2e..2455822f79 100644 --- a/dom/webgpu/ComputePassEncoder.h +++ b/dom/webgpu/ComputePassEncoder.h @@ -18,7 +18,7 @@ struct GPUComputePassDescriptor; namespace webgpu { namespace ffi { -struct WGPUComputePass; +struct WGPURecordedComputePass; } // namespace ffi class BindGroup; @@ -27,7 +27,7 @@ class CommandEncoder; class ComputePipeline; struct ffiWGPUComputePassDeleter { - void operator()(ffi::WGPUComputePass*); + void operator()(ffi::WGPURecordedComputePass*); }; class ComputePassEncoder final : public ObjectBase, @@ -43,7 +43,8 @@ class ComputePassEncoder final : public ObjectBase, virtual ~ComputePassEncoder(); void Cleanup() {} - std::unique_ptr<ffi::WGPUComputePass, ffiWGPUComputePassDeleter> mPass; + std::unique_ptr<ffi::WGPURecordedComputePass, ffiWGPUComputePassDeleter> + mPass; // keep all the used objects alive while the pass is recorded nsTArray<RefPtr<const BindGroup>> mUsedBindGroups; nsTArray<RefPtr<const ComputePipeline>> mUsedPipelines; diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp index a659047af1..a9fd5ee44c 100644 --- a/dom/webgpu/Device.cpp +++ b/dom/webgpu/Device.cpp @@ -247,6 +247,7 @@ already_AddRefed<Sampler> Device::CreateSampler( desc.mipmap_filter = ffi::WGPUFilterMode(aDesc.mMipmapFilter); desc.lod_min_clamp = aDesc.mLodMinClamp; desc.lod_max_clamp = aDesc.mLodMaxClamp; + desc.max_anisotropy = aDesc.mMaxAnisotropy; ffi::WGPUCompareFunction comparison = ffi::WGPUCompareFunction_Sentinel; if (aDesc.mCompare.WasPassed()) { @@ -320,8 +321,6 @@ already_AddRefed<BindGroupLayout> Device::CreateBindGroupLayout( case dom::GPUTextureSampleType::Depth: data.type = ffi::WGPURawTextureSampleType_Depth; break; - case dom::GPUTextureSampleType::EndGuard_: - MOZ_ASSERT_UNREACHABLE(); } } if (entry.mStorageTexture.WasPassed()) { @@ -349,8 +348,6 @@ already_AddRefed<BindGroupLayout> Device::CreateBindGroupLayout( case dom::GPUBufferBindingType::Read_only_storage: e.ty = ffi::WGPURawBindingType_ReadonlyStorageBuffer; break; - case dom::GPUBufferBindingType::EndGuard_: - MOZ_ASSERT_UNREACHABLE(); } e.has_dynamic_offset = entry.mBuffer.Value().mHasDynamicOffset; } @@ -361,10 +358,23 @@ already_AddRefed<BindGroupLayout> Device::CreateBindGroupLayout( e.multisampled = entry.mTexture.Value().mMultisampled; } if (entry.mStorageTexture.WasPassed()) { - e.ty = entry.mStorageTexture.Value().mAccess == - dom::GPUStorageTextureAccess::Write_only - ? ffi::WGPURawBindingType_WriteonlyStorageTexture - : ffi::WGPURawBindingType_ReadonlyStorageTexture; + switch (entry.mStorageTexture.Value().mAccess) { + case dom::GPUStorageTextureAccess::Write_only: { + e.ty = ffi::WGPURawBindingType_WriteonlyStorageTexture; + break; + } + case dom::GPUStorageTextureAccess::Read_only: { + e.ty = ffi::WGPURawBindingType_ReadonlyStorageTexture; + break; + } + case dom::GPUStorageTextureAccess::Read_write: { + e.ty = ffi::WGPURawBindingType_ReadWriteStorageTexture; + break; + } + default: { + MOZ_ASSERT_UNREACHABLE(); + } + } e.view_dimension = &optional[i].dim; e.storage_texture_format = &optional[i].format; } @@ -379,8 +389,6 @@ already_AddRefed<BindGroupLayout> Device::CreateBindGroupLayout( case dom::GPUSamplerBindingType::Comparison: e.sampler_compare = true; break; - case dom::GPUSamplerBindingType::EndGuard_: - MOZ_ASSERT_UNREACHABLE(); } } entries.AppendElement(e); @@ -671,8 +679,12 @@ RawId CreateComputePipelineImpl(PipelineCreationContext* const aContext, MOZ_ASSERT_UNREACHABLE(); } desc.stage.module = aDesc.mCompute.mModule->mId; - CopyUTF16toUTF8(aDesc.mCompute.mEntryPoint, entryPoint); - desc.stage.entry_point = entryPoint.get(); + if (aDesc.mCompute.mEntryPoint.WasPassed()) { + CopyUTF16toUTF8(aDesc.mCompute.mEntryPoint.Value(), entryPoint); + desc.stage.entry_point = entryPoint.get(); + } else { + desc.stage.entry_point = nullptr; + } RawId implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {}; RawId id = ffi::wgpu_client_create_compute_pipeline( @@ -717,8 +729,12 @@ RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext, { const auto& stage = aDesc.mVertex; vertexState.stage.module = stage.mModule->mId; - CopyUTF16toUTF8(stage.mEntryPoint, vsEntry); - vertexState.stage.entry_point = vsEntry.get(); + if (stage.mEntryPoint.WasPassed()) { + CopyUTF16toUTF8(stage.mEntryPoint.Value(), vsEntry); + vertexState.stage.entry_point = vsEntry.get(); + } else { + vertexState.stage.entry_point = nullptr; + } for (const auto& vertex_desc : stage.mBuffers) { ffi::WGPUVertexBufferLayout vb_desc = {}; @@ -753,8 +769,12 @@ RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext, if (aDesc.mFragment.WasPassed()) { const auto& stage = aDesc.mFragment.Value(); fragmentState.stage.module = stage.mModule->mId; - CopyUTF16toUTF8(stage.mEntryPoint, fsEntry); - fragmentState.stage.entry_point = fsEntry.get(); + if (stage.mEntryPoint.WasPassed()) { + CopyUTF16toUTF8(stage.mEntryPoint.Value(), fsEntry); + fragmentState.stage.entry_point = fsEntry.get(); + } else { + fragmentState.stage.entry_point = nullptr; + } // Note: we pre-collect the blend states into a different array // so that we can have non-stale pointers into it. diff --git a/dom/webgpu/Queue.cpp b/dom/webgpu/Queue.cpp index 26952ee173..ca25b2f290 100644 --- a/dom/webgpu/Queue.cpp +++ b/dom/webgpu/Queue.cpp @@ -69,18 +69,36 @@ void Queue::WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset, return; } - dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) { - uint64_t length = aData.Length(); - const auto checkedSize = aSize.WasPassed() - ? CheckedInt<size_t>(aSize.Value()) - : CheckedInt<size_t>(length) - aDataOffset; - if (!checkedSize.isValid()) { + size_t elementByteSize = 1; + if (aData.IsArrayBufferView()) { + auto type = aData.GetAsArrayBufferView().Type(); + if (type != JS::Scalar::MaxTypedArrayViewType) { + elementByteSize = byteSize(type); + } + } + dom::ProcessTypedArraysFixed(aData, [&, elementByteSize]( + const Span<const uint8_t>& aData) { + uint64_t byteLength = aData.Length(); + + auto checkedByteOffset = + CheckedInt<uint64_t>(aDataOffset) * elementByteSize; + if (!checkedByteOffset.isValid()) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + auto offset = checkedByteOffset.value(); + + const auto checkedByteSize = + aSize.WasPassed() ? CheckedInt<size_t>(aSize.Value()) * elementByteSize + : CheckedInt<size_t>(byteLength) - offset; + if (!checkedByteSize.isValid()) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } + auto size = checkedByteSize.value(); - const auto& size = checkedSize.value(); - if (aDataOffset + size > length) { + auto checkedByteEnd = CheckedInt<uint64_t>(offset) + size; + if (!checkedByteEnd.isValid() || checkedByteEnd.value() > byteLength) { aRv.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR, size)); return; } diff --git a/dom/webgpu/RenderPassEncoder.cpp b/dom/webgpu/RenderPassEncoder.cpp index c5cb19ce15..03c16ea3a4 100644 --- a/dom/webgpu/RenderPassEncoder.cpp +++ b/dom/webgpu/RenderPassEncoder.cpp @@ -18,7 +18,7 @@ GPU_IMPL_CYCLE_COLLECTION(RenderPassEncoder, mParent, mUsedBindGroups, mUsedRenderBundles) GPU_IMPL_JS_WRAP(RenderPassEncoder) -void ffiWGPURenderPassDeleter::operator()(ffi::WGPURenderPass* raw) { +void ffiWGPURenderPassDeleter::operator()(ffi::WGPURecordedRenderPass* raw) { if (raw) { ffi::wgpu_render_pass_destroy(raw); } @@ -30,8 +30,6 @@ static ffi::WGPULoadOp ConvertLoadOp(const dom::GPULoadOp& aOp) { return ffi::WGPULoadOp_Load; case dom::GPULoadOp::Clear: return ffi::WGPULoadOp_Clear; - case dom::GPULoadOp::EndGuard_: - break; } MOZ_CRASH("bad GPULoadOp"); } @@ -42,8 +40,6 @@ static ffi::WGPUStoreOp ConvertStoreOp(const dom::GPUStoreOp& aOp) { return ffi::WGPUStoreOp_Store; case dom::GPUStoreOp::Discard: return ffi::WGPUStoreOp_Discard; - case dom::GPUStoreOp::EndGuard_: - break; } MOZ_CRASH("bad GPUStoreOp"); } @@ -87,7 +83,7 @@ static ffi::WGPUColor ConvertColor( return ffi::WGPUColor(); } -ffi::WGPURenderPass* BeginRenderPass( +ffi::WGPURecordedRenderPass* BeginRenderPass( CommandEncoder* const aParent, const dom::GPURenderPassDescriptor& aDesc) { ffi::WGPURenderPassDescriptor desc = {}; @@ -155,7 +151,7 @@ ffi::WGPURenderPass* BeginRenderPass( } } - return ffi::wgpu_command_encoder_begin_render_pass(aParent->mId, &desc); + return ffi::wgpu_command_encoder_begin_render_pass(&desc); } RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent, @@ -186,16 +182,16 @@ void RenderPassEncoder::SetBindGroup( const dom::Sequence<uint32_t>& aDynamicOffsets) { if (mValid) { mUsedBindGroups.AppendElement(&aBindGroup); - ffi::wgpu_render_pass_set_bind_group(mPass.get(), aSlot, aBindGroup.mId, - aDynamicOffsets.Elements(), - aDynamicOffsets.Length()); + ffi::wgpu_recorded_render_pass_set_bind_group( + mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(), + aDynamicOffsets.Length()); } } void RenderPassEncoder::SetPipeline(const RenderPipeline& aPipeline) { if (mValid) { mUsedPipelines.AppendElement(&aPipeline); - ffi::wgpu_render_pass_set_pipeline(mPass.get(), aPipeline.mId); + ffi::wgpu_recorded_render_pass_set_pipeline(mPass.get(), aPipeline.mId); } } @@ -207,8 +203,8 @@ void RenderPassEncoder::SetIndexBuffer(const Buffer& aBuffer, const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32 ? ffi::WGPUIndexFormat_Uint32 : ffi::WGPUIndexFormat_Uint16; - ffi::wgpu_render_pass_set_index_buffer(mPass.get(), aBuffer.mId, iformat, - aOffset, aSize); + ffi::wgpu_recorded_render_pass_set_index_buffer(mPass.get(), aBuffer.mId, + iformat, aOffset, aSize); } } @@ -216,16 +212,17 @@ void RenderPassEncoder::SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer, uint64_t aOffset, uint64_t aSize) { if (mValid) { mUsedBuffers.AppendElement(&aBuffer); - ffi::wgpu_render_pass_set_vertex_buffer(mPass.get(), aSlot, aBuffer.mId, - aOffset, aSize); + ffi::wgpu_recorded_render_pass_set_vertex_buffer( + mPass.get(), aSlot, aBuffer.mId, aOffset, aSize); } } void RenderPassEncoder::Draw(uint32_t aVertexCount, uint32_t aInstanceCount, uint32_t aFirstVertex, uint32_t aFirstInstance) { if (mValid) { - ffi::wgpu_render_pass_draw(mPass.get(), aVertexCount, aInstanceCount, - aFirstVertex, aFirstInstance); + ffi::wgpu_recorded_render_pass_draw(mPass.get(), aVertexCount, + aInstanceCount, aFirstVertex, + aFirstInstance); } } @@ -234,24 +231,24 @@ void RenderPassEncoder::DrawIndexed(uint32_t aIndexCount, uint32_t aFirstIndex, int32_t aBaseVertex, uint32_t aFirstInstance) { if (mValid) { - ffi::wgpu_render_pass_draw_indexed(mPass.get(), aIndexCount, aInstanceCount, - aFirstIndex, aBaseVertex, - aFirstInstance); + ffi::wgpu_recorded_render_pass_draw_indexed(mPass.get(), aIndexCount, + aInstanceCount, aFirstIndex, + aBaseVertex, aFirstInstance); } } void RenderPassEncoder::DrawIndirect(const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) { if (mValid) { - ffi::wgpu_render_pass_draw_indirect(mPass.get(), aIndirectBuffer.mId, - aIndirectOffset); + ffi::wgpu_recorded_render_pass_draw_indirect( + mPass.get(), aIndirectBuffer.mId, aIndirectOffset); } } void RenderPassEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) { if (mValid) { - ffi::wgpu_render_pass_draw_indexed_indirect( + ffi::wgpu_recorded_render_pass_draw_indexed_indirect( mPass.get(), aIndirectBuffer.mId, aIndirectOffset); } } @@ -259,15 +256,16 @@ void RenderPassEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer, void RenderPassEncoder::SetViewport(float x, float y, float width, float height, float minDepth, float maxDepth) { if (mValid) { - ffi::wgpu_render_pass_set_viewport(mPass.get(), x, y, width, height, - minDepth, maxDepth); + ffi::wgpu_recorded_render_pass_set_viewport(mPass.get(), x, y, width, + height, minDepth, maxDepth); } } void RenderPassEncoder::SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { if (mValid) { - ffi::wgpu_render_pass_set_scissor_rect(mPass.get(), x, y, width, height); + ffi::wgpu_recorded_render_pass_set_scissor_rect(mPass.get(), x, y, width, + height); } } @@ -275,13 +273,14 @@ void RenderPassEncoder::SetBlendConstant( const dom::DoubleSequenceOrGPUColorDict& color) { if (mValid) { ffi::WGPUColor aColor = ConvertColor(color); - ffi::wgpu_render_pass_set_blend_constant(mPass.get(), &aColor); + ffi::wgpu_recorded_render_pass_set_blend_constant(mPass.get(), &aColor); } } void RenderPassEncoder::SetStencilReference(uint32_t reference) { if (mValid) { - ffi::wgpu_render_pass_set_stencil_reference(mPass.get(), reference); + ffi::wgpu_recorded_render_pass_set_stencil_reference(mPass.get(), + reference); } } @@ -293,26 +292,27 @@ void RenderPassEncoder::ExecuteBundles( mUsedRenderBundles.AppendElement(bundle); renderBundles.AppendElement(bundle->mId); } - ffi::wgpu_render_pass_execute_bundles(mPass.get(), renderBundles.Elements(), - renderBundles.Length()); + ffi::wgpu_recorded_render_pass_execute_bundles( + mPass.get(), renderBundles.Elements(), renderBundles.Length()); } } void RenderPassEncoder::PushDebugGroup(const nsAString& aString) { if (mValid) { const NS_ConvertUTF16toUTF8 utf8(aString); - ffi::wgpu_render_pass_push_debug_group(mPass.get(), utf8.get(), 0); + ffi::wgpu_recorded_render_pass_push_debug_group(mPass.get(), utf8.get(), 0); } } void RenderPassEncoder::PopDebugGroup() { if (mValid) { - ffi::wgpu_render_pass_pop_debug_group(mPass.get()); + ffi::wgpu_recorded_render_pass_pop_debug_group(mPass.get()); } } void RenderPassEncoder::InsertDebugMarker(const nsAString& aString) { if (mValid) { const NS_ConvertUTF16toUTF8 utf8(aString); - ffi::wgpu_render_pass_insert_debug_marker(mPass.get(), utf8.get(), 0); + ffi::wgpu_recorded_render_pass_insert_debug_marker(mPass.get(), utf8.get(), + 0); } } diff --git a/dom/webgpu/RenderPassEncoder.h b/dom/webgpu/RenderPassEncoder.h index 5ca414b4ea..b6008bd013 100644 --- a/dom/webgpu/RenderPassEncoder.h +++ b/dom/webgpu/RenderPassEncoder.h @@ -24,7 +24,7 @@ class AutoSequence; } // namespace dom namespace webgpu { namespace ffi { -struct WGPURenderPass; +struct WGPURecordedRenderPass; } // namespace ffi class BindGroup; @@ -35,7 +35,7 @@ class RenderPipeline; class TextureView; struct ffiWGPURenderPassDeleter { - void operator()(ffi::WGPURenderPass*); + void operator()(ffi::WGPURecordedRenderPass*); }; class RenderPassEncoder final : public ObjectBase, @@ -51,7 +51,7 @@ class RenderPassEncoder final : public ObjectBase, virtual ~RenderPassEncoder(); void Cleanup() {} - std::unique_ptr<ffi::WGPURenderPass, ffiWGPURenderPassDeleter> mPass; + std::unique_ptr<ffi::WGPURecordedRenderPass, ffiWGPURenderPassDeleter> mPass; // keep all the used objects alive while the pass is recorded nsTArray<RefPtr<const BindGroup>> mUsedBindGroups; nsTArray<RefPtr<const Buffer>> mUsedBuffers; diff --git a/dom/webgpu/SupportedFeatures.cpp b/dom/webgpu/SupportedFeatures.cpp index 294524bc81..a32879a2b0 100644 --- a/dom/webgpu/SupportedFeatures.cpp +++ b/dom/webgpu/SupportedFeatures.cpp @@ -5,6 +5,7 @@ #include "SupportedFeatures.h" #include "Adapter.h" +#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/WebGPUBinding.h" namespace mozilla::webgpu { @@ -17,7 +18,7 @@ SupportedFeatures::SupportedFeatures(Adapter* const aParent) void SupportedFeatures::Add(const dom::GPUFeatureName aFeature, ErrorResult& aRv) { - const auto u8 = dom::GPUFeatureNameValues::GetString(aFeature); + const auto u8 = dom::GetEnumString(aFeature); const auto u16 = NS_ConvertUTF8toUTF16(u8); dom::GPUSupportedFeatures_Binding::SetlikeHelpers::Add(this, u16, aRv); diff --git a/dom/webgpu/Utility.cpp b/dom/webgpu/Utility.cpp index fdb5732e8a..111bac4416 100644 --- a/dom/webgpu/Utility.cpp +++ b/dom/webgpu/Utility.cpp @@ -228,8 +228,6 @@ ffi::WGPUTextureFormat ConvertTextureFormat( case dom::GPUTextureFormat::Depth32float_stencil8: result.tag = ffi::WGPUTextureFormat_Depth32FloatStencil8; break; - case dom::GPUTextureFormat::EndGuard_: - MOZ_ASSERT_UNREACHABLE(); } // Clang will check for us that the switch above is exhaustive, diff --git a/dom/webgpu/ipc/PWebGPU.ipdl b/dom/webgpu/ipc/PWebGPU.ipdl index 5146dd6826..451480a1c3 100644 --- a/dom/webgpu/ipc/PWebGPU.ipdl +++ b/dom/webgpu/ipc/PWebGPU.ipdl @@ -44,6 +44,8 @@ parent: async DeviceActionWithAck(RawId selfId, ByteBuf buf) returns (bool dummy); async TextureAction(RawId selfId, RawId aDeviceId, ByteBuf buf); async CommandEncoderAction(RawId selfId, RawId aDeviceId, ByteBuf buf); + async RenderPass(RawId selfId, RawId aDeviceId, ByteBuf buf); + async ComputePass(RawId selfId, RawId aDeviceId, ByteBuf buf); async BumpImplicitBindGroupLayout(RawId pipelineId, bool isCompute, uint32_t index, RawId assignId); async DeviceCreateBuffer(RawId deviceId, RawId bufferId, GPUBufferDescriptor desc, UnsafeSharedMemoryHandle shm); diff --git a/dom/webgpu/ipc/WebGPUChild.cpp b/dom/webgpu/ipc/WebGPUChild.cpp index 663dd5cb89..ab1a100736 100644 --- a/dom/webgpu/ipc/WebGPUChild.cpp +++ b/dom/webgpu/ipc/WebGPUChild.cpp @@ -40,10 +40,10 @@ void WebGPUChild::JsWarning(nsIGlobalObject* aGlobal, if (aGlobal) { dom::AutoJSAPI api; if (api.Init(aGlobal)) { - JS::WarnUTF8(api.cx(), "%s", flatString.get()); + JS::WarnUTF8(api.cx(), "Uncaptured WebGPU error: %s", flatString.get()); } } else { - printf_stderr("Validation error without device target: %s\n", + printf_stderr("Uncaptured WebGPU error without device target: %s\n", flatString.get()); } } diff --git a/dom/webgpu/ipc/WebGPUParent.cpp b/dom/webgpu/ipc/WebGPUParent.cpp index 9b79988245..1c0560d31e 100644 --- a/dom/webgpu/ipc/WebGPUParent.cpp +++ b/dom/webgpu/ipc/WebGPUParent.cpp @@ -154,6 +154,12 @@ class ErrorBuffer { } return Some(Error{*filterType, false, nsCString{mMessageUtf8}}); } + + void CoerceValidationToInternal() { + if (mType == ffi::WGPUErrorBufferType_Validation) { + mType = ffi::WGPUErrorBufferType_Internal; + } + } }; struct PendingSwapChainDrop { @@ -180,10 +186,9 @@ class PresentationData { Maybe<PendingSwapChainDrop> mPendingSwapChainDrop; const uint32_t mSourcePitch; - std::vector<RawId> mUnassignedBufferIds MOZ_GUARDED_BY(mBuffersLock); - std::vector<RawId> mAvailableBufferIds MOZ_GUARDED_BY(mBuffersLock); - std::vector<RawId> mQueuedBufferIds MOZ_GUARDED_BY(mBuffersLock); - Mutex mBuffersLock; + std::vector<RawId> mUnassignedBufferIds; + std::vector<RawId> mAvailableBufferIds; + std::vector<RawId> mQueuedBufferIds; PresentationData(WebGPUParent* aParent, bool aUseExternalTextureInSwapChain, RawId aDeviceId, RawId aQueueId, @@ -194,8 +199,7 @@ class PresentationData { mDeviceId(aDeviceId), mQueueId(aQueueId), mDesc(aDesc), - mSourcePitch(aSourcePitch), - mBuffersLock("WebGPU presentation buffers") { + mSourcePitch(aSourcePitch) { MOZ_COUNT_CTOR(PresentationData); for (const RawId id : aBufferIds) { @@ -354,6 +358,11 @@ ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter( nsAutoCString message(aMessage); req->mParent->LoseDevice(deviceId, reason, message); + auto it = req->mParent->mDeviceFenceHandles.find(deviceId); + if (it != req->mParent->mDeviceFenceHandles.end()) { + req->mParent->mDeviceFenceHandles.erase(it); + } + // We're no longer tracking the memory for this callback, so erase // it to ensure we don't leak memory. req->mParent->mDeviceLostRequests.erase(deviceId); @@ -394,7 +403,9 @@ ipc::IPCResult WebGPUParent::RecvAdapterRequestDevice( HANDLE handle = wgpu_server_get_device_fence_handle(mContext.get(), aDeviceId); if (handle) { - mFenceHandle = new gfx::FileHandleWrapper(UniqueFileHandle(handle)); + RefPtr<gfx::FileHandleWrapper> fenceHandle = + new gfx::FileHandleWrapper(UniqueFileHandle(handle)); + mDeviceFenceHandles.emplace(aDeviceId, std::move(fenceHandle)); } #endif @@ -970,20 +981,16 @@ static void ReadbackPresentCallback(ffi::WGPUBufferMapAsyncStatus status, return; } - PresentationData* data = req->mData.get(); + RefPtr<PresentationData> data = req->mData; // get the buffer ID RawId bufferId; { - MutexAutoLock lock(data->mBuffersLock); bufferId = data->mQueuedBufferIds.back(); data->mQueuedBufferIds.pop_back(); } // Ensure we'll make the bufferId available for reuse - auto releaseBuffer = MakeScopeExit([data = RefPtr{data}, bufferId] { - MutexAutoLock lock(data->mBuffersLock); - data->mAvailableBufferIds.push_back(bufferId); - }); + data->mAvailableBufferIds.push_back(bufferId); MOZ_LOG(sLogger, LogLevel::Info, ("ReadbackPresentCallback for buffer %" PRIu64 " status=%d\n", @@ -994,15 +1001,16 @@ static void ReadbackPresentCallback(ffi::WGPUBufferMapAsyncStatus status, ErrorBuffer getRangeError; const auto mapped = ffi::wgpu_server_buffer_get_mapped_range( req->mContext, bufferId, 0, bufferSize, getRangeError.ToFFI()); + getRangeError.CoerceValidationToInternal(); if (req->mData->mParent) { req->mData->mParent->ForwardError(data->mDeviceId, getRangeError); - } else if (auto innerError = getRangeError.GetError()) { - // If an error occured in get_mapped_range, treat it as an internal error - // and crash. The error handling story for something unexpected happening - // during the present glue needs to befigured out in a more global way. + } + if (auto innerError = getRangeError.GetError()) { MOZ_LOG(sLogger, LogLevel::Info, - ("WebGPU present: buffer get_mapped_range failed: %s\n", + ("WebGPU present: buffer get_mapped_range for internal " + "presentation readback failed: %s\n", innerError->message.get())); + return; } MOZ_RELEASE_ASSERT(mapped.length >= bufferSize); @@ -1029,11 +1037,14 @@ static void ReadbackPresentCallback(ffi::WGPUBufferMapAsyncStatus status, } ErrorBuffer unmapError; wgpu_server_buffer_unmap(req->mContext, bufferId, unmapError.ToFFI()); + unmapError.CoerceValidationToInternal(); if (req->mData->mParent) { req->mData->mParent->ForwardError(data->mDeviceId, unmapError); - } else if (auto innerError = unmapError.GetError()) { + } + if (auto innerError = unmapError.GetError()) { MOZ_LOG(sLogger, LogLevel::Info, - ("WebGPU present: buffer unmap failed: %s\n", + ("WebGPU present: buffer unmap for internal presentation " + "readback failed: %s\n", innerError->message.get())); } } else { @@ -1083,9 +1094,12 @@ void WebGPUParent::PostExternalTexture( const auto index = aExternalTexture->GetSubmissionIndex(); MOZ_ASSERT(index != 0); + RefPtr<PresentationData> data = lookup->second.get(); + Maybe<gfx::FenceInfo> fenceInfo; - if (mFenceHandle) { - fenceInfo = Some(gfx::FenceInfo(mFenceHandle, index)); + auto it = mDeviceFenceHandles.find(data->mDeviceId); + if (it != mDeviceFenceHandles.end()) { + fenceInfo = Some(gfx::FenceInfo(it->second, index)); } Maybe<layers::SurfaceDescriptor> desc = @@ -1098,8 +1112,6 @@ void WebGPUParent::PostExternalTexture( mRemoteTextureOwner->PushTexture(aRemoteTextureId, aOwnerId, aExternalTexture, size, surfaceFormat, *desc); - RefPtr<PresentationData> data = lookup->second.get(); - auto recycledTexture = mRemoteTextureOwner->GetRecycledExternalTexture( size, surfaceFormat, desc->type(), aOwnerId); if (recycledTexture) { @@ -1140,7 +1152,6 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent( // step 1: find an available staging buffer, or create one { - MutexAutoLock lock(data->mBuffersLock); if (!data->mAvailableBufferIds.empty()) { bufferId = data->mAvailableBufferIds.back(); data->mAvailableBufferIds.pop_back(); @@ -1285,7 +1296,6 @@ ipc::IPCResult WebGPUParent::RecvSwapChainDrop( mPresentationDataMap.erase(lookup); - MutexAutoLock lock(data->mBuffersLock); ipc::ByteBuf dropByteBuf; for (const auto bid : data->mUnassignedBufferIds) { wgpu_server_buffer_free(bid, ToFFI(&dropByteBuf)); @@ -1351,6 +1361,24 @@ ipc::IPCResult WebGPUParent::RecvCommandEncoderAction( return IPC_OK(); } +ipc::IPCResult WebGPUParent::RecvRenderPass(RawId aEncoderId, RawId aDeviceId, + const ipc::ByteBuf& aByteBuf) { + ErrorBuffer error; + ffi::wgpu_server_render_pass(mContext.get(), aEncoderId, ToFFI(&aByteBuf), + error.ToFFI()); + ForwardError(aDeviceId, error); + return IPC_OK(); +} + +ipc::IPCResult WebGPUParent::RecvComputePass(RawId aEncoderId, RawId aDeviceId, + const ipc::ByteBuf& aByteBuf) { + ErrorBuffer error; + ffi::wgpu_server_compute_pass(mContext.get(), aEncoderId, ToFFI(&aByteBuf), + error.ToFFI()); + ForwardError(aDeviceId, error); + return IPC_OK(); +} + ipc::IPCResult WebGPUParent::RecvBumpImplicitBindGroupLayout(RawId aPipelineId, bool aIsCompute, uint32_t aIndex, @@ -1426,8 +1454,6 @@ ipc::IPCResult WebGPUParent::RecvDevicePopErrorScope( case dom::GPUErrorFilter::Internal: ret.resultType = PopErrorScopeResultType::InternalError; break; - case dom::GPUErrorFilter::EndGuard_: - MOZ_CRASH("Bad GPUErrorFilter"); } } return ret; diff --git a/dom/webgpu/ipc/WebGPUParent.h b/dom/webgpu/ipc/WebGPUParent.h index 6ad539c21e..a1eb36d723 100644 --- a/dom/webgpu/ipc/WebGPUParent.h +++ b/dom/webgpu/ipc/WebGPUParent.h @@ -118,6 +118,10 @@ class WebGPUParent final : public PWebGPUParent, public SupportsWeakPtr { const ipc::ByteBuf& aByteBuf); ipc::IPCResult RecvCommandEncoderAction(RawId aEncoderId, RawId aDeviceId, const ipc::ByteBuf& aByteBuf); + ipc::IPCResult RecvRenderPass(RawId aEncoderId, RawId aDeviceId, + const ipc::ByteBuf& aByteBuf); + ipc::IPCResult RecvComputePass(RawId aEncoderId, RawId aDeviceId, + const ipc::ByteBuf& aByteBuf); ipc::IPCResult RecvBumpImplicitBindGroupLayout(RawId aPipelineId, bool aIsCompute, uint32_t aIndex, @@ -219,7 +223,7 @@ class WebGPUParent final : public PWebGPUParent, public SupportsWeakPtr { nsTHashSet<RawId> mLostDeviceIds; // Shared handle of wgpu device's fence. - RefPtr<gfx::FileHandleWrapper> mFenceHandle; + std::unordered_map<RawId, RefPtr<gfx::FileHandleWrapper>> mDeviceFenceHandles; // Store DeviceLostRequest structs for each device as unique_ptrs mapped // to their device ids. We keep these unique_ptrs alive as long as the diff --git a/dom/webgpu/ipc/WebGPUSerialize.h b/dom/webgpu/ipc/WebGPUSerialize.h index 8d78d784cb..03f9ee1676 100644 --- a/dom/webgpu/ipc/WebGPUSerialize.h +++ b/dom/webgpu/ipc/WebGPUSerialize.h @@ -9,6 +9,7 @@ #include "WebGPUTypes.h" #include "ipc/EnumSerializer.h" #include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/WebGPUBinding.h" #include "mozilla/webgpu/ffi/wgpu.h" @@ -20,7 +21,9 @@ namespace IPC { : public ContiguousEnumSerializer<something, something(0), guard> {} #define DEFINE_IPC_SERIALIZER_DOM_ENUM(something) \ - DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, something::EndGuard_) + template <> \ + struct ParamTraits<something> \ + : public mozilla::dom::WebIDLEnumSerializer<something> {} #define DEFINE_IPC_SERIALIZER_FFI_ENUM(something) \ DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, something##_Sentinel) diff --git a/dom/webidl/ARIAMixin.webidl b/dom/webidl/ARIAMixin.webidl new file mode 100644 index 0000000000..10d23af96e --- /dev/null +++ b/dom/webidl/ARIAMixin.webidl @@ -0,0 +1,148 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/aria/#ARIAMixin + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +interface mixin ARIAMixin { + [Pref="accessibility.ARIAElementReflection.enabled", CEReactions] + attribute Element? ariaActiveDescendantElement; + + [CEReactions, SetterThrows] + attribute DOMString? role; + + [CEReactions, SetterThrows] + attribute DOMString? ariaAtomic; + + [CEReactions, SetterThrows] + attribute DOMString? ariaAutoComplete; + + [CEReactions, SetterThrows] + attribute DOMString? ariaBrailleLabel; + + [CEReactions, SetterThrows] + attribute DOMString? ariaBrailleRoleDescription; + + [CEReactions, SetterThrows] + attribute DOMString? ariaBusy; + + [CEReactions, SetterThrows] + attribute DOMString? ariaChecked; + + [CEReactions, SetterThrows] + attribute DOMString? ariaColCount; + + [CEReactions, SetterThrows] + attribute DOMString? ariaColIndex; + + [CEReactions, SetterThrows] + attribute DOMString? ariaColIndexText; + + [CEReactions, SetterThrows] + attribute DOMString? ariaColSpan; + + [CEReactions, SetterThrows] + attribute DOMString? ariaCurrent; + + [CEReactions, SetterThrows] + attribute DOMString? ariaDescription; + + [CEReactions, SetterThrows] + attribute DOMString? ariaDisabled; + + [CEReactions, SetterThrows] + attribute DOMString? ariaExpanded; + + [CEReactions, SetterThrows] + attribute DOMString? ariaHasPopup; + + [CEReactions, SetterThrows] + attribute DOMString? ariaHidden; + + [CEReactions, SetterThrows] + attribute DOMString? ariaInvalid; + + [CEReactions, SetterThrows] + attribute DOMString? ariaKeyShortcuts; + + [CEReactions, SetterThrows] + attribute DOMString? ariaLabel; + + [CEReactions, SetterThrows] + attribute DOMString? ariaLevel; + + [CEReactions, SetterThrows] + attribute DOMString? ariaLive; + + [CEReactions, SetterThrows] + attribute DOMString? ariaModal; + + [CEReactions, SetterThrows] + attribute DOMString? ariaMultiLine; + + [CEReactions, SetterThrows] + attribute DOMString? ariaMultiSelectable; + + [CEReactions, SetterThrows] + attribute DOMString? ariaOrientation; + + [CEReactions, SetterThrows] + attribute DOMString? ariaPlaceholder; + + [CEReactions, SetterThrows] + attribute DOMString? ariaPosInSet; + + [CEReactions, SetterThrows] + attribute DOMString? ariaPressed; + + [CEReactions, SetterThrows] + attribute DOMString? ariaReadOnly; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRelevant; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRequired; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRoleDescription; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRowCount; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRowIndex; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRowIndexText; + + [CEReactions, SetterThrows] + attribute DOMString? ariaRowSpan; + + [CEReactions, SetterThrows] + attribute DOMString? ariaSelected; + + [CEReactions, SetterThrows] + attribute DOMString? ariaSetSize; + + [CEReactions, SetterThrows] + attribute DOMString? ariaSort; + + [CEReactions, SetterThrows] + attribute DOMString? ariaValueMax; + + [CEReactions, SetterThrows] + attribute DOMString? ariaValueMin; + + [CEReactions, SetterThrows] + attribute DOMString? ariaValueNow; + + [CEReactions, SetterThrows] + attribute DOMString? ariaValueText; +}; diff --git a/dom/webidl/AbortController.webidl b/dom/webidl/AbortController.webidl index 2b172b7718..1d35d192ca 100644 --- a/dom/webidl/AbortController.webidl +++ b/dom/webidl/AbortController.webidl @@ -7,7 +7,7 @@ * https://dom.spec.whatwg.org/#abortcontroller */ -[Exposed=(Window,Worker)] +[Exposed=*] interface AbortController { [Throws] constructor(); diff --git a/dom/webidl/AccessibilityRole.webidl b/dom/webidl/AccessibilityRole.webidl deleted file mode 100644 index 3e47e3180d..0000000000 --- a/dom/webidl/AccessibilityRole.webidl +++ /dev/null @@ -1,16 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The origin of this IDL file is - * https://rawgit.com/w3c/aria/master/#AccessibilityRole - * - * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C - * liability, trademark and document use rules apply. - */ - -interface mixin AccessibilityRole { - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? role; -}; diff --git a/dom/webidl/AddonManager.webidl b/dom/webidl/AddonManager.webidl index d392b90fb9..fc94228954 100644 --- a/dom/webidl/AddonManager.webidl +++ b/dom/webidl/AddonManager.webidl @@ -35,9 +35,9 @@ interface Addon { [ChromeOnly, JSImplementation="dummy", Exposed=Window] interface AddonInstall : EventTarget { - // One of the STATE_* symbols from AddonManager.jsm + // One of the STATE_* symbols from AddonManager.sys.mjs readonly attribute DOMString state; - // One of the ERROR_* symbols from AddonManager.jsm, or null + // One of the ERROR_* symbols from AddonManager.sys.mjs, or null readonly attribute DOMString? error; // How many bytes have been downloaded readonly attribute long long progress; diff --git a/dom/webidl/AriaAttributes.webidl b/dom/webidl/AriaAttributes.webidl deleted file mode 100644 index f6c34f01dc..0000000000 --- a/dom/webidl/AriaAttributes.webidl +++ /dev/null @@ -1,136 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The origin of this IDL file is - * https://rawgit.com/w3c/aria/master/#AriaAttributes - * - * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C - * liability, trademark and document use rules apply. - */ - -interface mixin AriaAttributes { - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaAtomic; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaAutoComplete; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaBusy; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaChecked; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaColCount; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaColIndex; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaColIndexText; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaColSpan; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaCurrent; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaDescription; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaDisabled; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaExpanded; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaHasPopup; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaHidden; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaInvalid; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaKeyShortcuts; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaLabel; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaLevel; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaLive; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaModal; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaMultiLine; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaMultiSelectable; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaOrientation; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaPlaceholder; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaPosInSet; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaPressed; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaReadOnly; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRelevant; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRequired; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRoleDescription; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRowCount; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRowIndex; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRowIndexText; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaRowSpan; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaSelected; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaSetSize; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaSort; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaValueMax; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaValueMin; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaValueNow; - - [Pref="accessibility.ARIAReflection.enabled", CEReactions, SetterThrows] - attribute DOMString? ariaValueText; -}; diff --git a/dom/webidl/AudioData.webidl b/dom/webidl/AudioData.webidl new file mode 100644 index 0000000000..e413e93971 --- /dev/null +++ b/dom/webidl/AudioData.webidl @@ -0,0 +1,63 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/webcodecs/#audiodata + */ + +// [Serializable, Transferable] are implemented without adding attributes here, +// but directly with {Read,Write}StructuredClone and Transfer/FromTransfered. +[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"] +interface AudioData { + [Throws] + constructor(AudioDataInit init); + + readonly attribute AudioSampleFormat? format; + readonly attribute float sampleRate; + readonly attribute unsigned long numberOfFrames; + readonly attribute unsigned long numberOfChannels; + readonly attribute unsigned long long duration; // microseconds + readonly attribute long long timestamp; // microseconds + + [Throws] + unsigned long allocationSize(AudioDataCopyToOptions options); + [Throws] + undefined copyTo( + // bug 1696216: Should be `copyTo(AllowSharedBufferSource destination, ...)` + ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) destination, + AudioDataCopyToOptions options); + [Throws] + AudioData clone(); + undefined close(); +}; + +dictionary AudioDataInit { + required AudioSampleFormat format; + required float sampleRate; + required [EnforceRange] unsigned long numberOfFrames; + required [EnforceRange] unsigned long numberOfChannels; + required [EnforceRange] long long timestamp; // microseconds + // bug 1696216: Should be AllowSharedBufferSource + required ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) data; + sequence<ArrayBuffer> transfer = []; +}; + +enum AudioSampleFormat { + "u8", + "s16", + "s32", + "f32", + "u8-planar", + "s16-planar", + "s32-planar", + "f32-planar", +}; + +dictionary AudioDataCopyToOptions { + required [EnforceRange] unsigned long planeIndex; + [EnforceRange] unsigned long frameOffset = 0; + [EnforceRange] unsigned long frameCount; + AudioSampleFormat format; +}; diff --git a/dom/webidl/AudioDecoder.webidl b/dom/webidl/AudioDecoder.webidl new file mode 100644 index 0000000000..55cfe8e5e3 --- /dev/null +++ b/dom/webidl/AudioDecoder.webidl @@ -0,0 +1,53 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/webcodecs/#audiodecoder + */ + +[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"] +interface AudioDecoder : EventTarget { + [Throws] + constructor(AudioDecoderInit init); + + readonly attribute CodecState state; + readonly attribute unsigned long decodeQueueSize; + attribute EventHandler ondequeue; + + [Throws] + undefined configure(AudioDecoderConfig config); + [Throws] + undefined decode(EncodedAudioChunk chunk); + [NewObject, Throws] + Promise<undefined> flush(); + [Throws] + undefined reset(); + [Throws] + undefined close(); + + [NewObject, Throws] + static Promise<AudioDecoderSupport> isConfigSupported(AudioDecoderConfig config); +}; + +dictionary AudioDecoderInit { + required AudioDataOutputCallback output; + required WebCodecsErrorCallback error; +}; + +callback AudioDataOutputCallback = undefined(AudioData output); + +dictionary AudioDecoderSupport { + boolean supported; + AudioDecoderConfig config; +}; + +dictionary AudioDecoderConfig { + required DOMString codec; + required [EnforceRange] unsigned long sampleRate; + required [EnforceRange] unsigned long numberOfChannels; + + // Bug 1696216: Should be AllowSharedBufferSource + ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) description; +}; diff --git a/dom/webidl/CSSStyleDeclaration.webidl b/dom/webidl/CSSStyleDeclaration.webidl index 76050b75a0..8e38957412 100644 --- a/dom/webidl/CSSStyleDeclaration.webidl +++ b/dom/webidl/CSSStyleDeclaration.webidl @@ -21,6 +21,9 @@ interface CSSStyleDeclaration { [Throws, ChromeOnly] sequence<UTF8String> getCSSImageURLs(UTF8String property); + [ChromeOnly] + readonly attribute float usedFontSize; + UTF8String getPropertyValue(UTF8String property); UTF8String getPropertyPriority(UTF8String property); [CEReactions, NeedsSubjectPrincipal=NonSystem, Throws] diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index c89c098928..d6c27d91b5 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -151,6 +151,7 @@ interface mixin CanvasState { undefined save(); // push state on state stack undefined restore(); // pop state stack and restore state undefined reset(); // reset the rendering context to its default state + boolean isContextLost(); // return whether context is lost }; interface mixin CanvasTransform { diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl index 1f43ee19b6..68d86925d1 100644 --- a/dom/webidl/Console.webidl +++ b/dom/webidl/Console.webidl @@ -14,7 +14,7 @@ namespace console { // NOTE: if you touch this namespace, remember to update the ConsoleInstance - // interface as well! + // interface as well! - dom/chrome-webidl/ConsoleInstance.webidl // Logging [UseCounter] @@ -78,173 +78,3 @@ namespace console { [ChromeOnly, NewObject] ConsoleInstance createInstance(optional ConsoleInstanceOptions options = {}); }; - -// This is used to propagate console events to the observers. -[GenerateConversionToJS] -dictionary ConsoleEvent { - (unsigned long long or DOMString) ID; - (unsigned long long or DOMString) innerID; - DOMString consoleID = ""; - DOMString addonId = ""; - DOMString level = ""; - DOMString filename = ""; - // Unique identifier within the process for the script source this event is - // associated with, or zero. - unsigned long sourceId = 0; - unsigned long lineNumber = 0; - unsigned long columnNumber = 0; - DOMString functionName = ""; - double timeStamp = 0; - double microSecondTimeStamp = 0; - sequence<any> arguments; - sequence<DOMString?> styles; - boolean private = false; - // stacktrace is handled via a getter in some cases so we can construct it - // lazily. Note that we're not making this whole thing an interface because - // consumers expect to see own properties on it, which would mean making the - // props unforgeable, which means lots of JSFunction allocations. Maybe we - // should fix those consumers, of course.... - // sequence<ConsoleStackEntry> stacktrace; - DOMString groupName = ""; - any timer = null; - any counter = null; - DOMString prefix = ""; - boolean chromeContext = false; -}; - -// Event for profile operations -[GenerateConversionToJS] -dictionary ConsoleProfileEvent { - DOMString action = ""; - sequence<any> arguments; - boolean chromeContext = false; -}; - -// This dictionary is used to manage stack trace data. -[GenerateConversionToJS] -dictionary ConsoleStackEntry { - DOMString filename = ""; - // Unique identifier within the process for the script source this entry is - // associated with, or zero. - unsigned long sourceId = 0; - unsigned long lineNumber = 0; - unsigned long columnNumber = 0; - DOMString functionName = ""; - DOMString? asyncCause; -}; - -[GenerateConversionToJS] -dictionary ConsoleTimerStart { - DOMString name = ""; -}; - -[GenerateConversionToJS] -dictionary ConsoleTimerLogOrEnd { - DOMString name = ""; - double duration = 0; -}; - -[GenerateConversionToJS] -dictionary ConsoleTimerError { - DOMString error = ""; - DOMString name = ""; -}; - -[GenerateConversionToJS] -dictionary ConsoleCounter { - DOMString label = ""; - unsigned long count = 0; -}; - -[GenerateConversionToJS] -dictionary ConsoleCounterError { - DOMString label = ""; - DOMString error = ""; -}; - -[ChromeOnly, - Exposed=(Window,Worker,WorkerDebugger,Worklet)] -// This is basically a copy of the console namespace. -interface ConsoleInstance { - // Logging - undefined assert(optional boolean condition = false, any... data); - undefined clear(); - undefined count(optional DOMString label = "default"); - undefined countReset(optional DOMString label = "default"); - undefined debug(any... data); - undefined error(any... data); - undefined info(any... data); - undefined log(any... data); - undefined table(any... data); // FIXME: The spec is still unclear about this. - undefined trace(any... data); - undefined warn(any... data); - undefined dir(any... data); // FIXME: This doesn't follow the spec yet. - undefined dirxml(any... data); - - // Grouping - undefined group(any... data); - undefined groupCollapsed(any... data); - undefined groupEnd(); - - // Timing - undefined time(optional DOMString label = "default"); - undefined timeLog(optional DOMString label = "default", any... data); - undefined timeEnd(optional DOMString label = "default"); - - // Mozilla only or Webcompat methods - - undefined _exception(any... data); - undefined timeStamp(optional any data); - - undefined profile(any... data); - undefined profileEnd(any... data); - - // Returns true if the given level would log a message. Used for avoiding - // long/significant processing when logging messages. - boolean shouldLog(ConsoleLogLevel level); -}; - -callback ConsoleInstanceDumpCallback = undefined (DOMString message); - -enum ConsoleLogLevel { - "All", "Debug", "Log", "Info", "Clear", "Trace", "TimeLog", "TimeEnd", "Time", - "Group", "GroupEnd", "Profile", "ProfileEnd", "Dir", "Dirxml", "Warn", "Error", - "Off" -}; - -dictionary ConsoleInstanceOptions { - // An optional function to intercept all strings written to stdout. - ConsoleInstanceDumpCallback dump; - - // An optional prefix string to be printed before the actual logged message. - DOMString prefix = ""; - - // An ID representing the source of the message. Normally the inner ID of a - // DOM window. - DOMString innerID = ""; - - // String identified for the console, this will be passed through the console - // notifications. - DOMString consoleID = ""; - - // Identifier that allows to filter which messages are logged based on their - // log level. - ConsoleLogLevel maxLogLevel; - - // String pref name which contains the level to use for maxLogLevel. If the - // pref doesn't exist, gets removed or it is used in workers, the maxLogLevel - // will default to the value passed to this constructor (or "all" if it wasn't - // specified). - DOMString maxLogLevelPref = ""; -}; - -enum ConsoleLevel { "log", "warning", "error" }; - -// this interface is just for testing -partial interface ConsoleInstance { - [ChromeOnly] - undefined reportForServiceWorkerScope(DOMString scope, DOMString message, - DOMString filename, unsigned long lineNumber, - unsigned long columnNumber, - ConsoleLevel level); -}; diff --git a/dom/webidl/DOMRequest.webidl b/dom/webidl/DOMRequest.webidl deleted file mode 100644 index 606b33b02f..0000000000 --- a/dom/webidl/DOMRequest.webidl +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -enum DOMRequestReadyState { "pending", "done" }; - -[Exposed=(Window,Worker)] -interface mixin DOMRequestShared { - readonly attribute DOMRequestReadyState readyState; - - readonly attribute any result; - readonly attribute DOMException? error; - - attribute EventHandler onsuccess; - attribute EventHandler onerror; -}; - -[Pref="dom.domrequest.enabled", Exposed=(Window,Worker)] -interface DOMRequest : EventTarget { - // The [TreatNonCallableAsNull] annotation is required since then() should do - // nothing instead of throwing errors when non-callable arguments are passed. - // See documentation for Promise.then to see why we return "any". - [NewObject, Throws] - any then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null, - [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); - - [ChromeOnly] - undefined fireDetailedError(DOMException aError); -}; - -DOMRequest includes DOMRequestShared; diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl index 75d07995fb..32cb1dd30b 100644 --- a/dom/webidl/Element.webidl +++ b/dom/webidl/Element.webidl @@ -311,8 +311,7 @@ Element includes NonDocumentTypeChildNode; Element includes ParentNode; Element includes Animatable; Element includes GeometryUtils; -Element includes AccessibilityRole; -Element includes AriaAttributes; +Element includes ARIAMixin; // https://fullscreen.spec.whatwg.org/#api partial interface Element { diff --git a/dom/webidl/ElementInternals.webidl b/dom/webidl/ElementInternals.webidl index 175d0e4e1c..4b86e73060 100644 --- a/dom/webidl/ElementInternals.webidl +++ b/dom/webidl/ElementInternals.webidl @@ -63,8 +63,7 @@ partial interface ElementInternals { readonly attribute HTMLElement? validationAnchor; }; -ElementInternals includes AccessibilityRole; -ElementInternals includes AriaAttributes; +ElementInternals includes ARIAMixin; dictionary ValidityStateFlags { boolean valueMissing = false; diff --git a/dom/webidl/EncodedAudioChunk.webidl b/dom/webidl/EncodedAudioChunk.webidl new file mode 100644 index 0000000000..bf4c44e47d --- /dev/null +++ b/dom/webidl/EncodedAudioChunk.webidl @@ -0,0 +1,38 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/webcodecs/#encodedaudiochunk + */ + +// [Serializable] is implemented without adding attribute here. +[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"] +interface EncodedAudioChunk { + [Throws] + constructor(EncodedAudioChunkInit init); + readonly attribute EncodedAudioChunkType type; + readonly attribute long long timestamp; // microseconds + readonly attribute unsigned long long? duration; // microseconds + readonly attribute unsigned long byteLength; + + [Throws] + undefined copyTo( + // bug 1696216: Should be `copyTo(AllowSharedBufferSource destination, ...)` + ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) destination); +}; + +dictionary EncodedAudioChunkInit { + required EncodedAudioChunkType type; + required [EnforceRange] long long timestamp; // microseconds + [EnforceRange] unsigned long long duration; // microseconds + // bug 1696216: Should be AllowSharedBufferSource + required ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) data; + sequence<ArrayBuffer> transfer = []; +}; + +enum EncodedAudioChunkType { + "key", + "delta" +}; diff --git a/dom/webidl/EventHandler.webidl b/dom/webidl/EventHandler.webidl index 439ac79e5d..c88e4f0422 100644 --- a/dom/webidl/EventHandler.webidl +++ b/dom/webidl/EventHandler.webidl @@ -39,7 +39,9 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; + attribute EventHandler oncontextrestored; attribute EventHandler oncopy; attribute EventHandler oncuechange; attribute EventHandler oncut; diff --git a/dom/webidl/Gamepad.webidl b/dom/webidl/Gamepad.webidl index a8ec894441..e730d070ee 100644 --- a/dom/webidl/Gamepad.webidl +++ b/dom/webidl/Gamepad.webidl @@ -10,8 +10,7 @@ */ [Pref="dom.gamepad.enabled", - Exposed=Window, - SecureContext] + Exposed=Window] interface GamepadButton { readonly attribute boolean pressed; readonly attribute boolean touched; @@ -35,8 +34,7 @@ enum GamepadMappingType { }; [Pref="dom.gamepad.enabled", - Exposed=Window, - SecureContext] + Exposed=Window] interface Gamepad { /** * An identifier, unique per type of device. diff --git a/dom/webidl/GamepadAxisMoveEvent.webidl b/dom/webidl/GamepadAxisMoveEvent.webidl index 6ccdeabf71..24f16568f2 100644 --- a/dom/webidl/GamepadAxisMoveEvent.webidl +++ b/dom/webidl/GamepadAxisMoveEvent.webidl @@ -5,7 +5,7 @@ */ [Pref="dom.gamepad.non_standard_events.enabled", - Exposed=Window, SecureContext] + Exposed=Window] interface GamepadAxisMoveEvent : GamepadEvent { constructor(DOMString type, diff --git a/dom/webidl/GamepadButtonEvent.webidl b/dom/webidl/GamepadButtonEvent.webidl index 02bc4fb010..47d71107f0 100644 --- a/dom/webidl/GamepadButtonEvent.webidl +++ b/dom/webidl/GamepadButtonEvent.webidl @@ -5,8 +5,7 @@ */ [Pref="dom.gamepad.non_standard_events.enabled", - Exposed=Window, - SecureContext] + Exposed=Window] interface GamepadButtonEvent : GamepadEvent { constructor(DOMString type, diff --git a/dom/webidl/GamepadEvent.webidl b/dom/webidl/GamepadEvent.webidl index be2ad9ec10..0189050093 100644 --- a/dom/webidl/GamepadEvent.webidl +++ b/dom/webidl/GamepadEvent.webidl @@ -8,8 +8,7 @@ */ [Pref="dom.gamepad.enabled", - Exposed=Window, - SecureContext] + Exposed=Window] interface GamepadEvent : Event { constructor(DOMString type, optional GamepadEventInit eventInitDict = {}); diff --git a/dom/webidl/GamepadHapticActuator.webidl b/dom/webidl/GamepadHapticActuator.webidl index 9975b7ab85..0f3c8e0553 100644 --- a/dom/webidl/GamepadHapticActuator.webidl +++ b/dom/webidl/GamepadHapticActuator.webidl @@ -13,8 +13,7 @@ enum GamepadHapticActuatorType { [Pref="dom.gamepad.extensions.enabled", HeaderFile="mozilla/dom/GamepadHapticActuator.h", - Exposed=Window, - SecureContext] + Exposed=Window] interface GamepadHapticActuator { readonly attribute GamepadHapticActuatorType type; diff --git a/dom/webidl/GamepadLightIndicator.webidl b/dom/webidl/GamepadLightIndicator.webidl index 2c447439c8..c421c230bb 100644 --- a/dom/webidl/GamepadLightIndicator.webidl +++ b/dom/webidl/GamepadLightIndicator.webidl @@ -18,7 +18,7 @@ dictionary GamepadLightColor { required octet blue; }; -[SecureContext, Pref="dom.gamepad.extensions.lightindicator", +[Pref="dom.gamepad.extensions.lightindicator", Exposed=Window] interface GamepadLightIndicator { diff --git a/dom/webidl/GamepadPose.webidl b/dom/webidl/GamepadPose.webidl index 4de822e7a7..055df15930 100644 --- a/dom/webidl/GamepadPose.webidl +++ b/dom/webidl/GamepadPose.webidl @@ -8,8 +8,7 @@ */ [Pref="dom.gamepad.extensions.enabled", - Exposed=Window, - SecureContext] + Exposed=Window] interface GamepadPose { readonly attribute boolean hasOrientation; diff --git a/dom/webidl/GamepadTouch.webidl b/dom/webidl/GamepadTouch.webidl index a9c84edcbd..07e231d29d 100644 --- a/dom/webidl/GamepadTouch.webidl +++ b/dom/webidl/GamepadTouch.webidl @@ -7,7 +7,7 @@ * https://github.com/knyg/gamepad/blob/multitouch/extensions.html */ -[SecureContext, Pref="dom.gamepad.extensions.multitouch", +[Pref="dom.gamepad.extensions.multitouch", Exposed=Window] interface GamepadTouch { readonly attribute unsigned long touchId; diff --git a/dom/webidl/GleanMetrics.webidl b/dom/webidl/GleanMetrics.webidl index fc2697851d..47dc3262f1 100644 --- a/dom/webidl/GleanMetrics.webidl +++ b/dom/webidl/GleanMetrics.webidl @@ -111,6 +111,7 @@ interface GleanCounter : GleanMetric { dictionary GleanDistributionData { required unsigned long long sum; + required unsigned long long count; required record<UTF8String, unsigned long long> values; }; @@ -642,3 +643,35 @@ interface GleanText : GleanMetric { [Throws, ChromeOnly] UTF8String? testGetValue(optional UTF8String aPingName = ""); }; + +[Func="nsGlobalWindowInner::IsGleanNeeded", Exposed=Window] +interface GleanObject : GleanMetric { + /** + * Set to the specified object. + * + * The structure of the metric is validated against the predefined structure. + * + * @param object The object to set the metric to. + */ + undefined set(object value); + + /** + * **Test-only API** + * + * Gets the currently stored value as an object. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * This doesn't clear the stored value. + * Parent process only. Panics in child processes. + * + * @param aPingName The (optional) name of the ping to retrieve the metric + * for. Defaults to the first value in `send_in_pings`. + * + * @return value of the stored metric, or undefined if there is no value. + */ + [Throws, ChromeOnly] + object? testGetValue(optional UTF8String aPingName = ""); +}; diff --git a/dom/webidl/HTMLImageElement.webidl b/dom/webidl/HTMLImageElement.webidl index 1495d25f74..c57727bd51 100644 --- a/dom/webidl/HTMLImageElement.webidl +++ b/dom/webidl/HTMLImageElement.webidl @@ -43,6 +43,8 @@ interface HTMLImageElement : HTMLElement { attribute DOMString decoding; [CEReactions, SetterThrows] attribute DOMString loading; + [Pref="network.fetchpriority.enabled", CEReactions] + attribute DOMString fetchPriority; readonly attribute unsigned long naturalWidth; readonly attribute unsigned long naturalHeight; readonly attribute boolean complete; diff --git a/dom/webidl/HTMLTemplateElement.webidl b/dom/webidl/HTMLTemplateElement.webidl index ce71a51fe5..526a3427a8 100644 --- a/dom/webidl/HTMLTemplateElement.webidl +++ b/dom/webidl/HTMLTemplateElement.webidl @@ -16,6 +16,8 @@ interface HTMLTemplateElement : HTMLElement { readonly attribute DocumentFragment content; [CEReactions, Pref="dom.webcomponents.shadowdom.declarative.enabled"] attribute DOMString shadowRootMode; - [CEReactions, Pref="dom.webcomponents.shadowdom.declarative.enabled"] + [CEReactions, SetterThrows, Pref="dom.webcomponents.shadowdom.declarative.enabled"] attribute boolean shadowRootDelegatesFocus; + [CEReactions, SetterThrows, Pref="dom.webcomponents.shadowdom.declarative.enabled"] + attribute boolean shadowRootClonable; }; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 935ffdabec..3546a31bee 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -211,7 +211,7 @@ partial interface Navigator { // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#navigator-interface-extension partial interface Navigator { - [Throws, Pref="dom.gamepad.enabled", SecureContext] + [Throws, Pref="dom.gamepad.enabled"] sequence<Gamepad?> getGamepads(); }; partial interface Navigator { diff --git a/dom/webidl/RTCDtlsTransport.webidl b/dom/webidl/RTCDtlsTransport.webidl index 0b2f095ab6..99b70af470 100644 --- a/dom/webidl/RTCDtlsTransport.webidl +++ b/dom/webidl/RTCDtlsTransport.webidl @@ -18,6 +18,7 @@ enum RTCDtlsTransportState { [Pref="media.peerconnection.enabled", Exposed=Window] interface RTCDtlsTransport : EventTarget { + [SameObject] readonly attribute RTCIceTransport iceTransport; readonly attribute RTCDtlsTransportState state; attribute EventHandler onstatechange; }; diff --git a/dom/webidl/RTCIceTransport.webidl b/dom/webidl/RTCIceTransport.webidl new file mode 100644 index 0000000000..35a1c74018 --- /dev/null +++ b/dom/webidl/RTCIceTransport.webidl @@ -0,0 +1,43 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/webrtc-pc/#dom-rtcicetransport + */ + +enum RTCIceTransportState { + "closed", + "failed", + "disconnected", + "new", + "checking", + "completed", + "connected" +}; + +enum RTCIceGathererState { + "new", + "gathering", + "complete" +}; + +[Exposed=Window] +interface RTCIceTransport : EventTarget { + // TODO(bug 1307994) + // readonly attribute RTCIceRole role; + // readonly attribute RTCIceComponent component; + readonly attribute RTCIceTransportState state; + readonly attribute RTCIceGathererState gatheringState; + // TODO(bug 1307994) + // sequence<RTCIceCandidate> getLocalCandidates(); + // sequence<RTCIceCandidate> getRemoteCandidates(); + // RTCIceCandidatePair? getSelectedCandidatePair(); + // RTCIceParameters? getLocalParameters(); + // RTCIceParameters? getRemoteParameters(); + attribute EventHandler onstatechange; + attribute EventHandler ongatheringstatechange; + // TODO(bug 1307994) + // attribute EventHandler onselectedcandidatepairchange; +}; diff --git a/dom/webidl/RTCPeerConnection.webidl b/dom/webidl/RTCPeerConnection.webidl index 8fb908788e..ac3093a848 100644 --- a/dom/webidl/RTCPeerConnection.webidl +++ b/dom/webidl/RTCPeerConnection.webidl @@ -27,13 +27,13 @@ enum RTCIceGatheringState { }; enum RTCIceConnectionState { - "new", - "checking", - "connected", - "completed", - "failed", - "disconnected", - "closed" + "closed", + "failed", + "disconnected", + "new", + "checking", + "completed", + "connected" }; enum RTCPeerConnectionState { @@ -90,8 +90,7 @@ dictionary RTCOfferOptions : RTCOfferAnswerOptions { Exposed=Window] interface RTCPeerConnection : EventTarget { [Throws] - constructor(optional RTCConfiguration configuration = {}, - optional object? constraints); + constructor(optional RTCConfiguration configuration = {}); [Throws, StaticClassOverride="mozilla::dom::RTCCertificate"] static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm); @@ -99,13 +98,13 @@ interface RTCPeerConnection : EventTarget { undefined setIdentityProvider (DOMString provider, optional RTCIdentityProviderOptions options = {}); Promise<DOMString> getIdentityAssertion(); - Promise<RTCSessionDescriptionInit> createOffer (optional RTCOfferOptions options = {}); - Promise<RTCSessionDescriptionInit> createAnswer (optional RTCAnswerOptions options = {}); - Promise<undefined> setLocalDescription (optional RTCSessionDescriptionInit description = {}); - Promise<undefined> setRemoteDescription (optional RTCSessionDescriptionInit description = {}); + Promise<RTCSessionDescriptionInit> createOffer(optional RTCOfferOptions options = {}); + Promise<RTCSessionDescriptionInit> createAnswer(optional RTCAnswerOptions options = {}); + Promise<undefined> setLocalDescription(optional RTCLocalSessionDescriptionInit description = {}); readonly attribute RTCSessionDescription? localDescription; readonly attribute RTCSessionDescription? currentLocalDescription; readonly attribute RTCSessionDescription? pendingLocalDescription; + Promise<undefined> setRemoteDescription(RTCSessionDescriptionInit description); readonly attribute RTCSessionDescription? remoteDescription; readonly attribute RTCSessionDescription? currentRemoteDescription; readonly attribute RTCSessionDescription? pendingRemoteDescription; @@ -181,21 +180,23 @@ interface RTCPeerConnection : EventTarget { partial interface RTCPeerConnection { - // Dummy Promise<undefined> return values avoid "WebIDL.WebIDLError: error: - // We have overloads with both Promise and non-Promise return types" - - Promise<undefined> createOffer (RTCSessionDescriptionCallback successCallback, - RTCPeerConnectionErrorCallback failureCallback, - optional RTCOfferOptions options = {}); - Promise<undefined> createAnswer (RTCSessionDescriptionCallback successCallback, - RTCPeerConnectionErrorCallback failureCallback); - Promise<undefined> setLocalDescription (RTCSessionDescriptionInit description, + // Legacy Interface Extensions + // Supporting the methods in this section is optional. + // If these methods are supported + // they must be implemented as defined + // in section "Legacy Interface Extensions" + Promise<undefined> createOffer(RTCSessionDescriptionCallback successCallback, + RTCPeerConnectionErrorCallback failureCallback, + optional RTCOfferOptions options = {}); + Promise<undefined> setLocalDescription(RTCLocalSessionDescriptionInit description, + VoidFunction successCallback, + RTCPeerConnectionErrorCallback failureCallback); + Promise<undefined> createAnswer(RTCSessionDescriptionCallback successCallback, + RTCPeerConnectionErrorCallback failureCallback); + Promise<undefined> setRemoteDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback); - Promise<undefined> setRemoteDescription (RTCSessionDescriptionInit description, - VoidFunction successCallback, - RTCPeerConnectionErrorCallback failureCallback); - Promise<undefined> addIceCandidate (RTCIceCandidate candidate, - VoidFunction successCallback, - RTCPeerConnectionErrorCallback failureCallback); + Promise<undefined> addIceCandidate(RTCIceCandidateInit candidate, + VoidFunction successCallback, + RTCPeerConnectionErrorCallback failureCallback); }; diff --git a/dom/webidl/RTCSessionDescription.webidl b/dom/webidl/RTCSessionDescription.webidl index 6cf116ff6e..236d1e147c 100644 --- a/dom/webidl/RTCSessionDescription.webidl +++ b/dom/webidl/RTCSessionDescription.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCSessionDescription + * https://www.w3.org/TR/webrtc/#rtcsessiondescription-class */ enum RTCSdpType { @@ -15,6 +15,11 @@ enum RTCSdpType { }; dictionary RTCSessionDescriptionInit { + required RTCSdpType type; + DOMString sdp = ""; +}; + +dictionary RTCLocalSessionDescriptionInit { RTCSdpType type; DOMString sdp = ""; }; @@ -24,7 +29,7 @@ dictionary RTCSessionDescriptionInit { Exposed=Window] interface RTCSessionDescription { [Throws] - constructor(optional RTCSessionDescriptionInit descriptionInitDict = {}); + constructor(RTCSessionDescriptionInit descriptionInitDict); // These should be readonly, but writing causes deprecation warnings for a bit attribute RTCSdpType type; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index 1aa6be963f..54e4e5ec95 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -64,6 +64,9 @@ dictionary RequestInit { AbortSignal? signal; + [Pref="network.fetchpriority.enabled"] + RequestPriority priority; + [Pref="dom.fetchObserver.enabled"] ObserverCallback observe; }; diff --git a/dom/webidl/SVGAElement.webidl b/dom/webidl/SVGAElement.webidl index 94363513ea..f2621e9e79 100644 --- a/dom/webidl/SVGAElement.webidl +++ b/dom/webidl/SVGAElement.webidl @@ -29,7 +29,7 @@ interface SVGAElement : SVGGraphicsElement { [SetterThrows] attribute DOMString type; - [Throws] + [Throws, Pref="svg.SVGAElement.text.enabled"] attribute DOMString text; }; diff --git a/dom/webidl/SecurityPolicyViolationEvent.webidl b/dom/webidl/SecurityPolicyViolationEvent.webidl index 5578238347..4366347e14 100644 --- a/dom/webidl/SecurityPolicyViolationEvent.webidl +++ b/dom/webidl/SecurityPolicyViolationEvent.webidl @@ -1,6 +1,10 @@ /* 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/. */ + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://w3c.github.io/webappsec-csp/#violation-events + */ enum SecurityPolicyViolationEventDisposition { @@ -16,15 +20,15 @@ interface SecurityPolicyViolationEvent : Event readonly attribute DOMString documentURI; readonly attribute DOMString referrer; readonly attribute DOMString blockedURI; - readonly attribute DOMString violatedDirective; + readonly attribute DOMString violatedDirective; // historical alias of effectiveDirective readonly attribute DOMString effectiveDirective; readonly attribute DOMString originalPolicy; readonly attribute DOMString sourceFile; readonly attribute DOMString sample; readonly attribute SecurityPolicyViolationEventDisposition disposition; readonly attribute unsigned short statusCode; - readonly attribute long lineNumber; - readonly attribute long columnNumber; + readonly attribute unsigned long lineNumber; + readonly attribute unsigned long columnNumber; }; [GenerateInitFromJSON, GenerateToJSON] @@ -38,8 +42,8 @@ dictionary SecurityPolicyViolationEventInit : EventInit DOMString originalPolicy = ""; DOMString sourceFile = ""; DOMString sample = ""; - SecurityPolicyViolationEventDisposition disposition = "report"; + SecurityPolicyViolationEventDisposition disposition = "enforce"; unsigned short statusCode = 0; - long lineNumber = 0; - long columnNumber = 0; + unsigned long lineNumber = 0; + unsigned long columnNumber = 0; }; diff --git a/dom/webidl/TrustedTypes.webidl b/dom/webidl/TrustedTypes.webidl new file mode 100644 index 0000000000..3b7e35534f --- /dev/null +++ b/dom/webidl/TrustedTypes.webidl @@ -0,0 +1,64 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * <https://w3c.github.io/trusted-types/dist/spec/>. + */ + +[Exposed=(Window,Worker), Pref="dom.security.trusted_types.enabled"] +interface TrustedHTML { + stringifier; + DOMString toJSON(); +}; + +[Exposed=(Window,Worker), Pref="dom.security.trusted_types.enabled"] +interface TrustedScript { + stringifier; + DOMString toJSON(); +}; + +[Exposed=(Window,Worker), Pref="dom.security.trusted_types.enabled"] +interface TrustedScriptURL { + stringifier; + USVString toJSON(); +}; + +[Exposed=(Window,Worker), Pref="dom.security.trusted_types.enabled"] +interface TrustedTypePolicy { + readonly attribute DOMString name; + [NewObject] TrustedHTML createHTML(DOMString input, any... arguments); + [NewObject] TrustedScript createScript(DOMString input, any... arguments); + [NewObject] TrustedScriptURL createScriptURL(DOMString input, any... arguments); +}; + +dictionary TrustedTypePolicyOptions { + CreateHTMLCallback createHTML; + CreateScriptCallback createScript; + CreateScriptURLCallback createScriptURL; +}; + +callback CreateHTMLCallback = DOMString? (DOMString input, any... arguments); +callback CreateScriptCallback = DOMString? (DOMString input, any... arguments); +callback CreateScriptURLCallback = USVString? (DOMString input, any... arguments); + +[Exposed=(Window,Worker), Pref="dom.security.trusted_types.enabled"] +interface TrustedTypePolicyFactory { + TrustedTypePolicy createPolicy(DOMString policyName , optional TrustedTypePolicyOptions policyOptions = {}); + boolean isHTML(any value); + boolean isScript(any value); + boolean isScriptURL(any value); + [Pure, StoreInSlot] readonly attribute TrustedHTML emptyHTML; + [Pure, StoreInSlot] readonly attribute TrustedScript emptyScript; + DOMString? getAttributeType( + DOMString tagName, + DOMString attribute, + optional DOMString elementNs = "", + optional DOMString attrNs = ""); + DOMString? getPropertyType( + DOMString tagName, + DOMString property, + optional DOMString elementNs = ""); + readonly attribute TrustedTypePolicy? defaultPolicy; +}; diff --git a/dom/webidl/VideoDecoder.webidl b/dom/webidl/VideoDecoder.webidl index 0c8f5c9b5e..e8abb753dc 100644 --- a/dom/webidl/VideoDecoder.webidl +++ b/dom/webidl/VideoDecoder.webidl @@ -45,7 +45,7 @@ dictionary VideoDecoderSupport { dictionary VideoDecoderConfig { required DOMString codec; - // Bug 1696216: Should be 1696216 [AllowShared] BufferSource description; + // Bug 1696216: Should be [AllowShared] BufferSource description; ([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) description; [EnforceRange] unsigned long codedWidth; [EnforceRange] unsigned long codedHeight; diff --git a/dom/webidl/WebGPU.webidl b/dom/webidl/WebGPU.webidl index 58e259258d..c01d501542 100644 --- a/dom/webidl/WebGPU.webidl +++ b/dom/webidl/WebGPU.webidl @@ -499,6 +499,8 @@ dictionary GPUTextureBindingLayout { enum GPUStorageTextureAccess { "write-only", + "read-only", + "read-write", }; dictionary GPUStorageTextureBindingLayout { @@ -599,7 +601,7 @@ interface mixin GPUPipelineBase { dictionary GPUProgrammableStage { required GPUShaderModule module; - required USVString entryPoint; + USVString entryPoint; }; //TODO: Serializable diff --git a/dom/webidl/WebXR.webidl b/dom/webidl/WebXR.webidl index 302c82156a..7545ffe728 100644 --- a/dom/webidl/WebXR.webidl +++ b/dom/webidl/WebXR.webidl @@ -27,8 +27,8 @@ enum XRSessionMode { }; dictionary XRSessionInit { - sequence<any> requiredFeatures; - sequence<any> optionalFeatures; + sequence<DOMString> requiredFeatures; + sequence<DOMString> optionalFeatures; }; enum XRVisibilityState { diff --git a/dom/webidl/WindowOrWorkerGlobalScope.webidl b/dom/webidl/WindowOrWorkerGlobalScope.webidl index 5b5f5c61f6..5e3ff86e88 100644 --- a/dom/webidl/WindowOrWorkerGlobalScope.webidl +++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl @@ -85,3 +85,10 @@ partial interface mixin WindowOrWorkerGlobalScope { [Replaceable, Pref="dom.enable_web_task_scheduling", SameObject] readonly attribute Scheduler scheduler; }; + + +// https://w3c.github.io/trusted-types/dist/spec/#extensions-to-the-windoworworkerglobalscope-interface +partial interface mixin WindowOrWorkerGlobalScope { + [Pref="dom.security.trusted_types.enabled"] + readonly attribute TrustedTypePolicyFactory trustedTypes; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 68b42a9c54..3880b727e7 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -13,10 +13,7 @@ with Files("APZTestData.webidl"): with Files("AccessibleNode.webidl"): BUG_COMPONENT = ("Core", "Disability Access APIs") -with Files("AccessibilityRole.webidl"): - BUG_COMPONENT = ("Core", "Disability Access APIs") - -with Files("AriaAttributes.webidl"): +with Files("ARIAMixin.webidl"): BUG_COMPONENT = ("Core", "Disability Access APIs") with Files("Addon*"): @@ -402,7 +399,6 @@ WEBIDL_FILES = [ "AbortSignal.webidl", "AbstractRange.webidl", "AbstractWorker.webidl", - "AccessibilityRole.webidl", "AddonManager.webidl", "AnalyserNode.webidl", "Animatable.webidl", @@ -415,11 +411,13 @@ WEBIDL_FILES = [ "AppInfo.webidl", "AppNotificationServiceOptions.webidl", "APZTestData.webidl", - "AriaAttributes.webidl", + "ARIAMixin.webidl", "Attr.webidl", "AudioBuffer.webidl", "AudioBufferSourceNode.webidl", "AudioContext.webidl", + "AudioData.webidl", + "AudioDecoder.webidl", "AudioDestinationNode.webidl", "AudioListener.webidl", "AudioNode.webidl", @@ -523,7 +521,6 @@ WEBIDL_FILES = [ "DOMQuad.webidl", "DOMRect.webidl", "DOMRectList.webidl", - "DOMRequest.webidl", "DOMStringList.webidl", "DOMStringMap.webidl", "DOMTokenList.webidl", @@ -531,6 +528,7 @@ WEBIDL_FILES = [ "DynamicsCompressorNode.webidl", "Element.webidl", "ElementInternals.webidl", + "EncodedAudioChunk.webidl", "EncodedVideoChunk.webidl", "Event.webidl", "EventHandler.webidl", @@ -978,6 +976,7 @@ WEBIDL_FILES = [ "TransformStreamDefaultController.webidl", "TransitionEvent.webidl", "TreeWalker.webidl", + "TrustedTypes.webidl", "UDPMessageEvent.webidl", "UDPSocket.webidl", "UIEvent.webidl", @@ -1052,6 +1051,7 @@ if CONFIG["MOZ_WEBRTC"]: "RTCEncodedAudioFrame.webidl", "RTCEncodedVideoFrame.webidl", "RTCIceCandidate.webidl", + "RTCIceTransport.webidl", "RTCIdentityAssertion.webidl", "RTCIdentityProvider.webidl", "RTCPeerConnection.webidl", diff --git a/dom/webscheduling/WebTaskScheduler.cpp b/dom/webscheduling/WebTaskScheduler.cpp index b6fa749095..46ece9793c 100644 --- a/dom/webscheduling/WebTaskScheduler.cpp +++ b/dom/webscheduling/WebTaskScheduler.cpp @@ -291,9 +291,8 @@ WebTask* WebTaskScheduler::GetNextTask() const { return nullptr; } - for (uint32_t priority = static_cast<uint32_t>(TaskPriority::User_blocking); - priority < static_cast<uint32_t>(TaskPriority::EndGuard_); ++priority) { - if (auto queues = allQueues.Lookup(priority)) { + for (TaskPriority priority : MakeWebIDLEnumeratedRange<TaskPriority>()) { + if (auto queues = allQueues.Lookup(UnderlyingValue(priority))) { WebTaskQueue* oldestQueue = nullptr; MOZ_ASSERT(!queues.Data().IsEmpty()); for (auto& webTaskQueue : queues.Data()) { diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp index c9f7943cc3..236c9a945a 100644 --- a/dom/webtransport/parent/WebTransportParent.cpp +++ b/dom/webtransport/parent/WebTransportParent.cpp @@ -97,14 +97,15 @@ void WebTransportParent::Create( nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( "WebTransport AsyncConnect", [self = RefPtr{this}, uri = std::move(uri), + dedicated = true /* aDedicated, see BUG 1915735.*/, nsServerCertHashes = std::move(nsServerCertHashes), principal = RefPtr{aPrincipal}, flags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, clientInfo = aClientInfo] { LOG(("WebTransport %p AsyncConnect", self.get())); if (NS_FAILED(self->mWebTransport->AsyncConnectWithClient( - uri, std::move(nsServerCertHashes), principal, flags, self, - clientInfo))) { + uri, dedicated, std::move(nsServerCertHashes), principal, flags, + self, clientInfo))) { LOG(("AsyncConnect failure; we should get OnSessionClosed")); } }); diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 3d6a883867..02efb12053 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -369,6 +369,14 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { PREF("gc_min_empty_chunk_count", JSGC_MIN_EMPTY_CHUNK_COUNT), PREF("gc_max_empty_chunk_count", JSGC_MAX_EMPTY_CHUNK_COUNT), PREF("gc_compacting", JSGC_COMPACTING_ENABLED), + PREF("gc_parallel_marking", JSGC_PARALLEL_MARKING_ENABLED), + PREF("gc_parallel_marking_threshold_mb", + JSGC_PARALLEL_MARKING_THRESHOLD_MB), + // Note: Workers do not currently trigger eager minor GC, but if that is + // desired the following parameters should be added: + // javascript.options.mem.nursery_eager_collection_threshold_kb + // javascript.options.mem.nursery_eager_collection_threshold_percent + // javascript.options.mem.nursery_eager_collection_timeout_ms }; #undef PREF @@ -439,6 +447,7 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { case JSGC_MIN_EMPTY_CHUNK_COUNT: case JSGC_MAX_EMPTY_CHUNK_COUNT: case JSGC_HEAP_GROWTH_FACTOR: + case JSGC_PARALLEL_MARKING_THRESHOLD_MB: UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key); break; default: @@ -1410,10 +1419,16 @@ nsresult RuntimeService::Init() { Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN); gMaxWorkersPerDomain = std::max(0, maxPerDomain); - if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { + IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate(); + if (NS_WARN_IF(!idm)) { return NS_ERROR_UNEXPECTED; } + rv = idm->EnsureLocale(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // PerformanceService must be initialized on the main-thread. PerformanceService::GetOrCreate(); diff --git a/dom/workers/WorkerIPCUtils.h b/dom/workers/WorkerIPCUtils.h index 0be45b307f..a93fad5b02 100644 --- a/dom/workers/WorkerIPCUtils.h +++ b/dom/workers/WorkerIPCUtils.h @@ -6,7 +6,7 @@ #ifndef _mozilla_dom_WorkerIPCUtils_h #define _mozilla_dom_WorkerIPCUtils_h -#include "ipc/EnumSerializer.h" +#include "mozilla/dom/BindingIPCUtils.h" // Undo X11/X.h's definition of None #undef None @@ -17,9 +17,7 @@ namespace IPC { template <> struct ParamTraits<mozilla::dom::WorkerType> - : public ContiguousEnumSerializer<mozilla::dom::WorkerType, - mozilla::dom::WorkerType::Classic, - mozilla::dom::WorkerType::EndGuard_> {}; + : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::WorkerType> {}; } // namespace IPC diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 78de4adc3a..a8643981aa 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -2804,7 +2804,12 @@ nsresult WorkerPrivate::GetLoadInfo( AssertIsOnMainThread(); // Make sure that the IndexedDatabaseManager is set up - Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); + IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate(); + if (idm) { + Unused << NS_WARN_IF(NS_FAILED(idm->EnsureLocale())); + } else { + NS_WARNING("Failed to get IndexedDatabaseManager!"); + } nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); MOZ_ASSERT(ssm); diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 159829f4a8..92d6c89dca 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -404,6 +404,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScope, NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrustedTypePolicyFactory) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet) @@ -420,6 +421,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScope, tmp->mWebTaskScheduler->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler) } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrustedTypePolicyFactory) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) @@ -868,6 +870,14 @@ void WorkerGlobalScope::StorageAccessPermissionGranted() { mCacheStorage = nullptr; } +TrustedTypePolicyFactory* WorkerGlobalScope::TrustedTypes() { + if (!mTrustedTypePolicyFactory) { + mTrustedTypePolicyFactory = MakeRefPtr<TrustedTypePolicyFactory>(this); + } + + return mTrustedTypePolicyFactory; +} + bool WorkerGlobalScope::WindowInteractionAllowed() const { AssertIsOnWorkerThread(); return mWindowInteractionsAllowed > 0; diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 7e00f9b59b..12a97e12c3 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -22,6 +22,7 @@ #include "mozilla/dom/ImageBitmapSource.h" #include "mozilla/dom/PerformanceWorker.h" #include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/dom/TrustedTypePolicyFactory.h" #include "mozilla/dom/WorkerPrivate.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" @@ -352,6 +353,8 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase { MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void OnVsync(const VsyncEvent& aVsync) {} + TrustedTypePolicyFactory* TrustedTypes(); + protected: ~WorkerGlobalScope(); @@ -376,6 +379,7 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase { RefPtr<cache::CacheStorage> mCacheStorage; RefPtr<DebuggerNotificationManager> mDebuggerNotificationManager; RefPtr<WebTaskSchedulerWorker> mWebTaskScheduler; + RefPtr<TrustedTypePolicyFactory> mTrustedTypePolicyFactory; uint32_t mWindowInteractionsAllowed = 0; bool mIsEligibleForMessaging{true}; }; diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp index a644439a8d..feb294f3fc 100644 --- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp @@ -232,7 +232,12 @@ nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { // Ensure that the IndexedDatabaseManager is initialized so that if any // workers do any IndexedDB calls that all of IDB's prefs/etc. are // initialized. - Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); + IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate(); + if (idm) { + Unused << NS_WARN_IF(NS_FAILED(idm->EnsureLocale())); + } else { + NS_WARNING("Failed to get IndexedDatabaseManager!"); + } auto scopeExit = MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); }); diff --git a/dom/workers/test/WorkerDebugger.console_debugger.js b/dom/workers/test/WorkerDebugger.console_debugger.js index a8b2493200..805b7fb387 100644 --- a/dom/workers/test/WorkerDebugger.console_debugger.js +++ b/dom/workers/test/WorkerDebugger.console_debugger.js @@ -1,5 +1,7 @@ "use strict"; +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(a, msg) { postMessage(JSON.stringify({ type: "status", what: !!a, msg })); } diff --git a/dom/workers/test/bug1014466_worker.js b/dom/workers/test/bug1014466_worker.js index 2161954d2b..e06979d7b3 100644 --- a/dom/workers/test/bug1014466_worker.js +++ b/dom/workers/test/bug1014466_worker.js @@ -3,6 +3,8 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(a, msg) { postMessage({ type: "status", status: !!a, msg }); } diff --git a/dom/workers/test/notification_permission_worker.js b/dom/workers/test/notification_permission_worker.js index 0e6b96d975..0551247d07 100644 --- a/dom/workers/test/notification_permission_worker.js +++ b/dom/workers/test/notification_permission_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function info(message) { dump("INFO: " + message + "\n"); } diff --git a/dom/workers/test/notification_worker.js b/dom/workers/test/notification_worker.js index 87aa02ac05..b97a4eb505 100644 --- a/dom/workers/test/notification_worker.js +++ b/dom/workers/test/notification_worker.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(test, message) { postMessage({ type: "ok", test, message }); } diff --git a/dom/workers/test/notification_worker_child-child.js b/dom/workers/test/notification_worker_child-child.js index 236e314e47..9356f1be7a 100644 --- a/dom/workers/test/notification_worker_child-child.js +++ b/dom/workers/test/notification_worker_child-child.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(test, message) { postMessage({ type: "ok", test, message }); } diff --git a/dom/workers/test/onLine_worker_child.js b/dom/workers/test/onLine_worker_child.js index 92542c018f..cee3052c90 100644 --- a/dom/workers/test/onLine_worker_child.js +++ b/dom/workers/test/onLine_worker_child.js @@ -3,10 +3,7 @@ * http://creativecommons.org/licenses/publicdomain/ */ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ function info(text) { dump("Test for Bug 925437: worker: " + text + "\n"); diff --git a/dom/workers/test/onLine_worker_head.js b/dom/workers/test/onLine_worker_head.js index 632821b1f4..8c6d601aa5 100644 --- a/dom/workers/test/onLine_worker_head.js +++ b/dom/workers/test/onLine_worker_head.js @@ -3,6 +3,8 @@ * http://creativecommons.org/licenses/publicdomain/ */ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function info(text) { dump("Test for Bug 925437: worker: " + text + "\n"); } diff --git a/dom/workers/test/promise_worker.js b/dom/workers/test/promise_worker.js index fd4a9177d6..1c2d080e5b 100644 --- a/dom/workers/test/promise_worker.js +++ b/dom/workers/test/promise_worker.js @@ -1,5 +1,7 @@ "use strict"; +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(a, msg) { dump("OK: " + !!a + " => " + a + " " + msg + "\n"); postMessage({ type: "status", status: !!a, msg: a + ": " + msg }); diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index 3ea89ad6b5..efd108f85c 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + // This is a list of all interfaces that are exposed to workers. // Please only add things to this list with great care and proper review // from the associated module peers. @@ -132,6 +134,10 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "AbortSignal", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioData", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioDecoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "Blob", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "BroadcastChannel", insecureContext: true }, @@ -180,10 +186,10 @@ let interfaceNamesInGlobalScope = [ // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DOMRectReadOnly", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "DOMRequest", insecureContext: true, disabled: true }, - // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DOMStringList", insecureContext: true }, // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EncodedAudioChunk", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! { name: "EncodedVideoChunk", insecureContext: true, nightly: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "ErrorEvent", insecureContext: true }, diff --git a/dom/xhr/XMLHttpRequest.h b/dom/xhr/XMLHttpRequest.h index 0d82f8c035..a7c4c0550c 100644 --- a/dom/xhr/XMLHttpRequest.h +++ b/dom/xhr/XMLHttpRequest.h @@ -29,9 +29,12 @@ class XMLHttpRequest : public XMLHttpRequestEventTarget { const char* cStr; const char16_t* str; - EventType(const char* name, const char16_t* uname) + constexpr EventType(const char* name, const char16_t* uname) : cStr(name), str(uname) {} + constexpr EventType(const EventType& other) + : cStr(other.cStr), str(other.str) {} + operator const nsDependentString() const { return nsDependentString(str); } friend bool operator==(const EventType& a, const EventType& b) { @@ -56,55 +59,26 @@ class XMLHttpRequest : public XMLHttpRequestEventTarget { }; struct ProgressEventType : public EventType { - ProgressEventType(const char* name, const char16_t* uname) + constexpr ProgressEventType(const char* name, const char16_t* uname) : EventType(name, uname) {} }; struct ErrorProgressEventType : public ProgressEventType { const nsresult errorCode; - - ErrorProgressEventType(const char* name, const char16_t* uname, - const nsresult code) + constexpr ErrorProgressEventType(const char* name, const char16_t* uname, + const nsresult code) : ProgressEventType(name, uname), errorCode(code) {} }; -#define DECL_EVENT(NAME) \ - static inline const EventType NAME = EventType(#NAME, u## #NAME); - -#define DECL_PROGRESSEVENT(NAME) \ - static inline const ProgressEventType NAME = \ - ProgressEventType(#NAME, u## #NAME); - -#define DECL_ERRORPROGRESSEVENT(NAME, ERR) \ - static inline const ErrorProgressEventType NAME = \ - ErrorProgressEventType(#NAME, u## #NAME, ERR); - - struct Events { - DECL_EVENT(readystatechange); - DECL_PROGRESSEVENT(loadstart); - DECL_PROGRESSEVENT(progress); - DECL_ERRORPROGRESSEVENT(error, NS_ERROR_DOM_NETWORK_ERR); - DECL_ERRORPROGRESSEVENT(abort, NS_ERROR_DOM_ABORT_ERR); - DECL_ERRORPROGRESSEVENT(timeout, NS_ERROR_DOM_TIMEOUT_ERR); - DECL_PROGRESSEVENT(load); - DECL_PROGRESSEVENT(loadend); - - static inline const EventType* All[]{ - &readystatechange, &loadstart, &progress, &error, &abort, - &timeout, &load, &loadend}; - - static inline const EventType* ProgressEvents[]{ - &loadstart, &progress, &error, &abort, &timeout, &load, &loadend}; - - static inline const EventType* Find(const nsString& name) { - for (const EventType* type : Events::All) { - if (*type == name) { - return type; - } - } - return nullptr; - } - }; +#define DECL_EVENT(NAME) static constexpr EventType NAME{#NAME, u## #NAME}; + +#define DECL_PROGRESSEVENT(NAME) \ + static constexpr ProgressEventType NAME { #NAME, u## #NAME } + +#define DECL_ERRORPROGRESSEVENT(NAME, ERR) \ + static constexpr ErrorProgressEventType NAME{#NAME, u## #NAME, ERR}; + + struct Events; static already_AddRefed<XMLHttpRequest> Constructor( const GlobalObject& aGlobal, const MozXMLHttpRequestParameters& aParams, @@ -216,6 +190,33 @@ class XMLHttpRequest : public XMLHttpRequestEventTarget { : XMLHttpRequestEventTarget(aGlobalObject) {} }; +struct XMLHttpRequest::Events { + DECL_EVENT(readystatechange); + DECL_PROGRESSEVENT(loadstart); + DECL_PROGRESSEVENT(progress); + DECL_ERRORPROGRESSEVENT(error, NS_ERROR_DOM_NETWORK_ERR); + DECL_ERRORPROGRESSEVENT(abort, NS_ERROR_DOM_ABORT_ERR); + DECL_ERRORPROGRESSEVENT(timeout, NS_ERROR_DOM_TIMEOUT_ERR); + DECL_PROGRESSEVENT(load); + DECL_PROGRESSEVENT(loadend); + + static inline const EventType* All[]{ + &readystatechange, &loadstart, &progress, &error, &abort, + &timeout, &load, &loadend}; + + static inline const EventType* ProgressEvents[]{ + &loadstart, &progress, &error, &abort, &timeout, &load, &loadend}; + + static inline const EventType* Find(const nsString& name) { + for (const EventType* type : Events::All) { + if (*type == name) { + return type; + } + } + return nullptr; + } +}; + } // namespace mozilla::dom #endif // mozilla_dom_XMLHttpRequest_h diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp index dc256b10c2..ace26f296f 100644 --- a/dom/xhr/XMLHttpRequestMainThread.cpp +++ b/dom/xhr/XMLHttpRequestMainThread.cpp @@ -40,6 +40,7 @@ #include "mozilla/LoadInfo.h" #include "mozilla/LoadContext.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/net/ContentRange.h" #include "mozilla/PreloaderBase.h" #include "mozilla/ScopeExit.h" #include "mozilla/SpinEventLoopUntil.h" @@ -48,9 +49,9 @@ #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/dom/ProgressEvent.h" #include "nsDataChannel.h" +#include "nsIBaseChannel.h" #include "nsIJARChannel.h" #include "nsIJARURI.h" -#include "nsLayoutCID.h" #include "nsReadableUtils.h" #include "nsSandboxFlags.h" @@ -186,15 +187,17 @@ static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) { // invoked for increased scrutability. Save the previous value on the stack. namespace { struct DebugWorkerRefs { - RefPtr<ThreadSafeWorkerRef>& mTSWorkerRef; + Mutex& mMutex; + RefPtr<ThreadSafeWorkerRef> mTSWorkerRef; nsCString mPrev; - DebugWorkerRefs(RefPtr<ThreadSafeWorkerRef>& aTSWorkerRef, - const std::string& aStatus) - : mTSWorkerRef(aTSWorkerRef) { + DebugWorkerRefs(XMLHttpRequestMainThread& aXHR, const std::string& aStatus) + : mMutex(aXHR.mTSWorkerRefMutex) { + MutexAutoLock lock(mMutex); + + mTSWorkerRef = aXHR.mTSWorkerRef; + if (!mTSWorkerRef) { - MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info, - ("No WorkerRef during: %s", aStatus.c_str())); return; } @@ -206,6 +209,8 @@ struct DebugWorkerRefs { } ~DebugWorkerRefs() { + MutexAutoLock lock(mMutex); + if (!mTSWorkerRef) { return; } @@ -213,6 +218,8 @@ struct DebugWorkerRefs { MOZ_ASSERT(mTSWorkerRef->Private()); SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), mPrev); + + mTSWorkerRef = nullptr; } }; } // namespace @@ -220,11 +227,13 @@ struct DebugWorkerRefs { # define STREAM_STRING(stuff) \ (((const std::ostringstream&)(std::ostringstream() << stuff)) \ .str()) // NOLINT + # define DEBUG_WORKERREFS \ - DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(mTSWorkerRef, __func__) + DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(*this, __func__) + # define DEBUG_WORKERREFS1(x) \ DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)( \ - mTSWorkerRef, STREAM_STRING(__func__ << ": " << x)) // NOLINT + *this, STREAM_STRING(__func__ << ": " << x)) // NOLINT #else # define DEBUG_WORKERREFS void() @@ -236,6 +245,9 @@ bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false; XMLHttpRequestMainThread::XMLHttpRequestMainThread( nsIGlobalObject* aGlobalObject) : XMLHttpRequest(aGlobalObject), +#ifdef DEBUG + mTSWorkerRefMutex("Debug WorkerRefs"), +#endif mResponseBodyDecodedPos(0), mResponseType(XMLHttpRequestResponseType::_empty), mState(XMLHttpRequest_Binding::UNSENT), @@ -864,14 +876,28 @@ bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() { return false; } -Maybe<nsBaseChannel::ContentRange> +bool XMLHttpRequestMainThread::BadContentRangeRequested() { + if (!mChannel) { + return false; + } + // Only nsIBaseChannel supports this + nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel); + if (!baseChan) { + return false; + } + // A bad range was requested if the channel has no content range + // despite the request specifying a range header. + return !baseChan->ContentRange() && mAuthorRequestHeaders.Has("range"); +} + +RefPtr<mozilla::net::ContentRange> XMLHttpRequestMainThread::GetRequestedContentRange() const { MOZ_ASSERT(mChannel); - nsBaseChannel* baseChan = static_cast<nsBaseChannel*>(mChannel.get()); + nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel); if (!baseChan) { - return mozilla::Nothing(); + return nullptr; } - return baseChan->GetContentRange(); + return baseChan->ContentRange(); } void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const { @@ -879,8 +905,8 @@ void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const { out.SetIsVoid(true); return; } - Maybe<nsBaseChannel::ContentRange> range = GetRequestedContentRange(); - if (range.isSome()) { + RefPtr<mozilla::net::ContentRange> range = GetRequestedContentRange(); + if (range) { range->AsHeader(out); } else { out.SetIsVoid(true); @@ -944,8 +970,7 @@ uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) { nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel(); if (!httpChannel) { // Pretend like we got a 200/206 response, since our load was successful - return IsBlobURI(mRequestURL) && GetRequestedContentRange().isSome() ? 206 - : 200; + return GetRequestedContentRange() ? 206 : 200; } uint32_t status; @@ -1194,13 +1219,13 @@ bool XMLHttpRequestMainThread::IsSafeHeader( bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue) const { MOZ_ASSERT(mChannel); - nsCOMPtr<nsIURI> uri; - if (NS_SUCCEEDED(mChannel->GetURI(getter_AddRefs(uri))) && - uri->SchemeIs("data")) { - nsDataChannel* dchan = static_cast<nsDataChannel*>(mChannel.get()); - MOZ_ASSERT(dchan); - aValue.Assign(dchan->MimeType()); - return true; + nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel); + if (baseChan) { + RefPtr<CMimeType> fullMimeType(baseChan->FullMimeType()); + if (fullMimeType) { + fullMimeType->Serialize(aValue); + return true; + } } if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) { nsCString value; @@ -1956,8 +1981,7 @@ XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) { // If we were asked for a bad range on a blob URL, but we're async, // we should throw now in order to fire an error progress event. - if (IsBlobURI(mRequestURL) && GetRequestedContentRange().isNothing() && - mAuthorRequestHeaders.Has("range")) { + if (BadContentRangeRequested()) { return NS_ERROR_NET_PARTIAL_TRANSFER; } @@ -3128,7 +3152,7 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody, if (uploadContentType.IsVoid()) { uploadContentType = defaultContentType; } else if (aBodyIsDocumentOrString) { - UniquePtr<CMimeType> contentTypeRecord = + RefPtr<CMimeType> contentTypeRecord = CMimeType::Parse(uploadContentType); nsAutoCString charset; if (contentTypeRecord && @@ -3391,7 +3415,7 @@ void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType, return; } - UniquePtr<MimeType> parsed = MimeType::Parse(aMimeType); + RefPtr<MimeType> parsed = MimeType::Parse(aMimeType); if (parsed) { parsed->Serialize(mOverrideMimeType); } else { diff --git a/dom/xhr/XMLHttpRequestMainThread.h b/dom/xhr/XMLHttpRequestMainThread.h index 3f2d395991..b860f041fb 100644 --- a/dom/xhr/XMLHttpRequestMainThread.h +++ b/dom/xhr/XMLHttpRequestMainThread.h @@ -48,7 +48,6 @@ #include "mozilla/dom/XMLHttpRequestEventTarget.h" #include "mozilla/dom/XMLHttpRequestString.h" #include "mozilla/Encoding.h" -#include "nsBaseChannel.h" #ifdef Status /* Xlib headers insist on this for some reason... Nuke it because @@ -65,6 +64,10 @@ class nsILoadGroup; namespace mozilla { class ProfileChunkedBuffer; +namespace net { +class ContentRange; +} + namespace dom { class DOMString; @@ -447,7 +450,8 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest, #ifdef DEBUG // For logging when there's trouble - RefPtr<ThreadSafeWorkerRef> mTSWorkerRef = nullptr; + RefPtr<ThreadSafeWorkerRef> mTSWorkerRef MOZ_GUARDED_BY(mTSWorkerRefMutex); + Mutex mTSWorkerRefMutex; #endif protected: @@ -507,7 +511,8 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest, void AbortInternal(ErrorResult& aRv); - Maybe<nsBaseChannel::ContentRange> GetRequestedContentRange() const; + bool BadContentRangeRequested(); + RefPtr<mozilla::net::ContentRange> GetRequestedContentRange() const; void GetContentRangeHeader(nsACString&) const; struct PendingEvent { diff --git a/dom/xhr/XMLHttpRequestWorker.cpp b/dom/xhr/XMLHttpRequestWorker.cpp index 371f444ee9..7fdfa8fee9 100644 --- a/dom/xhr/XMLHttpRequestWorker.cpp +++ b/dom/xhr/XMLHttpRequestWorker.cpp @@ -198,11 +198,13 @@ class Proxy final : public nsIDOMEventListener { #ifdef DEBUG void DebugStoreWorkerRef(RefPtr<StrongWorkerRef>& aWorkerRef) { MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(mXHR->mTSWorkerRefMutex); mXHR->mTSWorkerRef = new ThreadSafeWorkerRef(aWorkerRef); } void DebugForgetWorkerRef() { MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(mXHR->mTSWorkerRefMutex); mXHR->mTSWorkerRef = nullptr; } #endif @@ -325,7 +327,6 @@ class LoadStartDetectionRunnable final : public Runnable, WorkerPrivate* mWorkerPrivate; RefPtr<Proxy> mProxy; RefPtr<XMLHttpRequest> mXHR; - nsString mEventType; uint32_t mChannelId; bool mReceivedLoadStart; diff --git a/dom/xhr/tests/file_XHRResponseURL.js b/dom/xhr/tests/file_XHRResponseURL.js index 1ab1694bfa..29e6ddea69 100644 --- a/dom/xhr/tests/file_XHRResponseURL.js +++ b/dom/xhr/tests/file_XHRResponseURL.js @@ -51,7 +51,7 @@ function info(aMessage) { } function request(aURL) { - return new Promise(function (aResolve, aReject) { + return new Promise(function (aResolve) { var xhr = new XMLHttpRequest(); xhr.open("GET", aURL); xhr.addEventListener("load", function () { @@ -272,7 +272,7 @@ function testNotToLeakResponseURLWhileDoingRedirects() { function testNotToLeakResponseURLWhileDoingRedirectsInWindow() { var xhr = new XMLHttpRequest(); var requestObserver = { - observe(aSubject, aTopic, aData) { + observe() { is(xhr.readyState, XMLHttpRequest.OPENED, "assert for XHR state"); is( xhr.responseURL, @@ -286,7 +286,7 @@ function testNotToLeakResponseURLWhileDoingRedirectsInWindow() { "specialpowers-http-notify-request" ); - return new Promise(function (aResolve, aReject) { + return new Promise(function (aResolve) { xhr.open( "GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text" @@ -322,7 +322,7 @@ function testNotToLeakResponseURLWhileDoingRedirectsInWorker() { } }; - return new Promise(function (aResolve, aReject) { + return new Promise(function (aResolve) { self.addEventListener("message", testRedirect); message({ type: "redirect_test", status: "start" }); xhr.open( @@ -334,7 +334,7 @@ function testNotToLeakResponseURLWhileDoingRedirectsInWorker() { message({ type: "redirect_test", status: "end" }); aResolve(); }); - xhr.addEventListener("error", function (e) { + xhr.addEventListener("error", function () { ok(false, "unexpected request falilure"); self.removeEventListener("message", testRedirect); message({ type: "redirect_test", status: "end" }); @@ -345,7 +345,7 @@ function testNotToLeakResponseURLWhileDoingRedirectsInWorker() { } function waitForAllMessagesProcessed() { - return new Promise(function (aResolve, aReject) { + return new Promise(function (aResolve) { var id = setInterval(function () { if (message.ping === message.pong) { clearInterval(id); diff --git a/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html b/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html index 2135011d9c..895fbc4e4b 100644 --- a/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html +++ b/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html @@ -7,7 +7,7 @@ function syncXHR() { xhr.send(null); } -addEventListener('load', evt => { +addEventListener('load', () => { syncXHR(); document.open(); document.write( diff --git a/dom/xhr/tests/relativeLoad_worker.js b/dom/xhr/tests/relativeLoad_worker.js index b600b592be..6d281244c2 100644 --- a/dom/xhr/tests/relativeLoad_worker.js +++ b/dom/xhr/tests/relativeLoad_worker.js @@ -6,7 +6,7 @@ /* global workerURL */ const importURL = "relativeLoad_import.js"; -onmessage = function (event) { +onmessage = function () { var xhr = new XMLHttpRequest(); xhr.open("GET", "worker_testXHR.txt", false); xhr.send(null); diff --git a/dom/xhr/tests/temporaryFileBlob.sjs b/dom/xhr/tests/temporaryFileBlob.sjs index d952b325ce..151c05a231 100644 --- a/dom/xhr/tests/temporaryFileBlob.sjs +++ b/dom/xhr/tests/temporaryFileBlob.sjs @@ -28,7 +28,7 @@ function handleRequest(request, response) { bos.writeByteArray(part); response.timer1 = new Timer( - function (timer) { + function () { bos.writeByteArray(bodyBytes); }, 1000, @@ -36,7 +36,7 @@ function handleRequest(request, response) { ); response.timer2 = new Timer( - function (timer) { + function () { response.finish(); }, 2000, diff --git a/dom/xhr/tests/terminateSyncXHR_worker.js b/dom/xhr/tests/terminateSyncXHR_worker.js index 7a2509af3d..29a5a8a369 100644 --- a/dom/xhr/tests/terminateSyncXHR_worker.js +++ b/dom/xhr/tests/terminateSyncXHR_worker.js @@ -3,7 +3,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -onmessage = function (event) { +onmessage = function () { throw new Error("No messages should reach me!"); }; diff --git a/dom/xhr/tests/test_XHR.js b/dom/xhr/tests/test_XHR.js index 12eb06e4f6..0034f8a0eb 100644 --- a/dom/xhr/tests/test_XHR.js +++ b/dom/xhr/tests/test_XHR.js @@ -1,4 +1,7 @@ "use strict"; + +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + SimpleTest.waitForExplicitFinish(); var gen = runTests(); diff --git a/dom/xhr/tests/test_XHRDocURI.html b/dom/xhr/tests/test_XHRDocURI.html index 1062be13a6..61be580ddd 100644 --- a/dom/xhr/tests/test_XHRDocURI.html +++ b/dom/xhr/tests/test_XHRDocURI.html @@ -89,7 +89,7 @@ function* runTest() { // use content XHR and access URI properties from content privileged script var xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -108,7 +108,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -126,7 +126,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.text"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { is(xhr.responseXML, null, "should not have document"); if (xhr.readyState == 4) { gen.next(); @@ -137,7 +137,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -156,7 +156,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -174,7 +174,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -193,7 +193,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -211,7 +211,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.text"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { is(xhr.responseXML, null, "should not have document"); if (xhr.readyState == 4) { gen.next(); @@ -225,7 +225,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -246,7 +246,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -266,7 +266,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -287,7 +287,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -307,7 +307,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -328,7 +328,7 @@ function* runTest() { xhr = new XMLHttpRequest; xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -351,7 +351,7 @@ function* runTest() { SpecialPowers.addPermission("systemXHR", true, document); xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -370,7 +370,7 @@ function* runTest() { xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -388,7 +388,7 @@ function* runTest() { xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -407,7 +407,7 @@ function* runTest() { xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } @@ -425,7 +425,7 @@ function* runTest() { xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml"); - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.responseXML) { return; } @@ -444,7 +444,7 @@ function* runTest() { xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true}); xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"); xhr.responseType = "document"; - xhr.onreadystatechange = function(e) { + xhr.onreadystatechange = function() { if (!xhr.response) { return; } diff --git a/dom/xhr/tests/test_XHR_timeout.js b/dom/xhr/tests/test_XHR_timeout.js index 1e75c1c174..69986770e1 100644 --- a/dom/xhr/tests/test_XHR_timeout.js +++ b/dom/xhr/tests/test_XHR_timeout.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + /* Notes: - All times are expressed in milliseconds in this test suite. - Test harness code is at the end of this file. diff --git a/dom/xhr/tests/test_bug1752863_worker.js b/dom/xhr/tests/test_bug1752863_worker.js index 196b825c0c..929b005939 100644 --- a/dom/xhr/tests/test_bug1752863_worker.js +++ b/dom/xhr/tests/test_bug1752863_worker.js @@ -24,7 +24,7 @@ async function handleLoadstart() { } } -self.onmessage = async function (ev) { +self.onmessage = async function () { xhr = new XMLHttpRequest({ mozAnon: false }); myself = self; xhr.addEventListener("loadstart", handleLoadstart, true); diff --git a/dom/xhr/tests/test_worker_xhr_responseURL.html b/dom/xhr/tests/test_worker_xhr_responseURL.html index 89924e9815..b33cc880c6 100644 --- a/dom/xhr/tests/test_worker_xhr_responseURL.html +++ b/dom/xhr/tests/test_worker_xhr_responseURL.html @@ -16,7 +16,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=998076 var worker = new Worker("../../../dom/xhr/tests/file_XHRResponseURL.js"); var requestObserver = { - observe (aSubject, aTopic, aData) { + observe () { worker.postMessage("request"); } }; diff --git a/dom/xhr/tests/test_worker_xhr_system.js b/dom/xhr/tests/test_worker_xhr_system.js index 23137801a0..ee5934363d 100644 --- a/dom/xhr/tests/test_worker_xhr_system.js +++ b/dom/xhr/tests/test_worker_xhr_system.js @@ -1,3 +1,5 @@ +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + function ok(what, msg) { postMessage({ event: msg, test: "ok", a: what }); } @@ -6,7 +8,7 @@ function is(a, b, msg) { postMessage({ event: msg, test: "is", a, b }); } -self.onmessage = function onmessage(event) { +self.onmessage = function onmessage() { // An XHR with system privileges will be able to do cross-site calls. const TEST_URL = diff --git a/dom/xhr/tests/test_xhr_progressevents.html b/dom/xhr/tests/test_xhr_progressevents.html index ebfc06fd5b..69f549f7a8 100644 --- a/dom/xhr/tests/test_xhr_progressevents.html +++ b/dom/xhr/tests/test_xhr_progressevents.html @@ -12,7 +12,7 @@ SimpleTest.waitForExplicitFinish(); var gen = runTests(); -function log(s) { +function log() { // Uncomment these to get debugging information /* document.getElementById("l").textContent += s + "\n"; diff --git a/dom/xhr/tests/worker_temporaryFileBlob.js b/dom/xhr/tests/worker_temporaryFileBlob.js index 50f071bab7..056b826e00 100644 --- a/dom/xhr/tests/worker_temporaryFileBlob.js +++ b/dom/xhr/tests/worker_temporaryFileBlob.js @@ -1,4 +1,7 @@ /* eslint-env worker */ + +/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ + importScripts("common_temporaryFileBlob.js"); function info(msg) { diff --git a/dom/xhr/tests/worker_terminateSyncXHR_frame.html b/dom/xhr/tests/worker_terminateSyncXHR_frame.html index 04bd53ff1d..720cf9551d 100644 --- a/dom/xhr/tests/worker_terminateSyncXHR_frame.html +++ b/dom/xhr/tests/worker_terminateSyncXHR_frame.html @@ -16,7 +16,7 @@ parent.postMessage(event.data, "*"); }; - worker.onerror = function(event) { + worker.onerror = function() { parent.postMessage("ERROR!", "*"); } } diff --git a/dom/xhr/tests/xhrAbort_worker.js b/dom/xhr/tests/xhrAbort_worker.js index 6b82241d68..e922f8259f 100644 --- a/dom/xhr/tests/xhrAbort_worker.js +++ b/dom/xhr/tests/xhrAbort_worker.js @@ -57,11 +57,11 @@ function runTest() { events.push(str); } - xhr.onerror = function (event) { + xhr.onerror = function () { throw new Error("Error: " + xhr.statusText); }; - xhr.onload = function (event) { + xhr.onload = function () { throw new Error("Shouldn't have gotten load event!"); }; diff --git a/dom/xhr/tests/xhr_worker.js b/dom/xhr/tests/xhr_worker.js index 46edd700db..4361b347e5 100644 --- a/dom/xhr/tests/xhr_worker.js +++ b/dom/xhr/tests/xhr_worker.js @@ -53,21 +53,21 @@ function onprogress(event) { } xhr.addEventListener("progress", onprogress); -xhr.addEventListener("foopety", function (event) {}); -xhr.removeEventListener("doopety", function (event) {}); +xhr.addEventListener("foopety", function () {}); +xhr.removeEventListener("doopety", function () {}); -xhr.onloadend = function (event) { +xhr.onloadend = function () { const message = { type: "loadend" }; postMessage(message); }; var upload = xhr.upload; -upload.onprogress = function (event) {}; -upload.addEventListener("foo", function (event) {}); -upload.removeEventListener("foo", function (event) {}); -upload.addEventListener("load", function (event) {}); -upload.removeEventListener("foo", function (event) {}); -upload.onload = function (event) { +upload.onprogress = function () {}; +upload.addEventListener("foo", function () {}); +upload.removeEventListener("foo", function () {}); +upload.addEventListener("load", function () {}); +upload.removeEventListener("foo", function () {}); +upload.onload = function () { const message = { type: "upload.load" }; postMessage(message); }; diff --git a/dom/xml/XMLDocument.cpp b/dom/xml/XMLDocument.cpp index e62269b9a6..e9136bbe46 100644 --- a/dom/xml/XMLDocument.cpp +++ b/dom/xml/XMLDocument.cpp @@ -17,7 +17,6 @@ #include "nsNetUtil.h" #include "nsError.h" #include "nsIPrincipal.h" -#include "nsLayoutCID.h" #include "mozilla/dom/Attr.h" #include "nsCExternalHandlerService.h" #include "nsMimeTypes.h" diff --git a/dom/xml/XMLStylesheetProcessingInstruction.cpp b/dom/xml/XMLStylesheetProcessingInstruction.cpp index 9f0d3f6156..a022ed4272 100644 --- a/dom/xml/XMLStylesheetProcessingInstruction.cpp +++ b/dom/xml/XMLStylesheetProcessingInstruction.cpp @@ -49,10 +49,11 @@ nsresult XMLStylesheetProcessingInstruction::BindToTree(BindContext& aContext, return rv; } -void XMLStylesheetProcessingInstruction::UnbindFromTree(bool aNullParent) { +void XMLStylesheetProcessingInstruction::UnbindFromTree( + UnbindContext& aContext) { nsCOMPtr<Document> oldDoc = GetUncomposedDoc(); - ProcessingInstruction::UnbindFromTree(aNullParent); + ProcessingInstruction::UnbindFromTree(aContext); Unused << UpdateStyleSheetInternal(oldDoc, nullptr); } diff --git a/dom/xml/XMLStylesheetProcessingInstruction.h b/dom/xml/XMLStylesheetProcessingInstruction.h index a6b3aa6978..cacc0aa932 100644 --- a/dom/xml/XMLStylesheetProcessingInstruction.h +++ b/dom/xml/XMLStylesheetProcessingInstruction.h @@ -43,7 +43,7 @@ class XMLStylesheetProcessingInstruction final : public ProcessingInstruction, // nsIContent virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; /** * Tells this processing instruction to use a different base URI. This is used diff --git a/dom/xml/nsXMLElement.cpp b/dom/xml/nsXMLElement.cpp index ef51715d1b..124d4f5873 100644 --- a/dom/xml/nsXMLElement.cpp +++ b/dom/xml/nsXMLElement.cpp @@ -28,7 +28,7 @@ JSObject* nsXMLElement::WrapNode(JSContext* aCx, return Element_Binding::Wrap(aCx, this, aGivenProto); } -void nsXMLElement::UnbindFromTree(bool aNullParent) { +void nsXMLElement::UnbindFromTree(UnbindContext& aContext) { nsAtom* property; switch (GetPseudoElementType()) { case PseudoStyleType::marker: @@ -48,7 +48,7 @@ void nsXMLElement::UnbindFromTree(bool aNullParent) { MOZ_ASSERT(GetParent()->IsElement()); GetParent()->RemoveProperty(property); } - Element::UnbindFromTree(aNullParent); + Element::UnbindFromTree(aContext); } NS_IMPL_ELEMENT_CLONE(nsXMLElement) diff --git a/dom/xml/nsXMLElement.h b/dom/xml/nsXMLElement.h index e401965fff..01b526f451 100644 --- a/dom/xml/nsXMLElement.h +++ b/dom/xml/nsXMLElement.h @@ -23,7 +23,7 @@ class nsXMLElement : public mozilla::dom::Element { virtual nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const override; - virtual void UnbindFromTree(bool aNullParent = true) override; + virtual void UnbindFromTree(UnbindContext&) override; protected: virtual ~nsXMLElement() = default; diff --git a/dom/xslt/tests/mochitest/mochitest.toml b/dom/xslt/tests/mochitest/mochitest.toml index 70d313758c..e22783231a 100644 --- a/dom/xslt/tests/mochitest/mochitest.toml +++ b/dom/xslt/tests/mochitest/mochitest.toml @@ -54,4 +54,6 @@ support-files = ["file_metaRefresh.xml"] ["test_parameter.html"] +["test_parameter_conversion.html"] + ["test_sorting_invalid_lang.html"] diff --git a/dom/xslt/tests/mochitest/test_parameter_conversion.html b/dom/xslt/tests/mochitest/test_parameter_conversion.html new file mode 100644 index 0000000000..a0ea6acce2 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_parameter_conversion.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for setParameter conversion to XSLT type</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script> + let parser = new DOMParser(); + let xml = parser.parseFromString('<?xml version="1.0" encoding="utf-8" ?><root/>', "text/xml"); + let xslt = parser.parseFromString(`<?xml version="1.0" encoding="utf-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:param name="test" /> +<xsl:template match="/"> + <xsl:value-of select="$test" /> +</xsl:template> +</xsl:stylesheet>`, "text/xml"); + + let processor = new XSLTProcessor(); + processor.importStylesheet(xslt); + + let callbackCalled = false; + let param = { + [Symbol.toPrimitive](hint) { + callbackCalled = true; + processor.removeParameter(null, 'test'); + if (hint == 'string') { + return "Value"; + } + throw new Error("Not converting to string?"); + } + }; + + processor.setParameter(null, 'test', param); + ok(callbackCalled, "Parameter was converted during call to setParameter."); + is(processor.getParameter(null, 'test'), "Value", "processor.removeParameter during string conversion should have no effect."); + + callbackCalled = false; + processor.transformToDocument(xml); + ok(!callbackCalled, "Parameter was not converted during call to transformToDocument."); + is(processor.getParameter(null, 'test'), "Value", "processor.removeParameter during string conversion should have no effect."); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/xpath/XPathEvaluator.cpp b/dom/xslt/xpath/XPathEvaluator.cpp index 269ea98ff6..14889462fb 100644 --- a/dom/xslt/xpath/XPathEvaluator.cpp +++ b/dom/xslt/xpath/XPathEvaluator.cpp @@ -16,7 +16,6 @@ #include "mozilla/dom/XPathNSResolverBinding.h" #include "nsAtom.h" #include "nsCOMPtr.h" -#include "nsContentCID.h" #include "nsContentUtils.h" #include "nsDOMString.h" #include "nsError.h" diff --git a/dom/xslt/xslt/txEXSLTFunctions.cpp b/dom/xslt/xslt/txEXSLTFunctions.cpp index d4882d0b13..cb4f204019 100644 --- a/dom/xslt/xslt/txEXSLTFunctions.cpp +++ b/dom/xslt/xslt/txEXSLTFunctions.cpp @@ -23,7 +23,6 @@ #include "nsImportModule.h" #include "nsPrintfCString.h" #include "nsComponentManagerUtils.h" -#include "nsContentCID.h" #include "nsContentCreatorFunctions.h" #include "nsIContent.h" #include "txMozillaXMLOutput.h" @@ -189,8 +188,8 @@ struct txEXSLTFunctionDescriptor { int32_t mNamespaceID; }; -static EnumeratedArray<txEXSLTType, txEXSLTType::_LIMIT, - txEXSLTFunctionDescriptor> +static EnumeratedArray<txEXSLTType, txEXSLTFunctionDescriptor, + size_t(txEXSLTType::_LIMIT)> descriptTable; class txEXSLTFunctionCall : public FunctionCall { diff --git a/dom/xslt/xslt/txMozillaTextOutput.cpp b/dom/xslt/xslt/txMozillaTextOutput.cpp index 2104a0fd6b..01974bb17e 100644 --- a/dom/xslt/xslt/txMozillaTextOutput.cpp +++ b/dom/xslt/xslt/txMozillaTextOutput.cpp @@ -4,7 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "txMozillaTextOutput.h" -#include "nsContentCID.h" #include "nsIContent.h" #include "mozilla/dom/Document.h" #include "nsIDocumentTransformer.h" diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp index 4aa51d9928..3ef89d31a4 100644 --- a/dom/xslt/xslt/txMozillaXMLOutput.cpp +++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp @@ -12,7 +12,6 @@ #include "nsIRefreshURI.h" #include "nsPIDOMWindow.h" #include "nsIContent.h" -#include "nsContentCID.h" #include "nsUnicharUtils.h" #include "nsGkAtoms.h" #include "txLog.h" diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp index db4de439ef..c26aea1bb9 100644 --- a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -4,7 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "txMozillaXSLTProcessor.h" -#include "nsContentCID.h" #include "nsError.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Document.h" diff --git a/dom/xul/XULButtonElement.cpp b/dom/xul/XULButtonElement.cpp index fa9ee28628..6c76488db4 100644 --- a/dom/xul/XULButtonElement.cpp +++ b/dom/xul/XULButtonElement.cpp @@ -317,9 +317,9 @@ void XULButtonElement::StartBlinking() { "XULButtonElement::StartBlinking", GetMainThreadSerialEventTarget()); } -void XULButtonElement::UnbindFromTree(bool aNullParent) { +void XULButtonElement::UnbindFromTree(UnbindContext& aContext) { StopBlinking(); - nsXULElement::UnbindFromTree(aNullParent); + nsXULElement::UnbindFromTree(aContext); } void XULButtonElement::ExecuteMenu(WidgetEvent& aEvent) { diff --git a/dom/xul/XULButtonElement.h b/dom/xul/XULButtonElement.h index fc880210a2..b79b8c2186 100644 --- a/dom/xul/XULButtonElement.h +++ b/dom/xul/XULButtonElement.h @@ -55,7 +55,7 @@ class XULButtonElement : public nsXULElement { XULMenuParentElement* GetMenuParent() const; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; MOZ_CAN_RUN_SCRIPT bool HandleKeyPress(KeyboardEvent& keyEvent); bool OpenedWithKey() const; diff --git a/dom/xul/XULFrameElement.cpp b/dom/xul/XULFrameElement.cpp index efbeb15829..87f1ecf71f 100644 --- a/dom/xul/XULFrameElement.cpp +++ b/dom/xul/XULFrameElement.cpp @@ -159,13 +159,13 @@ nsresult XULFrameElement::BindToTree(BindContext& aContext, nsINode& aParent) { return NS_OK; } -void XULFrameElement::UnbindFromTree(bool aNullParent) { +void XULFrameElement::UnbindFromTree(UnbindContext& aContext) { if (RefPtr<nsFrameLoader> frameLoader = GetFrameLoader()) { frameLoader->Destroy(); } mFrameLoader = nullptr; - nsXULElement::UnbindFromTree(aNullParent); + nsXULElement::UnbindFromTree(aContext); } void XULFrameElement::DestroyContent() { diff --git a/dom/xul/XULFrameElement.h b/dom/xul/XULFrameElement.h index 13a65f42e8..20b467f66a 100644 --- a/dom/xul/XULFrameElement.h +++ b/dom/xul/XULFrameElement.h @@ -54,7 +54,7 @@ class XULFrameElement final : public nsXULElement, public nsFrameLoaderOwner { // nsIContent nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; void DestroyContent() override; void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, diff --git a/dom/xul/XULMenuBarElement.cpp b/dom/xul/XULMenuBarElement.cpp index f32698b3cd..1ec36d3859 100644 --- a/dom/xul/XULMenuBarElement.cpp +++ b/dom/xul/XULMenuBarElement.cpp @@ -85,7 +85,7 @@ nsresult XULMenuBarElement::BindToTree(BindContext& aContext, return NS_OK; } -void XULMenuBarElement::UnbindFromTree(bool aNullParent) { +void XULMenuBarElement::UnbindFromTree(UnbindContext& aContext) { if (mListener) { mListener->Detach(); mListener = nullptr; @@ -97,7 +97,7 @@ void XULMenuBarElement::UnbindFromTree(bool aNullParent) { pm->SetActiveMenuBar(this, false); } } - return XULMenuParentElement::UnbindFromTree(aNullParent); + return XULMenuParentElement::UnbindFromTree(aContext); } } // namespace mozilla::dom diff --git a/dom/xul/XULMenuBarElement.h b/dom/xul/XULMenuBarElement.h index 117682642d..1d91fee660 100644 --- a/dom/xul/XULMenuBarElement.h +++ b/dom/xul/XULMenuBarElement.h @@ -38,7 +38,7 @@ class XULMenuBarElement final : public XULMenuParentElement { bool IsActiveByKeyboard() const { return mActiveByKeyboard; } nsresult BindToTree(BindContext&, nsINode& aParent) override; - void UnbindFromTree(bool aNullParent) override; + void UnbindFromTree(UnbindContext&) override; protected: ~XULMenuBarElement() override; diff --git a/dom/xul/XULTreeElement.cpp b/dom/xul/XULTreeElement.cpp index e190b22556..cd68d7e664 100644 --- a/dom/xul/XULTreeElement.cpp +++ b/dom/xul/XULTreeElement.cpp @@ -27,7 +27,7 @@ JSObject* XULTreeElement::WrapNode(JSContext* aCx, return XULTreeElement_Binding::Wrap(aCx, this, aGivenProto); } -void XULTreeElement::UnbindFromTree(bool aNullParent) { +void XULTreeElement::UnbindFromTree(UnbindContext& aContext) { // Drop the view's ref to us. if (mView) { nsCOMPtr<nsITreeSelection> sel; @@ -39,7 +39,7 @@ void XULTreeElement::UnbindFromTree(bool aNullParent) { } mView = nullptr; - nsXULElement::UnbindFromTree(aNullParent); + nsXULElement::UnbindFromTree(aContext); } void XULTreeElement::DestroyContent() { diff --git a/dom/xul/XULTreeElement.h b/dom/xul/XULTreeElement.h index 381ae88f41..8d6cc383db 100644 --- a/dom/xul/XULTreeElement.h +++ b/dom/xul/XULTreeElement.h @@ -101,7 +101,7 @@ class XULTreeElement final : public nsXULElement { void EndUpdateBatch(void); void ClearStyleAndImageCaches(void); - virtual void UnbindFromTree(bool aNullParent) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void DestroyContent() override; void BodyDestroyed(int32_t aFirstVisibleRow) { diff --git a/dom/xul/nsXULContentSink.cpp b/dom/xul/nsXULContentSink.cpp index 197c5c2b3b..9a4f2deb23 100644 --- a/dom/xul/nsXULContentSink.cpp +++ b/dom/xul/nsXULContentSink.cpp @@ -27,7 +27,6 @@ #include "nsParserBase.h" #include "nsViewManager.h" #include "nsIScriptSecurityManager.h" -#include "nsLayoutCID.h" #include "nsNetUtil.h" #include "nsString.h" #include "nsReadableUtils.h" diff --git a/dom/xul/nsXULContentUtils.cpp b/dom/xul/nsXULContentUtils.cpp index 109aa1975a..07de8f6cdb 100644 --- a/dom/xul/nsXULContentUtils.cpp +++ b/dom/xul/nsXULContentUtils.cpp @@ -20,7 +20,6 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "nsXULContentUtils.h" -#include "nsLayoutCID.h" #include "nsString.h" #include "nsGkAtoms.h" diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp index 78c29fb315..5bccc15f69 100644 --- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -654,7 +654,7 @@ nsresult nsXULElement::BindToTree(BindContext& aContext, nsINode& aParent) { return rv; } -void nsXULElement::UnbindFromTree(bool aNullParent) { +void nsXULElement::UnbindFromTree(UnbindContext& aContext) { if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { XULKeySetGlobalKeyListener::DetachKeyHandler(this); } @@ -689,7 +689,7 @@ void nsXULElement::UnbindFromTree(bool aNullParent) { slots->mControllers = nullptr; } - nsStyledElement::UnbindFromTree(aNullParent); + nsStyledElement::UnbindFromTree(aContext); } void nsXULElement::DoneAddingChildren(bool aHaveNotified) { @@ -1426,6 +1426,10 @@ nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos, mAttributes[aPos].mValue.ParseAtom(aValue); return NS_OK; + } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::aria_activedescendant)) { + mAttributes[aPos].mValue.ParseAtom(aValue); + + return NS_OK; } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) { // Store is as atom. mAttributes[aPos].mValue.ParseAtom(aValue); @@ -1839,9 +1843,8 @@ class ScriptCompileTask final : public Task { return; } - JS::CompilationStorage compileStorage; - mStencil = JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, - srcBuf, compileStorage); + mStencil = + JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, srcBuf); #ifdef DEBUG // Chrome-privileged code shouldn't have any compilation error. CheckErrorsAndWarnings(mFrontendContext, mOptions); diff --git a/dom/xul/nsXULElement.h b/dom/xul/nsXULElement.h index 4002ec63dc..2681a14a9a 100644 --- a/dom/xul/nsXULElement.h +++ b/dom/xul/nsXULElement.h @@ -374,7 +374,7 @@ class nsXULElement : public nsStyledElement { mozilla::EventChainVisitor& aVisitor) override; // nsIContent virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; - virtual void UnbindFromTree(bool aNullParent) override; + virtual void UnbindFromTree(UnbindContext&) override; virtual void DestroyContent() override; virtual void DoneAddingChildren(bool aHaveNotified) override; diff --git a/dom/xul/nsXULPopupListener.cpp b/dom/xul/nsXULPopupListener.cpp index 9f2ee88e8d..35445d6d3e 100644 --- a/dom/xul/nsXULPopupListener.cpp +++ b/dom/xul/nsXULPopupListener.cpp @@ -12,7 +12,6 @@ #include "XULButtonElement.h" #include "nsCOMPtr.h" #include "nsGkAtoms.h" -#include "nsContentCID.h" #include "nsContentUtils.h" #include "nsXULPopupManager.h" #include "nsIScriptContext.h" diff --git a/dom/xul/test/test_bug468176.xhtml b/dom/xul/test/test_bug468176.xhtml index f21c1bcde3..3f4c690b6f 100644 --- a/dom/xul/test/test_bug468176.xhtml +++ b/dom/xul/test/test_bug468176.xhtml @@ -27,7 +27,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=468176 SimpleTest.waitForExplicitFinish(); var broadcastCount = 0; - function b_listener(evt) { + function b_listener() { ++broadcastCount; } |