From fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:14:29 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- dom/base/BodyUtil.cpp | 4 +- dom/base/CharacterData.cpp | 27 +- dom/base/CharacterData.h | 2 +- dom/base/ChromeUtils.cpp | 108 +-- dom/base/ChromeUtils.h | 3 +- dom/base/ContentProcessMessageManager.cpp | 3 +- dom/base/DOMParser.cpp | 10 +- dom/base/DOMRequest.cpp | 256 ------- dom/base/DOMRequest.h | 103 --- dom/base/DOMRequestHelper.sys.mjs | 335 --------- dom/base/DirectionalityUtils.cpp | 745 +++++---------------- dom/base/DirectionalityUtils.h | 5 +- dom/base/Document.cpp | 149 ++++- dom/base/Document.h | 38 +- dom/base/DocumentFragment.h | 2 +- dom/base/Element.cpp | 135 ++-- dom/base/Element.h | 44 +- dom/base/EventSource.cpp | 3 +- dom/base/FlushType.h | 2 +- dom/base/FragmentOrElement.cpp | 48 +- dom/base/IDTracker.cpp | 25 +- dom/base/InProcessBrowserChildMessageManager.cpp | 24 +- dom/base/InProcessBrowserChildMessageManager.h | 3 - dom/base/IndexedDBHelper.sys.mjs | 6 +- dom/base/MimeType.cpp | 14 +- dom/base/MimeType.h | 9 +- dom/base/Navigator.cpp | 3 + dom/base/Navigator.h | 1 - dom/base/RadioGroupContainer.cpp | 11 + dom/base/RadioGroupContainer.h | 1 + dom/base/ResizeObserver.cpp | 121 +--- dom/base/ResizeObserver.h | 31 +- dom/base/Selection.cpp | 59 +- dom/base/ShadowRoot.cpp | 9 +- dom/base/StructuredCloneHolder.cpp | 121 ++++ dom/base/StructuredCloneHolder.h | 14 + dom/base/StructuredCloneTags.h | 4 + dom/base/UnbindContext.h | 35 + dom/base/UseCounters.conf | 1 - dom/base/crashtests/crashtests.list | 4 +- dom/base/moz.build | 6 +- dom/base/nsAttrValue.cpp | 4 +- dom/base/nsContentCID.h | 195 ------ dom/base/nsContentUtils.cpp | 41 +- dom/base/nsContentUtils.h | 129 +++- dom/base/nsCopySupport.cpp | 42 +- dom/base/nsDOMNavigationTiming.cpp | 3 +- dom/base/nsDOMWindowUtils.cpp | 1 + dom/base/nsFocusManager.cpp | 107 ++- dom/base/nsFocusManager.h | 30 +- dom/base/nsFrameLoader.cpp | 146 +--- dom/base/nsFrameLoader.h | 9 - dom/base/nsGlobalWindowInner.cpp | 33 +- dom/base/nsGlobalWindowInner.h | 13 + dom/base/nsGlobalWindowOuter.cpp | 1 - dom/base/nsIContent.h | 11 +- dom/base/nsIDOMRequestService.idl | 21 - dom/base/nsINode.cpp | 27 +- dom/base/nsINode.h | 42 +- dom/base/nsIScriptableContentIterator.idl | 8 - dom/base/nsImageLoadingContent.cpp | 14 +- dom/base/nsImageLoadingContent.h | 5 +- dom/base/nsJSEnvironment.cpp | 15 + dom/base/nsObjectLoadingContent.cpp | 8 +- dom/base/nsObjectLoadingContent.h | 2 +- dom/base/nsTextNode.cpp | 19 +- dom/base/nsTextNode.h | 2 +- dom/base/nsWindowRoot.cpp | 2 - dom/base/test/browser.toml | 10 + dom/base/test/browser_object_attachment.js | 168 +++++ dom/base/test/chrome.toml | 3 - dom/base/test/chrome/bug418986-1.js | 3 + dom/base/test/chrome/chrome.toml | 4 - dom/base/test/chrome/file_bug549682.xhtml | 10 +- dom/base/test/chrome/file_bug616841.xhtml | 2 +- dom/base/test/chrome/test_bug1339722.html | 4 +- dom/base/test/chrome/test_bug339494.xhtml | 2 +- dom/base/test/chrome/test_bug429785.xhtml | 2 +- dom/base/test/chrome/test_bug430050.xhtml | 2 +- .../test/chrome/test_chromeOuterWindowID.xhtml | 2 +- dom/base/test/chrome/test_swapFrameLoaders.xhtml | 25 - dom/base/test/chrome/title_window.xhtml | 4 +- .../test/chrome/window_nsITextInputProcessor.xhtml | 4 +- dom/base/test/chrome/window_swapFrameLoaders.xhtml | 223 ------ dom/base/test/common_postMessages.js | 2 + dom/base/test/file_bug1008126_worker.js | 2 + dom/base/test/file_bug945152_worker.js | 1 + dom/base/test/file_focus_shadow_dom.html | 24 +- dom/base/test/file_img_attachment.jpg | Bin 0 -> 2711 bytes dom/base/test/file_img_attachment.jpg^headers^ | 1 + dom/base/test/file_img_object_attachment.html | 6 + dom/base/test/file_pdf_attachment.pdf | Bin 0 -> 1568 bytes dom/base/test/file_pdf_attachment.pdf^headers^ | 2 + dom/base/test/file_pdf_object_attachment.html | 6 + .../test/fullscreen/MozDomFullscreen_chrome.xhtml | 2 +- .../browser_fullscreen-navigation-history-race.js | 2 +- .../browser_fullscreen-tab-close-race.js | 2 +- dom/base/test/fullscreen/file_fullscreen-api.html | 22 +- .../fullscreen/file_fullscreen-bug-1798219-2.html | 2 +- .../fullscreen/file_fullscreen-bug-1798219.html | 2 +- .../test/fullscreen/file_fullscreen-denied.html | 2 +- .../fullscreen/file_fullscreen-esc-exit-inner.html | 2 +- .../test/fullscreen/file_fullscreen-esc-exit.html | 4 +- .../test/fullscreen/file_fullscreen-shadowdom.html | 2 +- .../fullscreen/file_fullscreen-svg-element.html | 2 +- dom/base/test/fullscreen/fullscreen.xhtml | 2 +- dom/base/test/fullscreen/fullscreen_helpers.js | 4 +- dom/base/test/fullscreen/test_fullscreen-api.html | 2 +- .../test/fullscreen/test_fullscreen_modal.html | 11 +- dom/base/test/gtest/TestMimeType.cpp | 142 ++-- dom/base/test/meta_viewport/viewport_helpers.js | 1 + dom/base/test/mochitest.toml | 5 +- dom/base/test/test_domrequest.html | 233 ------- dom/base/test/test_domrequesthelper.xhtml | 549 --------------- dom/base/test/test_focus_radio.html | 90 +++ dom/base/test/unit/test_error_codes.js | 2 +- .../test/useractivation/file_clipboard_common.js | 8 +- .../test_popup_blocker_async_callback.html | 2 +- .../test_useractivation_scrollbar.html | 2 +- dom/base/use_counter_metrics.yaml | 34 + 120 files changed, 1722 insertions(+), 3375 deletions(-) delete mode 100644 dom/base/DOMRequest.cpp delete mode 100644 dom/base/DOMRequest.h delete mode 100644 dom/base/DOMRequestHelper.sys.mjs create mode 100644 dom/base/UnbindContext.h delete mode 100644 dom/base/nsContentCID.h delete mode 100644 dom/base/nsIDOMRequestService.idl create mode 100644 dom/base/test/browser_object_attachment.js delete mode 100644 dom/base/test/chrome/test_swapFrameLoaders.xhtml delete mode 100644 dom/base/test/chrome/window_swapFrameLoaders.xhtml create mode 100644 dom/base/test/file_img_attachment.jpg create mode 100644 dom/base/test/file_img_attachment.jpg^headers^ create mode 100644 dom/base/test/file_img_object_attachment.html create mode 100644 dom/base/test/file_pdf_attachment.pdf create mode 100644 dom/base/test/file_pdf_attachment.pdf^headers^ create mode 100644 dom/base/test/file_pdf_object_attachment.html delete mode 100644 dom/base/test/test_domrequest.html delete mode 100644 dom/base/test/test_domrequesthelper.xhtml create mode 100644 dom/base/test/test_focus_radio.html (limited to 'dom/base') 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 parsed = CMimeType::Parse(mMixedCaseMimeType); + RefPtr parsed = CMimeType::Parse(mMixedCaseMimeType); if (!parsed) { return false; } @@ -422,7 +422,7 @@ already_AddRefed BodyUtil::ConsumeFormData( if (isValidUrlEncodedMimeType) { RefPtr fd = new FormData(aParent); DebugOnly 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& 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& @@ -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& 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 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 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 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 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(ProcType::Max) + 1, + static_cast(MaxContiguousEnumValue::value) == + static_cast(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& aNames) { aNames.Clear(); - for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) { - auto idlName = static_cast(i); - aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName)); + for (UtilityActorName idlName : + MakeWebIDLEnumeratedRange()) { + 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 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 DOMParser::ParseFromStream(nsIInputStream* aStream, // Create a fake channel nsCOMPtr 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 aGivenProto) { - return DOMRequest_Binding::Wrap(aCx, this, aGivenProto); -} - -void DOMRequest::FireSuccess(JS::Handle 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 = 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 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 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 req = new DOMRequest(win); - req.forget(aRequest); - - return NS_OK; -} - -NS_IMETHODIMP -DOMRequestService::FireSuccess(DOMRequest* aRequest, - JS::Handle 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 asyncTask = - new FireSuccessAsyncTask(aRequest, aResult); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask)); - return NS_OK; - } - - NS_IMETHOD - Run() override { - mReq->FireSuccess( - JS::Handle::fromMarkedLocation(mResult.address())); - return NS_OK; - } - - private: - RefPtr mReq; - JS::PersistentRooted 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 mReq; - nsString mError; -}; - -NS_IMETHODIMP -DOMRequestService::FireSuccessAsync(DOMRequest* aRequest, - JS::Handle aResult) { - NS_ENSURE_STATE(aRequest); - return FireSuccessAsyncTask::Dispatch(aRequest, aResult); -} - -NS_IMETHODIMP -DOMRequestService::FireErrorAsync(DOMRequest* aRequest, - const nsAString& aError) { - NS_ENSURE_STATE(aRequest); - nsCOMPtr 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 mResult; - RefPtr mError; - RefPtr 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 aGivenProto) override; - - // WebIDL Interface - DOMRequestReadyState ReadyState() const { - return mDone ? DOMRequestReadyState::Done : DOMRequestReadyState::Pending; - } - - void GetResult(JSContext*, JS::MutableHandle 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 aRetval, - mozilla::ErrorResult& aRv); - - void FireSuccess(JS::Handle 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 FactoryCreate() { - return MakeAndAddRef(); - } -}; - -} // 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 , 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. ) + * 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. 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(assignedNode); - if (assignedNode != aSkip) { - Directionality textNodeDir = GetDirectionFromText(text); - if (textNodeDir != Directionality::Unset) { - *aDirectionality = textNodeDir; - return text; - } + auto* text = static_cast(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(child); + if (child->NodeType() == nsINode::TEXT_NODE) { + auto* text = static_cast(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(aObject); - textNode->ClearHasTextNodeDirectionalityMap(); - - nsTextNodeDirectionalityMap* map = - reinterpret_cast(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(aPropertyValue); - nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode); - if (map) { - map->RemoveEntryForProperty(static_cast(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> 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( - aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap)); - } - - return map; - } - - static nsCheapSetOperator SetNodeDirection(nsPtrHashKey* aEntry, - void* aDir) { - aEntry->GetKey()->SetDirectionality( - *reinterpret_cast(aDir), true); - return OpNext; - } - - struct nsTextNodeDirectionalityMapAndElement { - nsTextNodeDirectionalityMap* mMap; - nsCOMPtr mNode; - }; - - static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey* aEntry, - void* aData) { - // run the downward propagation algorithm - // and remove the text node from the map - nsTextNodeDirectionalityMapAndElement* data = - static_cast(aData); - nsINode* oldTextNode = data->mNode; - Element* rootNode = aEntry->GetKey(); - nsTextNode* newTextNode = nullptr; - if (rootNode->GetParentNode() && rootNode->HasDirAuto()) { - newTextNode = - WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode); - } - - AutoRestore restore(data->mMap->mElementToBeRemoved); - data->mMap->mElementToBeRemoved = rootNode; - if (newTextNode) { - nsINode* oldDirAutoSetBy = static_cast( - 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* aEntry, - void* aData) { - AutoTArray* entries = - static_cast*>(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 restore(mElementToBeRemoved); - AutoTArray 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 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( - 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( - 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(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 which is having its dir attribute set to auto (or + // Only test for ParticipatesInAutoDirection -- in other words, if aElement is + // a which is having its dir attribute set to auto (or // removed or set to an invalid value, which are equivalent to dir=auto for // , we *do* want to set AncestorHasDirAuto on its descendants, unlike // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a // 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( - 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( - 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( - 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 = GetHTMLEditor()) { nsCOMPtr spellChecker; - rv = htmlEditor->GetInlineSpellChecker(false, - getter_AddRefs(spellChecker)); - NS_ENSURE_SUCCESS_VOID(rv); + DebugOnly 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(&hash_table_ops, sizeof(SubDocMapEntry)); } // Add a mapping to the hash table @@ -12394,7 +12395,8 @@ already_AddRefed 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(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 aPredicate) { nsCOMPtr 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 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 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&& 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 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 mSubDocuments; class HeaderData; UniquePtr mHeaderData; @@ -5162,9 +5182,9 @@ class Document : public nsINode, RefPtr 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 mLastRememberedSizeObserver; + nsTHashSet> mElementsObservedForLastRememberedSize; // Stack of top layer elements. nsTArray 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 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 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 Element::GetBoundingClientRect() { @@ -1722,8 +1713,7 @@ already_AddRefed 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 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 attrEl = do_QueryReferent(weakAttrEl)) { + return attrEl; + } + } + return nullptr; } void Element::GetElementsWithGrid(nsTArray>& 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 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 @@ -458,23 +466,14 @@ 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 +const EnumeratedArray 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(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 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 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