summaryrefslogtreecommitdiffstats
path: root/dom/base/nsContentUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsContentUtils.cpp')
-rw-r--r--dom/base/nsContentUtils.cpp11471
1 files changed, 11471 insertions, 0 deletions
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
new file mode 100644
index 0000000000..d849b13927
--- /dev/null
+++ b/dom/base/nsContentUtils.cpp
@@ -0,0 +1,11471 @@
+/* -*- 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/. */
+
+/* A namespace class for static layout utilities. */
+
+#include "nsContentUtils.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <new>
+#include <utility>
+#include "BrowserChild.h"
+#include "DecoderTraits.h"
+#include "ErrorList.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "ImageOps.h"
+#include "InProcessBrowserChildMessageManager.h"
+#include "MainThreadUtils.h"
+#include "PLDHashTable.h"
+#include "ReferrerInfo.h"
+#include "ScopedNSSTypes.h"
+#include "ThirdPartyUtil.h"
+#include "Units.h"
+#include "chrome/common/ipc_message.h"
+#include "gfxDrawable.h"
+#include "harfbuzz/hb.h"
+#include "imgICache.h"
+#include "imgIContainer.h"
+#include "imgILoader.h"
+#include "imgIRequest.h"
+#include "imgLoader.h"
+#include "js/Array.h"
+#include "js/ArrayBuffer.h"
+#include "js/BuildId.h"
+#include "js/GCAPI.h"
+#include "js/Id.h"
+#include "js/JSON.h"
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetProperty
+#include "js/PropertyDescriptor.h"
+#include "js/Realm.h"
+#include "js/RegExp.h"
+#include "js/RegExpFlags.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozAutoDocUpdate.h"
+#include "mozIDOMWindow.h"
+#include "nsIOService.h"
+#include "nsObjectLoadingContent.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/AtomArray.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/BloomFilter.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/CallState.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/HangAnnotations.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/InputEventOptions.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Latin1.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/ManualNAC.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerRunnable.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/ShutdownPhase.h"
+#include "mozilla/Span.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+#ifdef FUZZING
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+#include "mozilla/StaticPrefs_nglayout.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_test.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextControlState.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Variant.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/AutocompleteInfoBinding.h"
+#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/BrowserBridgeParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CallbackFunction.h"
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/CustomElementTypes.h"
+#include "mozilla/dom/DOMArena.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/DOMSecurityMonitor.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/FileBlobImpl.h"
+#include "mozilla/dom/FileSystemSecurity.h"
+#include "mozilla/dom/FilteredNodeIterator.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/HTMLElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/IPCBlob.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+#include "mozilla/dom/MessageListenerManager.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeBinding.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/PBrowser.h"
+#include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/XULCommandEvent.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BaseMargin.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsAlgorithm.h"
+#include "nsArrayUtils.h"
+#include "nsAtomHashKeys.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsBaseHashtable.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsCanvasFrame.h"
+#include "nsCaseTreatment.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsCharTraits.h"
+#include "nsCompatibility.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContainerFrame.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentDLF.h"
+#include "nsContentList.h"
+#include "nsContentListDeclarations.h"
+#include "nsContentPolicyUtils.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMString.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsDocShell.h"
+#include "nsDocShellCID.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "nsFrameList.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHTMLDocument.h"
+#include "nsHTMLTags.h"
+#include "nsHashKeys.h"
+#include "nsHtml5StringParser.h"
+#include "nsIAboutModule.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIAppShell.h"
+#include "nsIArray.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIBidiKeyboard.h"
+#include "nsIBrowser.h"
+#include "nsICacheInfoChannel.h"
+#include "nsICategoryManager.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentSink.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIDocumentViewer.h"
+#include "nsIDragService.h"
+#include "nsIDragSession.h"
+#include "nsIFile.h"
+#include "nsIFocusManager.h"
+#include "nsIFormControl.h"
+#include "nsIFragmentContentSink.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIMIMEService.h"
+#include "nsIMemoryReporter.h"
+#include "nsINetUtil.h"
+#include "nsINode.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIParserUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsIProperties.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRequest.h"
+#include "nsIRunnable.h"
+#include "nsIScreen.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISerialEventTarget.h"
+#include "nsIStreamConverter.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStringBundle.h"
+#include "nsISupports.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISupportsUtils.h"
+#include "nsITransferable.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsIURIWithSpecialOrigin.h"
+#endif
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebNavigationInfo.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsIXPConnect.h"
+#include "nsJSPrincipals.h"
+#include "nsJSUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsLiteralString.h"
+#include "nsMargin.h"
+#include "nsMimeTypes.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsNodeInfoManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsParser.h"
+#include "nsParserConstants.h"
+#include "nsPoint.h"
+#include "nsPointerHashKeys.h"
+#include "nsPresContext.h"
+#include "nsQueryFrame.h"
+#include "nsQueryObject.h"
+#include "nsRange.h"
+#include "nsRefPtrHashtable.h"
+#include "nsSandboxFlags.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsStringBundle.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsStringIterator.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTPromiseFlatString.h"
+#include "nsTStringRepr.h"
+#include "nsTextFragment.h"
+#include "nsTextNode.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "nsTreeSanitizer.h"
+#include "nsUGenCategory.h"
+#include "nsURLHelper.h"
+#include "nsUnicodeProperties.h"
+#include "nsVariant.h"
+#include "nsWidgetsCID.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsXULAppAPI.h"
+#include "nsXULElement.h"
+#include "nsXULPopupManager.h"
+#include "nscore.h"
+#include "prinrval.h"
+#include "xpcprivate.h"
+#include "xpcpublic.h"
+
+#if defined(XP_WIN)
+// Undefine LoadImage to prevent naming conflict with Windows.
+# undef LoadImage
+#endif
+
+extern "C" int MOZ_XMLTranslateEntity(const char* ptr, const char* end,
+ const char** next, char16_t* result);
+extern "C" int MOZ_XMLCheckQName(const char* ptr, const char* end, int ns_aware,
+ const char** colon);
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+const char kLoadAsData[] = "loadAsData";
+
+nsIXPConnect* nsContentUtils::sXPConnect;
+nsIScriptSecurityManager* nsContentUtils::sSecurityManager;
+nsIPrincipal* nsContentUtils::sSystemPrincipal;
+nsIPrincipal* nsContentUtils::sNullSubjectPrincipal;
+nsIConsoleService* nsContentUtils::sConsoleService;
+
+static nsTHashMap<RefPtr<nsAtom>, EventNameMapping>* sAtomEventTable;
+static nsTHashMap<nsStringHashKey, EventNameMapping>* sStringEventTable;
+static nsTArray<RefPtr<nsAtom>>* sUserDefinedEvents;
+nsIStringBundleService* nsContentUtils::sStringBundleService;
+
+static StaticRefPtr<nsIStringBundle>
+ sStringBundles[nsContentUtils::PropertiesFile_COUNT];
+
+nsIContentPolicy* nsContentUtils::sContentPolicyService;
+bool nsContentUtils::sTriedToGetContentPolicy = false;
+StaticRefPtr<nsIBidiKeyboard> nsContentUtils::sBidiKeyboard;
+uint32_t nsContentUtils::sScriptBlockerCount = 0;
+uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0;
+AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners =
+ nullptr;
+uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0;
+nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr;
+
+bool nsContentUtils::sIsHandlingKeyBoardEvent = false;
+
+nsString* nsContentUtils::sShiftText = nullptr;
+nsString* nsContentUtils::sControlText = nullptr;
+nsString* nsContentUtils::sCommandOrWinText = nullptr;
+nsString* nsContentUtils::sAltText = nullptr;
+nsString* nsContentUtils::sModifierSeparator = nullptr;
+
+bool nsContentUtils::sInitialized = false;
+#ifndef RELEASE_OR_BETA
+bool nsContentUtils::sBypassCSSOMOriginCheck = false;
+#endif
+
+nsCString* nsContentUtils::sJSScriptBytecodeMimeType = nullptr;
+nsCString* nsContentUtils::sJSModuleBytecodeMimeType = nullptr;
+
+nsContentUtils::UserInteractionObserver*
+ nsContentUtils::sUserInteractionObserver = nullptr;
+
+nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
+nsParser* nsContentUtils::sXMLFragmentParser = nullptr;
+nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
+bool nsContentUtils::sFragmentParsingActive = false;
+
+bool nsContentUtils::sMayHaveFormCheckboxStateChangeListeners = false;
+bool nsContentUtils::sMayHaveFormRadioStateChangeListeners = false;
+
+mozilla::LazyLogModule nsContentUtils::gResistFingerprintingLog(
+ "nsResistFingerprinting");
+mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
+
+int32_t nsContentUtils::sInnerOrOuterWindowCount = 0;
+uint32_t nsContentUtils::sInnerOrOuterWindowSerialCounter = 0;
+
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RawRangeBoundary& aFirstBoundary,
+ const RangeBoundary& aSecondBoundary);
+template Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RawRangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary);
+
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundary& aFirstBoundary, const RangeBoundary& aSecondBoundary,
+ bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary, bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RawRangeBoundary& aFirstBoundary,
+ const RangeBoundary& aSecondBoundary, bool* aDisconnected);
+template int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RawRangeBoundary& aFirstBoundary,
+ const RawRangeBoundary& aSecondBoundary, bool* aDisconnected);
+
+// Subset of
+// http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
+enum AutocompleteUnsupportedFieldName : uint8_t {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+ eAutocompleteUnsupportedFieldName_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+};
+
+enum AutocompleteNoPersistFieldName : uint8_t {
+#define AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(name_, value_) \
+ eAutocompleteNoPersistFieldName_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+};
+
+enum AutocompleteUnsupportFieldContactHint : uint8_t {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+ eAutocompleteUnsupportedFieldContactHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+};
+
+enum AutocompleteFieldName : uint8_t {
+#define AUTOCOMPLETE_FIELD_NAME(name_, value_) eAutocompleteFieldName_##name_,
+#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
+ AUTOCOMPLETE_FIELD_NAME(name_, value_)
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_NAME
+#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
+};
+
+enum AutocompleteFieldHint : uint8_t {
+#define AUTOCOMPLETE_FIELD_HINT(name_, value_) eAutocompleteFieldHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_HINT
+};
+
+enum AutocompleteFieldContactHint : uint8_t {
+#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_) \
+ eAutocompleteFieldContactHint_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
+};
+
+enum AutocompleteCredentialType : uint8_t {
+#define AUTOCOMPLETE_CREDENTIAL_TYPE(name_, value_) \
+ eAutocompleteCredentialType_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CREDENTIAL_TYPE
+};
+
+enum AutocompleteCategory {
+#define AUTOCOMPLETE_CATEGORY(name_, value_) eAutocompleteCategory_##name_,
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CATEGORY
+};
+
+static const nsAttrValue::EnumTable kAutocompleteUnsupportedFieldNameTable[] = {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteUnsupportedFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteNoPersistFieldNameTable[] = {
+#define AUTOCOMPLETE_NO_PERSIST_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteNoPersistFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_NO_PERSIST_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable
+ kAutocompleteUnsupportedContactFieldHintTable[] = {
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+ {value_, eAutocompleteUnsupportedFieldContactHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteFieldNameTable[] = {
+#define AUTOCOMPLETE_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteContactFieldNameTable[] = {
+#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
+ {value_, eAutocompleteFieldName_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteFieldHintTable[] = {
+#define AUTOCOMPLETE_FIELD_HINT(name_, value_) \
+ {value_, eAutocompleteFieldHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_HINT
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteContactFieldHintTable[] = {
+#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_) \
+ {value_, eAutocompleteFieldContactHint_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
+ {nullptr, 0}};
+
+static const nsAttrValue::EnumTable kAutocompleteCredentialTypeTable[] = {
+#define AUTOCOMPLETE_CREDENTIAL_TYPE(name_, value_) \
+ {value_, eAutocompleteCredentialType_##name_},
+#include "AutocompleteFieldList.h"
+#undef AUTOCOMPLETE_CREDENTIAL_TYPE
+ {nullptr, 0}};
+
+namespace {
+
+static PLDHashTable* sEventListenerManagersHash;
+
+// A global hashtable to for keeping the arena alive for cross docGroup node
+// adoption.
+static nsRefPtrHashtable<nsPtrHashKey<const nsINode>, mozilla::dom::DOMArena>*
+ sDOMArenaHashtable;
+
+class DOMEventListenerManagersHashReporter final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~DOMEventListenerManagersHashReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ // We don't measure the |EventListenerManager| objects pointed to by the
+ // entries because those references are non-owning.
+ int64_t amount =
+ sEventListenerManagersHash
+ ? sEventListenerManagersHash->ShallowSizeOfIncludingThis(
+ MallocSizeOf)
+ : 0;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/dom/event-listener-managers-hash", KIND_HEAP, UNITS_BYTES,
+ amount, "Memory used by the event listener manager's hash table.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(DOMEventListenerManagersHashReporter, nsIMemoryReporter)
+
+class EventListenerManagerMapEntry : public PLDHashEntryHdr {
+ public:
+ explicit EventListenerManagerMapEntry(const void* aKey) : mKey(aKey) {}
+
+ ~EventListenerManagerMapEntry() {
+ NS_ASSERTION(!mListenerManager, "caller must release and disconnect ELM");
+ }
+
+ protected: // declared protected to silence clang warnings
+ const void* mKey; // must be first, to look like PLDHashEntryStub
+
+ public:
+ RefPtr<EventListenerManager> mListenerManager;
+};
+
+static void EventListenerManagerHashInitEntry(PLDHashEntryHdr* entry,
+ const void* key) {
+ // Initialize the entry with placement new
+ new (entry) EventListenerManagerMapEntry(key);
+}
+
+static void EventListenerManagerHashClearEntry(PLDHashTable* table,
+ PLDHashEntryHdr* entry) {
+ EventListenerManagerMapEntry* lm =
+ static_cast<EventListenerManagerMapEntry*>(entry);
+
+ // Let the EventListenerManagerMapEntry clean itself up...
+ lm->~EventListenerManagerMapEntry();
+}
+
+class SameOriginCheckerImpl final : public nsIChannelEventSink,
+ public nsIInterfaceRequestor {
+ ~SameOriginCheckerImpl() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+};
+
+} // namespace
+
+void AutoSuppressEventHandling::SuppressDocument(Document* aDoc) {
+ // Note: Document::SuppressEventHandling will also automatically suppress
+ // event handling for any in-process sub-documents. However, since we need
+ // to deal with cases where remote BrowsingContexts may be interleaved
+ // with in-process ones, we still need to walk the entire tree ourselves.
+ // This may be slightly redundant in some cases, but since event handling
+ // suppressions maintain a count of current blockers, it does not cause
+ // any problems.
+ aDoc->SuppressEventHandling();
+}
+
+void AutoSuppressEventHandling::UnsuppressDocument(Document* aDoc) {
+ aDoc->UnsuppressEventHandlingAndFireEvents(true);
+}
+
+AutoSuppressEventHandling::~AutoSuppressEventHandling() {
+ UnsuppressDocuments();
+}
+
+void AutoSuppressEventHandlingAndSuspend::SuppressDocument(Document* aDoc) {
+ AutoSuppressEventHandling::SuppressDocument(aDoc);
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->Suspend();
+ mWindows.AppendElement(win);
+ }
+}
+
+AutoSuppressEventHandlingAndSuspend::~AutoSuppressEventHandlingAndSuspend() {
+ for (const auto& win : mWindows) {
+ win->Resume();
+ }
+}
+
+/**
+ * This class is used to determine whether or not the user is currently
+ * interacting with the browser. It listens to observer events to toggle the
+ * value of the sUserActive static.
+ *
+ * This class is an internal implementation detail.
+ * nsContentUtils::GetUserIsInteracting() should be used to access current
+ * user interaction status.
+ */
+class nsContentUtils::UserInteractionObserver final
+ : public nsIObserver,
+ public BackgroundHangAnnotator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void Init();
+ void Shutdown();
+ void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override;
+
+ static Atomic<bool> sUserActive;
+
+ private:
+ ~UserInteractionObserver() = default;
+};
+
+static constexpr nsLiteralCString kRfpPrefs[] = {
+ "privacy.resistFingerprinting"_ns,
+ "privacy.resistFingerprinting.pbmode"_ns,
+ "privacy.fingerprintingProtection"_ns,
+ "privacy.fingerprintingProtection.pbmode"_ns,
+ "privacy.fingerprintingProtection.overrides"_ns,
+};
+
+static void RecomputeResistFingerprintingAllDocs(const char*, void*) {
+ AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups;
+ BrowsingContextGroup::GetAllGroups(bcGroups);
+ for (auto& bcGroup : bcGroups) {
+ AutoTArray<DocGroup*, 5> docGroups;
+ bcGroup->GetDocGroups(docGroups);
+ for (auto* docGroup : docGroups) {
+ for (Document* doc : *docGroup) {
+ if (doc->RecomputeResistFingerprinting()) {
+ if (auto* pc = doc->GetPresContext()) {
+ pc->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::PreferenceChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+ }
+ }
+ }
+ }
+}
+
+// static
+nsresult nsContentUtils::Init() {
+ if (sInitialized) {
+ NS_WARNING("Init() called twice");
+
+ return NS_OK;
+ }
+
+ nsHTMLTags::AddRefTable();
+
+ sXPConnect = nsXPConnect::XPConnect();
+ // We hold a strong ref to sXPConnect to ensure that it does not go away until
+ // nsLayoutStatics::Shutdown is happening. Otherwise ~nsXPConnect can be
+ // triggered by xpcModuleDtor late in shutdown and cause crashes due to
+ // various stuff already being torn down by then. Note that this means that
+ // we are effectively making sure that if we leak nsLayoutStatics then we also
+ // leak nsXPConnect.
+ NS_ADDREF(sXPConnect);
+
+ sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
+ if (!sSecurityManager) return NS_ERROR_FAILURE;
+ NS_ADDREF(sSecurityManager);
+
+ sSecurityManager->GetSystemPrincipal(&sSystemPrincipal);
+ MOZ_ASSERT(sSystemPrincipal);
+
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ if (!nullPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nullPrincipal.forget(&sNullSubjectPrincipal);
+
+ if (!InitializeEventTable()) return NS_ERROR_FAILURE;
+
+ if (!sEventListenerManagersHash) {
+ static const PLDHashTableOps hash_table_ops = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, EventListenerManagerHashClearEntry,
+ EventListenerManagerHashInitEntry};
+
+ sEventListenerManagersHash =
+ new PLDHashTable(&hash_table_ops, sizeof(EventListenerManagerMapEntry));
+
+ RegisterStrongMemoryReporter(new DOMEventListenerManagersHashReporter());
+ }
+
+ sBlockedScriptRunners = new AutoTArray<nsCOMPtr<nsIRunnable>, 8>;
+
+#ifndef RELEASE_OR_BETA
+ sBypassCSSOMOriginCheck = getenv("MOZ_BYPASS_CSSOM_ORIGIN_CHECK");
+#endif
+
+ Element::InitCCCallbacks();
+
+ RefPtr<nsRFPService> rfpService = nsRFPService::GetOrCreate();
+ MOZ_ASSERT(rfpService);
+
+ if (XRE_IsParentProcess()) {
+ AsyncPrecreateStringBundles();
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // On Android, at-shutdown ping submission isn't reliable
+ // (( because, on Android, we usually get killed, not shut down )).
+ // To have a chance at submitting the ping, aim for idle after startup.
+ nsresult rv = NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction(
+ "AndroidUseCounterPingSubmitter",
+ []() { glean_pings::UseCounters.Submit("idle_startup"_ns); }),
+ EventQueuePriority::Idle);
+ // This is mostly best-effort, so if it goes awry, just log.
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+ RunOnShutdown(
+ [&] { glean_pings::UseCounters.Submit("app_shutdown_confirmed"_ns); },
+ ShutdownPhase::AppShutdownConfirmed);
+ }
+
+ RefPtr<UserInteractionObserver> uio = new UserInteractionObserver();
+ uio->Init();
+ uio.forget(&sUserInteractionObserver);
+
+ for (const auto& pref : kRfpPrefs) {
+ Preferences::RegisterCallback(RecomputeResistFingerprintingAllDocs, pref);
+ }
+
+ sInitialized = true;
+
+ return NS_OK;
+}
+
+bool nsContentUtils::InitJSBytecodeMimeType() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sJSScriptBytecodeMimeType);
+ MOZ_ASSERT(!sJSModuleBytecodeMimeType);
+
+ JS::BuildIdCharVector jsBuildId;
+ if (!JS::GetScriptTranscodingBuildId(&jsBuildId)) {
+ return false;
+ }
+
+ nsDependentCSubstring jsBuildIdStr(jsBuildId.begin(), jsBuildId.length());
+ sJSScriptBytecodeMimeType =
+ new nsCString("javascript/moz-script-bytecode-"_ns + jsBuildIdStr);
+ sJSModuleBytecodeMimeType =
+ new nsCString("javascript/moz-module-bytecode-"_ns + jsBuildIdStr);
+ return true;
+}
+
+void nsContentUtils::GetShiftText(nsAString& text) {
+ if (!sShiftText) InitializeModifierStrings();
+ text.Assign(*sShiftText);
+}
+
+void nsContentUtils::GetControlText(nsAString& text) {
+ if (!sControlText) InitializeModifierStrings();
+ text.Assign(*sControlText);
+}
+
+void nsContentUtils::GetCommandOrWinText(nsAString& text) {
+ if (!sCommandOrWinText) {
+ InitializeModifierStrings();
+ }
+ text.Assign(*sCommandOrWinText);
+}
+
+void nsContentUtils::GetAltText(nsAString& text) {
+ if (!sAltText) InitializeModifierStrings();
+ text.Assign(*sAltText);
+}
+
+void nsContentUtils::GetModifierSeparatorText(nsAString& text) {
+ if (!sModifierSeparator) InitializeModifierStrings();
+ text.Assign(*sModifierSeparator);
+}
+
+void nsContentUtils::InitializeModifierStrings() {
+ // load the display strings for the keyboard accelerators
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ nsCOMPtr<nsIStringBundle> bundle;
+ DebugOnly<nsresult> rv = NS_OK;
+ if (bundleService) {
+ rv = bundleService->CreateBundle(
+ "chrome://global-platform/locale/platformKeys.properties",
+ getter_AddRefs(bundle));
+ }
+
+ NS_ASSERTION(
+ NS_SUCCEEDED(rv) && bundle,
+ "chrome://global/locale/platformKeys.properties could not be loaded");
+ nsAutoString shiftModifier;
+ nsAutoString commandOrWinModifier;
+ nsAutoString altModifier;
+ nsAutoString controlModifier;
+ nsAutoString modifierSeparator;
+ if (bundle) {
+ // macs use symbols for each modifier key, so fetch each from the bundle,
+ // which also covers i18n
+ bundle->GetStringFromName("VK_SHIFT", shiftModifier);
+ bundle->GetStringFromName("VK_COMMAND_OR_WIN", commandOrWinModifier);
+ bundle->GetStringFromName("VK_ALT", altModifier);
+ bundle->GetStringFromName("VK_CONTROL", controlModifier);
+ bundle->GetStringFromName("MODIFIER_SEPARATOR", modifierSeparator);
+ }
+ // if any of these don't exist, we get an empty string
+ sShiftText = new nsString(shiftModifier);
+ sCommandOrWinText = new nsString(commandOrWinModifier);
+ sAltText = new nsString(altModifier);
+ sControlText = new nsString(controlModifier);
+ sModifierSeparator = new nsString(modifierSeparator);
+}
+
+mozilla::EventClassID nsContentUtils::GetEventClassIDFromMessage(
+ EventMessage aEventMessage) {
+ switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+ case message_: \
+ return struct_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid event message?");
+ return eBasicEventClass;
+ }
+}
+
+bool nsContentUtils::IsExternalProtocol(nsIURI* aURI) {
+ bool doesNotReturnData = false;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &doesNotReturnData);
+ return NS_SUCCEEDED(rv) && doesNotReturnData;
+}
+
+/* static */
+nsAtom* nsContentUtils::GetEventTypeFromMessage(EventMessage aEventMessage) {
+ switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+ case message_: \
+ return nsGkAtoms::on##name_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+ default:
+ return nullptr;
+ }
+}
+
+bool nsContentUtils::InitializeEventTable() {
+ NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
+ NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
+
+ static const EventNameMapping eventArray[] = {
+#define EVENT(name_, _message, _type, _class) \
+ {nsGkAtoms::on##name_, _type, _message, _class},
+#define WINDOW_ONLY_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
+#define NON_IDL_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef WINDOW_ONLY_EVENT
+#undef NON_IDL_EVENT
+#undef EVENT
+ {nullptr}};
+
+ sAtomEventTable =
+ new nsTHashMap<RefPtr<nsAtom>, EventNameMapping>(ArrayLength(eventArray));
+ sStringEventTable = new nsTHashMap<nsStringHashKey, EventNameMapping>(
+ ArrayLength(eventArray));
+ sUserDefinedEvents = new nsTArray<RefPtr<nsAtom>>(64);
+
+ // Subtract one from the length because of the trailing null
+ for (uint32_t i = 0; i < ArrayLength(eventArray) - 1; ++i) {
+ MOZ_ASSERT(!sAtomEventTable->Contains(eventArray[i].mAtom),
+ "Double-defining event name; fix your EventNameList.h");
+ sAtomEventTable->InsertOrUpdate(eventArray[i].mAtom, eventArray[i]);
+ sStringEventTable->InsertOrUpdate(
+ Substring(nsDependentAtomString(eventArray[i].mAtom), 2),
+ eventArray[i]);
+ }
+
+ return true;
+}
+
+void nsContentUtils::InitializeTouchEventTable() {
+ static bool sEventTableInitialized = false;
+ if (!sEventTableInitialized && sAtomEventTable && sStringEventTable) {
+ sEventTableInitialized = true;
+ static const EventNameMapping touchEventArray[] = {
+#define EVENT(name_, _message, _type, _class)
+#define TOUCH_EVENT(name_, _message, _type, _class) \
+ {nsGkAtoms::on##name_, _type, _message, _class},
+#include "mozilla/EventNameList.h"
+#undef TOUCH_EVENT
+#undef EVENT
+ {nullptr}};
+ // Subtract one from the length because of the trailing null
+ for (uint32_t i = 0; i < ArrayLength(touchEventArray) - 1; ++i) {
+ sAtomEventTable->InsertOrUpdate(touchEventArray[i].mAtom,
+ touchEventArray[i]);
+ sStringEventTable->InsertOrUpdate(
+ Substring(nsDependentAtomString(touchEventArray[i].mAtom), 2),
+ touchEventArray[i]);
+ }
+ }
+}
+
+static bool Is8bit(const nsAString& aString) {
+ static const char16_t EIGHT_BIT = char16_t(~0x00FF);
+
+ for (nsAString::const_char_iterator start = aString.BeginReading(),
+ end = aString.EndReading();
+ start != end; ++start) {
+ if (*start & EIGHT_BIT) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult nsContentUtils::Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String) {
+ if (!Is8bit(aBinaryData)) {
+ aAsciiBase64String.Truncate();
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ return Base64Encode(aBinaryData, aAsciiBase64String);
+}
+
+nsresult nsContentUtils::Atob(const nsAString& aAsciiBase64String,
+ nsAString& aBinaryData) {
+ if (!Is8bit(aAsciiBase64String)) {
+ aBinaryData.Truncate();
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ const char16_t* start = aAsciiBase64String.BeginReading();
+ const char16_t* cur = start;
+ const char16_t* end = aAsciiBase64String.EndReading();
+ bool hasWhitespace = false;
+
+ while (cur < end) {
+ if (nsContentUtils::IsHTMLWhitespace(*cur)) {
+ hasWhitespace = true;
+ break;
+ }
+ cur++;
+ }
+
+ nsresult rv;
+
+ if (hasWhitespace) {
+ nsString trimmedString;
+
+ if (!trimmedString.SetCapacity(aAsciiBase64String.Length(), fallible)) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ trimmedString.Append(start, cur - start);
+
+ while (cur < end) {
+ if (!nsContentUtils::IsHTMLWhitespace(*cur)) {
+ trimmedString.Append(*cur);
+ }
+ cur++;
+ }
+ rv = Base64Decode(trimmedString, aBinaryData);
+ } else {
+ rv = Base64Decode(aAsciiBase64String, aBinaryData);
+ }
+
+ if (NS_FAILED(rv) && rv == NS_ERROR_INVALID_ARG) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+ return rv;
+}
+
+bool nsContentUtils::IsAutocompleteEnabled(
+ mozilla::dom::HTMLInputElement* aInput) {
+ MOZ_ASSERT(aInput, "aInput should not be null!");
+
+ nsAutoString autocomplete;
+ aInput->GetAutocomplete(autocomplete);
+
+ if (autocomplete.IsEmpty()) {
+ auto* form = aInput->GetForm();
+ if (!form) {
+ return true;
+ }
+
+ form->GetAutocomplete(autocomplete);
+ }
+
+ return !autocomplete.EqualsLiteral("off");
+}
+
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, nsAString& aResult,
+ AutocompleteAttrState aCachedState) {
+ if (!aAttr ||
+ aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+ return aCachedState;
+ }
+
+ if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
+ uint32_t atomCount = aAttr->GetAtomCount();
+ for (uint32_t i = 0; i < atomCount; i++) {
+ if (i != 0) {
+ aResult.Append(' ');
+ }
+ aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+ }
+ nsContentUtils::ASCIIToLower(aResult);
+ return aCachedState;
+ }
+
+ aResult.Truncate();
+
+ mozilla::dom::AutocompleteInfo info;
+ AutocompleteAttrState state =
+ InternalSerializeAutocompleteAttribute(aAttr, info);
+ if (state == eAutocompleteAttrState_Valid) {
+ // Concatenate the info fields.
+ aResult = info.mSection;
+
+ if (!info.mAddressType.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mAddressType;
+ }
+
+ if (!info.mContactType.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mContactType;
+ }
+
+ if (!info.mFieldName.IsEmpty()) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mFieldName;
+ }
+
+ // The autocomplete attribute value "webauthn" is interpreted as both a
+ // field name and a credential type. The corresponding IDL-exposed autofill
+ // value is "webauthn", not "webauthn webauthn".
+ if (!info.mCredentialType.IsEmpty() &&
+ !(info.mCredentialType.Equals(u"webauthn"_ns) &&
+ info.mCredentialType.Equals(aResult))) {
+ if (!aResult.IsEmpty()) {
+ aResult += ' ';
+ }
+ aResult += info.mCredentialType;
+ }
+ }
+
+ return state;
+}
+
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::SerializeAutocompleteAttribute(
+ const nsAttrValue* aAttr, mozilla::dom::AutocompleteInfo& aInfo,
+ AutocompleteAttrState aCachedState, bool aGrantAllValidValue) {
+ if (!aAttr ||
+ aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+ return aCachedState;
+ }
+
+ return InternalSerializeAutocompleteAttribute(aAttr, aInfo,
+ aGrantAllValidValue);
+}
+
+/**
+ * Helper to validate the @autocomplete tokens.
+ *
+ * @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
+ */
+nsContentUtils::AutocompleteAttrState
+nsContentUtils::InternalSerializeAutocompleteAttribute(
+ const nsAttrValue* aAttrVal, mozilla::dom::AutocompleteInfo& aInfo,
+ bool aGrantAllValidValue) {
+ // No autocomplete attribute so we are done
+ if (!aAttrVal) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ uint32_t numTokens = aAttrVal->GetAtomCount();
+ if (!numTokens || numTokens > INT32_MAX) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ uint32_t index = numTokens - 1;
+ nsString tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ AutocompleteCategory category;
+ nsAttrValue enumValue;
+ nsAutoString credentialTypeStr;
+
+ bool result = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteCredentialTypeTable, false);
+ if (result) {
+ if (!enumValue.Equals(u"webauthn"_ns, eIgnoreCase) || numTokens > 5) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ enumValue.ToString(credentialTypeStr);
+ ASCIIToLower(credentialTypeStr);
+ // category is Credential and the indexth token is "webauthn"
+ if (index == 0) {
+ aInfo.mFieldName.Assign(credentialTypeStr);
+ aInfo.mCredentialType.Assign(credentialTypeStr);
+ return eAutocompleteAttrState_Valid;
+ }
+
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+
+ // Only the Normal and Contact categories are allowed with webauthn
+ // - disallow Credential
+ if (enumValue.ParseEnumValue(tokenString, kAutocompleteCredentialTypeTable,
+ false)) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ // - disallow Off and Automatic
+ if (enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable,
+ false)) {
+ if (enumValue.Equals(u"off"_ns, eIgnoreCase) ||
+ enumValue.Equals(u"on"_ns, eIgnoreCase)) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ }
+
+ // Proceed to process the remaining tokens as if "webauthn" was not present.
+ // We need to decrement numTokens to enforce the correct per-category limits
+ // on the maximum number of tokens.
+ --numTokens;
+ }
+
+ bool unsupported = false;
+ if (!aGrantAllValidValue) {
+ unsupported = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteUnsupportedFieldNameTable, false);
+ if (unsupported) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ }
+
+ nsAutoString fieldNameStr;
+ result =
+ enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
+
+ if (result) {
+ // Off/Automatic/Normal categories.
+ if (enumValue.Equals(u"off"_ns, eIgnoreCase) ||
+ enumValue.Equals(u"on"_ns, eIgnoreCase)) {
+ if (numTokens > 1) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ enumValue.ToString(fieldNameStr);
+ ASCIIToLower(fieldNameStr);
+ aInfo.mFieldName.Assign(fieldNameStr);
+ aInfo.mCredentialType.Assign(credentialTypeStr);
+ aInfo.mCanAutomaticallyPersist =
+ !enumValue.Equals(u"off"_ns, eIgnoreCase);
+ return eAutocompleteAttrState_Valid;
+ }
+
+ // Only allow on/off if form autofill @autocomplete values aren't enabled
+ // and it doesn't grant all valid values.
+ if (!StaticPrefs::dom_forms_autocomplete_formautofill() &&
+ !aGrantAllValidValue) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ // Normal category
+ if (numTokens > 3) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ category = eAutocompleteCategory_NORMAL;
+ } else { // Check if the last token is of the contact category instead.
+ // Only allow on/off if form autofill @autocomplete values aren't enabled
+ // and it doesn't grant all valid values.
+ if (!StaticPrefs::dom_forms_autocomplete_formautofill() &&
+ !aGrantAllValidValue) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ result = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteContactFieldNameTable, false);
+ if (!result || numTokens > 4) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ category = eAutocompleteCategory_CONTACT;
+ }
+
+ enumValue.ToString(fieldNameStr);
+ ASCIIToLower(fieldNameStr);
+
+ aInfo.mFieldName.Assign(fieldNameStr);
+ aInfo.mCredentialType.Assign(credentialTypeStr);
+ aInfo.mCanAutomaticallyPersist = !enumValue.ParseEnumValue(
+ tokenString, kAutocompleteNoPersistFieldNameTable, false);
+
+ // We are done if this was the only token.
+ if (numTokens == 1) {
+ return eAutocompleteAttrState_Valid;
+ }
+
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+
+ if (category == eAutocompleteCategory_CONTACT) {
+ if (!aGrantAllValidValue) {
+ unsupported = enumValue.ParseEnumValue(
+ tokenString, kAutocompleteUnsupportedContactFieldHintTable, false);
+ if (unsupported) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ }
+
+ nsAttrValue contactFieldHint;
+ result = contactFieldHint.ParseEnumValue(
+ tokenString, kAutocompleteContactFieldHintTable, false);
+ if (result) {
+ nsAutoString contactFieldHintString;
+ contactFieldHint.ToString(contactFieldHintString);
+ ASCIIToLower(contactFieldHintString);
+ aInfo.mContactType.Assign(contactFieldHintString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ }
+ }
+
+ // Check for billing/shipping tokens
+ nsAttrValue fieldHint;
+ if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable,
+ false)) {
+ nsString fieldHintString;
+ fieldHint.ToString(fieldHintString);
+ ASCIIToLower(fieldHintString);
+ aInfo.mAddressType.Assign(fieldHintString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ --index;
+ tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
+ }
+
+ // Check for section-* token
+ const nsDependentSubstring& section = Substring(tokenString, 0, 8);
+ if (section.LowerCaseEqualsASCII("section-")) {
+ ASCIIToLower(tokenString);
+ aInfo.mSection.Assign(tokenString);
+ if (index == 0) {
+ return eAutocompleteAttrState_Valid;
+ }
+ }
+
+ // Clear the fields as the autocomplete attribute is invalid.
+ aInfo.mSection.Truncate();
+ aInfo.mAddressType.Truncate();
+ aInfo.mContactType.Truncate();
+ aInfo.mFieldName.Truncate();
+ aInfo.mCredentialType.Truncate();
+
+ return eAutocompleteAttrState_Invalid;
+}
+
+// Parse an integer according to HTML spec
+template <class CharT>
+int32_t nsContentUtils::ParseHTMLIntegerImpl(
+ const CharT* aStart, const CharT* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ int result = eParseHTMLInteger_NoFlags;
+
+ const CharT* iter = aStart;
+
+ while (iter != aEnd && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ if (iter == aEnd) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return 0;
+ }
+
+ int sign = 1;
+ if (*iter == CharT('-')) {
+ sign = -1;
+ result |= eParseHTMLInteger_Negative;
+ ++iter;
+ } else if (*iter == CharT('+')) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ bool foundValue = false;
+ CheckedInt32 value = 0;
+
+ // Check for leading zeros first.
+ uint64_t leadingZeros = 0;
+ while (iter != aEnd) {
+ if (*iter != CharT('0')) {
+ break;
+ }
+
+ ++leadingZeros;
+ foundValue = true;
+ ++iter;
+ }
+
+ while (iter != aEnd) {
+ if (*iter >= CharT('0') && *iter <= CharT('9')) {
+ value = (value * 10) + (*iter - CharT('0')) * sign;
+ ++iter;
+ if (!value.isValid()) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow;
+ break;
+ }
+ foundValue = true;
+ } else {
+ break;
+ }
+ }
+
+ if (!foundValue) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
+ }
+
+ if (value.isValid() &&
+ ((leadingZeros > 1 || (leadingZeros == 1 && !(value == 0))) ||
+ (sign == -1 && value == 0))) {
+ result |= eParseHTMLInteger_NonStandard;
+ }
+
+ if (iter != aEnd) {
+ result |= eParseHTMLInteger_DidNotConsumeAllInput;
+ }
+
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return value.isValid() ? value.value() : 0;
+}
+
+// Parse an integer according to HTML spec
+int32_t nsContentUtils::ParseHTMLInteger(const char16_t* aStart,
+ const char16_t* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLIntegerImpl(aStart, aEnd, aResult);
+}
+
+int32_t nsContentUtils::ParseHTMLInteger(const char* aStart, const char* aEnd,
+ ParseHTMLIntegerResultFlags* aResult) {
+ return ParseHTMLIntegerImpl(aStart, aEnd, aResult);
+}
+
+#define SKIP_WHITESPACE(iter, end_iter, end_res) \
+ while ((iter) != (end_iter) && nsCRT::IsAsciiSpace(*(iter))) { \
+ ++(iter); \
+ } \
+ if ((iter) == (end_iter)) { \
+ return (end_res); \
+ }
+
+#define SKIP_ATTR_NAME(iter, end_iter) \
+ while ((iter) != (end_iter) && !nsCRT::IsAsciiSpace(*(iter)) && \
+ *(iter) != '=') { \
+ ++(iter); \
+ }
+
+bool nsContentUtils::GetPseudoAttributeValue(const nsString& aSource,
+ nsAtom* aName, nsAString& aValue) {
+ aValue.Truncate();
+
+ const char16_t* start = aSource.get();
+ const char16_t* end = start + aSource.Length();
+ const char16_t* iter;
+
+ while (start != end) {
+ SKIP_WHITESPACE(start, end, false)
+ iter = start;
+ SKIP_ATTR_NAME(iter, end)
+
+ if (start == iter) {
+ return false;
+ }
+
+ // Remember the attr name.
+ const nsDependentSubstring& attrName = Substring(start, iter);
+
+ // Now check whether this is a valid name="value" pair.
+ start = iter;
+ SKIP_WHITESPACE(start, end, false)
+ if (*start != '=') {
+ // No '=', so this is not a name="value" pair. We don't know
+ // what it is, and we have no way to handle it.
+ return false;
+ }
+
+ // Have to skip the value.
+ ++start;
+ SKIP_WHITESPACE(start, end, false)
+ char16_t q = *start;
+ if (q != kQuote && q != kApostrophe) {
+ // Not a valid quoted value, so bail.
+ return false;
+ }
+
+ ++start; // Point to the first char of the value.
+ iter = start;
+
+ while (iter != end && *iter != q) {
+ ++iter;
+ }
+
+ if (iter == end) {
+ // Oops, unterminated quoted string.
+ return false;
+ }
+
+ // At this point attrName holds the name of the "attribute" and
+ // the value is between start and iter.
+
+ if (aName->Equals(attrName)) {
+ // We'll accumulate as many characters as possible (until we hit either
+ // the end of the string or the beginning of an entity). Chunks will be
+ // delimited by start and chunkEnd.
+ const char16_t* chunkEnd = start;
+ while (chunkEnd != iter) {
+ if (*chunkEnd == kLessThan) {
+ aValue.Truncate();
+
+ return false;
+ }
+
+ if (*chunkEnd == kAmpersand) {
+ aValue.Append(start, chunkEnd - start);
+
+ const char16_t* afterEntity = nullptr;
+ char16_t result[2];
+ uint32_t count = MOZ_XMLTranslateEntity(
+ reinterpret_cast<const char*>(chunkEnd),
+ reinterpret_cast<const char*>(iter),
+ reinterpret_cast<const char**>(&afterEntity), result);
+ if (count == 0) {
+ aValue.Truncate();
+
+ return false;
+ }
+
+ aValue.Append(result, count);
+
+ // Advance to after the entity and begin a new chunk.
+ start = chunkEnd = afterEntity;
+ } else {
+ ++chunkEnd;
+ }
+ }
+
+ // Append remainder.
+ aValue.Append(start, iter - start);
+
+ return true;
+ }
+
+ // Resume scanning after the end of the attribute value (past the quote
+ // char).
+ start = iter + 1;
+ }
+
+ return false;
+}
+
+bool nsContentUtils::IsJavaScriptLanguage(const nsString& aName) {
+ // Create MIME type as "text/" + given input
+ nsAutoString mimeType(u"text/");
+ mimeType.Append(aName);
+
+ return IsJavascriptMIMEType(mimeType);
+}
+
+void nsContentUtils::SplitMimeType(const nsAString& aValue, nsString& aType,
+ nsString& aParams) {
+ aType.Truncate();
+ aParams.Truncate();
+ int32_t semiIndex = aValue.FindChar(char16_t(';'));
+ if (-1 != semiIndex) {
+ aType = Substring(aValue, 0, semiIndex);
+ aParams =
+ Substring(aValue, semiIndex + 1, aValue.Length() - (semiIndex + 1));
+ aParams.StripWhitespace();
+ } else {
+ aType = aValue;
+ }
+ aType.StripWhitespace();
+}
+
+/**
+ * A helper function that parses a sandbox attribute (of an <iframe> or a CSP
+ * directive) and converts it to the set of flags used internally.
+ *
+ * @param aSandboxAttr the sandbox attribute
+ * @return the set of flags (SANDBOXED_NONE if aSandboxAttr is
+ * null)
+ */
+uint32_t nsContentUtils::ParseSandboxAttributeToFlags(
+ const nsAttrValue* aSandboxAttr) {
+ if (!aSandboxAttr) {
+ return SANDBOXED_NONE;
+ }
+
+ uint32_t out = SANDBOX_ALL_FLAGS;
+
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (aSandboxAttr->Contains(nsGkAtoms::atom, eIgnoreCase)) { \
+ out &= ~(flags); \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+
+ return out;
+}
+
+/**
+ * A helper function that checks if a string matches a valid sandbox flag.
+ *
+ * @param aFlag the potential sandbox flag.
+ * @return true if the flag is a sandbox flag.
+ */
+bool nsContentUtils::IsValidSandboxFlag(const nsAString& aFlag) {
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (EqualsIgnoreASCIICase(nsDependentAtomString(nsGkAtoms::atom), aFlag)) { \
+ return true; \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+ return false;
+}
+
+/**
+ * A helper function that returns a string attribute corresponding to the
+ * sandbox flags.
+ *
+ * @param aFlags the sandbox flags
+ * @param aString the attribute corresponding to the flags (null if aFlags
+ * is zero)
+ */
+void nsContentUtils::SandboxFlagsToString(uint32_t aFlags, nsAString& aString) {
+ if (!aFlags) {
+ SetDOMStringToNull(aString);
+ return;
+ }
+
+ aString.Truncate();
+
+#define SANDBOX_KEYWORD(string, atom, flags) \
+ if (!(aFlags & (flags))) { \
+ if (!aString.IsEmpty()) { \
+ aString.AppendLiteral(u" "); \
+ } \
+ aString.Append(nsDependentAtomString(nsGkAtoms::atom)); \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+}
+
+nsIBidiKeyboard* nsContentUtils::GetBidiKeyboard() {
+ if (!sBidiKeyboard) {
+ sBidiKeyboard = nsIWidget::CreateBidiKeyboard();
+ }
+ return sBidiKeyboard;
+}
+
+/**
+ * This is used to determine whether a character is in one of the classes
+ * which CSS says should be part of the first-letter. Currently, that is
+ * all punctuation classes (P*). Note that this is a change from CSS2
+ * which excluded Pc and Pd.
+ *
+ * https://www.w3.org/TR/css-pseudo-4/#first-letter-pseudo
+ * "Punctuation (i.e, characters that belong to the Punctuation (P*) Unicode
+ * general category [UAX44]) [...]"
+ */
+
+// static
+bool nsContentUtils::IsFirstLetterPunctuation(uint32_t aChar) {
+ switch (mozilla::unicode::GetGeneralCategory(aChar)) {
+ case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
+ case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */
+ case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
+ case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
+ case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
+ case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
+ case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */
+ return true;
+ default:
+ return false;
+ }
+}
+
+// static
+bool nsContentUtils::IsAlphanumeric(uint32_t aChar) {
+ nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar);
+
+ return (cat == nsUGenCategory::kLetter || cat == nsUGenCategory::kNumber);
+}
+
+// static
+bool nsContentUtils::IsAlphanumericOrSymbol(uint32_t aChar) {
+ nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar);
+
+ return cat == nsUGenCategory::kLetter || cat == nsUGenCategory::kNumber ||
+ cat == nsUGenCategory::kSymbol;
+}
+
+/* static */
+bool nsContentUtils::IsHTMLWhitespace(char16_t aChar) {
+ return aChar == char16_t(0x0009) || aChar == char16_t(0x000A) ||
+ aChar == char16_t(0x000C) || aChar == char16_t(0x000D) ||
+ aChar == char16_t(0x0020);
+}
+
+/* static */
+bool nsContentUtils::IsHTMLWhitespaceOrNBSP(char16_t aChar) {
+ return IsHTMLWhitespace(aChar) || aChar == char16_t(0xA0);
+}
+
+/* static */
+bool nsContentUtils::IsHTMLBlockLevelElement(nsIContent* aContent) {
+ return aContent->IsAnyOfHTMLElements(
+ nsGkAtoms::address, nsGkAtoms::article, nsGkAtoms::aside,
+ nsGkAtoms::blockquote, nsGkAtoms::center, nsGkAtoms::dir, nsGkAtoms::div,
+ nsGkAtoms::dl, // XXX why not dt and dd?
+ nsGkAtoms::fieldset,
+ nsGkAtoms::figure, // XXX shouldn't figcaption be on this list
+ nsGkAtoms::footer, nsGkAtoms::form, nsGkAtoms::h1, nsGkAtoms::h2,
+ nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6,
+ nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::hr, nsGkAtoms::li,
+ nsGkAtoms::listing, nsGkAtoms::menu, nsGkAtoms::nav, nsGkAtoms::ol,
+ nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::section, nsGkAtoms::table,
+ nsGkAtoms::ul, nsGkAtoms::xmp);
+}
+
+/* static */
+bool nsContentUtils::ParseIntMarginValue(const nsAString& aString,
+ nsIntMargin& result) {
+ nsAutoString marginStr(aString);
+ marginStr.CompressWhitespace(true, true);
+ if (marginStr.IsEmpty()) {
+ return false;
+ }
+
+ int32_t start = 0, end = 0;
+ for (int count = 0; count < 4; count++) {
+ if ((uint32_t)end >= marginStr.Length()) return false;
+
+ // top, right, bottom, left
+ if (count < 3)
+ end = Substring(marginStr, start).FindChar(',');
+ else
+ end = Substring(marginStr, start).Length();
+
+ if (end <= 0) return false;
+
+ nsresult ec;
+ int32_t val = nsString(Substring(marginStr, start, end)).ToInteger(&ec);
+ if (NS_FAILED(ec)) return false;
+
+ switch (count) {
+ case 0:
+ result.top = val;
+ break;
+ case 1:
+ result.right = val;
+ break;
+ case 2:
+ result.bottom = val;
+ break;
+ case 3:
+ result.left = val;
+ break;
+ }
+ start += end + 1;
+ }
+ return true;
+}
+
+// static
+int32_t nsContentUtils::ParseLegacyFontSize(const nsAString& aValue) {
+ nsAString::const_iterator iter, end;
+ aValue.BeginReading(iter);
+ aValue.EndReading(end);
+
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ ++iter;
+ }
+
+ if (iter == end) {
+ return 0;
+ }
+
+ bool relative = false;
+ bool negate = false;
+ if (*iter == char16_t('-')) {
+ relative = true;
+ negate = true;
+ ++iter;
+ } else if (*iter == char16_t('+')) {
+ relative = true;
+ ++iter;
+ }
+
+ if (iter == end || *iter < char16_t('0') || *iter > char16_t('9')) {
+ return 0;
+ }
+
+ // We don't have to worry about overflow, since we can bail out as soon as
+ // we're bigger than 7.
+ int32_t value = 0;
+ while (iter != end && *iter >= char16_t('0') && *iter <= char16_t('9')) {
+ value = 10 * value + (*iter - char16_t('0'));
+ if (value >= 7) {
+ break;
+ }
+ ++iter;
+ }
+
+ if (relative) {
+ if (negate) {
+ value = 3 - value;
+ } else {
+ value = 3 + value;
+ }
+ }
+
+ return clamped(value, 1, 7);
+}
+
+/* static */
+void nsContentUtils::GetOfflineAppManifest(Document* aDocument, nsIURI** aURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aDocument);
+ *aURI = nullptr;
+
+ if (aDocument->GetController().isSome()) {
+ return;
+ }
+
+ Element* docElement = aDocument->GetRootElement();
+ if (!docElement) {
+ return;
+ }
+
+ nsAutoString manifestSpec;
+ docElement->GetAttr(nsGkAtoms::manifest, manifestSpec);
+
+ // Manifest URIs can't have fragment identifiers.
+ if (manifestSpec.IsEmpty() || manifestSpec.Contains('#')) {
+ return;
+ }
+
+ nsContentUtils::NewURIWithDocumentCharset(aURI, manifestSpec, aDocument,
+ aDocument->GetDocBaseURI());
+}
+
+/* static */
+bool nsContentUtils::OfflineAppAllowed(nsIURI* aURI) { return false; }
+
+/* static */
+bool nsContentUtils::OfflineAppAllowed(nsIPrincipal* aPrincipal) {
+ return false;
+}
+// Static
+bool nsContentUtils::IsErrorPage(nsIURI* aURI) {
+ if (!aURI) {
+ return false;
+ }
+
+ if (!aURI->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString name;
+ nsresult rv = NS_GetAboutModuleName(aURI, name);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return name.EqualsLiteral("certerror") || name.EqualsLiteral("neterror") ||
+ name.EqualsLiteral("blocked");
+}
+
+// static
+void nsContentUtils::Shutdown() {
+ sInitialized = false;
+
+ nsHTMLTags::ReleaseTable();
+
+ NS_IF_RELEASE(sContentPolicyService);
+ sTriedToGetContentPolicy = false;
+ for (StaticRefPtr<nsIStringBundle>& bundle : sStringBundles) {
+ bundle = nullptr;
+ }
+
+ NS_IF_RELEASE(sStringBundleService);
+ NS_IF_RELEASE(sConsoleService);
+ NS_IF_RELEASE(sXPConnect);
+ NS_IF_RELEASE(sSecurityManager);
+ NS_IF_RELEASE(sSystemPrincipal);
+ NS_IF_RELEASE(sNullSubjectPrincipal);
+
+ sBidiKeyboard = nullptr;
+
+ delete sAtomEventTable;
+ sAtomEventTable = nullptr;
+ delete sStringEventTable;
+ sStringEventTable = nullptr;
+ delete sUserDefinedEvents;
+ sUserDefinedEvents = nullptr;
+
+ if (sEventListenerManagersHash) {
+ NS_ASSERTION(sEventListenerManagersHash->EntryCount() == 0,
+ "Event listener manager hash not empty at shutdown!");
+
+ // See comment above.
+
+ // However, we have to handle this table differently. If it still
+ // has entries, we want to leak it too, so that we can keep it alive
+ // in case any elements are destroyed. Because if they are, we need
+ // their event listener managers to be destroyed too, or otherwise
+ // it could leave dangling references in DOMClassInfo's preserved
+ // wrapper table.
+
+ if (sEventListenerManagersHash->EntryCount() == 0) {
+ delete sEventListenerManagersHash;
+ sEventListenerManagersHash = nullptr;
+ }
+ }
+
+ if (sDOMArenaHashtable) {
+ MOZ_ASSERT(sDOMArenaHashtable->Count() == 0);
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ delete sDOMArenaHashtable;
+ sDOMArenaHashtable = nullptr;
+ }
+
+ NS_ASSERTION(!sBlockedScriptRunners || sBlockedScriptRunners->Length() == 0,
+ "How'd this happen?");
+ delete sBlockedScriptRunners;
+ sBlockedScriptRunners = nullptr;
+
+ delete sShiftText;
+ sShiftText = nullptr;
+ delete sControlText;
+ sControlText = nullptr;
+ delete sCommandOrWinText;
+ sCommandOrWinText = nullptr;
+ delete sAltText;
+ sAltText = nullptr;
+ delete sModifierSeparator;
+ sModifierSeparator = nullptr;
+
+ delete sJSScriptBytecodeMimeType;
+ sJSScriptBytecodeMimeType = nullptr;
+
+ delete sJSModuleBytecodeMimeType;
+ sJSModuleBytecodeMimeType = nullptr;
+
+ NS_IF_RELEASE(sSameOriginChecker);
+
+ if (sUserInteractionObserver) {
+ sUserInteractionObserver->Shutdown();
+ NS_RELEASE(sUserInteractionObserver);
+ }
+
+ for (const auto& pref : kRfpPrefs) {
+ Preferences::UnregisterCallback(RecomputeResistFingerprintingAllDocs, pref);
+ }
+
+ TextControlState::Shutdown();
+}
+
+/**
+ * Checks whether two nodes come from the same origin. aTrustedNode is
+ * considered 'safe' in that a user can operate on it.
+ */
+// static
+nsresult nsContentUtils::CheckSameOrigin(const nsINode* aTrustedNode,
+ const nsINode* unTrustedNode) {
+ MOZ_ASSERT(aTrustedNode);
+ MOZ_ASSERT(unTrustedNode);
+
+ /*
+ * Get hold of each node's principal
+ */
+
+ nsIPrincipal* trustedPrincipal = aTrustedNode->NodePrincipal();
+ nsIPrincipal* unTrustedPrincipal = unTrustedNode->NodePrincipal();
+
+ if (trustedPrincipal == unTrustedPrincipal) {
+ return NS_OK;
+ }
+
+ bool equal;
+ // XXXbz should we actually have a Subsumes() check here instead? Or perhaps
+ // a separate method for that, with callers using one or the other?
+ if (NS_FAILED(trustedPrincipal->Equals(unTrustedPrincipal, &equal)) ||
+ !equal) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ return NS_OK;
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
+ nsIPrincipal* aPrincipal) {
+ bool subsumes;
+ nsresult rv = aSubjectPrincipal->Subsumes(aPrincipal, &subsumes);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (subsumes) {
+ return true;
+ }
+
+ // The subject doesn't subsume aPrincipal. Allow access only if the subject
+ // is chrome.
+ return IsCallerChrome();
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(const nsINode* aNode) {
+ nsIPrincipal* subject = SubjectPrincipal();
+ if (subject->IsSystemPrincipal()) {
+ return true;
+ }
+
+ if (aNode->ChromeOnlyAccess()) {
+ return false;
+ }
+
+ return CanCallerAccess(subject, aNode->NodePrincipal());
+}
+
+// static
+bool nsContentUtils::CanCallerAccess(nsPIDOMWindowInner* aWindow) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObject = do_QueryInterface(aWindow);
+ NS_ENSURE_TRUE(scriptObject, false);
+
+ return CanCallerAccess(SubjectPrincipal(), scriptObject->GetPrincipal());
+}
+
+// static
+bool nsContentUtils::PrincipalHasPermission(nsIPrincipal& aPrincipal,
+ const nsAtom* aPerm) {
+ // Chrome gets access by default.
+ if (aPrincipal.IsSystemPrincipal()) {
+ return true;
+ }
+
+ // Otherwise, only allow if caller is an addon with the permission.
+ return BasePrincipal::Cast(aPrincipal).AddonHasPermission(aPerm);
+}
+
+// static
+bool nsContentUtils::CallerHasPermission(JSContext* aCx, const nsAtom* aPerm) {
+ return PrincipalHasPermission(*SubjectPrincipal(aCx), aPerm);
+}
+
+// static
+nsIPrincipal* nsContentUtils::GetAttrTriggeringPrincipal(
+ nsIContent* aContent, const nsAString& aAttrValue,
+ nsIPrincipal* aSubjectPrincipal) {
+ nsIPrincipal* contentPrin = aContent ? aContent->NodePrincipal() : nullptr;
+
+ // If the subject principal is the same as the content principal, or no
+ // explicit subject principal was provided, we don't need to do any further
+ // checks. Just return the content principal.
+ if (contentPrin == aSubjectPrincipal || !aSubjectPrincipal) {
+ return contentPrin;
+ }
+
+ // Only use the subject principal if the URL string we are going to end up
+ // fetching is under the control of that principal, which is never the case
+ // for relative URLs.
+ if (aAttrValue.IsEmpty() ||
+ !IsAbsoluteURL(NS_ConvertUTF16toUTF8(aAttrValue))) {
+ return contentPrin;
+ }
+
+ // Only use the subject principal as the attr triggering principal if it
+ // should override the CSP of the node's principal.
+ if (BasePrincipal::Cast(aSubjectPrincipal)->OverridesCSP(contentPrin)) {
+ return aSubjectPrincipal;
+ }
+
+ return contentPrin;
+}
+
+// static
+bool nsContentUtils::IsAbsoluteURL(const nsACString& aURL) {
+ nsAutoCString scheme;
+ if (NS_FAILED(net_ExtractURLScheme(aURL, scheme))) {
+ // If we can't extract a scheme, it's not an absolute URL.
+ return false;
+ }
+
+ // If it parses as an absolute StandardURL, it's definitely an absolute URL,
+ // so no need to check with the IO service.
+ if (net_IsAbsoluteURL(aURL)) {
+ return true;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(&rv);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t flags;
+ if (NS_SUCCEEDED(io->GetProtocolFlags(scheme.get(), &flags))) {
+ return flags & nsIProtocolHandler::URI_NORELATIVE;
+ }
+
+ return false;
+}
+
+// static
+bool nsContentUtils::InProlog(nsINode* aNode) {
+ MOZ_ASSERT(aNode, "missing node to nsContentUtils::InProlog");
+
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent || !parent->IsDocument()) {
+ return false;
+ }
+
+ const Document* doc = parent->AsDocument();
+ const nsIContent* root = doc->GetRootElement();
+ if (!root) {
+ return true;
+ }
+ const Maybe<uint32_t> indexOfNode = doc->ComputeIndexOf(aNode);
+ const Maybe<uint32_t> indexOfRoot = doc->ComputeIndexOf(root);
+ if (MOZ_LIKELY(indexOfNode.isSome() && indexOfRoot.isSome())) {
+ return *indexOfNode < *indexOfRoot;
+ }
+ // XXX Keep the odd traditional behavior for now.
+ return indexOfNode.isNothing() && indexOfRoot.isSome();
+}
+
+bool nsContentUtils::IsCallerChrome() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return SubjectPrincipal() == sSystemPrincipal;
+}
+
+#ifdef FUZZING
+bool nsContentUtils::IsFuzzingEnabled() {
+ return StaticPrefs::fuzzing_enabled();
+}
+#endif
+
+/* static */
+bool nsContentUtils::IsCallerChromeOrElementTransformGettersEnabled(
+ JSContext* aCx, JSObject*) {
+ return ThreadsafeIsSystemCaller(aCx) ||
+ StaticPrefs::dom_element_transform_getters_enabled();
+}
+
+// Older Should RFP Functions ----------------------------------
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(bool aIsPrivateMode,
+ RFPTarget aTarget) {
+ return nsRFPService::IsRFPEnabledFor(aIsPrivateMode, aTarget, Nothing());
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget) {
+ if (!aGlobalObject) {
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+ return aGlobalObject->ShouldResistFingerprinting(aTarget);
+}
+
+// Newer Should RFP Functions ----------------------------------
+// Utilities ---------------------------------------------------
+
+inline void LogDomainAndPrefList(const char* urlType,
+ const char* exemptedDomainsPrefName,
+ nsAutoCString& url, bool isExemptDomain) {
+ nsAutoCString list;
+ Preferences::GetCString(exemptedDomainsPrefName, list);
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("%s \"%s\" is %s the exempt list \"%s\"", urlType,
+ PromiseFlatCString(url).get(), isExemptDomain ? "in" : "NOT in",
+ PromiseFlatCString(list).get()));
+}
+
+inline already_AddRefed<nsICookieJarSettings> GetCookieJarSettings(
+ nsILoadInfo* aLoadInfo) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The TRRLoadInfo in particular does not implement this method
+ // In that instance. We will return false and let other code decide if
+ // we shouldRFP for this connection
+ return nullptr;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called CookieJarSettingsSaysShouldResistFingerprinting but the "
+ "loadinfo's CookieJarSettings couldn't be retrieved"));
+ return nullptr;
+ }
+
+ MOZ_ASSERT(cookieJarSettings);
+ return cookieJarSettings.forget();
+}
+
+bool ETPSaysShouldNotResistFingerprinting(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo) {
+ // A positive return from this function should always be obeyed.
+ // A negative return means we should keep checking things.
+
+ bool isPBM = NS_UsePrivateBrowsing(aChannel);
+ // We do not want this check to apply to RFP, only to FPP
+ // There is one problematic combination of prefs; however:
+ // If RFP is enabled in PBMode only and FPP is enabled globally
+ // (so, in non-PBM mode) - we need to know if we're in PBMode or not.
+ // But that's kind of expensive and we'd like to avoid it if we
+ // don't have to, so special-case that scenario
+ if (StaticPrefs::privacy_fingerprintingProtection_DoNotUseDirectly() &&
+ !StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() &&
+ StaticPrefs::privacy_resistFingerprinting_pbmode_DoNotUseDirectly()) {
+ if (isPBM) {
+ // In PBM (where RFP is enabled) do not exempt based on the ETP toggle
+ return false;
+ }
+ } else if (StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() ||
+ (isPBM &&
+ StaticPrefs::
+ privacy_resistFingerprinting_pbmode_DoNotUseDirectly())) {
+ // In RFP, never use the ETP toggle to exempt.
+ // We can safely return false here even if we are not in PBM mode
+ // and RFP_pbmode is enabled because we will later see that and
+ // return false from the ShouldRFP function entirely.
+ return false;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ GetCookieJarSettings(aLoadInfo);
+ if (!cookieJarSettings) {
+ return false;
+ }
+
+ return ContentBlockingAllowList::Check(cookieJarSettings);
+}
+
+inline bool CookieJarSettingsSaysShouldResistFingerprinting(
+ nsILoadInfo* aLoadInfo) {
+ // A positive return from this function should always be obeyed.
+ // A negative return means we should keep checking things.
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ GetCookieJarSettings(aLoadInfo);
+ if (!cookieJarSettings) {
+ return false;
+ }
+ return cookieJarSettings->GetShouldResistFingerprinting();
+}
+
+inline bool SchemeSaysShouldNotResistFingerprinting(nsIURI* aURI) {
+ return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource") ||
+ aURI->SchemeIs("view-source") || aURI->SchemeIs("moz-extension") ||
+ (aURI->SchemeIs("about") && !NS_IsContentAccessibleAboutURI(aURI));
+}
+
+inline bool SchemeSaysShouldNotResistFingerprinting(nsIPrincipal* aPrincipal) {
+ if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") ||
+ aPrincipal->SchemeIs("view-source") ||
+ aPrincipal->SchemeIs("moz-extension")) {
+ return true;
+ }
+
+ if (!aPrincipal->SchemeIs("about")) {
+ return false;
+ }
+
+ bool isContentAccessibleAboutURI;
+ Unused << aPrincipal->IsContentAccessibleAboutURI(
+ &isContentAccessibleAboutURI);
+ return !isContentAccessibleAboutURI;
+}
+
+const char* kExemptedDomainsPrefName =
+ "privacy.resistFingerprinting.exemptedDomains";
+
+inline bool PartionKeyIsAlsoExempted(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // If we've gotten here we have (probably) passed the CookieJarSettings
+ // check that would tell us that if we _are_ a subdocument, then we are on
+ // an exempted top-level domain and we should see if we ourselves are
+ // exempted. But we may have gotten here because we directly called the
+ // _dangerous function and we haven't done that check, but we _were_
+ // instatiated from a state where we could have been partitioned.
+ // So perform this last-ditch check for that scenario.
+ // We arbitrarily use https as the scheme, but it doesn't matter.
+ nsresult rv = NS_ERROR_NOT_INITIALIZED;
+ nsCOMPtr<nsIURI> uri;
+ if (StaticPrefs::privacy_firstparty_isolate() &&
+ !aOriginAttributes.mFirstPartyDomain.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + aOriginAttributes.mFirstPartyDomain);
+ } else if (!aOriginAttributes.mPartitionKey.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(uri),
+ u"https://"_ns + aOriginAttributes.mPartitionKey);
+ }
+
+ if (!NS_FAILED(rv)) {
+ bool isExemptPartitionKey =
+ nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString url;
+ uri->GetHost(url);
+ LogDomainAndPrefList("Partition Key", kExemptedDomainsPrefName, url,
+ isExemptPartitionKey);
+ }
+ return isExemptPartitionKey;
+ }
+ return true;
+}
+
+// Functions ---------------------------------------------------
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(const char* aJustification,
+ RFPTarget aTarget) {
+ // See comment in header file for information about usage
+ // We hardcode PBM to true to be the more restrictive option.
+ return nsContentUtils::ShouldResistFingerprinting(true, aTarget);
+}
+
+namespace {
+
+// This function is only called within this file for Positive Return Checks
+bool ShouldResistFingerprinting_(const char* aJustification,
+ bool aIsPrivateMode, RFPTarget aTarget) {
+ // See comment in header file for information about usage
+ return nsContentUtils::ShouldResistFingerprinting(aIsPrivateMode, aTarget);
+}
+
+} // namespace
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(CallerType aCallerType,
+ nsIGlobalObject* aGlobalObject,
+ RFPTarget aTarget) {
+ if (aCallerType == CallerType::System) {
+ return false;
+ }
+ return ShouldResistFingerprinting(aGlobalObject, aTarget);
+}
+
+bool nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell,
+ RFPTarget aTarget) {
+ if (!aDocShell) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIDocShell*) "
+ "with NULL docshell"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+ Document* doc = aDocShell->GetDocument();
+ if (!doc) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIDocShell*) "
+ "with NULL doc"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+ return doc->ShouldResistFingerprinting(aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting(nsIChannel* aChannel,
+ RFPTarget aTarget) {
+ if (!aChannel) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIChannel* "
+ "aChannel) with NULL channel"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (!loadInfo) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsIChannel* "
+ "aChannel) but the channel's loadinfo was NULL"));
+ return ShouldResistFingerprinting("Null Object", aTarget);
+ }
+
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ bool isPBM = NS_UsePrivateBrowsing(aChannel);
+ if (!ShouldResistFingerprinting_("Positive return check", isPBM, aTarget)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIChannel*)"
+ " Positive return check said false (PBM: %s)",
+ isPBM ? "Yes" : "No"));
+ return false;
+ }
+
+ if (ETPSaysShouldNotResistFingerprinting(aChannel, loadInfo)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIChannel*)"
+ " ETPSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ if (CookieJarSettingsSaysShouldResistFingerprinting(loadInfo)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIChannel*)"
+ " CookieJarSettingsSaysShouldResistFingerprinting said true"));
+ return true;
+ }
+
+ // Document types have no loading principal. Subdocument types do have a
+ // loading principal, but it is the loading principal of the parent
+ // document; not the subdocument.
+ auto contentType = loadInfo->GetExternalContentPolicyType();
+ // Case 1: Document or Subdocument load
+ if (contentType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ MOZ_ASSERT(
+ NS_SUCCEEDED(rv),
+ "Failed to get URI in "
+ "nsContentUtils::ShouldResistFingerprinting(nsIChannel* aChannel)");
+ // this check is to ensure that we do not crash in non-debug builds.
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+#if 0
+ if (loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ nsAutoCString channelSpec;
+ channelURI->GetSpec(channelSpec);
+
+ if (!loadInfo->GetLoadingPrincipal()) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Sub Document Type. FinalChannelURI is %s, Loading Principal is NULL\n",
+ channelSpec.get()));
+
+ } else {
+ nsAutoCString loadingPrincipalSpec;
+ loadInfo->GetLoadingPrincipal()->GetOrigin(loadingPrincipalSpec);
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Sub Document Type. FinalChannelURI is %s, Loading Principal Origin is %s\n",
+ channelSpec.get(), loadingPrincipalSpec.get()));
+ }
+ }
+
+#endif
+
+ return ShouldResistFingerprinting_dangerous(
+ channelURI, loadInfo->GetOriginAttributes(), "Internal Call", aTarget);
+ }
+
+ // Case 2: Subresource Load
+ // Because this code is only used for subresource loads, this
+ // will check the parent's principal
+ nsIPrincipal* principal = loadInfo->GetLoadingPrincipal();
+
+ MOZ_ASSERT_IF(principal && !principal->IsSystemPrincipal() &&
+ !principal->GetIsAddonOrExpandedAddonPrincipal(),
+ BasePrincipal::Cast(principal)->OriginAttributesRef() ==
+ loadInfo->GetOriginAttributes());
+ return ShouldResistFingerprinting_dangerous(principal, "Internal Call",
+ aTarget);
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting_dangerous(
+ nsIURI* aURI, const mozilla::OriginAttributes& aOriginAttributes,
+ const char* aJustification, RFPTarget aTarget) {
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ bool isPBM = aOriginAttributes.mPrivateBrowsingId !=
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+ if (!ShouldResistFingerprinting_("Positive return check", isPBM, aTarget)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting_dangerous(nsIURI*,"
+ " OriginAttributes) Positive return check said false (PBM: %s)",
+ isPBM ? "Yes" : "No"));
+ return false;
+ }
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting_dangerous(nsIURI*,"
+ " OriginAttributes) and the URI is %s",
+ aURI->GetSpecOrDefault().get()));
+
+ if (!StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() &&
+ !StaticPrefs::privacy_fingerprintingProtection_DoNotUseDirectly()) {
+ // If neither of the 'regular' RFP prefs are set, then one (or both)
+ // of the PBM-Only prefs are set (or we would have failed the
+ // Positive return check.) Therefore, if we are not in PBM, return false
+ if (aOriginAttributes.mPrivateBrowsingId ==
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting_dangerous(nsIURI*,"
+ " OriginAttributes) OA PBM Check said false"));
+ return false;
+ }
+ }
+
+ // Exclude internal schemes and web extensions
+ if (SchemeSaysShouldNotResistFingerprinting(aURI)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIURI*)"
+ " SchemeSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ bool isExemptDomain = false;
+ nsAutoCString list;
+ Preferences::GetCString(kExemptedDomainsPrefName, list);
+ ToLowerCase(list);
+ isExemptDomain = IsURIInList(aURI, list);
+
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString url;
+ aURI->GetHost(url);
+ LogDomainAndPrefList("URI", kExemptedDomainsPrefName, url, isExemptDomain);
+ }
+
+ if (isExemptDomain) {
+ isExemptDomain &= PartionKeyIsAlsoExempted(aOriginAttributes);
+ }
+
+ return !isExemptDomain;
+}
+
+/* static */
+bool nsContentUtils::ShouldResistFingerprinting_dangerous(
+ nsIPrincipal* aPrincipal, const char* aJustification, RFPTarget aTarget) {
+ if (!aPrincipal) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Info,
+ ("Called nsContentUtils::ShouldResistFingerprinting(nsILoadInfo* "
+ "aChannel) but the loadinfo's loadingprincipal was NULL"));
+ return ShouldResistFingerprinting("Null object", aTarget);
+ }
+
+ auto originAttributes =
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+ // With this check, we can ensure that the prefs and target say yes, so only
+ // an exemption would cause us to return false.
+ bool isPBM = originAttributes.mPrivateBrowsingId ==
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+ if (!ShouldResistFingerprinting_("Positive return check", isPBM, aTarget)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIPrincipal*) Positive return "
+ "check said false (PBM: %s)",
+ isPBM ? "Yes" : "No"));
+ return false;
+ }
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIPrincipal*) System "
+ "Principal said false"));
+ return false;
+ }
+
+ // Exclude internal schemes and web extensions
+ if (SchemeSaysShouldNotResistFingerprinting(aPrincipal)) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIPrincipal*)"
+ " SchemeSaysShouldNotResistFingerprinting said false"));
+ return false;
+ }
+
+ // Web extension principals are also excluded
+ if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside ShouldResistFingerprinting(nsIPrincipal*)"
+ " and AddonPolicy said false"));
+ return false;
+ }
+
+ bool isExemptDomain = false;
+ aPrincipal->IsURIInPrefList(kExemptedDomainsPrefName, &isExemptDomain);
+
+ if (MOZ_LOG_TEST(nsContentUtils::ResistFingerprintingLog(),
+ mozilla::LogLevel::Debug)) {
+ nsAutoCString origin;
+ aPrincipal->GetOrigin(origin);
+ LogDomainAndPrefList("URI", kExemptedDomainsPrefName, origin,
+ isExemptDomain);
+ }
+
+ if (isExemptDomain) {
+ isExemptDomain &= PartionKeyIsAlsoExempted(originAttributes);
+ }
+
+ return !isExemptDomain;
+}
+
+// --------------------------------------------------------------------
+
+/* static */
+void nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+ int32_t aChromeWidth, int32_t aChromeHeight, int32_t aScreenWidth,
+ int32_t aScreenHeight, int32_t aInputWidth, int32_t aInputHeight,
+ bool aSetOuterWidth, bool aSetOuterHeight, int32_t* aOutputWidth,
+ int32_t* aOutputHeight) {
+ MOZ_ASSERT(aOutputWidth);
+ MOZ_ASSERT(aOutputHeight);
+
+ int32_t availContentWidth = 0;
+ int32_t availContentHeight = 0;
+
+ availContentWidth = std::min(StaticPrefs::privacy_window_maxInnerWidth(),
+ aScreenWidth - aChromeWidth);
+#ifdef MOZ_WIDGET_GTK
+ // In the GTK window, it will not report outside system decorations
+ // when we get available window size, see Bug 581863. So, we leave a
+ // 40 pixels space for them when calculating the available content
+ // height. It is not necessary for the width since the content width
+ // is usually pretty much the same as the chrome width.
+ availContentHeight = std::min(StaticPrefs::privacy_window_maxInnerHeight(),
+ (-40 + aScreenHeight) - aChromeHeight);
+#else
+ availContentHeight = std::min(StaticPrefs::privacy_window_maxInnerHeight(),
+ aScreenHeight - aChromeHeight);
+#endif
+
+ // Ideally, we'd like to round window size to 1000x1000, but the
+ // screen space could be too small to accommodate this size in some
+ // cases. If it happens, we would round the window size to the nearest
+ // 200x100.
+ availContentWidth = availContentWidth - (availContentWidth % 200);
+ availContentHeight = availContentHeight - (availContentHeight % 100);
+
+ // If aIsOuter is true, we are setting the outer window. So we
+ // have to consider the chrome UI.
+ int32_t chromeOffsetWidth = aSetOuterWidth ? aChromeWidth : 0;
+ int32_t chromeOffsetHeight = aSetOuterHeight ? aChromeHeight : 0;
+ int32_t resultWidth = 0, resultHeight = 0;
+
+ // if the original size is greater than the maximum available size, we set
+ // it to the maximum size. And if the original value is less than the
+ // minimum rounded size, we set it to the minimum 200x100.
+ if (aInputWidth > (availContentWidth + chromeOffsetWidth)) {
+ resultWidth = availContentWidth + chromeOffsetWidth;
+ } else if (aInputWidth < (200 + chromeOffsetWidth)) {
+ resultWidth = 200 + chromeOffsetWidth;
+ } else {
+ // Otherwise, we round the window to the nearest upper rounded 200x100.
+ resultWidth = NSToIntCeil((aInputWidth - chromeOffsetWidth) / 200.0) * 200 +
+ chromeOffsetWidth;
+ }
+
+ if (aInputHeight > (availContentHeight + chromeOffsetHeight)) {
+ resultHeight = availContentHeight + chromeOffsetHeight;
+ } else if (aInputHeight < (100 + chromeOffsetHeight)) {
+ resultHeight = 100 + chromeOffsetHeight;
+ } else {
+ resultHeight =
+ NSToIntCeil((aInputHeight - chromeOffsetHeight) / 100.0) * 100 +
+ chromeOffsetHeight;
+ }
+
+ *aOutputWidth = resultWidth;
+ *aOutputHeight = resultHeight;
+}
+
+bool nsContentUtils::ThreadsafeIsCallerChrome() {
+ return NS_IsMainThread() ? IsCallerChrome()
+ : IsCurrentThreadRunningChromeWorker();
+}
+
+bool nsContentUtils::IsCallerUAWidget() {
+ JSContext* cx = GetCurrentJSContext();
+ if (!cx) {
+ return false;
+ }
+
+ JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
+ if (!realm) {
+ return false;
+ }
+
+ return xpc::IsUAWidgetScope(realm);
+}
+
+bool nsContentUtils::IsSystemCaller(JSContext* aCx) {
+ // Note that SubjectPrincipal() assumes we are in a compartment here.
+ return SubjectPrincipal(aCx) == sSystemPrincipal;
+}
+
+bool nsContentUtils::ThreadsafeIsSystemCaller(JSContext* aCx) {
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx->Context() == aCx);
+
+ return ccjscx->IsSystemCaller();
+}
+
+// static
+bool nsContentUtils::LookupBindingMember(
+ JSContext* aCx, nsIContent* aContent, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
+ return true;
+}
+
+nsINode* nsContentUtils::GetNearestInProcessCrossDocParentNode(
+ nsINode* aChild) {
+ if (aChild->IsDocument()) {
+ for (BrowsingContext* bc = aChild->AsDocument()->GetBrowsingContext(); bc;
+ bc = bc->GetParent()) {
+ if (bc->GetEmbedderElement()) {
+ return bc->GetEmbedderElement();
+ }
+ }
+ return nullptr;
+ }
+
+ nsINode* parent = aChild->GetParentNode();
+ if (parent && parent->IsContent() && aChild->IsContent()) {
+ parent = aChild->AsContent()->GetFlattenedTreeParent();
+ }
+
+ return parent;
+}
+
+bool nsContentUtils::ContentIsHostIncludingDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) return true;
+ if (aPossibleDescendant->IsDocumentFragment()) {
+ aPossibleDescendant =
+ aPossibleDescendant->AsDocumentFragment()->GetHost();
+ } else {
+ aPossibleDescendant = aPossibleDescendant->GetParentNode();
+ }
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant,
+ nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+
+ aPossibleDescendant =
+ GetNearestInProcessCrossDocParentNode(aPossibleDescendant);
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsFlattenedTreeDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+ aPossibleDescendant = aPossibleDescendant->GetFlattenedTreeParentNode();
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) {
+ MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!");
+ MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor) {
+ return true;
+ }
+ aPossibleDescendant =
+ aPossibleDescendant->GetFlattenedTreeParentNodeForStyle();
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+nsINode* nsContentUtils::Retarget(nsINode* aTargetA, nsINode* aTargetB) {
+ while (true && aTargetA) {
+ // If A's root is not a shadow root...
+ nsINode* root = aTargetA->SubtreeRoot();
+ if (!root->IsShadowRoot()) {
+ // ...then return A.
+ return aTargetA;
+ }
+
+ // or A's root is a shadow-including inclusive ancestor of B...
+ if (aTargetB->IsShadowIncludingInclusiveDescendantOf(root)) {
+ // ...then return A.
+ return aTargetA;
+ }
+
+ aTargetA = ShadowRoot::FromNode(root)->GetHost();
+ }
+
+ return nullptr;
+}
+
+// static
+Element* nsContentUtils::GetAnElementForTiming(Element* aTarget,
+ const Document* aDocument,
+ nsIGlobalObject* aGlobal) {
+ if (!aTarget->IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ if (!aDocument) {
+ nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aGlobal);
+ if (!inner) {
+ return nullptr;
+ }
+ aDocument = inner->GetExtantDoc();
+ }
+
+ MOZ_ASSERT(aDocument);
+
+ if (aTarget->GetUncomposedDocOrConnectedShadowRoot() != aDocument ||
+ !aDocument->IsCurrentActiveDocument()) {
+ return nullptr;
+ }
+
+ return aTarget;
+}
+
+// static
+nsresult nsContentUtils::GetInclusiveAncestors(nsINode* aNode,
+ nsTArray<nsINode*>& aArray) {
+ while (aNode) {
+ aArray.AppendElement(aNode);
+ aNode = aNode->GetParentNode();
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsContentUtils::GetInclusiveAncestorsAndOffsets(
+ nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>* aAncestorNodes,
+ nsTArray<Maybe<uint32_t>>* aAncestorOffsets) {
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ if (!aNode->IsContent()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsIContent* content = aNode->AsContent();
+
+ if (!aAncestorNodes->IsEmpty()) {
+ NS_WARNING("aAncestorNodes is not empty");
+ aAncestorNodes->Clear();
+ }
+
+ if (!aAncestorOffsets->IsEmpty()) {
+ NS_WARNING("aAncestorOffsets is not empty");
+ aAncestorOffsets->Clear();
+ }
+
+ // insert the node itself
+ aAncestorNodes->AppendElement(content);
+ aAncestorOffsets->AppendElement(Some(aOffset));
+
+ // insert all the ancestors
+ nsIContent* child = content;
+ nsIContent* parent = child->GetParent();
+ while (parent) {
+ aAncestorNodes->AppendElement(parent);
+ aAncestorOffsets->AppendElement(parent->ComputeIndexOf(child));
+ child = parent;
+ parent = parent->GetParent();
+ }
+
+ return NS_OK;
+}
+
+template <typename Node, typename GetParentFunc>
+static Node* GetCommonAncestorInternal(Node* aNode1, Node* aNode2,
+ GetParentFunc aGetParentFunc) {
+ MOZ_ASSERT(aNode1 != aNode2);
+
+ // Build the chain of parents
+ AutoTArray<Node*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(aNode1);
+ aNode1 = aGetParentFunc(aNode1);
+ } while (aNode1);
+ do {
+ parents2.AppendElement(aNode2);
+ aNode2 = aGetParentFunc(aNode2);
+ } while (aNode2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ Node** data1 = parents1.Elements();
+ Node** data2 = parents2.Elements();
+ Node* parent = nullptr;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ Node* child1 = data1[--pos1];
+ Node* child2 = data2[--pos2];
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ return parent;
+}
+
+/* static */
+nsINode* nsContentUtils::GetCommonAncestorHelper(nsINode* aNode1,
+ nsINode* aNode2) {
+ return GetCommonAncestorInternal(
+ aNode1, aNode2, [](nsINode* aNode) { return aNode->GetParentNode(); });
+}
+
+/* static */
+nsIContent* nsContentUtils::GetCommonFlattenedTreeAncestorHelper(
+ nsIContent* aContent1, nsIContent* aContent2) {
+ return GetCommonAncestorInternal(
+ aContent1, aContent2,
+ [](nsIContent* aContent) { return aContent->GetFlattenedTreeParent(); });
+}
+
+/* static */
+Element* nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
+ Element* aElement1, Element* aElement2) {
+ return GetCommonAncestorInternal(aElement1, aElement2, [](Element* aElement) {
+ return aElement->GetFlattenedTreeParentElementForStyle();
+ });
+}
+
+/* static */
+bool nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2,
+ Maybe<uint32_t>* aNode1Index,
+ Maybe<uint32_t>* aNode2Index) {
+ // Note, CompareDocumentPosition takes the latter params in different order.
+ return (aNode2->CompareDocumentPosition(*aNode1, aNode2Index, aNode1Index) &
+ (Node_Binding::DOCUMENT_POSITION_PRECEDING |
+ Node_Binding::DOCUMENT_POSITION_DISCONNECTED)) ==
+ Node_Binding::DOCUMENT_POSITION_PRECEDING;
+}
+
+/* static */
+Maybe<int32_t> nsContentUtils::ComparePoints(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, ComparePointsCache* aParent1Cache) {
+ bool disconnected{false};
+
+ const int32_t order = ComparePoints_Deprecated(
+ aParent1, aOffset1, aParent2, aOffset2, &disconnected, aParent1Cache);
+ if (disconnected) {
+ return Nothing();
+ }
+
+ return Some(order);
+}
+
+/* static */
+int32_t nsContentUtils::ComparePoints_Deprecated(
+ const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
+ uint32_t aOffset2, bool* aDisconnected, ComparePointsCache* aParent1Cache) {
+ if (aParent1 == aParent2) {
+ return aOffset1 < aOffset2 ? -1 : aOffset1 > aOffset2 ? 1 : 0;
+ }
+
+ AutoTArray<const nsINode*, 32> parents1, parents2;
+ const nsINode* node1 = aParent1;
+ const nsINode* node2 = aParent2;
+ do {
+ parents1.AppendElement(node1);
+ node1 = node1->GetParentOrShadowHostNode();
+ } while (node1);
+ do {
+ parents2.AppendElement(node2);
+ node2 = node2->GetParentOrShadowHostNode();
+ } while (node2);
+
+ uint32_t pos1 = parents1.Length() - 1;
+ uint32_t pos2 = parents2.Length() - 1;
+
+ bool disconnected = parents1.ElementAt(pos1) != parents2.ElementAt(pos2);
+ if (aDisconnected) {
+ *aDisconnected = disconnected;
+ }
+ if (disconnected) {
+ NS_ASSERTION(aDisconnected, "unexpected disconnected nodes");
+ return 1;
+ }
+
+ // Find where the parent chains differ
+ const nsINode* parent = parents1.ElementAt(pos1);
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ const nsINode* child1 = parents1.ElementAt(--pos1);
+ const nsINode* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ if (MOZ_UNLIKELY(child1->IsShadowRoot())) {
+ // Shadow roots come before light DOM per
+ // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
+ MOZ_ASSERT(!child2->IsShadowRoot(), "Two shadow roots?");
+ return -1;
+ }
+ if (MOZ_UNLIKELY(child2->IsShadowRoot())) {
+ return 1;
+ }
+ const Maybe<uint32_t> child1Index =
+ aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1)
+ : parent->ComputeIndexOf(child1);
+ const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2);
+ if (MOZ_LIKELY(child1Index.isSome() && child2Index.isSome())) {
+ return *child1Index < *child2Index ? -1 : 1;
+ }
+ // XXX Keep the odd traditional behavior for now.
+ return child1Index.isNothing() && child2Index.isSome() ? -1 : 1;
+ }
+ parent = child1;
+ }
+
+ // The parent chains never differed, so one of the nodes is an ancestor of
+ // the other
+
+ NS_ASSERTION(!pos1 || !pos2,
+ "should have run out of parent chain for one of the nodes");
+
+ if (!pos1) {
+ const nsINode* child2 = parents2.ElementAt(--pos2);
+ const Maybe<uint32_t> child2Index = parent->ComputeIndexOf(child2);
+ if (MOZ_UNLIKELY(NS_WARN_IF(child2Index.isNothing()))) {
+ return 1;
+ }
+ return aOffset1 <= *child2Index ? -1 : 1;
+ }
+
+ const nsINode* child1 = parents1.ElementAt(--pos1);
+ const Maybe<uint32_t> child1Index =
+ aParent1Cache ? aParent1Cache->ComputeIndexOf(parent, child1)
+ : parent->ComputeIndexOf(child1);
+ if (MOZ_UNLIKELY(NS_WARN_IF(child1Index.isNothing()))) {
+ return -1;
+ }
+ return *child1Index < aOffset2 ? -1 : 1;
+}
+
+/* static */
+BrowserParent* nsContentUtils::GetCommonBrowserParentAncestor(
+ BrowserParent* aBrowserParent1, BrowserParent* aBrowserParent2) {
+ return GetCommonAncestorInternal(
+ aBrowserParent1, aBrowserParent2, [](BrowserParent* aBrowserParent) {
+ return aBrowserParent->GetBrowserBridgeParent()
+ ? aBrowserParent->GetBrowserBridgeParent()->Manager()
+ : nullptr;
+ });
+}
+
+/* static */
+Element* nsContentUtils::GetTargetElement(Document* aDocument,
+ const nsAString& aAnchorName) {
+ MOZ_ASSERT(aDocument);
+
+ if (aAnchorName.IsEmpty()) {
+ return nullptr;
+ }
+ // 1. If there is an element in the document tree that has an ID equal to
+ // fragment, then return the first such element in tree order.
+ if (Element* el = aDocument->GetElementById(aAnchorName)) {
+ return el;
+ }
+
+ // 2. If there is an a element in the document tree that has a name
+ // attribute whose value is equal to fragment, then return the first such
+ // element in tree order.
+ //
+ // FIXME(emilio): Why the different code-paths for HTML and non-HTML docs?
+ if (aDocument->IsHTMLDocument()) {
+ nsCOMPtr<nsINodeList> list = aDocument->GetElementsByName(aAnchorName);
+ // Loop through the named nodes looking for the first anchor
+ uint32_t length = list->Length();
+ for (uint32_t i = 0; i < length; i++) {
+ nsIContent* node = list->Item(i);
+ if (node->IsHTMLElement(nsGkAtoms::a)) {
+ return node->AsElement();
+ }
+ }
+ } else {
+ constexpr auto nameSpace = u"http://www.w3.org/1999/xhtml"_ns;
+ // Get the list of anchor elements
+ nsCOMPtr<nsINodeList> list =
+ aDocument->GetElementsByTagNameNS(nameSpace, u"a"_ns);
+ // Loop through the anchors looking for the first one with the given name.
+ for (uint32_t i = 0; true; i++) {
+ nsIContent* node = list->Item(i);
+ if (!node) { // End of list
+ break;
+ }
+
+ // Compare the name attribute
+ if (node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ aAnchorName, eCaseMatters)) {
+ return node->AsElement();
+ }
+ }
+ }
+
+ // 3. Return null.
+ return nullptr;
+}
+
+/* static */
+template <typename FPT, typename FRT, typename SPT, typename SRT>
+Maybe<int32_t> nsContentUtils::ComparePoints(
+ const RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const RangeBoundaryBase<SPT, SRT>& aSecondBoundary) {
+ if (!aFirstBoundary.IsSet() || !aSecondBoundary.IsSet()) {
+ return Nothing{};
+ }
+
+ bool disconnected{false};
+ const int32_t order =
+ ComparePoints_Deprecated(aFirstBoundary, aSecondBoundary, &disconnected);
+
+ if (disconnected) {
+ return Nothing{};
+ }
+
+ return Some(order);
+}
+
+/* static */
+template <typename FPT, typename FRT, typename SPT, typename SRT>
+int32_t nsContentUtils::ComparePoints_Deprecated(
+ const RangeBoundaryBase<FPT, FRT>& aFirstBoundary,
+ const RangeBoundaryBase<SPT, SRT>& aSecondBoundary, bool* aDisconnected) {
+ if (NS_WARN_IF(!aFirstBoundary.IsSet()) ||
+ NS_WARN_IF(!aSecondBoundary.IsSet())) {
+ return -1;
+ }
+ // XXX Re-implement this without calling `Offset()` as far as possible,
+ // and the other overload should be an alias of this.
+ return ComparePoints_Deprecated(
+ aFirstBoundary.Container(),
+ *aFirstBoundary.Offset(
+ RangeBoundaryBase<FPT, FRT>::OffsetFilter::kValidOrInvalidOffsets),
+ aSecondBoundary.Container(),
+ *aSecondBoundary.Offset(
+ RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOrInvalidOffsets),
+ aDisconnected);
+}
+
+inline bool IsCharInSet(const char* aSet, const char16_t aChar) {
+ char16_t ch;
+ while ((ch = *aSet)) {
+ if (aChar == char16_t(ch)) {
+ return true;
+ }
+ ++aSet;
+ }
+ return false;
+}
+
+/**
+ * This method strips leading/trailing chars, in given set, from string.
+ */
+
+// static
+const nsDependentSubstring nsContentUtils::TrimCharsInSet(
+ const char* aSet, const nsAString& aValue) {
+ nsAString::const_iterator valueCurrent, valueEnd;
+
+ aValue.BeginReading(valueCurrent);
+ aValue.EndReading(valueEnd);
+
+ // Skip characters in the beginning
+ while (valueCurrent != valueEnd) {
+ if (!IsCharInSet(aSet, *valueCurrent)) {
+ break;
+ }
+ ++valueCurrent;
+ }
+
+ if (valueCurrent != valueEnd) {
+ for (;;) {
+ --valueEnd;
+ if (!IsCharInSet(aSet, *valueEnd)) {
+ break;
+ }
+ }
+ ++valueEnd; // Step beyond the last character we want in the value.
+ }
+
+ // valueEnd should point to the char after the last to copy
+ return Substring(valueCurrent, valueEnd);
+}
+
+/**
+ * This method strips leading and trailing whitespace from a string.
+ */
+
+// static
+template <bool IsWhitespace(char16_t)>
+const nsDependentSubstring nsContentUtils::TrimWhitespace(const nsAString& aStr,
+ bool aTrimTrailing) {
+ nsAString::const_iterator start, end;
+
+ aStr.BeginReading(start);
+ aStr.EndReading(end);
+
+ // Skip whitespace characters in the beginning
+ while (start != end && IsWhitespace(*start)) {
+ ++start;
+ }
+
+ if (aTrimTrailing) {
+ // Skip whitespace characters in the end.
+ while (end != start) {
+ --end;
+
+ if (!IsWhitespace(*end)) {
+ // Step back to the last non-whitespace character.
+ ++end;
+
+ break;
+ }
+ }
+ }
+
+ // Return a substring for the string w/o leading and/or trailing
+ // whitespace
+
+ return Substring(start, end);
+}
+
+// Declaring the templates we are going to use avoid linking issues without
+// inlining the method. Considering there is not so much spaces checking
+// methods we can consider this to be better than inlining.
+template const nsDependentSubstring
+nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(const nsAString&, bool);
+template const nsDependentSubstring nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespace>(const nsAString&, bool);
+template const nsDependentSubstring nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespaceOrNBSP>(const nsAString&, bool);
+
+static inline void KeyAppendSep(nsACString& aKey) {
+ if (!aKey.IsEmpty()) {
+ aKey.Append('>');
+ }
+}
+
+static inline void KeyAppendString(const nsAString& aString, nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ // Could escape separator here if collisions happen. > is not a legal char
+ // for a name or type attribute, so we should be safe avoiding that extra
+ // work.
+
+ AppendUTF16toUTF8(aString, aKey);
+}
+
+static inline void KeyAppendString(const nsACString& aString,
+ nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ // Could escape separator here if collisions happen. > is not a legal char
+ // for a name or type attribute, so we should be safe avoiding that extra
+ // work.
+
+ aKey.Append(aString);
+}
+
+static inline void KeyAppendInt(int32_t aInt, nsACString& aKey) {
+ KeyAppendSep(aKey);
+
+ aKey.AppendInt(aInt);
+}
+
+static inline bool IsAutocompleteOff(const nsIContent* aContent) {
+ return aContent->IsElement() &&
+ aContent->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::autocomplete, u"off"_ns,
+ eIgnoreCase);
+}
+
+/*static*/
+void nsContentUtils::GenerateStateKey(nsIContent* aContent, Document* aDocument,
+ nsACString& aKey) {
+ MOZ_ASSERT(aContent);
+
+ aKey.Truncate();
+
+ uint32_t partID = aDocument ? aDocument->GetPartID() : 0;
+
+ // Don't capture state for anonymous content
+ if (aContent->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+
+ if (IsAutocompleteOff(aContent)) {
+ return;
+ }
+
+ RefPtr<Document> doc = aContent->GetUncomposedDoc();
+
+ KeyAppendInt(partID, aKey); // first append a partID
+ bool generatedUniqueKey = false;
+
+ if (doc && doc->IsHTMLOrXHTML()) {
+ nsHTMLDocument* htmlDoc = doc->AsHTMLDocument();
+
+ // If we have a form control and can calculate form information, use that
+ // as the key - it is more reliable than just recording position in the
+ // DOM.
+ // XXXbz Is it, really? We have bugs on this, I think...
+ // Important to have a unique key, and tag/type/name may not be.
+ //
+ // The format of the key depends on whether the control has a form,
+ // and whether the element was parser inserted:
+ //
+ // [Has Form, Parser Inserted]:
+ // fp>type>FormNum>IndOfControlInForm>FormName>name
+ //
+ // [No Form, Parser Inserted]:
+ // dp>type>ControlNum>name
+ //
+ // [Has Form, Not Parser Inserted]:
+ // fn>type>IndOfFormInDoc>IndOfControlInForm>FormName>name
+ //
+ // [No Form, Not Parser Inserted]:
+ // dn>type>IndOfControlInDoc>name
+ //
+ // XXX We don't need to use index if name is there
+ // XXXbz We don't? Why not? I don't follow.
+ //
+ nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent));
+ if (control) {
+ // Get the control number if this was a parser inserted element from the
+ // network.
+ int32_t controlNumber =
+ control->GetParserInsertedControlNumberForStateKey();
+ bool parserInserted = controlNumber != -1;
+
+ RefPtr<nsContentList> htmlForms;
+ RefPtr<nsContentList> htmlFormControls;
+ if (!parserInserted) {
+ // Getting these lists is expensive, as we need to keep them up to date
+ // as the document loads, so we avoid it if we don't need them.
+ htmlDoc->GetFormsAndFormControls(getter_AddRefs(htmlForms),
+ getter_AddRefs(htmlFormControls));
+ }
+
+ // Append the control type
+ KeyAppendInt(int32_t(control->ControlType()), aKey);
+
+ // If in a form, add form name / index of form / index in form
+ HTMLFormElement* formElement = control->GetForm();
+ if (formElement) {
+ if (IsAutocompleteOff(formElement)) {
+ aKey.Truncate();
+ return;
+ }
+
+ // Append the form number, if this is a parser inserted control, or
+ // the index of the form in the document otherwise.
+ bool appendedForm = false;
+ if (parserInserted) {
+ MOZ_ASSERT(formElement->GetFormNumberForStateKey() != -1,
+ "when generating a state key for a parser inserted form "
+ "control we should have a parser inserted <form> element");
+ KeyAppendString("fp"_ns, aKey);
+ KeyAppendInt(formElement->GetFormNumberForStateKey(), aKey);
+ appendedForm = true;
+ } else {
+ KeyAppendString("fn"_ns, aKey);
+ int32_t index = htmlForms->IndexOf(formElement, false);
+ if (index <= -1) {
+ //
+ // XXX HACK this uses some state that was dumped into the document
+ // specifically to fix bug 138892. What we are trying to do is
+ // *guess* which form this control's state is found in, with the
+ // highly likely guess that the highest form parsed so far is the
+ // one. This code should not be on trunk, only branch.
+ //
+ index = htmlDoc->GetNumFormsSynchronous() - 1;
+ }
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ appendedForm = true;
+ }
+ }
+
+ if (appendedForm) {
+ // Append the index of the control in the form
+ int32_t index = formElement->IndexOfContent(aContent);
+
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ generatedUniqueKey = true;
+ }
+ }
+
+ // Append the form name
+ nsAutoString formName;
+ formElement->GetAttr(nsGkAtoms::name, formName);
+ KeyAppendString(formName, aKey);
+ } else {
+ // Not in a form. Append the control number, if this is a parser
+ // inserted control, or the index of the control in the document
+ // otherwise.
+ if (parserInserted) {
+ KeyAppendString("dp"_ns, aKey);
+ KeyAppendInt(control->GetParserInsertedControlNumberForStateKey(),
+ aKey);
+ generatedUniqueKey = true;
+ } else {
+ KeyAppendString("dn"_ns, aKey);
+ int32_t index = htmlFormControls->IndexOf(aContent, true);
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ generatedUniqueKey = true;
+ }
+ }
+
+ // Append the control name
+ nsAutoString name;
+ aContent->AsElement()->GetAttr(nsGkAtoms::name, name);
+ KeyAppendString(name, aKey);
+ }
+ }
+ }
+
+ if (!generatedUniqueKey) {
+ // Either we didn't have a form control or we aren't in an HTML document so
+ // we can't figure out form info. Append the tag name if it's an element
+ // to avoid restoring state for one type of element on another type.
+ if (aContent->IsElement()) {
+ KeyAppendString(nsDependentAtomString(aContent->NodeInfo()->NameAtom()),
+ aKey);
+ } else {
+ // Append a character that is not "d" or "f" to disambiguate from
+ // the case when we were a form control in an HTML document.
+ KeyAppendString("o"_ns, aKey);
+ }
+
+ // Now start at aContent and append the indices of it and all its ancestors
+ // in their containers. That should at least pin down its position in the
+ // DOM...
+ nsINode* parent = aContent->GetParentNode();
+ nsINode* content = aContent;
+ while (parent) {
+ KeyAppendInt(parent->ComputeIndexOf_Deprecated(content), aKey);
+ content = parent;
+ parent = content->GetParentNode();
+ }
+ }
+}
+
+// static
+nsIPrincipal* nsContentUtils::SubjectPrincipal(JSContext* aCx) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // As opposed to SubjectPrincipal(), we do in fact assume that
+ // we're in a realm here; anyone who calls this function in
+ // situations where that's not the case is doing it wrong.
+ JS::Realm* realm = js::GetContextRealm(aCx);
+ MOZ_ASSERT(realm);
+
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsIPrincipal* nsContentUtils::SubjectPrincipal() {
+ MOZ_ASSERT(IsInitialized());
+ MOZ_ASSERT(NS_IsMainThread());
+ JSContext* cx = GetCurrentJSContext();
+ if (!cx) {
+ MOZ_CRASH(
+ "Accessing the Subject Principal without an AutoJSAPI on the stack is "
+ "forbidden");
+ }
+
+ JS::Realm* realm = js::GetContextRealm(cx);
+
+ // When an AutoJSAPI is instantiated, we are in a null realm until the
+ // first JSAutoRealm, which is kind of a purgatory as far as permissions
+ // go. It would be nice to just hard-abort if somebody does a security check
+ // in this purgatory zone, but that would be too fragile, since it could be
+ // triggered by random IsCallerChrome() checks 20-levels deep.
+ //
+ // So we want to return _something_ here - and definitely not the System
+ // Principal, since that would make an AutoJSAPI a very dangerous thing to
+ // instantiate.
+ //
+ // The natural thing to return is a null principal. Ideally, we'd return a
+ // different null principal each time, to avoid any unexpected interactions
+ // when the principal accidentally gets inherited somewhere. But
+ // SubjectPrincipal doesn't return strong references, so there's no way to
+ // sanely manage the lifetime of multiple null principals.
+ //
+ // So we use a singleton null principal. To avoid it being accidentally
+ // inherited and becoming a "real" subject or object principal, we do a
+ // release-mode assert during realm creation against using this principal on
+ // an actual global.
+ if (!realm) {
+ return sNullSubjectPrincipal;
+ }
+
+ return SubjectPrincipal(cx);
+}
+
+// static
+nsIPrincipal* nsContentUtils::ObjectPrincipal(JSObject* aObj) {
+#ifdef DEBUG
+ JS::AssertObjectBelongsToCurrentThread(aObj);
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(!js::IsCrossCompartmentWrapper(aObj));
+
+ JS::Realm* realm = js::GetNonCCWObjectRealm(aObj);
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsresult nsContentUtils::NewURIWithDocumentCharset(nsIURI** aResult,
+ const nsAString& aSpec,
+ Document* aDocument,
+ nsIURI* aBaseURI) {
+ if (aDocument) {
+ return NS_NewURI(aResult, aSpec, aDocument->GetDocumentCharacterSet(),
+ aBaseURI);
+ }
+ return NS_NewURI(aResult, aSpec, nullptr, aBaseURI);
+}
+
+// static
+bool nsContentUtils::IsNameWithDash(nsAtom* aName) {
+ // A valid custom element name is a sequence of characters name which
+ // must match the PotentialCustomElementName production:
+ // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)*
+ const char16_t* name = aName->GetUTF16String();
+ uint32_t len = aName->GetLength();
+ bool hasDash = false;
+
+ if (!len || name[0] < 'a' || name[0] > 'z') {
+ return false;
+ }
+
+ uint32_t i = 1;
+ while (i < len) {
+ if (i + 1 < len && NS_IS_SURROGATE_PAIR(name[i], name[i + 1])) {
+ // Merged two 16-bit surrogate pairs into code point.
+ char32_t code = SURROGATE_TO_UCS4(name[i], name[i + 1]);
+
+ if (code < 0x10000 || code > 0xEFFFF) {
+ return false;
+ }
+
+ i += 2;
+ } else {
+ if (name[i] == '-') {
+ hasDash = true;
+ }
+
+ if (name[i] != '-' && name[i] != '.' && name[i] != '_' &&
+ name[i] != 0xB7 && (name[i] < '0' || name[i] > '9') &&
+ (name[i] < 'a' || name[i] > 'z') &&
+ (name[i] < 0xC0 || name[i] > 0xD6) &&
+ (name[i] < 0xF8 || name[i] > 0x37D) &&
+ (name[i] < 0x37F || name[i] > 0x1FFF) &&
+ (name[i] < 0x200C || name[i] > 0x200D) &&
+ (name[i] < 0x203F || name[i] > 0x2040) &&
+ (name[i] < 0x2070 || name[i] > 0x218F) &&
+ (name[i] < 0x2C00 || name[i] > 0x2FEF) &&
+ (name[i] < 0x3001 || name[i] > 0xD7FF) &&
+ (name[i] < 0xF900 || name[i] > 0xFDCF) &&
+ (name[i] < 0xFDF0 || name[i] > 0xFFFD)) {
+ return false;
+ }
+
+ i++;
+ }
+ }
+
+ return hasDash;
+}
+
+// static
+bool nsContentUtils::IsCustomElementName(nsAtom* aName, uint32_t aNameSpaceID) {
+ // Allow non-dashed names in XUL for XBL to Custom Element migrations.
+ if (aNameSpaceID == kNameSpaceID_XUL) {
+ return true;
+ }
+
+ bool hasDash = IsNameWithDash(aName);
+ if (!hasDash) {
+ return false;
+ }
+
+ // The custom element name must not be one of the following values:
+ // annotation-xml
+ // color-profile
+ // font-face
+ // font-face-src
+ // font-face-uri
+ // font-face-format
+ // font-face-name
+ // missing-glyph
+ return aName != nsGkAtoms::annotation_xml_ &&
+ aName != nsGkAtoms::colorProfile && aName != nsGkAtoms::font_face &&
+ aName != nsGkAtoms::font_face_src &&
+ aName != nsGkAtoms::font_face_uri &&
+ aName != nsGkAtoms::font_face_format &&
+ aName != nsGkAtoms::font_face_name && aName != nsGkAtoms::missingGlyph;
+}
+
+// static
+nsresult nsContentUtils::CheckQName(const nsAString& aQualifiedName,
+ bool aNamespaceAware,
+ const char16_t** aColon) {
+ const char* colon = nullptr;
+ const char16_t* begin = aQualifiedName.BeginReading();
+ const char16_t* end = aQualifiedName.EndReading();
+
+ int result = MOZ_XMLCheckQName(reinterpret_cast<const char*>(begin),
+ reinterpret_cast<const char*>(end),
+ aNamespaceAware, &colon);
+
+ if (!result) {
+ if (aColon) {
+ *aColon = reinterpret_cast<const char16_t*>(colon);
+ }
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+}
+
+// static
+nsresult nsContentUtils::SplitQName(const nsIContent* aNamespaceResolver,
+ const nsString& aQName, int32_t* aNamespace,
+ nsAtom** aLocalName) {
+ const char16_t* colon;
+ nsresult rv = nsContentUtils::CheckQName(aQName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (colon) {
+ const char16_t* end;
+ aQName.EndReading(end);
+ nsAutoString nameSpace;
+ rv = aNamespaceResolver->LookupNamespaceURIInternal(
+ Substring(aQName.get(), colon), nameSpace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aNamespace = nsNameSpaceManager::GetInstance()->GetNameSpaceID(
+ nameSpace, nsContentUtils::IsChromeDoc(aNamespaceResolver->OwnerDoc()));
+ if (*aNamespace == kNameSpaceID_Unknown) return NS_ERROR_FAILURE;
+
+ *aLocalName = NS_AtomizeMainThread(Substring(colon + 1, end)).take();
+ } else {
+ *aNamespace = kNameSpaceID_None;
+ *aLocalName = NS_AtomizeMainThread(aQName).take();
+ }
+ NS_ENSURE_TRUE(aLocalName, NS_ERROR_OUT_OF_MEMORY);
+ return NS_OK;
+}
+
+// static
+nsresult nsContentUtils::GetNodeInfoFromQName(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ nsNodeInfoManager* aNodeInfoManager, uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo) {
+ const nsString& qName = PromiseFlatString(aQualifiedName);
+ const char16_t* colon;
+ nsresult rv = nsContentUtils::CheckQName(qName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t nsID;
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsID);
+ if (colon) {
+ const char16_t* end;
+ qName.EndReading(end);
+
+ RefPtr<nsAtom> prefix = NS_AtomizeMainThread(Substring(qName.get(), colon));
+
+ rv = aNodeInfoManager->GetNodeInfo(Substring(colon + 1, end), prefix, nsID,
+ aNodeType, aNodeInfo);
+ } else {
+ rv = aNodeInfoManager->GetNodeInfo(aQualifiedName, nullptr, nsID, aNodeType,
+ aNodeInfo);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsContentUtils::IsValidNodeName((*aNodeInfo)->NameAtom(),
+ (*aNodeInfo)->GetPrefixAtom(),
+ (*aNodeInfo)->NamespaceID())
+ ? NS_OK
+ : NS_ERROR_DOM_NAMESPACE_ERR;
+}
+
+// static
+void nsContentUtils::SplitExpatName(const char16_t* aExpatName,
+ nsAtom** aPrefix, nsAtom** aLocalName,
+ int32_t* aNameSpaceID) {
+ /**
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ *
+ * and we use 0xFFFF for the <separator>.
+ *
+ */
+
+ const char16_t* uriEnd = nullptr;
+ const char16_t* nameEnd = nullptr;
+ const char16_t* pos;
+ for (pos = aExpatName; *pos; ++pos) {
+ if (*pos == 0xFFFF) {
+ if (uriEnd) {
+ nameEnd = pos;
+ } else {
+ uriEnd = pos;
+ }
+ }
+ }
+
+ const char16_t* nameStart;
+ if (uriEnd) {
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ nsDependentSubstring(aExpatName, uriEnd), *aNameSpaceID);
+
+ nameStart = (uriEnd + 1);
+ if (nameEnd) {
+ const char16_t* prefixStart = nameEnd + 1;
+ *aPrefix = NS_AtomizeMainThread(Substring(prefixStart, pos)).take();
+ } else {
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ } else {
+ *aNameSpaceID = kNameSpaceID_None;
+ nameStart = aExpatName;
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ *aLocalName = NS_AtomizeMainThread(Substring(nameStart, nameEnd)).take();
+}
+
+// static
+PresShell* nsContentUtils::GetPresShellForContent(const nsIContent* aContent) {
+ Document* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ return doc->GetPresShell();
+}
+
+// static
+nsPresContext* nsContentUtils::GetContextForContent(
+ const nsIContent* aContent) {
+ PresShell* presShell = GetPresShellForContent(aContent);
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetPresContext();
+}
+
+// static
+bool nsContentUtils::CanLoadImage(nsIURI* aURI, nsINode* aNode,
+ Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aLoadingDocument, "Must have a document");
+ MOZ_ASSERT(aLoadingPrincipal, "Must have a loading principal");
+
+ nsresult rv;
+
+ auto appType = nsIDocShell::APP_TYPE_UNKNOWN;
+
+ {
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
+ aLoadingDocument->GetDocShell();
+ if (docShellTreeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShellTreeItem->GetInProcessRootTreeItem(getter_AddRefs(root));
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(root));
+
+ if (docShell) {
+ appType = docShell->GetAppType();
+ }
+ }
+ }
+
+ if (appType != nsIDocShell::APP_TYPE_EDITOR) {
+ // Editor apps get special treatment here, editors can load images
+ // from anywhere. This allows editor to insert images from file://
+ // into documents that are being edited.
+ rv = sSecurityManager->CheckLoadURIWithPrincipal(
+ aLoadingPrincipal, aURI, nsIScriptSecurityManager::ALLOW_CHROME,
+ aLoadingDocument->InnerWindowID());
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal,
+ aLoadingPrincipal, // triggering principal
+ aNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+
+ rv = NS_CheckContentLoadPolicy(aURI, secCheckLoadInfo, &decision,
+ GetContentPolicy());
+
+ return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(decision);
+}
+
+// static
+bool nsContentUtils::IsInPrivateBrowsing(const Document* aDoc) {
+ if (!aDoc) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup();
+ // See duplicated code below in IsInPrivateBrowsing(nsILoadGroup*)
+ // and Document::Reset/ResetToURI
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (loadContext) {
+ return loadContext->UsePrivateBrowsing();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = aDoc->GetChannel();
+ return channel && NS_UsePrivateBrowsing(channel);
+}
+
+// static
+bool nsContentUtils::IsInPrivateBrowsing(nsILoadGroup* aLoadGroup) {
+ if (!aLoadGroup) {
+ return false;
+ }
+ bool isPrivate = false;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ isPrivate = loadContext && loadContext->UsePrivateBrowsing();
+ }
+ return isPrivate;
+}
+
+// FIXME(emilio): This is (effectively) almost but not quite the same as
+// Document::ShouldLoadImages(), which one is right?
+bool nsContentUtils::DocumentInactiveForImageLoads(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+ if (IsChromeDoc(aDocument) || aDocument->IsResourceDoc() ||
+ aDocument->IsStaticDocument()) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(aDocument->GetScopeObject());
+ return !win || !win->GetDocShell();
+}
+
+imgLoader* nsContentUtils::GetImgLoaderForDocument(Document* aDoc) {
+ NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aDoc), nullptr);
+
+ if (!aDoc) {
+ return imgLoader::NormalLoader();
+ }
+ bool isPrivate = IsInPrivateBrowsing(aDoc);
+ return isPrivate ? imgLoader::PrivateBrowsingLoader()
+ : imgLoader::NormalLoader();
+}
+
+// static
+imgLoader* nsContentUtils::GetImgLoaderForChannel(nsIChannel* aChannel,
+ Document* aContext) {
+ NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aContext), nullptr);
+
+ if (!aChannel) {
+ return imgLoader::NormalLoader();
+ }
+ nsCOMPtr<nsILoadContext> context;
+ NS_QueryNotificationCallbacks(aChannel, context);
+ return context && context->UsePrivateBrowsing()
+ ? imgLoader::PrivateBrowsingLoader()
+ : imgLoader::NormalLoader();
+}
+
+// static
+int32_t nsContentUtils::CORSModeToLoadImageFlags(mozilla::CORSMode aMode) {
+ switch (aMode) {
+ case CORS_ANONYMOUS:
+ return imgILoader::LOAD_CORS_ANONYMOUS;
+ case CORS_USE_CREDENTIALS:
+ return imgILoader::LOAD_CORS_USE_CREDENTIALS;
+ default:
+ return 0;
+ }
+}
+
+// static
+nsresult nsContentUtils::LoadImage(
+ nsIURI* aURI, nsINode* aContext, Document* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
+ nsIReferrerInfo* aReferrerInfo, imgINotificationObserver* aObserver,
+ int32_t aLoadFlags, const nsAString& initiatorType,
+ imgRequestProxy** aRequest, nsContentPolicyType aContentPolicyType,
+ bool aUseUrgentStartForChannel, bool aLinkPreload,
+ uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aContext, "Must have a context");
+ MOZ_ASSERT(aLoadingDocument, "Must have a document");
+ MOZ_ASSERT(aLoadingPrincipal, "Must have a principal");
+ MOZ_ASSERT(aRequest, "Null out param");
+
+ imgLoader* imgLoader = GetImgLoaderForDocument(aLoadingDocument);
+ if (!imgLoader) {
+ // nothing we can do here
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aLoadingDocument->GetDocumentLoadGroup();
+
+ nsIURI* documentURI = aLoadingDocument->GetDocumentURI();
+
+ NS_ASSERTION(loadGroup || aLoadingDocument->IsSVGGlyphsDocument(),
+ "Could not get loadgroup; onload may fire too early");
+
+ // XXXbz using "documentURI" for the initialDocumentURI is not quite
+ // right, but the best we can do here...
+ return imgLoader->LoadImage(aURI, /* uri to load */
+ documentURI, /* initialDocumentURI */
+ aReferrerInfo, /* referrerInfo */
+ aLoadingPrincipal, /* loading principal */
+ aRequestContextID, /* request context ID */
+ loadGroup, /* loadgroup */
+ aObserver, /* imgINotificationObserver */
+ aContext, /* loading context */
+ aLoadingDocument, /* uniquification key */
+ aLoadFlags, /* load flags */
+ nullptr, /* cache key */
+ aContentPolicyType, /* content policy type */
+ initiatorType, /* the load initiator */
+ aUseUrgentStartForChannel, /* urgent-start flag */
+ aLinkPreload, /* <link preload> initiator */
+ aEarlyHintPreloaderId, aRequest);
+}
+
+// static
+already_AddRefed<imgIContainer> nsContentUtils::GetImageFromContent(
+ nsIImageLoadingContent* aContent, imgIRequest** aRequest) {
+ if (aRequest) {
+ *aRequest = nullptr;
+ }
+
+ NS_ENSURE_TRUE(aContent, nullptr);
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ aContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (!imgRequest) {
+ return nullptr;
+ }
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ imgRequest->GetImage(getter_AddRefs(imgContainer));
+
+ if (!imgContainer) {
+ return nullptr;
+ }
+
+ if (aRequest) {
+ // If the consumer wants the request, verify it has actually loaded
+ // successfully.
+ uint32_t imgStatus;
+ imgRequest->GetImageStatus(&imgStatus);
+ if (imgStatus & imgIRequest::STATUS_FRAME_COMPLETE &&
+ !(imgStatus & imgIRequest::STATUS_ERROR)) {
+ imgRequest.swap(*aRequest);
+ }
+ }
+
+ return imgContainer.forget();
+}
+
+static bool IsLinkWithURI(const nsIContent& aContent) {
+ const auto* element = Element::FromNode(aContent);
+ if (!element || !element->IsLink()) {
+ return false;
+ }
+ nsCOMPtr<nsIURI> absURI = element->GetHrefURI();
+ return !!absURI;
+}
+
+static bool HasImageRequest(nsIContent& aContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageContent(do_QueryInterface(&aContent));
+ if (!imageContent) {
+ return false;
+ }
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+
+ // XXXbz It may be draggable even if the request resulted in an error. Why?
+ // Not sure; that's what the old nsContentAreaDragDrop/nsFrame code did.
+ return !!imgRequest;
+}
+
+static Maybe<bool> DraggableOverride(const nsIContent& aContent) {
+ if (auto* el = nsGenericHTMLElement::FromNode(aContent)) {
+ if (el->Draggable()) {
+ return Some(true);
+ }
+
+ if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_false, eIgnoreCase)) {
+ return Some(false);
+ }
+ }
+ if (aContent.IsSVGElement()) {
+ return Some(false);
+ }
+ return Nothing();
+}
+
+// static
+bool nsContentUtils::ContentIsDraggable(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+
+ if (auto draggable = DraggableOverride(*aContent)) {
+ return *draggable;
+ }
+
+ // special handling for content area image and link dragging
+ return HasImageRequest(*aContent) || IsLinkWithURI(*aContent);
+}
+
+// static
+bool nsContentUtils::IsDraggableImage(nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ return HasImageRequest(*aContent) &&
+ DraggableOverride(*aContent).valueOr(true);
+}
+
+// static
+bool nsContentUtils::IsDraggableLink(const nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+ return IsLinkWithURI(*aContent) && DraggableOverride(*aContent).valueOr(true);
+}
+
+// static
+nsresult nsContentUtils::QNameChanged(mozilla::dom::NodeInfo* aNodeInfo,
+ nsAtom* aName,
+ mozilla::dom::NodeInfo** aResult) {
+ nsNodeInfoManager* niMgr = aNodeInfo->NodeInfoManager();
+
+ *aResult = niMgr
+ ->GetNodeInfo(aName, nullptr, aNodeInfo->NamespaceID(),
+ aNodeInfo->NodeType(), aNodeInfo->GetExtraName())
+ .take();
+ return NS_OK;
+}
+
+static bool TestSitePerm(nsIPrincipal* aPrincipal, const nsACString& aType,
+ uint32_t aPerm, bool aExactHostMatch) {
+ if (!aPrincipal) {
+ // We always deny (i.e. don't allow) the permission if we don't have a
+ // principal.
+ return aPerm != nsIPermissionManager::ALLOW_ACTION;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ components::PermissionManager::Service();
+ NS_ENSURE_TRUE(permMgr, false);
+
+ uint32_t perm;
+ nsresult rv;
+ if (aExactHostMatch) {
+ rv = permMgr->TestExactPermissionFromPrincipal(aPrincipal, aType, &perm);
+ } else {
+ rv = permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &perm);
+ }
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return perm == aPerm;
+}
+
+bool nsContentUtils::IsSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION,
+ false);
+}
+
+bool nsContentUtils::IsSitePermDeny(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION,
+ false);
+}
+
+bool nsContentUtils::IsExactSitePermAllow(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION,
+ true);
+}
+
+bool nsContentUtils::IsExactSitePermDeny(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION,
+ true);
+}
+
+bool nsContentUtils::HasSitePerm(nsIPrincipal* aPrincipal,
+ const nsACString& aType) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ components::PermissionManager::Service();
+ NS_ENSURE_TRUE(permMgr, false);
+
+ uint32_t perm;
+ nsresult rv = permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &perm);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return perm != nsIPermissionManager::UNKNOWN_ACTION;
+}
+
+static const char* gEventNames[] = {"event"};
+static const char* gSVGEventNames[] = {"evt"};
+// for b/w compat, the first name to onerror is still 'event', even though it
+// is actually the error message
+static const char* gOnErrorNames[] = {"event", "source", "lineno", "colno",
+ "error"};
+
+// static
+void nsContentUtils::GetEventArgNames(int32_t aNameSpaceID, nsAtom* aEventName,
+ bool aIsForWindow, uint32_t* aArgCount,
+ const char*** aArgArray) {
+#define SET_EVENT_ARG_NAMES(names) \
+ *aArgCount = sizeof(names) / sizeof(names[0]); \
+ *aArgArray = names;
+
+ // JSEventHandler is what does the arg magic for onerror, and it does
+ // not seem to take the namespace into account. So we let onerror in all
+ // namespaces get the 3 arg names.
+ if (aEventName == nsGkAtoms::onerror && aIsForWindow) {
+ SET_EVENT_ARG_NAMES(gOnErrorNames);
+ } else if (aNameSpaceID == kNameSpaceID_SVG) {
+ SET_EVENT_ARG_NAMES(gSVGEventNames);
+ } else {
+ SET_EVENT_ARG_NAMES(gEventNames);
+ }
+}
+
+// Note: The list of content bundles in nsStringBundle.cpp should be updated
+// whenever entries are added or removed from this list.
+static const char* gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT] = {
+ // Must line up with the enum values in |PropertiesFile| enum.
+ "chrome://global/locale/css.properties",
+ "chrome://global/locale/xul.properties",
+ "chrome://global/locale/layout_errors.properties",
+ "chrome://global/locale/layout/HtmlForm.properties",
+ "chrome://global/locale/printing.properties",
+ "chrome://global/locale/dom/dom.properties",
+ "chrome://global/locale/layout/htmlparser.properties",
+ "chrome://global/locale/svg/svg.properties",
+ "chrome://branding/locale/brand.properties",
+ "chrome://global/locale/commonDialogs.properties",
+ "chrome://global/locale/mathml/mathml.properties",
+ "chrome://global/locale/security/security.properties",
+ "chrome://necko/locale/necko.properties",
+ "resource://gre/res/locale/layout/HtmlForm.properties",
+ "resource://gre/res/locale/dom/dom.properties"};
+
+/* static */
+nsresult nsContentUtils::EnsureStringBundle(PropertiesFile aFile) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "Should not create bundles off main thread.");
+ if (!sStringBundles[aFile]) {
+ if (!sStringBundleService) {
+ nsresult rv =
+ CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ RefPtr<nsIStringBundle> bundle;
+ MOZ_TRY(sStringBundleService->CreateBundle(gPropertiesFiles[aFile],
+ getter_AddRefs(bundle)));
+ sStringBundles[aFile] = bundle.forget();
+ }
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::AsyncPrecreateStringBundles() {
+ // We only ever want to pre-create bundles in the parent process.
+ //
+ // All nsContentUtils bundles are shared between the parent and child
+ // precesses, and the shared memory regions that back them *must* be created
+ // in the parent, and then sent to all children.
+ //
+ // If we attempt to create a bundle in the child before its memory region is
+ // available, we need to create a temporary non-shared bundle, and later
+ // replace that with the shared memory copy. So attempting to pre-load in the
+ // child is wasteful and unnecessary.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ for (uint32_t bundleIndex = 0; bundleIndex < PropertiesFile_COUNT;
+ ++bundleIndex) {
+ nsresult rv = NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction("AsyncPrecreateStringBundles",
+ [bundleIndex]() {
+ PropertiesFile file =
+ static_cast<PropertiesFile>(bundleIndex);
+ EnsureStringBundle(file);
+ nsIStringBundle* bundle = sStringBundles[file];
+ bundle->AsyncPreload();
+ }),
+ EventQueuePriority::Idle);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+}
+
+/* static */
+bool nsContentUtils::SpoofLocaleEnglish() {
+ // 0 - will prompt
+ // 1 - don't spoof
+ // 2 - spoof
+ return StaticPrefs::privacy_spoof_english() == 2;
+}
+
+static nsContentUtils::PropertiesFile GetMaybeSpoofedPropertiesFile(
+ nsContentUtils::PropertiesFile aFile, const char* aKey,
+ Document* aDocument) {
+ // When we spoof English, use en-US properties in strings that are accessible
+ // by content.
+ bool spoofLocale = nsContentUtils::SpoofLocaleEnglish() &&
+ (!aDocument || !aDocument->AllowsL10n());
+ if (spoofLocale) {
+ switch (aFile) {
+ case nsContentUtils::eFORMS_PROPERTIES:
+ return nsContentUtils::eFORMS_PROPERTIES_en_US;
+ case nsContentUtils::eDOM_PROPERTIES:
+ return nsContentUtils::eDOM_PROPERTIES_en_US;
+ default:
+ break;
+ }
+ }
+ return aFile;
+}
+
+/* static */
+nsresult nsContentUtils::GetMaybeLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ Document* aDocument,
+ nsAString& aResult) {
+ return GetLocalizedString(
+ GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ nsAString& aResult) {
+ return FormatLocalizedString(aFile, aKey, {}, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::FormatMaybeLocalizedString(
+ PropertiesFile aFile, const char* aKey, Document* aDocument,
+ const nsTArray<nsString>& aParams, nsAString& aResult) {
+ return FormatLocalizedString(
+ GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aParams,
+ aResult);
+}
+
+class FormatLocalizedStringRunnable final : public WorkerMainThreadRunnable {
+ public:
+ FormatLocalizedStringRunnable(WorkerPrivate* aWorkerPrivate,
+ nsContentUtils::PropertiesFile aFile,
+ const char* aKey,
+ const nsTArray<nsString>& aParams,
+ nsAString& aLocalizedString)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "FormatLocalizedStringRunnable"_ns),
+ mFile(aFile),
+ mKey(aKey),
+ mParams(aParams),
+ mLocalizedString(aLocalizedString) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ mResult = nsContentUtils::FormatLocalizedString(mFile, mKey, mParams,
+ mLocalizedString);
+ Unused << NS_WARN_IF(NS_FAILED(mResult));
+ return true;
+ }
+
+ nsresult GetResult() const { return mResult; }
+
+ private:
+ const nsContentUtils::PropertiesFile mFile;
+ const char* mKey;
+ const nsTArray<nsString>& mParams;
+ nsresult mResult = NS_ERROR_FAILURE;
+ nsAString& mLocalizedString;
+};
+
+/* static */
+nsresult nsContentUtils::FormatLocalizedString(
+ PropertiesFile aFile, const char* aKey, const nsTArray<nsString>& aParams,
+ nsAString& aResult) {
+ if (!NS_IsMainThread()) {
+ // nsIStringBundle is thread-safe but its creation is not, and in particular
+ // we don't create and store nsIStringBundle objects in a thread-safe way.
+ //
+ // TODO(emilio): Maybe if we already have the right bundle created we could
+ // just call into it, but we should make sure that Shutdown() doesn't get
+ // called on the main thread when that happens which is a bit tricky to
+ // prove?
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (NS_WARN_IF(!workerPrivate)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto runnable = MakeRefPtr<FormatLocalizedStringRunnable>(
+ workerPrivate, aFile, aKey, aParams, aResult);
+
+ runnable->Dispatch(Canceling, IgnoreErrors());
+ return runnable->GetResult();
+ }
+
+ MOZ_TRY(EnsureStringBundle(aFile));
+ nsIStringBundle* bundle = sStringBundles[aFile];
+ if (aParams.IsEmpty()) {
+ return bundle->GetStringFromName(aKey, aResult);
+ }
+ return bundle->FormatStringFromName(aKey, aParams, aResult);
+}
+
+/* static */
+void nsContentUtils::LogSimpleConsoleError(const nsAString& aErrorText,
+ const nsACString& aCategory,
+ bool aFromPrivateWindow,
+ bool aFromChromeContext,
+ uint32_t aErrorFlags) {
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ if (scriptError) {
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (console && NS_SUCCEEDED(scriptError->Init(
+ aErrorText, u""_ns, u""_ns, 0, 0, aErrorFlags, aCategory,
+ aFromPrivateWindow, aFromChromeContext))) {
+ console->LogMessage(scriptError);
+ }
+ }
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsole(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ const Document* aDocument, PropertiesFile aFile, const char* aMessageName,
+ const nsTArray<nsString>& aParams, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber) {
+ nsresult rv;
+ nsAutoString errorText;
+ if (!aParams.IsEmpty()) {
+ rv = FormatLocalizedString(aFile, aMessageName, aParams, errorText);
+ } else {
+ rv = GetLocalizedString(aFile, aMessageName, errorText);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ReportToConsoleNonLocalized(errorText, aErrorFlags, aCategory,
+ aDocument, aURI, aSourceLine, aLineNumber,
+ aColumnNumber);
+}
+
+/* static */
+void nsContentUtils::ReportEmptyGetElementByIdArg(const Document* aDoc) {
+ ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
+ nsContentUtils::eDOM_PROPERTIES, "EmptyGetElementByIdParam");
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsoleNonLocalized(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, const Document* aDocument, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber,
+ MissingErrorLocationMode aLocationMode) {
+ uint64_t innerWindowID = 0;
+ if (aDocument) {
+ if (!aURI) {
+ aURI = aDocument->GetDocumentURI();
+ }
+ innerWindowID = aDocument->InnerWindowID();
+ }
+
+ return ReportToConsoleByWindowID(aErrorText, aErrorFlags, aCategory,
+ innerWindowID, aURI, aSourceLine,
+ aLineNumber, aColumnNumber, aLocationMode);
+}
+
+/* static */
+nsresult nsContentUtils::ReportToConsoleByWindowID(
+ const nsAString& aErrorText, uint32_t aErrorFlags,
+ const nsACString& aCategory, uint64_t aInnerWindowID, nsIURI* aURI,
+ const nsString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber,
+ MissingErrorLocationMode aLocationMode) {
+ nsresult rv;
+ if (!sConsoleService) { // only need to bother null-checking here
+ rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoString spec;
+ if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) {
+ JSContext* cx = GetCurrentJSContext();
+ if (cx) {
+ nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
+ }
+ }
+
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spec.IsEmpty()) {
+ rv = errorObject->InitWithWindowID(aErrorText,
+ spec, // file name
+ aSourceLine, aLineNumber, aColumnNumber,
+ aErrorFlags, aCategory, aInnerWindowID);
+ } else {
+ rv = errorObject->InitWithSourceURI(aErrorText, aURI, aSourceLine,
+ aLineNumber, aColumnNumber, aErrorFlags,
+ aCategory, aInnerWindowID);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return sConsoleService->LogMessage(errorObject);
+}
+
+void nsContentUtils::LogMessageToConsole(const char* aMsg) {
+ if (!sConsoleService) { // only need to bother null-checking here
+ CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
+ if (!sConsoleService) {
+ return;
+ }
+ }
+ sConsoleService->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
+}
+
+bool nsContentUtils::IsChromeDoc(const Document* aDocument) {
+ return aDocument && aDocument->NodePrincipal() == sSystemPrincipal;
+}
+
+bool nsContentUtils::IsChildOfSameType(Document* aDoc) {
+ if (BrowsingContext* bc = aDoc->GetBrowsingContext()) {
+ return bc->GetParent();
+ }
+ return false;
+}
+
+bool nsContentUtils::IsPlainTextType(const nsACString& aContentType) {
+ // NOTE: if you add a type here, add it to the CONTENTDLF_CATEGORIES
+ // define in nsContentDLF.h as well.
+ return aContentType.EqualsLiteral(TEXT_PLAIN) ||
+ aContentType.EqualsLiteral(TEXT_CSS) ||
+ aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
+ aContentType.EqualsLiteral(TEXT_VTT) ||
+ aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
+ aContentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ aContentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON);
+}
+
+bool nsContentUtils::IsUtf8OnlyPlainTextType(const nsACString& aContentType) {
+ // NOTE: This must be a subset of the list in IsPlainTextType().
+ return aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON) ||
+ aContentType.EqualsLiteral(TEXT_VTT);
+}
+
+bool nsContentUtils::IsInChromeDocshell(const Document* aDocument) {
+ return aDocument && aDocument->IsInChromeDocShell();
+}
+
+// static
+nsIContentPolicy* nsContentUtils::GetContentPolicy() {
+ if (!sTriedToGetContentPolicy) {
+ CallGetService(NS_CONTENTPOLICY_CONTRACTID, &sContentPolicyService);
+ // It's OK to not have a content policy service
+ sTriedToGetContentPolicy = true;
+ }
+
+ return sContentPolicyService;
+}
+
+// static
+bool nsContentUtils::IsEventAttributeName(nsAtom* aName, int32_t aType) {
+ const char16_t* name = aName->GetUTF16String();
+ if (name[0] != 'o' || name[1] != 'n') {
+ return false;
+ }
+
+ EventNameMapping mapping;
+ return (sAtomEventTable->Get(aName, &mapping) && mapping.mType & aType);
+}
+
+// static
+EventMessage nsContentUtils::GetEventMessage(nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread(), "sAtomEventTable is not threadsafe");
+ if (aName) {
+ EventNameMapping mapping;
+ if (sAtomEventTable->Get(aName, &mapping)) {
+ return mapping.mMessage;
+ }
+ }
+
+ return eUnidentifiedEvent;
+}
+
+// static
+mozilla::EventClassID nsContentUtils::GetEventClassID(const nsAString& aName) {
+ EventNameMapping mapping;
+ if (sStringEventTable->Get(aName, &mapping)) return mapping.mEventClassID;
+
+ return eBasicEventClass;
+}
+
+nsAtom* nsContentUtils::GetEventMessageAndAtom(
+ const nsAString& aName, mozilla::EventClassID aEventClassID,
+ EventMessage* aEventMessage) {
+ MOZ_ASSERT(NS_IsMainThread(), "Our hashtables are not threadsafe");
+ EventNameMapping mapping;
+ if (sStringEventTable->Get(aName, &mapping)) {
+ *aEventMessage = mapping.mEventClassID == aEventClassID
+ ? mapping.mMessage
+ : eUnidentifiedEvent;
+ return mapping.mAtom;
+ }
+
+ // If we have cached lots of user defined event names, clear some of them.
+ if (sUserDefinedEvents->Length() > 127) {
+ while (sUserDefinedEvents->Length() > 64) {
+ nsAtom* first = sUserDefinedEvents->ElementAt(0);
+ sStringEventTable->Remove(Substring(nsDependentAtomString(first), 2));
+ sUserDefinedEvents->RemoveElementAt(0);
+ }
+ }
+
+ *aEventMessage = eUnidentifiedEvent;
+ RefPtr<nsAtom> atom = NS_AtomizeMainThread(u"on"_ns + aName);
+ sUserDefinedEvents->AppendElement(atom);
+ mapping.mAtom = atom;
+ mapping.mMessage = eUnidentifiedEvent;
+ mapping.mType = EventNameType_None;
+ mapping.mEventClassID = eBasicEventClass;
+ sStringEventTable->InsertOrUpdate(aName, mapping);
+ return mapping.mAtom;
+}
+
+// static
+EventMessage nsContentUtils::GetEventMessageAndAtomForListener(
+ const nsAString& aName, nsAtom** aOnName) {
+ MOZ_ASSERT(NS_IsMainThread(), "Our hashtables are not threadsafe");
+
+ // Check sStringEventTable for a matching entry. This will only fail for
+ // user-defined event types.
+ EventNameMapping mapping;
+ if (sStringEventTable->Get(aName, &mapping)) {
+ RefPtr<nsAtom> atom = mapping.mAtom;
+ atom.forget(aOnName);
+ return mapping.mMessage;
+ }
+
+ // sStringEventTable did not contain an entry for this event type string.
+ // Call GetEventMessageAndAtom, which will create an event type atom and
+ // cache it in sStringEventTable for future calls.
+ EventMessage msg = eUnidentifiedEvent;
+ RefPtr<nsAtom> atom = GetEventMessageAndAtom(aName, eBasicEventClass, &msg);
+ atom.forget(aOnName);
+ return msg;
+}
+
+static already_AddRefed<Event> GetEventWithTarget(
+ Document* aDoc, EventTarget* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
+ Trusted aTrusted, ErrorResult& aErrorResult) {
+ RefPtr<Event> event =
+ aDoc->CreateEvent(u"Events"_ns, CallerType::System, aErrorResult);
+ if (aErrorResult.Failed()) {
+ return nullptr;
+ }
+
+ event->InitEvent(aEventName, aCanBubble, aCancelable, aComposed);
+ event->SetTrusted(aTrusted == Trusted::eYes);
+
+ event->SetTarget(aTarget);
+
+ return event.forget();
+}
+
+// static
+nsresult nsContentUtils::DispatchTrustedEvent(
+ Document* aDoc, EventTarget* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
+ bool* aDefaultAction) {
+ MOZ_ASSERT(!aEventName.EqualsLiteral("input") &&
+ !aEventName.EqualsLiteral("beforeinput"),
+ "Use DispatchInputEvent() instead");
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ aComposed, Trusted::eYes, aDefaultAction);
+}
+
+// static
+nsresult nsContentUtils::DispatchUntrustedEvent(
+ Document* aDoc, EventTarget* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ Composed::eDefault, Trusted::eNo, aDefaultAction);
+}
+
+// static
+nsresult nsContentUtils::DispatchEvent(Document* aDoc, EventTarget* aTarget,
+ const nsAString& aEventName,
+ CanBubble aCanBubble,
+ Cancelable aCancelable,
+ Composed aComposed, Trusted aTrusted,
+ bool* aDefaultAction,
+ ChromeOnlyDispatch aOnlyChromeDispatch) {
+ if (!aDoc || !aTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ErrorResult err;
+ RefPtr<Event> event =
+ GetEventWithTarget(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ aComposed, aTrusted, err);
+ if (err.Failed()) {
+ return err.StealNSResult();
+ }
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch =
+ aOnlyChromeDispatch == ChromeOnlyDispatch::eYes;
+
+ bool doDefault = aTarget->DispatchEvent(*event, CallerType::System, err);
+ if (aDefaultAction) {
+ *aDefaultAction = doDefault;
+ }
+ return err.StealNSResult();
+}
+
+// static
+nsresult nsContentUtils::DispatchEvent(Document* aDoc, EventTarget* aTarget,
+ WidgetEvent& aEvent,
+ EventMessage aEventMessage,
+ CanBubble aCanBubble,
+ Cancelable aCancelable, Trusted aTrusted,
+ bool* aDefaultAction,
+ ChromeOnlyDispatch aOnlyChromeDispatch) {
+ MOZ_ASSERT_IF(aOnlyChromeDispatch == ChromeOnlyDispatch::eYes,
+ aTrusted == Trusted::eYes);
+
+ aEvent.mSpecifiedEventType = GetEventTypeFromMessage(aEventMessage);
+ aEvent.SetDefaultComposed();
+ aEvent.SetDefaultComposedInNativeAnonymousContent();
+
+ aEvent.mFlags.mBubbles = aCanBubble == CanBubble::eYes;
+ aEvent.mFlags.mCancelable = aCancelable == Cancelable::eYes;
+ aEvent.mFlags.mOnlyChromeDispatch =
+ aOnlyChromeDispatch == ChromeOnlyDispatch::eYes;
+
+ aEvent.mTarget = aTarget;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(aTarget, &aEvent, nullptr,
+ nullptr, &status);
+ if (aDefaultAction) {
+ *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
+ }
+ return rv;
+}
+
+// static
+nsresult nsContentUtils::DispatchInputEvent(Element* aEventTarget) {
+ return DispatchInputEvent(aEventTarget, mozilla::eEditorInput,
+ mozilla::EditorInputType::eUnknown, nullptr,
+ InputEventOptions());
+}
+
+// static
+nsresult nsContentUtils::DispatchInputEvent(
+ Element* aEventTargetElement, EventMessage aEventMessage,
+ EditorInputType aEditorInputType, EditorBase* aEditorBase,
+ InputEventOptions&& aOptions, nsEventStatus* aEventStatus /* = nullptr */) {
+ MOZ_ASSERT(aEventMessage == eEditorInput ||
+ aEventMessage == eEditorBeforeInput);
+
+ if (NS_WARN_IF(!aEventTargetElement)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // If this is called from editor, the instance should be set to aEditorBase.
+ // Otherwise, we need to look for an editor for aEventTargetElement.
+ // However, we don't need to do it for HTMLEditor since nobody shouldn't
+ // dispatch "beforeinput" nor "input" event for HTMLEditor except HTMLEditor
+ // itself.
+ bool useInputEvent = false;
+ if (aEditorBase) {
+ useInputEvent = true;
+ } else if (HTMLTextAreaElement* textAreaElement =
+ HTMLTextAreaElement::FromNode(aEventTargetElement)) {
+ aEditorBase = textAreaElement->GetTextEditorWithoutCreation();
+ useInputEvent = true;
+ } else if (HTMLInputElement* inputElement =
+ HTMLInputElement::FromNode(aEventTargetElement)) {
+ if (inputElement->IsInputEventTarget()) {
+ aEditorBase = inputElement->GetTextEditorWithoutCreation();
+ useInputEvent = true;
+ }
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(!aEventTargetElement->IsTextControlElement(),
+ "The event target may have editor, but we've not known it yet.");
+ }
+#endif // #ifdef DEBUG
+
+ if (!useInputEvent) {
+ MOZ_ASSERT(aEventMessage == eEditorInput);
+ MOZ_ASSERT(aEditorInputType == EditorInputType::eUnknown);
+ MOZ_ASSERT(!aOptions.mNeverCancelable);
+ // Dispatch "input" event with Event instance.
+ WidgetEvent widgetEvent(true, eUnidentifiedEvent);
+ widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
+ widgetEvent.mFlags.mCancelable = false;
+ widgetEvent.mFlags.mComposed = true;
+ return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
+ widgetEvent, aEventStatus);
+ }
+
+ MOZ_ASSERT_IF(aEventMessage != eEditorBeforeInput,
+ !aOptions.mNeverCancelable);
+ MOZ_ASSERT_IF(
+ aEventMessage == eEditorBeforeInput && aOptions.mNeverCancelable,
+ aEditorInputType == EditorInputType::eInsertReplacementText);
+
+ nsCOMPtr<nsIWidget> widget;
+ if (aEditorBase) {
+ widget = aEditorBase->GetWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ Document* document = aEventTargetElement->OwnerDoc();
+ if (NS_WARN_IF(!document)) {
+ return NS_ERROR_FAILURE;
+ }
+ // If we're running xpcshell tests, we fail to get presShell here.
+ // Even in such case, we need to dispatch "input" event without widget.
+ PresShell* presShell = document->GetPresShell();
+ if (presShell) {
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ widget = presContext->GetRootWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // Dispatch "input" event with InputEvent instance.
+ InternalEditorInputEvent inputEvent(true, aEventMessage, widget);
+
+ inputEvent.mFlags.mCancelable =
+ !aOptions.mNeverCancelable && aEventMessage == eEditorBeforeInput &&
+ IsCancelableBeforeInputEvent(aEditorInputType);
+ MOZ_ASSERT(!inputEvent.mFlags.mCancelable || aEventStatus);
+
+ // If there is an editor, set isComposing to true when it has composition.
+ // Note that EditorBase::IsIMEComposing() may return false even when we
+ // need to set it to true.
+ // Otherwise, i.e., editor hasn't been created for the element yet,
+ // we should set isComposing to false since the element can never has
+ // composition without editor.
+ inputEvent.mIsComposing = aEditorBase && aEditorBase->GetComposition();
+
+ if (!aEditorBase || aEditorBase->IsTextEditor()) {
+ if (IsDataAvailableOnTextEditor(aEditorInputType)) {
+ inputEvent.mData = std::move(aOptions.mData);
+ MOZ_ASSERT(!inputEvent.mData.IsVoid(),
+ "inputEvent.mData shouldn't be void");
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
+ }
+#endif // #ifdef DEBUG
+ MOZ_ASSERT(
+ aOptions.mTargetRanges.IsEmpty(),
+ "Target ranges for <input> and <textarea> should always be empty");
+ } else {
+ MOZ_ASSERT(aEditorBase->IsHTMLEditor());
+ if (IsDataAvailableOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mData = std::move(aOptions.mData);
+ MOZ_ASSERT(!inputEvent.mData.IsVoid(),
+ "inputEvent.mData shouldn't be void");
+ } else {
+ MOZ_ASSERT(inputEvent.mData.IsVoid(), "inputEvent.mData should be void");
+ if (IsDataTransferAvailableOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mDataTransfer = std::move(aOptions.mDataTransfer);
+ MOZ_ASSERT(inputEvent.mDataTransfer,
+ "inputEvent.mDataTransfer shouldn't be nullptr");
+ MOZ_ASSERT(inputEvent.mDataTransfer->IsReadOnly(),
+ "inputEvent.mDataTransfer should be read only");
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(!inputEvent.mDataTransfer,
+ "inputEvent.mDataTransfer should be nullptr");
+ }
+#endif // #ifdef DEBUG
+ }
+ if (aEventMessage == eEditorBeforeInput &&
+ MayHaveTargetRangesOnHTMLEditor(aEditorInputType)) {
+ inputEvent.mTargetRanges = std::move(aOptions.mTargetRanges);
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(aOptions.mTargetRanges.IsEmpty(),
+ "Target ranges shouldn't be set for the dispatching event");
+ }
+#endif // #ifdef DEBUG
+ }
+
+ inputEvent.mInputType = aEditorInputType;
+
+ // If we cannot dispatch an event right now, we cannot make it cancelable.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ NS_ASSERTION(
+ !inputEvent.mFlags.mCancelable,
+ "Cancelable beforeinput event dispatcher should run when it's safe");
+ inputEvent.mFlags.mCancelable = false;
+ }
+ return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
+ inputEvent, aEventStatus);
+}
+
+nsresult nsContentUtils::DispatchChromeEvent(
+ Document* aDoc, EventTarget* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, bool* aDefaultAction) {
+ if (!aDoc || !aTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aDoc->GetWindow()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ EventTarget* piTarget = aDoc->GetWindow()->GetParentTarget();
+ if (!piTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ErrorResult err;
+ RefPtr<Event> event =
+ GetEventWithTarget(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ Composed::eDefault, Trusted::eYes, err);
+ if (err.Failed()) {
+ return err.StealNSResult();
+ }
+
+ bool defaultActionEnabled =
+ piTarget->DispatchEvent(*event, CallerType::System, err);
+ if (aDefaultAction) {
+ *aDefaultAction = defaultActionEnabled;
+ }
+ return err.StealNSResult();
+}
+
+void nsContentUtils::RequestFrameFocus(Element& aFrameElement, bool aCanRaise,
+ CallerType aCallerType) {
+ RefPtr<Element> target = &aFrameElement;
+ bool defaultAction = true;
+ if (aCanRaise) {
+ DispatchEventOnlyToChrome(target->OwnerDoc(), target,
+ u"framefocusrequested"_ns, CanBubble::eYes,
+ Cancelable::eYes, &defaultAction);
+ }
+ if (!defaultAction) {
+ return;
+ }
+
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return;
+ }
+
+ uint32_t flags = nsIFocusManager::FLAG_NOSCROLL;
+ if (aCanRaise) {
+ flags |= nsIFocusManager::FLAG_RAISE;
+ }
+
+ if (aCallerType == CallerType::NonSystem) {
+ flags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
+ }
+
+ fm->SetFocus(target, flags);
+}
+
+nsresult nsContentUtils::DispatchEventOnlyToChrome(
+ Document* aDoc, EventTarget* aTarget, const nsAString& aEventName,
+ CanBubble aCanBubble, Cancelable aCancelable, Composed aComposed,
+ bool* aDefaultAction) {
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ aComposed, Trusted::eYes, aDefaultAction,
+ ChromeOnlyDispatch::eYes);
+}
+
+/* static */
+Element* nsContentUtils::MatchElementId(nsIContent* aContent,
+ const nsAtom* aId) {
+ for (nsIContent* cur = aContent; cur; cur = cur->GetNextNode(aContent)) {
+ if (aId == cur->GetID()) {
+ return cur->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+Element* nsContentUtils::MatchElementId(nsIContent* aContent,
+ const nsAString& aId) {
+ MOZ_ASSERT(!aId.IsEmpty(), "Will match random elements");
+
+ // ID attrs are generally stored as atoms, so just atomize this up front
+ RefPtr<nsAtom> id(NS_Atomize(aId));
+ if (!id) {
+ // OOM, so just bail
+ return nullptr;
+ }
+
+ return MatchElementId(aContent, id);
+}
+
+/* static */
+void nsContentUtils::RegisterShutdownObserver(nsIObserver* aObserver) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ }
+}
+
+/* static */
+void nsContentUtils::UnregisterShutdownObserver(nsIObserver* aObserver) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+}
+
+/* static */
+bool nsContentUtils::HasNonEmptyAttr(const nsIContent* aContent,
+ int32_t aNameSpaceID, nsAtom* aName) {
+ static AttrArray::AttrValuesArray strings[] = {nsGkAtoms::_empty, nullptr};
+ return aContent->IsElement() &&
+ aContent->AsElement()->FindAttrValueIn(aNameSpaceID, aName, strings,
+ eCaseMatters) ==
+ AttrArray::ATTR_VALUE_NO_MATCH;
+}
+
+/* static */
+bool nsContentUtils::HasMutationListeners(nsINode* aNode, uint32_t aType,
+ nsINode* aTargetForSubtreeModified) {
+ Document* doc = aNode->OwnerDoc();
+
+ // global object will be null for documents that don't have windows.
+ nsPIDOMWindowInner* window = doc->GetInnerWindow();
+ // This relies on EventListenerManager::AddEventListener, which sets
+ // all mutation bits when there is a listener for DOMSubtreeModified event.
+ if (window && !window->HasMutationListeners(aType)) {
+ return false;
+ }
+
+ if (aNode->ChromeOnlyAccess() || aNode->IsInShadowTree()) {
+ return false;
+ }
+
+ doc->MayDispatchMutationEvent(aTargetForSubtreeModified);
+
+ // If we have a window, we can check it for mutation listeners now.
+ if (aNode->IsInUncomposedDoc()) {
+ nsCOMPtr<EventTarget> piTarget(do_QueryInterface(window));
+ if (piTarget) {
+ EventListenerManager* manager = piTarget->GetExistingListenerManager();
+ if (manager && manager->HasMutationListeners()) {
+ return true;
+ }
+ }
+ }
+
+ // If we have a window, we know a mutation listener is registered, but it
+ // might not be in our chain. If we don't have a window, we might have a
+ // mutation listener. Check quickly to see.
+ while (aNode) {
+ EventListenerManager* manager = aNode->GetExistingListenerManager();
+ if (manager && manager->HasMutationListeners()) {
+ return true;
+ }
+
+ aNode = aNode->GetParentNode();
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::HasMutationListeners(Document* aDocument, uint32_t aType) {
+ nsPIDOMWindowInner* window =
+ aDocument ? aDocument->GetInnerWindow() : nullptr;
+
+ // This relies on EventListenerManager::AddEventListener, which sets
+ // all mutation bits when there is a listener for DOMSubtreeModified event.
+ return !window || window->HasMutationListeners(aType);
+}
+
+void nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent) {
+ MOZ_ASSERT(aChild, "Missing child");
+ MOZ_ASSERT(aChild->GetParentNode() == aParent, "Wrong parent");
+ MOZ_ASSERT(aChild->OwnerDoc() == aParent->OwnerDoc(), "Wrong owner-doc");
+
+ // Having an explicit check here since it's an easy mistake to fall into,
+ // and there might be existing code with problems. We'd rather be safe
+ // than fire DOMNodeRemoved in all corner cases. We also rely on it for
+ // nsAutoScriptBlockerSuppressNodeRemoved.
+ if (!IsSafeToRunScript()) {
+ // This checks that IsSafeToRunScript is true since we don't want to fire
+ // events when that is false. We can't rely on EventDispatcher to assert
+ // this in this situation since most of the time there are no mutation
+ // event listeners, in which case we won't even attempt to dispatch events.
+ // However this also allows for two exceptions. First off, we don't assert
+ // if the mutation happens to native anonymous content since we never fire
+ // mutation events on such content anyway.
+ // Second, we don't assert if sDOMNodeRemovedSuppressCount is true since
+ // that is a know case when we'd normally fire a mutation event, but can't
+ // make that safe and so we suppress it at this time. Ideally this should
+ // go away eventually.
+ if (!aChild->IsInNativeAnonymousSubtree() &&
+ !sDOMNodeRemovedSuppressCount) {
+ NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe");
+ WarnScriptWasIgnored(aChild->OwnerDoc());
+ }
+ return;
+ }
+
+ {
+ Document* doc = aParent->OwnerDoc();
+ if (MOZ_UNLIKELY(doc->DevToolsWatchingDOMMutations()) &&
+ aChild->IsInComposedDoc() && !aChild->ChromeOnlyAccess()) {
+ DispatchChromeEvent(doc, aChild, u"devtoolschildremoved"_ns,
+ CanBubble::eNo, Cancelable::eNo);
+ }
+ }
+
+ if (HasMutationListeners(aChild, NS_EVENT_BITS_MUTATION_NODEREMOVED,
+ aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeRemoved);
+ mutation.mRelatedNode = aParent;
+
+ mozAutoSubtreeModified subtree(aParent->OwnerDoc(), aParent);
+ EventDispatcher::Dispatch(aChild, nullptr, &mutation);
+ }
+}
+
+void nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments() {
+ if (!sEventListenerManagersHash) {
+ return;
+ }
+
+ for (auto i = sEventListenerManagersHash->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<EventListenerManagerMapEntry*>(i.Get());
+ nsINode* n = static_cast<nsINode*>(entry->mListenerManager->GetTarget());
+ if (n && n->IsInComposedDoc() &&
+ nsCCUncollectableMarker::InGeneration(
+ n->OwnerDoc()->GetMarkedCCGeneration())) {
+ entry->mListenerManager->MarkForCC();
+ }
+ }
+}
+
+/* static */
+void nsContentUtils::TraverseListenerManager(
+ nsINode* aNode, nsCycleCollectionTraversalCallback& cb) {
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, just return.
+ return;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ CycleCollectionNoteChild(cb, entry->mListenerManager.get(),
+ "[via hash] mListenerManager");
+ }
+}
+
+EventListenerManager* nsContentUtils::GetListenerManagerForNode(
+ nsINode* aNode) {
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, don't bother creating an event listener
+ // manager.
+
+ return nullptr;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Add(aNode, fallible));
+
+ if (!entry) {
+ return nullptr;
+ }
+
+ if (!entry->mListenerManager) {
+ entry->mListenerManager = new EventListenerManager(aNode);
+
+ aNode->SetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ return entry->mListenerManager;
+}
+
+EventListenerManager* nsContentUtils::GetExistingListenerManagerForNode(
+ const nsINode* aNode) {
+ if (!aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ return nullptr;
+ }
+
+ if (!sEventListenerManagersHash) {
+ // We're already shut down, don't bother creating an event listener
+ // manager.
+
+ return nullptr;
+ }
+
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ return entry->mListenerManager;
+ }
+
+ return nullptr;
+}
+
+void nsContentUtils::AddEntryToDOMArenaTable(nsINode* aNode,
+ DOMArena* aDOMArena) {
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ MOZ_ASSERT_IF(sDOMArenaHashtable, !sDOMArenaHashtable->Contains(aNode));
+ MOZ_ASSERT(!aNode->HasFlag(NODE_KEEPS_DOMARENA));
+ if (!sDOMArenaHashtable) {
+ sDOMArenaHashtable =
+ new nsRefPtrHashtable<nsPtrHashKey<const nsINode>, dom::DOMArena>();
+ }
+ aNode->SetFlags(NODE_KEEPS_DOMARENA);
+ sDOMArenaHashtable->InsertOrUpdate(aNode, RefPtr<DOMArena>(aDOMArena));
+}
+
+already_AddRefed<DOMArena> nsContentUtils::TakeEntryFromDOMArenaTable(
+ const nsINode* aNode) {
+ MOZ_ASSERT(sDOMArenaHashtable->Contains(aNode));
+ MOZ_ASSERT(StaticPrefs::dom_arena_allocator_enabled_AtStartup());
+ RefPtr<DOMArena> arena;
+ sDOMArenaHashtable->Remove(aNode, getter_AddRefs(arena));
+ return arena.forget();
+}
+
+/* static */
+void nsContentUtils::RemoveListenerManager(nsINode* aNode) {
+ if (sEventListenerManagersHash) {
+ auto entry = static_cast<EventListenerManagerMapEntry*>(
+ sEventListenerManagersHash->Search(aNode));
+ if (entry) {
+ RefPtr<EventListenerManager> listenerManager;
+ listenerManager.swap(entry->mListenerManager);
+ // Remove the entry and *then* do operations that could cause further
+ // modification of sEventListenerManagersHash. See bug 334177.
+ sEventListenerManagersHash->RawRemove(entry);
+ if (listenerManager) {
+ listenerManager->Disconnect();
+ }
+ }
+ }
+}
+
+/* static */
+bool nsContentUtils::IsValidNodeName(nsAtom* aLocalName, nsAtom* aPrefix,
+ int32_t aNamespaceID) {
+ if (aNamespaceID == kNameSpaceID_Unknown) {
+ return false;
+ }
+
+ if (!aPrefix) {
+ // If the prefix is null, then either the QName must be xmlns or the
+ // namespace must not be XMLNS.
+ return (aLocalName == nsGkAtoms::xmlns) ==
+ (aNamespaceID == kNameSpaceID_XMLNS);
+ }
+
+ // If the prefix is non-null then the namespace must not be null.
+ if (aNamespaceID == kNameSpaceID_None) {
+ return false;
+ }
+
+ // If the namespace is the XMLNS namespace then the prefix must be xmlns,
+ // but the localname must not be xmlns.
+ if (aNamespaceID == kNameSpaceID_XMLNS) {
+ return aPrefix == nsGkAtoms::xmlns && aLocalName != nsGkAtoms::xmlns;
+ }
+
+ // If the namespace is not the XMLNS namespace then the prefix must not be
+ // xmlns.
+ // If the namespace is the XML namespace then the prefix can be anything.
+ // If the namespace is not the XML namespace then the prefix must not be xml.
+ return aPrefix != nsGkAtoms::xmlns &&
+ (aNamespaceID == kNameSpaceID_XML || aPrefix != nsGkAtoms::xml);
+}
+
+already_AddRefed<DocumentFragment> nsContentUtils::CreateContextualFragment(
+ nsINode* aContextNode, const nsAString& aFragment,
+ bool aPreventScriptExecution, ErrorResult& aRv) {
+ if (!aContextNode) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ // If we don't have a document here, we can't get the right security context
+ // for compiling event handlers... so just bail out.
+ RefPtr<Document> document = aContextNode->OwnerDoc();
+ bool isHTML = document->IsHTMLDocument();
+
+ if (isHTML) {
+ RefPtr<DocumentFragment> frag = new (document->NodeInfoManager())
+ DocumentFragment(document->NodeInfoManager());
+
+ Element* element = aContextNode->GetAsElementOrParentElement();
+ if (element && !element->IsHTMLElement(nsGkAtoms::html)) {
+ aRv = ParseFragmentHTML(
+ aFragment, frag, element->NodeInfo()->NameAtom(),
+ element->GetNameSpaceID(),
+ (document->GetCompatibilityMode() == eCompatibility_NavQuirks),
+ aPreventScriptExecution);
+ } else {
+ aRv = ParseFragmentHTML(
+ aFragment, frag, nsGkAtoms::body, kNameSpaceID_XHTML,
+ (document->GetCompatibilityMode() == eCompatibility_NavQuirks),
+ aPreventScriptExecution);
+ }
+
+ return frag.forget();
+ }
+
+ AutoTArray<nsString, 32> tagStack;
+ nsAutoString uriStr, nameStr;
+ for (Element* element : aContextNode->InclusiveAncestorsOfType<Element>()) {
+ nsString& tagName = *tagStack.AppendElement();
+ // It mostly doesn't actually matter what tag name we use here: XML doesn't
+ // have parsing that depends on the open tag stack, apart from namespace
+ // declarations. So this whole tagStack bit is just there to get the right
+ // namespace declarations to the XML parser. That said, the parser _is_
+ // going to create elements with the tag names we provide here, so we need
+ // to make sure they are not names that can trigger custom element
+ // constructors. Just make up a name that is never going to be a valid
+ // custom element name.
+ //
+ // The principled way to do this would probably be to add a new FromParser
+ // value and make sure we use it when creating the context elements, then
+ // make sure we teach all FromParser consumers (and in particular the custom
+ // element code) about it as needed. But right now the XML parser never
+ // actually uses FromParser values other than NOT_FROM_PARSER, and changing
+ // that is pretty complicated.
+ tagName.AssignLiteral("notacustomelement");
+
+ // see if we need to add xmlns declarations
+ uint32_t count = element->GetAttrCount();
+ bool setDefaultNamespace = false;
+ if (count > 0) {
+ uint32_t index;
+
+ for (index = 0; index < count; index++) {
+ const BorrowedAttrInfo info = element->GetAttrInfoAt(index);
+ const nsAttrName* name = info.mName;
+ if (name->NamespaceEquals(kNameSpaceID_XMLNS)) {
+ info.mValue->ToString(uriStr);
+
+ // really want something like nsXMLContentSerializer::SerializeAttr
+ tagName.AppendLiteral(" xmlns"); // space important
+ if (name->GetPrefix()) {
+ tagName.Append(char16_t(':'));
+ name->LocalName()->ToString(nameStr);
+ tagName.Append(nameStr);
+ } else {
+ setDefaultNamespace = true;
+ }
+ tagName.AppendLiteral(R"(=")");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+ }
+
+ if (!setDefaultNamespace) {
+ mozilla::dom::NodeInfo* info = element->NodeInfo();
+ if (!info->GetPrefixAtom() && info->NamespaceID() != kNameSpaceID_None) {
+ // We have no namespace prefix, but have a namespace ID. Push
+ // default namespace attr in, so that our kids will be in our
+ // namespace.
+ info->GetNamespaceURI(uriStr);
+ tagName.AppendLiteral(R"( xmlns=")");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+ }
+
+ RefPtr<DocumentFragment> frag;
+ aRv = ParseFragmentXML(aFragment, document, tagStack, aPreventScriptExecution,
+ -1, getter_AddRefs(frag));
+ return frag.forget();
+}
+
+/* static */
+void nsContentUtils::DropFragmentParsers() {
+ NS_IF_RELEASE(sHTMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentSink);
+}
+
+/* static */
+void nsContentUtils::XPCOMShutdown() { nsContentUtils::DropFragmentParsers(); }
+
+/* Helper function to compuate Sanitization Flags for ParseFramentHTML/XML */
+uint32_t computeSanitizationFlags(nsIPrincipal* aPrincipal, int32_t aFlags) {
+ uint32_t sanitizationFlags = 0;
+ if (aPrincipal->IsSystemPrincipal()) {
+ if (aFlags < 0) {
+ // if this is a chrome-privileged document and no explicit flags
+ // were passed, then use this sanitization flags.
+ sanitizationFlags = nsIParserUtils::SanitizerAllowStyle |
+ nsIParserUtils::SanitizerAllowComments |
+ nsIParserUtils::SanitizerDropForms |
+ nsIParserUtils::SanitizerLogRemovals;
+ } else {
+ // if the caller explicitly passes flags, then we use those
+ // flags but additionally drop forms.
+ sanitizationFlags = aFlags | nsIParserUtils::SanitizerDropForms;
+ }
+ } else if (aFlags >= 0) {
+ // aFlags by default is -1 and is only ever non equal to -1 if the
+ // caller of ParseFragmentHTML/ParseFragmentXML is
+ // ParserUtils::ParseFragment(). Only in that case we should use
+ // the sanitization flags passed within aFlags.
+ sanitizationFlags = aFlags;
+ }
+ return sanitizationFlags;
+}
+
+/* static */
+void nsContentUtils::SetHTMLUnsafe(FragmentOrElement* aTarget,
+ Element* aContext,
+ const nsAString& aSource) {
+ RefPtr<DocumentFragment> fragment;
+ {
+ MOZ_ASSERT(!sFragmentParsingActive,
+ "Re-entrant fragment parsing attempted.");
+ mozilla::AutoRestore<bool> guard(sFragmentParsingActive);
+ sFragmentParsingActive = true;
+ if (!sHTMLFragmentParser) {
+ NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
+ // Now sHTMLFragmentParser owns the object
+ }
+
+ nsAtom* contextLocalName = aContext->NodeInfo()->NameAtom();
+ int32_t contextNameSpaceID = aContext->GetNameSpaceID();
+
+ RefPtr<Document> doc = aTarget->OwnerDoc();
+ fragment = doc->CreateDocumentFragment();
+ nsresult rv = sHTMLFragmentParser->ParseFragment(
+ aSource, fragment, contextLocalName, contextNameSpaceID,
+ fragment->OwnerDoc()->GetCompatibilityMode() ==
+ eCompatibility_NavQuirks,
+ true, true);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to parse fragment for SetHTMLUnsafe");
+ }
+ }
+
+ aTarget->ReplaceChildren(fragment, IgnoreErrors());
+}
+
+/* static */
+nsresult nsContentUtils::ParseFragmentHTML(
+ const nsAString& aSourceBuffer, nsIContent* aTargetNode,
+ nsAtom* aContextLocalName, int32_t aContextNamespace, bool aQuirks,
+ bool aPreventScriptExecution, int32_t aFlags) {
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sHTMLFragmentParser) {
+ NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
+ // Now sHTMLFragmentParser owns the object
+ }
+
+ nsCOMPtr<nsIPrincipal> nodePrincipal = aTargetNode->NodePrincipal();
+
+#ifdef DEBUG
+ // aFlags should always be -1 unless the caller of ParseFragmentHTML
+ // is ParserUtils::ParseFragment() which is the only caller that intends
+ // sanitization. For all other callers we need to ensure to call
+ // AuditParsingOfHTMLXMLFragments.
+ if (aFlags < 0) {
+ DOMSecurityMonitor::AuditParsingOfHTMLXMLFragments(nodePrincipal,
+ aSourceBuffer);
+ }
+#endif
+
+ nsIContent* target = aTargetNode;
+
+ RefPtr<Document> doc = aTargetNode->OwnerDoc();
+ RefPtr<DocumentFragment> fragment;
+ // We sanitize if the fragment occurs in a system privileged
+ // context, an about: page, or if there are explicit sanitization flags.
+ // Please note that about:blank and about:srcdoc inherit the security
+ // context from the embedding context and hence are not loaded using
+ // an about: scheme principal.
+ bool shouldSanitize = nodePrincipal->IsSystemPrincipal() ||
+ nodePrincipal->SchemeIs("about") || aFlags >= 0;
+ if (shouldSanitize) {
+ if (!doc->IsLoadedAsData()) {
+ doc = nsContentUtils::CreateInertHTMLDocument(doc);
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ fragment =
+ new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
+ target = fragment;
+ }
+
+ nsresult rv = sHTMLFragmentParser->ParseFragment(
+ aSourceBuffer, target, aContextLocalName, aContextNamespace, aQuirks,
+ aPreventScriptExecution, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fragment) {
+ uint32_t sanitizationFlags =
+ computeSanitizationFlags(nodePrincipal, aFlags);
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+ nsTreeSanitizer sanitizer(sanitizationFlags);
+ sanitizer.Sanitize(fragment);
+
+ ErrorResult error;
+ aTargetNode->AppendChild(*fragment, error);
+ rv = error.StealNSResult();
+ }
+
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ParseDocumentHTML(
+ const nsAString& aSourceBuffer, Document* aTargetDocument,
+ bool aScriptingEnabledForNoscriptParsing) {
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sHTMLFragmentParser) {
+ NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
+ // Now sHTMLFragmentParser owns the object
+ }
+ nsresult rv = sHTMLFragmentParser->ParseDocument(
+ aSourceBuffer, aTargetDocument, aScriptingEnabledForNoscriptParsing);
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ParseFragmentXML(const nsAString& aSourceBuffer,
+ Document* aDocument,
+ nsTArray<nsString>& aTagStack,
+ bool aPreventScriptExecution,
+ int32_t aFlags,
+ DocumentFragment** aReturn) {
+ if (nsContentUtils::sFragmentParsingActive) {
+ MOZ_ASSERT_UNREACHABLE("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sXMLFragmentParser) {
+ RefPtr<nsParser> parser = new nsParser();
+ parser.forget(&sXMLFragmentParser);
+ // sXMLFragmentParser now owns the parser
+ }
+ if (!sXMLFragmentSink) {
+ NS_NewXMLFragmentContentSink(&sXMLFragmentSink);
+ // sXMLFragmentSink now owns the sink
+ }
+ nsCOMPtr<nsIContentSink> contentsink = do_QueryInterface(sXMLFragmentSink);
+ MOZ_ASSERT(contentsink, "Sink doesn't QI to nsIContentSink!");
+ sXMLFragmentParser->SetContentSink(contentsink);
+
+ RefPtr<Document> doc;
+ nsCOMPtr<nsIPrincipal> nodePrincipal = aDocument->NodePrincipal();
+
+#ifdef DEBUG
+ // aFlags should always be -1 unless the caller of ParseFragmentXML
+ // is ParserUtils::ParseFragment() which is the only caller that intends
+ // sanitization. For all other callers we need to ensure to call
+ // AuditParsingOfHTMLXMLFragments.
+ if (aFlags < 0) {
+ DOMSecurityMonitor::AuditParsingOfHTMLXMLFragments(nodePrincipal,
+ aSourceBuffer);
+ }
+#endif
+
+ // We sanitize if the fragment occurs in a system privileged
+ // context, an about: page, or if there are explicit sanitization flags.
+ // Please note that about:blank and about:srcdoc inherit the security
+ // context from the embedding context and hence are not loaded using
+ // an about: scheme principal.
+ bool shouldSanitize = nodePrincipal->IsSystemPrincipal() ||
+ nodePrincipal->SchemeIs("about") || aFlags >= 0;
+ if (shouldSanitize && !aDocument->IsLoadedAsData()) {
+ doc = nsContentUtils::CreateInertXMLDocument(aDocument);
+ } else {
+ doc = aDocument;
+ }
+
+ sXMLFragmentSink->SetTargetDocument(doc);
+ sXMLFragmentSink->SetPreventScriptExecution(aPreventScriptExecution);
+
+ nsresult rv = sXMLFragmentParser->ParseFragment(aSourceBuffer, aTagStack);
+ if (NS_FAILED(rv)) {
+ // Drop the fragment parser and sink that might be in an inconsistent state
+ NS_IF_RELEASE(sXMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentSink);
+ return rv;
+ }
+
+ rv = sXMLFragmentSink->FinishFragmentParsing(aReturn);
+
+ sXMLFragmentParser->Reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (shouldSanitize) {
+ uint32_t sanitizationFlags =
+ computeSanitizationFlags(nodePrincipal, aFlags);
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+ nsTreeSanitizer sanitizer(sanitizationFlags);
+ sanitizer.Sanitize(*aReturn);
+ }
+
+ return rv;
+}
+
+/* static */
+nsresult nsContentUtils::ConvertToPlainText(const nsAString& aSourceBuffer,
+ nsAString& aResultBuffer,
+ uint32_t aFlags,
+ uint32_t aWrapCol) {
+ RefPtr<Document> document = nsContentUtils::CreateInertHTMLDocument(nullptr);
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = nsContentUtils::ParseDocumentHTML(
+ aSourceBuffer, document,
+ !(aFlags & nsIDocumentEncoder::OutputNoScriptContent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder("text/plain");
+
+ rv = encoder->Init(document, u"text/plain"_ns, aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ encoder->SetWrapColumn(aWrapCol);
+
+ return encoder->EncodeToString(aResultBuffer);
+}
+
+static already_AddRefed<Document> CreateInertDocument(const Document* aTemplate,
+ DocumentFlavor aFlavor) {
+ if (aTemplate) {
+ bool hasHad = true;
+ nsIScriptGlobalObject* sgo = aTemplate->GetScriptHandlingObject(hasHad);
+ NS_ENSURE_TRUE(sgo || !hasHad, nullptr);
+
+ nsCOMPtr<Document> doc;
+ nsresult rv = NS_NewDOMDocument(
+ getter_AddRefs(doc), u""_ns, u""_ns, nullptr,
+ aTemplate->GetDocumentURI(), aTemplate->GetDocBaseURI(),
+ aTemplate->NodePrincipal(), true, sgo, aFlavor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ return doc.forget();
+ }
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank"_ns);
+ if (!uri) {
+ return nullptr;
+ }
+
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ if (!nullPrincipal) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc;
+ nsresult rv =
+ NS_NewDOMDocument(getter_AddRefs(doc), u""_ns, u""_ns, nullptr, uri, uri,
+ nullPrincipal, true, nullptr, aFlavor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ return doc.forget();
+}
+
+/* static */
+already_AddRefed<Document> nsContentUtils::CreateInertXMLDocument(
+ const Document* aTemplate) {
+ return CreateInertDocument(aTemplate, DocumentFlavorXML);
+}
+
+/* static */
+already_AddRefed<Document> nsContentUtils::CreateInertHTMLDocument(
+ const Document* aTemplate) {
+ return CreateInertDocument(aTemplate, DocumentFlavorHTML);
+}
+
+/* static */
+nsresult nsContentUtils::SetNodeTextContent(nsIContent* aContent,
+ const nsAString& aValue,
+ bool aTryReuse) {
+ // Fire DOMNodeRemoved mutation events before we do anything else.
+ nsCOMPtr<nsIContent> owningContent;
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(nullptr, nullptr);
+
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ // We're relying on mozAutoSubtreeModified to keep a strong reference if
+ // needed.
+ Document* doc = aContent->OwnerDoc();
+
+ // Optimize the common case of there being no observers
+ if (HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ subtree.UpdateTarget(doc, nullptr);
+ owningContent = aContent;
+ nsCOMPtr<nsINode> child;
+ bool skipFirst = aTryReuse;
+ for (child = aContent->GetFirstChild();
+ child && child->GetParentNode() == aContent;
+ child = child->GetNextSibling()) {
+ if (skipFirst && child->IsText()) {
+ skipFirst = false;
+ continue;
+ }
+ nsContentUtils::MaybeFireNodeRemoved(child, aContent);
+ }
+ }
+ }
+
+ // Might as well stick a batch around this since we're performing several
+ // mutations.
+ mozAutoDocUpdate updateBatch(aContent->GetComposedDoc(), true);
+ nsAutoMutationBatch mb;
+
+ if (aTryReuse && !aValue.IsEmpty()) {
+ // Let's remove nodes until we find a eTEXT.
+ while (aContent->HasChildren()) {
+ nsIContent* child = aContent->GetFirstChild();
+ if (child->IsText()) {
+ break;
+ }
+ aContent->RemoveChildNode(child, true);
+ }
+
+ // If we have a node, it must be a eTEXT and we reuse it.
+ if (aContent->HasChildren()) {
+ nsIContent* child = aContent->GetFirstChild();
+ nsresult rv = child->AsText()->SetText(aValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // All the following nodes, if they exist, must be deleted.
+ while (nsIContent* nextChild = child->GetNextSibling()) {
+ aContent->RemoveChildNode(nextChild, true);
+ }
+ }
+
+ if (aContent->HasChildren()) {
+ return NS_OK;
+ }
+ } else {
+ mb.Init(aContent, true, false);
+ while (aContent->HasChildren()) {
+ aContent->RemoveChildNode(aContent->GetFirstChild(), true);
+ }
+ }
+ mb.RemovalDone();
+
+ if (aValue.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<nsTextNode> textContent = new (aContent->NodeInfo()->NodeInfoManager())
+ nsTextNode(aContent->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(aValue, true);
+
+ ErrorResult rv;
+ aContent->AppendChildTo(textContent, true, rv);
+ mb.NodesAdded();
+ return rv.StealNSResult();
+}
+
+static bool AppendNodeTextContentsRecurse(const nsINode* aNode,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ bool ok = AppendNodeTextContentsRecurse(child, aResult, aFallible);
+ if (!ok) {
+ return false;
+ }
+ } else if (Text* text = child->GetAsText()) {
+ bool ok = text->AppendTextTo(aResult, aFallible);
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool nsContentUtils::AppendNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ if (const Text* text = aNode->GetAsText()) {
+ return text->AppendTextTo(aResult, aFallible);
+ }
+ if (aDeep) {
+ return AppendNodeTextContentsRecurse(aNode, aResult, aFallible);
+ }
+
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (Text* text = child->GetAsText()) {
+ bool ok = text->AppendTextTo(aResult, fallible);
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool nsContentUtils::HasNonEmptyTextContent(
+ nsINode* aNode, TextContentDiscoverMode aDiscoverMode) {
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsText() && child->TextLength() > 0) {
+ return true;
+ }
+
+ if (aDiscoverMode == eRecurseIntoChildren &&
+ HasNonEmptyTextContent(child, aDiscoverMode)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode,
+ const nsINode* aOtherNode) {
+ MOZ_ASSERT(aNode, "Must have a node to work with");
+ MOZ_ASSERT(aOtherNode, "Must have a content to work with");
+
+ const bool anon = aNode->IsInNativeAnonymousSubtree();
+ if (anon != aOtherNode->IsInNativeAnonymousSubtree()) {
+ return false;
+ }
+
+ if (anon) {
+ return aOtherNode->GetClosestNativeAnonymousSubtreeRoot() ==
+ aNode->GetClosestNativeAnonymousSubtreeRoot();
+ }
+
+ // FIXME: This doesn't deal with disconnected nodes whatsoever, but it didn't
+ // use to either. Maybe that's fine.
+ return aNode->GetContainingShadow() == aOtherNode->GetContainingShadow();
+}
+
+/* static */
+bool nsContentUtils::IsInInteractiveHTMLContent(const Element* aElement,
+ const Element* aStop) {
+ const Element* element = aElement;
+ while (element && element != aStop) {
+ if (element->IsInteractiveHTMLContent()) {
+ return true;
+ }
+ element = element->GetFlattenedTreeParentElement();
+ }
+ return false;
+}
+
+/* static */
+void nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling) {
+ IMEStateManager::OnInstalledMenuKeyboardListener(aInstalling);
+}
+
+/* static */
+bool nsContentUtils::SchemeIs(nsIURI* aURI, const char* aScheme) {
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(baseURI, false);
+ return baseURI->SchemeIs(aScheme);
+}
+
+bool nsContentUtils::IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
+ return aPrincipal && aPrincipal->GetIsExpandedPrincipal();
+}
+
+bool nsContentUtils::IsSystemOrExpandedPrincipal(nsIPrincipal* aPrincipal) {
+ return (aPrincipal && aPrincipal->IsSystemPrincipal()) ||
+ IsExpandedPrincipal(aPrincipal);
+}
+
+nsIPrincipal* nsContentUtils::GetSystemPrincipal() {
+ MOZ_ASSERT(IsInitialized());
+ return sSystemPrincipal;
+}
+
+bool nsContentUtils::CombineResourcePrincipals(
+ nsCOMPtr<nsIPrincipal>* aResourcePrincipal, nsIPrincipal* aExtraPrincipal) {
+ if (!aExtraPrincipal) {
+ return false;
+ }
+ if (!*aResourcePrincipal) {
+ *aResourcePrincipal = aExtraPrincipal;
+ return true;
+ }
+ if (*aResourcePrincipal == aExtraPrincipal) {
+ return false;
+ }
+ bool subsumes;
+ if (NS_SUCCEEDED(
+ (*aResourcePrincipal)->Subsumes(aExtraPrincipal, &subsumes)) &&
+ subsumes) {
+ return false;
+ }
+ *aResourcePrincipal = sSystemPrincipal;
+ return true;
+}
+
+/* static */
+void nsContentUtils::TriggerLink(nsIContent* aContent, nsIURI* aLinkURI,
+ const nsString& aTargetSpec, bool aClick,
+ bool aIsTrusted) {
+ MOZ_ASSERT(aLinkURI, "No link URI");
+
+ if (aContent->IsEditable() || !aContent->OwnerDoc()->LinkHandlingEnabled()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aContent->OwnerDoc()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ if (!aClick) {
+ nsDocShell::Cast(docShell)->OnOverLink(aContent, aLinkURI, aTargetSpec);
+ return;
+ }
+
+ // Check that this page is allowed to load this URI.
+ nsresult proceed = NS_OK;
+
+ if (sSecurityManager) {
+ uint32_t flag = static_cast<uint32_t>(nsIScriptSecurityManager::STANDARD);
+ proceed = sSecurityManager->CheckLoadURIWithPrincipal(
+ aContent->NodePrincipal(), aLinkURI, flag,
+ aContent->OwnerDoc()->InnerWindowID());
+ }
+
+ // Only pass off the click event if the script security manager says it's ok.
+ // We need to rest aTargetSpec for forced downloads.
+ if (NS_SUCCEEDED(proceed)) {
+ // A link/area element with a download attribute is allowed to set
+ // a pseudo Content-Disposition header.
+ // For security reasons we only allow websites to declare same-origin
+ // resources as downloadable. If this check fails we will just do the normal
+ // thing (i.e. navigate to the resource).
+ nsAutoString fileName;
+ if ((!aContent->IsHTMLElement(nsGkAtoms::a) &&
+ !aContent->IsHTMLElement(nsGkAtoms::area) &&
+ !aContent->IsSVGElement(nsGkAtoms::a)) ||
+ !aContent->AsElement()->GetAttr(nsGkAtoms::download, fileName) ||
+ NS_FAILED(aContent->NodePrincipal()->CheckMayLoad(aLinkURI, true))) {
+ fileName.SetIsVoid(true); // No actionable download attribute was found.
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aContent->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aContent->GetCsp();
+
+ // Sanitize fileNames containing null characters by replacing them with
+ // underscores.
+ if (!fileName.IsVoid()) {
+ fileName.ReplaceChar(char16_t(0), '_');
+ }
+ nsDocShell::Cast(docShell)->OnLinkClick(
+ aContent, aLinkURI, fileName.IsVoid() ? aTargetSpec : u""_ns, fileName,
+ nullptr, nullptr, UserActivation::IsHandlingUserInput(), aIsTrusted,
+ triggeringPrincipal, csp);
+ }
+}
+
+/* static */
+void nsContentUtils::GetLinkLocation(Element* aElement,
+ nsString& aLocationString) {
+ nsCOMPtr<nsIURI> hrefURI = aElement->GetHrefURI();
+ if (hrefURI) {
+ nsAutoCString specUTF8;
+ nsresult rv = hrefURI->GetSpec(specUTF8);
+ if (NS_SUCCEEDED(rv)) CopyUTF8toUTF16(specUTF8, aLocationString);
+ }
+}
+
+/* static */
+nsIWidget* nsContentUtils::GetTopLevelWidget(nsIWidget* aWidget) {
+ if (!aWidget) return nullptr;
+
+ return aWidget->GetTopLevelWidget();
+}
+
+/* static */
+const nsDependentString nsContentUtils::GetLocalizedEllipsis() {
+ static char16_t sBuf[4] = {0, 0, 0, 0};
+ if (!sBuf[0]) {
+ if (!SpoofLocaleEnglish()) {
+ nsAutoString tmp;
+ Preferences::GetLocalizedString("intl.ellipsis", tmp);
+ uint32_t len =
+ std::min(uint32_t(tmp.Length()), uint32_t(ArrayLength(sBuf) - 1));
+ CopyUnicodeTo(tmp, 0, sBuf, len);
+ }
+ if (!sBuf[0]) sBuf[0] = char16_t(0x2026);
+ }
+ return nsDependentString(sBuf);
+}
+
+/* static */
+void nsContentUtils::AddScriptBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sScriptBlockerCount) {
+ MOZ_ASSERT(sRunnersCountAtFirstBlocker == 0,
+ "Should not already have a count");
+ sRunnersCountAtFirstBlocker =
+ sBlockedScriptRunners ? sBlockedScriptRunners->Length() : 0;
+ }
+ ++sScriptBlockerCount;
+}
+
+#ifdef DEBUG
+static bool sRemovingScriptBlockers = false;
+#endif
+
+/* static */
+void nsContentUtils::RemoveScriptBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sRemovingScriptBlockers);
+ NS_ASSERTION(sScriptBlockerCount != 0, "Negative script blockers");
+ --sScriptBlockerCount;
+ if (sScriptBlockerCount) {
+ return;
+ }
+
+ if (!sBlockedScriptRunners) {
+ return;
+ }
+
+ uint32_t firstBlocker = sRunnersCountAtFirstBlocker;
+ uint32_t lastBlocker = sBlockedScriptRunners->Length();
+ uint32_t originalFirstBlocker = firstBlocker;
+ uint32_t blockersCount = lastBlocker - firstBlocker;
+ sRunnersCountAtFirstBlocker = 0;
+ NS_ASSERTION(firstBlocker <= lastBlocker, "bad sRunnersCountAtFirstBlocker");
+
+ while (firstBlocker < lastBlocker) {
+ nsCOMPtr<nsIRunnable> runnable;
+ runnable.swap((*sBlockedScriptRunners)[firstBlocker]);
+ ++firstBlocker;
+
+ // Calling the runnable can reenter us
+ {
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
+ runnable->Run();
+ }
+ // So can dropping the reference to the runnable
+ runnable = nullptr;
+
+ NS_ASSERTION(sRunnersCountAtFirstBlocker == 0, "Bad count");
+ NS_ASSERTION(!sScriptBlockerCount, "This is really bad");
+ }
+#ifdef DEBUG
+ AutoRestore<bool> removingScriptBlockers(sRemovingScriptBlockers);
+ sRemovingScriptBlockers = true;
+#endif
+ sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount);
+}
+
+/* static */
+already_AddRefed<nsPIDOMWindowOuter>
+nsContentUtils::GetMostRecentNonPBWindow() {
+ nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ wm->GetMostRecentNonPBWindow(u"navigator:browser", getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> pwindow;
+ pwindow = do_QueryInterface(window);
+
+ return pwindow.forget();
+}
+
+/* static */
+void nsContentUtils::WarnScriptWasIgnored(Document* aDocument) {
+ nsAutoString msg;
+ bool privateBrowsing = false;
+ bool chromeContext = false;
+
+ if (aDocument) {
+ nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+ if (uri) {
+ msg.Append(NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault()));
+ msg.AppendLiteral(" : ");
+ }
+ privateBrowsing =
+ !!aDocument->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId;
+ chromeContext = aDocument->NodePrincipal()->IsSystemPrincipal();
+ }
+
+ msg.AppendLiteral(
+ "Unable to run script because scripts are blocked internally.");
+ LogSimpleConsoleError(msg, "DOM"_ns, privateBrowsing, chromeContext);
+}
+
+/* static */
+void nsContentUtils::AddScriptRunner(already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ if (!runnable) {
+ return;
+ }
+
+ if (sScriptBlockerCount) {
+ sBlockedScriptRunners->AppendElement(runnable.forget());
+ return;
+ }
+
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
+ runnable->Run();
+}
+
+/* static */
+void nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ AddScriptRunner(runnable.forget());
+}
+
+/* static */ bool nsContentUtils::IsSafeToRunScript() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "This static variable only makes sense on the main thread!");
+ return sScriptBlockerCount == 0;
+}
+
+/* static */
+void nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->RunInStableState(std::move(aRunnable));
+}
+
+/* static */
+void nsContentUtils::AddPendingIDBTransaction(
+ already_AddRefed<nsIRunnable> aTransaction) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->AddPendingIDBTransaction(
+ std::move(aTransaction));
+}
+
+/* static */
+bool nsContentUtils::IsInStableOrMetaStableState() {
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ return CycleCollectedJSContext::Get()->IsInStableOrMetaStableState();
+}
+
+/* static */
+void nsContentUtils::HidePopupsInDocument(Document* aDocument) {
+ RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance();
+ if (!pm || !aDocument) {
+ return;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> docShellToHide = aDocument->GetDocShell();
+ if (docShellToHide) {
+ pm->HidePopupsInDocShell(docShellToHide);
+ }
+}
+
+/* static */
+already_AddRefed<nsIDragSession> nsContentUtils::GetDragSession() {
+ nsCOMPtr<nsIDragSession> dragSession;
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (dragService) dragService->GetCurrentSession(getter_AddRefs(dragSession));
+ return dragSession.forget();
+}
+
+/* static */
+nsresult nsContentUtils::SetDataTransferInEvent(WidgetDragEvent* aDragEvent) {
+ if (aDragEvent->mDataTransfer || !aDragEvent->IsTrusted()) {
+ return NS_OK;
+ }
+
+ // For dragstart events, the data transfer object is
+ // created before the event fires, so it should already be set. For other
+ // drag events, get the object from the drag session.
+ NS_ASSERTION(aDragEvent->mMessage != eDragStart,
+ "draggesture event created without a dataTransfer");
+
+ nsCOMPtr<nsIDragSession> dragSession = GetDragSession();
+ NS_ENSURE_TRUE(dragSession, NS_OK); // no drag in progress
+
+ RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer();
+ if (!initialDataTransfer) {
+ // A dataTransfer won't exist when a drag was started by some other
+ // means, for instance calling the drag service directly, or a drag
+ // from another application. In either case, a new dataTransfer should
+ // be created that reflects the data.
+ initialDataTransfer =
+ new DataTransfer(aDragEvent->mTarget, aDragEvent->mMessage, true, -1);
+
+ // now set it in the drag session so we don't need to create it again
+ dragSession->SetDataTransfer(initialDataTransfer);
+ }
+
+ bool isCrossDomainSubFrameDrop = false;
+ if (aDragEvent->mMessage == eDrop) {
+ isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent);
+ }
+
+ // each event should use a clone of the original dataTransfer.
+ initialDataTransfer->Clone(
+ aDragEvent->mTarget, aDragEvent->mMessage, aDragEvent->mUserCancelled,
+ isCrossDomainSubFrameDrop, getter_AddRefs(aDragEvent->mDataTransfer));
+ if (NS_WARN_IF(!aDragEvent->mDataTransfer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // for the dragenter and dragover events, initialize the drop effect
+ // from the drop action, which platform specific widget code sets before
+ // the event is fired based on the keyboard state.
+ if (aDragEvent->mMessage == eDragEnter || aDragEvent->mMessage == eDragOver) {
+ uint32_t action;
+ dragSession->GetDragAction(&action);
+ uint32_t effectAllowed = aDragEvent->mDataTransfer->EffectAllowedInt();
+ aDragEvent->mDataTransfer->SetDropEffectInt(
+ FilterDropEffect(action, effectAllowed));
+ } else if (aDragEvent->mMessage == eDrop ||
+ aDragEvent->mMessage == eDragEnd) {
+ // For the drop and dragend events, set the drop effect based on the
+ // last value that the dropEffect had. This will have been set in
+ // EventStateManager::PostHandleEvent for the last dragenter or
+ // dragover event.
+ aDragEvent->mDataTransfer->SetDropEffectInt(
+ initialDataTransfer->DropEffectInt());
+ }
+
+ return NS_OK;
+}
+
+/* static */
+uint32_t nsContentUtils::FilterDropEffect(uint32_t aAction,
+ uint32_t aEffectAllowed) {
+ // It is possible for the drag action to include more than one action, but
+ // the widget code which sets the action from the keyboard state should only
+ // be including one. If multiple actions were set, we just consider them in
+ // the following order:
+ // copy, link, move
+ if (aAction & nsIDragService::DRAGDROP_ACTION_COPY)
+ aAction = nsIDragService::DRAGDROP_ACTION_COPY;
+ else if (aAction & nsIDragService::DRAGDROP_ACTION_LINK)
+ aAction = nsIDragService::DRAGDROP_ACTION_LINK;
+ else if (aAction & nsIDragService::DRAGDROP_ACTION_MOVE)
+ aAction = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // Filter the action based on the effectAllowed. If the effectAllowed
+ // doesn't include the action, then that action cannot be done, so adjust
+ // the action to something that is allowed. For a copy, adjust to move or
+ // link. For a move, adjust to copy or link. For a link, adjust to move or
+ // link. Otherwise, use none.
+ if (aAction & aEffectAllowed ||
+ aEffectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED)
+ return aAction;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_MOVE)
+ return nsIDragService::DRAGDROP_ACTION_MOVE;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_COPY)
+ return nsIDragService::DRAGDROP_ACTION_COPY;
+ if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_LINK)
+ return nsIDragService::DRAGDROP_ACTION_LINK;
+ return nsIDragService::DRAGDROP_ACTION_NONE;
+}
+
+/* static */
+bool nsContentUtils::CheckForSubFrameDrop(nsIDragSession* aDragSession,
+ WidgetDragEvent* aDropEvent) {
+ nsCOMPtr<nsIContent> target =
+ nsIContent::FromEventTargetOrNull(aDropEvent->mOriginalTarget);
+ if (!target) {
+ return true;
+ }
+
+ // Always allow dropping onto chrome shells.
+ BrowsingContext* targetBC = target->OwnerDoc()->GetBrowsingContext();
+ if (targetBC->IsChrome()) {
+ return false;
+ }
+
+ WindowContext* targetWC = target->OwnerDoc()->GetWindowContext();
+
+ // If there is no source browsing context, then this is a drag from another
+ // application, which should be allowed.
+ RefPtr<WindowContext> sourceWC;
+ aDragSession->GetSourceWindowContext(getter_AddRefs(sourceWC));
+ if (sourceWC) {
+ // Get each successive parent of the source document and compare it to
+ // the drop document. If they match, then this is a drag from a child frame.
+ for (sourceWC = sourceWC->GetParentWindowContext(); sourceWC;
+ sourceWC = sourceWC->GetParentWindowContext()) {
+ // If the source and the target match, then the drag started in a
+ // descendant frame. If the source is discarded, err on the side of
+ // caution and treat it as a subframe drag.
+ if (sourceWC == targetWC || sourceWC->IsDiscarded()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool nsContentUtils::URIIsLocalFile(nsIURI* aURI) {
+ bool isFile;
+ nsCOMPtr<nsINetUtil> util = mozilla::components::IO::Service();
+
+ // Important: we do NOT test the entire URI chain here!
+ return util &&
+ NS_SUCCEEDED(util->ProtocolHasFlags(
+ aURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &isFile)) &&
+ isFile;
+}
+
+/* static */
+JSContext* nsContentUtils::GetCurrentJSContext() {
+ MOZ_ASSERT(IsInitialized());
+ if (!IsJSAPIActive()) {
+ return nullptr;
+ }
+ return danger::GetJSContext();
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToLowerInSitu(StringType& aStr) {
+ CharType* iter = aStr.BeginWriting();
+ CharType* end = aStr.EndWriting();
+ MOZ_ASSERT(iter && end);
+
+ while (iter != end) {
+ CharType c = *iter;
+ if (c >= 'A' && c <= 'Z') {
+ *iter = c + ('a' - 'A');
+ }
+ ++iter;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(nsAString& aStr) {
+ return _ASCIIToLowerInSitu<nsAString, char16_t>(aStr);
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(nsACString& aStr) {
+ return _ASCIIToLowerInSitu<nsACString, char>(aStr);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToLowerCopy(const StringType& aSource, StringType& aDest) {
+ uint32_t len = aSource.Length();
+ aDest.SetLength(len);
+ MOZ_ASSERT(aDest.Length() == len);
+
+ CharType* dest = aDest.BeginWriting();
+ MOZ_ASSERT(dest);
+
+ const CharType* iter = aSource.BeginReading();
+ const CharType* end = aSource.EndReading();
+ while (iter != end) {
+ CharType c = *iter;
+ *dest = (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
+ ++iter;
+ ++dest;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(const nsAString& aSource, nsAString& aDest) {
+ return _ASCIIToLowerCopy<nsAString, char16_t>(aSource, aDest);
+}
+
+/* static */
+void nsContentUtils::ASCIIToLower(const nsACString& aSource,
+ nsACString& aDest) {
+ return _ASCIIToLowerCopy<nsACString, char>(aSource, aDest);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToUpperInSitu(StringType& aStr) {
+ CharType* iter = aStr.BeginWriting();
+ CharType* end = aStr.EndWriting();
+ MOZ_ASSERT(iter && end);
+
+ while (iter != end) {
+ CharType c = *iter;
+ if (c >= 'a' && c <= 'z') {
+ *iter = c + ('A' - 'a');
+ }
+ ++iter;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(nsAString& aStr) {
+ return _ASCIIToUpperInSitu<nsAString, char16_t>(aStr);
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(nsACString& aStr) {
+ return _ASCIIToUpperInSitu<nsACString, char>(aStr);
+}
+
+template <typename StringType, typename CharType>
+void _ASCIIToUpperCopy(const StringType& aSource, StringType& aDest) {
+ uint32_t len = aSource.Length();
+ aDest.SetLength(len);
+ MOZ_ASSERT(aDest.Length() == len);
+
+ CharType* dest = aDest.BeginWriting();
+ MOZ_ASSERT(dest);
+
+ const CharType* iter = aSource.BeginReading();
+ const CharType* end = aSource.EndReading();
+ while (iter != end) {
+ CharType c = *iter;
+ *dest = (c >= 'a' && c <= 'z') ? c + ('A' - 'a') : c;
+ ++iter;
+ ++dest;
+ }
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(const nsAString& aSource, nsAString& aDest) {
+ return _ASCIIToUpperCopy<nsAString, char16_t>(aSource, aDest);
+}
+
+/* static */
+void nsContentUtils::ASCIIToUpper(const nsACString& aSource,
+ nsACString& aDest) {
+ return _ASCIIToUpperCopy<nsACString, char>(aSource, aDest);
+}
+
+/* static */
+bool nsContentUtils::EqualsIgnoreASCIICase(nsAtom* aAtom1, nsAtom* aAtom2) {
+ if (aAtom1 == aAtom2) {
+ return true;
+ }
+
+ // If both are ascii lowercase already, we know that the slow comparison
+ // below is going to return false.
+ if (aAtom1->IsAsciiLowercase() && aAtom2->IsAsciiLowercase()) {
+ return false;
+ }
+
+ return EqualsIgnoreASCIICase(nsDependentAtomString(aAtom1),
+ nsDependentAtomString(aAtom2));
+}
+
+/* static */
+bool nsContentUtils::EqualsIgnoreASCIICase(const nsAString& aStr1,
+ const nsAString& aStr2) {
+ uint32_t len = aStr1.Length();
+ if (len != aStr2.Length()) {
+ return false;
+ }
+
+ const char16_t* str1 = aStr1.BeginReading();
+ const char16_t* str2 = aStr2.BeginReading();
+ const char16_t* end = str1 + len;
+
+ while (str1 < end) {
+ char16_t c1 = *str1++;
+ char16_t c2 = *str2++;
+
+ // First check if any bits other than the 0x0020 differs
+ if ((c1 ^ c2) & 0xffdf) {
+ return false;
+ }
+
+ // We know they can only differ in the 0x0020 bit.
+ // Likely the two chars are the same, so check that first
+ if (c1 != c2) {
+ // They do differ, but since it's only in the 0x0020 bit, check if it's
+ // the same ascii char, but just differing in case
+ char16_t c1Upper = c1 & 0xffdf;
+ if (!('A' <= c1Upper && c1Upper <= 'Z')) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool nsContentUtils::StringContainsASCIIUpper(const nsAString& aStr) {
+ const char16_t* iter = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+ while (iter != end) {
+ char16_t c = *iter;
+ if (c >= 'A' && c <= 'Z') {
+ return true;
+ }
+ ++iter;
+ }
+
+ return false;
+}
+
+/* static */
+nsIInterfaceRequestor* nsContentUtils::SameOriginChecker() {
+ if (!sSameOriginChecker) {
+ sSameOriginChecker = new SameOriginCheckerImpl();
+ NS_ADDREF(sSameOriginChecker);
+ }
+ return sSameOriginChecker;
+}
+
+/* static */
+nsresult nsContentUtils::CheckSameOrigin(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel) {
+ if (!nsContentUtils::GetSecurityManager()) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aOldChannel, getter_AddRefs(oldPrincipal));
+
+ nsCOMPtr<nsIURI> newURI;
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ nsCOMPtr<nsIURI> newOriginalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI);
+
+ nsresult rv = oldPrincipal->CheckMayLoad(newURI, false);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = oldPrincipal->CheckMayLoad(newOriginalURI, false);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(SameOriginCheckerImpl, nsIChannelEventSink,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+SameOriginCheckerImpl::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ MOZ_ASSERT(aNewChannel, "Redirecting to null channel?");
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
+ if (NS_SUCCEEDED(rv)) {
+ cb->OnRedirectVerifyCallback(NS_OK);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+SameOriginCheckerImpl::GetInterface(const nsIID& aIID, void** aResult) {
+ return QueryInterface(aIID, aResult);
+}
+
+/* static */
+nsresult nsContentUtils::GetWebExposedOriginSerialization(nsIURI* aURI,
+ nsACString& aOrigin) {
+ nsresult rv;
+ MOZ_ASSERT(aURI, "missing uri");
+
+ // For Blob URI, the path is the URL of the owning page.
+ if (aURI->SchemeIs(BLOBURI_SCHEME)) {
+ nsAutoCString path;
+ rv = aURI->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), path);
+ if (NS_FAILED(rv)) {
+ aOrigin.AssignLiteral("null");
+ return NS_OK;
+ }
+
+ return GetWebExposedOriginSerialization(uri, aOrigin);
+ }
+
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+
+ // If the protocol doesn't have URI_HAS_WEB_EXPOSED_ORIGIN, then
+ // return "null" as the origin serialization.
+ // We make an exception for "ftp" since we don't have a protocol handler
+ // for this scheme
+ uint32_t flags = 0;
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(&rv);
+ if (!scheme.Equals("ftp") && NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(io->GetProtocolFlags(scheme.get(), &flags))) {
+ if (!(flags & nsIProtocolHandler::URI_HAS_WEB_EXPOSED_ORIGIN)) {
+ aOrigin.AssignLiteral("null");
+ return NS_OK;
+ }
+ }
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString host;
+ rv = uri->GetAsciiHost(host);
+
+ if (NS_SUCCEEDED(rv) && !host.IsEmpty()) {
+ nsAutoCString userPass;
+ uri->GetUserPass(userPass);
+
+ nsAutoCString prePath;
+ if (!userPass.IsEmpty()) {
+ rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = uri->GetPrePath(prePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOrigin = prePath;
+ } else {
+ aOrigin.AssignLiteral("null");
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult nsContentUtils::GetWebExposedOriginSerialization(
+ nsIPrincipal* aPrincipal, nsAString& aOrigin) {
+ MOZ_ASSERT(aPrincipal, "missing principal");
+
+ aOrigin.Truncate();
+ nsAutoCString webExposedOriginSerialization;
+
+ nsresult rv = aPrincipal->GetWebExposedOriginSerialization(
+ webExposedOriginSerialization);
+ if (NS_FAILED(rv)) {
+ webExposedOriginSerialization.AssignLiteral("null");
+ }
+
+ CopyUTF8toUTF16(webExposedOriginSerialization, aOrigin);
+ return NS_OK;
+}
+
+/* static */
+nsresult nsContentUtils::GetWebExposedOriginSerialization(nsIURI* aURI,
+ nsAString& aOrigin) {
+ MOZ_ASSERT(aURI, "missing uri");
+ nsresult rv;
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Check if either URI has a special origin.
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(aURI);
+ if (uriWithSpecialOrigin) {
+ nsCOMPtr<nsIURI> origin;
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetWebExposedOriginSerialization(origin, aOrigin);
+ }
+#endif
+
+ nsAutoCString webExposedOriginSerialization;
+ rv = GetWebExposedOriginSerialization(aURI, webExposedOriginSerialization);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(webExposedOriginSerialization, aOrigin);
+ return NS_OK;
+}
+
+/* static */
+bool nsContentUtils::CheckMayLoad(nsIPrincipal* aPrincipal,
+ nsIChannel* aChannel,
+ bool aAllowIfInheritsPrincipal) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return NS_SUCCEEDED(
+ aPrincipal->CheckMayLoad(channelURI, aAllowIfInheritsPrincipal));
+}
+
+/* static */
+bool nsContentUtils::CanAccessNativeAnon() {
+ return LegacyIsCallerChromeOrNativeCode();
+}
+
+/* static */
+nsresult nsContentUtils::DispatchXULCommand(nsIContent* aTarget, bool aTrusted,
+ Event* aSourceEvent,
+ PresShell* aPresShell, bool aCtrl,
+ bool aAlt, bool aShift, bool aMeta,
+ uint16_t aInputSource,
+ int16_t aButton) {
+ NS_ENSURE_STATE(aTarget);
+ Document* doc = aTarget->OwnerDoc();
+ nsPresContext* presContext = doc->GetPresContext();
+
+ RefPtr<XULCommandEvent> xulCommand =
+ new XULCommandEvent(doc, presContext, nullptr);
+ xulCommand->InitCommandEvent(u"command"_ns, true, true,
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
+ 0, aCtrl, aAlt, aShift, aMeta, aButton,
+ aSourceEvent, aInputSource, IgnoreErrors());
+
+ if (aPresShell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ return aPresShell->HandleDOMEventWithTarget(aTarget, xulCommand, &status);
+ }
+
+ ErrorResult rv;
+ aTarget->DispatchEvent(*xulCommand, rv);
+ return rv.StealNSResult();
+}
+
+// static
+nsresult nsContentUtils::WrapNative(JSContext* cx, nsISupports* native,
+ nsWrapperCache* cache, const nsIID* aIID,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping) {
+ MOZ_ASSERT(cx == GetCurrentJSContext());
+
+ if (!native) {
+ vp.setNull();
+
+ return NS_OK;
+ }
+
+ JSObject* wrapper = xpc_FastGetCachedWrapper(cx, cache, vp);
+ if (wrapper) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(sXPConnect, NS_ERROR_UNEXPECTED);
+
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ nsresult rv = sXPConnect->WrapNativeToJSVal(cx, scope, native, cache, aIID,
+ aAllowWrapping, vp);
+ return rv;
+}
+
+void nsContentUtils::StripNullChars(const nsAString& aInStr,
+ nsAString& aOutStr) {
+ // In common cases where we don't have nulls in the
+ // string we can simple simply bypass the checking code.
+ int32_t firstNullPos = aInStr.FindChar('\0');
+ if (firstNullPos == kNotFound) {
+ aOutStr.Assign(aInStr);
+ return;
+ }
+
+ aOutStr.SetCapacity(aInStr.Length() - 1);
+ nsAString::const_iterator start, end;
+ aInStr.BeginReading(start);
+ aInStr.EndReading(end);
+ while (start != end) {
+ if (*start != '\0') aOutStr.Append(*start);
+ ++start;
+ }
+}
+
+struct ClassMatchingInfo {
+ AtomArray mClasses;
+ nsCaseTreatment mCaseTreatment;
+};
+
+// static
+bool nsContentUtils::MatchClassNames(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAtom, void* aData) {
+ // We can't match if there are no class names
+ const nsAttrValue* classAttr = aElement->GetClasses();
+ if (!classAttr) {
+ return false;
+ }
+
+ // need to match *all* of the classes
+ ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData);
+ uint32_t length = info->mClasses.Length();
+ if (!length) {
+ // If we actually had no classes, don't match.
+ return false;
+ }
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (!classAttr->Contains(info->mClasses[i], info->mCaseTreatment)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// static
+void nsContentUtils::DestroyClassNameArray(void* aData) {
+ ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData);
+ delete info;
+}
+
+// static
+void* nsContentUtils::AllocClassMatchingInfo(nsINode* aRootNode,
+ const nsString* aClasses) {
+ nsAttrValue attrValue;
+ attrValue.ParseAtomArray(*aClasses);
+ // nsAttrValue::Equals is sensitive to order, so we'll send an array
+ auto* info = new ClassMatchingInfo;
+ if (attrValue.Type() == nsAttrValue::eAtomArray) {
+ info->mClasses = attrValue.GetAtomArrayValue()->mArray.Clone();
+ } else if (attrValue.Type() == nsAttrValue::eAtom) {
+ info->mClasses.AppendElement(attrValue.GetAtomValue());
+ }
+
+ info->mCaseTreatment =
+ aRootNode->OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks
+ ? eIgnoreCase
+ : eCaseMatters;
+ return info;
+}
+
+// static
+bool nsContentUtils::IsFocusedContent(const nsIContent* aContent) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+
+ return fm && fm->GetFocusedElement() == aContent;
+}
+
+bool nsContentUtils::HasScrollgrab(nsIContent* aContent) {
+ // If we ever standardize this feature we'll want to hook this up properly
+ // again. For now we're removing all the DOM-side code related to it but
+ // leaving the layout and APZ handling for it in place.
+ return false;
+}
+
+void nsContentUtils::FlushLayoutForTree(nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow) {
+ return;
+ }
+
+ // Note that because FlushPendingNotifications flushes parents, this
+ // is O(N^2) in docshell tree depth. However, the docshell tree is
+ // usually pretty shallow.
+
+ if (RefPtr<Document> doc = aWindow->GetDoc()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
+ int32_t i = 0, i_end;
+ docShell->GetInProcessChildCount(&i_end);
+ for (; i < i_end; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ if (docShell->GetInProcessChildAt(i, getter_AddRefs(item)) == NS_OK &&
+ item) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = item->GetWindow()) {
+ FlushLayoutForTree(win);
+ }
+ }
+ }
+ }
+}
+
+void nsContentUtils::RemoveNewlines(nsString& aString) { aString.StripCRLF(); }
+
+void nsContentUtils::PlatformToDOMLineBreaks(nsString& aString) {
+ if (!PlatformToDOMLineBreaks(aString, fallible)) {
+ aString.AllocFailed(aString.Length());
+ }
+}
+
+bool nsContentUtils::PlatformToDOMLineBreaks(nsString& aString,
+ const fallible_t& aFallible) {
+ if (aString.FindChar(char16_t('\r')) != -1) {
+ // Windows linebreaks: Map CRLF to LF:
+ if (!aString.ReplaceSubstring(u"\r\n", u"\n", aFallible)) {
+ return false;
+ }
+
+ // Mac linebreaks: Map any remaining CR to LF:
+ if (!aString.ReplaceSubstring(u"\r", u"\n", aFallible)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void nsContentUtils::PopulateStringFromStringBuffer(nsStringBuffer* aBuf,
+ nsAString& aResultString) {
+ MOZ_ASSERT(aBuf, "Expecting a non-null string buffer");
+
+ uint32_t stringLen = NS_strlen(static_cast<char16_t*>(aBuf->Data()));
+
+ // SANITY CHECK: In case the nsStringBuffer isn't correctly
+ // null-terminated, let's clamp its length using the allocated size, to be
+ // sure the resulting string doesn't sample past the end of the the buffer.
+ // (Note that StorageSize() is in units of bytes, so we have to convert that
+ // to units of PRUnichars, and subtract 1 for the null-terminator.)
+ uint32_t allocStringLen = (aBuf->StorageSize() / sizeof(char16_t)) - 1;
+ MOZ_ASSERT(stringLen <= allocStringLen,
+ "string buffer lacks null terminator!");
+ stringLen = std::min(stringLen, allocStringLen);
+
+ aBuf->ToString(stringLen, aResultString);
+}
+
+already_AddRefed<nsContentList> nsContentUtils::GetElementsByClassName(
+ nsINode* aRootNode, const nsAString& aClasses) {
+ MOZ_ASSERT(aRootNode, "Must have root node");
+
+ return GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(
+ aRootNode, MatchClassNames, DestroyClassNameArray, AllocClassMatchingInfo,
+ aClasses);
+}
+
+PresShell* nsContentUtils::FindPresShellForDocument(const Document* aDocument) {
+ const Document* doc = aDocument;
+ Document* displayDoc = doc->GetDisplayDocument();
+ if (displayDoc) {
+ doc = displayDoc;
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (presShell) {
+ return presShell;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+ while (docShellTreeItem) {
+ // We may be in a display:none subdocument, or we may not have a presshell
+ // created yet.
+ // Walk the docshell tree to find the nearest container that has a
+ // presshell, and return that.
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(docShellTreeItem);
+ if (PresShell* presShell = docShell->GetPresShell()) {
+ return presShell;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShellTreeItem->GetInProcessParent(getter_AddRefs(parent));
+ docShellTreeItem = parent;
+ }
+
+ return nullptr;
+}
+
+/* static */
+nsPresContext* nsContentUtils::FindPresContextForDocument(
+ const Document* aDocument) {
+ if (PresShell* presShell = FindPresShellForDocument(aDocument)) {
+ return presShell->GetPresContext();
+ }
+ return nullptr;
+}
+
+nsIWidget* nsContentUtils::WidgetForDocument(const Document* aDocument) {
+ PresShell* presShell = FindPresShellForDocument(aDocument);
+ if (!presShell) {
+ return nullptr;
+ }
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+ nsView* rootView = vm->GetRootView();
+ if (!rootView) {
+ return nullptr;
+ }
+ nsView* displayRoot = nsViewManager::GetDisplayRootFor(rootView);
+ if (!displayRoot) {
+ return nullptr;
+ }
+ return displayRoot->GetNearestWidget(nullptr);
+}
+
+nsIWidget* nsContentUtils::WidgetForContent(const nsIContent* aContent) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ frame = nsLayoutUtils::GetDisplayRootFrame(frame);
+
+ nsView* view = frame->GetView();
+ if (view) {
+ return view->GetWidget();
+ }
+ }
+
+ return nullptr;
+}
+
+WindowRenderer* nsContentUtils::WindowRendererForContent(
+ const nsIContent* aContent) {
+ nsIWidget* widget = nsContentUtils::WidgetForContent(aContent);
+ if (widget) {
+ return widget->GetWindowRenderer();
+ }
+
+ return nullptr;
+}
+
+WindowRenderer* nsContentUtils::WindowRendererForDocument(
+ const Document* aDoc) {
+ nsIWidget* widget = nsContentUtils::WidgetForDocument(aDoc);
+ if (widget) {
+ return widget->GetWindowRenderer();
+ }
+
+ return nullptr;
+}
+
+bool nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ return xpc::IsInAutomation() && IsSitePermAllow(aPrincipal, "allowXULXBL"_ns);
+}
+
+bool nsContentUtils::IsPDFJSEnabled() {
+ nsCOMPtr<nsIStreamConverter> conv = do_CreateInstance(
+ "@mozilla.org/streamconv;1?from=application/pdf&to=text/html");
+ return conv;
+}
+
+bool nsContentUtils::IsPDFJS(nsIPrincipal* aPrincipal) {
+ if (!aPrincipal || !aPrincipal->SchemeIs("resource")) {
+ return false;
+ }
+ nsAutoCString spec;
+ nsresult rv = aPrincipal->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+ return spec.EqualsLiteral("resource://pdf.js/web/viewer.html");
+}
+
+bool nsContentUtils::IsSystemOrPDFJS(JSContext* aCx, JSObject*) {
+ nsIPrincipal* principal = SubjectPrincipal(aCx);
+ return principal && (principal->IsSystemPrincipal() || IsPDFJS(principal));
+}
+
+bool nsContentUtils::IsSecureContextOrWebExtension(JSContext* aCx,
+ JSObject* aGlobal) {
+ nsIPrincipal* principal = SubjectPrincipal(aCx);
+ return mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(aCx,
+ aGlobal) ||
+ (principal && principal->GetIsAddonOrExpandedAddonPrincipal());
+}
+
+already_AddRefed<nsIDocumentLoaderFactory>
+nsContentUtils::FindInternalDocumentViewer(const nsACString& aType,
+ DocumentViewerType* aLoaderType) {
+ if (aLoaderType) {
+ *aLoaderType = TYPE_UNSUPPORTED;
+ }
+
+ // one helper factory, please
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
+ if (!catMan) return nullptr;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docFactory;
+
+ nsCString contractID;
+ nsresult rv =
+ catMan->GetCategoryEntry("Gecko-Content-Viewers", aType, contractID);
+ if (NS_SUCCEEDED(rv)) {
+ docFactory = do_GetService(contractID.get());
+ if (docFactory && aLoaderType) {
+ if (contractID.EqualsLiteral(CONTENT_DLF_CONTRACTID))
+ *aLoaderType = TYPE_CONTENT;
+ else if (contractID.EqualsLiteral(PLUGIN_DLF_CONTRACTID))
+ *aLoaderType = TYPE_FALLBACK;
+ else
+ *aLoaderType = TYPE_UNKNOWN;
+ }
+ return docFactory.forget();
+ }
+
+ if (DecoderTraits::IsSupportedInVideoDocument(aType)) {
+ docFactory =
+ do_GetService("@mozilla.org/content/document-loader-factory;1");
+ if (docFactory && aLoaderType) {
+ *aLoaderType = TYPE_CONTENT;
+ }
+ return docFactory.forget();
+ }
+
+ return nullptr;
+}
+
+static void ReportPatternCompileFailure(nsAString& aPattern,
+ const Document* aDocument,
+ JS::MutableHandle<JS::Value> error,
+ JSContext* cx) {
+ JS::AutoSaveExceptionState savedExc(cx);
+ JS::Rooted<JSObject*> exnObj(cx, &error.toObject());
+ JS::Rooted<JS::Value> messageVal(cx);
+ if (!JS_GetProperty(cx, exnObj, "message", &messageVal)) {
+ return;
+ }
+ JS::Rooted<JSString*> messageStr(cx, messageVal.toString());
+ MOZ_ASSERT(messageStr);
+
+ AutoTArray<nsString, 2> strings;
+ strings.AppendElement(aPattern);
+ if (!AssignJSString(cx, *strings.AppendElement(), messageStr)) {
+ return;
+ }
+
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
+ aDocument, nsContentUtils::eDOM_PROPERTIES,
+ "PatternAttributeCompileFailure", strings);
+ savedExc.drop();
+}
+
+// static
+Maybe<bool> nsContentUtils::IsPatternMatching(const nsAString& aValue,
+ nsString&& aPattern,
+ const Document* aDocument,
+ bool aHasMultiple,
+ JS::RegExpFlags aFlags) {
+ NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
+
+ // The fact that we're using a JS regexp under the hood should not be visible
+ // to things like window onerror handlers, so we don't initialize our JSAPI
+ // with the document's window (which may not exist anyway).
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ AutoDisableJSInterruptCallback disabler(cx);
+
+ // We can use the junk scope here, because we're just using it for regexp
+ // evaluation, not actual script execution, and we disable statics so that the
+ // evaluation does not interact with the execution global.
+ JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
+
+ // Check if the pattern by itself is valid first, and not that it only becomes
+ // valid once we add ^(?: and )$.
+ JS::Rooted<JS::Value> error(cx);
+ if (!JS::CheckRegExpSyntax(cx, aPattern.BeginReading(), aPattern.Length(),
+ aFlags, &error)) {
+ return Nothing();
+ }
+
+ if (!error.isUndefined()) {
+ ReportPatternCompileFailure(aPattern, aDocument, &error, cx);
+ return Some(true);
+ }
+
+ // The pattern has to match the entire value.
+ aPattern.InsertLiteral(u"^(?:", 0);
+ aPattern.AppendLiteral(")$");
+
+ JS::Rooted<JSObject*> re(
+ cx, JS::NewUCRegExpObject(cx, aPattern.BeginReading(), aPattern.Length(),
+ aFlags));
+ if (!re) {
+ return Nothing();
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::NullValue());
+ if (!aHasMultiple) {
+ size_t idx = 0;
+ if (!JS::ExecuteRegExpNoStatics(cx, re, aValue.BeginReading(),
+ aValue.Length(), &idx, true, &rval)) {
+ return Nothing();
+ }
+ return Some(!rval.isNull());
+ }
+
+ HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsAString& value = tokenizer.nextToken();
+ size_t idx = 0;
+ if (!JS::ExecuteRegExpNoStatics(cx, re, value.BeginReading(),
+ value.Length(), &idx, true, &rval)) {
+ return Nothing();
+ }
+ if (rval.isNull()) {
+ return Some(false);
+ }
+ }
+ return Some(true);
+}
+
+// static
+nsresult nsContentUtils::URIInheritsSecurityContext(nsIURI* aURI,
+ bool* aResult) {
+ // Note: about:blank URIs do NOT inherit the security context from the
+ // current document, which is what this function tests for...
+ return NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, aResult);
+}
+
+// static
+bool nsContentUtils::ChannelShouldInheritPrincipal(
+ nsIPrincipal* aLoadingPrincipal, nsIURI* aURI, bool aInheritForAboutBlank,
+ bool aForceInherit) {
+ MOZ_ASSERT(aLoadingPrincipal,
+ "Can not check inheritance without a principal");
+
+ // Only tell the channel to inherit if it can't provide its own security
+ // context.
+ //
+ // XXX: If this is ever changed, check all callers for what owners
+ // they're passing in. In particular, see the code and
+ // comments in nsDocShell::LoadURI where we fall back on
+ // inheriting the owner if called from chrome. That would be
+ // very wrong if this code changed anything but channels that
+ // can't provide their own security context!
+ //
+ // If aForceInherit is true, we will inherit, even for a channel that
+ // can provide its own security context. This is used for srcdoc loads.
+ bool inherit = aForceInherit;
+ if (!inherit) {
+ bool uriInherits;
+ // We expect URIInheritsSecurityContext to return success for an
+ // about:blank URI, so don't call NS_IsAboutBlank() if this call fails.
+ // This condition needs to match the one in nsDocShell::InternalLoad where
+ // we're checking for things that will use the owner.
+ inherit =
+ (NS_SUCCEEDED(URIInheritsSecurityContext(aURI, &uriInherits)) &&
+ (uriInherits || (aInheritForAboutBlank && NS_IsAboutBlank(aURI)))) ||
+ //
+ // file: uri special-casing
+ //
+ // If this is a file: load opened from another file: then it may need
+ // to inherit the owner from the referrer so they can script each other.
+ // If we don't set the owner explicitly then each file: gets an owner
+ // based on its own codebase later.
+ //
+ (URIIsLocalFile(aURI) &&
+ NS_SUCCEEDED(aLoadingPrincipal->CheckMayLoad(aURI, false)) &&
+ // One more check here. CheckMayLoad will always return true for the
+ // system principal, but we do NOT want to inherit in that case.
+ !aLoadingPrincipal->IsSystemPrincipal());
+ }
+ return inherit;
+}
+
+/* static */
+bool nsContentUtils::IsCutCopyAllowed(Document* aDocument,
+ nsIPrincipal& aSubjectPrincipal) {
+ if (StaticPrefs::dom_allow_cut_copy() && aDocument &&
+ aDocument->HasValidTransientUserGestureActivation()) {
+ return true;
+ }
+
+ return PrincipalHasPermission(aSubjectPrincipal, nsGkAtoms::clipboardWrite);
+}
+
+/* static */
+bool nsContentUtils::HaveEqualPrincipals(Document* aDoc1, Document* aDoc2) {
+ if (!aDoc1 || !aDoc2) {
+ return false;
+ }
+ bool principalsEqual = false;
+ aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
+ return principalsEqual;
+}
+
+/* static */
+void nsContentUtils::FireMutationEventsForDirectParsing(
+ Document* aDoc, nsIContent* aDest, int32_t aOldChildCount) {
+ // Fire mutation events. Optimize for the case when there are no listeners
+ int32_t newChildCount = aDest->GetChildCount();
+ if (newChildCount && nsContentUtils::HasMutationListeners(
+ aDoc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
+ AutoTArray<nsCOMPtr<nsIContent>, 50> childNodes;
+ NS_ASSERTION(newChildCount - aOldChildCount >= 0,
+ "What, some unexpected dom mutation has happened?");
+ childNodes.SetCapacity(newChildCount - aOldChildCount);
+ for (nsIContent* child = aDest->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ childNodes.AppendElement(child);
+ }
+ FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes);
+ }
+}
+
+/* static */
+const Document* nsContentUtils::GetInProcessSubtreeRootDocument(
+ const Document* aDoc) {
+ if (!aDoc) {
+ return nullptr;
+ }
+ const Document* doc = aDoc;
+ while (doc->GetInProcessParentDocument()) {
+ doc = doc->GetInProcessParentDocument();
+ }
+ return doc;
+}
+
+// static
+int32_t nsContentUtils::GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame,
+ int32_t aOffset) {
+ // The structure of the anonymous frames within a text control frame is
+ // an optional block frame, followed by an optional br frame.
+
+ // If the offset frame has a child, then this frame is the block which
+ // has the text frames (containing the content) as its children. This will
+ // be the case if we click to the right of any of the text frames, or at the
+ // bottom of the text area.
+ nsIFrame* firstChild = aOffsetFrame->PrincipalChildList().FirstChild();
+ if (firstChild) {
+ // In this case, the passed-in offset is incorrect, and we want the length
+ // of the entire content in the text control frame.
+ return firstChild->GetContent()->Length();
+ }
+
+ if (aOffsetFrame->GetPrevSibling() && !aOffsetFrame->GetNextSibling()) {
+ // In this case, we're actually within the last frame, which is a br
+ // frame. Our offset should therefore be the length of the first child of
+ // our parent.
+ int32_t aOutOffset = aOffsetFrame->GetParent()
+ ->PrincipalChildList()
+ .FirstChild()
+ ->GetContent()
+ ->Length();
+ return aOutOffset;
+ }
+
+ // Otherwise, we're within one of the text frames, in which case our offset
+ // has already been correctly calculated.
+ return aOffset;
+}
+
+// static
+bool nsContentUtils::IsPointInSelection(
+ const mozilla::dom::Selection& aSelection, const nsINode& aNode,
+ const uint32_t aOffset) {
+ if (aSelection.IsCollapsed()) {
+ return false;
+ }
+
+ const uint32_t rangeCount = aSelection.RangeCount();
+ for (const uint32_t i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(aSelection.RangeCount() == rangeCount);
+ RefPtr<const nsRange> range = aSelection.GetRangeAt(i);
+ if (NS_WARN_IF(!range)) {
+ // Don't bail yet, iterate through them all
+ continue;
+ }
+
+ // Done when we find a range that we are in
+ if (range->IsPointInRange(aNode, aOffset, IgnoreErrors())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// static
+void nsContentUtils::GetSelectionInTextControl(Selection* aSelection,
+ Element* aRoot,
+ uint32_t& aOutStartOffset,
+ uint32_t& aOutEndOffset) {
+ MOZ_ASSERT(aSelection && aRoot);
+
+ // We don't care which end of this selection is anchor and which is focus. In
+ // fact, we explicitly want to know which is the _start_ and which is the
+ // _end_, not anchor vs focus.
+ const nsRange* range = aSelection->GetAnchorFocusRange();
+ if (!range) {
+ // Nothing selected
+ aOutStartOffset = aOutEndOffset = 0;
+ return;
+ }
+
+ // All the node pointers here are raw pointers for performance. We shouldn't
+ // be doing anything in this function that invalidates the node tree.
+ nsINode* startContainer = range->GetStartContainer();
+ uint32_t startOffset = range->StartOffset();
+ nsINode* endContainer = range->GetEndContainer();
+ uint32_t endOffset = range->EndOffset();
+
+ // We have at most two children, consisting of an optional text node followed
+ // by an optional <br>.
+ NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children");
+ nsIContent* firstChild = aRoot->GetFirstChild();
+#ifdef DEBUG
+ nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild();
+ NS_ASSERTION(startContainer == aRoot || startContainer == firstChild ||
+ startContainer == lastChild,
+ "Unexpected startContainer");
+ NS_ASSERTION(endContainer == aRoot || endContainer == firstChild ||
+ endContainer == lastChild,
+ "Unexpected endContainer");
+ // firstChild is either text or a <br> (hence an element).
+ MOZ_ASSERT_IF(firstChild, firstChild->IsText() || firstChild->IsElement());
+#endif
+ if (!firstChild || firstChild->IsElement()) {
+ // No text node, so everything is 0
+ startOffset = endOffset = 0;
+ } else {
+ // First child is text. If the start/end is already in the text node,
+ // or the start of the root node, no change needed. If it's in the root
+ // node but not the start, or in the trailing <br>, we need to set the
+ // offset to the end.
+ if ((startContainer == aRoot && startOffset != 0) ||
+ (startContainer != aRoot && startContainer != firstChild)) {
+ startOffset = firstChild->Length();
+ }
+ if ((endContainer == aRoot && endOffset != 0) ||
+ (endContainer != aRoot && endContainer != firstChild)) {
+ endOffset = firstChild->Length();
+ }
+ }
+
+ MOZ_ASSERT(startOffset <= endOffset);
+ aOutStartOffset = startOffset;
+ aOutEndOffset = endOffset;
+}
+
+// static
+HTMLEditor* nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext) {
+ if (!aPresContext) {
+ return nullptr;
+ }
+ return GetHTMLEditor(aPresContext->GetDocShell());
+}
+
+// static
+HTMLEditor* nsContentUtils::GetHTMLEditor(nsDocShell* aDocShell) {
+ bool isEditable;
+ if (!aDocShell || NS_FAILED(aDocShell->GetEditable(&isEditable)) ||
+ !isEditable) {
+ return nullptr;
+ }
+ return aDocShell->GetHTMLEditor();
+}
+
+// static
+EditorBase* nsContentUtils::GetActiveEditor(nsPresContext* aPresContext) {
+ if (!aPresContext) {
+ return nullptr;
+ }
+
+ return GetActiveEditor(aPresContext->Document()->GetWindow());
+}
+
+// static
+EditorBase* nsContentUtils::GetActiveEditor(nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow || !aWindow->GetExtantDoc()) {
+ return nullptr;
+ }
+
+ // If it's in designMode, nobody can have focus. Therefore, the HTMLEditor
+ // handles all events. I.e., it's focused editor in this case.
+ if (aWindow->GetExtantDoc()->IsInDesignMode()) {
+ return GetHTMLEditor(nsDocShell::Cast(aWindow->GetDocShell()));
+ }
+
+ // If focused element is associated with TextEditor, it must be <input>
+ // element or <textarea> element. Let's return it even if it's in a
+ // contenteditable element.
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ if (Element* focusedElement = nsFocusManager::GetFocusedDescendant(
+ aWindow, nsFocusManager::SearchRange::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow))) {
+ if (TextEditor* textEditor = focusedElement->GetTextEditorInternal()) {
+ return textEditor;
+ }
+ }
+
+ // Otherwise, HTMLEditor may handle inputs even non-editable element has
+ // focus or nobody has focus.
+ return GetHTMLEditor(nsDocShell::Cast(aWindow->GetDocShell()));
+}
+
+// static
+TextEditor* nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
+ const nsIContent* aAnonymousContent) {
+ if (!aAnonymousContent) {
+ return nullptr;
+ }
+ nsIContent* parent = aAnonymousContent->FindFirstNonChromeOnlyAccessContent();
+ if (!parent || parent == aAnonymousContent) {
+ return nullptr;
+ }
+ if (HTMLInputElement* inputElement =
+ HTMLInputElement::FromNodeOrNull(parent)) {
+ return inputElement->GetTextEditorWithoutCreation();
+ }
+ if (HTMLTextAreaElement* textareaElement =
+ HTMLTextAreaElement::FromNodeOrNull(parent)) {
+ return textareaElement->GetTextEditorWithoutCreation();
+ }
+ return nullptr;
+}
+
+// static
+bool nsContentUtils::IsNodeInEditableRegion(nsINode* aNode) {
+ while (aNode) {
+ if (aNode->IsEditable()) {
+ return true;
+ }
+ aNode = aNode->GetParent();
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue) {
+ if (IsForbiddenSystemRequestHeader(aHeader)) {
+ return true;
+ }
+
+ if ((nsContentUtils::IsOverrideMethodHeader(aHeader) &&
+ nsContentUtils::ContainsForbiddenMethod(aValue))) {
+ return true;
+ }
+
+ if (StringBeginsWith(aHeader, "proxy-"_ns,
+ nsCaseInsensitiveCStringComparator) ||
+ StringBeginsWith(aHeader, "sec-"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenSystemRequestHeader(const nsACString& aHeader) {
+ static const char* kInvalidHeaders[] = {"accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "date",
+ "dnt",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "set-cookie",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "via"};
+ for (auto& kInvalidHeader : kInvalidHeaders) {
+ if (aHeader.LowerCaseEqualsASCII(kInvalidHeader)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader) {
+ return (aHeader.LowerCaseEqualsASCII("set-cookie") ||
+ aHeader.LowerCaseEqualsASCII("set-cookie2"));
+}
+
+// static
+bool nsContentUtils::IsOverrideMethodHeader(const nsACString& headerName) {
+ return headerName.EqualsIgnoreCase("x-http-method-override") ||
+ headerName.EqualsIgnoreCase("x-http-method") ||
+ headerName.EqualsIgnoreCase("x-method-override");
+}
+
+// static
+bool nsContentUtils::ContainsForbiddenMethod(const nsACString& headerValue) {
+ bool hasInsecureMethod = false;
+ nsCCharSeparatedTokenizer tokenizer(headerValue, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring& value = tokenizer.nextToken();
+
+ if (value.EqualsIgnoreCase("connect") || value.EqualsIgnoreCase("trace") ||
+ value.EqualsIgnoreCase("track")) {
+ hasInsecureMethod = true;
+ break;
+ }
+ }
+
+ return hasInsecureMethod;
+}
+
+Maybe<nsContentUtils::ParsedRange> nsContentUtils::ParseSingleRangeRequest(
+ const nsACString& aHeaderValue, bool aAllowWhitespace) {
+ // See https://fetch.spec.whatwg.org/#simple-range-header-value
+ mozilla::Tokenizer p(aHeaderValue);
+ Maybe<uint64_t> rangeStart;
+ Maybe<uint64_t> rangeEnd;
+
+ // Step 2 and 3
+ if (!p.CheckWord("bytes")) {
+ return Nothing();
+ }
+
+ // Step 4
+ if (aAllowWhitespace) {
+ p.SkipWhites();
+ }
+
+ // Step 5 and 6
+ if (!p.CheckChar('=')) {
+ return Nothing();
+ }
+
+ // Step 7
+ if (aAllowWhitespace) {
+ p.SkipWhites();
+ }
+
+ // Step 8 and 9
+ uint64_t res;
+ if (p.ReadInteger(&res)) {
+ rangeStart = Some(res);
+ }
+
+ // Step 10
+ if (aAllowWhitespace) {
+ p.SkipWhites();
+ }
+
+ // Step 11
+ if (!p.CheckChar('-')) {
+ return Nothing();
+ }
+
+ // Step 13
+ if (aAllowWhitespace) {
+ p.SkipWhites();
+ }
+
+ // Step 14 and 15
+ if (p.ReadInteger(&res)) {
+ rangeEnd = Some(res);
+ }
+
+ // Step 16
+ if (!p.CheckEOF()) {
+ return Nothing();
+ }
+
+ // Step 17
+ if (!rangeStart && !rangeEnd) {
+ return Nothing();
+ }
+
+ // Step 18
+ if (rangeStart && rangeEnd && *rangeStart > *rangeEnd) {
+ return Nothing();
+ }
+
+ return Some(ParsedRange(rangeStart, rangeEnd));
+}
+
+// static
+bool nsContentUtils::IsCorsUnsafeRequestHeaderValue(
+ const nsACString& aHeaderValue) {
+ const char* cur = aHeaderValue.BeginReading();
+ const char* end = aHeaderValue.EndReading();
+
+ while (cur != end) {
+ // Implementation of
+ // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte Is less
+ // than a space but not a horizontal tab
+ if ((*cur < ' ' && *cur != '\t') || *cur == '"' || *cur == '(' ||
+ *cur == ')' || *cur == ':' || *cur == '<' || *cur == '>' ||
+ *cur == '?' || *cur == '@' || *cur == '[' || *cur == '\\' ||
+ *cur == ']' || *cur == '{' || *cur == '}' ||
+ *cur == 0x7F) { // 0x75 is DEL
+ return true;
+ }
+ cur++;
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsAccept(const nsACString& aHeaderValue) {
+ if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+ return false;
+ }
+ return true;
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsContentType(
+ const nsACString& aHeaderValue) {
+ nsAutoCString contentType;
+ nsAutoCString unused;
+
+ if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+ return false;
+ }
+
+ nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return contentType.LowerCaseEqualsLiteral("text/plain") ||
+ contentType.LowerCaseEqualsLiteral(
+ "application/x-www-form-urlencoded") ||
+ contentType.LowerCaseEqualsLiteral("multipart/form-data");
+}
+
+// static
+bool nsContentUtils::IsAllowedNonCorsLanguage(const nsACString& aHeaderValue) {
+ const char* cur = aHeaderValue.BeginReading();
+ const char* end = aHeaderValue.EndReading();
+
+ while (cur != end) {
+ if ((*cur >= '0' && *cur <= '9') || (*cur >= 'A' && *cur <= 'Z') ||
+ (*cur >= 'a' && *cur <= 'z') || *cur == ' ' || *cur == '*' ||
+ *cur == ',' || *cur == '-' || *cur == '.' || *cur == ';' ||
+ *cur == '=') {
+ cur++;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool nsContentUtils::IsAllowedNonCorsRange(const nsACString& aHeaderValue) {
+ Maybe<ParsedRange> parsedRange = ParseSingleRangeRequest(aHeaderValue, false);
+ if (!parsedRange) {
+ return false;
+ }
+
+ if (!parsedRange->Start()) {
+ return false;
+ }
+
+ return true;
+}
+
+// static
+bool nsContentUtils::IsCORSSafelistedRequestHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ // see https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+ if (aValue.Length() > 128) {
+ return false;
+ }
+ return (aName.LowerCaseEqualsLiteral("accept") &&
+ nsContentUtils::IsAllowedNonCorsAccept(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("accept-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("content-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("content-type") &&
+ nsContentUtils::IsAllowedNonCorsContentType(aValue)) ||
+ (aName.LowerCaseEqualsLiteral("range") &&
+ nsContentUtils::IsAllowedNonCorsRange(aValue));
+}
+
+mozilla::LogModule* nsContentUtils::ResistFingerprintingLog() {
+ return gResistFingerprintingLog;
+}
+mozilla::LogModule* nsContentUtils::DOMDumpLog() { return sDOMDumpLog; }
+
+bool nsContentUtils::GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const fallible_t& aFallible) {
+ aResult.Truncate();
+ return AppendNodeTextContent(aNode, aDeep, aResult, aFallible);
+}
+
+void nsContentUtils::GetNodeTextContent(const nsINode* aNode, bool aDeep,
+ nsAString& aResult) {
+ if (!GetNodeTextContent(aNode, aDeep, aResult, fallible)) {
+ NS_ABORT_OOM(0); // Unfortunately we don't know the allocation size
+ }
+}
+
+void nsContentUtils::DestroyMatchString(void* aData) {
+ if (aData) {
+ nsString* matchString = static_cast<nsString*>(aData);
+ delete matchString;
+ }
+}
+
+bool nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) {
+ // Table ordered from most to least likely JS MIME types.
+ static const char* jsTypes[] = {"text/javascript",
+ "text/ecmascript",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "application/x-ecmascript",
+ "text/javascript1.0",
+ "text/javascript1.1",
+ "text/javascript1.2",
+ "text/javascript1.3",
+ "text/javascript1.4",
+ "text/javascript1.5",
+ "text/jscript",
+ "text/livescript",
+ "text/x-ecmascript",
+ "text/x-javascript",
+ nullptr};
+
+ for (uint32_t i = 0; jsTypes[i]; ++i) {
+ if (aMIMEType.LowerCaseEqualsASCII(jsTypes[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsContentUtils::PrefetchPreloadEnabled(nsIDocShell* aDocShell) {
+ //
+ // SECURITY CHECK: disable prefetching and preloading from mailnews!
+ //
+ // walk up the docshell tree to see if any containing
+ // docshell are of type MAIL.
+ //
+
+ if (!aDocShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docshell = aDocShell;
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+
+ do {
+ auto appType = docshell->GetAppType();
+ if (appType == nsIDocShell::APP_TYPE_MAIL) {
+ return false; // do not prefetch, preload, preconnect from mailnews
+ }
+
+ docshell->GetInProcessParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ docshell = do_QueryInterface(parentItem);
+ if (!docshell) {
+ NS_ERROR("cannot get a docshell from a treeItem!");
+ return false;
+ }
+ }
+ } while (parentItem);
+
+ return true;
+}
+
+uint64_t nsContentUtils::GetInnerWindowID(nsIRequest* aRequest) {
+ // can't do anything if there's no nsIRequest!
+ if (!aRequest) {
+ return 0;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ if (NS_FAILED(rv) || !loadGroup) {
+ return 0;
+ }
+
+ return GetInnerWindowID(loadGroup);
+}
+
+uint64_t nsContentUtils::GetInnerWindowID(nsILoadGroup* aLoadGroup) {
+ if (!aLoadGroup) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsresult rv = aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv) || !callbacks) {
+ return 0;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (!loadContext) {
+ return 0;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ rv = loadContext->GetAssociatedWindow(getter_AddRefs(window));
+ if (NS_FAILED(rv) || !window) {
+ return 0;
+ }
+
+ auto* pwindow = nsPIDOMWindowOuter::From(window);
+ if (!pwindow) {
+ return 0;
+ }
+
+ nsPIDOMWindowInner* inner = pwindow->GetCurrentInnerWindow();
+ return inner ? inner->WindowID() : 0;
+}
+
+// static
+void nsContentUtils::MaybeFixIPv6Host(nsACString& aHost) {
+ if (aHost.FindChar(':') != -1) { // Escape IPv6 address
+ MOZ_ASSERT(!aHost.Length() ||
+ (aHost[0] != '[' && aHost[aHost.Length() - 1] != ']'));
+ aHost.Insert('[', 0);
+ aHost.Append(']');
+ }
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI,
+ nsACString& aHost) {
+ aHost.Truncate();
+ nsresult rv = aURI->GetHost(aHost);
+ if (NS_FAILED(rv)) { // Some URIs do not have a host
+ return rv;
+ }
+
+ MaybeFixIPv6Host(aHost);
+
+ return NS_OK;
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI,
+ nsAString& aHost) {
+ nsAutoCString hostname;
+ nsresult rv = GetHostOrIPv6WithBrackets(aURI, hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ CopyUTF8toUTF16(hostname, aHost);
+ return NS_OK;
+}
+
+nsresult nsContentUtils::GetHostOrIPv6WithBrackets(nsIPrincipal* aPrincipal,
+ nsACString& aHost) {
+ nsresult rv = aPrincipal->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) { // Some URIs do not have a host
+ return rv;
+ }
+
+ MaybeFixIPv6Host(aHost);
+ return NS_OK;
+}
+
+CallState nsContentUtils::CallOnAllRemoteChildren(
+ MessageBroadcaster* aManager,
+ const std::function<CallState(BrowserParent*)>& aCallback) {
+ uint32_t browserChildCount = aManager->ChildCount();
+ for (uint32_t j = 0; j < browserChildCount; ++j) {
+ RefPtr<MessageListenerManager> childMM = aManager->GetChildAt(j);
+ if (!childMM) {
+ continue;
+ }
+
+ RefPtr<MessageBroadcaster> nonLeafMM = MessageBroadcaster::From(childMM);
+ if (nonLeafMM) {
+ if (CallOnAllRemoteChildren(nonLeafMM, aCallback) == CallState::Stop) {
+ return CallState::Stop;
+ }
+ continue;
+ }
+
+ mozilla::dom::ipc::MessageManagerCallback* cb = childMM->GetCallback();
+ if (cb) {
+ nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
+ BrowserParent* remote = BrowserParent::GetFrom(fl);
+ if (remote && aCallback) {
+ if (aCallback(remote) == CallState::Stop) {
+ return CallState::Stop;
+ }
+ }
+ }
+ }
+
+ return CallState::Continue;
+}
+
+void nsContentUtils::CallOnAllRemoteChildren(
+ nsPIDOMWindowOuter* aWindow,
+ const std::function<CallState(BrowserParent*)>& aCallback) {
+ nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
+ if (window->IsChromeWindow()) {
+ RefPtr<MessageBroadcaster> windowMM = window->GetMessageManager();
+ if (windowMM) {
+ CallOnAllRemoteChildren(windowMM, aCallback);
+ }
+ }
+}
+
+bool nsContentUtils::IPCTransferableDataItemHasKnownFlavor(
+ const IPCTransferableDataItem& aItem) {
+ // Unknown types are converted to kCustomTypesMime.
+ if (aItem.flavor().EqualsASCII(kCustomTypesMime)) {
+ return true;
+ }
+
+ for (const char* format : DataTransfer::kKnownFormats) {
+ if (aItem.flavor().EqualsASCII(format)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsContentUtils::IPCTransferableDataToTransferable(
+ const IPCTransferableData& aTransferableData, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors) {
+ nsresult rv;
+ const nsTArray<IPCTransferableDataItem>& items = aTransferableData.items();
+ for (const auto& item : items) {
+ if (aFilterUnknownFlavors && !IPCTransferableDataItemHasKnownFlavor(item)) {
+ NS_WARNING(
+ "Ignoring unknown flavor in "
+ "nsContentUtils::IPCTransferableDataToTransferable");
+ continue;
+ }
+
+ if (aAddDataFlavor) {
+ aTransferable->AddDataFlavor(item.flavor().get());
+ }
+
+ nsCOMPtr<nsISupports> transferData;
+ switch (item.data().type()) {
+ case IPCTransferableDataType::TIPCTransferableDataString: {
+ const auto& data = item.data().get_IPCTransferableDataString();
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dataWrapper->SetData(nsDependentSubstring(
+ reinterpret_cast<const char16_t*>(data.data().Data()),
+ data.data().Size() / sizeof(char16_t)));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = dataWrapper;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataCString: {
+ const auto& data = item.data().get_IPCTransferableDataCString();
+ nsCOMPtr<nsISupportsCString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dataWrapper->SetData(nsDependentCSubstring(
+ reinterpret_cast<const char*>(data.data().Data()),
+ data.data().Size()));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = dataWrapper;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataInputStream: {
+ const auto& data = item.data().get_IPCTransferableDataInputStream();
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ AsChars(data.data().AsSpan()),
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = stream.forget();
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataImageContainer: {
+ const auto& data = item.data().get_IPCTransferableDataImageContainer();
+ nsCOMPtr<imgIContainer> container;
+ rv = DeserializeTransferableDataImageContainer(
+ data, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+ transferData = container;
+ break;
+ }
+ case IPCTransferableDataType::TIPCTransferableDataBlob: {
+ const auto& data = item.data().get_IPCTransferableDataBlob();
+ transferData = IPCBlobUtils::Deserialize(data.blob());
+ break;
+ }
+ case IPCTransferableDataType::T__None:
+ MOZ_ASSERT_UNREACHABLE();
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), transferData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult nsContentUtils::IPCTransferableToTransferable(
+ const IPCTransferable& aIPCTransferable, bool aAddDataFlavor,
+ nsITransferable* aTransferable, const bool aFilterUnknownFlavors) {
+ // Note that we need to set privacy status of transferable before adding any
+ // data into it.
+ aTransferable->SetIsPrivateData(aIPCTransferable.isPrivateData());
+
+ nsresult rv =
+ IPCTransferableDataToTransferable(aIPCTransferable.data(), aAddDataFlavor,
+ aTransferable, aFilterUnknownFlavors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIPCTransferable.cookieJarSettings().isSome()) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ net::CookieJarSettings::Deserialize(
+ aIPCTransferable.cookieJarSettings().ref(),
+ getter_AddRefs(cookieJarSettings));
+ aTransferable->SetCookieJarSettings(cookieJarSettings);
+ }
+ aTransferable->SetReferrerInfo(aIPCTransferable.referrerInfo());
+ aTransferable->SetRequestingPrincipal(aIPCTransferable.requestingPrincipal());
+ aTransferable->SetContentPolicyType(aIPCTransferable.contentPolicyType());
+
+ return NS_OK;
+}
+
+nsresult nsContentUtils::IPCTransferableDataItemToVariant(
+ const IPCTransferableDataItem& aItem, nsIWritableVariant* aVariant) {
+ MOZ_ASSERT(aVariant);
+
+ switch (aItem.data().type()) {
+ case IPCTransferableDataType::TIPCTransferableDataString: {
+ const auto& data = aItem.data().get_IPCTransferableDataString();
+ return aVariant->SetAsAString(nsDependentSubstring(
+ reinterpret_cast<const char16_t*>(data.data().Data()),
+ data.data().Size() / sizeof(char16_t)));
+ }
+ case IPCTransferableDataType::TIPCTransferableDataCString: {
+ const auto& data = aItem.data().get_IPCTransferableDataCString();
+ return aVariant->SetAsACString(nsDependentCSubstring(
+ reinterpret_cast<const char*>(data.data().Data()),
+ data.data().Size()));
+ }
+ case IPCTransferableDataType::TIPCTransferableDataInputStream: {
+ const auto& data = aItem.data().get_IPCTransferableDataInputStream();
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ AsChars(data.data().AsSpan()),
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aVariant->SetAsISupports(stream);
+ }
+ case IPCTransferableDataType::TIPCTransferableDataImageContainer: {
+ const auto& data = aItem.data().get_IPCTransferableDataImageContainer();
+ nsCOMPtr<imgIContainer> container;
+ nsresult rv = DeserializeTransferableDataImageContainer(
+ data, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aVariant->SetAsISupports(container);
+ }
+ case IPCTransferableDataType::TIPCTransferableDataBlob: {
+ const auto& data = aItem.data().get_IPCTransferableDataBlob();
+ RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(data.blob());
+ return aVariant->SetAsISupports(blobImpl);
+ }
+ case IPCTransferableDataType::T__None:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsContentUtils::TransferablesToIPCTransferableDatas(
+ nsIArray* aTransferables, nsTArray<IPCTransferableData>& aIPC,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ aIPC.Clear();
+ if (aTransferables) {
+ uint32_t transferableCount = 0;
+ aTransferables->GetLength(&transferableCount);
+ for (uint32_t i = 0; i < transferableCount; ++i) {
+ IPCTransferableData* dt = aIPC.AppendElement();
+ nsCOMPtr<nsITransferable> transferable =
+ do_QueryElementAt(aTransferables, i);
+ TransferableToIPCTransferableData(transferable, dt, aInSyncMessage,
+ aParent);
+ }
+ }
+}
+
+nsresult nsContentUtils::CalculateBufferSizeForImage(
+ const uint32_t& aStride, const IntSize& aImageSize,
+ const SurfaceFormat& aFormat, size_t* aMaxBufferSize,
+ size_t* aUsedBufferSize) {
+ CheckedInt32 requiredBytes =
+ CheckedInt32(aStride) * CheckedInt32(aImageSize.height);
+
+ CheckedInt32 usedBytes =
+ requiredBytes - aStride +
+ (CheckedInt32(aImageSize.width) * BytesPerPixel(aFormat));
+ if (!usedBytes.isValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(requiredBytes.isValid(), "usedBytes valid but not required?");
+ *aMaxBufferSize = requiredBytes.value();
+ *aUsedBufferSize = usedBytes.value();
+ return NS_OK;
+}
+
+static already_AddRefed<DataSourceSurface> BigBufferToDataSurface(
+ BigBuffer& aData, uint32_t aStride, const IntSize& aImageSize,
+ SurfaceFormat aFormat) {
+ if (!aData.Size() || !aImageSize.width || !aImageSize.height) {
+ return nullptr;
+ }
+
+ // Validate shared memory buffer size
+ size_t imageBufLen = 0;
+ size_t maxBufLen = 0;
+ if (NS_FAILED(nsContentUtils::CalculateBufferSizeForImage(
+ aStride, aImageSize, aFormat, &maxBufLen, &imageBufLen))) {
+ return nullptr;
+ }
+ if (imageBufLen > aData.Size()) {
+ return nullptr;
+ }
+ return CreateDataSourceSurfaceFromData(aImageSize, aFormat, aData.Data(),
+ aStride);
+}
+
+nsresult nsContentUtils::DeserializeTransferableDataImageContainer(
+ const IPCTransferableDataImageContainer& aData,
+ imgIContainer** aContainer) {
+ const IntSize size(aData.width(), aData.height());
+ size_t maxBufferSize = 0;
+ size_t usedBufferSize = 0;
+ nsresult rv = CalculateBufferSizeForImage(
+ aData.stride(), size, aData.format(), &maxBufferSize, &usedBufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (usedBufferSize > aData.data().Size()) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<DataSourceSurface> surface =
+ CreateDataSourceSurfaceFromData(size, aData.format(), aData.data().Data(),
+ static_cast<int32_t>(aData.stride()));
+ if (!surface) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size);
+ nsCOMPtr<imgIContainer> imageContainer =
+ image::ImageOps::CreateFromDrawable(drawable);
+ imageContainer.forget(aContainer);
+
+ return NS_OK;
+}
+
+bool nsContentUtils::IsFlavorImage(const nsACString& aFlavor) {
+ return aFlavor.EqualsLiteral(kNativeImageMime) ||
+ aFlavor.EqualsLiteral(kJPEGImageMime) ||
+ aFlavor.EqualsLiteral(kJPGImageMime) ||
+ aFlavor.EqualsLiteral(kPNGImageMime) ||
+ aFlavor.EqualsLiteral(kGIFImageMime);
+}
+
+// FIXME: This can probably be removed once bug 1783240 lands, as `nsString`
+// will be implicitly serialized in shmem when sent over IPDL directly.
+static IPCTransferableDataString AsIPCTransferableDataString(
+ Span<const char16_t> aInput) {
+ return IPCTransferableDataString{BigBuffer(AsBytes(aInput))};
+}
+
+// FIXME: This can probably be removed once bug 1783240 lands, as `nsCString`
+// will be implicitly serialized in shmem when sent over IPDL directly.
+static IPCTransferableDataCString AsIPCTransferableDataCString(
+ Span<const char> aInput) {
+ return IPCTransferableDataCString{BigBuffer(AsBytes(aInput))};
+}
+
+void nsContentUtils::TransferableToIPCTransferableData(
+ nsITransferable* aTransferable, IPCTransferableData* aTransferableData,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), aParent);
+
+ if (aTransferable) {
+ nsTArray<nsCString> flavorList;
+ aTransferable->FlavorsTransferableCanExport(flavorList);
+
+ for (uint32_t j = 0; j < flavorList.Length(); ++j) {
+ nsCString& flavorStr = flavorList[j];
+ if (!flavorStr.Length()) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> data;
+ nsresult rv =
+ aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+
+ if (NS_FAILED(rv) || !data) {
+ if (aInSyncMessage) {
+ // Can't do anything.
+ // FIXME: This shouldn't be the case anymore!
+ continue;
+ }
+
+ // This is a hack to support kFilePromiseMime.
+ // On Windows there just needs to be an entry for it,
+ // and for OSX we need to create
+ // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
+ if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() =
+ AsIPCTransferableDataString(NS_ConvertUTF8toUTF16(flavorStr));
+ continue;
+ }
+
+ // Empty element, transfer only the flavor
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataString(EmptyString());
+ continue;
+ }
+
+ // We need to handle nsIInputStream before nsISupportsCString, otherwise
+ // nsStringInputStream would be converted into a wrong type.
+ if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(data)) {
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ nsCString imageData;
+ DebugOnly<nsresult> rv =
+ NS_ConsumeStream(stream, UINT32_MAX, imageData);
+ MOZ_ASSERT(
+ rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "cannot use async input streams in nsITransferable right now");
+ // FIXME: This can probably be simplified once bug 1783240 lands, as
+ // `nsCString` will be implicitly serialized in shmem when sent over
+ // IPDL directly.
+ item->data() =
+ IPCTransferableDataInputStream(BigBuffer(AsBytes(Span(imageData))));
+ continue;
+ }
+
+ if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(data)) {
+ nsAutoString dataAsString;
+ MOZ_ALWAYS_SUCCEEDS(text->GetData(dataAsString));
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataString(dataAsString);
+ continue;
+ }
+
+ if (nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data)) {
+ nsAutoCString dataAsString;
+ MOZ_ALWAYS_SUCCEEDS(ctext->GetData(dataAsString));
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = AsIPCTransferableDataCString(dataAsString);
+ continue;
+ }
+
+ if (nsCOMPtr<imgIContainer> image = do_QueryInterface(data)) {
+ // Images to be placed on the clipboard are imgIContainers.
+ RefPtr<mozilla::gfx::SourceSurface> surface = image->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ if (!surface) {
+ continue;
+ }
+ RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface();
+ if (!dataSurface) {
+ continue;
+ }
+ size_t length;
+ int32_t stride;
+ Maybe<BigBuffer> surfaceData =
+ GetSurfaceData(*dataSurface, &length, &stride);
+
+ if (surfaceData.isNothing()) {
+ continue;
+ }
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+
+ mozilla::gfx::IntSize size = dataSurface->GetSize();
+ item->data() = IPCTransferableDataImageContainer(
+ std::move(*surfaceData), size.width, size.height, stride,
+ dataSurface->GetFormat());
+ continue;
+ }
+
+ // Otherwise, handle this as a file.
+ nsCOMPtr<BlobImpl> blobImpl;
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
+ if (aParent) {
+ bool isDir = false;
+ if (NS_SUCCEEDED(file->IsDirectory(&isDir)) && isDir) {
+ nsAutoString path;
+ if (NS_WARN_IF(NS_FAILED(file->GetPath(path)))) {
+ continue;
+ }
+
+ RefPtr<FileSystemSecurity> fss = FileSystemSecurity::GetOrCreate();
+ fss->GrantAccessToContentProcess(aParent->ChildID(), path);
+ }
+ }
+
+ blobImpl = new FileBlobImpl(file);
+
+ IgnoredErrorResult rv;
+
+ // Ensure that file data is cached no that the content process
+ // has this data available to it when passed over:
+ blobImpl->GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ continue;
+ }
+
+ blobImpl->GetLastModified(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ continue;
+ }
+ } else {
+ if (aInSyncMessage) {
+ // Can't do anything.
+ // FIXME: This shouldn't be the case anymore!
+ continue;
+ }
+
+ blobImpl = do_QueryInterface(data);
+ }
+
+ if (blobImpl) {
+ // If we failed to create the blob actor, then this blob probably
+ // can't get the file size for the underlying file, ignore it for
+ // now. TODO pass this through anyway.
+ IPCBlob ipcBlob;
+ nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ IPCTransferableDataItem* item =
+ aTransferableData->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = IPCTransferableDataBlob(ipcBlob);
+ }
+ }
+ }
+}
+
+void nsContentUtils::TransferableToIPCTransferable(
+ nsITransferable* aTransferable, IPCTransferable* aIPCTransferable,
+ bool aInSyncMessage, mozilla::dom::ContentParent* aParent) {
+ IPCTransferableData ipcTransferableData;
+ TransferableToIPCTransferableData(aTransferable, &ipcTransferableData,
+ aInSyncMessage, aParent);
+
+ Maybe<net::CookieJarSettingsArgs> cookieJarSettingsArgs;
+ if (nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aTransferable->GetCookieJarSettings()) {
+ net::CookieJarSettingsArgs args;
+ net::CookieJarSettings::Cast(cookieJarSettings)->Serialize(args);
+ cookieJarSettingsArgs = Some(std::move(args));
+ }
+
+ aIPCTransferable->data() = std::move(ipcTransferableData);
+ aIPCTransferable->isPrivateData() = aTransferable->GetIsPrivateData();
+ aIPCTransferable->requestingPrincipal() =
+ aTransferable->GetRequestingPrincipal();
+ aIPCTransferable->cookieJarSettings() = std::move(cookieJarSettingsArgs);
+ aIPCTransferable->contentPolicyType() = aTransferable->GetContentPolicyType();
+ aIPCTransferable->referrerInfo() = aTransferable->GetReferrerInfo();
+}
+
+Maybe<BigBuffer> nsContentUtils::GetSurfaceData(DataSourceSurface& aSurface,
+ size_t* aLength,
+ int32_t* aStride) {
+ mozilla::gfx::DataSourceSurface::MappedSurface map;
+ if (!aSurface.Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map)) {
+ return Nothing();
+ }
+
+ size_t bufLen = 0;
+ size_t maxBufLen = 0;
+ nsresult rv = nsContentUtils::CalculateBufferSizeForImage(
+ map.mStride, aSurface.GetSize(), aSurface.GetFormat(), &maxBufLen,
+ &bufLen);
+ if (NS_FAILED(rv)) {
+ aSurface.Unmap();
+ return Nothing();
+ }
+
+ BigBuffer surfaceData(maxBufLen);
+ memcpy(surfaceData.Data(), map.mData, bufLen);
+ memset(surfaceData.Data() + bufLen, 0, maxBufLen - bufLen);
+
+ *aLength = maxBufLen;
+ *aStride = map.mStride;
+
+ aSurface.Unmap();
+ return Some(std::move(surfaceData));
+}
+
+Maybe<IPCImage> nsContentUtils::SurfaceToIPCImage(DataSourceSurface& aSurface) {
+ size_t len = 0;
+ int32_t stride = 0;
+ auto mem = GetSurfaceData(aSurface, &len, &stride);
+ if (!mem) {
+ return Nothing();
+ }
+ return Some(IPCImage{std::move(*mem), uint32_t(stride), aSurface.GetFormat(),
+ ImageIntSize::FromUnknownSize(aSurface.GetSize())});
+}
+
+already_AddRefed<DataSourceSurface> nsContentUtils::IPCImageToSurface(
+ IPCImage&& aImage) {
+ return BigBufferToDataSurface(aImage.data(), aImage.stride(),
+ aImage.size().ToUnknownSize(), aImage.format());
+}
+
+Modifiers nsContentUtils::GetWidgetModifiers(int32_t aModifiers) {
+ Modifiers result = 0;
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SHIFT) {
+ result |= mozilla::MODIFIER_SHIFT;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_CONTROL) {
+ result |= mozilla::MODIFIER_CONTROL;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALT) {
+ result |= mozilla::MODIFIER_ALT;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_META) {
+ result |= mozilla::MODIFIER_META;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALTGRAPH) {
+ result |= mozilla::MODIFIER_ALTGRAPH;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_CAPSLOCK) {
+ result |= mozilla::MODIFIER_CAPSLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_FN) {
+ result |= mozilla::MODIFIER_FN;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_FNLOCK) {
+ result |= mozilla::MODIFIER_FNLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_NUMLOCK) {
+ result |= mozilla::MODIFIER_NUMLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SCROLLLOCK) {
+ result |= mozilla::MODIFIER_SCROLLLOCK;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOL) {
+ result |= mozilla::MODIFIER_SYMBOL;
+ }
+ if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK) {
+ result |= mozilla::MODIFIER_SYMBOLLOCK;
+ }
+ return result;
+}
+
+nsIWidget* nsContentUtils::GetWidget(PresShell* aPresShell, nsPoint* aOffset) {
+ if (!aPresShell) {
+ return nullptr;
+ }
+ nsIFrame* frame = aPresShell->GetRootFrame();
+ if (!frame) {
+ return nullptr;
+ }
+ return frame->GetView()->GetNearestWidget(aOffset);
+}
+
+int16_t nsContentUtils::GetButtonsFlagForButton(int32_t aButton) {
+ switch (aButton) {
+ case -1:
+ return MouseButtonsFlag::eNoButtons;
+ case MouseButton::ePrimary:
+ return MouseButtonsFlag::ePrimaryFlag;
+ case MouseButton::eMiddle:
+ return MouseButtonsFlag::eMiddleFlag;
+ case MouseButton::eSecondary:
+ return MouseButtonsFlag::eSecondaryFlag;
+ case 3:
+ return MouseButtonsFlag::e4thFlag;
+ case 4:
+ return MouseButtonsFlag::e5thFlag;
+ case MouseButton::eEraser:
+ return MouseButtonsFlag::eEraserFlag;
+ default:
+ NS_ERROR("Button not known.");
+ return 0;
+ }
+}
+
+LayoutDeviceIntPoint nsContentUtils::ToWidgetPoint(
+ const CSSPoint& aPoint, const nsPoint& aOffset,
+ nsPresContext* aPresContext) {
+ nsPoint layoutRelative = CSSPoint::ToAppUnits(aPoint) + aOffset;
+ nsPoint visualRelative =
+ ViewportUtils::LayoutToVisual(layoutRelative, aPresContext->PresShell());
+ return LayoutDeviceIntPoint::FromAppUnitsRounded(
+ visualRelative, aPresContext->AppUnitsPerDevPixel());
+}
+
+nsView* nsContentUtils::GetViewToDispatchEvent(nsPresContext* aPresContext,
+ PresShell** aPresShell) {
+ if (!aPresContext || !aPresShell) {
+ return nullptr;
+ }
+ RefPtr<PresShell> presShell = aPresContext->PresShell();
+ if (NS_WARN_IF(!presShell)) {
+ *aPresShell = nullptr;
+ return nullptr;
+ }
+ nsViewManager* viewManager = presShell->GetViewManager();
+ if (!viewManager) {
+ presShell.forget(aPresShell); // XXX Is this intentional?
+ return nullptr;
+ }
+ presShell.forget(aPresShell);
+ return viewManager->GetRootView();
+}
+
+nsresult nsContentUtils::SendMouseEvent(
+ mozilla::PresShell* aPresShell, const nsAString& aType, float aX, float aY,
+ int32_t aButton, int32_t aButtons, int32_t aClickCount, int32_t aModifiers,
+ bool aIgnoreRootScrollFrame, float aPressure,
+ unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
+ PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized) {
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
+ if (!widget) return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
+ bool contextMenuKey = false;
+ if (aType.EqualsLiteral("mousedown")) {
+ msg = eMouseDown;
+ } else if (aType.EqualsLiteral("mouseup")) {
+ msg = eMouseUp;
+ } else if (aType.EqualsLiteral("mousemove")) {
+ msg = eMouseMove;
+ } else if (aType.EqualsLiteral("mouseover")) {
+ msg = eMouseEnterIntoWidget;
+ } else if (aType.EqualsLiteral("mouseout")) {
+ msg = eMouseExitFromWidget;
+ exitFrom = Some(WidgetMouseEvent::ePlatformChild);
+ } else if (aType.EqualsLiteral("mousecancel")) {
+ msg = eMouseExitFromWidget;
+ exitFrom = Some(XRE_IsParentProcess() ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePuppet);
+ } else if (aType.EqualsLiteral("mouselongtap")) {
+ msg = eMouseLongTap;
+ } else if (aType.EqualsLiteral("contextmenu")) {
+ msg = eContextMenu;
+ contextMenuKey = (aButton == 0);
+ } else if (aType.EqualsLiteral("MozMouseHittest")) {
+ msg = eMouseHitTest;
+ } else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
+ msg = eMouseExploreByTouch;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aInputSourceArg == MouseEvent_Binding::MOZ_SOURCE_UNKNOWN) {
+ aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
+ }
+
+ WidgetMouseEvent event(true, msg, widget,
+ aIsWidgetEventSynthesized
+ ? WidgetMouseEvent::eSynthesized
+ : WidgetMouseEvent::eReal,
+ contextMenuKey ? WidgetMouseEvent::eContextMenuKey
+ : WidgetMouseEvent::eNormal);
+ event.pointerId = aIdentifier;
+ event.mModifiers = GetWidgetModifiers(aModifiers);
+ event.mButton = aButton;
+ event.mButtons = aButtons != nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED
+ ? aButtons
+ : msg == eMouseUp ? 0
+ : GetButtonsFlagForButton(aButton);
+ event.mPressure = aPressure;
+ event.mInputSource = aInputSourceArg;
+ event.mClickCount = aClickCount;
+ event.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized;
+ event.mExitFrom = exitFrom;
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ if (!presContext) return NS_ERROR_FAILURE;
+
+ event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+ event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (aToWindow) {
+ RefPtr<PresShell> presShell;
+ nsView* view =
+ GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+ if (StaticPrefs::test_events_async_enabled()) {
+ status = widget->DispatchInputEvent(&event).mContentStatus;
+ } else {
+ nsresult rv = widget->DispatchEvent(&event, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aPreventDefault) {
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ if (event.mFlags.mDefaultPreventedByContent) {
+ *aPreventDefault = PreventDefaultResult::ByContent;
+ } else {
+ *aPreventDefault = PreventDefaultResult::ByChrome;
+ }
+ } else {
+ *aPreventDefault = PreventDefaultResult::No;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::FirePageHideEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem, EventTarget* aChromeEventHandler,
+ bool aOnlySystemGroup) {
+ MOZ_DIAGNOSTIC_ASSERT(aItem);
+ MOZ_DIAGNOSTIC_ASSERT(aChromeEventHandler);
+
+ RefPtr<Document> doc = aItem->GetDocument();
+ NS_ASSERTION(doc, "What happened here?");
+ doc->OnPageHide(true, aChromeEventHandler, aOnlySystemGroup);
+
+ int32_t childCount = 0;
+ aItem->GetInProcessChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetInProcessChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageHideEventForFrameLoaderSwap(kids[i], aChromeEventHandler,
+ aOnlySystemGroup);
+ }
+ }
+}
+
+// The pageshow event is fired for a given document only if IsShowing() returns
+// the same thing as aFireIfShowing. This gives us a way to fire pageshow only
+// on documents that are still loading or only on documents that are already
+// loaded.
+/* static */
+void nsContentUtils::FirePageShowEventForFrameLoaderSwap(
+ nsIDocShellTreeItem* aItem, EventTarget* aChromeEventHandler,
+ bool aFireIfShowing, bool aOnlySystemGroup) {
+ int32_t childCount = 0;
+ aItem->GetInProcessChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetInProcessChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageShowEventForFrameLoaderSwap(kids[i], aChromeEventHandler,
+ aFireIfShowing, aOnlySystemGroup);
+ }
+ }
+
+ RefPtr<Document> doc = aItem->GetDocument();
+ if (doc && doc->IsShowing() == aFireIfShowing) {
+ doc->OnPageShow(true, aChromeEventHandler, aOnlySystemGroup);
+ }
+}
+
+/* static */
+already_AddRefed<nsPIWindowRoot> nsContentUtils::GetWindowRoot(Document* aDoc) {
+ if (aDoc) {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ return win->GetTopWindowRoot();
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+bool nsContentUtils::LinkContextIsURI(const nsAString& aAnchor,
+ nsIURI* aDocURI) {
+ if (aAnchor.IsEmpty()) {
+ // anchor parameter not present or empty -> same document reference
+ return true;
+ }
+
+ // the document URI might contain a fragment identifier ("#...')
+ // we want to ignore that because it's invisible to the server
+ // and just affects the local interpretation in the recipient
+ nsCOMPtr<nsIURI> contextUri;
+ nsresult rv = NS_GetURIWithoutRef(aDocURI, getter_AddRefs(contextUri));
+
+ if (NS_FAILED(rv)) {
+ // copying failed
+ return false;
+ }
+
+ // resolve anchor against context
+ nsCOMPtr<nsIURI> resolvedUri;
+ rv = NS_NewURI(getter_AddRefs(resolvedUri), aAnchor, nullptr, contextUri);
+
+ if (NS_FAILED(rv)) {
+ // resolving failed
+ return false;
+ }
+
+ bool same;
+ rv = contextUri->Equals(resolvedUri, &same);
+ if (NS_FAILED(rv)) {
+ // comparison failed
+ return false;
+ }
+
+ return same;
+}
+
+/* static */
+bool nsContentUtils::IsPreloadType(nsContentPolicyType aType) {
+ return (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD);
+}
+
+// static
+ReferrerPolicy nsContentUtils::GetReferrerPolicyFromChannel(
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ return ReferrerPolicy::_empty;
+ }
+
+ nsresult rv;
+ nsAutoCString headerValue;
+ rv = httpChannel->GetResponseHeader("referrer-policy"_ns, headerValue);
+ if (NS_FAILED(rv) || headerValue.IsEmpty()) {
+ return ReferrerPolicy::_empty;
+ }
+
+ return ReferrerInfo::ReferrerPolicyFromHeaderString(
+ NS_ConvertUTF8toUTF16(headerValue));
+}
+
+// static
+bool nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel) {
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsContentPolicyType type = loadInfo->InternalContentPolicyType();
+ return IsNonSubresourceInternalPolicyType(type);
+}
+
+// static
+bool nsContentUtils::IsNonSubresourceInternalPolicyType(
+ nsContentPolicyType aType) {
+ return aType == nsIContentPolicy::TYPE_DOCUMENT ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+}
+
+// static public
+bool nsContentUtils::IsThirdPartyTrackingResourceWindow(
+ nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ Document* document = aWindow->GetExtantDoc();
+ if (!document) {
+ return false;
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(document->GetChannel());
+ if (!classifiedChannel) {
+ return false;
+ }
+
+ return classifiedChannel->IsThirdPartyTrackingResource();
+}
+
+// static public
+bool nsContentUtils::IsFirstPartyTrackingResourceWindow(
+ nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ Document* document = aWindow->GetExtantDoc();
+ if (!document) {
+ return false;
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(document->GetChannel());
+ if (!classifiedChannel) {
+ return false;
+ }
+
+ uint32_t classificationFlags =
+ classifiedChannel->GetFirstPartyClassificationFlags();
+
+ return mozilla::net::UrlClassifierCommon::IsTrackingClassificationFlag(
+ classificationFlags, NS_UsePrivateBrowsing(document->GetChannel()));
+}
+
+namespace {
+
+// We put StringBuilder in the anonymous namespace to prevent anything outside
+// this file from accidentally being linked against it.
+class BulkAppender {
+ using size_type = typename nsAString::size_type;
+
+ public:
+ explicit BulkAppender(BulkWriteHandle<char16_t>&& aHandle)
+ : mHandle(std::move(aHandle)), mPosition(0) {}
+ ~BulkAppender() = default;
+
+ template <int N>
+ void AppendLiteral(const char16_t (&aStr)[N]) {
+ size_t len = N - 1;
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ memcpy(mHandle.Elements() + mPosition, aStr, len * sizeof(char16_t));
+ mPosition += len;
+ }
+
+ void Append(Span<const char16_t> aStr) {
+ size_t len = aStr.Length();
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ // Both mHandle.Elements() and aStr.Elements() are guaranteed
+ // to be non-null (by the string implementation and by Span,
+ // respectively), so not checking the pointers for null before
+ // memcpy does not lead to UB even if len was zero.
+ memcpy(mHandle.Elements() + mPosition, aStr.Elements(),
+ len * sizeof(char16_t));
+ mPosition += len;
+ }
+
+ void Append(Span<const char> aStr) {
+ size_t len = aStr.Length();
+ MOZ_ASSERT(mPosition + len <= mHandle.Length());
+ ConvertLatin1toUtf16(aStr, mHandle.AsSpan().From(mPosition));
+ mPosition += len;
+ }
+
+ void Finish() { mHandle.Finish(mPosition, false); }
+
+ private:
+ BulkWriteHandle<char16_t> mHandle;
+ size_type mPosition;
+};
+
+class StringBuilder {
+ private:
+ class Unit {
+ public:
+ Unit() : mAtom(nullptr) { MOZ_COUNT_CTOR(StringBuilder::Unit); }
+ ~Unit() {
+ if (mType == Type::String || mType == Type::StringWithEncode) {
+ mString.~nsString();
+ }
+ MOZ_COUNT_DTOR(StringBuilder::Unit);
+ }
+
+ enum class Type : uint8_t {
+ Unknown,
+ Atom,
+ String,
+ StringWithEncode,
+ Literal,
+ TextFragment,
+ TextFragmentWithEncode,
+ };
+
+ struct LiteralSpan {
+ const char16_t* mData;
+ uint32_t mLength;
+
+ Span<const char16_t> AsSpan() { return Span(mData, mLength); }
+ };
+
+ union {
+ nsAtom* mAtom;
+ LiteralSpan mLiteral;
+ nsString mString;
+ const nsTextFragment* mTextFragment;
+ };
+ Type mType = Type::Unknown;
+ };
+
+ static_assert(sizeof(void*) != 8 || sizeof(Unit) <= 3 * sizeof(void*),
+ "Unit should remain small");
+
+ public:
+ // Try to keep the size of StringBuilder close to a jemalloc bucket size (the
+ // 16kb one in this case).
+ static constexpr uint32_t TARGET_SIZE = 16 * 1024;
+
+ // The number of units we need to remove from the inline buffer so that the
+ // rest of the builder members fit. A more precise approach would be to
+ // calculate that extra size and use (TARGET_SIZE - OTHER_SIZE) / sizeof(Unit)
+ // or so, but this is simpler.
+ static constexpr uint32_t PADDING_UNITS = sizeof(void*) == 8 ? 1 : 2;
+
+ static constexpr uint32_t STRING_BUFFER_UNITS =
+ TARGET_SIZE / sizeof(Unit) - PADDING_UNITS;
+
+ StringBuilder() : mLast(this), mLength(0) { MOZ_COUNT_CTOR(StringBuilder); }
+
+ MOZ_COUNTED_DTOR(StringBuilder)
+
+ void Append(nsAtom* aAtom) {
+ Unit* u = AddUnit();
+ u->mAtom = aAtom;
+ u->mType = Unit::Type::Atom;
+ uint32_t len = aAtom->GetLength();
+ mLength += len;
+ }
+
+ template <int N>
+ void Append(const char16_t (&aLiteral)[N]) {
+ constexpr uint32_t len = N - 1;
+ Unit* u = AddUnit();
+ u->mLiteral = {aLiteral, len};
+ u->mType = Unit::Type::Literal;
+ mLength += len;
+ }
+
+ void Append(nsString&& aString) {
+ Unit* u = AddUnit();
+ uint32_t len = aString.Length();
+ new (&u->mString) nsString(std::move(aString));
+ u->mType = Unit::Type::String;
+ mLength += len;
+ }
+
+ // aLen can be !isValid(), which will get propagated into mLength.
+ void AppendWithAttrEncode(nsString&& aString, CheckedInt<uint32_t> aLen) {
+ Unit* u = AddUnit();
+ new (&u->mString) nsString(std::move(aString));
+ u->mType = Unit::Type::StringWithEncode;
+ mLength += aLen;
+ }
+
+ void Append(const nsTextFragment* aTextFragment) {
+ Unit* u = AddUnit();
+ u->mTextFragment = aTextFragment;
+ u->mType = Unit::Type::TextFragment;
+ uint32_t len = aTextFragment->GetLength();
+ mLength += len;
+ }
+
+ // aLen can be !isValid(), which will get propagated into mLength.
+ void AppendWithEncode(const nsTextFragment* aTextFragment,
+ CheckedInt<uint32_t> aLen) {
+ Unit* u = AddUnit();
+ u->mTextFragment = aTextFragment;
+ u->mType = Unit::Type::TextFragmentWithEncode;
+ mLength += aLen;
+ }
+
+ bool ToString(nsAString& aOut) {
+ if (!mLength.isValid()) {
+ return false;
+ }
+ auto appenderOrErr = aOut.BulkWrite(mLength.value(), 0, true);
+ if (appenderOrErr.isErr()) {
+ return false;
+ }
+
+ BulkAppender appender{appenderOrErr.unwrap()};
+
+ for (StringBuilder* current = this; current;
+ current = current->mNext.get()) {
+ uint32_t len = current->mUnits.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ Unit& u = current->mUnits[i];
+ switch (u.mType) {
+ case Unit::Type::Atom:
+ appender.Append(*(u.mAtom));
+ break;
+ case Unit::Type::String:
+ appender.Append(u.mString);
+ break;
+ case Unit::Type::StringWithEncode:
+ EncodeAttrString(u.mString, appender);
+ break;
+ case Unit::Type::Literal:
+ appender.Append(u.mLiteral.AsSpan());
+ break;
+ case Unit::Type::TextFragment:
+ if (u.mTextFragment->Is2b()) {
+ appender.Append(
+ Span(u.mTextFragment->Get2b(), u.mTextFragment->GetLength()));
+ } else {
+ appender.Append(
+ Span(u.mTextFragment->Get1b(), u.mTextFragment->GetLength()));
+ }
+ break;
+ case Unit::Type::TextFragmentWithEncode:
+ if (u.mTextFragment->Is2b()) {
+ EncodeTextFragment(
+ Span(u.mTextFragment->Get2b(), u.mTextFragment->GetLength()),
+ appender);
+ } else {
+ EncodeTextFragment(
+ Span(u.mTextFragment->Get1b(), u.mTextFragment->GetLength()),
+ appender);
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown unit type?");
+ }
+ }
+ }
+ appender.Finish();
+ return true;
+ }
+
+ private:
+ Unit* AddUnit() {
+ if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
+ new StringBuilder(this);
+ }
+ return mLast->mUnits.AppendElement();
+ }
+
+ explicit StringBuilder(StringBuilder* aFirst) : mLast(nullptr), mLength(0) {
+ MOZ_COUNT_CTOR(StringBuilder);
+ aFirst->mLast->mNext = WrapUnique(this);
+ aFirst->mLast = this;
+ }
+
+ void EncodeAttrString(Span<const char16_t> aStr, BulkAppender& aAppender) {
+ size_t flushedUntil = 0;
+ size_t currentPosition = 0;
+ for (char16_t c : aStr) {
+ switch (c) {
+ case '"':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&quot;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '&':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&amp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case 0x00A0:
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&nbsp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ }
+ if (currentPosition > flushedUntil) {
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ }
+ }
+
+ template <class T>
+ void EncodeTextFragment(Span<const T> aStr, BulkAppender& aAppender) {
+ size_t flushedUntil = 0;
+ size_t currentPosition = 0;
+ for (T c : aStr) {
+ switch (c) {
+ case '<':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&lt;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '>':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&gt;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case '&':
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&amp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ case T(0xA0):
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ aAppender.AppendLiteral(u"&nbsp;");
+ flushedUntil = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ }
+ if (currentPosition > flushedUntil) {
+ aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
+ }
+ }
+
+ AutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
+ UniquePtr<StringBuilder> mNext;
+ StringBuilder* mLast;
+ // mLength is used only in the first StringBuilder object in the linked list.
+ CheckedInt<uint32_t> mLength;
+};
+
+static_assert(sizeof(StringBuilder) <= StringBuilder::TARGET_SIZE,
+ "StringBuilder should fit in the target bucket");
+
+} // namespace
+
+static void AppendEncodedCharacters(const nsTextFragment* aText,
+ StringBuilder& aBuilder) {
+ uint32_t numEncodedChars = 0;
+ uint32_t len = aText->GetLength();
+ if (aText->Is2b()) {
+ const char16_t* data = aText->Get2b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const char16_t c = data[i];
+ switch (c) {
+ case '<':
+ case '>':
+ case '&':
+ case 0x00A0:
+ ++numEncodedChars;
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ const char* data = aText->Get1b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const unsigned char c = data[i];
+ switch (c) {
+ case '<':
+ case '>':
+ case '&':
+ case 0x00A0:
+ ++numEncodedChars;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (numEncodedChars) {
+ // For simplicity, conservatively estimate the size of the string after
+ // encoding. This will result in reserving more memory than we actually
+ // need, but that should be fine unless the string has an enormous number of
+ // eg < in it. We subtract 1 for the null terminator, then 1 more for the
+ // existing character that will be replaced.
+ constexpr uint32_t maxCharExtraSpace =
+ std::max({ArrayLength("&lt;"), ArrayLength("&gt;"),
+ ArrayLength("&amp;"), ArrayLength("&nbsp;")}) -
+ 2;
+ static_assert(maxCharExtraSpace < 100, "Possible underflow");
+ CheckedInt<uint32_t> maxExtraSpace =
+ CheckedInt<uint32_t>(numEncodedChars) * maxCharExtraSpace;
+ aBuilder.AppendWithEncode(aText, maxExtraSpace + len);
+ } else {
+ aBuilder.Append(aText);
+ }
+}
+
+static CheckedInt<uint32_t> ExtraSpaceNeededForAttrEncoding(
+ const nsAString& aValue) {
+ const char16_t* c = aValue.BeginReading();
+ const char16_t* end = aValue.EndReading();
+
+ uint32_t numEncodedChars = 0;
+ while (c < end) {
+ switch (*c) {
+ case '"':
+ case '&':
+ case 0x00A0:
+ ++numEncodedChars;
+ break;
+ default:
+ break;
+ }
+ ++c;
+ }
+
+ if (!numEncodedChars) {
+ return 0;
+ }
+
+ // For simplicity, conservatively estimate the size of the string after
+ // encoding. This will result in reserving more memory than we actually
+ // need, but that should be fine unless the string has an enormous number of
+ // & in it. We subtract 1 for the null terminator, then 1 more for the
+ // existing character that will be replaced.
+ constexpr uint32_t maxCharExtraSpace =
+ std::max({ArrayLength("&quot;"), ArrayLength("&amp;"),
+ ArrayLength("&nbsp;")}) -
+ 2;
+ static_assert(maxCharExtraSpace < 100, "Possible underflow");
+ return CheckedInt<uint32_t>(numEncodedChars) * maxCharExtraSpace;
+}
+
+static void AppendEncodedAttributeValue(const nsAttrValue& aValue,
+ StringBuilder& aBuilder) {
+ if (nsAtom* atom = aValue.GetStoredAtom()) {
+ nsDependentAtomString atomStr(atom);
+ auto space = ExtraSpaceNeededForAttrEncoding(atomStr);
+ if (space.isValid() && !space.value()) {
+ aBuilder.Append(atom);
+ } else {
+ aBuilder.AppendWithAttrEncode(nsString(atomStr),
+ space + atomStr.Length());
+ }
+ return;
+ }
+ // NOTE(emilio): In most cases this will just be a reference to the stored
+ // nsStringBuffer.
+ nsString str;
+ aValue.ToString(str);
+ auto space = ExtraSpaceNeededForAttrEncoding(str);
+ if (!space.isValid() || space.value()) {
+ aBuilder.AppendWithAttrEncode(std::move(str), space + str.Length());
+ } else {
+ aBuilder.Append(std::move(str));
+ }
+}
+
+static void StartElement(Element* aElement, StringBuilder& aBuilder) {
+ nsAtom* localName = aElement->NodeInfo()->NameAtom();
+ const int32_t tagNS = aElement->GetNameSpaceID();
+
+ aBuilder.Append(u"<");
+ if (tagNS == kNameSpaceID_XHTML || tagNS == kNameSpaceID_SVG ||
+ tagNS == kNameSpaceID_MathML) {
+ aBuilder.Append(localName);
+ } else {
+ aBuilder.Append(nsString(aElement->NodeName()));
+ }
+
+ if (CustomElementData* ceData = aElement->GetCustomElementData()) {
+ nsAtom* isAttr = ceData->GetIs(aElement);
+ if (isAttr && !aElement->HasAttr(nsGkAtoms::is)) {
+ aBuilder.Append(uR"( is=")");
+ aBuilder.Append(isAttr);
+ aBuilder.Append(uR"(")");
+ }
+ }
+
+ uint32_t i = 0;
+ while (BorrowedAttrInfo info = aElement->GetAttrInfoAt(i++)) {
+ const nsAttrName* name = info.mName;
+
+ int32_t attNs = name->NamespaceID();
+ nsAtom* attName = name->LocalName();
+
+ // Filter out any attribute starting with [-|_]moz
+ // FIXME(emilio): Do we still need this?
+ nsDependentAtomString attrNameStr(attName);
+ if (StringBeginsWith(attrNameStr, u"_moz"_ns) ||
+ StringBeginsWith(attrNameStr, u"-moz"_ns)) {
+ continue;
+ }
+
+ aBuilder.Append(u" ");
+
+ if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
+ (attNs == kNameSpaceID_XMLNS && attName == nsGkAtoms::xmlns)) {
+ // Nothing else required
+ } else if (attNs == kNameSpaceID_XML) {
+ aBuilder.Append(u"xml:");
+ } else if (attNs == kNameSpaceID_XMLNS) {
+ aBuilder.Append(u"xmlns:");
+ } else if (attNs == kNameSpaceID_XLink) {
+ aBuilder.Append(u"xlink:");
+ } else if (nsAtom* prefix = name->GetPrefix()) {
+ aBuilder.Append(prefix);
+ aBuilder.Append(u":");
+ }
+
+ aBuilder.Append(attName);
+ aBuilder.Append(uR"(=")");
+ AppendEncodedAttributeValue(*info.mValue, aBuilder);
+ aBuilder.Append(uR"(")");
+ }
+
+ aBuilder.Append(u">");
+
+ /*
+ // Per HTML spec we should append one \n if the first child of
+ // pre/textarea/listing is a textnode and starts with a \n.
+ // But because browsers haven't traditionally had that behavior,
+ // we're not changing our behavior either - yet.
+ if (aContent->IsHTMLElement()) {
+ if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
+ localName == nsGkAtoms::listing) {
+ nsIContent* fc = aContent->GetFirstChild();
+ if (fc &&
+ (fc->NodeType() == nsINode::TEXT_NODE ||
+ fc->NodeType() == nsINode::CDATA_SECTION_NODE)) {
+ const nsTextFragment* text = fc->GetText();
+ if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) {
+ aBuilder.Append("\n");
+ }
+ }
+ }
+ }*/
+}
+
+static inline bool ShouldEscape(nsIContent* aParent) {
+ if (!aParent || !aParent->IsHTMLElement()) {
+ return true;
+ }
+
+ static const nsAtom* nonEscapingElements[] = {
+ nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
+ nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
+ nsGkAtoms::plaintext, nsGkAtoms::noscript};
+ static mozilla::BitBloomFilter<12, nsAtom> sFilter;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ for (auto& nonEscapingElement : nonEscapingElements) {
+ sFilter.add(nonEscapingElement);
+ }
+ }
+
+ nsAtom* tag = aParent->NodeInfo()->NameAtom();
+ if (sFilter.mightContain(tag)) {
+ for (auto& nonEscapingElement : nonEscapingElements) {
+ if (tag == nonEscapingElement) {
+ if (MOZ_UNLIKELY(tag == nsGkAtoms::noscript) &&
+ MOZ_UNLIKELY(!aParent->OwnerDoc()->IsScriptEnabled())) {
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static inline bool IsVoidTag(Element* aElement) {
+ if (!aElement->IsHTMLElement()) {
+ return false;
+ }
+ return FragmentOrElement::IsHTMLVoid(aElement->NodeInfo()->NameAtom());
+}
+
+bool nsContentUtils::SerializeNodeToMarkup(nsINode* aRoot,
+ bool aDescendantsOnly,
+ nsAString& aOut) {
+ // If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true
+ MOZ_ASSERT(aDescendantsOnly || aRoot->NodeType() != nsINode::DOCUMENT_NODE);
+
+ nsINode* current =
+ aDescendantsOnly ? aRoot->GetFirstChildOfTemplateOrNode() : aRoot;
+
+ if (!current) {
+ return true;
+ }
+
+ StringBuilder builder;
+ nsIContent* next;
+ while (true) {
+ bool isVoid = false;
+ switch (current->NodeType()) {
+ case nsINode::ELEMENT_NODE: {
+ Element* elem = current->AsElement();
+ StartElement(elem, builder);
+ isVoid = IsVoidTag(elem);
+ if (!isVoid && (next = current->GetFirstChildOfTemplateOrNode())) {
+ current = next;
+ continue;
+ }
+ break;
+ }
+
+ case nsINode::TEXT_NODE:
+ case nsINode::CDATA_SECTION_NODE: {
+ const nsTextFragment* text = &current->AsText()->TextFragment();
+ nsIContent* parent = current->GetParent();
+ if (ShouldEscape(parent)) {
+ AppendEncodedCharacters(text, builder);
+ } else {
+ builder.Append(text);
+ }
+ break;
+ }
+
+ case nsINode::COMMENT_NODE: {
+ builder.Append(u"<!--");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append(u"-->");
+ break;
+ }
+
+ case nsINode::DOCUMENT_TYPE_NODE: {
+ builder.Append(u"<!DOCTYPE ");
+ builder.Append(nsString(current->NodeName()));
+ builder.Append(u">");
+ break;
+ }
+
+ case nsINode::PROCESSING_INSTRUCTION_NODE: {
+ builder.Append(u"<?");
+ builder.Append(nsString(current->NodeName()));
+ builder.Append(u" ");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append(u">");
+ break;
+ }
+ }
+
+ while (true) {
+ if (!isVoid && current->NodeType() == nsINode::ELEMENT_NODE) {
+ builder.Append(u"</");
+ nsIContent* elem = static_cast<nsIContent*>(current);
+ if (elem->IsHTMLElement() || elem->IsSVGElement() ||
+ elem->IsMathMLElement()) {
+ builder.Append(elem->NodeInfo()->NameAtom());
+ } else {
+ builder.Append(nsString(current->NodeName()));
+ }
+ builder.Append(u">");
+ }
+ isVoid = false;
+
+ if (current == aRoot) {
+ return builder.ToString(aOut);
+ }
+
+ if ((next = current->GetNextSibling())) {
+ current = next;
+ break;
+ }
+
+ current = current->GetParentNode();
+
+ // Handle template element. If the parent is a template's content,
+ // then adjust the parent to be the template element.
+ if (current != aRoot &&
+ current->NodeType() == nsINode::DOCUMENT_FRAGMENT_NODE) {
+ DocumentFragment* frag = static_cast<DocumentFragment*>(current);
+ nsIContent* fragHost = frag->GetHost();
+ if (fragHost && fragHost->IsTemplateElement()) {
+ current = fragHost;
+ }
+ }
+
+ if (aDescendantsOnly && current == aRoot) {
+ return builder.ToString(aOut);
+ }
+ }
+ }
+}
+
+bool nsContentUtils::IsSpecificAboutPage(JSObject* aGlobal, const char* aUri) {
+ // aUri must start with about: or this isn't the right function to be using.
+ MOZ_ASSERT(strncmp(aUri, "about:", 6) == 0);
+
+ // Make sure the global is a window
+ MOZ_DIAGNOSTIC_ASSERT(JS_IsGlobalObject(aGlobal));
+ nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobal);
+ if (!win) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = win->GetPrincipal();
+ NS_ENSURE_TRUE(principal, false);
+
+ // First check the scheme to avoid getting long specs in the common case.
+ if (!principal->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ principal->GetAsciiSpec(spec);
+
+ return spec.EqualsASCII(aUri);
+}
+
+/* static */
+void nsContentUtils::SetScrollbarsVisibility(nsIDocShell* aDocShell,
+ bool aVisible) {
+ if (!aDocShell) {
+ return;
+ }
+ auto pref = aVisible ? ScrollbarPreference::Auto : ScrollbarPreference::Never;
+ nsDocShell::Cast(aDocShell)->SetScrollbarPreference(pref);
+}
+
+/* static */
+nsIDocShell* nsContentUtils::GetDocShellForEventTarget(EventTarget* aTarget) {
+ if (!aTarget) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow;
+ if (nsCOMPtr<nsINode> node = nsINode::FromEventTarget(aTarget)) {
+ bool ignore;
+ innerWindow =
+ do_QueryInterface(node->OwnerDoc()->GetScriptHandlingObject(ignore));
+ } else if ((innerWindow = nsPIDOMWindowInner::FromEventTarget(aTarget))) {
+ // Nothing else to do
+ } else {
+ nsCOMPtr<DOMEventTargetHelper> helper = do_QueryInterface(aTarget);
+ if (helper) {
+ innerWindow = helper->GetOwner();
+ }
+ }
+
+ if (innerWindow) {
+ return innerWindow->GetDocShell();
+ }
+
+ return nullptr;
+}
+
+/*
+ * Note: this function only relates to figuring out HTTPS state, which is an
+ * input to the Secure Context algorithm. We are not actually implementing any
+ * part of the Secure Context algorithm itself here.
+ *
+ * This is a bit of a hack. Ideally we'd propagate HTTPS state through
+ * nsIChannel as described in the Fetch and HTML specs, but making channels
+ * know about whether they should inherit HTTPS state, propagating information
+ * about who the channel's "client" is, exposing GetHttpsState API on channels
+ * and modifying the various cache implementations to store and retrieve HTTPS
+ * state involves a huge amount of code (see bug 1220687). We avoid that for
+ * now using this function.
+ *
+ * This function takes advantage of the observation that we can return true if
+ * nsIContentSecurityManager::IsOriginPotentiallyTrustworthy returns true for
+ * the document's origin (e.g. the origin has a scheme of 'https' or host
+ * 'localhost' etc.). Since we generally propagate a creator document's origin
+ * onto data:, blob:, etc. documents, this works for them too.
+ *
+ * The scenario where this observation breaks down is sandboxing without the
+ * 'allow-same-origin' flag, since in this case a document is given a unique
+ * origin (IsOriginPotentiallyTrustworthy would return false). We handle that
+ * by using the origin that the document would have had had it not been
+ * sandboxed.
+ *
+ * DEFICIENCIES: Note that this function uses nsIScriptSecurityManager's
+ * getChannelResultPrincipalIfNotSandboxed, and that method's ignoring of
+ * sandboxing is limited to the immediate sandbox. In the case that aDocument
+ * should inherit its origin (e.g. data: URI) but its parent has ended up
+ * with a unique origin due to sandboxing further up the parent chain we may
+ * end up returning false when we would ideally return true (since we will
+ * examine the parent's origin for 'https' and not finding it.) This means
+ * that we may restrict the privileges of some pages unnecessarily in this
+ * edge case.
+ */
+/* static */
+bool nsContentUtils::HttpsStateIsModern(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+
+ if (principal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ // If aDocument is sandboxed, try and get the principal that it would have
+ // been given had it not been sandboxed:
+ if (principal->GetIsNullPrincipal() &&
+ (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN)) {
+ nsIChannel* channel = aDocument->GetChannel();
+ if (channel) {
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->GetChannelResultPrincipalIfNotSandboxed(
+ channel, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (principal->IsSystemPrincipal()) {
+ // If a document with the system principal is sandboxing a subdocument
+ // that would normally inherit the embedding element's principal (e.g.
+ // a srcdoc document) then the embedding document does not trust the
+ // content that is written to the embedded document. Unlike when the
+ // embedding document is https, in this case we have no indication as
+ // to whether the embedded document's contents are delivered securely
+ // or not, and the sandboxing would possibly indicate that they were
+ // not. To play it safe we return false here. (See bug 1162772
+ // comment 73-80.)
+ return false;
+ }
+ }
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ return false;
+ }
+
+ MOZ_ASSERT(principal->GetIsContentPrincipal());
+
+ return principal->GetIsOriginPotentiallyTrustworthy();
+}
+
+/* static */
+bool nsContentUtils::ComputeIsSecureContext(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = ssm->GetChannelResultPrincipalIfNotSandboxed(
+ aChannel, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ const RefPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (principal->IsSystemPrincipal()) {
+ // If the load would've been sandboxed, treat this load as an untrusted
+ // load, as system code considers sandboxed resources insecure.
+ return !loadInfo->GetLoadingSandboxed();
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ return false;
+ }
+
+ if (const RefPtr<WindowContext> windowContext =
+ WindowContext::GetById(loadInfo->GetInnerWindowID())) {
+ if (!windowContext->GetIsSecureContext()) {
+ return false;
+ }
+ }
+
+ return principal->GetIsOriginPotentiallyTrustworthy();
+}
+
+/* static */
+void nsContentUtils::TryToUpgradeElement(Element* aElement) {
+ NodeInfo* nodeInfo = aElement->NodeInfo();
+ RefPtr<nsAtom> typeAtom =
+ aElement->GetCustomElementData()->GetCustomElementType();
+
+ MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName()));
+ CustomElementDefinition* definition =
+ nsContentUtils::LookupCustomElementDefinition(
+ nodeInfo->GetDocument(), nodeInfo->NameAtom(),
+ nodeInfo->NamespaceID(), typeAtom);
+ if (definition) {
+ nsContentUtils::EnqueueUpgradeReaction(aElement, definition);
+ } else {
+ // Add an unresolved custom element that is a candidate for upgrade when a
+ // custom element is connected to the document.
+ nsContentUtils::RegisterUnresolvedElement(aElement, typeAtom);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void DoCustomElementCreate(Element** aElement, JSContext* aCx,
+ Document* aDoc, NodeInfo* aNodeInfo,
+ CustomElementConstructor* aConstructor,
+ ErrorResult& aRv, FromParser aFromParser) {
+ JS::Rooted<JS::Value> constructResult(aCx);
+ aConstructor->Construct(&constructResult, aRv, "Custom Element Create",
+ CallbackFunction::eRethrowExceptions);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ RefPtr<Element> element;
+ // constructResult is an ObjectValue because construction with a callback
+ // always forms the return value from a JSObject.
+ UNWRAP_OBJECT(Element, &constructResult, element);
+ if (aNodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ if (!element || !element->IsHTMLElement()) {
+ aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("\"this\"",
+ "HTMLElement");
+ return;
+ }
+ } else {
+ if (!element || !element->IsXULElement()) {
+ aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("\"this\"",
+ "XULElement");
+ return;
+ }
+ }
+
+ nsAtom* localName = aNodeInfo->NameAtom();
+
+ if (aDoc != element->OwnerDoc() || element->GetParentNode() ||
+ element->HasChildren() || element->GetAttrCount() ||
+ element->NodeInfo()->NameAtom() != localName) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ if (element->IsHTMLElement()) {
+ static_cast<HTMLElement*>(&*element)->InhibitRestoration(
+ !(aFromParser & FROM_PARSER_NETWORK));
+ }
+
+ element.forget(aElement);
+}
+
+/* static */
+nsresult nsContentUtils::NewXULOrHTMLElement(
+ Element** aResult, mozilla::dom::NodeInfo* aNodeInfo,
+ FromParser aFromParser, nsAtom* aIsAtom,
+ mozilla::dom::CustomElementDefinition* aDefinition) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
+ MOZ_ASSERT(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML) ||
+ nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
+ "Can only create XUL or XHTML elements.");
+
+ nsAtom* name = nodeInfo->NameAtom();
+ int32_t tag = eHTMLTag_unknown;
+ bool isCustomElementName = false;
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ tag = nsHTMLTags::CaseSensitiveAtomTagToId(name);
+ isCustomElementName =
+ (tag == eHTMLTag_userdefined &&
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XHTML));
+ } else { // kNameSpaceID_XUL
+ if (aIsAtom) {
+ // Make sure the customized built-in element to be constructed confirms
+ // to our naming requirement, i.e. [is] must be a dashed name and
+ // the tag name must not.
+ // if so, set isCustomElementName to false to kick off all the logics
+ // that pick up aIsAtom.
+ if (nsContentUtils::IsNameWithDash(aIsAtom) &&
+ !nsContentUtils::IsNameWithDash(name)) {
+ isCustomElementName = false;
+ } else {
+ isCustomElementName =
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XUL);
+ }
+ } else {
+ isCustomElementName =
+ nsContentUtils::IsCustomElementName(name, kNameSpaceID_XUL);
+ }
+ }
+
+ nsAtom* tagAtom = nodeInfo->NameAtom();
+ nsAtom* typeAtom = nullptr;
+ bool isCustomElement = isCustomElementName || aIsAtom;
+ if (isCustomElement) {
+ typeAtom = isCustomElementName ? tagAtom : aIsAtom;
+ }
+
+ MOZ_ASSERT_IF(aDefinition, isCustomElement);
+
+ // https://dom.spec.whatwg.org/#concept-create-element
+ // We only handle the "synchronous custom elements flag is set" now.
+ // For the unset case (e.g. cloning a node), see bug 1319342 for that.
+ // Step 4.
+ RefPtr<CustomElementDefinition> definition = aDefinition;
+ if (isCustomElement && !definition) {
+ MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName()));
+ definition = nsContentUtils::LookupCustomElementDefinition(
+ nodeInfo->GetDocument(), nodeInfo->NameAtom(), nodeInfo->NamespaceID(),
+ typeAtom);
+ }
+
+ // It might be a problem that parser synchronously calls constructor, so filed
+ // bug 1378079 to figure out what we should do for parser case.
+ if (definition) {
+ /*
+ * Synchronous custom elements flag is determined by 3 places in spec,
+ * 1) create an element for a token, the flag is determined by
+ * "will execute script" which is not originally created
+ * for the HTML fragment parsing algorithm.
+ * 2) createElement and createElementNS, the flag is the same as
+ * NOT_FROM_PARSER.
+ * 3) clone a node, our implementation will not go into this function.
+ * For the unset case which is non-synchronous only applied for
+ * inner/outerHTML.
+ */
+ bool synchronousCustomElements = aFromParser != dom::FROM_PARSER_FRAGMENT;
+ // Per discussion in https://github.com/w3c/webcomponents/issues/635,
+ // use entry global in those places that are called from JS APIs and use the
+ // node document's global object if it is called from parser.
+ nsIGlobalObject* global;
+ if (aFromParser == dom::NOT_FROM_PARSER) {
+ global = GetEntryGlobal();
+
+ // Documents created from the PrototypeDocumentSink always use
+ // NOT_FROM_PARSER for non-XUL elements. We can get the global from the
+ // document in that case.
+ if (!global) {
+ Document* doc = nodeInfo->GetDocument();
+ if (doc && doc->LoadedFromPrototype()) {
+ global = doc->GetScopeObject();
+ }
+ }
+ } else {
+ global = nodeInfo->GetDocument()->GetScopeObject();
+ }
+ if (!global) {
+ // In browser chrome code, one may have access to a document which doesn't
+ // have scope object anymore.
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoAllowLegacyScriptExecution exemption;
+ AutoEntryScript aes(global, "create custom elements");
+ JSContext* cx = aes.cx();
+ ErrorResult rv;
+
+ // Step 5.
+ if (definition->IsCustomBuiltIn()) {
+ // SetupCustomElement() should be called with an element that don't have
+ // CustomElementData setup, if not we will hit the assertion in
+ // SetCustomElementData().
+ // Built-in element
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ *aResult =
+ CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetCustomElementData(MakeUnique<CustomElementData>(typeAtom));
+ if (synchronousCustomElements) {
+ CustomElementRegistry::Upgrade(*aResult, definition, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ aes.ReportException();
+ }
+ } else {
+ nsContentUtils::EnqueueUpgradeReaction(*aResult, definition);
+ }
+
+ return NS_OK;
+ }
+
+ // Step 6.1.
+ if (synchronousCustomElements) {
+ definition->mPrefixStack.AppendElement(nodeInfo->GetPrefixAtom());
+ RefPtr<Document> doc = nodeInfo->GetDocument();
+ DoCustomElementCreate(aResult, cx, doc, nodeInfo,
+ MOZ_KnownLive(definition->mConstructor), rv,
+ aFromParser);
+ if (rv.MaybeSetPendingException(cx)) {
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ NS_IF_ADDREF(*aResult = NS_NewHTMLUnknownElement(nodeInfo.forget(),
+ aFromParser));
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetDefined(false);
+ }
+ definition->mPrefixStack.RemoveLastElement();
+ return NS_OK;
+ }
+
+ // Step 6.2.
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ NS_IF_ADDREF(*aResult =
+ NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+ (*aResult)->SetCustomElementData(
+ MakeUnique<CustomElementData>(definition->mType));
+ nsContentUtils::EnqueueUpgradeReaction(*aResult, definition);
+ return NS_OK;
+ }
+
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XHTML)) {
+ // Per the Custom Element specification, unknown tags that are valid custom
+ // element names should be HTMLElement instead of HTMLUnknownElement.
+ if (isCustomElementName) {
+ NS_IF_ADDREF(*aResult =
+ NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+ } else {
+ *aResult = CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
+ }
+ } else {
+ NS_IF_ADDREF(*aResult = nsXULElement::Construct(nodeInfo.forget()));
+ }
+
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (isCustomElement) {
+ (*aResult)->SetCustomElementData(MakeUnique<CustomElementData>(typeAtom));
+ nsContentUtils::RegisterCallbackUpgradeElement(*aResult, typeAtom);
+ }
+
+ return NS_OK;
+}
+
+CustomElementRegistry* nsContentUtils::GetCustomElementRegistry(
+ Document* aDoc) {
+ MOZ_ASSERT(aDoc);
+
+ if (!aDoc->GetDocShell()) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->CustomElements();
+}
+
+/* static */
+CustomElementDefinition* nsContentUtils::LookupCustomElementDefinition(
+ Document* aDoc, nsAtom* aNameAtom, uint32_t aNameSpaceID,
+ nsAtom* aTypeAtom) {
+ if (aNameSpaceID != kNameSpaceID_XUL && aNameSpaceID != kNameSpaceID_XHTML) {
+ return nullptr;
+ }
+
+ RefPtr<CustomElementRegistry> registry = GetCustomElementRegistry(aDoc);
+ if (!registry) {
+ return nullptr;
+ }
+
+ return registry->LookupCustomElementDefinition(aNameAtom, aNameSpaceID,
+ aTypeAtom);
+}
+
+/* static */
+void nsContentUtils::RegisterCallbackUpgradeElement(Element* aElement,
+ nsAtom* aTypeName) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterCallbackUpgradeElement(aElement, aTypeName);
+ }
+}
+
+/* static */
+void nsContentUtils::RegisterUnresolvedElement(Element* aElement,
+ nsAtom* aTypeName) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->RegisterUnresolvedElement(aElement, aTypeName);
+ }
+}
+
+/* static */
+void nsContentUtils::UnregisterUnresolvedElement(Element* aElement) {
+ MOZ_ASSERT(aElement);
+
+ nsAtom* typeAtom = aElement->GetCustomElementData()->GetCustomElementType();
+ Document* doc = aElement->OwnerDoc();
+ CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+ if (registry) {
+ registry->UnregisterUnresolvedElement(aElement, typeAtom);
+ }
+}
+
+/* static */
+void nsContentUtils::EnqueueUpgradeReaction(
+ Element* aElement, CustomElementDefinition* aDefinition) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->OwnerDoc();
+
+ // No DocGroup means no custom element reactions stack.
+ if (!doc->GetDocGroup()) {
+ return;
+ }
+
+ CustomElementReactionsStack* stack =
+ doc->GetDocGroup()->CustomElementReactionsStack();
+ stack->EnqueueUpgradeReaction(aElement, aDefinition);
+}
+
+/* static */
+void nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType aType, Element* aCustomElement,
+ const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
+ // No DocGroup means no custom element reactions stack.
+ if (!aCustomElement->OwnerDoc()->GetDocGroup()) {
+ return;
+ }
+
+ CustomElementRegistry::EnqueueLifecycleCallback(aType, aCustomElement, aArgs,
+ aDefinition);
+}
+
+/* static */
+CustomElementFormValue nsContentUtils::ConvertToCustomElementFormValue(
+ const Nullable<OwningFileOrUSVStringOrFormData>& aState) {
+ if (aState.IsNull()) {
+ return void_t{};
+ }
+ const auto& state = aState.Value();
+ if (state.IsFile()) {
+ RefPtr<BlobImpl> impl = state.GetAsFile()->Impl();
+ return {std::move(impl)};
+ }
+ if (state.IsUSVString()) {
+ return state.GetAsUSVString();
+ }
+ return state.GetAsFormData()->ConvertToCustomElementFormValue();
+}
+
+/* static */
+Nullable<OwningFileOrUSVStringOrFormData>
+nsContentUtils::ExtractFormAssociatedCustomElementValue(
+ nsIGlobalObject* aGlobal,
+ const mozilla::dom::CustomElementFormValue& aCEValue) {
+ MOZ_ASSERT(aGlobal);
+
+ OwningFileOrUSVStringOrFormData value;
+ switch (aCEValue.type()) {
+ case CustomElementFormValue::TBlobImpl: {
+ RefPtr<File> file = File::Create(aGlobal, aCEValue.get_BlobImpl());
+ if (NS_WARN_IF(!file)) {
+ return {};
+ }
+ value.SetAsFile() = file;
+ } break;
+
+ case CustomElementFormValue::TnsString:
+ value.SetAsUSVString() = aCEValue.get_nsString();
+ break;
+
+ case CustomElementFormValue::TArrayOfFormDataTuple: {
+ const auto& array = aCEValue.get_ArrayOfFormDataTuple();
+ auto formData = MakeRefPtr<FormData>();
+
+ for (auto i = 0ul; i < array.Length(); ++i) {
+ const auto& item = array.ElementAt(i);
+ switch (item.value().type()) {
+ case FormDataValue::TnsString:
+ formData->AddNameValuePair(item.name(),
+ item.value().get_nsString());
+ break;
+
+ case FormDataValue::TBlobImpl: {
+ auto blobImpl = item.value().get_BlobImpl();
+ auto* blob = Blob::Create(aGlobal, blobImpl);
+ formData->AddNameBlobPair(item.name(), blob);
+ } break;
+
+ default:
+ continue;
+ }
+ }
+
+ value.SetAsFormData() = formData;
+ } break;
+ case CustomElementFormValue::Tvoid_t:
+ return {};
+ default:
+ NS_WARNING("Invalid CustomElementContentData type!");
+ return {};
+ }
+ return value;
+}
+
+/* static */
+void nsContentUtils::AppendDocumentLevelNativeAnonymousContentTo(
+ Document* aDocument, nsTArray<nsIContent*>& aElements) {
+ MOZ_ASSERT(aDocument);
+#ifdef DEBUG
+ size_t oldLength = aElements.Length();
+#endif
+
+ if (PresShell* presShell = aDocument->GetPresShell()) {
+ if (nsIFrame* scrollFrame = presShell->GetRootScrollFrame()) {
+ nsIAnonymousContentCreator* creator = do_QueryFrame(scrollFrame);
+ MOZ_ASSERT(
+ creator,
+ "scroll frame should always implement nsIAnonymousContentCreator");
+ creator->AppendAnonymousContentTo(aElements, 0);
+ }
+ if (nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame()) {
+ canvasFrame->AppendAnonymousContentTo(aElements, 0);
+ }
+ }
+
+#ifdef DEBUG
+ for (size_t i = oldLength; i < aElements.Length(); i++) {
+ MOZ_ASSERT(
+ aElements[i]->GetProperty(nsGkAtoms::docLevelNativeAnonymousContent),
+ "Someone here has lied, or missed to flag the node");
+ }
+#endif
+}
+
+static void AppendNativeAnonymousChildrenFromFrame(nsIFrame* aFrame,
+ nsTArray<nsIContent*>& aKids,
+ uint32_t aFlags) {
+ if (nsIAnonymousContentCreator* ac = do_QueryFrame(aFrame)) {
+ ac->AppendAnonymousContentTo(aKids, aFlags);
+ }
+}
+
+/* static */
+void nsContentUtils::AppendNativeAnonymousChildren(const nsIContent* aContent,
+ nsTArray<nsIContent*>& aKids,
+ uint32_t aFlags) {
+ if (aContent->MayHaveAnonymousChildren()) {
+ if (nsIFrame* primaryFrame = aContent->GetPrimaryFrame()) {
+ // NAC created by the element's primary frame.
+ AppendNativeAnonymousChildrenFromFrame(primaryFrame, aKids, aFlags);
+
+ // NAC created by any other non-primary frames for the element.
+ AutoTArray<nsIFrame::OwnedAnonBox, 8> ownedAnonBoxes;
+ primaryFrame->AppendOwnedAnonBoxes(ownedAnonBoxes);
+ for (nsIFrame::OwnedAnonBox& box : ownedAnonBoxes) {
+ MOZ_ASSERT(box.mAnonBoxFrame->GetContent() == aContent);
+ AppendNativeAnonymousChildrenFromFrame(box.mAnonBoxFrame, aKids,
+ aFlags);
+ }
+ }
+
+ // Get manually created NAC (editor resize handles, etc.).
+ if (auto nac = static_cast<ManualNACArray*>(
+ aContent->GetProperty(nsGkAtoms::manualNACProperty))) {
+ aKids.AppendElements(*nac);
+ }
+ }
+
+ // The root scroll frame is not the primary frame of the root element.
+ // Detect and handle this case.
+ if (!(aFlags & nsIContent::eSkipDocumentLevelNativeAnonymousContent) &&
+ aContent == aContent->OwnerDoc()->GetRootElement()) {
+ AppendDocumentLevelNativeAnonymousContentTo(aContent->OwnerDoc(), aKids);
+ }
+}
+
+bool nsContentUtils::IsImageAvailable(nsIContent* aLoadingNode, nsIURI* aURI,
+ nsIPrincipal* aDefaultTriggeringPrincipal,
+ CORSMode aCORSMode) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ QueryTriggeringPrincipal(aLoadingNode, aDefaultTriggeringPrincipal,
+ getter_AddRefs(triggeringPrincipal));
+ MOZ_ASSERT(triggeringPrincipal);
+
+ Document* doc = aLoadingNode->OwnerDoc();
+ return IsImageAvailable(aURI, triggeringPrincipal, aCORSMode, doc);
+}
+
+bool nsContentUtils::IsImageAvailable(nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ CORSMode aCORSMode, Document* aDoc) {
+ imgLoader* imgLoader = GetImgLoaderForDocument(aDoc);
+ return imgLoader->IsImageAvailable(aURI, aTriggeringPrincipal, aCORSMode,
+ aDoc);
+}
+
+/* static */
+bool nsContentUtils::QueryTriggeringPrincipal(
+ nsIContent* aLoadingNode, nsIPrincipal* aDefaultPrincipal,
+ nsIPrincipal** aTriggeringPrincipal) {
+ MOZ_ASSERT(aLoadingNode);
+ MOZ_ASSERT(aTriggeringPrincipal);
+
+ bool result = false;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aDefaultPrincipal;
+ if (!loadingPrincipal) {
+ loadingPrincipal = aLoadingNode->NodePrincipal();
+ }
+
+ // If aLoadingNode is content, bail out early.
+ if (!aLoadingNode->NodePrincipal()->IsSystemPrincipal()) {
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ return result;
+ }
+
+ nsAutoString loadingStr;
+ if (aLoadingNode->IsElement()) {
+ aLoadingNode->AsElement()->GetAttr(
+ kNameSpaceID_None, nsGkAtoms::triggeringprincipal, loadingStr);
+ }
+
+ // Fall back if 'triggeringprincipal' isn't specified,
+ if (loadingStr.IsEmpty()) {
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ return result;
+ }
+
+ nsCString binary;
+ nsCOMPtr<nsIPrincipal> serializedPrin =
+ BasePrincipal::FromJSON(NS_ConvertUTF16toUTF8(loadingStr));
+ if (serializedPrin) {
+ result = true;
+ serializedPrin.forget(aTriggeringPrincipal);
+ }
+
+ if (!result) {
+ // Fallback if the deserialization is failed.
+ loadingPrincipal.forget(aTriggeringPrincipal);
+ }
+
+ return result;
+}
+
+/* static */
+void nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ nsIContent* aLoadingNode, nsIPrincipal** aTriggeringPrincipal,
+ nsContentPolicyType& aContentPolicyType, uint64_t* aRequestContextID) {
+ MOZ_ASSERT(aRequestContextID);
+
+ bool result = QueryTriggeringPrincipal(aLoadingNode, aTriggeringPrincipal);
+ if (result) {
+ // Set the content policy type to TYPE_INTERNAL_IMAGE_FAVICON for
+ // indicating it's a favicon loading.
+ aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON;
+
+ nsAutoString requestContextID;
+ if (aLoadingNode->IsElement()) {
+ aLoadingNode->AsElement()->GetAttr(
+ kNameSpaceID_None, nsGkAtoms::requestcontextid, requestContextID);
+ }
+ nsresult rv;
+ int64_t val = requestContextID.ToInteger64(&rv);
+ *aRequestContextID = NS_SUCCEEDED(rv) ? val : 0;
+ } else {
+ aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+ }
+}
+
+/* static */
+nsresult nsContentUtils::CreateJSValueFromSequenceOfObject(
+ JSContext* aCx, const Sequence<JSObject*>& aTransfer,
+ JS::MutableHandle<JS::Value> aValue) {
+ if (aTransfer.IsEmpty()) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, aTransfer.Length()));
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < aTransfer.Length(); ++i) {
+ JS::Rooted<JSObject*> object(aCx, aTransfer[i]);
+ if (!object) {
+ continue;
+ }
+
+ if (NS_WARN_IF(
+ !JS_DefineElement(aCx, array, i, object, JSPROP_ENUMERATE))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ aValue.setObject(*array);
+ return NS_OK;
+}
+
+/* static */
+void nsContentUtils::StructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JS::Handle<JS::Value> aValue,
+ const StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError) {
+ JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
+ aError = nsContentUtils::CreateJSValueFromSequenceOfObject(
+ aCx, aOptions.mTransfer, &transferArray);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ JS::CloneDataPolicy clonePolicy;
+ // We are definitely staying in the same agent cluster.
+ clonePolicy.allowIntraClusterClonableSharedObjects();
+ if (aGlobal->IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ StructuredCloneHolder holder(StructuredCloneHolder::CloningSupported,
+ StructuredCloneHolder::TransferringSupported,
+ JS::StructuredCloneScope::SameProcess);
+ holder.Write(aCx, aValue, transferArray, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ holder.Read(aGlobal, aCx, aRetval, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ nsTArray<RefPtr<MessagePort>> ports = holder.TakeTransferredPorts();
+ Unused << ports;
+}
+
+/* static */
+bool nsContentUtils::ShouldBlockReservedKeys(WidgetKeyboardEvent* aKeyEvent) {
+ nsCOMPtr<nsIPrincipal> principal;
+ RefPtr<Element> targetElement =
+ Element::FromEventTargetOrNull(aKeyEvent->mOriginalTarget);
+ nsCOMPtr<nsIBrowser> targetBrowser;
+ if (targetElement) {
+ targetBrowser = targetElement->AsBrowser();
+ }
+ bool isRemoteBrowser = false;
+ if (targetBrowser) {
+ targetBrowser->GetIsRemoteBrowser(&isRemoteBrowser);
+ }
+
+ if (isRemoteBrowser) {
+ targetBrowser->GetContentPrincipal(getter_AddRefs(principal));
+ return principal ? nsContentUtils::IsSitePermDeny(principal, "shortcuts"_ns)
+ : false;
+ }
+
+ if (targetElement) {
+ Document* doc = targetElement->GetUncomposedDoc();
+ if (doc) {
+ RefPtr<WindowContext> wc = doc->GetWindowContext();
+ if (wc) {
+ return wc->TopWindowContext()->GetShortcutsPermission() ==
+ nsIPermissionManager::DENY_ACTION;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether the given type is a supported document type for
+ * loading within the nsObjectLoadingContent specified by aContent.
+ *
+ * NOTE Helper method for nsContentUtils::HtmlObjectContentTypeForMIMEType.
+ * NOTE Does not take content policy or capabilities into account
+ */
+static bool HtmlObjectContentSupportsDocument(const nsCString& aMimeType) {
+ nsCOMPtr<nsIWebNavigationInfo> info(
+ do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID));
+ if (!info) {
+ return false;
+ }
+
+ uint32_t supported;
+ nsresult rv = info->IsTypeSupported(aMimeType, &supported);
+
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (supported != nsIWebNavigationInfo::UNSUPPORTED) {
+ // Don't want to support plugins as documents
+ return supported != nsIWebNavigationInfo::FALLBACK;
+ }
+
+ // Try a stream converter
+ // NOTE: We treat any type we can convert from as a supported type. If a
+ // type is not actually supported, the URI loader will detect that and
+ // return an error, and we'll fallback.
+ nsCOMPtr<nsIStreamConverterService> convServ =
+ do_GetService("@mozilla.org/streamConverters;1");
+ bool canConvert = false;
+ if (convServ) {
+ rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert);
+ }
+ return NS_SUCCEEDED(rv) && canConvert;
+}
+
+/* static */
+uint32_t nsContentUtils::HtmlObjectContentTypeForMIMEType(
+ const nsCString& aMIMEType) {
+ if (aMIMEType.IsEmpty()) {
+ return nsIObjectLoadingContent::TYPE_FALLBACK;
+ }
+
+ if (imgLoader::SupportImageWithMimeType(aMIMEType)) {
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+ }
+
+ // Faking support of the PDF content as a document for EMBED tags
+ // when internal PDF viewer is enabled.
+ if (aMIMEType.LowerCaseEqualsLiteral("application/pdf") && IsPDFJSEnabled()) {
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+ }
+
+ if (HtmlObjectContentSupportsDocument(aMIMEType)) {
+ return nsIObjectLoadingContent::TYPE_DOCUMENT;
+ }
+
+ return nsIObjectLoadingContent::TYPE_FALLBACK;
+}
+
+/* static */
+bool nsContentUtils::IsLocalRefURL(const nsAString& aString) {
+ return !aString.IsEmpty() && aString[0] == '#';
+}
+
+// We use only 53 bits for the ID so that it can be converted to and from a JS
+// value without loss of precision. The upper bits of the ID hold the process
+// ID. The lower bits identify the object itself.
+static constexpr uint64_t kIdTotalBits = 53;
+static constexpr uint64_t kIdProcessBits = 22;
+static constexpr uint64_t kIdBits = kIdTotalBits - kIdProcessBits;
+
+/* static */
+uint64_t nsContentUtils::GenerateProcessSpecificId(uint64_t aId) {
+ uint64_t processId = 0;
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ processId = cc->GetID();
+ }
+
+ MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
+ uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
+
+ uint64_t id = aId;
+ MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
+ uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
+
+ return (processBits << kIdBits) | bits;
+}
+
+/* static */
+std::tuple<uint64_t, uint64_t> nsContentUtils::SplitProcessSpecificId(
+ uint64_t aId) {
+ return {aId >> kIdBits, aId & ((uint64_t(1) << kIdBits) - 1)};
+}
+
+// Next process-local Tab ID.
+static uint64_t gNextTabId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateTabId() {
+ return GenerateProcessSpecificId(++gNextTabId);
+}
+
+// Next process-local Browser ID.
+static uint64_t gNextBrowserId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateBrowserId() {
+ return GenerateProcessSpecificId(++gNextBrowserId);
+}
+
+// Next process-local Browsing Context ID.
+static uint64_t gNextBrowsingContextId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateBrowsingContextId() {
+ return GenerateProcessSpecificId(++gNextBrowsingContextId);
+}
+
+// Next process-local Window ID.
+static uint64_t gNextWindowId = 0;
+
+/* static */
+uint64_t nsContentUtils::GenerateWindowId() {
+ return GenerateProcessSpecificId(++gNextWindowId);
+}
+
+// Next process-local load.
+static Atomic<uint64_t> gNextLoadIdentifier(0);
+
+/* static */
+uint64_t nsContentUtils::GenerateLoadIdentifier() {
+ return GenerateProcessSpecificId(++gNextLoadIdentifier);
+}
+
+/* static */
+bool nsContentUtils::GetUserIsInteracting() {
+ return UserInteractionObserver::sUserActive;
+}
+
+/* static */
+bool nsContentUtils::GetSourceMapURL(nsIHttpChannel* aChannel,
+ nsACString& aResult) {
+ nsresult rv = aChannel->GetResponseHeader("SourceMap"_ns, aResult);
+ if (NS_FAILED(rv)) {
+ rv = aChannel->GetResponseHeader("X-SourceMap"_ns, aResult);
+ }
+ return NS_SUCCEEDED(rv);
+}
+
+/* static */
+bool nsContentUtils::IsMessageInputEvent(const IPC::Message& aMsg) {
+ if ((aMsg.type() & mozilla::dom::PBrowser::PBrowserStart) ==
+ mozilla::dom::PBrowser::PBrowserStart) {
+ switch (aMsg.type()) {
+ case mozilla::dom::PBrowser::Msg_RealMouseMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealMouseButtonEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealMouseEnterExitWidgetEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealKeyEvent__ID:
+ case mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealDragEvent__ID:
+ case mozilla::dom::PBrowser::Msg_UpdateDimensions__ID:
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsContentUtils::IsMessageCriticalInputEvent(const IPC::Message& aMsg) {
+ if ((aMsg.type() & mozilla::dom::PBrowser::PBrowserStart) ==
+ mozilla::dom::PBrowser::PBrowserStart) {
+ switch (aMsg.type()) {
+ case mozilla::dom::PBrowser::Msg_RealMouseButtonEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealKeyEvent__ID:
+ case mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealDragEvent__ID:
+ return true;
+ }
+ }
+ return false;
+}
+
+static const char* kUserInteractionInactive = "user-interaction-inactive";
+static const char* kUserInteractionActive = "user-interaction-active";
+
+void nsContentUtils::UserInteractionObserver::Init() {
+ // Listen for the observer messages from EventStateManager which are telling
+ // us whether or not the user is interacting.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, kUserInteractionInactive, false);
+ obs->AddObserver(this, kUserInteractionActive, false);
+
+ // We can't register ourselves as an annotator yet, as the
+ // BackgroundHangMonitor hasn't started yet. It will have started by the
+ // time we have the chance to spin the event loop.
+ RefPtr<UserInteractionObserver> self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsContentUtils::UserInteractionObserver::Init",
+ [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); }));
+}
+
+void nsContentUtils::UserInteractionObserver::Shutdown() {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, kUserInteractionInactive);
+ obs->RemoveObserver(this, kUserInteractionActive);
+ }
+
+ BackgroundHangMonitor::UnregisterAnnotator(*this);
+}
+
+/**
+ * NB: This function is always called by the BackgroundHangMonitor thread.
+ * Plan accordingly
+ */
+void nsContentUtils::UserInteractionObserver::AnnotateHang(
+ BackgroundHangAnnotations& aAnnotations) {
+ // NOTE: Only annotate the hang report if the user is known to be interacting.
+ if (sUserActive) {
+ aAnnotations.AddAnnotation(u"UserInteracting"_ns, true);
+ }
+}
+
+NS_IMETHODIMP
+nsContentUtils::UserInteractionObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, kUserInteractionInactive)) {
+ if (sUserActive && XRE_IsParentProcess()) {
+ glean::RecordPowerMetrics();
+ }
+ sUserActive = false;
+ } else if (!strcmp(aTopic, kUserInteractionActive)) {
+ if (!sUserActive && XRE_IsParentProcess()) {
+ glean::RecordPowerMetrics();
+
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+ }
+
+ sUserActive = true;
+ } else {
+ NS_WARNING("Unexpected observer notification");
+ }
+ return NS_OK;
+}
+
+Atomic<bool> nsContentUtils::UserInteractionObserver::sUserActive(false);
+NS_IMPL_ISUPPORTS(nsContentUtils::UserInteractionObserver, nsIObserver)
+
+/* static */
+bool nsContentUtils::IsSpecialName(const nsAString& aName) {
+ return aName.LowerCaseEqualsLiteral("_blank") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent") ||
+ aName.LowerCaseEqualsLiteral("_self");
+}
+
+/* static */
+bool nsContentUtils::IsOverridingWindowName(const nsAString& aName) {
+ return !aName.IsEmpty() && !IsSpecialName(aName);
+}
+
+// Unfortunately, we can't unwrap an IDL object using only a concrete type.
+// We need to calculate type data based on the IDL typename. Which means
+// wrapping our templated function in a macro.
+#define EXTRACT_EXN_VALUES(T, ...) \
+ ExtractExceptionValues<mozilla::dom::prototypes::id::T, \
+ T##_Binding::NativeType, T>(__VA_ARGS__) \
+ .isOk()
+
+template <prototypes::ID PrototypeID, class NativeType, typename T>
+static Result<Ok, nsresult> ExtractExceptionValues(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, nsAString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ AssertStaticUnwrapOK<PrototypeID>();
+ RefPtr<T> exn;
+ MOZ_TRY((UnwrapObject<PrototypeID, NativeType>(aObj, exn, nullptr)));
+
+ exn->GetFilename(aCx, aSourceSpecOut);
+ if (!aSourceSpecOut.IsEmpty()) {
+ *aLineOut = exn->LineNumber(aCx);
+ *aColumnOut = exn->ColumnNumber();
+ }
+
+ exn->GetName(aMessageOut);
+ aMessageOut.AppendLiteral(": ");
+
+ nsAutoString message;
+ exn->GetMessageMoz(message);
+ aMessageOut.Append(message);
+ return Ok();
+}
+
+/* static */
+void nsContentUtils::ExtractErrorValues(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, nsACString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ nsAutoString sourceSpec;
+ ExtractErrorValues(aCx, aValue, sourceSpec, aLineOut, aColumnOut,
+ aMessageOut);
+ CopyUTF16toUTF8(sourceSpec, aSourceSpecOut);
+}
+
+/* static */
+void nsContentUtils::ExtractErrorValues(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, nsAString& aSourceSpecOut,
+ uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+ MOZ_ASSERT(aLineOut);
+ MOZ_ASSERT(aColumnOut);
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ // Try to process as an Error object. Use the file/line/column values
+ // from the Error as they will be more specific to the root cause of
+ // the problem.
+ JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
+ if (err) {
+ // Use xpc to extract the error message only. We don't actually send
+ // this report anywhere.
+ RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
+ report->Init(err,
+ nullptr, // toString result
+ false, // chrome
+ 0); // window ID
+
+ if (!report->mFileName.IsEmpty()) {
+ aSourceSpecOut = report->mFileName;
+ *aLineOut = report->mLineNumber;
+ *aColumnOut = report->mColumn;
+ }
+ aMessageOut.Assign(report->mErrorMsg);
+ }
+
+ // Next, try to unwrap the rejection value as a DOMException.
+ else if (EXTRACT_EXN_VALUES(DOMException, aCx, obj, aSourceSpecOut,
+ aLineOut, aColumnOut, aMessageOut)) {
+ return;
+ }
+
+ // Next, try to unwrap the rejection value as an XPC Exception.
+ else if (EXTRACT_EXN_VALUES(Exception, aCx, obj, aSourceSpecOut, aLineOut,
+ aColumnOut, aMessageOut)) {
+ return;
+ }
+ }
+
+ // If we could not unwrap a specific error type, then perform default safe
+ // string conversions on primitives. Objects will result in "[Object]"
+ // unfortunately.
+ if (aMessageOut.IsEmpty()) {
+ nsAutoJSString jsString;
+ if (jsString.init(aCx, aValue)) {
+ aMessageOut = jsString;
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+}
+
+#undef EXTRACT_EXN_VALUES
+
+/* static */
+bool nsContentUtils::ContentIsLink(nsIContent* aContent) {
+ if (!aContent || !aContent->IsElement()) {
+ return false;
+ }
+
+ if (aContent->IsHTMLElement(nsGkAtoms::a)) {
+ return true;
+ }
+
+ return aContent->AsElement()->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type,
+ nsGkAtoms::simple, eCaseMatters);
+}
+
+/* static */
+already_AddRefed<ContentFrameMessageManager>
+nsContentUtils::TryGetBrowserChildGlobal(nsISupports* aFrom) {
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(aFrom);
+ if (!frameLoaderOwner) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ if (!frameLoader) {
+ return nullptr;
+ }
+
+ RefPtr<ContentFrameMessageManager> manager =
+ frameLoader->GetBrowserChildMessageManager();
+ return manager.forget();
+}
+
+/* static */
+uint32_t nsContentUtils::InnerOrOuterWindowCreated() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++sInnerOrOuterWindowCount;
+ return ++sInnerOrOuterWindowSerialCounter;
+}
+
+/* static */
+void nsContentUtils::InnerOrOuterWindowDestroyed() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sInnerOrOuterWindowCount > 0);
+ --sInnerOrOuterWindowCount;
+}
+
+/* static */
+nsresult nsContentUtils::AnonymizeURI(nsIURI* aURI, nsCString& aAnonymizedURI) {
+ MOZ_ASSERT(aURI);
+
+ if (aURI->SchemeIs("data")) {
+ aAnonymizedURI.Assign("data:..."_ns);
+ return NS_OK;
+ }
+ // Anonymize the URL.
+ // Strip the URL of any possible username/password and make it ready to be
+ // presented in the UI.
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(aURI);
+ return exposableURI->GetSpec(aAnonymizedURI);
+}
+
+static bool JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) {
+ nsAString* result = static_cast<nsAString*>(aData);
+ return result->Append(aBuf, aLen, fallible);
+}
+
+/* static */
+bool nsContentUtils::StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsAString& aOutStr, JSONBehavior aBehavior) {
+ MOZ_ASSERT(aCx);
+ switch (aBehavior) {
+ case UndefinedIsNullStringLiteral: {
+ aOutStr.Truncate();
+ JS::Rooted<JS::Value> value(aCx, aValue);
+ return JS_Stringify(aCx, &value, nullptr, JS::NullHandleValue,
+ JSONCreator, &aOutStr);
+ }
+ case UndefinedIsVoidString: {
+ aOutStr.SetIsVoid(true);
+ return JS::ToJSON(aCx, aValue, nullptr, JS::NullHandleValue, JSONCreator,
+ &aOutStr);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid value for aBehavior");
+ return false;
+ }
+}
+
+/* static */
+bool nsContentUtils::
+ HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+ Document* aDocument) {
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "This function only makes sense in content processes");
+
+ if (aDocument && !aDocument->IsLoadedAsData()) {
+ if (nsPresContext* presContext = FindPresContextForDocument(aDocument)) {
+ MOZ_ASSERT(!presContext->IsChrome(),
+ "Should never have a chrome PresContext in a content process");
+
+ return !presContext->GetInProcessRootContentDocumentPresContext()
+ ->HadFirstContentfulPaint() &&
+ nsThreadManager::MainThreadHasPendingHighPriorityEvents();
+ }
+ }
+ return false;
+}
+
+static nsGlobalWindowInner* GetInnerWindowForGlobal(nsIGlobalObject* aGlobal) {
+ NS_ENSURE_TRUE(aGlobal, nullptr);
+
+ if (auto* window = aGlobal->GetAsInnerWindow()) {
+ return nsGlobalWindowInner::Cast(window);
+ }
+
+ // When Extensions run content scripts inside a sandbox, it uses
+ // sandboxPrototype to make them appear as though they're running in the
+ // scope of the page. So when a content script invokes postMessage, it expects
+ // the |source| of the received message to be the window set as the
+ // sandboxPrototype. This used to work incidentally for unrelated reasons, but
+ // now we need to do some special handling to support it.
+ JS::Rooted<JSObject*> scope(RootingCx(), aGlobal->GetGlobalJSObject());
+ NS_ENSURE_TRUE(scope, nullptr);
+
+ if (xpc::IsSandbox(scope)) {
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(scope));
+ JSContext* cx = jsapi.cx();
+ // Our current Realm on aCx is the sandbox. Using that for unwrapping
+ // makes sense: if the sandbox can unwrap the window, we can use it.
+ return xpc::SandboxWindowOrNull(scope, cx);
+ }
+
+ // The calling window must be holding a reference, so we can return a weak
+ // pointer.
+ return nsGlobalWindowInner::Cast(aGlobal->GetAsInnerWindow());
+}
+
+/* static */
+nsGlobalWindowInner* nsContentUtils::IncumbentInnerWindow() {
+ return GetInnerWindowForGlobal(GetIncumbentGlobal());
+}
+
+/* static */
+nsGlobalWindowInner* nsContentUtils::EntryInnerWindow() {
+ return GetInnerWindowForGlobal(GetEntryGlobal());
+}
+
+/* static */
+bool nsContentUtils::IsURIInPrefList(nsIURI* aURI, const char* aPrefName) {
+ MOZ_ASSERT(aPrefName);
+
+ nsAutoCString list;
+ Preferences::GetCString(aPrefName, list);
+ ToLowerCase(list);
+ return IsURIInList(aURI, list);
+}
+
+/* static */
+bool nsContentUtils::IsURIInList(nsIURI* aURI, const nsCString& aList) {
+#ifdef DEBUG
+ nsAutoCString listLowerCase(aList);
+ ToLowerCase(listLowerCase);
+ MOZ_ASSERT(listLowerCase.Equals(aList),
+ "The aList argument should be lower-case");
+#endif
+
+ if (!aURI) {
+ return false;
+ }
+
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
+ return false;
+ }
+
+ if (aList.IsEmpty()) {
+ return false;
+ }
+
+ // The list is comma separated domain list. Each item may start with "*.".
+ // If starts with "*.", it matches any sub-domains.
+
+ nsCCharSeparatedTokenizer tokenizer(aList, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCString token(tokenizer.nextToken());
+
+ nsAutoCString host;
+ aURI->GetHost(host);
+ if (host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ for (;;) {
+ int32_t index = token.Find(host);
+ if (index >= 0 &&
+ static_cast<uint32_t>(index) + host.Length() <= token.Length()) {
+ // If we found a full match, return true.
+ size_t indexAfterHost = index + host.Length();
+ if (index == 0 && indexAfterHost == token.Length()) {
+ return true;
+ }
+ // If next character is '/', we need to check the path too.
+ // We assume the path in the list means "/foo" + "*".
+ if (token[indexAfterHost] == '/') {
+ nsDependentCSubstring pathInList(
+ token, indexAfterHost,
+ static_cast<nsDependentCSubstring::size_type>(-1));
+ nsAutoCString filePath;
+ aURI->GetFilePath(filePath);
+ ToLowerCase(filePath);
+ if (StringBeginsWith(filePath, pathInList) &&
+ (filePath.Length() == pathInList.Length() ||
+ pathInList.EqualsLiteral("/") ||
+ filePath[pathInList.Length() - 1] == '/' ||
+ filePath[pathInList.Length() - 1] == '?' ||
+ filePath[pathInList.Length() - 1] == '#')) {
+ return true;
+ }
+ }
+ }
+ int32_t startIndexOfCurrentLevel = host[0] == '*' ? 1 : 0;
+ int32_t startIndexOfNextLevel =
+ host.Find(".", startIndexOfCurrentLevel + 1);
+ if (startIndexOfNextLevel <= 0) {
+ break;
+ }
+ host.ReplaceLiteral(0, startIndexOfNextLevel, "*");
+ }
+ }
+
+ return false;
+}
+
+/* static */
+ScreenIntMargin nsContentUtils::GetWindowSafeAreaInsets(
+ nsIScreen* aScreen, const ScreenIntMargin& aSafeAreaInsets,
+ const LayoutDeviceIntRect& aWindowRect) {
+ // This calculates safe area insets of window from screen rectangle, window
+ // rectangle and safe area insets of screen.
+ //
+ // +----------------------------------------+ <-- screen
+ // | +-------------------------------+ <------- window
+ // | | window's safe area inset top) | |
+ // +--+-------------------------------+--+ |
+ // | | | |<------ safe area rectangle of
+ // | | | | | screen
+ // +--+-------------------------------+--+ |
+ // | |window's safe area inset bottom| |
+ // | +-------------------------------+ |
+ // +----------------------------------------+
+ //
+ ScreenIntMargin windowSafeAreaInsets;
+
+ if (windowSafeAreaInsets == aSafeAreaInsets) {
+ // no safe area insets.
+ return windowSafeAreaInsets;
+ }
+
+ int32_t screenLeft, screenTop, screenWidth, screenHeight;
+ nsresult rv =
+ aScreen->GetRect(&screenLeft, &screenTop, &screenWidth, &screenHeight);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return windowSafeAreaInsets;
+ }
+
+ const ScreenIntRect screenRect(screenLeft, screenTop, screenWidth,
+ screenHeight);
+
+ ScreenIntRect safeAreaRect = screenRect;
+ safeAreaRect.Deflate(aSafeAreaInsets);
+
+ ScreenIntRect windowRect = ViewAs<ScreenPixel>(
+ aWindowRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
+
+ // FIXME(bug 1754323): This can trigger because the screen rect is not
+ // orientation-aware.
+ // MOZ_ASSERT(screenRect.Contains(windowRect),
+ // "Screen doesn't contain window rect? Something seems off");
+
+ // window's rect of safe area
+ safeAreaRect = safeAreaRect.Intersect(windowRect);
+
+ windowSafeAreaInsets.top = safeAreaRect.y - aWindowRect.y;
+ windowSafeAreaInsets.left = safeAreaRect.x - aWindowRect.x;
+ windowSafeAreaInsets.right =
+ aWindowRect.x + aWindowRect.width - (safeAreaRect.x + safeAreaRect.width);
+ windowSafeAreaInsets.bottom = aWindowRect.y + aWindowRect.height -
+ (safeAreaRect.y + safeAreaRect.height);
+
+ windowSafeAreaInsets.EnsureAtLeast(ScreenIntMargin());
+ // This shouldn't be needed, but it wallpapers orientation issues, see bug
+ // 1754323.
+ windowSafeAreaInsets.EnsureAtMost(aSafeAreaInsets);
+
+ return windowSafeAreaInsets;
+}
+
+/* static */
+nsContentUtils::SubresourceCacheValidationInfo
+nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
+ nsIURI* aURI) {
+ SubresourceCacheValidationInfo info;
+ if (nsCOMPtr<nsICacheInfoChannel> cache = do_QueryInterface(aRequest)) {
+ uint32_t value = 0;
+ if (NS_SUCCEEDED(cache->GetCacheTokenExpirationTime(&value))) {
+ info.mExpirationTime.emplace(value);
+ }
+ }
+
+ // Determine whether the cache entry must be revalidated when we try to use
+ // it. Currently, only HTTP specifies this information...
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
+ Unused << httpChannel->IsNoStoreResponse(&info.mMustRevalidate);
+
+ if (!info.mMustRevalidate) {
+ Unused << httpChannel->IsNoCacheResponse(&info.mMustRevalidate);
+ }
+ }
+
+ // data: URIs are safe to cache across documents under any circumstance, so we
+ // special-case them here even though the channel itself doesn't have any
+ // caching policy. Same for chrome:// uris.
+ //
+ // TODO(emilio): Figure out which other schemes that don't have caching
+ // policies are safe to cache. Blobs should be...
+ const bool knownCacheable = [&] {
+ if (!aURI) {
+ return false;
+ }
+ if (aURI->SchemeIs("data") || aURI->SchemeIs("moz-page-thumb") ||
+ aURI->SchemeIs("moz-extension")) {
+ return true;
+ }
+ if (aURI->SchemeIs("chrome") || aURI->SchemeIs("resource")) {
+ return !StaticPrefs::nglayout_debug_disable_xul_cache();
+ }
+ return false;
+ }();
+
+ if (knownCacheable) {
+ MOZ_ASSERT(!info.mExpirationTime);
+ MOZ_ASSERT(!info.mMustRevalidate);
+ info.mExpirationTime = Some(0); // 0 means "doesn't expire".
+ }
+
+ return info;
+}
+
+nsCString nsContentUtils::TruncatedURLForDisplay(nsIURI* aURL, size_t aMaxLen) {
+ nsCString spec;
+ if (aURL) {
+ aURL->GetSpec(spec);
+ spec.Truncate(std::min(aMaxLen, spec.Length()));
+ }
+ return spec;
+}
+
+/* static */
+nsresult nsContentUtils::AnonymizeId(nsAString& aId,
+ const nsACString& aOriginKey,
+ OriginFormat aFormat) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCString rawKey;
+ if (aFormat == OriginFormat::Base64) {
+ rv = Base64Decode(aOriginKey, rawKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rawKey = aOriginKey;
+ }
+
+ HMAC hmac;
+ rv = hmac.Begin(
+ SEC_OID_SHA256,
+ Span(reinterpret_cast<const uint8_t*>(rawKey.get()), rawKey.Length()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 id(aId);
+ rv = hmac.Update(reinterpret_cast<const uint8_t*>(id.get()), id.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<uint8_t> macBytes;
+ rv = hmac.End(macBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString macBase64;
+ rv = Base64Encode(
+ nsDependentCSubstring(reinterpret_cast<const char*>(macBytes.Elements()),
+ macBytes.Length()),
+ macBase64);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(macBase64, aId);
+ return NS_OK;
+}
+
+void nsContentUtils::RequestGeckoTaskBurst() {
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(NS_APPSHELL_CID);
+ if (appShell) {
+ appShell->GeckoTaskBurst();
+ }
+}
+
+nsIContent* nsContentUtils::GetClosestLinkInFlatTree(nsIContent* aContent) {
+ for (nsIContent* content = aContent; content;
+ content = content->GetFlattenedTreeParent()) {
+ if (nsContentUtils::IsDraggableLink(content)) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+template <TreeKind aKind>
+MOZ_ALWAYS_INLINE const nsINode* GetParent(const nsINode* aNode) {
+ if constexpr (aKind == TreeKind::DOM) {
+ return aNode->GetParentNode();
+ } else {
+ return aNode->GetFlattenedTreeParentNode();
+ }
+}
+
+template <TreeKind aKind>
+MOZ_ALWAYS_INLINE Maybe<uint32_t> GetIndexInParent(const nsINode* aParent,
+ const nsINode* aNode) {
+ if constexpr (aKind == TreeKind::DOM) {
+ return aParent->ComputeIndexOf(aNode);
+ } else {
+ return aParent->ComputeFlatTreeIndexOf(aNode);
+ }
+}
+
+template <TreeKind aTreeKind>
+int32_t nsContentUtils::CompareTreePosition(const nsINode* aNode1,
+ const nsINode* aNode2,
+ const nsINode* aCommonAncestor) {
+ MOZ_ASSERT(aNode1, "aNode1 must not be null");
+ MOZ_ASSERT(aNode2, "aNode2 must not be null");
+
+ if (MOZ_UNLIKELY(NS_WARN_IF(aNode1 == aNode2))) {
+ return 0;
+ }
+
+ AutoTArray<const nsINode*, 32> node1Ancestors;
+ const nsINode* c1;
+ for (c1 = aNode1; c1 && c1 != aCommonAncestor;
+ c1 = GetParent<aTreeKind>(c1)) {
+ node1Ancestors.AppendElement(c1);
+ }
+ if (!c1 && aCommonAncestor) {
+ // So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
+ // Never mind. We can continue as if aCommonAncestor was null.
+ aCommonAncestor = nullptr;
+ }
+
+ AutoTArray<const nsINode*, 32> node2Ancestors;
+ const nsINode* c2;
+ for (c2 = aNode2; c2 && c2 != aCommonAncestor;
+ c2 = GetParent<aTreeKind>(c2)) {
+ node2Ancestors.AppendElement(c2);
+ }
+ if (!c2 && aCommonAncestor) {
+ // So, it turns out aCommonAncestor was not an ancestor of c2.
+ // We need to retry with no common ancestor hint.
+ return CompareTreePosition<aTreeKind>(aNode1, aNode2, nullptr);
+ }
+
+ int last1 = node1Ancestors.Length() - 1;
+ int last2 = node2Ancestors.Length() - 1;
+ const nsINode* node1Ancestor = nullptr;
+ const nsINode* node2Ancestor = nullptr;
+ while (last1 >= 0 && last2 >= 0 &&
+ ((node1Ancestor = node1Ancestors.ElementAt(last1)) ==
+ (node2Ancestor = node2Ancestors.ElementAt(last2)))) {
+ last1--;
+ last2--;
+ }
+
+ if (last1 < 0) {
+ if (last2 < 0) {
+ NS_ASSERTION(aNode1 == aNode2, "internal error?");
+ return 0;
+ }
+ // aContent1 is an ancestor of aContent2
+ return -1;
+ }
+
+ if (last2 < 0) {
+ // aContent2 is an ancestor of aContent1
+ return 1;
+ }
+
+ // node1Ancestor != node2Ancestor, so they must be siblings with the
+ // same parent
+ const nsINode* parent = GetParent<aTreeKind>(node1Ancestor);
+ if (NS_WARN_IF(!parent)) { // different documents??
+ return 0;
+ }
+
+ const Maybe<uint32_t> index1 =
+ GetIndexInParent<aTreeKind>(parent, node1Ancestor);
+ const Maybe<uint32_t> index2 =
+ GetIndexInParent<aTreeKind>(parent, node2Ancestor);
+
+ // None of the nodes are anonymous, just do a regular comparison.
+ if (index1.isSome() && index2.isSome()) {
+ return static_cast<int32_t>(static_cast<int64_t>(*index1) - *index2);
+ }
+
+ // Otherwise handle pseudo-element and anonymous node ordering.
+ // ::marker -> ::before -> anon siblings -> regular siblings -> ::after
+ auto PseudoIndex = [](const nsINode* aNode,
+ const Maybe<uint32_t>& aNodeIndex) -> int32_t {
+ if (aNodeIndex.isSome()) {
+ return 1; // Not a pseudo.
+ }
+ if (aNode->IsGeneratedContentContainerForMarker()) {
+ return -2;
+ }
+ if (aNode->IsGeneratedContentContainerForBefore()) {
+ return -1;
+ }
+ if (aNode->IsGeneratedContentContainerForAfter()) {
+ return 2;
+ }
+ return 0;
+ };
+
+ return PseudoIndex(node1Ancestor, index1) -
+ PseudoIndex(node2Ancestor, index2);
+}
+
+nsIContent* nsContentUtils::AttachDeclarativeShadowRoot(nsIContent* aHost,
+ ShadowRootMode aMode,
+ bool aDelegatesFocus) {
+ RefPtr<Element> host = mozilla::dom::Element::FromNodeOrNull(aHost);
+ if (!host) {
+ return nullptr;
+ }
+
+ ShadowRootInit init;
+ init.mMode = aMode;
+ init.mDelegatesFocus = aDelegatesFocus;
+ init.mSlotAssignment = SlotAssignmentMode::Named;
+ init.mClonable = true;
+
+ RefPtr shadowRoot = host->AttachShadow(init, IgnoreErrors(),
+ Element::ShadowRootDeclarative::Yes);
+ if (shadowRoot) {
+ // https://html.spec.whatwg.org/#parsing-main-inhead:available-to-element-internals
+ shadowRoot->SetAvailableToElementInternals();
+ }
+ return shadowRoot;
+}
+
+template int32_t nsContentUtils::CompareTreePosition<TreeKind::DOM>(
+ const nsINode*, const nsINode*, const nsINode*);
+template int32_t nsContentUtils::CompareTreePosition<TreeKind::Flat>(
+ const nsINode*, const nsINode*, const nsINode*);
+
+namespace mozilla {
+std::ostream& operator<<(std::ostream& aOut,
+ const PreventDefaultResult aPreventDefaultResult) {
+ switch (aPreventDefaultResult) {
+ case PreventDefaultResult::No:
+ aOut << "unhandled";
+ break;
+ case PreventDefaultResult::ByContent:
+ aOut << "handled-by-content";
+ break;
+ case PreventDefaultResult::ByChrome:
+ aOut << "handled-by-chrome";
+ break;
+ }
+ return aOut;
+}
+} // namespace mozilla