Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/ScrollContainerFrame.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/CacheExpirationTime.h"
#include "mozilla/dom/CallbackFunction.h"
#include "mozilla/dom/CallbackObject.h"
#include "mozilla/dom/ChildIterator.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/HTMLTemplateElement.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/TrustedHTML.h"
#include "mozilla/dom/TrustedTypesConstants.h"
#include "mozilla/dom/TrustedTypeUtils.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/ViewTransition.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/net/UrlClassifierCommon.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/widget/IMEData.h"
#include "nsAboutProtocolUtils.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 "nsICachingChannel.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 "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;
nsIPrincipal* nsContentUtils::sFingerprintingProtectionPrincipal;
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,
NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const RangeBoundary& aFirstBoundary,
const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const RawRangeBoundary& aFirstBoundary,
const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const RawRangeBoundary& aFirstBoundary,
const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const RangeBoundary& aFirstBoundary,
const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const ConstRawRangeBoundary& aFirstBoundary,
const RangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const RawRangeBoundary& aFirstBoundary,
const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const ConstRawRangeBoundary& aFirstBoundary,
const RawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
template Maybe<int32_t> nsContentUtils::ComparePoints(
const ConstRawRangeBoundary& aFirstBoundary,
const ConstRawRangeBoundary& aSecondBoundary, NodeIndexCache* aIndexCache);
// Subset of
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();
}
}
static auto* GetParentNode(const nsINode* aNode) {
return aNode->GetParentNode();
}
static auto* GetParentOrShadowHostNode(const nsINode* aNode) {
return aNode->GetParentOrShadowHostNode();
}
static auto* GetFlattenedTreeParent(const nsIContent* aContent) {
return aContent->GetFlattenedTreeParent();
}
static auto* GetFlattenedTreeParentNodeForSelection(
const nsIContent* aContent) {
return aContent->GetFlattenedTreeParentNodeForSelection();
}
static auto* GetFlattenedTreeParentElementForStyle(const Element* aElement) {
return aElement->GetFlattenedTreeParentElementForStyle();
}
static auto* GetParentBrowserParent(const BrowserParent* aBrowserParent) {
return aBrowserParent->GetBrowserBridgeParent()
? aBrowserParent->GetBrowserBridgeParent()->Manager()
: nullptr;
}
template <typename Node1, typename Node2, typename GetParentFunc>
class MOZ_STACK_CLASS CommonAncestors final {
public:
CommonAncestors(Node1& aNode1, Node2& aNode2, GetParentFunc aGetParentFunc)
: GetParent(aGetParentFunc) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mAssertNoGC.emplace();
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
AppendInclusiveAncestors(&aNode1, GetParent, mInclusiveAncestors1);
AppendInclusiveAncestors(&aNode2, GetParent, mInclusiveAncestors2);
// Find where the parent chain differs
size_t depth1 = mInclusiveAncestors1.Length();
size_t depth2 = mInclusiveAncestors2.Length();
const size_t shorterLength = std::min(depth1, depth2);
Node1** const inclusiveAncestors1 = mInclusiveAncestors1.Elements();
Node2** const inclusiveAncestors2 = mInclusiveAncestors2.Elements();
for ([[maybe_unused]] const size_t unused : IntegerRange(shorterLength)) {
Node1* const inclusiveAncestor1 = inclusiveAncestors1[--depth1];
Node2* const inclusiveAncestor2 = inclusiveAncestors2[--depth2];
if (inclusiveAncestor1 != inclusiveAncestor2) {
MOZ_ASSERT_IF(mClosestCommonAncestor,
inclusiveAncestor1 == GetClosestCommonAncestorChild1());
MOZ_ASSERT_IF(mClosestCommonAncestor,
inclusiveAncestor2 == GetClosestCommonAncestorChild2());
return;
}
mNumberOfCommonAncestors++;
mClosestCommonAncestor = inclusiveAncestor1;
}
MOZ_ASSERT(mClosestCommonAncestor);
MOZ_ASSERT(mNumberOfCommonAncestors);
MOZ_ASSERT(!depth1 || !depth2);
MOZ_ASSERT_IF(!depth1, !GetClosestCommonAncestorChild1());
MOZ_ASSERT_IF(depth1, GetClosestCommonAncestorChild1());
MOZ_ASSERT_IF(!depth2, !GetClosestCommonAncestorChild2());
MOZ_ASSERT_IF(depth2, GetClosestCommonAncestorChild2());
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
~CommonAncestors() { MOZ_DIAGNOSTIC_ASSERT(!mMutationGuard.Mutated(0)); }
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
[[nodiscard]] Node1* GetClosestCommonAncestor() const {
return mClosestCommonAncestor;
}
[[nodiscard]] Node1* GetClosestCommonAncestorChild1() const {
return GetClosestCommonAncestorChild(mInclusiveAncestors1);
}
[[nodiscard]] Node2* GetClosestCommonAncestorChild2() const {
return GetClosestCommonAncestorChild(mInclusiveAncestors2);
}
void WarnIfClosestCommonAncestorChildrenAreNotInChildList() const {
WarnIfClosestCommonAncestorChildIsNotInChildList(mInclusiveAncestors1);
WarnIfClosestCommonAncestorChildIsNotInChildList(mInclusiveAncestors2);
}
private:
template <typename Node>
static void AppendInclusiveAncestors(Node* aNode,
GetParentFunc aGetParentFunc,
nsTArray<Node*>& aArrayOfParents) {
Node* node = aNode;
while (node) {
aArrayOfParents.AppendElement(node);
node = aGetParentFunc(node);
}
}
template <typename Node>
Maybe<size_t> GetClosestCommonAncestorChildIndex(
const nsTArray<Node*>& aInclusiveAncestors) const {
if (!mClosestCommonAncestor ||
aInclusiveAncestors.Length() <= mNumberOfCommonAncestors) {
return Nothing();
}
return Some((aInclusiveAncestors.Length() - 1) // last index
- mNumberOfCommonAncestors); // before closest common ancestor
}
template <typename Node>
[[nodiscard]] Node* GetClosestCommonAncestorChild(
const nsTArray<Node*>& aInclusiveAncestors) const {
const Maybe<size_t> index =
GetClosestCommonAncestorChildIndex(aInclusiveAncestors);
if (index.isNothing()) {
MOZ_ASSERT_IF(mClosestCommonAncestor,
aInclusiveAncestors.Length() == mNumberOfCommonAncestors);
return nullptr;
}
Node* const child = aInclusiveAncestors[*index];
MOZ_ASSERT(child);
MOZ_ASSERT(GetParent(child) == mClosestCommonAncestor);
return child;
}
template <typename Node>
void WarnIfClosestCommonAncestorChildIsNotInChildList(
const nsTArray<Node*>& aInclusiveAncestors) const {
#ifdef DEBUG
if constexpr (std::is_base_of_v<nsINode, Node>) {
Node* const child = GetClosestCommonAncestorChild(aInclusiveAncestors);
if (!child) {
return;
}
const Maybe<uint32_t> childIndex =
mClosestCommonAncestor->ComputeIndexOf(child);
if (MOZ_LIKELY(childIndex.isSome())) {
return;
}
const Maybe<size_t> index =
GetClosestCommonAncestorChildIndex(aInclusiveAncestors);
NS_WARNING(
fmt::format(
FMT_STRING("The caller cannot compare the position of the child "
"of the common ancestor due to not in the child list "
"of the common ancestor:\n"
" {}\n" // common ancestor
" + {}\n" // common ancestor child
"{}"), // child of common ancestor child if there is
ToString(*mClosestCommonAncestor), ToString(*child),
*index ? fmt::format(FMT_STRING(" + {}"),
ToString(*aInclusiveAncestors[*index - 1]))
: "")
.c_str());
}
#endif
}
AutoTArray<Node1*, 30> mInclusiveAncestors1;
AutoTArray<Node2*, 30> mInclusiveAncestors2;
Node1* mClosestCommonAncestor = nullptr;
const GetParentFunc GetParent;
uint32_t mNumberOfCommonAncestors = 0;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
nsMutationGuard mMutationGuard;
Maybe<JS::AutoAssertNoGC> mAssertNoGC;
#endif
};
/**
* 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<Document>, 64> allDocuments;
Document::GetAllInProcessDocuments(allDocuments);
for (auto& doc : allDocuments) {
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);
RefPtr<nsIPrincipal> fingerprintingProtectionPrincipal =
BasePrincipal::CreateContentPrincipal(
"about:fingerprintingprotection"_ns);
if (!fingerprintingProtectionPrincipal) {
return NS_ERROR_FAILURE;
}
fingerprintingProtectionPrincipal.forget(&sFingerprintingProtectionPrincipal);
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,
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>(std::size(eventArray));
sStringEventTable =
new nsTHashMap<nsStringHashKey, EventNameMapping>(std::size(eventArray));
sUserDefinedEvents = new nsTArray<RefPtr<nsAtom>>(64);
// Subtract one from the length because of the trailing null
for (uint32_t i = 0; i < std::size(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 < std::size(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.
*
* "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::IsHyphen(uint32_t aChar) {
// Characters treated as hyphens for the purpose of "emergency" breaking
// when the content would otherwise overflow.
return aChar == uint32_t('-') || // HYPHEN-MINUS
aChar == 0x2010 || // HYPHEN
aChar == 0x2012 || // FIGURE DASH
aChar == 0x2013 || // EN DASH
aChar == 0x058A; // ARMENIAN HYPHEN
}
/* 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
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 std::clamp(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);
NS_IF_RELEASE(sFingerprintingProtectionPrincipal);
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 nsContentUtils::ETPSaysShouldNotResistFingerprinting(
nsICookieJarSettings* aCookieJarSettings, bool aIsPBM) {
// A positive return from this function should always be obeyed.
// A negative return means we should keep checking things.
// 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 (aIsPBM) {
// In PBM (where RFP is enabled) do not exempt based on the ETP toggle
return false;
}
} else if (StaticPrefs::privacy_resistFingerprinting_DoNotUseDirectly() ||
(aIsPBM &&
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;
}
return ContentBlockingAllowList::Check(aCookieJarSettings);
}
bool nsContentUtils::ETPSaysShouldNotResistFingerprinting(
nsIChannel* aChannel, nsILoadInfo* aLoadInfo) {
bool isPBM = NS_UsePrivateBrowsing(aChannel);
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
GetCookieJarSettings(aLoadInfo);
if (!cookieJarSettings) {
return false;
}
return ETPSaysShouldNotResistFingerprinting(cookieJarSettings, isPBM);
}
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.IsPrivateBrowsing();
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.IsPrivateBrowsing()) {
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.IsPrivateBrowsing();
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!");
while (true) {
if (aPossibleDescendant == aPossibleAncestor) {
return true;
}
if (nsINode* parent = aPossibleDescendant->GetParentNode()) {
aPossibleDescendant = parent;
continue;
}
if (auto* df = DocumentFragment::FromNode(aPossibleDescendant)) {
if (nsINode* host = df->GetHost()) {
aPossibleDescendant = host;
continue;
}
}
break;
}
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
template <typename GetParentFunc>
nsresult static GetInclusiveAncestorsAndOffsetsHelper(
nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>& aAncestorNodes,
nsTArray<Maybe<uint32_t>>& aAncestorOffsets, GetParentFunc aGetParentFunc) {
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 = aGetParentFunc(child);
while (parent) {
aAncestorNodes.AppendElement(parent->AsContent());
aAncestorOffsets.AppendElement(parent->ComputeIndexOf(child));
child = parent;
parent = aGetParentFunc(child);
}
return NS_OK;
}
nsresult nsContentUtils::GetInclusiveAncestorsAndOffsets(
nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>& aAncestorNodes,
nsTArray<Maybe<uint32_t>>& aAncestorOffsets) {
return GetInclusiveAncestorsAndOffsetsHelper(
aNode, aOffset, aAncestorNodes, aAncestorOffsets,
[](nsIContent* aContent) { return aContent->GetParent(); });
}
nsresult nsContentUtils::GetShadowIncludingAncestorsAndOffsets(
nsINode* aNode, uint32_t aOffset, nsTArray<nsIContent*>& aAncestorNodes,
nsTArray<Maybe<uint32_t>>& aAncestorOffsets) {
return GetInclusiveAncestorsAndOffsetsHelper(
aNode, aOffset, aAncestorNodes, aAncestorOffsets,
[](nsIContent* aContent) -> nsIContent* {
return nsIContent::FromNodeOrNull(
aContent->GetParentOrShadowHostNode());
});
}
/* static */
nsINode* nsContentUtils::GetCommonAncestorHelper(nsINode* aNode1,
nsINode* aNode2) {
MOZ_ASSERT(aNode1);
MOZ_ASSERT(aNode2);
return CommonAncestors(*aNode1, *aNode2, GetParentNode)
.GetClosestCommonAncestor();
}
/* static */
nsINode* nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
nsINode* aNode1, nsINode* aNode2) {
if (aNode1 == aNode2) {
return aNode1;
}
MOZ_ASSERT(aNode1);
MOZ_ASSERT(aNode2);
return CommonAncestors(*aNode1, *aNode2, GetParentOrShadowHostNode)
.GetClosestCommonAncestor();
}
/* static */
nsIContent* nsContentUtils::GetCommonFlattenedTreeAncestorHelper(
nsIContent* aContent1, nsIContent* aContent2) {
MOZ_ASSERT(aContent1);
MOZ_ASSERT(aContent2);
return CommonAncestors(*aContent1, *aContent2, GetFlattenedTreeParent)
.GetClosestCommonAncestor();
}
/* static */
nsIContent* nsContentUtils::GetCommonFlattenedTreeAncestorForSelection(
nsIContent* aContent1, nsIContent* aContent2) {
if (aContent1 == aContent2) {
return aContent1;
}
MOZ_ASSERT(aContent1);
MOZ_ASSERT(aContent2);
return CommonAncestors(*aContent1, *aContent2,
GetFlattenedTreeParentNodeForSelection)
.GetClosestCommonAncestor();
}
/* static */
Element* nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
Element* aElement1, Element* aElement2) {
MOZ_ASSERT(aElement1);
MOZ_ASSERT(aElement2);
return CommonAncestors(*aElement1, *aElement2,
GetFlattenedTreeParentElementForStyle)
.GetClosestCommonAncestor();
}
/* static */
Maybe<int32_t> nsContentUtils::CompareChildNodes(
const nsINode* aChild1, const nsINode* aChild2,
NodeIndexCache* aIndexCache /* = nullptr */) {
if (MOZ_UNLIKELY(
(aChild1 && (NS_WARN_IF(aChild1->IsRootOfNativeAnonymousSubtree()) ||
NS_WARN_IF(aChild1->IsDocumentFragment()))) ||
(aChild2 && (NS_WARN_IF(aChild2->IsRootOfNativeAnonymousSubtree()) ||
NS_WARN_IF(aChild2->IsDocumentFragment()))))) {
return Nothing();
}
if (MOZ_UNLIKELY(aChild1 == aChild2)) {
return Some(0);
}
MOZ_ASSERT(aChild1 || aChild2);
if (!aChild1) { // i.e., end of parent vs aChild2
MOZ_ASSERT(aChild2->GetParentOrShadowHostNode());
return Some(1);
}
if (!aChild2) { // i.e., aChild1 vs. end of parent
MOZ_ASSERT(aChild1->GetParentOrShadowHostNode());
return Some(-1);
}
MOZ_ASSERT(aChild1->GetParentOrShadowHostNode());
const nsINode& commonParentNode = *aChild1->GetParentOrShadowHostNode();
MOZ_ASSERT(aChild2->GetParentOrShadowHostNode() == &commonParentNode);
if (aChild1->GetNextSibling() == aChild2) {
return Some(-1);
}
if (aChild2->GetNextSibling() == aChild1) {
return Some(1);
}
MOZ_ASSERT(commonParentNode.GetFirstChild());
const nsIContent& firstChild = *commonParentNode.GetFirstChild();
if (aChild1 == &firstChild) {
return Some(-1);
}
if (aChild2 == &firstChild) {
return Some(1);
}
MOZ_ASSERT(commonParentNode.GetLastChild());
const nsIContent& lastChild = *commonParentNode.GetLastChild();
MOZ_ASSERT(&firstChild != &lastChild);
if (aChild1 == &lastChild) {
return Some(1);
}
if (aChild2 == &lastChild) {
return Some(-1);
}
// nsINode::ComputeIndexOf() computes the index with scanning siblings from
// the first child in the worst case. However, if the node caches the
// computed result, the scan range may be narrowed in fortunate cases.
// Therefore, if the node uses the cache, we should use it because the callers
// may need to compute the index again. In such cases, the cache saves the
// computation cost.
if (commonParentNode.MaybeCachesComputedIndex()) {
Maybe<int32_t> child1Index;
Maybe<int32_t> child2Index;
if (aIndexCache) {
aIndexCache->ComputeIndicesOf(&commonParentNode, aChild1, aChild2,
child1Index, child2Index);
} else {
child1Index = commonParentNode.ComputeIndexOf(aChild1);
child2Index = commonParentNode.ComputeIndexOf(aChild2);
}
if (MOZ_LIKELY(child1Index.isSome() && child2Index.isSome())) {
MOZ_ASSERT(*child1Index != *child2Index);
return Some(*child1Index < *child2Index ? -1 : 1);
}
// XXX Keep the odd traditional behavior for now.
return Some(child1Index.isNothing() && child2Index.isSome() ? -1 : 1);
}
// On the other hand, it does not use the cache, let's scan siblings starting
// from aChild1. Then, if we find aChild2, the position is aChild1 < aChild2.
// Otherwise, aChild2 < aChild1. This narrows the scanning range than using
// ComputeIndexOf(), i.e., even in the worst case, this is faster than a call
// of ComputeIndexOf().
for (const nsIContent* followingSiblingOfChild1 = aChild1->GetNextSibling();
followingSiblingOfChild1;
followingSiblingOfChild1 = followingSiblingOfChild1->GetNextSibling()) {
if (followingSiblingOfChild1 == aChild2) {
return Some(-1);
}
}
MOZ_ASSERT(aChild2->ComputeIndexInParentNode().isSome());
return Some(1);
}
/* static */
Maybe<int32_t> nsContentUtils::CompareClosestCommonAncestorChildren(
const nsINode& aParent, const nsINode* aChild1, const nsINode* aChild2,
nsContentUtils::NodeIndexCache* aIndexCache = nullptr) {
MOZ_ASSERT_IF(aChild1, GetParentOrShadowHostNode(aChild1));
MOZ_ASSERT_IF(aChild2, GetParentOrShadowHostNode(aChild2));
if (aChild1 && aChild2) {
if (MOZ_UNLIKELY(aChild1->IsShadowRoot())) {
// Shadow roots come before light DOM per
MOZ_ASSERT(!aChild2->IsShadowRoot(), "Two shadow roots?");
return Some(-1);
}
if (MOZ_UNLIKELY(aChild2->IsShadowRoot())) {
return Some(1);
}
}
if (MOZ_UNLIKELY((aChild1 && (aChild1->IsRootOfNativeAnonymousSubtree() ||
aChild1->IsDocumentFragment())) ||
(aChild2 && (aChild2->IsRootOfNativeAnonymousSubtree() ||
aChild2->IsDocumentFragment())))) {
// XXX Keep the odd traditional behavior for now. I think that we should
// return Nothing in this case.
return Some(1);
}
const Maybe<int32_t> comp =
nsContentUtils::CompareChildNodes(aChild1, aChild2, aIndexCache);
if (MOZ_UNLIKELY(comp.isNothing())) {
NS_ASSERTION(comp.isSome(),
"nsContentUtils::CompareChildNodes() must return Some here. "
"It should've already checked before we call it.");
// XXX Keep the odd traditional behavior for now. I think that we should
// return Nothing in this case.
return Some(1);
}
MOZ_ASSERT_IF(!*comp, aChild1 == aChild2);
MOZ_ASSERT_IF(*comp < 0, (aChild1 ? *aChild1->ComputeIndexInParentNode()
: aParent.GetChildCount()) <
(aChild2 ? *aChild2->ComputeIndexInParentNode()
: aParent.GetChildCount()));
MOZ_ASSERT_IF(*comp > 0, (aChild2 ? *aChild2->ComputeIndexInParentNode()
: aParent.GetChildCount()) <
(aChild1 ? *aChild1->ComputeIndexInParentNode()
: aParent.GetChildCount()));
return comp;
}
/* static */
Maybe<int32_t> nsContentUtils::CompareChildOffsetAndChildNode(
uint32_t aOffset1, const nsINode& aChild2,
NodeIndexCache* aIndexCache /* = nullptr */) {
if (NS_WARN_IF(aChild2.IsRootOfNativeAnonymousSubtree()) ||
NS_WARN_IF(aChild2.IsDocumentFragment())) {
return Nothing();
}
MOZ_ASSERT(aChild2.GetParentOrShadowHostNode());
const nsINode& parentNode = *aChild2.GetParentOrShadowHostNode();
if (aOffset1 >= parentNode.GetChildCount()) {
return Some(1);
}
MOZ_ASSERT(parentNode.GetFirstChild());
const nsIContent& firstChild = *parentNode.GetFirstChild();
if (&aChild2 == &firstChild) {
return Some(!aOffset1 ? 0 : 1);
}
MOZ_ASSERT(parentNode.GetLastChild());
const nsIContent& lastChild = *parentNode.GetLastChild();
MOZ_ASSERT(&firstChild != &lastChild);
if (&aChild2 == &lastChild) {
return Some(aOffset1 == parentNode.GetChildCount() - 1 ? 0 : -1);
}
const Maybe<int32_t> child2Index =
aIndexCache ? aIndexCache->ComputeIndexOf(&parentNode, &aChild2)
: GetIndexInParent<TreeKind::DOM>(&parentNode, &aChild2);
if (NS_WARN_IF(child2Index.isNothing())) {
return Some(1);
}
return Some(aOffset1 == uint32_t(*child2Index)
? 0
: (aOffset1 < uint32_t(*child2Index) ? -1 : 1));
}
/* static */
Maybe<int32_t> nsContentUtils::CompareChildNodeAndChildOffset(
const nsINode& aChild1, uint32_t aOffset2,
NodeIndexCache* aIndexCache /* = nullptr */) {
Maybe<int32_t> comp = CompareChildOffsetAndChildNode(aOffset2, aChild1);
if (comp.isNothing()) {
return comp;
}
return Some(*comp * -1);
}
/* static */
Maybe<int32_t> nsContentUtils::ComparePointsWithIndices(
const nsINode* aParent1, uint32_t aOffset1, const nsINode* aParent2,
uint32_t aOffset2, NodeIndexCache* aIndexCache) {
MOZ_ASSERT(aParent1);
MOZ_ASSERT(aParent2);
if (aParent1 == aParent2) {
return Some(aOffset1 < aOffset2 ? -1 : (aOffset1 > aOffset2 ? 1 : 0));
}
const CommonAncestors commonAncestors(*aParent1, *aParent2,
GetParentOrShadowHostNode);
if (MOZ_UNLIKELY(!commonAncestors.GetClosestCommonAncestor())) {
return Nothing();
}
const nsINode* closestCommonAncestorChild1 =
commonAncestors.GetClosestCommonAncestorChild1();
const nsINode* closestCommonAncestorChild2 =
commonAncestors.GetClosestCommonAncestorChild2();
MOZ_ASSERT(closestCommonAncestorChild1 != closestCommonAncestorChild2);
commonAncestors.WarnIfClosestCommonAncestorChildrenAreNotInChildList();
if (closestCommonAncestorChild1 && closestCommonAncestorChild2) {
return CompareClosestCommonAncestorChildren(
*commonAncestors.GetClosestCommonAncestor(),
closestCommonAncestorChild1, closestCommonAncestorChild2, aIndexCache);
}
if (closestCommonAncestorChild2) {
MOZ_ASSERT(closestCommonAncestorChild2->GetParentOrShadowHostNode() ==
aParent1);
if (MOZ_UNLIKELY(
closestCommonAncestorChild2->IsRootOfNativeAnonymousSubtree() ||
closestCommonAncestorChild2->IsDocumentFragment())) {
// XXX Keep the odd traditional behavior for now.
return Some(1);
}
const Maybe<int32_t> comp = nsContentUtils::CompareChildOffsetAndChildNode(
aOffset1, *closestCommonAncestorChild2, aIndexCache);
if (NS_WARN_IF(comp.isNothing())) {
NS_ASSERTION(
comp.isSome(),
"nsContentUtils::CompareChildOffsetAndChildNode() must return Some "
"here. It should've already checked before we call it.");
// XXX Keep the odd traditional behavior for now.
return Some(1);
}
// If the child of the closest common ancestor is at aOffset1 of aParent1,
// it means that aOffset2 of aParent2 refers a descendant of the child.
// So, aOffset2 of aParent2 refers a descendant of aOffset1 of aParent1.
if (!*comp) {
return Some(-1);
}
return comp;
}
MOZ_ASSERT(closestCommonAncestorChild1);
MOZ_ASSERT(closestCommonAncestorChild1->GetParentOrShadowHostNode() ==
aParent2);
if (MOZ_UNLIKELY(
closestCommonAncestorChild1->IsRootOfNativeAnonymousSubtree() ||
closestCommonAncestorChild1->IsDocumentFragment())) {
// XXX Keep the odd traditional behavior for now.
return Some(-1);
}
const Maybe<int32_t> comp = nsContentUtils::CompareChildNodeAndChildOffset(
*closestCommonAncestorChild1, aOffset2, aIndexCache);
if (NS_WARN_IF(comp.isNothing())) {
NS_ASSERTION(comp.isSome(),
"nsContentUtils::CompareChildOffsetAndChildNode() must return "
"Some here. It should've already checked before we call it.");
// XXX Keep the odd traditional behavior for now.
return Some(-1);
}
// If the child of the closet common ancestor is at aOffset2 of aParent2, it
// means that aOffset1 of aParent1 refers a descendant of the child.
// So, aOffset1 of aParent1 refers a descendant of aOffset2 of aParent2.
if (!*comp) {
return Some(1);
}
return comp;
}
/* static */
BrowserParent* nsContentUtils::GetCommonBrowserParentAncestor(
BrowserParent* aBrowserParent1, BrowserParent* aBrowserParent2) {
MOZ_ASSERT(aBrowserParent1);
MOZ_ASSERT(aBrowserParent2);
return CommonAncestors(*aBrowserParent1, *aBrowserParent2,
GetParentBrowserParent)
.GetClosestCommonAncestor();
}
/* 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 PT1, typename RT1, typename PT2, typename RT2>
Maybe<int32_t> nsContentUtils::ComparePoints(
const RangeBoundaryBase<PT1, RT1>& aBoundary1,
const RangeBoundaryBase<PT2, RT2>& aBoundary2,
NodeIndexCache* aIndexCache /* = nullptr */) {
if (!aBoundary1.IsSet() || !aBoundary2.IsSet()) {
return Nothing{};
}
const auto kValidOrInvalidOffsets1 =
RangeBoundaryBase<PT1, RT1>::OffsetFilter::kValidOrInvalidOffsets;
const auto kValidOrInvalidOffsets2 =
RangeBoundaryBase<PT2, RT2>::OffsetFilter::kValidOrInvalidOffsets;
// RangeBoundaryBase instances may be initialized only with a child node or an
// offset in the container. If both instances have computed offset, we can
// use ComparePointsWithIndices() which works with offsets.
if (aBoundary1.HasOffset() && aBoundary2.HasOffset()) {
return ComparePointsWithIndices(
aBoundary1.GetContainer(), *aBoundary1.Offset(kValidOrInvalidOffsets1),
aBoundary2.GetContainer(), *aBoundary2.Offset(kValidOrInvalidOffsets2),
aIndexCache);
}
// Otherwise, i.e., at least one RangeBoundaryBase stores the child node.
// In the most cases, RangeBoundaryBase has it, so, the worst scenario here
// is, one of the boundaries comes from a StaticRange or is initialized with
// offset and RangeBoundaryIsMutationObserved::No. However, for making it
// faster in the most cases, we should compare the child nodes without
// offsets if possible.
// If we're comparing children in the same container, we don't need to compute
// common ancestors. So, we can skip it and just compare the children.
if (aBoundary1.GetContainer() == aBoundary2.GetContainer()) {
const nsIContent* const child1 = aBoundary1.GetChildAtOffset();
const nsIContent* const child2 = aBoundary2.GetChildAtOffset();
return CompareClosestCommonAncestorChildren(*aBoundary1.GetContainer(),
child1, child2, aIndexCache);
}
// Otherwise, we need to compare the common ancestor children which is the
// most distant different inclusive ancestors of the containers. So, the
// following implementation is similar to ComparePointsWithIndices(), but we
// don't have offset, so, we cannot use offset when we compare the boundaries
// whose one is a descendant of the other.
const CommonAncestors commonAncestors(*aBoundary1.GetContainer(),
*aBoundary2.GetContainer(),
GetParentOrShadowHostNode);
if (MOZ_UNLIKELY(!commonAncestors.GetClosestCommonAncestor())) {
return Nothing();
}
MOZ_ASSERT(commonAncestors.GetClosestCommonAncestor());
const nsINode* closestCommonAncestorChild1 =
commonAncestors.GetClosestCommonAncestorChild1();
const nsINode* closestCommonAncestorChild2 =
commonAncestors.GetClosestCommonAncestorChild2();
commonAncestors.WarnIfClosestCommonAncestorChildrenAreNotInChildList();
MOZ_ASSERT(closestCommonAncestorChild1 != closestCommonAncestorChild2);
if (closestCommonAncestorChild1 && closestCommonAncestorChild2) {
return CompareClosestCommonAncestorChildren(
*commonAncestors.GetClosestCommonAncestor(),
closestCommonAncestorChild1, closestCommonAncestorChild2, aIndexCache);
}
if (closestCommonAncestorChild2) {
MOZ_ASSERT(closestCommonAncestorChild2->GetParentOrShadowHostNode() ==
aBoundary1.GetContainer());
if (MOZ_UNLIKELY(
closestCommonAncestorChild2->IsRootOfNativeAnonymousSubtree() ||
closestCommonAncestorChild2->IsDocumentFragment())) {
// XXX Keep the odd traditional behavior for now.
return Some(1);
}
const Maybe<int32_t> comp = nsContentUtils::CompareChildNodes(
aBoundary1.GetChildAtOffset(), closestCommonAncestorChild2,
aIndexCache);
if (NS_WARN_IF(comp.isNothing())) {
NS_ASSERTION(
comp.isSome(),
"nsContentUtils::CompareChildOffsetAndChildNode() must return Some "
"here. It should've already checked before we call it.");
// XXX Keep the odd traditional behavior for now.
return Some(1);
}
// If the child of the closest common ancestor is at aBoundary1, it means
// that aBoundary2 refers a descendant of the child. So, aBoundary2 refers a
// descendant of aBoundary1.
if (!*comp) {
MOZ_ASSERT(*closestCommonAncestorChild2->ComputeIndexInParentNode() ==
*aBoundary1.Offset(kValidOrInvalidOffsets1));
return Some(-1);
}
MOZ_ASSERT_IF(*comp < 0,
*aBoundary1.Offset(kValidOrInvalidOffsets1) <
*closestCommonAncestorChild2->ComputeIndexInParentNode());
MOZ_ASSERT_IF(*comp > 0,
*closestCommonAncestorChild2->ComputeIndexInParentNode() <
*aBoundary1.Offset(kValidOrInvalidOffsets1));
return comp;
}
MOZ_ASSERT(closestCommonAncestorChild1);
MOZ_ASSERT(closestCommonAncestorChild1->GetParentOrShadowHostNode() ==
aBoundary2.GetContainer());
if (MOZ_UNLIKELY(
closestCommonAncestorChild1->IsRootOfNativeAnonymousSubtree() ||
closestCommonAncestorChild1->IsDocumentFragment())) {
// XXX Keep the odd traditional behavior for now.
return Some(-1);
}
const Maybe<int32_t> comp = nsContentUtils::CompareChildNodes(
closestCommonAncestorChild1, aBoundary2.GetChildAtOffset(), aIndexCache);
if (NS_WARN_IF(comp.isNothing())) {
NS_ASSERTION(comp.isSome(),
"nsContentUtils::CompareChildOffsetAndChildNode() must return "
"Some here. It should've already checked before we call it.");
// XXX Keep the odd traditional behavior for now.
return Some(-1);
}
// If the child of the closet common ancestor is at aBoundary2, it means that
// aBoundary1 refers a descendant of the child. So, aBoundary1 refers a
// descendant of aBoundary2.
if (!*comp) {
MOZ_ASSERT(*closestCommonAncestorChild1->ComputeIndexInParentNode() ==
*aBoundary2.Offset(kValidOrInvalidOffsets2));
return Some(1);
}
MOZ_ASSERT_IF(*comp < 0,
*closestCommonAncestorChild1->ComputeIndexInParentNode() <
*aBoundary2.Offset(kValidOrInvalidOffsets2));
MOZ_ASSERT_IF(*comp > 0,
*aBoundary2.Offset(kValidOrInvalidOffsets2) <
*closestCommonAncestorChild1->ComputeIndexInParentNode());
return comp;
}
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.
//
if (const auto* control = nsIFormControl::FromNode(aContent)) {
// 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::ContainsChar(nsAtom* aAtom, char aChar) {
const uint32_t len = aAtom->GetLength();
if (!len) {
return false;
}
const char16_t* name = aAtom->GetUTF16String();
uint32_t i = 0;
while (i < len) {
if (name[i] == aChar) {
return true;
}
i++;
}
return false;
}
// 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::IsInPrivateBrowsing(nsILoadGroup* aLoadGroup) {
if (!aLoadGroup) {
return false;
}
// See duplicated code in Document::Reset/ResetToURI
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();
}
const bool isPrivate = aDoc->IsInPrivateBrowsing();
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();
}
return NS_UsePrivateBrowsing(aChannel) ? 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,
mozilla::dom::FetchPriority aFetchPriority) {
MOZ_ASSERT(aURI, "Must have a URI");
MOZ_ASSERT(aContext, "Must have a context");
MOZ_ASSERT(aLoadingDocument, "Must have a document");
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, aFetchPriority, 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 */
bool nsContentUtils::SpoofLocaleEnglish(const Document* aDocument) {
return SpoofLocaleEnglish() && (!aDocument || !aDocument->AllowsL10n());
}
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);
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(workerPrivate, 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, ""_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, const SourceLocation& aLoc) {
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, aLoc);
}
/* 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,
const SourceLocation& aLoc) {
uint64_t innerWindowID = aDocument ? aDocument->InnerWindowID() : 0;
if (aLoc || !aDocument || !aDocument->GetDocumentURI()) {
return ReportToConsoleByWindowID(aErrorText, aErrorFlags, aCategory,
innerWindowID, aLoc);
}
return ReportToConsoleByWindowID(aErrorText, aErrorFlags, aCategory,
innerWindowID,
SourceLocation(aDocument->GetDocumentURI()));
}
/* static */
nsresult nsContentUtils::ReportToConsoleByWindowID(
const nsAString& aErrorText, uint32_t aErrorFlags,
const nsACString& aCategory, uint64_t aInnerWindowID,
const SourceLocation& aLocation) {
nsresult rv;
if (!sConsoleService) { // only need to bother null-checking here
rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIScriptError> errorObject =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (aLocation.mResource.is<nsCOMPtr<nsIURI>>()) {
nsIURI* uri = aLocation.mResource.as<nsCOMPtr<nsIURI>>();
rv = errorObject->InitWithSourceURI(aErrorText, uri, aLocation.mLine,
aLocation.mColumn, aErrorFlags,
aCategory, aInnerWindowID);
} else {
rv = errorObject->InitWithWindowID(
aErrorText, aLocation.mResource.as<nsCString>(), aLocation.mLine,
aLocation.mColumn, 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::IsAddonDoc(const Document* aDocument) {
return aDocument &&
aDocument->NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal();
}
bool nsContentUtils::IsChildOfSameType(Document* aDoc) {
if (BrowsingContext* bc = aDoc->GetBrowsingContext()) {
return bc->GetParent();
}
return false;
}
static bool IsJSONType(const nsACString& aContentType) {
return aContentType.EqualsLiteral(TEXT_JSON) ||
aContentType.EqualsLiteral(APPLICATION_JSON);
}
static bool IsNonPlainTextType(const nsACString& aContentType) {
// MIME type suffixes which should not be plain text.
static constexpr std::string_view kNonPlainTextTypes[] = {
"html",
"xml",
"xsl",
"calendar",
"x-calendar",
"x-vcalendar",
"vcalendar",
"vcard",
"x-vcard",
"directory",
"ldif",
"qif",
"x-qif",
"x-csv",
"x-vcf",
"rtf",
"comma-separated-values",
"csv",
"tab-separated-values",
"tsv",
"ofx",
"vnd.sun.j2me.app-descriptor",
"x-ms-iqy",
"x-ms-odc",
"x-ms-rqy",
"x-ms-contact"};
// Trim off the "text/" prefix for comparison.
MOZ_ASSERT(StringBeginsWith(aContentType, "text/"_ns));
std::string_view suffix = aContentType;
suffix.remove_prefix(5);
for (std::string_view type : kNonPlainTextTypes) {
if (type == suffix) {
return true;
}
}
return false;
}
bool nsContentUtils::IsPlainTextType(const nsACString& aContentType) {
// All `text/*`, any JSON type and any JavaScript type are considered "plain
// text" types for the purposes of how to render them as a document.
return (StringBeginsWith(aContentType, "text/"_ns) &&
!IsNonPlainTextType(aContentType)) ||
IsJSONType(aContentType) || IsJavascriptMIMEType(aContentType);
}
bool nsContentUtils::IsUtf8OnlyPlainTextType(const nsACString& aContentType) {
// NOTE: This must be a subset of the list in IsPlainTextType().
return IsJSONType(aContentType) ||
aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
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
void nsContentUtils::ForEachEventAttributeName(
int32_t aType, const FunctionRef<void(nsAtom*)> aFunc) {
for (auto iter = sAtomEventTable->ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data().mType & aType) {
aFunc(iter.Key());
}
}
}
// 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 (const HTMLTextAreaElement* const textAreaElement =
HTMLTextAreaElement::FromNode(aEventTargetElement)) {
aEditorBase = textAreaElement->GetExtantTextEditor();
useInputEvent = true;
} else if (const HTMLInputElement* const inputElement =
HTMLInputElement::FromNode(aEventTargetElement)) {
if (inputElement->IsInputEventTarget()) {
aEditorBase = inputElement->GetExtantTextEditor();
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);