diff options
Diffstat (limited to 'dom/base/nsContentUtils.cpp')
-rw-r--r-- | dom/base/nsContentUtils.cpp | 9783 |
1 files changed, 9783 insertions, 0 deletions
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp new file mode 100644 index 000000000..29d28f8ce --- /dev/null +++ b/dom/base/nsContentUtils.cpp @@ -0,0 +1,9783 @@ +/* -*- 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 <math.h> + +#include "DecoderTraits.h" +#include "harfbuzz/hb.h" +#include "imgICache.h" +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "imgLoader.h" +#include "imgRequestProxy.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Value.h" +#include "Layers.h" +#include "MediaDecoder.h" +// nsNPAPIPluginInstance must be included before nsIDocument.h, which is included in mozAutoDocUpdate.h. +#include "nsNPAPIPluginInstance.h" +#include "gfxDrawable.h" +#include "gfxPrefs.h" +#include "ImageOps.h" +#include "mozAutoDocUpdate.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/AutoTimelineMarker.h" +#include "mozilla/Base64.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FileSystemSecurity.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/dom/HTMLTemplateElement.h" +#include "mozilla/dom/HTMLContentElement.h" +#include "mozilla/dom/HTMLShadowElement.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/TextDecoder.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/Likely.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/TextEvents.h" +#include "nsArrayUtils.h" +#include "nsAString.h" +#include "nsAttrName.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsBindingManager.h" +#include "nsCaret.h" +#include "nsCCUncollectableMarker.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsCOMPtr.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentDLF.h" +#include "nsContentList.h" +#include "nsContentPolicyUtils.h" +#include "nsContentSecurityManager.h" +#include "nsCPrefetchService.h" +#include "nsCRT.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDataHashtable.h" +#include "nsDocShellCID.h" +#include "nsDocument.h" +#include "nsDOMCID.h" +#include "mozilla/dom/DataTransfer.h" +#include "nsDOMJSUtils.h" +#include "nsDOMMutationObserver.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "nsGenericHTMLElement.h" +#include "nsGenericHTMLFrameElement.h" +#include "nsGkAtoms.h" +#include "nsHostObjectProtocolHandler.h" +#include "nsHtml5Module.h" +#include "nsHtml5StringParser.h" +#include "nsIAddonPolicyService.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsICategoryManager.h" +#include "nsIChannelEventSink.h" +#include "nsICharsetDetectionObserver.h" +#include "nsIChromeRegistry.h" +#include "nsIConsoleService.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentSink.h" +#include "nsIContentViewer.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentType.h" +#include "nsIDOMEvent.h" +#include "nsIDOMElement.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDOMXULCommandEvent.h" +#include "nsIDragService.h" +#include "nsIEditor.h" +#include "nsIFormControl.h" +#include "nsIForm.h" +#include "nsIFragmentContentSink.h" +#include "nsContainerFrame.h" +#include "nsIHTMLDocument.h" +#include "nsIIdleService.h" +#include "nsIImageLoadingContent.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIIOService.h" +#include "nsILineBreaker.h" +#include "nsILoadContext.h" +#include "nsILoadGroup.h" +#include "nsIMemoryReporter.h" +#include "nsIMIMEHeaderParam.h" +#include "nsIMIMEService.h" +#include "nsINode.h" +#include "mozilla/dom/NodeInfo.h" +#include "nsIObjectLoadingContent.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsIParser.h" +#include "nsIParserService.h" +#include "nsIPermissionManager.h" +#include "nsIPluginHost.h" +#include "nsIRequest.h" +#include "nsIRunnable.h" +#include "nsIScriptContext.h" +#include "nsIScriptError.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScrollable.h" +#include "nsIStreamConverterService.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "nsIURIWithPrincipal.h" +#include "nsIURL.h" +#include "nsIWebNavigation.h" +#include "nsIWindowMediator.h" +#include "nsIWordBreaker.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "nsLWBrkCIID.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsNodeInfoManager.h" +#include "nsNullPrincipal.h" +#include "nsParserCIID.h" +#include "nsParserConstants.h" +#include "nsPIDOMWindow.h" +#include "nsPresContext.h" +#include "nsPrintfCString.h" +#include "nsReferencedElement.h" +#include "nsSandboxFlags.h" +#include "nsScriptSecurityManager.h" +#include "nsStreamUtils.h" +#include "nsTextEditorState.h" +#include "nsTextFragment.h" +#include "nsTextNode.h" +#include "nsThreadUtils.h" +#include "nsUnicharUtilCIID.h" +#include "nsUnicodeProperties.h" +#include "nsViewManager.h" +#include "nsViewportInfo.h" +#include "nsWidgetsCID.h" +#include "nsIWindowProvider.h" +#include "nsWrapperCacheInlines.h" +#include "nsXULPopupManager.h" +#include "xpcprivate.h" // nsXPConnect +#include "HTMLSplitOnSpacesTokenizer.h" +#include "nsContentTypeParser.h" +#include "nsICookiePermission.h" +#include "mozIThirdPartyUtil.h" +#include "nsICookieService.h" +#include "mozilla/EnumSet.h" +#include "mozilla/BloomFilter.h" +#include "TabChild.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" + +#include "nsIBidiKeyboard.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); + +class imgLoader; + +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; +nsIParserService *nsContentUtils::sParserService = nullptr; +nsNameSpaceManager *nsContentUtils::sNameSpaceManager; +nsIIOService *nsContentUtils::sIOService; +nsIUUIDGenerator *nsContentUtils::sUUIDGenerator; +nsIConsoleService *nsContentUtils::sConsoleService; +nsDataHashtable<nsISupportsHashKey, EventNameMapping>* nsContentUtils::sAtomEventTable = nullptr; +nsDataHashtable<nsStringHashKey, EventNameMapping>* nsContentUtils::sStringEventTable = nullptr; +nsCOMArray<nsIAtom>* nsContentUtils::sUserDefinedEvents = nullptr; +nsIStringBundleService *nsContentUtils::sStringBundleService; +nsIStringBundle *nsContentUtils::sStringBundles[PropertiesFile_COUNT]; +nsIContentPolicy *nsContentUtils::sContentPolicyService; +bool nsContentUtils::sTriedToGetContentPolicy = false; +nsILineBreaker *nsContentUtils::sLineBreaker; +nsIWordBreaker *nsContentUtils::sWordBreaker; +nsIBidiKeyboard *nsContentUtils::sBidiKeyboard = nullptr; +uint32_t nsContentUtils::sScriptBlockerCount = 0; +uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0; +uint32_t nsContentUtils::sMicroTaskLevel = 0; +AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners = nullptr; +uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0; +nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr; + +bool nsContentUtils::sIsHandlingKeyBoardEvent = false; +bool nsContentUtils::sAllowXULXBL_for_file = false; + +nsString* nsContentUtils::sShiftText = nullptr; +nsString* nsContentUtils::sControlText = nullptr; +nsString* nsContentUtils::sMetaText = nullptr; +nsString* nsContentUtils::sOSText = nullptr; +nsString* nsContentUtils::sAltText = nullptr; +nsString* nsContentUtils::sModifierSeparator = nullptr; + +bool nsContentUtils::sInitialized = false; +bool nsContentUtils::sIsFullScreenApiEnabled = false; +bool nsContentUtils::sIsUnprefixedFullscreenApiEnabled = false; +bool nsContentUtils::sTrustedFullScreenOnly = true; +bool nsContentUtils::sIsCutCopyAllowed = true; +bool nsContentUtils::sIsFrameTimingPrefEnabled = false; +bool nsContentUtils::sIsPerformanceTimingEnabled = false; +bool nsContentUtils::sIsResourceTimingEnabled = false; +bool nsContentUtils::sIsUserTimingLoggingEnabled = false; +bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; +bool nsContentUtils::sEncodeDecodeURLHash = false; +bool nsContentUtils::sGettersDecodeURLHash = false; +bool nsContentUtils::sPrivacyResistFingerprinting = false; +bool nsContentUtils::sSendPerformanceTimingNotifications = false; +bool nsContentUtils::sUseActivityCursor = false; + +uint32_t nsContentUtils::sHandlingInputTimeout = 1000; + +uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY; +uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT; + +nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr; +nsIParser* nsContentUtils::sXMLFragmentParser = nullptr; +nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr; +bool nsContentUtils::sFragmentParsingActive = false; + +#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) +bool nsContentUtils::sDOMWindowDumpEnabled; +#endif + +bool nsContentUtils::sDoNotTrackEnabled = false; + +mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump"); + +// Subset of http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name +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 AutocompleteCategory +{ + #define AUTOCOMPLETE_CATEGORY(name_, value_) eAutocompleteCategory_##name_, + #include "AutocompleteFieldList.h" + #undef AUTOCOMPLETE_CATEGORY +}; + +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 } +}; + +namespace { + +static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID); +static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); + +static PLDHashTable* sEventListenerManagersHash; + +class DOMEventListenerManagersHashReporter final : public nsIMemoryReporter +{ + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~DOMEventListenerManagersHashReporter() {} + +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() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR +}; + +class CharsetDetectionObserver final : public nsICharsetDetectionObserver +{ +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD Notify(const char *aCharset, nsDetectionConfident aConf) override + { + mCharset = aCharset; + return NS_OK; + } + + const nsACString& GetResult() const + { + return mCharset; + } + +private: + nsCString mCharset; +}; + +} // namespace + +/* static */ +TimeDuration +nsContentUtils::HandlingUserInputTimeout() +{ + return TimeDuration::FromMilliseconds(sHandlingInputTimeout); +} + +// static +nsresult +nsContentUtils::Init() +{ + if (sInitialized) { + NS_WARNING("Init() called twice"); + + return NS_OK; + } + + sNameSpaceManager = nsNameSpaceManager::GetInstance(); + NS_ENSURE_TRUE(sNameSpaceManager, NS_ERROR_OUT_OF_MEMORY); + + sXPConnect = nsXPConnect::XPConnect(); + + sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager(); + if(!sSecurityManager) + return NS_ERROR_FAILURE; + NS_ADDREF(sSecurityManager); + + sSecurityManager->GetSystemPrincipal(&sSystemPrincipal); + MOZ_ASSERT(sSystemPrincipal); + + // We use the constructor here because we want infallible initialization; we + // apparently don't care whether sNullSubjectPrincipal has a sane URI or not. + RefPtr<nsNullPrincipal> nullPrincipal = new nsNullPrincipal(); + nullPrincipal->Init(); + nullPrincipal.forget(&sNullSubjectPrincipal); + + nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); + if (NS_FAILED(rv)) { + // This makes life easier, but we can live without it. + + sIOService = nullptr; + } + + rv = CallGetService(NS_LBRK_CONTRACTID, &sLineBreaker); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CallGetService(NS_WBRK_CONTRACTID, &sWordBreaker); + NS_ENSURE_SUCCESS(rv, rv); + + 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>; + + Preferences::AddBoolVarCache(&sAllowXULXBL_for_file, + "dom.allow_XUL_XBL_for_file"); + + Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled, + "full-screen-api.enabled"); + + Preferences::AddBoolVarCache(&sIsUnprefixedFullscreenApiEnabled, + "full-screen-api.unprefix.enabled"); + + Preferences::AddBoolVarCache(&sTrustedFullScreenOnly, + "full-screen-api.allow-trusted-requests-only"); + + Preferences::AddBoolVarCache(&sIsCutCopyAllowed, + "dom.allow_cut_copy", true); + + Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled, + "dom.enable_performance", true); + + Preferences::AddBoolVarCache(&sIsResourceTimingEnabled, + "dom.enable_resource_timing", true); + + Preferences::AddBoolVarCache(&sIsUserTimingLoggingEnabled, + "dom.performance.enable_user_timing_logging", false); + + Preferences::AddBoolVarCache(&sIsFrameTimingPrefEnabled, + "dom.enable_frame_timing", false); + + Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled, + "dom.forms.autocomplete.experimental", false); + + Preferences::AddBoolVarCache(&sEncodeDecodeURLHash, + "dom.url.encode_decode_hash", false); + + Preferences::AddBoolVarCache(&sGettersDecodeURLHash, + "dom.url.getters_decode_hash", false); + + Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting, + "privacy.resistFingerprinting", false); + + Preferences::AddUintVarCache(&sHandlingInputTimeout, + "dom.event.handling-user-input-time-limit", + 1000); + + Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications, + "dom.performance.enable_notify_performance_timing", false); + + Preferences::AddUintVarCache(&sCookiesLifetimePolicy, + "network.cookie.lifetimePolicy", + nsICookieService::ACCEPT_NORMALLY); + + Preferences::AddUintVarCache(&sCookiesBehavior, + "network.cookie.cookieBehavior", + nsICookieService::BEHAVIOR_ACCEPT); + +#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) + Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled, + "browser.dom.window.dump.enabled"); +#endif + + Preferences::AddBoolVarCache(&sDoNotTrackEnabled, + "privacy.donottrackheader.enabled", false); + + Preferences::AddBoolVarCache(&sUseActivityCursor, + "ui.use_activity_cursor", false); + + Element::InitCCCallbacks(); + + nsCOMPtr<nsIUUIDGenerator> uuidGenerator = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + uuidGenerator.forget(&sUUIDGenerator); + + sInitialized = true; + + return NS_OK; +} + +void +nsContentUtils::GetShiftText(nsAString& text) +{ + if (!sShiftText) + InitializeModifierStrings(); + text.Assign(*sShiftText); +} + +void +nsContentUtils::GetControlText(nsAString& text) +{ + if (!sControlText) + InitializeModifierStrings(); + text.Assign(*sControlText); +} + +void +nsContentUtils::GetMetaText(nsAString& text) +{ + if (!sMetaText) + InitializeModifierStrings(); + text.Assign(*sMetaText); +} + +void +nsContentUtils::GetOSText(nsAString& text) +{ + if (!sOSText) { + InitializeModifierStrings(); + } + text.Assign(*sOSText); +} + +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::services::GetStringBundleService(); + nsCOMPtr<nsIStringBundle> bundle; + DebugOnly<nsresult> rv = NS_OK; + if (bundleService) { + rv = bundleService->CreateBundle( "chrome://global-platform/locale/platformKeys.properties", + getter_AddRefs(bundle)); + } + + NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/platformKeys.properties could not be loaded"); + nsXPIDLString shiftModifier; + nsXPIDLString metaModifier; + nsXPIDLString osModifier; + nsXPIDLString altModifier; + nsXPIDLString controlModifier; + nsXPIDLString modifierSeparator; + if (bundle) { + //macs use symbols for each modifier key, so fetch each from the bundle, which also covers i18n + bundle->GetStringFromName(u"VK_SHIFT", getter_Copies(shiftModifier)); + bundle->GetStringFromName(u"VK_META", getter_Copies(metaModifier)); + bundle->GetStringFromName(u"VK_WIN", getter_Copies(osModifier)); + bundle->GetStringFromName(u"VK_ALT", getter_Copies(altModifier)); + bundle->GetStringFromName(u"VK_CONTROL", getter_Copies(controlModifier)); + bundle->GetStringFromName(u"MODIFIER_SEPARATOR", getter_Copies(modifierSeparator)); + } + //if any of these don't exist, we get an empty string + sShiftText = new nsString(shiftModifier); + sMetaText = new nsString(metaModifier); + sOSText = new nsString(osModifier); + sAltText = new nsString(altModifier); + sControlText = new nsString(controlModifier); + sModifierSeparator = new nsString(modifierSeparator); +} + +// Because of SVG/SMIL we have several atoms mapped to the same +// id, but we can rely on MESSAGE_TO_EVENT to map id to only one atom. +static bool +ShouldAddEventToStringEventTable(const EventNameMapping& aMapping) +{ + switch(aMapping.mMessage) { +#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \ + case message_: return nsGkAtoms::on##name_ == aMapping.mAtom; +#include "mozilla/EventNameList.h" +#undef MESSAGE_TO_EVENT + default: + break; + } + return false; +} + +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, false }, +#define WINDOW_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 nsDataHashtable<nsISupportsHashKey, EventNameMapping>( + ArrayLength(eventArray)); + sStringEventTable = new nsDataHashtable<nsStringHashKey, EventNameMapping>( + ArrayLength(eventArray)); + sUserDefinedEvents = new nsCOMArray<nsIAtom>(64); + + // Subtract one from the length because of the trailing null + for (uint32_t i = 0; i < ArrayLength(eventArray) - 1; ++i) { + sAtomEventTable->Put(eventArray[i].mAtom, eventArray[i]); + if (ShouldAddEventToStringEventTable(eventArray[i])) { + sStringEventTable->Put( + Substring(nsDependentAtomString(eventArray[i].mAtom), 2), + eventArray[i]); + } + } + + return true; +} + +void +nsContentUtils::InitializeTouchEventTable() +{ + static bool sEventTableInitialized = false; + if (!sEventTableInitialized && sAtomEventTable && sStringEventTable) { + sEventTableInitialized = true; + static const EventNameMapping touchEventArray[] = { +#define EVENT(name_, _message, _type, _class) +#define TOUCH_EVENT(name_, _message, _type, _class) \ + { nsGkAtoms::on##name_, _type, _message, _class }, +#include "mozilla/EventNameList.h" +#undef TOUCH_EVENT +#undef EVENT + { nullptr } + }; + // Subtract one from the length because of the trailing null + for (uint32_t i = 0; i < ArrayLength(touchEventArray) - 1; ++i) { + sAtomEventTable->Put(touchEventArray[i].mAtom, touchEventArray[i]); + sStringEventTable->Put(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* end = aAsciiBase64String.EndReading(); + nsString trimmedString; + if (!trimmedString.SetCapacity(aAsciiBase64String.Length(), fallible)) { + return NS_ERROR_DOM_INVALID_CHARACTER_ERR; + } + while (start < end) { + if (!nsContentUtils::IsHTMLWhitespace(*start)) { + trimmedString.Append(*start); + } + start++; + } + nsresult rv = Base64Decode(trimmedString, aBinaryData); + if (NS_FAILED(rv) && rv == NS_ERROR_INVALID_ARG) { + return NS_ERROR_DOM_INVALID_CHARACTER_ERR; + } + return rv; +} + +bool +nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput) +{ + NS_PRECONDITION(aInput, "aInput should not be null!"); + + nsAutoString autocomplete; + aInput->GetAutocomplete(autocomplete); + + if (autocomplete.IsEmpty()) { + nsCOMPtr<nsIDOMHTMLFormElement> form; + aInput->GetForm(getter_AddRefs(form)); + 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; + } + } + + return state; +} + +nsContentUtils::AutocompleteAttrState +nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr, + mozilla::dom::AutocompleteInfo& aInfo, + AutocompleteAttrState aCachedState) +{ + if (!aAttr || + aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) { + return aCachedState; + } + + return InternalSerializeAutocompleteAttribute(aAttr, aInfo); +} + +/** + * 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) +{ + // No sandbox attribute so we are done + if (!aAttrVal) { + return eAutocompleteAttrState_Invalid; + } + + uint32_t numTokens = aAttrVal->GetAtomCount(); + if (!numTokens) { + return eAutocompleteAttrState_Invalid; + } + + uint32_t index = numTokens - 1; + nsString tokenString = nsDependentAtomString(aAttrVal->AtomAt(index)); + AutocompleteCategory category; + nsAttrValue enumValue; + + nsAutoString str; + bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false); + if (result) { + // Off/Automatic/Normal categories. + if (enumValue.Equals(NS_LITERAL_STRING("off"), eIgnoreCase) || + enumValue.Equals(NS_LITERAL_STRING("on"), eIgnoreCase)) { + if (numTokens > 1) { + return eAutocompleteAttrState_Invalid; + } + enumValue.ToString(str); + ASCIIToLower(str); + aInfo.mFieldName.Assign(str); + return eAutocompleteAttrState_Valid; + } + + // Only allow on/off if experimental @autocomplete values aren't enabled. + if (!sIsExperimentalAutocompleteEnabled) { + return eAutocompleteAttrState_Invalid; + } + + // Normal category + if (numTokens > 2) { + return eAutocompleteAttrState_Invalid; + } + category = eAutocompleteCategory_NORMAL; + } else { // Check if the last token is of the contact category instead. + // Only allow on/off if experimental @autocomplete values aren't enabled. + if (!sIsExperimentalAutocompleteEnabled) { + return eAutocompleteAttrState_Invalid; + } + + result = enumValue.ParseEnumValue(tokenString, kAutocompleteContactFieldNameTable, false); + if (!result || numTokens > 3) { + return eAutocompleteAttrState_Invalid; + } + + category = eAutocompleteCategory_CONTACT; + } + + enumValue.ToString(str); + ASCIIToLower(str); + aInfo.mFieldName.Assign(str); + + // 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) { + 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; + } + + // Clear the fields as the autocomplete attribute is invalid. + aInfo.mAddressType.Truncate(); + aInfo.mContactType.Truncate(); + aInfo.mFieldName.Truncate(); + + return eAutocompleteAttrState_Invalid; +} + +// Parse an integer according to HTML spec +int32_t +nsContentUtils::ParseHTMLInteger(const nsAString& aValue, + ParseHTMLIntegerResultFlags *aResult) +{ + int result = eParseHTMLInteger_NoFlags; + + nsAString::const_iterator iter, end; + aValue.BeginReading(iter); + aValue.EndReading(end); + + while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { + result |= eParseHTMLInteger_NonStandard; + ++iter; + } + + if (iter == end) { + result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue; + *aResult = (ParseHTMLIntegerResultFlags)result; + return 0; + } + + int sign = 1; + if (*iter == char16_t('-')) { + sign = -1; + ++iter; + } else if (*iter == char16_t('+')) { + result |= eParseHTMLInteger_NonStandard; + ++iter; + } + + bool foundValue = false; + CheckedInt32 value = 0; + + // Check for leading zeros first. + uint64_t leadingZeros = 0; + while (iter != end) { + if (*iter != char16_t('0')) { + break; + } + + ++leadingZeros; + foundValue = true; + ++iter; + } + + while (iter != end) { + if (*iter >= char16_t('0') && *iter <= char16_t('9')) { + value = (value * 10) + (*iter - char16_t('0')) * sign; + ++iter; + if (!value.isValid()) { + result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow; + break; + } else { + foundValue = true; + } + } else if (*iter == char16_t('%')) { + ++iter; + result |= eParseHTMLInteger_IsPercent; + break; + } 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 != end) { + result |= eParseHTMLInteger_DidNotConsumeAllInput; + } + + *aResult = (ParseHTMLIntegerResultFlags)result; + return value.isValid() ? value.value() : 0; +} + +#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, nsIAtom *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) +{ + return aName.LowerCaseEqualsLiteral("javascript") || + aName.LowerCaseEqualsLiteral("livescript") || + aName.LowerCaseEqualsLiteral("mocha") || + aName.LowerCaseEqualsLiteral("javascript1.0") || + aName.LowerCaseEqualsLiteral("javascript1.1") || + aName.LowerCaseEqualsLiteral("javascript1.2") || + aName.LowerCaseEqualsLiteral("javascript1.3") || + aName.LowerCaseEqualsLiteral("javascript1.4") || + aName.LowerCaseEqualsLiteral("javascript1.5"); +} + +JSVersion +nsContentUtils::ParseJavascriptVersion(const nsAString& aVersionStr) +{ + if (aVersionStr.Length() != 3 || aVersionStr[0] != '1' || + aVersionStr[1] != '.') { + return JSVERSION_UNKNOWN; + } + + switch (aVersionStr[2]) { + case '0': /* fall through */ + case '1': /* fall through */ + case '2': /* fall through */ + case '3': /* fall through */ + case '4': /* fall through */ + case '5': return JSVERSION_DEFAULT; + case '6': return JSVERSION_1_6; + case '7': return JSVERSION_1_7; + case '8': return JSVERSION_1_8; + default: return JSVERSION_UNKNOWN; + } +} + +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(); +} + +nsresult +nsContentUtils::IsUserIdle(uint32_t aRequestedIdleTimeInMS, bool* aUserIsIdle) +{ + nsresult rv; + nsCOMPtr<nsIIdleService> idleService = + do_GetService("@mozilla.org/widget/idleservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t idleTimeInMS; + rv = idleService->GetIdleTime(&idleTimeInMS); + NS_ENSURE_SUCCESS(rv, rv); + + *aUserIsIdle = idleTimeInMS >= aRequestedIdleTimeInMS; + return NS_OK; +} + +/** + * Access a cached parser service. Don't addref. We need only one + * reference to it and this class has that one. + */ +/* static */ +nsIParserService* +nsContentUtils::GetParserService() +{ + // XXX: This isn't accessed from several threads, is it? + if (!sParserService) { + // Lock, recheck sCachedParserService and aquire if this should be + // safe for multiple threads. + nsresult rv = CallGetService(kParserServiceCID, &sParserService); + if (NS_FAILED(rv)) { + sParserService = nullptr; + } + } + + return sParserService; +} + +/** +* 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.Append(NS_LITERAL_STRING(" ")); \ + } \ + aString.Append(nsDependentAtomString(nsGkAtoms::atom)); \ + } +#include "IframeSandboxKeywordList.h" +#undef SANDBOX_KEYWORD +} + +nsIBidiKeyboard* +nsContentUtils::GetBidiKeyboard() +{ + if (!sBidiKeyboard) { + nsresult rv = CallGetService("@mozilla.org/widget/bidikeyboard;1", &sBidiKeyboard); + if (NS_FAILED(rv)) { + sBidiKeyboard = nullptr; + } + } + return sBidiKeyboard; +} + +template <class OutputIterator> +struct NormalizeNewlinesCharTraits { + public: + typedef typename OutputIterator::value_type value_type; + + public: + explicit NormalizeNewlinesCharTraits(OutputIterator& aIterator) : mIterator(aIterator) { } + void writechar(typename OutputIterator::value_type aChar) { + *mIterator++ = aChar; + } + + private: + OutputIterator mIterator; +}; + +template <class CharT> +struct NormalizeNewlinesCharTraits<CharT*> { + public: + typedef CharT value_type; + + public: + explicit NormalizeNewlinesCharTraits(CharT* aCharPtr) : mCharPtr(aCharPtr) { } + void writechar(CharT aChar) { + *mCharPtr++ = aChar; + } + + private: + CharT* mCharPtr; +}; + +template <class OutputIterator> +class CopyNormalizeNewlines +{ + public: + typedef typename OutputIterator::value_type value_type; + + public: + explicit CopyNormalizeNewlines(OutputIterator* aDestination, + bool aLastCharCR = false) : + mLastCharCR(aLastCharCR), + mDestination(aDestination), + mWritten(0) + { } + + uint32_t GetCharsWritten() { + return mWritten; + } + + bool IsLastCharCR() { + return mLastCharCR; + } + + void write(const typename OutputIterator::value_type* aSource, uint32_t aSourceLength) { + + const typename OutputIterator::value_type* done_writing = aSource + aSourceLength; + + // If the last source buffer ended with a CR... + if (mLastCharCR) { + // ..and if the next one is a LF, then skip it since + // we've already written out a newline + if (aSourceLength && (*aSource == value_type('\n'))) { + ++aSource; + } + mLastCharCR = false; + } + + uint32_t num_written = 0; + while ( aSource < done_writing ) { + if (*aSource == value_type('\r')) { + mDestination->writechar('\n'); + ++aSource; + // If we've reached the end of the buffer, record + // that we wrote out a CR + if (aSource == done_writing) { + mLastCharCR = true; + } + // If the next character is a LF, skip it + else if (*aSource == value_type('\n')) { + ++aSource; + } + } + else { + mDestination->writechar(*aSource++); + } + ++num_written; + } + + mWritten += num_written; + } + + private: + bool mLastCharCR; + OutputIterator* mDestination; + uint32_t mWritten; +}; + +// static +uint32_t +nsContentUtils::CopyNewlineNormalizedUnicodeTo(const nsAString& aSource, + uint32_t aSrcOffset, + char16_t* aDest, + uint32_t aLength, + bool& aLastCharCR) +{ + typedef NormalizeNewlinesCharTraits<char16_t*> sink_traits; + + sink_traits dest_traits(aDest); + CopyNormalizeNewlines<sink_traits> normalizer(&dest_traits,aLastCharCR); + nsReadingIterator<char16_t> fromBegin, fromEnd; + copy_string(aSource.BeginReading(fromBegin).advance( int32_t(aSrcOffset) ), + aSource.BeginReading(fromEnd).advance( int32_t(aSrcOffset+aLength) ), + normalizer); + aLastCharCR = normalizer.IsLastCharCR(); + return normalizer.GetCharsWritten(); +} + +// static +uint32_t +nsContentUtils::CopyNewlineNormalizedUnicodeTo(nsReadingIterator<char16_t>& aSrcStart, const nsReadingIterator<char16_t>& aSrcEnd, nsAString& aDest) +{ + typedef nsWritingIterator<char16_t> WritingIterator; + typedef NormalizeNewlinesCharTraits<WritingIterator> sink_traits; + + WritingIterator iter; + aDest.BeginWriting(iter); + sink_traits dest_traits(iter); + CopyNormalizeNewlines<sink_traits> normalizer(&dest_traits); + copy_string(aSrcStart, aSrcEnd, normalizer); + return normalizer.GetCharsWritten(); +} + +/** + * This is used to determine whether a character is in one of the classes + * which CSS says should be part of the first-letter. Currently, that is + * all punctuation classes (P*). Note that this is a change from CSS2 + * which excluded Pc and Pd. + * + * https://www.w3.org/TR/css-pseudo-4/#first-letter-pseudo + * "Punctuation (i.e, characters that belong to the Punctuation (P*) Unicode + * general category [UAX44]) [...]" + */ + +// static +bool +nsContentUtils::IsFirstLetterPunctuation(uint32_t aChar) +{ + switch (mozilla::unicode::GetGeneralCategory(aChar)) { + case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ + case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */ + case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ + case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ + case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ + case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ + case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */ + return true; + default: + return false; + } +} + +// static +bool +nsContentUtils::IsFirstLetterPunctuationAt(const nsTextFragment* aFrag, uint32_t aOffset) +{ + char16_t h = aFrag->CharAt(aOffset); + if (!IS_SURROGATE(h)) { + return IsFirstLetterPunctuation(h); + } + if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) { + char16_t l = aFrag->CharAt(aOffset + 1); + if (NS_IS_LOW_SURROGATE(l)) { + return IsFirstLetterPunctuation(SURROGATE_TO_UCS4(h, l)); + } + } + return false; +} + +// static +bool nsContentUtils::IsAlphanumeric(uint32_t aChar) +{ + nsIUGenCategory::nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar); + + return (cat == nsIUGenCategory::kLetter || cat == nsIUGenCategory::kNumber); +} + +// static +bool nsContentUtils::IsAlphanumericAt(const nsTextFragment* aFrag, uint32_t aOffset) +{ + char16_t h = aFrag->CharAt(aOffset); + if (!IS_SURROGATE(h)) { + return IsAlphanumeric(h); + } + if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) { + char16_t l = aFrag->CharAt(aOffset + 1); + if (NS_IS_LOW_SURROGATE(l)) { + return IsAlphanumeric(SURROGATE_TO_UCS4(h, l)); + } + } + return false; +} + +/* 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::IsHTMLBlock(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::multicol, // XXX get rid of this one? + nsGkAtoms::nav, + nsGkAtoms::ol, + nsGkAtoms::p, + nsGkAtoms::pre, + nsGkAtoms::section, + nsGkAtoms::table, + nsGkAtoms::ul, + nsGkAtoms::xmp); +} + +/* static */ +bool +nsContentUtils::ParseIntMarginValue(const nsAString& aString, nsIntMargin& result) +{ + nsAutoString marginStr(aString); + marginStr.CompressWhitespace(true, true); + if (marginStr.IsEmpty()) { + return false; + } + + int32_t start = 0, end = 0; + for (int count = 0; count < 4; count++) { + if ((uint32_t)end >= marginStr.Length()) + return false; + + // top, right, bottom, left + if (count < 3) + end = Substring(marginStr, start).FindChar(','); + else + end = Substring(marginStr, start).Length(); + + if (end <= 0) + return false; + + nsresult ec; + int32_t val = nsString(Substring(marginStr, start, end)).ToInteger(&ec); + if (NS_FAILED(ec)) + return false; + + switch(count) { + case 0: + result.top = val; + break; + case 1: + result.right = val; + break; + case 2: + result.bottom = val; + break; + case 3: + result.left = val; + break; + } + start += end + 1; + } + return true; +} + +// static +int32_t +nsContentUtils::ParseLegacyFontSize(const nsAString& aValue) +{ + nsAString::const_iterator iter, end; + aValue.BeginReading(iter); + aValue.EndReading(end); + + while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { + ++iter; + } + + if (iter == end) { + return 0; + } + + bool relative = false; + bool negate = false; + if (*iter == char16_t('-')) { + relative = true; + negate = true; + ++iter; + } else if (*iter == char16_t('+')) { + relative = true; + ++iter; + } + + if (iter == end || *iter < char16_t('0') || *iter > char16_t('9')) { + return 0; + } + + // We don't have to worry about overflow, since we can bail out as soon as + // we're bigger than 7. + int32_t value = 0; + while (iter != end && *iter >= char16_t('0') && *iter <= char16_t('9')) { + value = 10*value + (*iter - char16_t('0')); + if (value >= 7) { + break; + } + ++iter; + } + + if (relative) { + if (negate) { + value = 3 - value; + } else { + value = 3 + value; + } + } + + return clamped(value, 1, 7); +} + +/* static */ +bool +nsContentUtils::IsControlledByServiceWorker(nsIDocument* aDocument) +{ + if (nsContentUtils::IsInPrivateBrowsing(aDocument)) { + return false; + } + + RefPtr<workers::ServiceWorkerManager> swm = + workers::ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + ErrorResult rv; + bool controlled = swm->IsControlled(aDocument, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + return controlled; +} + +/* static */ +void +nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aDocument); + *aURI = nullptr; + + if (IsControlledByServiceWorker(aDocument)) { + return; + } + + Element* docElement = aDocument->GetRootElement(); + if (!docElement) { + return; + } + + nsAutoString manifestSpec; + docElement->GetAttr(kNameSpaceID_None, 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) +{ + nsCOMPtr<nsIOfflineCacheUpdateService> updateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); + if (!updateService) { + return false; + } + + bool allowed; + nsresult rv = + updateService->OfflineAppAllowedForURI(aURI, + Preferences::GetRootBranch(), + &allowed); + return NS_SUCCEEDED(rv) && allowed; +} + +/* static */ +bool +nsContentUtils::OfflineAppAllowed(nsIPrincipal *aPrincipal) +{ + nsCOMPtr<nsIOfflineCacheUpdateService> updateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); + if (!updateService) { + return false; + } + + bool allowed; + nsresult rv = updateService->OfflineAppAllowed(aPrincipal, + Preferences::GetRootBranch(), + &allowed); + return NS_SUCCEEDED(rv) && allowed; +} + +bool +nsContentUtils::MaybeAllowOfflineAppByDefault(nsIPrincipal *aPrincipal) +{ + if (!Preferences::GetRootBranch()) + return false; + + nsresult rv; + + bool allowedByDefault; + rv = Preferences::GetRootBranch()->GetBoolPref( + "offline-apps.allow_by_default", &allowedByDefault); + if (NS_FAILED(rv)) + return false; + + if (!allowedByDefault) + return false; + + nsCOMPtr<nsIOfflineCacheUpdateService> updateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); + if (!updateService) { + return false; + } + + rv = updateService->AllowOfflineApp(aPrincipal); + return NS_SUCCEEDED(rv); +} + +// static +void +nsContentUtils::Shutdown() +{ + sInitialized = false; + + NS_IF_RELEASE(sContentPolicyService); + sTriedToGetContentPolicy = false; + uint32_t i; + for (i = 0; i < PropertiesFile_COUNT; ++i) + NS_IF_RELEASE(sStringBundles[i]); + + NS_IF_RELEASE(sStringBundleService); + NS_IF_RELEASE(sConsoleService); + sXPConnect = nullptr; + NS_IF_RELEASE(sSecurityManager); + NS_IF_RELEASE(sSystemPrincipal); + NS_IF_RELEASE(sNullSubjectPrincipal); + NS_IF_RELEASE(sParserService); + NS_IF_RELEASE(sIOService); + NS_IF_RELEASE(sUUIDGenerator); + NS_IF_RELEASE(sLineBreaker); + NS_IF_RELEASE(sWordBreaker); + NS_IF_RELEASE(sBidiKeyboard); + + 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; + } + } + + NS_ASSERTION(!sBlockedScriptRunners || + sBlockedScriptRunners->Length() == 0, + "How'd this happen?"); + delete sBlockedScriptRunners; + sBlockedScriptRunners = nullptr; + + delete sShiftText; + sShiftText = nullptr; + delete sControlText; + sControlText = nullptr; + delete sMetaText; + sMetaText = nullptr; + delete sOSText; + sOSText = nullptr; + delete sAltText; + sAltText = nullptr; + delete sModifierSeparator; + sModifierSeparator = nullptr; + + NS_IF_RELEASE(sSameOriginChecker); +} + +/** + * Checks whether two nodes come from the same origin. aTrustedNode is + * considered 'safe' in that a user can operate on it and that it isn't + * a js-object that implements nsIDOMNode. + * Never call this function with the first node provided by script, it + * must always be known to be a 'real' node! + */ +// static +nsresult +nsContentUtils::CheckSameOrigin(const nsINode *aTrustedNode, + nsIDOMNode *aUnTrustedNode) +{ + MOZ_ASSERT(aTrustedNode); + + // Make sure it's a real node. + nsCOMPtr<nsINode> unTrustedNode = do_QueryInterface(aUnTrustedNode); + NS_ENSURE_TRUE(unTrustedNode, NS_ERROR_UNEXPECTED); + return CheckSameOrigin(aTrustedNode, unTrustedNode); +} + +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(nsIDOMNode *aNode) +{ + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); + NS_ENSURE_TRUE(node, false); + return CanCallerAccess(node); +} + +// static +bool +nsContentUtils::CanCallerAccess(nsINode* aNode) +{ + return CanCallerAccess(SubjectPrincipal(), 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::InProlog(nsINode *aNode) +{ + NS_PRECONDITION(aNode, "missing node to nsContentUtils::InProlog"); + + nsINode* parent = aNode->GetParentNode(); + if (!parent || !parent->IsNodeOfType(nsINode::eDOCUMENT)) { + return false; + } + + nsIDocument* doc = static_cast<nsIDocument*>(parent); + nsIContent* root = doc->GetRootElement(); + + return !root || doc->IndexOf(aNode) < doc->IndexOf(root); +} + +nsIDocument* +nsContentUtils::GetDocumentFromCaller() +{ + AutoJSContext cx; + + nsCOMPtr<nsPIDOMWindowInner> win = + do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(cx))); + if (!win) { + return nullptr; + } + + return win->GetExtantDoc(); +} + +bool +nsContentUtils::IsCallerChrome() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (SubjectPrincipal() == sSystemPrincipal) { + return true; + } + + // If the check failed, look for UniversalXPConnect on the cx compartment. + return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext()); +} + +bool +nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell) +{ + if (!aDocShell) { + return false; + } + bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument()); + return !isChrome && sPrivacyResistFingerprinting; +} + +namespace mozilla { +namespace dom { +namespace workers { +extern bool IsCurrentThreadRunningChromeWorker(); +extern JSContext* GetCurrentThreadJSContext(); +} // namespace workers +} // namespace dom +} // namespace mozilla + +bool +nsContentUtils::ThreadsafeIsCallerChrome() +{ + return NS_IsMainThread() ? + IsCallerChrome() : + mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); +} + +bool +nsContentUtils::IsCallerContentXBL() +{ + JSContext *cx = GetCurrentJSContext(); + if (!cx) + return false; + + JSCompartment *c = js::GetContextCompartment(cx); + + // For remote XUL, we run XBL in the XUL scope. Given that we care about + // compat and not security for remote XUL, just always claim to be XBL. + if (!xpc::AllowContentXBLScope(c)) { + MOZ_ASSERT(nsContentUtils::AllowXULXBLForPrincipal(xpc::GetCompartmentPrincipal(c))); + return true; + } + + return xpc::IsContentXBLScope(c); +} + +// static +bool +nsContentUtils::LookupBindingMember(JSContext* aCx, nsIContent *aContent, + JS::Handle<jsid> aId, + JS::MutableHandle<JS::PropertyDescriptor> aDesc) +{ + nsXBLBinding* binding = aContent->GetXBLBinding(); + if (!binding) + return true; + return binding->LookupMember(aCx, aId, aDesc); +} + +// static +nsINode* +nsContentUtils::GetCrossDocParentNode(nsINode* aChild) +{ + NS_PRECONDITION(aChild, "The child is null!"); + + nsINode* parent = aChild->GetParentNode(); + if (parent && parent->IsContent() && aChild->IsContent()) { + parent = aChild->AsContent()->GetFlattenedTreeParent(); + } + + if (parent || !aChild->IsNodeOfType(nsINode::eDOCUMENT)) + return parent; + + nsIDocument* doc = static_cast<nsIDocument*>(aChild); + nsIDocument* parentDoc = doc->GetParentDocument(); + return parentDoc ? parentDoc->FindContentForSubDocument(doc) : nullptr; +} + +// static +bool +nsContentUtils::ContentIsDescendantOf(const nsINode* aPossibleDescendant, + const nsINode* aPossibleAncestor) +{ + NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); + NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); + + do { + if (aPossibleDescendant == aPossibleAncestor) + return true; + aPossibleDescendant = aPossibleDescendant->GetParentNode(); + } while (aPossibleDescendant); + + return false; +} + +bool +nsContentUtils::ContentIsHostIncludingDescendantOf( + const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) +{ + NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); + NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); + + do { + if (aPossibleDescendant == aPossibleAncestor) + return true; + if (aPossibleDescendant->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { + aPossibleDescendant = + static_cast<const DocumentFragment*>(aPossibleDescendant)->GetHost(); + } else { + aPossibleDescendant = aPossibleDescendant->GetParentNode(); + } + } while (aPossibleDescendant); + + return false; +} + +// static +bool +nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant, + nsINode* aPossibleAncestor) +{ + NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); + NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); + + do { + if (aPossibleDescendant == aPossibleAncestor) + return true; + + aPossibleDescendant = GetCrossDocParentNode(aPossibleDescendant); + } while (aPossibleDescendant); + + return false; +} + + +// static +nsresult +nsContentUtils::GetAncestors(nsINode* aNode, + nsTArray<nsINode*>& aArray) +{ + while (aNode) { + aArray.AppendElement(aNode); + aNode = aNode->GetParentNode(); + } + return NS_OK; +} + +// static +nsresult +nsContentUtils::GetAncestorsAndOffsets(nsIDOMNode* aNode, + int32_t aOffset, + nsTArray<nsIContent*>* aAncestorNodes, + nsTArray<int32_t>* aAncestorOffsets) +{ + NS_ENSURE_ARG_POINTER(aNode); + + nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); + + if (!content) { + return NS_ERROR_FAILURE; + } + + 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.get()); + aAncestorOffsets->AppendElement(aOffset); + + // insert all the ancestors + nsIContent* child = content; + nsIContent* parent = child->GetParent(); + while (parent) { + aAncestorNodes->AppendElement(parent); + aAncestorOffsets->AppendElement(parent->IndexOf(child)); + child = parent; + parent = parent->GetParent(); + } + + return NS_OK; +} + +// static +nsresult +nsContentUtils::GetCommonAncestor(nsIDOMNode *aNode, + nsIDOMNode *aOther, + nsIDOMNode** aCommonAncestor) +{ + *aCommonAncestor = nullptr; + + nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode); + nsCOMPtr<nsINode> node2 = do_QueryInterface(aOther); + + NS_ENSURE_TRUE(node1 && node2, NS_ERROR_UNEXPECTED); + + nsINode* common = GetCommonAncestor(node1, node2); + NS_ENSURE_TRUE(common, NS_ERROR_NOT_AVAILABLE); + + return CallQueryInterface(common, aCommonAncestor); +} + +// static +nsINode* +nsContentUtils::GetCommonAncestor(nsINode* aNode1, + nsINode* aNode2) +{ + if (aNode1 == aNode2) { + return aNode1; + } + + // Build the chain of parents + AutoTArray<nsINode*, 30> parents1, parents2; + do { + parents1.AppendElement(aNode1); + aNode1 = aNode1->GetParentNode(); + } while (aNode1); + do { + parents2.AppendElement(aNode2); + aNode2 = aNode2->GetParentNode(); + } while (aNode2); + + // Find where the parent chain differs + uint32_t pos1 = parents1.Length(); + uint32_t pos2 = parents2.Length(); + nsINode* parent = nullptr; + uint32_t len; + for (len = std::min(pos1, pos2); len > 0; --len) { + nsINode* child1 = parents1.ElementAt(--pos1); + nsINode* child2 = parents2.ElementAt(--pos2); + if (child1 != child2) { + break; + } + parent = child1; + } + + return parent; +} + +/* static */ +bool +nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2) +{ + return (aNode2->CompareDocumentPosition(*aNode1) & + (nsIDOMNode::DOCUMENT_POSITION_PRECEDING | + nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED)) == + nsIDOMNode::DOCUMENT_POSITION_PRECEDING; +} + +/* static */ +int32_t +nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1, + nsINode* aParent2, int32_t aOffset2, + bool* aDisconnected) +{ + if (aParent1 == aParent2) { + return aOffset1 < aOffset2 ? -1 : + aOffset1 > aOffset2 ? 1 : + 0; + } + + AutoTArray<nsINode*, 32> parents1, parents2; + nsINode* node1 = aParent1; + nsINode* node2 = aParent2; + do { + parents1.AppendElement(node1); + node1 = node1->GetParentNode(); + } while (node1); + do { + parents2.AppendElement(node2); + node2 = node2->GetParentNode(); + } while (node2); + + uint32_t pos1 = parents1.Length() - 1; + uint32_t pos2 = parents2.Length() - 1; + + bool disconnected = parents1.ElementAt(pos1) != parents2.ElementAt(pos2); + if (aDisconnected) { + *aDisconnected = disconnected; + } + if (disconnected) { + NS_ASSERTION(aDisconnected, "unexpected disconnected nodes"); + return 1; + } + + // Find where the parent chains differ + nsINode* parent = parents1.ElementAt(pos1); + uint32_t len; + for (len = std::min(pos1, pos2); len > 0; --len) { + nsINode* child1 = parents1.ElementAt(--pos1); + nsINode* child2 = parents2.ElementAt(--pos2); + if (child1 != child2) { + return parent->IndexOf(child1) < parent->IndexOf(child2) ? -1 : 1; + } + parent = child1; + } + + + // The parent chains never differed, so one of the nodes is an ancestor of + // the other + + NS_ASSERTION(!pos1 || !pos2, + "should have run out of parent chain for one of the nodes"); + + if (!pos1) { + nsINode* child2 = parents2.ElementAt(--pos2); + return aOffset1 <= parent->IndexOf(child2) ? -1 : 1; + } + + nsINode* child1 = parents1.ElementAt(--pos1); + return parent->IndexOf(child1) < aOffset2 ? -1 : 1; +} + +/* static */ +int32_t +nsContentUtils::ComparePoints(nsIDOMNode* aParent1, int32_t aOffset1, + nsIDOMNode* aParent2, int32_t aOffset2, + bool* aDisconnected) +{ + nsCOMPtr<nsINode> parent1 = do_QueryInterface(aParent1); + nsCOMPtr<nsINode> parent2 = do_QueryInterface(aParent2); + NS_ENSURE_TRUE(parent1 && parent2, -1); + return ComparePoints(parent1, aOffset1, parent2, aOffset2); +} + +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.Append(nsPrintfCString("%d", aInt)); +} + +static inline bool IsAutocompleteOff(const nsIContent* aElement) +{ + return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocomplete, + NS_LITERAL_STRING("off"), eIgnoreCase); +} + +/*static*/ nsresult +nsContentUtils::GenerateStateKey(nsIContent* aContent, + const nsIDocument* aDocument, + nsACString& aKey) +{ + aKey.Truncate(); + + uint32_t partID = aDocument ? aDocument->GetPartID() : 0; + + // We must have content if we're not using a special state id + NS_ENSURE_TRUE(aContent, NS_ERROR_FAILURE); + + // Don't capture state for anonymous content + if (aContent->IsInAnonymousSubtree()) { + return NS_OK; + } + + if (IsAutocompleteOff(aContent)) { + return NS_OK; + } + + nsCOMPtr<nsIHTMLDocument> htmlDocument = + do_QueryInterface(aContent->GetUncomposedDoc()); + + KeyAppendInt(partID, aKey); // first append a partID + bool generatedUniqueKey = false; + + if (htmlDocument) { + // Flush our content model so it'll be up to date + // If this becomes unnecessary and the following line is removed, + // please also remove the corresponding flush operation from + // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.) + aContent->GetUncomposedDoc()->FlushPendingNotifications(Flush_Content); + + nsContentList *htmlForms = htmlDocument->GetForms(); + nsContentList *htmlFormControls = htmlDocument->GetFormControls(); + + NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY); + + // 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. + // + // If the control has a form, the format of the key is: + // f>type>IndOfFormInDoc>IndOfControlInForm>FormName>name + // else: + // d>type>IndOfControlInDoc>name + // + // XXX We don't need to use index if name is there + // XXXbz We don't? Why not? I don't follow. + // + nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent)); + if (control && htmlFormControls && htmlForms) { + + // Append the control type + KeyAppendInt(control->GetType(), aKey); + + // If in a form, add form name / index of form / index in form + int32_t index = -1; + Element *formElement = control->GetFormElement(); + if (formElement) { + if (IsAutocompleteOff(formElement)) { + aKey.Truncate(); + return NS_OK; + } + + KeyAppendString(NS_LITERAL_CSTRING("f"), aKey); + + // Append the index of the form in the document + 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 = htmlDocument->GetNumFormsSynchronous() - 1; + } + if (index > -1) { + KeyAppendInt(index, aKey); + + // Append the index of the control in the form + nsCOMPtr<nsIForm> form(do_QueryInterface(formElement)); + index = form->IndexOfControl(control); + + if (index > -1) { + KeyAppendInt(index, aKey); + generatedUniqueKey = true; + } + } + + // Append the form name + nsAutoString formName; + formElement->GetAttr(kNameSpaceID_None, nsGkAtoms::name, formName); + KeyAppendString(formName, aKey); + + } else { + + KeyAppendString(NS_LITERAL_CSTRING("d"), aKey); + + // If not in a form, add index of control in document + // Less desirable than indexing by form info. + + // Hash by index of control in doc (we are not in a form) + // These are important as they are unique, and type/name may not be. + + // We have to flush sink notifications at this point to make + // sure that htmlFormControls is up to date. + index = htmlFormControls->IndexOf(aContent, true); + if (index > -1) { + KeyAppendInt(index, aKey); + generatedUniqueKey = true; + } + } + + // Append the control name + nsAutoString name; + aContent->GetAttr(kNameSpaceID_None, 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(NS_LITERAL_CSTRING("o"), 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->IndexOf(content), aKey); + content = parent; + parent = content->GetParentNode(); + } + } + + return NS_OK; +} + +// 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"); + } + + JSCompartment *compartment = js::GetContextCompartment(cx); + + // When an AutoJSAPI is instantiated, we are in a null compartment until the + // first JSAutoCompartment, 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 + // GetSubjectPrincipal 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 compartment creation against using this + // principal on an actual global. + if (!compartment) { + return sNullSubjectPrincipal; + } + + JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +// static +nsIPrincipal* +nsContentUtils::ObjectPrincipal(JSObject* aObj) +{ + MOZ_ASSERT(NS_IsMainThread()); + +#ifdef DEBUG + JS::AssertObjectBelongsToCurrentThread(aObj); +#endif + + // This is duplicated from nsScriptSecurityManager. We don't call through there + // because the API unnecessarily requires a JSContext for historical reasons. + JSCompartment *compartment = js::GetObjectCompartment(aObj); + JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +// static +nsresult +nsContentUtils::NewURIWithDocumentCharset(nsIURI** aResult, + const nsAString& aSpec, + nsIDocument* aDocument, + nsIURI* aBaseURI) +{ + return NS_NewURI(aResult, aSpec, + aDocument ? aDocument->GetDocumentCharacterSet().get() : nullptr, + aBaseURI, sIOService); +} + +// static +bool +nsContentUtils::IsCustomElementName(nsIAtom* 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 (NS_IS_HIGH_SURROGATE(name[i]) && i + 1 < len && + NS_IS_LOW_SURROGATE(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++; + } + } + + 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; + } + + // MOZ_EXPAT_EMPTY_QNAME || MOZ_EXPAT_INVALID_CHARACTER + if (result == (1 << 0) || result == (1 << 1)) { + return NS_ERROR_DOM_INVALID_CHARACTER_ERR; + } + + return NS_ERROR_DOM_NAMESPACE_ERR; +} + +//static +nsresult +nsContentUtils::SplitQName(const nsIContent* aNamespaceResolver, + const nsAFlatString& aQName, + int32_t *aNamespace, nsIAtom **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 = NameSpaceManager()->GetNameSpaceID(nameSpace, + nsContentUtils::IsChromeDoc(aNamespaceResolver->OwnerDoc())); + if (*aNamespace == kNameSpaceID_Unknown) + return NS_ERROR_FAILURE; + + *aLocalName = NS_Atomize(Substring(colon + 1, end)).take(); + } + else { + *aNamespace = kNameSpaceID_None; + *aLocalName = NS_Atomize(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 nsAFlatString& qName = PromiseFlatString(aQualifiedName); + const char16_t* colon; + nsresult rv = nsContentUtils::CheckQName(qName, true, &colon); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t nsID; + sNameSpaceManager->RegisterNameSpace(aNamespaceURI, nsID); + if (colon) { + const char16_t* end; + qName.EndReading(end); + + nsCOMPtr<nsIAtom> prefix = NS_Atomize(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, nsIAtom **aPrefix, + nsIAtom **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) { + if (sNameSpaceManager) { + sNameSpaceManager->RegisterNameSpace(nsDependentSubstring(aExpatName, + uriEnd), + *aNameSpaceID); + } + else { + *aNameSpaceID = kNameSpaceID_Unknown; + } + + nameStart = (uriEnd + 1); + if (nameEnd) { + const char16_t *prefixStart = nameEnd + 1; + *aPrefix = NS_Atomize(Substring(prefixStart, pos)).take(); + } + else { + nameEnd = pos; + *aPrefix = nullptr; + } + } + else { + *aNameSpaceID = kNameSpaceID_None; + nameStart = aExpatName; + nameEnd = pos; + *aPrefix = nullptr; + } + *aLocalName = NS_Atomize(Substring(nameStart, nameEnd)).take(); +} + +// static +nsPresContext* +nsContentUtils::GetContextForContent(const nsIContent* aContent) +{ + nsIDocument* doc = aContent->GetComposedDoc(); + if (doc) { + nsIPresShell *presShell = doc->GetShell(); + if (presShell) { + return presShell->GetPresContext(); + } + } + return nullptr; +} + +// static +bool +nsContentUtils::CanLoadImage(nsIURI* aURI, nsISupports* aContext, + nsIDocument* aLoadingDocument, + nsIPrincipal* aLoadingPrincipal, + int16_t* aImageBlockingStatus, + uint32_t aContentType) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aLoadingDocument, "Must have a document"); + NS_PRECONDITION(aLoadingPrincipal, "Must have a loading principal"); + + nsresult rv; + + uint32_t appType = nsIDocShell::APP_TYPE_UNKNOWN; + + { + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aLoadingDocument->GetDocShell(); + if (docShellTreeItem) { + nsCOMPtr<nsIDocShellTreeItem> root; + docShellTreeItem->GetRootTreeItem(getter_AddRefs(root)); + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(root)); + + if (!docShell || NS_FAILED(docShell->GetAppType(&appType))) { + appType = nsIDocShell::APP_TYPE_UNKNOWN; + } + } + } + + if (appType != nsIDocShell::APP_TYPE_EDITOR) { + // Editor apps get special treatment here, editors can load images + // from anywhere. This allows editor to insert images from file:// + // into documents that are being edited. + rv = sSecurityManager-> + CheckLoadURIWithPrincipal(aLoadingPrincipal, aURI, + nsIScriptSecurityManager::ALLOW_CHROME); + if (NS_FAILED(rv)) { + if (aImageBlockingStatus) { + // Reject the request itself, not all requests to the relevant + // server... + *aImageBlockingStatus = nsIContentPolicy::REJECT_REQUEST; + } + return false; + } + } + + int16_t decision = nsIContentPolicy::ACCEPT; + + rv = NS_CheckContentLoadPolicy(aContentType, + aURI, + aLoadingPrincipal, + aContext, + EmptyCString(), //mime guess + nullptr, //extra + &decision, + GetContentPolicy(), + sSecurityManager); + + if (aImageBlockingStatus) { + *aImageBlockingStatus = + NS_FAILED(rv) ? nsIContentPolicy::REJECT_REQUEST : decision; + } + return NS_FAILED(rv) ? false : NS_CP_ACCEPTED(decision); +} + +// static +mozilla::PrincipalOriginAttributes +nsContentUtils::GetOriginAttributes(nsIDocument* aDocument) +{ + if (!aDocument) { + return mozilla::PrincipalOriginAttributes(); + } + + nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup(); + if (loadGroup) { + return GetOriginAttributes(loadGroup); + } + + mozilla::PrincipalOriginAttributes attrs; + mozilla::NeckoOriginAttributes nattrs; + nsCOMPtr<nsIChannel> channel = aDocument->GetChannel(); + if (channel && NS_GetOriginAttributes(channel, nattrs)) { + attrs.InheritFromNecko(nattrs); + } + return attrs; +} + +// static +mozilla::PrincipalOriginAttributes +nsContentUtils::GetOriginAttributes(nsILoadGroup* aLoadGroup) +{ + if (!aLoadGroup) { + return mozilla::PrincipalOriginAttributes(); + } + mozilla::PrincipalOriginAttributes attrs; + mozilla::DocShellOriginAttributes dsattrs; + nsCOMPtr<nsIInterfaceRequestor> callbacks; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); + if (loadContext && loadContext->GetOriginAttributes(dsattrs)) { + attrs.InheritFromDocShellToDoc(dsattrs, nullptr); + } + } + return attrs; +} + +// static +bool +nsContentUtils::IsInPrivateBrowsing(nsIDocument* aDoc) +{ + if (!aDoc) { + return false; + } + + nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup(); + if (loadGroup) { + return IsInPrivateBrowsing(loadGroup); + } + + nsCOMPtr<nsIChannel> channel = aDoc->GetChannel(); + return channel && NS_UsePrivateBrowsing(channel); +} + +// static +bool +nsContentUtils::IsInPrivateBrowsing(nsILoadGroup* aLoadGroup) +{ + if (!aLoadGroup) { + return false; + } + bool isPrivate = false; + nsCOMPtr<nsIInterfaceRequestor> callbacks; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); + isPrivate = loadContext && loadContext->UsePrivateBrowsing(); + } + return isPrivate; +} + +bool +nsContentUtils::DocumentInactiveForImageLoads(nsIDocument* aDocument) +{ + if (aDocument && !IsChromeDoc(aDocument) && !aDocument->IsResourceDoc()) { + nsCOMPtr<nsPIDOMWindowInner> win = + do_QueryInterface(aDocument->GetScopeObject()); + return !win || !win->GetDocShell(); + } + return false; +} + +imgLoader* +nsContentUtils::GetImgLoaderForDocument(nsIDocument* aDoc) +{ + NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aDoc), nullptr); + + if (!aDoc) { + return imgLoader::NormalLoader(); + } + bool isPrivate = IsInPrivateBrowsing(aDoc); + return isPrivate ? imgLoader::PrivateBrowsingLoader() + : imgLoader::NormalLoader(); +} + +// static +imgLoader* +nsContentUtils::GetImgLoaderForChannel(nsIChannel* aChannel, + nsIDocument* aContext) +{ + NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aContext), nullptr); + + if (!aChannel) { + return imgLoader::NormalLoader(); + } + nsCOMPtr<nsILoadContext> context; + NS_QueryNotificationCallbacks(aChannel, context); + return context && context->UsePrivateBrowsing() ? + imgLoader::PrivateBrowsingLoader() : + imgLoader::NormalLoader(); +} + +// static +bool +nsContentUtils::IsImageInCache(nsIURI* aURI, nsIDocument* aDocument) +{ + imgILoader* loader = GetImgLoaderForDocument(aDocument); + nsCOMPtr<imgICache> cache = do_QueryInterface(loader); + + // If something unexpected happened we return false, otherwise if props + // is set, the image is cached and we return true + nsCOMPtr<nsIProperties> props; + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument); + nsresult rv = cache->FindEntryProperties(aURI, domDoc, getter_AddRefs(props)); + return (NS_SUCCEEDED(rv) && props); +} + +// static +nsresult +nsContentUtils::LoadImage(nsIURI* aURI, nsINode* aContext, + nsIDocument* aLoadingDocument, + nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer, + net::ReferrerPolicy aReferrerPolicy, + imgINotificationObserver* aObserver, int32_t aLoadFlags, + const nsAString& initiatorType, + imgRequestProxy** aRequest, + uint32_t aContentPolicyType) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aContext, "Must have a context"); + NS_PRECONDITION(aLoadingDocument, "Must have a document"); + NS_PRECONDITION(aLoadingPrincipal, "Must have a principal"); + NS_PRECONDITION(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 || IsFontTableURI(documentURI), + "Could not get loadgroup; onload may fire too early"); + + // Make the URI immutable so people won't change it under us + NS_TryToSetImmutable(aURI); + + // 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 */ + aReferrer, /* referrer */ + aReferrerPolicy, /* referrer policy */ + aLoadingPrincipal, /* loading principal */ + loadGroup, /* loadgroup */ + aObserver, /* imgINotificationObserver */ + aContext, /* loading context */ + aLoadingDocument, /* uniquification key */ + aLoadFlags, /* load flags */ + nullptr, /* cache key */ + aContentPolicyType, /* content policy type */ + initiatorType, /* the load initiator */ + 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) { + imgRequest.swap(*aRequest); + } + + return imgContainer.forget(); +} + +//static +already_AddRefed<imgRequestProxy> +nsContentUtils::GetStaticRequest(imgRequestProxy* aRequest) +{ + NS_ENSURE_TRUE(aRequest, nullptr); + RefPtr<imgRequestProxy> retval; + aRequest->GetStaticRequest(getter_AddRefs(retval)); + return retval.forget(); +} + +// static +bool +nsContentUtils::ContentIsDraggable(nsIContent* aContent) +{ + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aContent); + if (htmlElement) { + bool draggable = false; + htmlElement->GetDraggable(&draggable); + if (draggable) + return true; + + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable, + nsGkAtoms::_false, eIgnoreCase)) + return false; + } + + // special handling for content area image and link dragging + return IsDraggableImage(aContent) || IsDraggableLink(aContent); +} + +// static +bool +nsContentUtils::IsDraggableImage(nsIContent* aContent) +{ + NS_PRECONDITION(aContent, "Must have content node to test"); + + 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 != nullptr; +} + +// static +bool +nsContentUtils::IsDraggableLink(const nsIContent* aContent) { + nsCOMPtr<nsIURI> absURI; + return aContent->IsLink(getter_AddRefs(absURI)); +} + +// static +nsresult +nsContentUtils::NameChanged(mozilla::dom::NodeInfo* aNodeInfo, nsIAtom* aName, + mozilla::dom::NodeInfo** aResult) +{ + nsNodeInfoManager *niMgr = aNodeInfo->NodeInfoManager(); + + *aResult = niMgr->GetNodeInfo(aName, aNodeInfo->GetPrefixAtom(), + aNodeInfo->NamespaceID(), + aNodeInfo->NodeType(), + aNodeInfo->GetExtraName()).take(); + return NS_OK; +} + + +static bool +TestSitePerm(nsIPrincipal* aPrincipal, const char* 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 = services::GetPermissionManager(); + 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 char* aType) +{ + return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, false); +} + +bool +nsContentUtils::IsSitePermDeny(nsIPrincipal* aPrincipal, const char* aType) +{ + return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, false); +} + +bool +nsContentUtils::IsExactSitePermAllow(nsIPrincipal* aPrincipal, const char* aType) +{ + return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, true); +} + +bool +nsContentUtils::IsExactSitePermDeny(nsIPrincipal* aPrincipal, const char* aType) +{ + return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, true); +} + +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, + nsIAtom *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); + } +} + +static const char gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT][56] = { + // Must line up with the enum values in |PropertiesFile| enum. + "chrome://global/locale/css.properties", + "chrome://global/locale/xbl.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" +}; + +/* static */ nsresult +nsContentUtils::EnsureStringBundle(PropertiesFile aFile) +{ + if (!sStringBundles[aFile]) { + if (!sStringBundleService) { + nsresult rv = + CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService); + NS_ENSURE_SUCCESS(rv, rv); + } + nsIStringBundle *bundle; + nsresult rv = + sStringBundleService->CreateBundle(gPropertiesFiles[aFile], &bundle); + NS_ENSURE_SUCCESS(rv, rv); + sStringBundles[aFile] = bundle; // transfer ownership + } + return NS_OK; +} + +/* static */ +nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile, + const char* aKey, + nsXPIDLString& aResult) +{ + nsresult rv = EnsureStringBundle(aFile); + NS_ENSURE_SUCCESS(rv, rv); + nsIStringBundle *bundle = sStringBundles[aFile]; + + return bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aKey).get(), + getter_Copies(aResult)); +} + +/* static */ +nsresult nsContentUtils::FormatLocalizedString(PropertiesFile aFile, + const char* aKey, + const char16_t **aParams, + uint32_t aParamsLength, + nsXPIDLString& aResult) +{ + nsresult rv = EnsureStringBundle(aFile); + NS_ENSURE_SUCCESS(rv, rv); + nsIStringBundle *bundle = sStringBundles[aFile]; + + return bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aKey).get(), + aParams, aParamsLength, + getter_Copies(aResult)); +} + +/* static */ +nsresult nsContentUtils::FormatLocalizedString( + PropertiesFile aFile, + const char* aKey, + const nsTArray<nsString>& aParamArray, + nsXPIDLString& aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<const char16_t*[]> params; + uint32_t paramsLength = aParamArray.Length(); + if (paramsLength > 0) { + params = MakeUnique<const char16_t*[]>(paramsLength); + for (uint32_t i = 0; i < paramsLength; ++i) { + params[i] = aParamArray[i].get(); + } + } + return FormatLocalizedString(aFile, aKey, params.get(), paramsLength, + aResult); +} + + +/* static */ void +nsContentUtils::LogSimpleConsoleError(const nsAString& aErrorText, + const char * classification) +{ + 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, EmptyString(), + EmptyString(), 0, 0, + nsIScriptError::errorFlag, + classification))) { + console->LogMessage(scriptError); + } + } +} + +/* static */ nsresult +nsContentUtils::ReportToConsole(uint32_t aErrorFlags, + const nsACString& aCategory, + const nsIDocument* aDocument, + PropertiesFile aFile, + const char *aMessageName, + const char16_t **aParams, + uint32_t aParamsLength, + nsIURI* aURI, + const nsAFlatString& aSourceLine, + uint32_t aLineNumber, + uint32_t aColumnNumber) +{ + NS_ASSERTION((aParams && aParamsLength) || (!aParams && !aParamsLength), + "Supply either both parameters and their number or no" + "parameters and 0."); + + nsresult rv; + nsXPIDLString errorText; + if (aParams) { + rv = FormatLocalizedString(aFile, aMessageName, aParams, aParamsLength, + errorText); + } + else { + rv = GetLocalizedString(aFile, aMessageName, errorText); + } + NS_ENSURE_SUCCESS(rv, rv); + + return ReportToConsoleNonLocalized(errorText, aErrorFlags, aCategory, + aDocument, aURI, aSourceLine, + aLineNumber, aColumnNumber); +} + + +/* static */ nsresult +nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText, + uint32_t aErrorFlags, + const nsACString& aCategory, + const nsIDocument* aDocument, + nsIURI* aURI, + const nsAFlatString& aSourceLine, + uint32_t aLineNumber, + uint32_t aColumnNumber, + MissingErrorLocationMode aLocationMode) +{ + uint64_t innerWindowID = 0; + if (aDocument) { + if (!aURI) { + aURI = aDocument->GetDocumentURI(); + } + innerWindowID = aDocument->InnerWindowID(); + } + + nsresult rv; + if (!sConsoleService) { // only need to bother null-checking here + rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoCString spec; + if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) { + JSContext *cx = GetCurrentJSContext(); + if (cx) { + nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber); + } + } + if (spec.IsEmpty() && aURI) { + spec = aURI->GetSpecOrDefault(); + } + + nsCOMPtr<nsIScriptError> errorObject = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = errorObject->InitWithWindowID(aErrorText, + NS_ConvertUTF8toUTF16(spec), // file name + aSourceLine, + aLineNumber, aColumnNumber, + aErrorFlags, aCategory, + innerWindowID); + 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(nsIDocument *aDocument) +{ + if (!aDocument) { + return false; + } + return aDocument->NodePrincipal() == sSystemPrincipal; +} + +bool +nsContentUtils::IsChildOfSameType(nsIDocument* aDoc) +{ + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(aDoc->GetDocShell()); + nsCOMPtr<nsIDocShellTreeItem> sameTypeParent; + if (docShellAsItem) { + docShellAsItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); + } + return sameTypeParent != nullptr; +} + +bool +nsContentUtils::IsScriptType(const nsACString& aContentType) +{ + // NOTE: if you add a type here, add it to the CONTENTDLF_CATEGORIES + // define in nsContentDLF.h as well. + return aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) || + aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) || + aContentType.EqualsLiteral(TEXT_ECMASCRIPT) || + aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) || + aContentType.EqualsLiteral(TEXT_JAVASCRIPT) || + aContentType.EqualsLiteral(APPLICATION_JSON) || + aContentType.EqualsLiteral(TEXT_JSON); +} + +bool +nsContentUtils::IsPlainTextType(const nsACString& aContentType) +{ + // NOTE: if you add a type here, add it to the CONTENTDLF_CATEGORIES + // define in nsContentDLF.h as well. + return aContentType.EqualsLiteral(TEXT_PLAIN) || + aContentType.EqualsLiteral(TEXT_CSS) || + aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) || + aContentType.EqualsLiteral(TEXT_VTT) || + IsScriptType(aContentType); +} + +bool +nsContentUtils::GetWrapperSafeScriptFilename(nsIDocument* aDocument, + nsIURI* aURI, + nsACString& aScriptURI, + nsresult* aRv) +{ + MOZ_ASSERT(aRv); + bool scriptFileNameModified = false; + *aRv = NS_OK; + + *aRv = aURI->GetSpec(aScriptURI); + NS_ENSURE_SUCCESS(*aRv, false); + + if (IsChromeDoc(aDocument)) { + nsCOMPtr<nsIChromeRegistry> chromeReg = + mozilla::services::GetChromeRegistryService(); + + if (!chromeReg) { + // If we're running w/o a chrome registry we won't modify any + // script file names. + + return scriptFileNameModified; + } + + bool docWrappersEnabled = + chromeReg->WrappersEnabled(aDocument->GetDocumentURI()); + + bool uriWrappersEnabled = chromeReg->WrappersEnabled(aURI); + + nsIURI *docURI = aDocument->GetDocumentURI(); + + if (docURI && docWrappersEnabled && !uriWrappersEnabled) { + // aURI is a script from a URL that doesn't get wrapper + // automation. aDocument is a chrome document that does get + // wrapper automation. Prepend the chrome document's URI + // followed by the string " -> " to the URI of the script we're + // loading here so that script in that URI gets the same wrapper + // automation that the chrome document expects. + nsAutoCString spec; + *aRv = docURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(*aRv))) { + return false; + } + + spec.AppendLiteral(" -> "); + spec.Append(aScriptURI); + + aScriptURI = spec; + + scriptFileNameModified = true; + } + } + + return scriptFileNameModified; +} + +// static +bool +nsContentUtils::IsInChromeDocshell(nsIDocument *aDocument) +{ + if (!aDocument) { + return false; + } + + if (aDocument->GetDisplayDocument()) { + return IsInChromeDocshell(aDocument->GetDisplayDocument()); + } + + nsCOMPtr<nsIDocShellTreeItem> docShell = aDocument->GetDocShell(); + if (!docShell) { + return false; + } + + return docShell->ItemType() == nsIDocShellTreeItem::typeChrome; +} + +// 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(nsIAtom* 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(nsIAtom* aName) +{ + if (aName) { + EventNameMapping mapping; + if (sAtomEventTable->Get(aName, &mapping)) { + return mapping.mMessage; + } + } + + return eUnidentifiedEvent; +} + +// static +mozilla::EventClassID +nsContentUtils::GetEventClassID(const nsAString& aName) +{ + EventNameMapping mapping; + if (sStringEventTable->Get(aName, &mapping)) + return mapping.mEventClassID; + + return eBasicEventClass; +} + +nsIAtom* +nsContentUtils::GetEventMessageAndAtom(const nsAString& aName, + mozilla::EventClassID aEventClassID, + EventMessage* aEventMessage) +{ + 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->Count() > 127) { + while (sUserDefinedEvents->Count() > 64) { + nsIAtom* first = sUserDefinedEvents->ObjectAt(0); + sStringEventTable->Remove(Substring(nsDependentAtomString(first), 2)); + sUserDefinedEvents->RemoveObjectAt(0); + } + } + + *aEventMessage = eUnidentifiedEvent; + nsCOMPtr<nsIAtom> atom = NS_Atomize(NS_LITERAL_STRING("on") + aName); + sUserDefinedEvents->AppendObject(atom); + mapping.mAtom = atom; + mapping.mMessage = eUnidentifiedEvent; + mapping.mType = EventNameType_None; + mapping.mEventClassID = eBasicEventClass; + // This is a slow hashtable call, but at least we cache the result for the + // following calls. Because GetEventMessageAndAtomForListener utilizes + // sStringEventTable, it needs to know in which cases sStringEventTable + // doesn't contain the information it needs so that it can use + // sAtomEventTable instead. + mapping.mMaybeSpecialSVGorSMILEvent = + GetEventMessage(atom) != eUnidentifiedEvent; + sStringEventTable->Put(aName, mapping); + return mapping.mAtom; +} + +// static +EventMessage +nsContentUtils::GetEventMessageAndAtomForListener(const nsAString& aName, + nsIAtom** aOnName) +{ + // Because of SVG/SMIL sStringEventTable contains a subset of the event names + // comparing to the sAtomEventTable. However, usually sStringEventTable + // contains the information we need, so in order to reduce hashtable + // lookups, start from it. + EventNameMapping mapping; + EventMessage msg = eUnidentifiedEvent; + nsCOMPtr<nsIAtom> atom; + if (sStringEventTable->Get(aName, &mapping)) { + if (mapping.mMaybeSpecialSVGorSMILEvent) { + // Try the atom version so that we should get the right message for + // SVG/SMIL. + atom = NS_Atomize(NS_LITERAL_STRING("on") + aName); + msg = GetEventMessage(atom); + } else { + atom = mapping.mAtom; + msg = mapping.mMessage; + } + atom.forget(aOnName); + return msg; + } + + // GetEventMessageAndAtom will cache the event type for the future usage... + GetEventMessageAndAtom(aName, eBasicEventClass, &msg); + + // ...and then call this method recursively to get the message and atom from + // now updated sStringEventTable. + return GetEventMessageAndAtomForListener(aName, aOnName); +} + +static +nsresult GetEventAndTarget(nsIDocument* aDoc, nsISupports* aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool aTrusted, nsIDOMEvent** aEvent, + EventTarget** aTargetOut) +{ + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc); + nsCOMPtr<EventTarget> target(do_QueryInterface(aTarget)); + NS_ENSURE_TRUE(domDoc && target, NS_ERROR_INVALID_ARG); + + nsCOMPtr<nsIDOMEvent> event; + nsresult rv = + domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); + NS_ENSURE_SUCCESS(rv, rv); + + event->InitEvent(aEventName, aCanBubble, aCancelable); + event->SetTrusted(aTrusted); + + rv = event->SetTarget(target); + NS_ENSURE_SUCCESS(rv, rv); + + event.forget(aEvent); + target.forget(aTargetOut); + return NS_OK; +} + +// static +nsresult +nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool *aDefaultAction) +{ + return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable, + true, aDefaultAction); +} + +// static +nsresult +nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool *aDefaultAction) +{ + return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable, + false, aDefaultAction); +} + +// static +nsresult +nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool aTrusted, bool *aDefaultAction, + bool aOnlyChromeDispatch) +{ + nsCOMPtr<nsIDOMEvent> event; + nsCOMPtr<EventTarget> target; + nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble, + aCancelable, aTrusted, getter_AddRefs(event), + getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = aOnlyChromeDispatch; + + bool dummy; + return target->DispatchEvent(event, aDefaultAction ? aDefaultAction : &dummy); +} + +nsresult +nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc, + nsISupports *aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool *aDefaultAction) +{ + + nsCOMPtr<nsIDOMEvent> event; + nsCOMPtr<EventTarget> target; + nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble, + aCancelable, true, getter_AddRefs(event), + getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(aDoc, "GetEventAndTarget lied?"); + if (!aDoc->GetWindow()) + return NS_ERROR_INVALID_ARG; + + EventTarget* piTarget = aDoc->GetWindow()->GetParentTarget(); + if (!piTarget) + return NS_ERROR_INVALID_ARG; + + nsEventStatus status = nsEventStatus_eIgnore; + rv = piTarget->DispatchDOMEvent(nullptr, event, nullptr, &status); + if (aDefaultAction) { + *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault); + } + return rv; +} + +/* static */ +nsresult +nsContentUtils::DispatchFocusChromeEvent(nsPIDOMWindowOuter* aWindow) +{ + MOZ_ASSERT(aWindow); + + nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + return DispatchChromeEvent(doc, aWindow, + NS_LITERAL_STRING("DOMServiceWorkerFocusClient"), + true, true); +} + +nsresult +nsContentUtils::DispatchEventOnlyToChrome(nsIDocument* aDoc, + nsISupports* aTarget, + const nsAString& aEventName, + bool aCanBubble, bool aCancelable, + bool* aDefaultAction) +{ + return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable, + true, aDefaultAction, true); +} + +/* static */ +Element* +nsContentUtils::MatchElementId(nsIContent *aContent, const nsIAtom* aId) +{ + for (nsIContent* cur = aContent; + cur; + cur = cur->GetNextNode(aContent)) { + if (aId == cur->GetID()) { + return cur->AsElement(); + } + } + + return nullptr; +} + +/* static */ +Element * +nsContentUtils::MatchElementId(nsIContent *aContent, const nsAString& aId) +{ + NS_PRECONDITION(!aId.IsEmpty(), "Will match random elements"); + + // ID attrs are generally stored as atoms, so just atomize this up front + nsCOMPtr<nsIAtom> id(NS_Atomize(aId)); + if (!id) { + // OOM, so just bail + return nullptr; + } + + return MatchElementId(aContent, id); +} + +/* static */ +nsIDocument* +nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument, + uint64_t aOuterWindowId) +{ + if (!aDocument || !aOuterWindowId) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId)->AsOuter(); + if (!window) { + return nullptr; + } + + nsCOMPtr<nsIDocument> foundDoc = window->GetDoc(); + if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) { + // Note that ContentIsCrossDocDescendantOf will return true if + // foundDoc == aDocument. + return foundDoc; + } + + return nullptr; +} + +// Convert the string from the given encoding to Unicode. +/* static */ +nsresult +nsContentUtils::ConvertStringFromEncoding(const nsACString& aEncoding, + const nsACString& aInput, + nsAString& aOutput) +{ + nsAutoCString encoding; + if (aEncoding.IsEmpty()) { + encoding.AssignLiteral("UTF-8"); + } else { + encoding.Assign(aEncoding); + } + + ErrorResult rv; + nsAutoPtr<TextDecoder> decoder(new TextDecoder()); + decoder->InitWithEncoding(encoding, false); + + decoder->Decode(aInput.BeginReading(), aInput.Length(), false, + aOutput, rv); + return rv.StealNSResult(); +} + +/* static */ +bool +nsContentUtils::CheckForBOM(const unsigned char* aBuffer, uint32_t aLength, + nsACString& aCharset) +{ + bool found = true; + aCharset.Truncate(); + if (aLength >= 3 && + aBuffer[0] == 0xEF && + aBuffer[1] == 0xBB && + aBuffer[2] == 0xBF) { + aCharset = "UTF-8"; + } + else if (aLength >= 2 && + aBuffer[0] == 0xFE && aBuffer[1] == 0xFF) { + aCharset = "UTF-16BE"; + } + else if (aLength >= 2 && + aBuffer[0] == 0xFF && aBuffer[1] == 0xFE) { + aCharset = "UTF-16LE"; + } else { + found = false; + } + + return found; +} + +/* static */ +void +nsContentUtils::RegisterShutdownObserver(nsIObserver* aObserver) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(aObserver, + NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + } +} + +/* static */ +void +nsContentUtils::UnregisterShutdownObserver(nsIObserver* aObserver) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } +} + +/* static */ +bool +nsContentUtils::HasNonEmptyAttr(const nsIContent* aContent, int32_t aNameSpaceID, + nsIAtom* aName) +{ + static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::_empty, nullptr}; + return aContent->FindAttrValueIn(aNameSpaceID, aName, strings, eCaseMatters) + == nsIContent::ATTR_VALUE_NO_MATCH; +} + +/* static */ +bool +nsContentUtils::HasMutationListeners(nsINode* aNode, + uint32_t aType, + nsINode* aTargetForSubtreeModified) +{ + nsIDocument* doc = aNode->OwnerDoc(); + + // global object will be null for documents that don't have windows. + nsPIDOMWindowInner* window = doc->GetInnerWindow(); + // This relies on EventListenerManager::AddEventListener, which sets + // all mutation bits when there is a listener for DOMSubtreeModified event. + if (window && !window->HasMutationListeners(aType)) { + return false; + } + + if (aNode->IsNodeOfType(nsINode::eCONTENT) && + static_cast<nsIContent*>(aNode)->ChromeOnlyAccess()) { + return false; + } + + doc->MayDispatchMutationEvent(aTargetForSubtreeModified); + + // If we have a window, we can check it for mutation listeners now. + if (aNode->IsInUncomposedDoc()) { + nsCOMPtr<EventTarget> piTarget(do_QueryInterface(window)); + if (piTarget) { + EventListenerManager* manager = piTarget->GetExistingListenerManager(); + if (manager && manager->HasMutationListeners()) { + return true; + } + } + } + + // If we have a window, we know a mutation listener is registered, but it + // might not be in our chain. If we don't have a window, we might have a + // mutation listener. Check quickly to see. + while (aNode) { + EventListenerManager* manager = aNode->GetExistingListenerManager(); + if (manager && manager->HasMutationListeners()) { + return true; + } + + if (aNode->IsNodeOfType(nsINode::eCONTENT)) { + nsIContent* content = static_cast<nsIContent*>(aNode); + nsIContent* insertionParent = content->GetXBLInsertionParent(); + if (insertionParent) { + aNode = insertionParent; + continue; + } + } + aNode = aNode->GetParentNode(); + } + + return false; +} + +/* static */ +bool +nsContentUtils::HasMutationListeners(nsIDocument* aDocument, + uint32_t aType) +{ + nsPIDOMWindowInner* window = aDocument ? + aDocument->GetInnerWindow() : nullptr; + + // This relies on EventListenerManager::AddEventListener, which sets + // all mutation bits when there is a listener for DOMSubtreeModified event. + return !window || window->HasMutationListeners(aType); +} + +void +nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent, + nsIDocument* aOwnerDoc) +{ + NS_PRECONDITION(aChild, "Missing child"); + NS_PRECONDITION(aChild->GetParentNode() == aParent, "Wrong parent"); + NS_PRECONDITION(aChild->OwnerDoc() == aOwnerDoc, "Wrong owner-doc"); + + // Having an explicit check here since it's an easy mistake to fall into, + // and there might be existing code with problems. We'd rather be safe + // than fire DOMNodeRemoved in all corner cases. We also rely on it for + // nsAutoScriptBlockerSuppressNodeRemoved. + if (!IsSafeToRunScript()) { + // This checks that IsSafeToRunScript is true since we don't want to fire + // events when that is false. We can't rely on EventDispatcher to assert + // this in this situation since most of the time there are no mutation + // event listeners, in which case we won't even attempt to dispatch events. + // However this also allows for two exceptions. First off, we don't assert + // if the mutation happens to native anonymous content since we never fire + // mutation events on such content anyway. + // Second, we don't assert if sDOMNodeRemovedSuppressCount is true since + // that is a know case when we'd normally fire a mutation event, but can't + // make that safe and so we suppress it at this time. Ideally this should + // go away eventually. + if (!(aChild->IsContent() && aChild->AsContent()->IsInNativeAnonymousSubtree()) && + !sDOMNodeRemovedSuppressCount) { + NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe"); + WarnScriptWasIgnored(aOwnerDoc); + } + return; + } + + if (HasMutationListeners(aChild, + NS_EVENT_BITS_MUTATION_NODEREMOVED, aParent)) { + InternalMutationEvent mutation(true, eLegacyNodeRemoved); + mutation.mRelatedNode = do_QueryInterface(aParent); + + mozAutoSubtreeModified subtree(aOwnerDoc, aParent); + EventDispatcher::Dispatch(aChild, nullptr, &mutation); + } +} + +void +nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments() +{ + if (!sEventListenerManagersHash) { + return; + } + + for (auto i = sEventListenerManagersHash->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast<EventListenerManagerMapEntry*>(i.Get()); + nsINode* n = static_cast<nsINode*>(entry->mListenerManager->GetTarget()); + if (n && n->IsInUncomposedDoc() && + nsCCUncollectableMarker::InGeneration(n->OwnerDoc()->GetMarkedCCGeneration())) { + entry->mListenerManager->MarkForCC(); + } + } +} + +/* static */ +void +nsContentUtils::TraverseListenerManager(nsINode *aNode, + nsCycleCollectionTraversalCallback &cb) +{ + if (!sEventListenerManagersHash) { + // We're already shut down, just return. + return; + } + + auto entry = static_cast<EventListenerManagerMapEntry*> + (sEventListenerManagersHash->Search(aNode)); + if (entry) { + CycleCollectionNoteChild(cb, entry->mListenerManager.get(), + "[via hash] mListenerManager"); + } +} + +EventListenerManager* +nsContentUtils::GetListenerManagerForNode(nsINode *aNode) +{ + if (!sEventListenerManagersHash) { + // We're already shut down, don't bother creating an event listener + // manager. + + return nullptr; + } + + auto entry = + static_cast<EventListenerManagerMapEntry*> + (sEventListenerManagersHash->Add(aNode, fallible)); + + if (!entry) { + return nullptr; + } + + if (!entry->mListenerManager) { + entry->mListenerManager = new EventListenerManager(aNode); + + aNode->SetFlags(NODE_HAS_LISTENERMANAGER); + } + + return entry->mListenerManager; +} + +EventListenerManager* +nsContentUtils::GetExistingListenerManagerForNode(const nsINode *aNode) +{ + if (!aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) { + return nullptr; + } + + if (!sEventListenerManagersHash) { + // We're already shut down, don't bother creating an event listener + // manager. + + return nullptr; + } + + auto entry = static_cast<EventListenerManagerMapEntry*> + (sEventListenerManagersHash->Search(aNode)); + if (entry) { + return entry->mListenerManager; + } + + return nullptr; +} + +/* static */ +void +nsContentUtils::RemoveListenerManager(nsINode *aNode) +{ + if (sEventListenerManagersHash) { + auto entry = static_cast<EventListenerManagerMapEntry*> + (sEventListenerManagersHash->Search(aNode)); + if (entry) { + RefPtr<EventListenerManager> listenerManager; + listenerManager.swap(entry->mListenerManager); + // Remove the entry and *then* do operations that could cause further + // modification of sEventListenerManagersHash. See bug 334177. + sEventListenerManagersHash->RawRemove(entry); + if (listenerManager) { + listenerManager->Disconnect(); + } + } + } +} + +/* static */ +bool +nsContentUtils::IsValidNodeName(nsIAtom *aLocalName, nsIAtom *aPrefix, + int32_t aNamespaceID) +{ + if (aNamespaceID == kNameSpaceID_Unknown) { + return false; + } + + if (!aPrefix) { + // If the prefix is null, then either the QName must be xmlns or the + // namespace must not be XMLNS. + return (aLocalName == nsGkAtoms::xmlns) == + (aNamespaceID == kNameSpaceID_XMLNS); + } + + // If the prefix is non-null then the namespace must not be null. + if (aNamespaceID == kNameSpaceID_None) { + return false; + } + + // If the namespace is the XMLNS namespace then the prefix must be xmlns, + // but the localname must not be xmlns. + if (aNamespaceID == kNameSpaceID_XMLNS) { + return aPrefix == nsGkAtoms::xmlns && aLocalName != nsGkAtoms::xmlns; + } + + // If the namespace is not the XMLNS namespace then the prefix must not be + // xmlns. + // If the namespace is the XML namespace then the prefix can be anything. + // If the namespace is not the XML namespace then the prefix must not be xml. + return aPrefix != nsGkAtoms::xmlns && + (aNamespaceID == kNameSpaceID_XML || aPrefix != nsGkAtoms::xml); +} + +/* static */ +nsresult +nsContentUtils::CreateContextualFragment(nsINode* aContextNode, + const nsAString& aFragment, + bool aPreventScriptExecution, + nsIDOMDocumentFragment** aReturn) +{ + ErrorResult rv; + *aReturn = CreateContextualFragment(aContextNode, aFragment, + aPreventScriptExecution, rv).take(); + return rv.StealNSResult(); +} + +already_AddRefed<DocumentFragment> +nsContentUtils::CreateContextualFragment(nsINode* aContextNode, + const nsAString& aFragment, + bool aPreventScriptExecution, + ErrorResult& aRv) +{ + if (!aContextNode) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return nullptr; + } + + // If we don't have a document here, we can't get the right security context + // for compiling event handlers... so just bail out. + nsCOMPtr<nsIDocument> document = aContextNode->OwnerDoc(); + bool isHTML = document->IsHTMLDocument(); +#ifdef DEBUG + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document); + NS_ASSERTION(!isHTML || htmlDoc, "Should have HTMLDocument here!"); +#endif + + if (isHTML) { + RefPtr<DocumentFragment> frag = + new DocumentFragment(document->NodeInfoManager()); + + nsCOMPtr<nsIContent> contextAsContent = do_QueryInterface(aContextNode); + if (contextAsContent && !contextAsContent->IsElement()) { + contextAsContent = contextAsContent->GetParent(); + if (contextAsContent && !contextAsContent->IsElement()) { + // can this even happen? + contextAsContent = nullptr; + } + } + + if (contextAsContent && !contextAsContent->IsHTMLElement(nsGkAtoms::html)) { + aRv = ParseFragmentHTML(aFragment, frag, + contextAsContent->NodeInfo()->NameAtom(), + contextAsContent->GetNameSpaceID(), + (document->GetCompatibilityMode() == + eCompatibility_NavQuirks), + aPreventScriptExecution); + } else { + aRv = ParseFragmentHTML(aFragment, frag, + nsGkAtoms::body, + kNameSpaceID_XHTML, + (document->GetCompatibilityMode() == + eCompatibility_NavQuirks), + aPreventScriptExecution); + } + + return frag.forget(); + } + + AutoTArray<nsString, 32> tagStack; + nsAutoString uriStr, nameStr; + nsCOMPtr<nsIContent> content = do_QueryInterface(aContextNode); + // just in case we have a text node + if (content && !content->IsElement()) + content = content->GetParent(); + + while (content && content->IsElement()) { + nsString& tagName = *tagStack.AppendElement(); + tagName = content->NodeInfo()->QualifiedName(); + + // see if we need to add xmlns declarations + uint32_t count = content->GetAttrCount(); + bool setDefaultNamespace = false; + if (count > 0) { + uint32_t index; + + for (index = 0; index < count; index++) { + const BorrowedAttrInfo info = content->GetAttrInfoAt(index); + const nsAttrName* name = info.mName; + if (name->NamespaceEquals(kNameSpaceID_XMLNS)) { + info.mValue->ToString(uriStr); + + // really want something like nsXMLContentSerializer::SerializeAttr + tagName.AppendLiteral(" xmlns"); // space important + if (name->GetPrefix()) { + tagName.Append(char16_t(':')); + name->LocalName()->ToString(nameStr); + tagName.Append(nameStr); + } else { + setDefaultNamespace = true; + } + tagName.AppendLiteral("=\""); + tagName.Append(uriStr); + tagName.Append('"'); + } + } + } + + if (!setDefaultNamespace) { + mozilla::dom::NodeInfo* info = content->NodeInfo(); + if (!info->GetPrefixAtom() && + info->NamespaceID() != kNameSpaceID_None) { + // We have no namespace prefix, but have a namespace ID. Push + // default namespace attr in, so that our kids will be in our + // namespace. + info->GetNamespaceURI(uriStr); + tagName.AppendLiteral(" xmlns=\""); + tagName.Append(uriStr); + tagName.Append('"'); + } + } + + content = content->GetParent(); + } + + nsCOMPtr<nsIDOMDocumentFragment> frag; + aRv = ParseFragmentXML(aFragment, document, tagStack, + aPreventScriptExecution, getter_AddRefs(frag)); + return frag.forget().downcast<DocumentFragment>(); +} + +/* static */ +void +nsContentUtils::DropFragmentParsers() +{ + NS_IF_RELEASE(sHTMLFragmentParser); + NS_IF_RELEASE(sXMLFragmentParser); + NS_IF_RELEASE(sXMLFragmentSink); +} + +/* static */ +void +nsContentUtils::XPCOMShutdown() +{ + nsContentUtils::DropFragmentParsers(); +} + +/* static */ +nsresult +nsContentUtils::ParseFragmentHTML(const nsAString& aSourceBuffer, + nsIContent* aTargetNode, + nsIAtom* aContextLocalName, + int32_t aContextNamespace, + bool aQuirks, + bool aPreventScriptExecution) +{ + AutoTimelineMarker m(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML"); + + if (nsContentUtils::sFragmentParsingActive) { + NS_NOTREACHED("Re-entrant fragment parsing attempted."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive); + nsContentUtils::sFragmentParsingActive = true; + if (!sHTMLFragmentParser) { + NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser()); + // Now sHTMLFragmentParser owns the object + } + nsresult rv = + sHTMLFragmentParser->ParseFragment(aSourceBuffer, + aTargetNode, + aContextLocalName, + aContextNamespace, + aQuirks, + aPreventScriptExecution); + return rv; +} + +/* static */ +nsresult +nsContentUtils::ParseDocumentHTML(const nsAString& aSourceBuffer, + nsIDocument* aTargetDocument, + bool aScriptingEnabledForNoscriptParsing) +{ + AutoTimelineMarker m(aTargetDocument->GetDocShell(), "Parse HTML"); + + if (nsContentUtils::sFragmentParsingActive) { + NS_NOTREACHED("Re-entrant fragment parsing attempted."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive); + nsContentUtils::sFragmentParsingActive = true; + if (!sHTMLFragmentParser) { + NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser()); + // Now sHTMLFragmentParser owns the object + } + nsresult rv = + sHTMLFragmentParser->ParseDocument(aSourceBuffer, + aTargetDocument, + aScriptingEnabledForNoscriptParsing); + return rv; +} + +/* static */ +nsresult +nsContentUtils::ParseFragmentXML(const nsAString& aSourceBuffer, + nsIDocument* aDocument, + nsTArray<nsString>& aTagStack, + bool aPreventScriptExecution, + nsIDOMDocumentFragment** aReturn) +{ + AutoTimelineMarker m(aDocument->GetDocShell(), "Parse XML"); + + if (nsContentUtils::sFragmentParsingActive) { + NS_NOTREACHED("Re-entrant fragment parsing attempted."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive); + nsContentUtils::sFragmentParsingActive = true; + if (!sXMLFragmentParser) { + nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID); + parser.forget(&sXMLFragmentParser); + // sXMLFragmentParser now owns the parser + } + if (!sXMLFragmentSink) { + NS_NewXMLFragmentContentSink(&sXMLFragmentSink); + // sXMLFragmentSink now owns the sink + } + nsCOMPtr<nsIContentSink> contentsink = do_QueryInterface(sXMLFragmentSink); + MOZ_ASSERT(contentsink, "Sink doesn't QI to nsIContentSink!"); + sXMLFragmentParser->SetContentSink(contentsink); + + sXMLFragmentSink->SetTargetDocument(aDocument); + sXMLFragmentSink->SetPreventScriptExecution(aPreventScriptExecution); + + nsresult rv = + sXMLFragmentParser->ParseFragment(aSourceBuffer, + aTagStack); + if (NS_FAILED(rv)) { + // Drop the fragment parser and sink that might be in an inconsistent state + NS_IF_RELEASE(sXMLFragmentParser); + NS_IF_RELEASE(sXMLFragmentSink); + return rv; + } + + rv = sXMLFragmentSink->FinishFragmentParsing(aReturn); + + sXMLFragmentParser->Reset(); + + return rv; +} + +/* static */ +nsresult +nsContentUtils::ConvertToPlainText(const nsAString& aSourceBuffer, + nsAString& aResultBuffer, + uint32_t aFlags, + uint32_t aWrapCol) +{ + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), "about:blank"); + nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create(); + nsCOMPtr<nsIDOMDocument> domDocument; + nsresult rv = NS_NewDOMDocument(getter_AddRefs(domDocument), + EmptyString(), + EmptyString(), + nullptr, + uri, + uri, + principal, + true, + nullptr, + DocumentFlavorHTML); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument); + rv = nsContentUtils::ParseDocumentHTML(aSourceBuffer, document, + !(aFlags & nsIDocumentEncoder::OutputNoScriptContent)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance( + "@mozilla.org/layout/documentEncoder;1?type=text/plain"); + + rv = encoder->Init(domDocument, NS_LITERAL_STRING("text/plain"), aFlags); + NS_ENSURE_SUCCESS(rv, rv); + + encoder->SetWrapColumn(aWrapCol); + + return encoder->EncodeToString(aResultBuffer); +} + +/* static */ +nsresult +nsContentUtils::SetNodeTextContent(nsIContent* aContent, + const nsAString& aValue, + bool aTryReuse) +{ + // Fire DOMNodeRemoved mutation events before we do anything else. + nsCOMPtr<nsIContent> owningContent; + + // Batch possible DOMSubtreeModified events. + mozAutoSubtreeModified subtree(nullptr, nullptr); + + // Scope firing mutation events so that we don't carry any state that + // might be stale + { + // We're relying on mozAutoSubtreeModified to keep a strong reference if + // needed. + nsIDocument* doc = aContent->OwnerDoc(); + + // Optimize the common case of there being no observers + if (HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) { + subtree.UpdateTarget(doc, nullptr); + owningContent = aContent; + nsCOMPtr<nsINode> child; + bool skipFirst = aTryReuse; + for (child = aContent->GetFirstChild(); + child && child->GetParentNode() == aContent; + child = child->GetNextSibling()) { + if (skipFirst && child->IsNodeOfType(nsINode::eTEXT)) { + skipFirst = false; + continue; + } + nsContentUtils::MaybeFireNodeRemoved(child, aContent, doc); + } + } + } + + // Might as well stick a batch around this since we're performing several + // mutations. + mozAutoDocUpdate updateBatch(aContent->GetComposedDoc(), + UPDATE_CONTENT_MODEL, true); + nsAutoMutationBatch mb; + + uint32_t childCount = aContent->GetChildCount(); + + if (aTryReuse && !aValue.IsEmpty()) { + uint32_t removeIndex = 0; + + for (uint32_t i = 0; i < childCount; ++i) { + nsIContent* child = aContent->GetChildAt(removeIndex); + if (removeIndex == 0 && child && child->IsNodeOfType(nsINode::eTEXT)) { + nsresult rv = child->SetText(aValue, true); + NS_ENSURE_SUCCESS(rv, rv); + + removeIndex = 1; + } + else { + aContent->RemoveChildAt(removeIndex, true); + } + } + + if (removeIndex == 1) { + return NS_OK; + } + } + else { + mb.Init(aContent, true, false); + for (uint32_t i = 0; i < childCount; ++i) { + aContent->RemoveChildAt(0, true); + } + } + mb.RemovalDone(); + + if (aValue.IsEmpty()) { + return NS_OK; + } + + RefPtr<nsTextNode> textContent = + new nsTextNode(aContent->NodeInfo()->NodeInfoManager()); + + textContent->SetText(aValue, true); + + nsresult rv = aContent->AppendChildTo(textContent, true); + mb.NodesAdded(); + return rv; +} + +static bool +AppendNodeTextContentsRecurse(nsINode* aNode, nsAString& aResult, + const fallible_t& aFallible) +{ + for (nsIContent* child = aNode->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->IsElement()) { + bool ok = AppendNodeTextContentsRecurse(child, aResult, + aFallible); + if (!ok) { + return false; + } + } + else if (child->IsNodeOfType(nsINode::eTEXT)) { + bool ok = child->AppendTextTo(aResult, aFallible); + if (!ok) { + return false; + } + } + } + + return true; +} + +/* static */ +bool +nsContentUtils::AppendNodeTextContent(nsINode* aNode, bool aDeep, + nsAString& aResult, + const fallible_t& aFallible) +{ + if (aNode->IsNodeOfType(nsINode::eTEXT)) { + return static_cast<nsIContent*>(aNode)->AppendTextTo(aResult, + aFallible); + } + else if (aDeep) { + return AppendNodeTextContentsRecurse(aNode, aResult, aFallible); + } + else { + for (nsIContent* child = aNode->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->IsNodeOfType(nsINode::eTEXT)) { + bool ok = child->AppendTextTo(aResult, fallible); + if (!ok) { + return false; + } + } + } + } + + return true; +} + +bool +nsContentUtils::HasNonEmptyTextContent(nsINode* aNode, + TextContentDiscoverMode aDiscoverMode) +{ + for (nsIContent* child = aNode->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->IsNodeOfType(nsINode::eTEXT) && + child->TextLength() > 0) { + return true; + } + + if (aDiscoverMode == eRecurseIntoChildren && + HasNonEmptyTextContent(child, aDiscoverMode)) { + return true; + } + } + + return false; +} + +/* static */ +bool +nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode, + const nsIContent* aContent) +{ + NS_PRECONDITION(aNode, + "Must have a node to work with"); + NS_PRECONDITION(aContent, + "Must have a content to work with"); + + if (!aNode->IsNodeOfType(nsINode::eCONTENT)) { + /** + * The root isn't an nsIContent, so it's a document or attribute. The only + * nodes in the same anonymous subtree as it will have a null + * bindingParent. + * + * XXXbz strictly speaking, that's not true for attribute nodes. + */ + return aContent->GetBindingParent() == nullptr; + } + + const nsIContent* nodeAsContent = static_cast<const nsIContent*>(aNode); + + // For nodes in a shadow tree, it is insufficient to simply compare + // the binding parent because a node may host multiple ShadowRoots, + // thus nodes in different shadow tree may have the same binding parent. + if (aNode->IsInShadowTree()) { + return nodeAsContent->GetContainingShadow() == + aContent->GetContainingShadow(); + } + + return nodeAsContent->GetBindingParent() == aContent->GetBindingParent(); +} + +class AnonymousContentDestroyer : public Runnable { +public: + explicit AnonymousContentDestroyer(nsCOMPtr<nsIContent>* aContent) { + mContent.swap(*aContent); + mParent = mContent->GetParent(); + mDoc = mContent->OwnerDoc(); + } + explicit AnonymousContentDestroyer(nsCOMPtr<Element>* aElement) { + mContent = aElement->forget(); + mParent = mContent->GetParent(); + mDoc = mContent->OwnerDoc(); + } + NS_IMETHOD Run() override { + mContent->UnbindFromTree(); + return NS_OK; + } +private: + nsCOMPtr<nsIContent> mContent; + // Hold strong refs to the parent content and document so that they + // don't die unexpectedly + nsCOMPtr<nsIDocument> mDoc; + nsCOMPtr<nsIContent> mParent; +}; + +/* static */ +void +nsContentUtils::DestroyAnonymousContent(nsCOMPtr<nsIContent>* aContent) +{ + if (*aContent) { + AddScriptRunner(new AnonymousContentDestroyer(aContent)); + } +} + +/* static */ +void +nsContentUtils::DestroyAnonymousContent(nsCOMPtr<Element>* aElement) +{ + if (*aElement) { + AddScriptRunner(new AnonymousContentDestroyer(aElement)); + } +} + +/* static */ +void +nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling) +{ + IMEStateManager::OnInstalledMenuKeyboardListener(aInstalling); +} + +static bool SchemeIs(nsIURI* aURI, const char* aScheme) +{ + nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(baseURI, false); + + bool isScheme = false; + return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme; +} + +bool +nsContentUtils::IsSystemPrincipal(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(IsInitialized()); + return aPrincipal == sSystemPrincipal; +} + +bool +nsContentUtils::IsExpandedPrincipal(nsIPrincipal* aPrincipal) +{ + nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal); + return !!ep; +} + +nsIPrincipal* +nsContentUtils::GetSystemPrincipal() +{ + MOZ_ASSERT(IsInitialized()); + return sSystemPrincipal; +} + +bool +nsContentUtils::CombineResourcePrincipals(nsCOMPtr<nsIPrincipal>* aResourcePrincipal, + nsIPrincipal* aExtraPrincipal) +{ + if (!aExtraPrincipal) { + return false; + } + if (!*aResourcePrincipal) { + *aResourcePrincipal = aExtraPrincipal; + return true; + } + if (*aResourcePrincipal == aExtraPrincipal) { + return false; + } + bool subsumes; + if (NS_SUCCEEDED((*aResourcePrincipal)->Subsumes(aExtraPrincipal, &subsumes)) && + subsumes) { + return false; + } + *aResourcePrincipal = sSystemPrincipal; + return true; +} + +/* static */ +void +nsContentUtils::TriggerLink(nsIContent *aContent, nsPresContext *aPresContext, + nsIURI *aLinkURI, const nsString &aTargetSpec, + bool aClick, bool aIsUserTriggered, + bool aIsTrusted) +{ + NS_ASSERTION(aPresContext, "Need a nsPresContext"); + NS_PRECONDITION(aLinkURI, "No link URI"); + + if (aContent->IsEditable()) { + return; + } + + nsILinkHandler *handler = aPresContext->GetLinkHandler(); + if (!handler) { + return; + } + + if (!aClick) { + handler->OnOverLink(aContent, aLinkURI, aTargetSpec.get()); + return; + } + + // Check that this page is allowed to load this URI. + nsresult proceed = NS_OK; + + if (sSecurityManager) { + uint32_t flag = + aIsUserTriggered ? + (uint32_t)nsIScriptSecurityManager::STANDARD : + (uint32_t)nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT; + proceed = + sSecurityManager->CheckLoadURIWithPrincipal(aContent->NodePrincipal(), + aLinkURI, flag); + } + + // Only pass off the click event if the script security manager says it's ok. + // We need to rest aTargetSpec for forced downloads. + if (NS_SUCCEEDED(proceed)) { + + // A link/area element with a download attribute is allowed to set + // a pseudo Content-Disposition header. + // For security reasons we only allow websites to declare same-origin resources + // as downloadable. If this check fails we will just do the normal thing + // (i.e. navigate to the resource). + nsAutoString fileName; + if ((!aContent->IsHTMLElement(nsGkAtoms::a) && + !aContent->IsHTMLElement(nsGkAtoms::area) && + !aContent->IsSVGElement(nsGkAtoms::a)) || + !aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::download, fileName) || + NS_FAILED(aContent->NodePrincipal()->CheckMayLoad(aLinkURI, false, true))) { + fileName.SetIsVoid(true); // No actionable download attribute was found. + } + + handler->OnLinkClick(aContent, aLinkURI, + fileName.IsVoid() ? aTargetSpec.get() : EmptyString().get(), + fileName, nullptr, nullptr, aIsTrusted); + } +} + +/* static */ +void +nsContentUtils::GetLinkLocation(Element* aElement, nsString& aLocationString) +{ + nsCOMPtr<nsIURI> hrefURI = aElement->GetHrefURI(); + if (hrefURI) { + nsAutoCString specUTF8; + nsresult rv = hrefURI->GetSpec(specUTF8); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(specUTF8, aLocationString); + } +} + +/* static */ +nsIWidget* +nsContentUtils::GetTopLevelWidget(nsIWidget* aWidget) +{ + if (!aWidget) + return nullptr; + + return aWidget->GetTopLevelWidget(); +} + +/* static */ +const nsDependentString +nsContentUtils::GetLocalizedEllipsis() +{ + static char16_t sBuf[4] = { 0, 0, 0, 0 }; + if (!sBuf[0]) { + nsAdoptingString tmp = Preferences::GetLocalizedString("intl.ellipsis"); + uint32_t len = std::min(uint32_t(tmp.Length()), + uint32_t(ArrayLength(sBuf) - 1)); + CopyUnicodeTo(tmp, 0, sBuf, len); + if (!sBuf[0]) + sBuf[0] = char16_t(0x2026); + } + return nsDependentString(sBuf); +} + +/* static */ +void +nsContentUtils::AddScriptBlocker() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!sScriptBlockerCount) { + MOZ_ASSERT(sRunnersCountAtFirstBlocker == 0, + "Should not already have a count"); + sRunnersCountAtFirstBlocker = sBlockedScriptRunners ? sBlockedScriptRunners->Length() : 0; + } + ++sScriptBlockerCount; +} + +#ifdef DEBUG +static bool sRemovingScriptBlockers = false; +#endif + +/* static */ +void +nsContentUtils::RemoveScriptBlocker() +{ + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sRemovingScriptBlockers); + NS_ASSERTION(sScriptBlockerCount != 0, "Negative script blockers"); + --sScriptBlockerCount; + if (sScriptBlockerCount) { + return; + } + + if (!sBlockedScriptRunners) { + return; + } + + uint32_t firstBlocker = sRunnersCountAtFirstBlocker; + uint32_t lastBlocker = sBlockedScriptRunners->Length(); + uint32_t originalFirstBlocker = firstBlocker; + uint32_t blockersCount = lastBlocker - firstBlocker; + sRunnersCountAtFirstBlocker = 0; + NS_ASSERTION(firstBlocker <= lastBlocker, + "bad sRunnersCountAtFirstBlocker"); + + while (firstBlocker < lastBlocker) { + nsCOMPtr<nsIRunnable> runnable; + runnable.swap((*sBlockedScriptRunners)[firstBlocker]); + ++firstBlocker; + + // Calling the runnable can reenter us + runnable->Run(); + // So can dropping the reference to the runnable + runnable = nullptr; + + NS_ASSERTION(sRunnersCountAtFirstBlocker == 0, + "Bad count"); + NS_ASSERTION(!sScriptBlockerCount, "This is really bad"); + } +#ifdef DEBUG + AutoRestore<bool> removingScriptBlockers(sRemovingScriptBlockers); + sRemovingScriptBlockers = true; +#endif + sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount); +} + +/* static */ +nsIWindowProvider* +nsContentUtils::GetWindowProviderForContentProcess() +{ + MOZ_ASSERT(XRE_IsContentProcess()); + return ContentChild::GetSingleton(); +} + +/* static */ +already_AddRefed<nsPIDOMWindowOuter> +nsContentUtils::GetMostRecentNonPBWindow() +{ + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); + nsCOMPtr<nsIWindowMediator_44> wm = do_QueryInterface(windowMediator); + + nsCOMPtr<mozIDOMWindowProxy> window; + wm->GetMostRecentNonPBWindow(u"navigator:browser", + getter_AddRefs(window)); + nsCOMPtr<nsPIDOMWindowOuter> pwindow; + pwindow = do_QueryInterface(window); + + return pwindow.forget(); +} + +/* static */ +void +nsContentUtils::WarnScriptWasIgnored(nsIDocument* aDocument) +{ + nsAutoString msg; + if (aDocument) { + nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI(); + if (uri) { + msg.Append(NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())); + msg.AppendLiteral(" : "); + } + } + msg.AppendLiteral("Unable to run script because scripts are blocked internally."); + + LogSimpleConsoleError(msg, "DOM"); +} + +/* static */ +void +nsContentUtils::AddScriptRunner(already_AddRefed<nsIRunnable> aRunnable) +{ + nsCOMPtr<nsIRunnable> runnable = aRunnable; + if (!runnable) { + return; + } + + if (sScriptBlockerCount) { + sBlockedScriptRunners->AppendElement(runnable.forget()); + return; + } + + runnable->Run(); +} + +/* static */ +void +nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; + AddScriptRunner(runnable.forget()); +} + +/* static */ +void +nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable) +{ + MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!"); + CycleCollectedJSContext::Get()->RunInStableState(Move(aRunnable)); +} + +/* static */ +void +nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable) +{ + MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!"); + CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable)); +} + +void +nsContentUtils::EnterMicroTask() +{ + MOZ_ASSERT(NS_IsMainThread()); + ++sMicroTaskLevel; +} + +void +nsContentUtils::LeaveMicroTask() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (--sMicroTaskLevel == 0) { + PerformMainThreadMicroTaskCheckpoint(); + } +} + +bool +nsContentUtils::IsInMicroTask() +{ + MOZ_ASSERT(NS_IsMainThread()); + return sMicroTaskLevel != 0; +} + +uint32_t +nsContentUtils::MicroTaskLevel() +{ + MOZ_ASSERT(NS_IsMainThread()); + return sMicroTaskLevel; +} + +void +nsContentUtils::SetMicroTaskLevel(uint32_t aLevel) +{ + MOZ_ASSERT(NS_IsMainThread()); + sMicroTaskLevel = aLevel; +} + +void +nsContentUtils::PerformMainThreadMicroTaskCheckpoint() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsDOMMutationObserver::HandleMutations(); +} + +/* + * Helper function for nsContentUtils::ProcessViewportInfo. + * + * Handles a single key=value pair. If it corresponds to a valid viewport + * attribute, add it to the document header data. No validation is done on the + * value itself (this is done at display time). + */ +static void ProcessViewportToken(nsIDocument *aDocument, + const nsAString &token) { + + /* Iterators. */ + nsAString::const_iterator tip, tail, end; + token.BeginReading(tip); + tail = tip; + token.EndReading(end); + + /* Move tip to the '='. */ + while ((tip != end) && (*tip != '=')) + ++tip; + + /* If we didn't find an '=', punt. */ + if (tip == end) + return; + + /* Extract the key and value. */ + const nsAString &key = + nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(Substring(tail, tip), + true); + const nsAString &value = + nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(Substring(++tip, end), + true); + + /* Check for known keys. If we find a match, insert the appropriate + * information into the document header. */ + nsCOMPtr<nsIAtom> key_atom = NS_Atomize(key); + if (key_atom == nsGkAtoms::height) + aDocument->SetHeaderData(nsGkAtoms::viewport_height, value); + else if (key_atom == nsGkAtoms::width) + aDocument->SetHeaderData(nsGkAtoms::viewport_width, value); + else if (key_atom == nsGkAtoms::initial_scale) + aDocument->SetHeaderData(nsGkAtoms::viewport_initial_scale, value); + else if (key_atom == nsGkAtoms::minimum_scale) + aDocument->SetHeaderData(nsGkAtoms::viewport_minimum_scale, value); + else if (key_atom == nsGkAtoms::maximum_scale) + aDocument->SetHeaderData(nsGkAtoms::viewport_maximum_scale, value); + else if (key_atom == nsGkAtoms::user_scalable) + aDocument->SetHeaderData(nsGkAtoms::viewport_user_scalable, value); +} + +#define IS_SEPARATOR(c) ((c == '=') || (c == ',') || (c == ';') || \ + (c == '\t') || (c == '\n') || (c == '\r')) + +/* static */ +nsresult +nsContentUtils::ProcessViewportInfo(nsIDocument *aDocument, + const nsAString &viewportInfo) { + + /* We never fail. */ + nsresult rv = NS_OK; + + aDocument->SetHeaderData(nsGkAtoms::viewport, viewportInfo); + + /* Iterators. */ + nsAString::const_iterator tip, tail, end; + viewportInfo.BeginReading(tip); + tail = tip; + viewportInfo.EndReading(end); + + /* Read the tip to the first non-separator character. */ + while ((tip != end) && (IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) + ++tip; + + /* Read through and find tokens separated by separators. */ + while (tip != end) { + + /* Synchronize tip and tail. */ + tail = tip; + + /* Advance tip past non-separator characters. */ + while ((tip != end) && !IS_SEPARATOR(*tip)) + ++tip; + + /* Allow white spaces that surround the '=' character */ + if ((tip != end) && (*tip == '=')) { + ++tip; + + while ((tip != end) && nsCRT::IsAsciiSpace(*tip)) + ++tip; + + while ((tip != end) && !(IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) + ++tip; + } + + /* Our token consists of the characters between tail and tip. */ + ProcessViewportToken(aDocument, Substring(tail, tip)); + + /* Skip separators. */ + while ((tip != end) && (IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip))) + ++tip; + } + + return rv; + +} + +#undef IS_SEPARATOR + +/* static */ +void +nsContentUtils::HidePopupsInDocument(nsIDocument* aDocument) +{ +#ifdef MOZ_XUL + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && aDocument) { + nsCOMPtr<nsIDocShellTreeItem> docShellToHide = aDocument->GetDocShell(); + if (docShellToHide) + pm->HidePopupsInDocShell(docShellToHide); + } +#endif +} + +/* static */ +already_AddRefed<nsIDragSession> +nsContentUtils::GetDragSession() +{ + nsCOMPtr<nsIDragSession> dragSession; + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) + dragService->GetCurrentSession(getter_AddRefs(dragSession)); + return dragSession.forget(); +} + +/* static */ +nsresult +nsContentUtils::SetDataTransferInEvent(WidgetDragEvent* aDragEvent) +{ + if (aDragEvent->mDataTransfer || !aDragEvent->IsTrusted()) { + return NS_OK; + } + + // For dragstart events, the data transfer object is + // created before the event fires, so it should already be set. For other + // drag events, get the object from the drag session. + NS_ASSERTION(aDragEvent->mMessage != eDragStart, + "draggesture event created without a dataTransfer"); + + nsCOMPtr<nsIDragSession> dragSession = GetDragSession(); + NS_ENSURE_TRUE(dragSession, NS_OK); // no drag in progress + + nsCOMPtr<nsIDOMDataTransfer> dataTransfer; + nsCOMPtr<DataTransfer> initialDataTransfer; + dragSession->GetDataTransfer(getter_AddRefs(dataTransfer)); + if (dataTransfer) { + initialDataTransfer = do_QueryInterface(dataTransfer); + if (!initialDataTransfer) { + return NS_ERROR_FAILURE; + } + } else { + // A dataTransfer won't exist when a drag was started by some other + // means, for instance calling the drag service directly, or a drag + // from another application. In either case, a new dataTransfer should + // be created that reflects the data. + initialDataTransfer = + new DataTransfer(aDragEvent->mTarget, aDragEvent->mMessage, true, -1); + + // now set it in the drag session so we don't need to create it again + dragSession->SetDataTransfer(initialDataTransfer); + } + + bool isCrossDomainSubFrameDrop = false; + if (aDragEvent->mMessage == eDrop) { + isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent); + } + + // each event should use a clone of the original dataTransfer. + initialDataTransfer->Clone(aDragEvent->mTarget, aDragEvent->mMessage, + aDragEvent->mUserCancelled, + isCrossDomainSubFrameDrop, + getter_AddRefs(aDragEvent->mDataTransfer)); + if (NS_WARN_IF(!aDragEvent->mDataTransfer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // for the dragenter and dragover events, initialize the drop effect + // from the drop action, which platform specific widget code sets before + // the event is fired based on the keyboard state. + if (aDragEvent->mMessage == eDragEnter || aDragEvent->mMessage == eDragOver) { + uint32_t action, effectAllowed; + dragSession->GetDragAction(&action); + aDragEvent->mDataTransfer->GetEffectAllowedInt(&effectAllowed); + aDragEvent->mDataTransfer->SetDropEffectInt( + FilterDropEffect(action, effectAllowed)); + } + else if (aDragEvent->mMessage == eDrop || + aDragEvent->mMessage == eDragEnd) { + // For the drop and dragend events, set the drop effect based on the + // last value that the dropEffect had. This will have been set in + // EventStateManager::PostHandleEvent for the last dragenter or + // dragover event. + uint32_t dropEffect; + initialDataTransfer->GetDropEffectInt(&dropEffect); + aDragEvent->mDataTransfer->SetDropEffectInt(dropEffect); + } + + return NS_OK; +} + +/* static */ +uint32_t +nsContentUtils::FilterDropEffect(uint32_t aAction, uint32_t aEffectAllowed) +{ + // It is possible for the drag action to include more than one action, but + // the widget code which sets the action from the keyboard state should only + // be including one. If multiple actions were set, we just consider them in + // the following order: + // copy, link, move + if (aAction & nsIDragService::DRAGDROP_ACTION_COPY) + aAction = nsIDragService::DRAGDROP_ACTION_COPY; + else if (aAction & nsIDragService::DRAGDROP_ACTION_LINK) + aAction = nsIDragService::DRAGDROP_ACTION_LINK; + else if (aAction & nsIDragService::DRAGDROP_ACTION_MOVE) + aAction = nsIDragService::DRAGDROP_ACTION_MOVE; + + // Filter the action based on the effectAllowed. If the effectAllowed + // doesn't include the action, then that action cannot be done, so adjust + // the action to something that is allowed. For a copy, adjust to move or + // link. For a move, adjust to copy or link. For a link, adjust to move or + // link. Otherwise, use none. + if (aAction & aEffectAllowed || + aEffectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) + return aAction; + if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_MOVE) + return nsIDragService::DRAGDROP_ACTION_MOVE; + if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_COPY) + return nsIDragService::DRAGDROP_ACTION_COPY; + if (aEffectAllowed & nsIDragService::DRAGDROP_ACTION_LINK) + return nsIDragService::DRAGDROP_ACTION_LINK; + return nsIDragService::DRAGDROP_ACTION_NONE; +} + +/* static */ +bool +nsContentUtils::CheckForSubFrameDrop(nsIDragSession* aDragSession, + WidgetDragEvent* aDropEvent) +{ + nsCOMPtr<nsIContent> target = do_QueryInterface(aDropEvent->mOriginalTarget); + if (!target) { + return true; + } + + nsIDocument* targetDoc = target->OwnerDoc(); + nsPIDOMWindowOuter* targetWin = targetDoc->GetWindow(); + if (!targetWin) { + return true; + } + + nsCOMPtr<nsIDocShellTreeItem> tdsti = targetWin->GetDocShell(); + if (!tdsti) { + return true; + } + + // Always allow dropping onto chrome shells. + if (tdsti->ItemType() == nsIDocShellTreeItem::typeChrome) { + return false; + } + + // If there is no source node, then this is a drag from another + // application, which should be allowed. + nsCOMPtr<nsIDOMDocument> sourceDocument; + aDragSession->GetSourceDocument(getter_AddRefs(sourceDocument)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(sourceDocument); + if (doc) { + // Get each successive parent of the source document and compare it to + // the drop document. If they match, then this is a drag from a child frame. + do { + doc = doc->GetParentDocument(); + if (doc == targetDoc) { + // The drag is from a child frame. + return true; + } + } while (doc); + } + + return false; +} + +/* static */ +bool +nsContentUtils::URIIsLocalFile(nsIURI *aURI) +{ + bool isFile; + nsCOMPtr<nsINetUtil> util = do_QueryInterface(sIOService); + + // Important: we do NOT test the entire URI chain here! + return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI, + nsIProtocolHandler::URI_IS_LOCAL_FILE, + &isFile)) && + isFile; +} + +/* static */ +nsIScriptContext* +nsContentUtils::GetContextForEventHandlers(nsINode* aNode, + nsresult* aRv) +{ + *aRv = NS_OK; + bool hasHadScriptObject = true; + nsIScriptGlobalObject* sgo = + aNode->OwnerDoc()->GetScriptHandlingObject(hasHadScriptObject); + // It is bad if the document doesn't have event handling context, + // but it used to have one. + if (!sgo && hasHadScriptObject) { + *aRv = NS_ERROR_UNEXPECTED; + return nullptr; + } + + if (sgo) { + nsIScriptContext* scx = sgo->GetContext(); + // Bad, no context from script global object! + if (!scx) { + *aRv = NS_ERROR_UNEXPECTED; + return nullptr; + } + return scx; + } + + return nullptr; +} + +/* static */ +JSContext * +nsContentUtils::GetCurrentJSContext() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsInitialized()); + if (!IsJSAPIActive()) { + return nullptr; + } + return danger::GetJSContext(); +} + +/* static */ +JSContext * +nsContentUtils::GetCurrentJSContextForThread() +{ + MOZ_ASSERT(IsInitialized()); + if (MOZ_LIKELY(NS_IsMainThread())) { + return GetCurrentJSContext(); + } else { + return workers::GetCurrentThreadJSContext(); + } +} + +template<typename StringType, typename CharType> +void +_ASCIIToLowerInSitu(StringType& aStr) +{ + CharType* iter = aStr.BeginWriting(); + CharType* end = aStr.EndWriting(); + MOZ_ASSERT(iter && end); + + while (iter != end) { + CharType c = *iter; + if (c >= 'A' && c <= 'Z') { + *iter = c + ('a' - 'A'); + } + ++iter; + } +} + +/* static */ +void +nsContentUtils::ASCIIToLower(nsAString& aStr) +{ + return _ASCIIToLowerInSitu<nsAString, char16_t>(aStr); +} + +/* static */ +void +nsContentUtils::ASCIIToLower(nsACString& aStr) +{ + return _ASCIIToLowerInSitu<nsACString, char>(aStr); +} + +template<typename StringType, typename CharType> +void +_ASCIIToLowerCopy(const StringType& aSource, StringType& aDest) +{ + uint32_t len = aSource.Length(); + aDest.SetLength(len); + MOZ_ASSERT(aDest.Length() == len); + + CharType* dest = aDest.BeginWriting(); + MOZ_ASSERT(dest); + + const CharType* iter = aSource.BeginReading(); + const CharType* end = aSource.EndReading(); + while (iter != end) { + CharType c = *iter; + *dest = (c >= 'A' && c <= 'Z') ? + c + ('a' - 'A') : c; + ++iter; + ++dest; + } +} + +/* static */ +void +nsContentUtils::ASCIIToLower(const nsAString& aSource, nsAString& aDest) { + return _ASCIIToLowerCopy<nsAString, char16_t>(aSource, aDest); +} + +/* static */ +void +nsContentUtils::ASCIIToLower(const nsACString& aSource, nsACString& aDest) { + return _ASCIIToLowerCopy<nsACString, char>(aSource, aDest); +} + + +template<typename StringType, typename CharType> +void +_ASCIIToUpperInSitu(StringType& aStr) +{ + CharType* iter = aStr.BeginWriting(); + CharType* end = aStr.EndWriting(); + MOZ_ASSERT(iter && end); + + while (iter != end) { + CharType c = *iter; + if (c >= 'a' && c <= 'z') { + *iter = c + ('A' - 'a'); + } + ++iter; + } +} + +/* static */ +void +nsContentUtils::ASCIIToUpper(nsAString& aStr) +{ + return _ASCIIToUpperInSitu<nsAString, char16_t>(aStr); +} + +/* static */ +void +nsContentUtils::ASCIIToUpper(nsACString& aStr) +{ + return _ASCIIToUpperInSitu<nsACString, char>(aStr); +} + +template<typename StringType, typename CharType> +void +_ASCIIToUpperCopy(const StringType& aSource, StringType& aDest) +{ + uint32_t len = aSource.Length(); + aDest.SetLength(len); + MOZ_ASSERT(aDest.Length() == len); + + CharType* dest = aDest.BeginWriting(); + MOZ_ASSERT(dest); + + const CharType* iter = aSource.BeginReading(); + const CharType* end = aSource.EndReading(); + while (iter != end) { + CharType c = *iter; + *dest = (c >= 'a' && c <= 'z') ? + c + ('A' - 'a') : c; + ++iter; + ++dest; + } +} + +/* static */ +void +nsContentUtils::ASCIIToUpper(const nsAString& aSource, nsAString& aDest) +{ + return _ASCIIToUpperCopy<nsAString, char16_t>(aSource, aDest); +} + +/* static */ +void +nsContentUtils::ASCIIToUpper(const nsACString& aSource, nsACString& aDest) +{ + return _ASCIIToUpperCopy<nsACString, char>(aSource, aDest); +} + +/* static */ +bool +nsContentUtils::EqualsIgnoreASCIICase(const nsAString& aStr1, + const nsAString& aStr2) +{ + uint32_t len = aStr1.Length(); + if (len != aStr2.Length()) { + return false; + } + + const char16_t* str1 = aStr1.BeginReading(); + const char16_t* str2 = aStr2.BeginReading(); + const char16_t* end = str1 + len; + + while (str1 < end) { + char16_t c1 = *str1++; + char16_t c2 = *str2++; + + // First check if any bits other than the 0x0020 differs + if ((c1 ^ c2) & 0xffdf) { + return false; + } + + // We know they can only differ in the 0x0020 bit. + // Likely the two chars are the same, so check that first + if (c1 != c2) { + // They do differ, but since it's only in the 0x0020 bit, check if it's + // the same ascii char, but just differing in case + char16_t c1Upper = c1 & 0xffdf; + if (!('A' <= c1Upper && c1Upper <= 'Z')) { + return false; + } + } + } + + return true; +} + +/* static */ +bool +nsContentUtils::StringContainsASCIIUpper(const nsAString& aStr) +{ + const char16_t* iter = aStr.BeginReading(); + const char16_t* end = aStr.EndReading(); + while (iter != end) { + char16_t c = *iter; + if (c >= 'A' && c <= 'Z') { + return true; + } + ++iter; + } + + return false; +} + +/* static */ +nsIInterfaceRequestor* +nsContentUtils::SameOriginChecker() +{ + if (!sSameOriginChecker) { + sSameOriginChecker = new SameOriginCheckerImpl(); + NS_ADDREF(sSameOriginChecker); + } + return sSameOriginChecker; +} + +/* static */ +nsresult +nsContentUtils::CheckSameOrigin(nsIChannel *aOldChannel, nsIChannel *aNewChannel) +{ + if (!nsContentUtils::GetSecurityManager()) + return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr<nsIPrincipal> oldPrincipal; + nsContentUtils::GetSecurityManager()-> + GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal)); + + nsCOMPtr<nsIURI> newURI; + aNewChannel->GetURI(getter_AddRefs(newURI)); + nsCOMPtr<nsIURI> newOriginalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI)); + + NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI); + + nsresult rv = oldPrincipal->CheckMayLoad(newURI, false, false); + if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) { + rv = oldPrincipal->CheckMayLoad(newOriginalURI, false, false); + } + + return rv; +} + +NS_IMPL_ISUPPORTS(SameOriginCheckerImpl, + nsIChannelEventSink, + nsIInterfaceRequestor) + +NS_IMETHODIMP +SameOriginCheckerImpl::AsyncOnChannelRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aFlags, + nsIAsyncVerifyRedirectCallback* cb) +{ + NS_PRECONDITION(aNewChannel, "Redirecting to null channel?"); + + nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel); + if (NS_SUCCEEDED(rv)) { + cb->OnRedirectVerifyCallback(NS_OK); + } + + return rv; +} + +NS_IMETHODIMP +SameOriginCheckerImpl::GetInterface(const nsIID& aIID, void** aResult) +{ + return QueryInterface(aIID, aResult); +} + +/* static */ +nsresult +nsContentUtils::GetASCIIOrigin(nsIPrincipal* aPrincipal, nsACString& aOrigin) +{ + NS_PRECONDITION(aPrincipal, "missing principal"); + + aOrigin.Truncate(); + + nsCOMPtr<nsIURI> uri; + nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (uri) { + return GetASCIIOrigin(uri, aOrigin); + } + + aOrigin.AssignLiteral("null"); + + return NS_OK; +} + +/* static */ +nsresult +nsContentUtils::GetASCIIOrigin(nsIURI* aURI, nsACString& aOrigin) +{ + NS_PRECONDITION(aURI, "missing uri"); + + // For Blob URI we have to return the origin of page using its principal. + nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(aURI); + if (uriWithPrincipal) { + nsCOMPtr<nsIPrincipal> principal; + uriWithPrincipal->GetPrincipal(getter_AddRefs(principal)); + + if (principal) { + nsCOMPtr<nsIURI> uri; + nsresult rv = principal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (uri && uri != aURI) { + return GetASCIIOrigin(uri, aOrigin); + } + } + } + + aOrigin.Truncate(); + + nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + nsCString host; + nsresult rv = uri->GetAsciiHost(host); + + if (NS_SUCCEEDED(rv) && !host.IsEmpty()) { + nsCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t port = -1; + uri->GetPort(&port); + if (port != -1 && port == NS_GetDefaultPort(scheme.get())) + port = -1; + + nsCString hostPort; + rv = NS_GenerateHostPort(host, port, hostPort); + NS_ENSURE_SUCCESS(rv, rv); + + aOrigin = scheme + NS_LITERAL_CSTRING("://") + hostPort; + } + else { + aOrigin.AssignLiteral("null"); + } + + return NS_OK; +} + +/* static */ +nsresult +nsContentUtils::GetUTFOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin) +{ + NS_PRECONDITION(aPrincipal, "missing principal"); + + aOrigin.Truncate(); + + nsCOMPtr<nsIURI> uri; + nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (uri) { + return GetUTFOrigin(uri, aOrigin); + } + + aOrigin.AssignLiteral("null"); + + return NS_OK; +} + +/* static */ +nsresult +nsContentUtils::GetUTFOrigin(nsIURI* aURI, nsAString& aOrigin) +{ + NS_PRECONDITION(aURI, "missing uri"); + + // For Blob URI we have to return the origin of page using its principal. + nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(aURI); + if (uriWithPrincipal) { + nsCOMPtr<nsIPrincipal> principal; + uriWithPrincipal->GetPrincipal(getter_AddRefs(principal)); + + if (principal) { + nsCOMPtr<nsIURI> uri; + nsresult rv = principal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (uri && uri != aURI) { + return GetUTFOrigin(uri, aOrigin); + } + } else { + // We are probably dealing with an unknown blob URL. + bool isBlobURL = false; + nsresult rv = aURI->SchemeIs(BLOBURI_SCHEME, &isBlobURL); + NS_ENSURE_SUCCESS(rv, rv); + + if (isBlobURL) { + nsAutoCString path; + rv = aURI->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), path); + if (NS_FAILED(rv)) { + aOrigin.AssignLiteral("null"); + return NS_OK; + } + + return GetUTFOrigin(uri, aOrigin); + } + } + } + + aOrigin.Truncate(); + + nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + nsCString host; + nsresult rv = uri->GetHost(host); + + if (NS_SUCCEEDED(rv) && !host.IsEmpty()) { + nsCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t port = -1; + uri->GetPort(&port); + if (port != -1 && port == NS_GetDefaultPort(scheme.get())) + port = -1; + + nsCString hostPort; + rv = NS_GenerateHostPort(host, port, hostPort); + NS_ENSURE_SUCCESS(rv, rv); + + aOrigin = NS_ConvertUTF8toUTF16( + scheme + NS_LITERAL_CSTRING("://") + hostPort); + } + else { + aOrigin.AssignLiteral("null"); + } + + return NS_OK; +} + +/* static */ +bool +nsContentUtils::CheckMayLoad(nsIPrincipal* aPrincipal, nsIChannel* aChannel, bool aAllowIfInheritsPrincipal) +{ + nsCOMPtr<nsIURI> channelURI; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); + NS_ENSURE_SUCCESS(rv, false); + + return NS_SUCCEEDED(aPrincipal->CheckMayLoad(channelURI, false, aAllowIfInheritsPrincipal)); +} + +nsContentTypeParser::nsContentTypeParser(const nsAString& aString) + : mString(aString), mService(nullptr) +{ + CallGetService("@mozilla.org/network/mime-hdrparam;1", &mService); +} + +nsContentTypeParser::~nsContentTypeParser() +{ + NS_IF_RELEASE(mService); +} + +nsresult +nsContentTypeParser::GetParameter(const char* aParameterName, + nsAString& aResult) const +{ + NS_ENSURE_TRUE(mService, NS_ERROR_FAILURE); + return mService->GetParameterHTTP(mString, aParameterName, + EmptyCString(), false, nullptr, + aResult); +} + +nsresult +nsContentTypeParser::GetType(nsAString& aResult) const +{ + nsresult rv = GetParameter(nullptr, aResult); + NS_ENSURE_SUCCESS(rv, rv); + nsContentUtils::ASCIIToLower(aResult); + return NS_OK; +} + +/* static */ + +bool +nsContentUtils::CanAccessNativeAnon() +{ + return LegacyIsCallerChromeOrNativeCode() || IsCallerContentXBL(); +} + +/* static */ nsresult +nsContentUtils::DispatchXULCommand(nsIContent* aTarget, + bool aTrusted, + nsIDOMEvent* aSourceEvent, + nsIPresShell* aShell, + bool aCtrl, + bool aAlt, + bool aShift, + bool aMeta) +{ + NS_ENSURE_STATE(aTarget); + nsIDocument* doc = aTarget->OwnerDoc(); + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc); + NS_ENSURE_STATE(domDoc); + nsCOMPtr<nsIDOMEvent> event; + domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), + getter_AddRefs(event)); + nsCOMPtr<nsIDOMXULCommandEvent> xulCommand = do_QueryInterface(event); + nsresult rv = xulCommand->InitCommandEvent(NS_LITERAL_STRING("command"), + true, true, doc->GetInnerWindow(), + 0, aCtrl, aAlt, aShift, aMeta, + aSourceEvent); + NS_ENSURE_SUCCESS(rv, rv); + + if (aShell) { + nsEventStatus status = nsEventStatus_eIgnore; + nsCOMPtr<nsIPresShell> kungFuDeathGrip = aShell; + return aShell->HandleDOMEventWithTarget(aTarget, event, &status); + } + + nsCOMPtr<EventTarget> target = do_QueryInterface(aTarget); + NS_ENSURE_STATE(target); + bool dummy; + return target->DispatchEvent(event, &dummy); +} + +// static +nsresult +nsContentUtils::WrapNative(JSContext *cx, nsISupports *native, + nsWrapperCache *cache, const nsIID* aIID, + JS::MutableHandle<JS::Value> vp, bool aAllowWrapping) +{ + MOZ_ASSERT(cx == GetCurrentJSContext()); + + if (!native) { + vp.setNull(); + + return NS_OK; + } + + JSObject *wrapper = xpc_FastGetCachedWrapper(cx, cache, vp); + if (wrapper) { + return NS_OK; + } + + NS_ENSURE_TRUE(sXPConnect, NS_ERROR_UNEXPECTED); + + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + nsresult rv = NS_OK; + JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx)); + rv = sXPConnect->WrapNativeToJSVal(cx, scope, native, cache, aIID, + aAllowWrapping, vp); + return rv; +} + +nsresult +nsContentUtils::CreateArrayBuffer(JSContext *aCx, const nsACString& aData, + JSObject** aResult) +{ + if (!aCx) { + return NS_ERROR_FAILURE; + } + + int32_t dataLen = aData.Length(); + *aResult = JS_NewArrayBuffer(aCx, dataLen); + if (!*aResult) { + return NS_ERROR_FAILURE; + } + + if (dataLen > 0) { + NS_ASSERTION(JS_IsArrayBufferObject(*aResult), "What happened?"); + JS::AutoCheckCannotGC nogc; + bool isShared; + memcpy(JS_GetArrayBufferData(*aResult, &isShared, nogc), aData.BeginReading(), dataLen); + MOZ_ASSERT(!isShared); + } + + return NS_OK; +} + +void +nsContentUtils::StripNullChars(const nsAString& aInStr, nsAString& aOutStr) +{ + // In common cases where we don't have nulls in the + // string we can simple simply bypass the checking code. + int32_t firstNullPos = aInStr.FindChar('\0'); + if (firstNullPos == kNotFound) { + aOutStr.Assign(aInStr); + return; + } + + aOutStr.SetCapacity(aInStr.Length() - 1); + nsAString::const_iterator start, end; + aInStr.BeginReading(start); + aInStr.EndReading(end); + while (start != end) { + if (*start != '\0') + aOutStr.Append(*start); + ++start; + } +} + +struct ClassMatchingInfo { + nsAttrValue::AtomArray mClasses; + nsCaseTreatment mCaseTreatment; +}; + +// static +bool +nsContentUtils::MatchClassNames(nsIContent* aContent, int32_t aNamespaceID, + nsIAtom* aAtom, void* aData) +{ + // We can't match if there are no class names + const nsAttrValue* classAttr = aContent->GetClasses(); + if (!classAttr) { + return false; + } + + // need to match *all* of the classes + ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData); + uint32_t length = info->mClasses.Length(); + if (!length) { + // If we actually had no classes, don't match. + return false; + } + uint32_t i; + for (i = 0; i < length; ++i) { + if (!classAttr->Contains(info->mClasses[i], + info->mCaseTreatment)) { + return false; + } + } + + return true; +} + +// static +void +nsContentUtils::DestroyClassNameArray(void* aData) +{ + ClassMatchingInfo* info = static_cast<ClassMatchingInfo*>(aData); + delete info; +} + +// static +void* +nsContentUtils::AllocClassMatchingInfo(nsINode* aRootNode, + const nsString* aClasses) +{ + nsAttrValue attrValue; + attrValue.ParseAtomArray(*aClasses); + // nsAttrValue::Equals is sensitive to order, so we'll send an array + ClassMatchingInfo* info = new ClassMatchingInfo; + if (attrValue.Type() == nsAttrValue::eAtomArray) { + info->mClasses.SwapElements(*(attrValue.GetAtomArrayValue())); + } else if (attrValue.Type() == nsAttrValue::eAtom) { + info->mClasses.AppendElement(attrValue.GetAtomValue()); + } + + info->mCaseTreatment = + aRootNode->OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks ? + eIgnoreCase : eCaseMatters; + return info; +} + +// static +bool +nsContentUtils::IsFocusedContent(const nsIContent* aContent) +{ + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + + return fm && fm->GetFocusedContent() == aContent; +} + +bool +nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent) +{ + //XXXsmaug Shadow DOM spec issue! + // We may need to change this to GetComposedDoc(). + nsIDocument* doc = aContent->GetUncomposedDoc(); + if (!doc) { + return false; + } + + // If the subdocument lives in another process, the frame is + // tabbable. + if (EventStateManager::IsRemoteTarget(aContent)) { + return true; + } + + // XXXbz should this use OwnerDoc() for GetSubDocumentFor? + // sXBL/XBL2 issue! + nsIDocument* subDoc = doc->GetSubDocumentFor(aContent); + if (!subDoc) { + return false; + } + + nsCOMPtr<nsIDocShell> docShell = subDoc->GetDocShell(); + if (!docShell) { + return false; + } + + nsCOMPtr<nsIContentViewer> contentViewer; + docShell->GetContentViewer(getter_AddRefs(contentViewer)); + if (!contentViewer) { + return false; + } + + nsCOMPtr<nsIContentViewer> zombieViewer; + contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer)); + + // If there are 2 viewers for the current docshell, that + // means the current document is a zombie document. + // Only navigate into the subdocument if it's not a zombie. + return !zombieViewer; +} + +bool +nsContentUtils::IsUserFocusIgnored(nsINode* aNode) +{ + if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) { + return false; + } + + // Check if our mozbrowser iframe ancestors has ignoreuserfocus attribute. + while (aNode) { + nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aNode); + if (browserFrame && + aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ignoreuserfocus) && + browserFrame->GetReallyIsBrowserOrApp()) { + return true; + } + nsPIDOMWindowOuter* win = aNode->OwnerDoc()->GetWindow(); + aNode = win ? win->GetFrameElementInternal() : nullptr; + } + + return false; +} + +bool +nsContentUtils::HasScrollgrab(nsIContent* aContent) +{ + nsGenericHTMLElement* element = nsGenericHTMLElement::FromContentOrNull(aContent); + return element && element->Scrollgrab(); +} + +void +nsContentUtils::FlushLayoutForTree(nsPIDOMWindowOuter* aWindow) +{ + if (!aWindow) { + return; + } + + // Note that because FlushPendingNotifications flushes parents, this + // is O(N^2) in docshell tree depth. However, the docshell tree is + // usually pretty shallow. + + if (nsCOMPtr<nsIDocument> doc = aWindow->GetDoc()) { + doc->FlushPendingNotifications(Flush_Layout); + } + + if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) { + int32_t i = 0, i_end; + docShell->GetChildCount(&i_end); + for (; i < i_end; ++i) { + nsCOMPtr<nsIDocShellTreeItem> item; + docShell->GetChildAt(i, getter_AddRefs(item)); + if (nsCOMPtr<nsPIDOMWindowOuter> win = item->GetWindow()) { + FlushLayoutForTree(win); + } + } + } +} + +void nsContentUtils::RemoveNewlines(nsString &aString) +{ + // strip CR/LF and null + static const char badChars[] = {'\r', '\n', 0}; + aString.StripChars(badChars); +} + +void +nsContentUtils::PlatformToDOMLineBreaks(nsString &aString) +{ + if (!PlatformToDOMLineBreaks(aString, fallible)) { + aString.AllocFailed(aString.Length()); + } +} + +bool +nsContentUtils::PlatformToDOMLineBreaks(nsString& aString, const fallible_t& aFallible) +{ + if (aString.FindChar(char16_t('\r')) != -1) { + // Windows linebreaks: Map CRLF to LF: + if (!aString.ReplaceSubstring(u"\r\n", u"\n", aFallible)) { + return false; + } + + // Mac linebreaks: Map any remaining CR to LF: + if (!aString.ReplaceSubstring(u"\r", u"\n", aFallible)) { + return false; + } + } + + return true; +} + +void +nsContentUtils::PopulateStringFromStringBuffer(nsStringBuffer* aBuf, + nsAString& aResultString) +{ + MOZ_ASSERT(aBuf, "Expecting a non-null string buffer"); + + uint32_t stringLen = NS_strlen(static_cast<char16_t*>(aBuf->Data())); + + // SANITY CHECK: In case the nsStringBuffer isn't correctly + // null-terminated, let's clamp its length using the allocated size, to be + // sure the resulting string doesn't sample past the end of the the buffer. + // (Note that StorageSize() is in units of bytes, so we have to convert that + // to units of PRUnichars, and subtract 1 for the null-terminator.) + uint32_t allocStringLen = (aBuf->StorageSize() / sizeof(char16_t)) - 1; + MOZ_ASSERT(stringLen <= allocStringLen, + "string buffer lacks null terminator!"); + stringLen = std::min(stringLen, allocStringLen); + + aBuf->ToString(stringLen, aResultString); +} + +nsIPresShell* +nsContentUtils::FindPresShellForDocument(const nsIDocument* aDoc) +{ + const nsIDocument* doc = aDoc; + nsIDocument* displayDoc = doc->GetDisplayDocument(); + if (displayDoc) { + doc = displayDoc; + } + + nsIPresShell* shell = doc->GetShell(); + if (shell) { + return shell; + } + + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell(); + while (docShellTreeItem) { + // We may be in a display:none subdocument, or we may not have a presshell + // created yet. + // Walk the docshell tree to find the nearest container that has a presshell, + // and return that. + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(docShellTreeItem); + nsIPresShell* presShell = docShell->GetPresShell(); + if (presShell) { + return presShell; + } + nsCOMPtr<nsIDocShellTreeItem> parent; + docShellTreeItem->GetParent(getter_AddRefs(parent)); + docShellTreeItem = parent; + } + + return nullptr; +} + +nsIWidget* +nsContentUtils::WidgetForDocument(const nsIDocument* aDoc) +{ + nsIPresShell* shell = FindPresShellForDocument(aDoc); + if (shell) { + nsViewManager* VM = shell->GetViewManager(); + if (VM) { + nsView* rootView = VM->GetRootView(); + if (rootView) { + nsView* displayRoot = nsViewManager::GetDisplayRootFor(rootView); + if (displayRoot) { + return displayRoot->GetNearestWidget(nullptr); + } + } + } + } + + return nullptr; +} + +static already_AddRefed<LayerManager> +LayerManagerForDocumentInternal(const nsIDocument *aDoc, bool aRequirePersistent) +{ + nsIWidget *widget = nsContentUtils::WidgetForDocument(aDoc); + if (widget) { + RefPtr<LayerManager> manager = + widget->GetLayerManager(aRequirePersistent ? nsIWidget::LAYER_MANAGER_PERSISTENT : + nsIWidget::LAYER_MANAGER_CURRENT); + return manager.forget(); + } + + return nullptr; +} + +already_AddRefed<LayerManager> +nsContentUtils::LayerManagerForDocument(const nsIDocument *aDoc) +{ + return LayerManagerForDocumentInternal(aDoc, false); +} + +already_AddRefed<LayerManager> +nsContentUtils::PersistentLayerManagerForDocument(nsIDocument *aDoc) +{ + return LayerManagerForDocumentInternal(aDoc, true); +} + +bool +nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal) +{ + if (IsSystemPrincipal(aPrincipal)) { + return true; + } + + nsCOMPtr<nsIURI> princURI; + aPrincipal->GetURI(getter_AddRefs(princURI)); + + return princURI && + ((sAllowXULXBL_for_file && SchemeIs(princURI, "file")) || + IsSitePermAllow(aPrincipal, "allowXULXBL")); +} + +bool +nsContentUtils::IsPDFJSEnabled() +{ + nsCOMPtr<nsIStreamConverterService> convServ = + do_GetService("@mozilla.org/streamConverters;1"); + nsresult rv = NS_ERROR_FAILURE; + bool canConvert = false; + if (convServ) { + rv = convServ->CanConvert("application/pdf", "text/html", &canConvert); + } + return NS_SUCCEEDED(rv) && canConvert; +} + +bool +nsContentUtils::IsSWFPlayerEnabled() +{ + nsCOMPtr<nsIStreamConverterService> convServ = + do_GetService("@mozilla.org/streamConverters;1"); + nsresult rv = NS_ERROR_FAILURE; + bool canConvert = false; + if (convServ) { + rv = convServ->CanConvert("application/x-shockwave-flash", + "text/html", &canConvert); + } + return NS_SUCCEEDED(rv) && canConvert; +} + +already_AddRefed<nsIDocumentLoaderFactory> +nsContentUtils::FindInternalContentViewer(const nsACString& aType, + ContentViewerType* aLoaderType) +{ + if (aLoaderType) { + *aLoaderType = TYPE_UNSUPPORTED; + } + + // one helper factory, please + nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + if (!catMan) + return nullptr; + + nsCOMPtr<nsIDocumentLoaderFactory> docFactory; + + nsXPIDLCString contractID; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", + PromiseFlatCString(aType).get(), + getter_Copies(contractID)); + if (NS_SUCCEEDED(rv)) { + docFactory = do_GetService(contractID); + if (docFactory && aLoaderType) { + if (contractID.EqualsLiteral(CONTENT_DLF_CONTRACTID)) + *aLoaderType = TYPE_CONTENT; + else if (contractID.EqualsLiteral(PLUGIN_DLF_CONTRACTID)) + *aLoaderType = TYPE_PLUGIN; + else + *aLoaderType = TYPE_UNKNOWN; + } + return docFactory.forget(); + } + + if (DecoderTraits::IsSupportedInVideoDocument(aType)) { + docFactory = do_GetService("@mozilla.org/content/document-loader-factory;1"); + if (docFactory && aLoaderType) { + *aLoaderType = TYPE_CONTENT; + } + return docFactory.forget(); + } + + return nullptr; +} + +static void +ReportPatternCompileFailure(nsAString& aPattern, nsIDocument* aDocument, + JSContext* cx) +{ + MOZ_ASSERT(JS_IsExceptionPending(cx)); + + JS::RootedValue exn(cx); + if (!JS_GetPendingException(cx, &exn)) { + return; + } + if (!exn.isObject()) { + // If pending exception is not an object, it should be OOM. + return; + } + + JS::AutoSaveExceptionState savedExc(cx); + JS::RootedObject exnObj(cx, &exn.toObject()); + JS::RootedValue messageVal(cx); + if (!JS_GetProperty(cx, exnObj, "message", &messageVal)) { + return; + } + MOZ_ASSERT(messageVal.isString()); + + JS::RootedString messageStr(cx, messageVal.toString()); + MOZ_ASSERT(messageStr); + + nsAutoString wideMessage; + if (!AssignJSString(cx, wideMessage, messageStr)) { + return; + } + + const nsString& pattern = PromiseFlatString(aPattern); + const char16_t *strings[] = { pattern.get(), wideMessage.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, + NS_LITERAL_CSTRING("DOM"), + aDocument, + nsContentUtils::eDOM_PROPERTIES, + "PatternAttributeCompileFailure", + strings, ArrayLength(strings)); + savedExc.drop(); +} + +// static +bool +nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern, + nsIDocument* aDocument) +{ + NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)"); + + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + // We can use the junk scope here, because we're just using it for + // regexp evaluation, not actual script execution. + JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope()); + + // The pattern has to match the entire value. + aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0); + aPattern.AppendLiteral(")$"); + + JS::Rooted<JSObject*> re(cx, + JS_NewUCRegExpObject(cx, + static_cast<char16_t*>(aPattern.BeginWriting()), + aPattern.Length(), JSREG_UNICODE)); + if (!re) { + // Remove extra patterns added above to report with the original pattern. + aPattern.Cut(0, 4); + aPattern.Cut(aPattern.Length() - 2, 2); + ReportPatternCompileFailure(aPattern, aDocument, cx); + return true; + } + + JS::Rooted<JS::Value> rval(cx, JS::NullValue()); + size_t idx = 0; + if (!JS_ExecuteRegExpNoStatics(cx, re, + static_cast<char16_t*>(aValue.BeginWriting()), + aValue.Length(), &idx, true, &rval)) { + return true; + } + + return !rval.isNull(); +} + +// static +nsresult +nsContentUtils::URIInheritsSecurityContext(nsIURI *aURI, bool *aResult) +{ + // Note: about:blank URIs do NOT inherit the security context from the + // current document, which is what this function tests for... + return NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + aResult); +} + +// static +bool +nsContentUtils::ChannelShouldInheritPrincipal(nsIPrincipal* aLoadingPrincipal, + nsIURI* aURI, + bool aInheritForAboutBlank, + bool aForceInherit) +{ + MOZ_ASSERT(aLoadingPrincipal, "Can not check inheritance without a principal"); + + // Only tell the channel to inherit if it can't provide its own security context. + // + // XXX: If this is ever changed, check all callers for what owners + // they're passing in. In particular, see the code and + // comments in nsDocShell::LoadURI where we fall back on + // inheriting the owner if called from chrome. That would be + // very wrong if this code changed anything but channels that + // can't provide their own security context! + // + // If aForceInherit is true, we will inherit, even for a channel that + // can provide its own security context. This is used for srcdoc loads. + bool inherit = aForceInherit; + if (!inherit) { + bool uriInherits; + // We expect URIInheritsSecurityContext to return success for an + // about:blank URI, so don't call NS_IsAboutBlank() if this call fails. + // This condition needs to match the one in nsDocShell::InternalLoad where + // we're checking for things that will use the owner. + inherit = + (NS_SUCCEEDED(URIInheritsSecurityContext(aURI, &uriInherits)) && + (uriInherits || (aInheritForAboutBlank && NS_IsAboutBlank(aURI)))) || + // + // file: uri special-casing + // + // If this is a file: load opened from another file: then it may need + // to inherit the owner from the referrer so they can script each other. + // If we don't set the owner explicitly then each file: gets an owner + // based on its own codebase later. + // + (URIIsLocalFile(aURI) && + NS_SUCCEEDED(aLoadingPrincipal->CheckMayLoad(aURI, false, false)) && + // One more check here. CheckMayLoad will always return true for the + // system principal, but we do NOT want to inherit in that case. + !IsSystemPrincipal(aLoadingPrincipal)); + } + return inherit; +} + +/* static */ +bool +nsContentUtils::IsFullScreenApiEnabled() +{ + return sIsFullScreenApiEnabled; +} + +/* static */ +bool +nsContentUtils::IsRequestFullScreenAllowed() +{ + return !sTrustedFullScreenOnly || + EventStateManager::IsHandlingUserInput() || + IsCallerChrome(); +} + +/* static */ +bool +nsContentUtils::IsCutCopyAllowed() +{ + if ((!IsCutCopyRestricted() && EventStateManager::IsHandlingUserInput()) || + IsCallerChrome()) { + return true; + } + + return BasePrincipal::Cast(SubjectPrincipal())->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite")); +} + +/* static */ +bool +nsContentUtils::IsFrameTimingEnabled() +{ + return sIsFrameTimingPrefEnabled; +} + +/* static */ +bool +nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2) +{ + if (!aDoc1 || !aDoc2) { + return false; + } + bool principalsEqual = false; + aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual); + return principalsEqual; +} + +/* static */ +bool +nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent) +{ +#ifdef XP_MACOSX + // We control dispatch to all mac plugins. + return false; +#else + if (!aContent || !aContent->IsInUncomposedDoc()) { + return false; + } + + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aContent); + if (!olc) { + return false; + } + + RefPtr<nsNPAPIPluginInstance> plugin; + olc->GetPluginInstance(getter_AddRefs(plugin)); + if (!plugin) { + return false; + } + + bool isWindowless = false; + nsresult res = plugin->IsWindowless(&isWindowless); + if (NS_FAILED(res)) { + return false; + } + + return !isWindowless; +#endif +} + +/* static */ +void +nsContentUtils::FireMutationEventsForDirectParsing(nsIDocument* aDoc, + nsIContent* aDest, + int32_t aOldChildCount) +{ + // Fire mutation events. Optimize for the case when there are no listeners + int32_t newChildCount = aDest->GetChildCount(); + if (newChildCount && nsContentUtils:: + HasMutationListeners(aDoc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) { + AutoTArray<nsCOMPtr<nsIContent>, 50> childNodes; + NS_ASSERTION(newChildCount - aOldChildCount >= 0, + "What, some unexpected dom mutation has happened?"); + childNodes.SetCapacity(newChildCount - aOldChildCount); + for (nsIContent* child = aDest->GetFirstChild(); + child; + child = child->GetNextSibling()) { + childNodes.AppendElement(child); + } + FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes); + } +} + +/* static */ +nsIDocument* +nsContentUtils::GetRootDocument(nsIDocument* aDoc) +{ + if (!aDoc) { + return nullptr; + } + nsIDocument* doc = aDoc; + while (doc->GetParentDocument()) { + doc = doc->GetParentDocument(); + } + return doc; +} + +/* static */ +bool +nsContentUtils::IsInPointerLockContext(nsPIDOMWindowOuter* aWin) +{ + if (!aWin) { + return false; + } + + nsCOMPtr<nsIDocument> pointerLockedDoc = + do_QueryReferent(EventStateManager::sPointerLockedDoc); + if (!pointerLockedDoc || !pointerLockedDoc->GetWindow()) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> lockTop = + pointerLockedDoc->GetWindow()->GetScriptableTop(); + nsCOMPtr<nsPIDOMWindowOuter> top = aWin->GetScriptableTop(); + + return top == lockTop; +} + +// static +int32_t +nsContentUtils::GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame, + int32_t aOffset) +{ + // The structure of the anonymous frames within a text control frame is + // an optional block frame, followed by an optional br frame. + + // If the offset frame has a child, then this frame is the block which + // has the text frames (containing the content) as its children. This will + // be the case if we click to the right of any of the text frames, or at the + // bottom of the text area. + nsIFrame* firstChild = aOffsetFrame->PrincipalChildList().FirstChild(); + if (firstChild) { + // In this case, the passed-in offset is incorrect, and we want the length + // of the entire content in the text control frame. + return firstChild->GetContent()->Length(); + } + + if (aOffsetFrame->GetPrevSibling() && + !aOffsetFrame->GetNextSibling()) { + // In this case, we're actually within the last frame, which is a br + // frame. Our offset should therefore be the length of the first child of + // our parent. + int32_t aOutOffset = + aOffsetFrame->GetParent()->PrincipalChildList().FirstChild()->GetContent()->Length(); + return aOutOffset; + } + + // Otherwise, we're within one of the text frames, in which case our offset + // has already been correctly calculated. + return aOffset; +} + +// static +void +nsContentUtils::GetSelectionInTextControl(Selection* aSelection, + Element* aRoot, + int32_t& aOutStartOffset, + int32_t& aOutEndOffset) +{ + MOZ_ASSERT(aSelection && aRoot); + + if (!aSelection->RangeCount()) { + // Nothing selected + aOutStartOffset = aOutEndOffset = 0; + return; + } + + nsCOMPtr<nsINode> anchorNode = aSelection->GetAnchorNode(); + uint32_t anchorOffset = aSelection->AnchorOffset(); + nsCOMPtr<nsINode> focusNode = aSelection->GetFocusNode(); + uint32_t focusOffset = aSelection->FocusOffset(); + + // We have at most two children, consisting of an optional text node followed + // by an optional <br>. + NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children"); + nsCOMPtr<nsIContent> firstChild = aRoot->GetFirstChild(); +#ifdef DEBUG + nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild(); + NS_ASSERTION(anchorNode == aRoot || anchorNode == firstChild || + anchorNode == lastChild, "Unexpected anchorNode"); + NS_ASSERTION(focusNode == aRoot || focusNode == firstChild || + focusNode == lastChild, "Unexpected focusNode"); +#endif + if (!firstChild || !firstChild->IsNodeOfType(nsINode::eTEXT)) { + // No text node, so everything is 0 + anchorOffset = focusOffset = 0; + } else { + // First child is text. If the anchor/focus is already in the text node, + // or the start of the root node, no change needed. If it's in the root + // node but not the start, or in the trailing <br>, we need to set the + // offset to the end. + if ((anchorNode == aRoot && anchorOffset != 0) || + (anchorNode != aRoot && anchorNode != firstChild)) { + anchorOffset = firstChild->Length(); + } + if ((focusNode == aRoot && focusOffset != 0) || + (focusNode != aRoot && focusNode != firstChild)) { + focusOffset = firstChild->Length(); + } + } + + // Make sure aOutStartOffset <= aOutEndOffset. + aOutStartOffset = std::min(anchorOffset, focusOffset); + aOutEndOffset = std::max(anchorOffset, focusOffset); +} + + +nsIEditor* +nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext) +{ + nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell()); + bool isEditable; + if (!docShell || + NS_FAILED(docShell->GetEditable(&isEditable)) || !isEditable) + return nullptr; + + nsCOMPtr<nsIEditor> editor; + docShell->GetEditor(getter_AddRefs(editor)); + return editor; +} + +bool +nsContentUtils::IsContentInsertionPoint(nsIContent* aContent) +{ + // Check if the content is a XBL insertion point. + if (aContent->IsActiveChildrenElement()) { + return true; + } + + // Check if the content is a web components content insertion point. + HTMLContentElement* contentElement = + HTMLContentElement::FromContent(aContent); + return contentElement && contentElement->IsInsertionPoint(); +} + +// static +bool +nsContentUtils::HasDistributedChildren(nsIContent* aContent) +{ + if (!aContent) { + return false; + } + + if (aContent->GetShadowRoot()) { + // Children of a shadow root host are distributed + // to content insertion points in the shadow root. + return true; + } + + ShadowRoot* shadow = ShadowRoot::FromNode(aContent); + if (shadow) { + // Children of a shadow root are distributed to + // the shadow insertion point of the younger shadow root. + return shadow->GetYoungerShadowRoot(); + } + + HTMLShadowElement* shadowEl = HTMLShadowElement::FromContent(aContent); + if (shadowEl && shadowEl->IsInsertionPoint()) { + // Children of a shadow insertion points are distributed + // to the insertion points in the older shadow root. + return shadowEl->GetOlderShadowRoot(); + } + + HTMLContentElement* contentEl = HTMLContentElement::FromContent(aContent); + if (contentEl && contentEl->IsInsertionPoint()) { + // Children of a content insertion point are distributed to the + // content insertion point if the content insertion point does + // not match any nodes (fallback content). + return contentEl->MatchedNodes().IsEmpty(); + } + + return false; +} + +// static +bool +nsContentUtils::IsForbiddenRequestHeader(const nsACString& aHeader) +{ + if (IsForbiddenSystemRequestHeader(aHeader)) { + return true; + } + + return StringBeginsWith(aHeader, NS_LITERAL_CSTRING("proxy-"), + nsCaseInsensitiveCStringComparator()) || + StringBeginsWith(aHeader, NS_LITERAL_CSTRING("sec-"), + nsCaseInsensitiveCStringComparator()); +} + +// static +bool +nsContentUtils::IsForbiddenSystemRequestHeader(const nsACString& aHeader) +{ + static const char *kInvalidHeaders[] = { + "accept-charset", "accept-encoding", "access-control-request-headers", + "access-control-request-method", "connection", "content-length", + "cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive", + "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "via" + }; + for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) { + if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) { + return true; + } + } + return false; +} + +// static +bool +nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader) +{ + return (aHeader.LowerCaseEqualsASCII("set-cookie") || + aHeader.LowerCaseEqualsASCII("set-cookie2")); +} + +// static +bool +nsContentUtils::IsAllowedNonCorsContentType(const nsACString& aHeaderValue) +{ + nsAutoCString contentType; + nsAutoCString unused; + + nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused); + if (NS_FAILED(rv)) { + return false; + } + + return contentType.LowerCaseEqualsLiteral("text/plain") || + contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") || + contentType.LowerCaseEqualsLiteral("multipart/form-data"); +} + +bool +nsContentUtils::DOMWindowDumpEnabled() +{ +#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) + // In optimized builds we check a pref that controls if we should + // enable output from dump() or not, in debug builds it's always + // enabled. + return nsContentUtils::sDOMWindowDumpEnabled; +#else + return true; +#endif +} + +bool +nsContentUtils::DoNotTrackEnabled() +{ + return nsContentUtils::sDoNotTrackEnabled; +} + +mozilla::LogModule* +nsContentUtils::DOMDumpLog() +{ + return sDOMDumpLog; +} + +bool +nsContentUtils::GetNodeTextContent(nsINode* aNode, bool aDeep, nsAString& aResult, + const fallible_t& aFallible) +{ + aResult.Truncate(); + return AppendNodeTextContent(aNode, aDeep, aResult, aFallible); +} + +void +nsContentUtils::GetNodeTextContent(nsINode* aNode, bool aDeep, nsAString& aResult) +{ + if (!GetNodeTextContent(aNode, aDeep, aResult, fallible)) { + NS_ABORT_OOM(0); // Unfortunately we don't know the allocation size + } +} + +void +nsContentUtils::DestroyMatchString(void* aData) +{ + if (aData) { + nsString* matchString = static_cast<nsString*>(aData); + delete matchString; + } +} + +bool +nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) +{ + // Table ordered from most to least likely JS MIME types. + static const char* jsTypes[] = { + "text/javascript", + "text/ecmascript", + "application/javascript", + "application/ecmascript", + "application/x-javascript", + "application/x-ecmascript", + "text/javascript1.0", + "text/javascript1.1", + "text/javascript1.2", + "text/javascript1.3", + "text/javascript1.4", + "text/javascript1.5", + "text/jscript", + "text/livescript", + "text/x-ecmascript", + "text/x-javascript", + nullptr + }; + + for (uint32_t i = 0; jsTypes[i]; ++i) { + if (aMIMEType.LowerCaseEqualsASCII(jsTypes[i])) { + return true; + } + } + + return false; +} + +nsresult +nsContentUtils::GenerateUUIDInPlace(nsID& aUUID) +{ + MOZ_ASSERT(sUUIDGenerator); + + nsresult rv = sUUIDGenerator->GenerateUUIDInPlace(&aUUID); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +bool +nsContentUtils::PrefetchEnabled(nsIDocShell* aDocShell) +{ + // + // SECURITY CHECK: disable prefetching from mailnews! + // + // walk up the docshell tree to see if any containing + // docshell are of type MAIL. + // + + if (!aDocShell) { + return false; + } + + nsCOMPtr<nsIDocShell> docshell = aDocShell; + nsCOMPtr<nsIDocShellTreeItem> parentItem; + + do { + uint32_t appType = 0; + nsresult rv = docshell->GetAppType(&appType); + if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL) { + return false; // do not prefetch, preconnect from mailnews + } + + docshell->GetParent(getter_AddRefs(parentItem)); + if (parentItem) { + docshell = do_QueryInterface(parentItem); + if (!docshell) { + NS_ERROR("cannot get a docshell from a treeItem!"); + return false; + } + } + } while (parentItem); + + return true; +} + +uint64_t +nsContentUtils::GetInnerWindowID(nsIRequest* aRequest) +{ + // can't do anything if there's no nsIRequest! + if (!aRequest) { + return 0; + } + + nsCOMPtr<nsILoadGroup> loadGroup; + nsresult rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup)); + + if (NS_FAILED(rv) || !loadGroup) { + return 0; + } + + nsCOMPtr<nsIInterfaceRequestor> callbacks; + rv = loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (NS_FAILED(rv) || !callbacks) { + return 0; + } + + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); + if (!loadContext) { + return 0; + } + + nsCOMPtr<mozIDOMWindowProxy> window; + rv = loadContext->GetAssociatedWindow(getter_AddRefs(window)); + if (NS_FAILED(rv) || !window) { + return 0; + } + + auto* pwindow = nsPIDOMWindowOuter::From(window); + if (!pwindow) { + return 0; + } + + nsPIDOMWindowInner* inner = pwindow->GetCurrentInnerWindow(); + return inner ? inner->WindowID() : 0; +} + +nsresult +nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsCString& aHost) +{ + aHost.Truncate(); + nsresult rv = aURI->GetHost(aHost); + if (NS_FAILED(rv)) { // Some URIs do not have a host + return rv; + } + + if (aHost.FindChar(':') != -1) { // Escape IPv6 address + MOZ_ASSERT(!aHost.Length() || + (aHost[0] !='[' && aHost[aHost.Length() - 1] != ']')); + aHost.Insert('[', 0); + aHost.Append(']'); + } + + return NS_OK; +} + +nsresult +nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost) +{ + nsAutoCString hostname; + nsresult rv = GetHostOrIPv6WithBrackets(aURI, hostname); + if (NS_FAILED(rv)) { + return rv; + } + CopyUTF8toUTF16(hostname, aHost); + return NS_OK; +} + +bool +nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager, + CallOnRemoteChildFunction aCallback, + void* aArg) +{ + uint32_t tabChildCount = 0; + aManager->GetChildCount(&tabChildCount); + for (uint32_t j = 0; j < tabChildCount; ++j) { + nsCOMPtr<nsIMessageListenerManager> childMM; + aManager->GetChildAt(j, getter_AddRefs(childMM)); + if (!childMM) { + continue; + } + + nsCOMPtr<nsIMessageBroadcaster> nonLeafMM = do_QueryInterface(childMM); + if (nonLeafMM) { + if (CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg)) { + return true; + } + continue; + } + + nsCOMPtr<nsIMessageSender> tabMM = do_QueryInterface(childMM); + + mozilla::dom::ipc::MessageManagerCallback* cb = + static_cast<nsFrameMessageManager*>(tabMM.get())->GetCallback(); + if (cb) { + nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb); + TabParent* remote = TabParent::GetFrom(fl); + if (remote && aCallback) { + if (aCallback(remote, aArg)) { + return true; + } + } + } + } + + return false; +} + +void +nsContentUtils::CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow, + CallOnRemoteChildFunction aCallback, + void* aArg) +{ + nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(aWindow)); + if (chromeWindow) { + nsCOMPtr<nsIMessageBroadcaster> windowMM; + chromeWindow->GetMessageManager(getter_AddRefs(windowMM)); + if (windowMM) { + CallOnAllRemoteChildren(windowMM, aCallback, aArg); + } + } +} + +struct UIStateChangeInfo { + UIStateChangeType mShowAccelerators; + UIStateChangeType mShowFocusRings; + + UIStateChangeInfo(UIStateChangeType aShowAccelerators, + UIStateChangeType aShowFocusRings) + : mShowAccelerators(aShowAccelerators), + mShowFocusRings(aShowFocusRings) + {} +}; + +bool +SetKeyboardIndicatorsChild(TabParent* aParent, void* aArg) +{ + UIStateChangeInfo* stateInfo = static_cast<UIStateChangeInfo*>(aArg); + Unused << aParent->SendSetKeyboardIndicators(stateInfo->mShowAccelerators, + stateInfo->mShowFocusRings); + return false; +} + +void +nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(nsPIDOMWindowOuter* aWindow, + UIStateChangeType aShowAccelerators, + UIStateChangeType aShowFocusRings) +{ + UIStateChangeInfo stateInfo(aShowAccelerators, aShowFocusRings); + CallOnAllRemoteChildren(aWindow, SetKeyboardIndicatorsChild, + (void *)&stateInfo); +} + +nsresult +nsContentUtils::IPCTransferableToTransferable(const IPCDataTransfer& aDataTransfer, + const bool& aIsPrivateData, + nsIPrincipal* aRequestingPrincipal, + nsITransferable* aTransferable, + mozilla::dom::nsIContentParent* aContentParent, + mozilla::dom::TabChild* aTabChild) +{ + nsresult rv; + + const nsTArray<IPCDataTransferItem>& items = aDataTransfer.items(); + for (const auto& item : items) { + aTransferable->AddDataFlavor(item.flavor().get()); + + if (item.data().type() == IPCDataTransferData::TnsString) { + nsCOMPtr<nsISupportsString> dataWrapper = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + const nsString& text = item.data().get_nsString(); + rv = dataWrapper->SetData(text); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper, + text.Length() * sizeof(char16_t)); + + NS_ENSURE_SUCCESS(rv, rv); + } else if (item.data().type() == IPCDataTransferData::TShmem) { + if (nsContentUtils::IsFlavorImage(item.flavor())) { + nsCOMPtr<imgIContainer> imageContainer; + rv = nsContentUtils::DataTransferItemToImage(item, + getter_AddRefs(imageContainer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsInterfacePointer> imgPtr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID); + NS_ENSURE_TRUE(imgPtr, NS_ERROR_FAILURE); + + rv = imgPtr->SetData(imageContainer); + NS_ENSURE_SUCCESS(rv, rv); + + aTransferable->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*)); + } else { + nsCOMPtr<nsISupportsCString> dataWrapper = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // The buffer contains the terminating null. + Shmem itemData = item.data().get_Shmem(); + const nsDependentCString text(itemData.get<char>(), + itemData.Size<char>()); + rv = dataWrapper->SetData(text); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper, text.Length()); + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aContentParent) { + Unused << aContentParent->DeallocShmem(item.data().get_Shmem()); + } else if (aTabChild) { + Unused << aTabChild->DeallocShmem(item.data().get_Shmem()); + } + } + } + + aTransferable->SetIsPrivateData(aIsPrivateData); + aTransferable->SetRequestingPrincipal(aRequestingPrincipal); + return NS_OK; +} + +void +nsContentUtils::TransferablesToIPCTransferables(nsIArray* aTransferables, + nsTArray<IPCDataTransfer>& aIPC, + bool aInSyncMessage, + mozilla::dom::nsIContentChild* aChild, + mozilla::dom::nsIContentParent* aParent) +{ + aIPC.Clear(); + if (aTransferables) { + uint32_t transferableCount = 0; + aTransferables->GetLength(&transferableCount); + for (uint32_t i = 0; i < transferableCount; ++i) { + IPCDataTransfer* dt = aIPC.AppendElement(); + nsCOMPtr<nsITransferable> transferable = do_QueryElementAt(aTransferables, i); + TransferableToIPCTransferable(transferable, dt, aInSyncMessage, aChild, aParent); + } + } +} + +nsresult +nsContentUtils::SlurpFileToString(nsIFile* aFile, nsACString& aString) +{ + aString.Truncate(); + + nsCOMPtr<nsIURI> fileURI; + nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + fileURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream; + rv = channel->Open2(getter_AddRefs(stream)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_ConsumeStream(stream, UINT32_MAX, aString); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Close(); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +bool +nsContentUtils::IsFileImage(nsIFile* aFile, nsACString& aType) +{ + nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1"); + if (!mime) { + return false; + } + + nsresult rv = mime->GetTypeFromFile(aFile, aType); + if (NS_FAILED(rv)) { + return false; + } + + return StringBeginsWith(aType, NS_LITERAL_CSTRING("image/")); +} + +nsresult +nsContentUtils::DataTransferItemToImage(const IPCDataTransferItem& aItem, + imgIContainer** aContainer) +{ + MOZ_ASSERT(aItem.data().type() == IPCDataTransferData::TShmem); + MOZ_ASSERT(IsFlavorImage(aItem.flavor())); + + const IPCDataTransferImage& imageDetails = aItem.imageDetails(); + const IntSize size(imageDetails.width(), imageDetails.height()); + if (!size.width || !size.height) { + return NS_ERROR_FAILURE; + } + + Shmem data = aItem.data().get_Shmem(); + + RefPtr<DataSourceSurface> image = + CreateDataSourceSurfaceFromData(size, + static_cast<SurfaceFormat>(imageDetails.format()), + data.get<uint8_t>(), + imageDetails.stride()); + + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size); + nsCOMPtr<imgIContainer> imageContainer = + image::ImageOps::CreateFromDrawable(drawable); + imageContainer.forget(aContainer); + + return NS_OK; +} + +bool +nsContentUtils::IsFlavorImage(const nsACString& aFlavor) +{ + return aFlavor.EqualsLiteral(kNativeImageMime) || + aFlavor.EqualsLiteral(kJPEGImageMime) || + aFlavor.EqualsLiteral(kJPGImageMime) || + aFlavor.EqualsLiteral(kPNGImageMime) || + aFlavor.EqualsLiteral(kGIFImageMime); +} + +static Shmem +ConvertToShmem(mozilla::dom::nsIContentChild* aChild, + mozilla::dom::nsIContentParent* aParent, + const nsACString& aInput) +{ + MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent)); + + IShmemAllocator* allocator = + aChild ? static_cast<IShmemAllocator*>(aChild) + : static_cast<IShmemAllocator*>(aParent); + + Shmem result; + if (!allocator->AllocShmem(aInput.Length() + 1, + SharedMemory::TYPE_BASIC, + &result)) { + return result; + } + + memcpy(result.get<char>(), + aInput.BeginReading(), + aInput.Length() + 1); + + return result; +} + +void +nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable, + IPCDataTransfer* aIPCDataTransfer, + bool aInSyncMessage, + mozilla::dom::nsIContentChild* aChild, + mozilla::dom::nsIContentParent* aParent) +{ + MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent)); + + if (aTransferable) { + nsCOMPtr<nsIArray> flavorList; + aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); + if (flavorList) { + uint32_t flavorCount = 0; + flavorList->GetLength(&flavorCount); + for (uint32_t j = 0; j < flavorCount; ++j) { + nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j); + if (!flavor) { + continue; + } + + nsAutoCString flavorStr; + flavor->GetData(flavorStr); + if (!flavorStr.Length()) { + continue; + } + + nsCOMPtr<nsISupports> data; + uint32_t dataLen = 0; + aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data), &dataLen); + + nsCOMPtr<nsISupportsString> text = do_QueryInterface(data); + nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data); + if (text) { + nsAutoString dataAsString; + text->GetData(dataAsString); + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + item->data() = dataAsString; + } else if (ctext) { + nsAutoCString dataAsString; + ctext->GetData(dataAsString); + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + + Shmem dataAsShmem = ConvertToShmem(aChild, aParent, dataAsString); + if (!dataAsShmem.IsReadable() || !dataAsShmem.Size<char>()) { + continue; + } + + item->data() = dataAsShmem; + } else { + nsCOMPtr<nsISupportsInterfacePointer> sip = + do_QueryInterface(data); + if (sip) { + sip->GetData(getter_AddRefs(data)); + } + + // Images to be pasted on the clipboard are nsIInputStreams + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(data)); + if (stream) { + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + + nsCString imageData; + NS_ConsumeStream(stream, UINT32_MAX, imageData); + + Shmem imageDataShmem = ConvertToShmem(aChild, aParent, imageData); + if (!imageDataShmem.IsReadable() || !imageDataShmem.Size<char>()) { + continue; + } + + item->data() = imageDataShmem; + continue; + } + + // Images to be placed on the clipboard are imgIContainers. + nsCOMPtr<imgIContainer> image(do_QueryInterface(data)); + if (image) { + RefPtr<mozilla::gfx::SourceSurface> surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + if (!surface) { + continue; + } + RefPtr<mozilla::gfx::DataSourceSurface> dataSurface = + surface->GetDataSurface(); + if (!dataSurface) { + continue; + } + size_t length; + int32_t stride; + Shmem surfaceData; + IShmemAllocator* allocator = aChild ? static_cast<IShmemAllocator*>(aChild) + : static_cast<IShmemAllocator*>(aParent); + GetSurfaceData(dataSurface, &length, &stride, + allocator, + &surfaceData); + + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + // Turn item->data() into an nsCString prior to accessing it. + item->data() = surfaceData; + + IPCDataTransferImage& imageDetails = item->imageDetails(); + mozilla::gfx::IntSize size = dataSurface->GetSize(); + imageDetails.width() = size.width; + imageDetails.height() = size.height; + imageDetails.stride() = stride; + imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat()); + + continue; + } + + // Otherwise, handle this as a file. + nsCOMPtr<BlobImpl> blobImpl; + nsCOMPtr<nsIFile> file = do_QueryInterface(data); + if (file) { + // If we can send this over as a blob, do so. Otherwise, we're + // responding to a sync message and the child can't process the blob + // constructor before processing our response, which would crash. In + // that case, hope that the caller is nsClipboardProxy::GetData, + // called from editor and send over images as raw data. + if (aInSyncMessage) { + nsAutoCString type; + if (IsFileImage(file, type)) { + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = type; + nsAutoCString data; + SlurpFileToString(file, data); + + Shmem dataAsShmem = ConvertToShmem(aChild, aParent, data); + item->data() = dataAsShmem; + } + + continue; + } + + if (aParent) { + bool isDir = false; + if (NS_SUCCEEDED(file->IsDirectory(&isDir)) && isDir) { + nsAutoString path; + if (NS_WARN_IF(NS_FAILED(file->GetPath(path)))) { + continue; + } + + RefPtr<FileSystemSecurity> fss = FileSystemSecurity::GetOrCreate(); + fss->GrantAccessToContentProcess(aParent->ChildID(), path); + } + } + + blobImpl = new BlobImplFile(file, false); + ErrorResult rv; + // Ensure that file data is cached no that the content process + // has this data available to it when passed over: + blobImpl->GetSize(rv); + blobImpl->GetLastModified(rv); + } else { + if (aInSyncMessage) { + // Can't do anything. + continue; + } + blobImpl = do_QueryInterface(data); + } + if (blobImpl) { + IPCDataTransferData data; + + // If we failed to create the blob actor, then this blob probably + // can't get the file size for the underlying file, ignore it for + // now. TODO pass this through anyway. + if (aChild) { + auto* child = mozilla::dom::BlobChild::GetOrCreate(aChild, + static_cast<BlobImpl*>(blobImpl.get())); + if (!child) { + continue; + } + + data = child; + } else if (aParent) { + auto* parent = mozilla::dom::BlobParent::GetOrCreate(aParent, + static_cast<BlobImpl*>(blobImpl.get())); + if (!parent) { + continue; + } + + data = parent; + } + + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + item->data() = data; + } else { + // This is a hack to support kFilePromiseMime. + // On Windows there just needs to be an entry for it, + // and for OSX we need to create + // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider. + if (flavorStr.EqualsLiteral(kFilePromiseMime)) { + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + item->data() = NS_ConvertUTF8toUTF16(flavorStr); + } else if (!data) { + // Empty element, transfer only the flavor + IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement(); + item->flavor() = flavorStr; + item->data() = nsString(); + continue; + } + } + } + } + } + } +} + +namespace { +// The default type used for calling GetSurfaceData(). Gets surface data as +// raw buffer. +struct GetSurfaceDataRawBuffer +{ + using ReturnType = mozilla::UniquePtr<char[]>; + using BufferType = char*; + + ReturnType Allocate(size_t aSize) + { + return ReturnType(new char[aSize]); + } + + static BufferType + GetBuffer(const ReturnType& aReturnValue) + { + return aReturnValue.get(); + } + + static ReturnType + NullValue() + { + return ReturnType(); + } +}; + +// The type used for calling GetSurfaceData() that allocates and writes to +// a shared memory buffer. +struct GetSurfaceDataShmem +{ + using ReturnType = Shmem; + using BufferType = char*; + + explicit GetSurfaceDataShmem(IShmemAllocator* aAllocator) + : mAllocator(aAllocator) + { } + + ReturnType Allocate(size_t aSize) + { + Shmem returnValue; + mAllocator->AllocShmem(aSize, + SharedMemory::TYPE_BASIC, + &returnValue); + return returnValue; + } + + static BufferType + GetBuffer(ReturnType aReturnValue) + { + return aReturnValue.get<char>(); + } + + static ReturnType + NullValue() + { + return ReturnType(); + } +private: + IShmemAllocator* mAllocator; +}; + +/* + * Get the pixel data from the given source surface and return it as a buffer. + * The length and stride will be assigned from the surface. + */ +template <typename GetSurfaceDataContext = GetSurfaceDataRawBuffer> +typename GetSurfaceDataContext::ReturnType +GetSurfaceDataImpl(mozilla::gfx::DataSourceSurface* aSurface, + size_t* aLength, int32_t* aStride, + GetSurfaceDataContext aContext = GetSurfaceDataContext()) +{ + mozilla::gfx::DataSourceSurface::MappedSurface map; + if (!aSurface->Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map)) { + return GetSurfaceDataContext::NullValue(); + } + + mozilla::gfx::IntSize size = aSurface->GetSize(); + mozilla::CheckedInt32 requiredBytes = + mozilla::CheckedInt32(map.mStride) * mozilla::CheckedInt32(size.height); + if (!requiredBytes.isValid()) { + return GetSurfaceDataContext::NullValue(); + } + + size_t maxBufLen = requiredBytes.value(); + mozilla::gfx::SurfaceFormat format = aSurface->GetFormat(); + + // Surface data handling is totally nuts. This is the magic one needs to + // know to access the data. + size_t bufLen = maxBufLen - map.mStride + (size.width * BytesPerPixel(format)); + + // nsDependentCString wants null-terminated string. + typename GetSurfaceDataContext::ReturnType surfaceData = aContext.Allocate(maxBufLen + 1); + if (GetSurfaceDataContext::GetBuffer(surfaceData)) { + memcpy(GetSurfaceDataContext::GetBuffer(surfaceData), + reinterpret_cast<char*>(map.mData), + bufLen); + memset(GetSurfaceDataContext::GetBuffer(surfaceData) + bufLen, + 0, + maxBufLen - bufLen + 1); + } + + *aLength = maxBufLen; + *aStride = map.mStride; + + aSurface->Unmap(); + return surfaceData; +} +} // Anonymous namespace. + +mozilla::UniquePtr<char[]> +nsContentUtils::GetSurfaceData( + NotNull<mozilla::gfx::DataSourceSurface*> aSurface, + size_t* aLength, int32_t* aStride) +{ + return GetSurfaceDataImpl(aSurface, aLength, aStride); +} + +void +nsContentUtils::GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface, + size_t* aLength, int32_t* aStride, + IShmemAllocator* aAllocator, + Shmem *aOutShmem) +{ + *aOutShmem = GetSurfaceDataImpl(aSurface, aLength, aStride, + GetSurfaceDataShmem(aAllocator)); +} + +mozilla::Modifiers +nsContentUtils::GetWidgetModifiers(int32_t aModifiers) +{ + Modifiers result = 0; + if (aModifiers & nsIDOMWindowUtils::MODIFIER_SHIFT) { + result |= mozilla::MODIFIER_SHIFT; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_CONTROL) { + result |= mozilla::MODIFIER_CONTROL; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALT) { + result |= mozilla::MODIFIER_ALT; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_META) { + result |= mozilla::MODIFIER_META; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_ALTGRAPH) { + result |= mozilla::MODIFIER_ALTGRAPH; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_CAPSLOCK) { + result |= mozilla::MODIFIER_CAPSLOCK; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_FN) { + result |= mozilla::MODIFIER_FN; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_FNLOCK) { + result |= mozilla::MODIFIER_FNLOCK; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_NUMLOCK) { + result |= mozilla::MODIFIER_NUMLOCK; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_SCROLLLOCK) { + result |= mozilla::MODIFIER_SCROLLLOCK; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOL) { + result |= mozilla::MODIFIER_SYMBOL; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK) { + result |= mozilla::MODIFIER_SYMBOLLOCK; + } + if (aModifiers & nsIDOMWindowUtils::MODIFIER_OS) { + result |= mozilla::MODIFIER_OS; + } + return result; +} + +nsIWidget* +nsContentUtils::GetWidget(nsIPresShell* aPresShell, nsPoint* aOffset) { + if (aPresShell) { + nsIFrame* frame = aPresShell->GetRootFrame(); + if (frame) + return frame->GetView()->GetNearestWidget(aOffset); + } + return nullptr; +} + +int16_t +nsContentUtils::GetButtonsFlagForButton(int32_t aButton) +{ + switch (aButton) { + case -1: + return WidgetMouseEvent::eNoButtonFlag; + case WidgetMouseEvent::eLeftButton: + return WidgetMouseEvent::eLeftButtonFlag; + case WidgetMouseEvent::eMiddleButton: + return WidgetMouseEvent::eMiddleButtonFlag; + case WidgetMouseEvent::eRightButton: + return WidgetMouseEvent::eRightButtonFlag; + case 4: + return WidgetMouseEvent::e4thButtonFlag; + case 5: + return WidgetMouseEvent::e5thButtonFlag; + default: + NS_ERROR("Button not known."); + return 0; + } +} + +LayoutDeviceIntPoint +nsContentUtils::ToWidgetPoint(const CSSPoint& aPoint, + const nsPoint& aOffset, + nsPresContext* aPresContext) +{ + return LayoutDeviceIntPoint::FromAppUnitsRounded( + (CSSPoint::ToAppUnits(aPoint) + + aOffset).ApplyResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(aPresContext->PresShell())), + aPresContext->AppUnitsPerDevPixel()); +} + +nsView* +nsContentUtils::GetViewToDispatchEvent(nsPresContext* presContext, + nsIPresShell** presShell) +{ + if (presContext && presShell) { + *presShell = presContext->PresShell(); + if (*presShell) { + NS_ADDREF(*presShell); + if (nsViewManager* viewManager = (*presShell)->GetViewManager()) { + if (nsView* view = viewManager->GetRootView()) { + return view; + } + } + } + } + return nullptr; +} + +nsresult +nsContentUtils::SendKeyEvent(nsIWidget* aWidget, + const nsAString& aType, + int32_t aKeyCode, + int32_t aCharCode, + int32_t aModifiers, + uint32_t aAdditionalFlags, + bool* aDefaultActionTaken) +{ + // get the widget to send the event to + if (!aWidget) + return NS_ERROR_FAILURE; + + EventMessage msg; + if (aType.EqualsLiteral("keydown")) + msg = eKeyDown; + else if (aType.EqualsLiteral("keyup")) + msg = eKeyUp; + else if (aType.EqualsLiteral("keypress")) + msg = eKeyPress; + else + return NS_ERROR_FAILURE; + + WidgetKeyboardEvent event(true, msg, aWidget); + event.mModifiers = GetWidgetModifiers(aModifiers); + + if (msg == eKeyPress) { + event.mKeyCode = aCharCode ? 0 : aKeyCode; + event.mCharCode = aCharCode; + } else { + event.mKeyCode = aKeyCode; + event.mCharCode = 0; + } + + uint32_t locationFlag = (aAdditionalFlags & + (nsIDOMWindowUtils::KEY_FLAG_LOCATION_STANDARD | nsIDOMWindowUtils::KEY_FLAG_LOCATION_LEFT | + nsIDOMWindowUtils::KEY_FLAG_LOCATION_RIGHT | nsIDOMWindowUtils::KEY_FLAG_LOCATION_NUMPAD)); + switch (locationFlag) { + case nsIDOMWindowUtils::KEY_FLAG_LOCATION_STANDARD: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; + break; + case nsIDOMWindowUtils::KEY_FLAG_LOCATION_LEFT: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; + break; + case nsIDOMWindowUtils::KEY_FLAG_LOCATION_RIGHT: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; + break; + case nsIDOMWindowUtils::KEY_FLAG_LOCATION_NUMPAD: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + break; + default: + if (locationFlag != 0) { + return NS_ERROR_INVALID_ARG; + } + // If location flag isn't set, choose the location from keycode. + switch (aKeyCode) { + case nsIDOMKeyEvent::DOM_VK_NUMPAD0: + case nsIDOMKeyEvent::DOM_VK_NUMPAD1: + case nsIDOMKeyEvent::DOM_VK_NUMPAD2: + case nsIDOMKeyEvent::DOM_VK_NUMPAD3: + case nsIDOMKeyEvent::DOM_VK_NUMPAD4: + case nsIDOMKeyEvent::DOM_VK_NUMPAD5: + case nsIDOMKeyEvent::DOM_VK_NUMPAD6: + case nsIDOMKeyEvent::DOM_VK_NUMPAD7: + case nsIDOMKeyEvent::DOM_VK_NUMPAD8: + case nsIDOMKeyEvent::DOM_VK_NUMPAD9: + case nsIDOMKeyEvent::DOM_VK_MULTIPLY: + case nsIDOMKeyEvent::DOM_VK_ADD: + case nsIDOMKeyEvent::DOM_VK_SEPARATOR: + case nsIDOMKeyEvent::DOM_VK_SUBTRACT: + case nsIDOMKeyEvent::DOM_VK_DECIMAL: + case nsIDOMKeyEvent::DOM_VK_DIVIDE: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + break; + case nsIDOMKeyEvent::DOM_VK_SHIFT: + case nsIDOMKeyEvent::DOM_VK_CONTROL: + case nsIDOMKeyEvent::DOM_VK_ALT: + case nsIDOMKeyEvent::DOM_VK_META: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; + break; + default: + event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; + break; + } + break; + } + + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + event.mTime = PR_IntervalNow(); + if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) { + event.mFlags.mIsSynthesizedForTests = true; + } + + if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) { + event.PreventDefaultBeforeDispatch(); + } + + nsEventStatus status; + nsresult rv = aWidget->DispatchEvent(&event, status); + NS_ENSURE_SUCCESS(rv, rv); + + *aDefaultActionTaken = (status != nsEventStatus_eConsumeNoDefault); + + return NS_OK; +} + +nsresult +nsContentUtils::SendMouseEvent(nsCOMPtr<nsIPresShell> aPresShell, + const nsAString& aType, + float aX, + float aY, + int32_t aButton, + int32_t aButtons, + int32_t aClickCount, + int32_t aModifiers, + bool aIgnoreRootScrollFrame, + float aPressure, + unsigned short aInputSourceArg, + bool aToWindow, + bool *aPreventDefault, + bool aIsDOMEventSynthesized, + bool aIsWidgetEventSynthesized) +{ + nsPoint offset; + nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset); + if (!widget) + return NS_ERROR_FAILURE; + + EventMessage msg; + WidgetMouseEvent::ExitFrom exitFrom = WidgetMouseEvent::eChild; + bool contextMenuKey = false; + if (aType.EqualsLiteral("mousedown")) { + msg = eMouseDown; + } else if (aType.EqualsLiteral("mouseup")) { + msg = eMouseUp; + } else if (aType.EqualsLiteral("mousemove")) { + msg = eMouseMove; + } else if (aType.EqualsLiteral("mouseover")) { + msg = eMouseEnterIntoWidget; + } else if (aType.EqualsLiteral("mouseout")) { + msg = eMouseExitFromWidget; + } else if (aType.EqualsLiteral("mousecancel")) { + msg = eMouseExitFromWidget; + exitFrom = WidgetMouseEvent::eTopLevel; + } else if (aType.EqualsLiteral("mouselongtap")) { + msg = eMouseLongTap; + } else if (aType.EqualsLiteral("contextmenu")) { + msg = eContextMenu; + contextMenuKey = (aButton == 0); + } else if (aType.EqualsLiteral("MozMouseHittest")) { + msg = eMouseHitTest; + } else { + return NS_ERROR_FAILURE; + } + + if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) { + aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; + } + + WidgetMouseEvent event(true, msg, widget, + aIsWidgetEventSynthesized ? + WidgetMouseEvent::eSynthesized : + WidgetMouseEvent::eReal, + contextMenuKey ? WidgetMouseEvent::eContextMenuKey : + WidgetMouseEvent::eNormal); + event.mModifiers = GetWidgetModifiers(aModifiers); + event.button = aButton; + event.buttons = aButtons != nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED ? + aButtons : + msg == eMouseUp ? 0 : GetButtonsFlagForButton(aButton); + event.pressure = aPressure; + event.inputSource = aInputSourceArg; + event.mClickCount = aClickCount; + event.mTime = PR_IntervalNow(); + event.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized; + event.mExitFrom = exitFrom; + + nsPresContext* presContext = aPresShell->GetPresContext(); + if (!presContext) + return NS_ERROR_FAILURE; + + event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext); + event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame; + + nsEventStatus status = nsEventStatus_eIgnore; + if (aToWindow) { + nsCOMPtr<nsIPresShell> presShell; + nsView* view = GetViewToDispatchEvent(presContext, getter_AddRefs(presShell)); + if (!presShell || !view) { + return NS_ERROR_FAILURE; + } + return presShell->HandleEvent(view->GetFrame(), &event, false, &status); + } + if (gfxPrefs::TestEventsAsyncEnabled()) { + status = widget->DispatchInputEvent(&event); + } else { + nsresult rv = widget->DispatchEvent(&event, status); + NS_ENSURE_SUCCESS(rv, rv); + } + if (aPreventDefault) { + *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault); + } + + return NS_OK; +} + +/* static */ +void +nsContentUtils::FirePageHideEvent(nsIDocShellTreeItem* aItem, + EventTarget* aChromeEventHandler) +{ + nsCOMPtr<nsIDocument> doc = aItem->GetDocument(); + NS_ASSERTION(doc, "What happened here?"); + doc->OnPageHide(true, aChromeEventHandler); + + int32_t childCount = 0; + aItem->GetChildCount(&childCount); + AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids; + kids.AppendElements(childCount); + for (int32_t i = 0; i < childCount; ++i) { + aItem->GetChildAt(i, getter_AddRefs(kids[i])); + } + + for (uint32_t i = 0; i < kids.Length(); ++i) { + if (kids[i]) { + FirePageHideEvent(kids[i], aChromeEventHandler); + } + } +} + +// The pageshow event is fired for a given document only if IsShowing() returns +// the same thing as aFireIfShowing. This gives us a way to fire pageshow only +// on documents that are still loading or only on documents that are already +// loaded. +/* static */ +void +nsContentUtils::FirePageShowEvent(nsIDocShellTreeItem* aItem, + EventTarget* aChromeEventHandler, + bool aFireIfShowing) +{ + int32_t childCount = 0; + aItem->GetChildCount(&childCount); + AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids; + kids.AppendElements(childCount); + for (int32_t i = 0; i < childCount; ++i) { + aItem->GetChildAt(i, getter_AddRefs(kids[i])); + } + + for (uint32_t i = 0; i < kids.Length(); ++i) { + if (kids[i]) { + FirePageShowEvent(kids[i], aChromeEventHandler, aFireIfShowing); + } + } + + nsCOMPtr<nsIDocument> doc = aItem->GetDocument(); + NS_ASSERTION(doc, "What happened here?"); + if (doc->IsShowing() == aFireIfShowing) { + doc->OnPageShow(true, aChromeEventHandler); + } +} + +/* static */ +already_AddRefed<nsPIWindowRoot> +nsContentUtils::GetWindowRoot(nsIDocument* aDoc) +{ + if (aDoc) { + if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) { + return win->GetTopWindowRoot(); + } + } + return nullptr; +} + +/* static */ +nsContentPolicyType +nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType) +{ + switch (aType) { + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + return nsIContentPolicy::TYPE_SCRIPT; + + case nsIContentPolicy::TYPE_INTERNAL_EMBED: + case nsIContentPolicy::TYPE_INTERNAL_OBJECT: + return nsIContentPolicy::TYPE_OBJECT; + + case nsIContentPolicy::TYPE_INTERNAL_FRAME: + case nsIContentPolicy::TYPE_INTERNAL_IFRAME: + return nsIContentPolicy::TYPE_SUBDOCUMENT; + + case nsIContentPolicy::TYPE_INTERNAL_AUDIO: + case nsIContentPolicy::TYPE_INTERNAL_VIDEO: + case nsIContentPolicy::TYPE_INTERNAL_TRACK: + return nsIContentPolicy::TYPE_MEDIA; + + case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST: + case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE: + return nsIContentPolicy::TYPE_XMLHTTPREQUEST; + + case nsIContentPolicy::TYPE_INTERNAL_IMAGE: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: + return nsIContentPolicy::TYPE_IMAGE; + + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: + return nsIContentPolicy::TYPE_STYLESHEET; + + default: + return aType; + } +} + +/* static */ +nsContentPolicyType +nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType) +{ + switch (aType) { + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + return aType; + + default: + return InternalContentPolicyTypeToExternal(aType); + } +} + +/* static */ +bool +nsContentUtils::IsPreloadType(nsContentPolicyType aType) +{ + if (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD || + aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD || + aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD) { + return true; + } + return false; +} + +nsresult +nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal, + nsIDocument* aDoc, + nsIHttpChannel* aChannel, + mozilla::net::ReferrerPolicy aReferrerPolicy) +{ + NS_ENSURE_ARG_POINTER(aPrincipal); + NS_ENSURE_ARG_POINTER(aChannel); + + nsCOMPtr<nsIURI> principalURI; + + if (IsSystemPrincipal(aPrincipal)) { + return NS_OK; + } + + aPrincipal->GetURI(getter_AddRefs(principalURI)); + + if (!aDoc) { + return aChannel->SetReferrerWithPolicy(principalURI, aReferrerPolicy); + } + + // If it weren't for history.push/replaceState, we could just use the + // principal's URI here. But since we want changes to the URI effected + // by push/replaceState to be reflected in the XHR referrer, we have to + // be more clever. + // + // If the document's original URI (before any push/replaceStates) matches + // our principal, then we use the document's current URI (after + // push/replaceStates). Otherwise (if the document is, say, a data: + // URI), we just use the principal's URI. + nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI(); + nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI(); + + nsCOMPtr<nsIURI> referrerURI; + + if (principalURI && docCurURI && docOrigURI) { + bool equal = false; + principalURI->Equals(docOrigURI, &equal); + if (equal) { + referrerURI = docCurURI; + } + } + + if (!referrerURI) { + referrerURI = principalURI; + } + + net::ReferrerPolicy referrerPolicy = aReferrerPolicy; + if (referrerPolicy == net::RP_Default) { + referrerPolicy = aDoc->GetReferrerPolicy(); + } + return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy); +} + +// static +net::ReferrerPolicy +nsContentUtils::GetReferrerPolicyFromHeader(const nsAString& aHeader) +{ + // Multiple headers could be concatenated into one comma-separated + // list of policies. Need to tokenize the multiple headers. + nsCharSeparatedTokenizer tokenizer(aHeader, ','); + nsAutoString token; + net::ReferrerPolicy referrerPolicy = mozilla::net::RP_Unset; + while (tokenizer.hasMoreTokens()) { + token = tokenizer.nextToken(); + net::ReferrerPolicy policy = net::ReferrerPolicyFromString(token); + if (policy != net::RP_Unset) { + referrerPolicy = policy; + } + } + return referrerPolicy; +} + +// static +bool +nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.push.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->PushEnabled(); +} + +// static +bool +nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel) +{ + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { + return true; + } + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (!loadInfo) { + return false; + } + nsContentPolicyType type = loadInfo->InternalContentPolicyType(); + return type == nsIContentPolicy::TYPE_INTERNAL_WORKER || + type == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; +} + +// static, public +nsContentUtils::StorageAccess +nsContentUtils::StorageAllowedForWindow(nsPIDOMWindowInner* aWindow) +{ + MOZ_ASSERT(aWindow->IsInnerWindow()); + + if (nsIDocument* document = aWindow->GetExtantDoc()) { + nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal(); + return InternalStorageAllowedForPrincipal(principal, aWindow); + } + + return StorageAccess::eDeny; +} + +// static, public +nsContentUtils::StorageAccess +nsContentUtils::StorageAllowedForPrincipal(nsIPrincipal* aPrincipal) +{ + return InternalStorageAllowedForPrincipal(aPrincipal, nullptr); +} + +// static, private +nsContentUtils::StorageAccess +nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal, + nsPIDOMWindowInner* aWindow) +{ + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow()); + + StorageAccess access = StorageAccess::eAllow; + + // We don't allow storage on the null principal, in general. Even if the + // calling context is chrome. + if (aPrincipal->GetIsNullPrincipal()) { + return StorageAccess::eDeny; + } + + if (aWindow) { + // If the document is sandboxed, then it is not permitted to use storage + nsIDocument* document = aWindow->GetExtantDoc(); + if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) { + return StorageAccess::eDeny; + } + + // Check if we are in private browsing, and record that fact + if (IsInPrivateBrowsing(document)) { + access = StorageAccess::ePrivateBrowsing; + } + } + + nsCOMPtr<nsIPermissionManager> permissionManager = + services::GetPermissionManager(); + if (!permissionManager) { + return StorageAccess::eDeny; + } + + // check the permission manager for any allow or deny permissions + // for cookies for the window. + uint32_t perm; + permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm); + if (perm == nsIPermissionManager::DENY_ACTION) { + return StorageAccess::eDeny; + } else if (perm == nsICookiePermission::ACCESS_SESSION) { + return std::min(access, StorageAccess::eSessionScoped); + } else if (perm == nsIPermissionManager::ALLOW_ACTION) { + return access; + } + + // Check if we should only allow storage for the session, and record that fact + if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) { + // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow + // so perform a std::min comparison to make sure we preserve ePrivateBrowsing + // if it has been set. + access = std::min(StorageAccess::eSessionScoped, access); + } + + // About URIs are allowed to access storage, even if they don't have chrome + // privileges. If this is not desired, than the consumer will have to + // implement their own restriction functionality. + // + // This is due to backwards-compatibility and the state of storage access before + // the introducton of nsContentUtils::InternalStorageAllowedForPrincipal: + // + // BEFORE: + // localStorage, caches: allowed in 3rd-party iframes always + // IndexedDB: allowed in 3rd-party iframes only if 3rd party URI is an about: + // URI within a specific whitelist + // + // AFTER: + // localStorage, caches: allowed in 3rd-party iframes by default. Preference + // can be set to disable in 3rd-party, which will not disallow in about: URIs. + // IndexedDB: allowed in 3rd-party iframes by default. Preference can be set to + // disable in 3rd-party, which will disallow in about: URIs, unless they are + // within a specific whitelist. + // + // This means that behavior for storage with internal about: URIs should not be + // affected, which is desireable due to the lack of automated testing for about: + // URIs with these preferences set, and the importance of the correct functioning + // of these URIs even with custom preferences. + nsCOMPtr<nsIURI> uri; + nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + bool isAbout = false; + MOZ_ALWAYS_SUCCEEDS(uri->SchemeIs("about", &isAbout)); + if (isAbout) { + return access; + } + } + + // We don't want to prompt for every attempt to access permissions. + if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) { + return StorageAccess::eDeny; + } + + // In the absense of a window, we assume that we are first-party. + if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN || + sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) { + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + MOZ_ASSERT(thirdPartyUtil); + + bool thirdPartyWindow = false; + if (NS_SUCCEEDED(thirdPartyUtil->IsThirdPartyWindow( + aWindow->GetOuterWindow(), nullptr, &thirdPartyWindow)) && thirdPartyWindow) { + // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by + // simply rejecting the request to use the storage. In the future, if we + // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense + // for non-cookie storage types, this may change. + + return StorageAccess::eDeny; + } + } + + return access; +} + +namespace { + +// We put StringBuilder in the anonymous namespace to prevent anything outside +// this file from accidentally being linked against it. + +class StringBuilder +{ +private: + // Try to keep the size of StringBuilder close to a jemalloc bucket size. + static const uint32_t STRING_BUFFER_UNITS = 1020; + class Unit + { + public: + Unit() : mAtom(nullptr), mType(eUnknown), mLength(0) + { + MOZ_COUNT_CTOR(StringBuilder::Unit); + } + ~Unit() + { + if (mType == eString || mType == eStringWithEncode) { + delete mString; + } + MOZ_COUNT_DTOR(StringBuilder::Unit); + } + + enum Type + { + eUnknown, + eAtom, + eString, + eStringWithEncode, + eLiteral, + eTextFragment, + eTextFragmentWithEncode, + }; + + union + { + nsIAtom* mAtom; + const char* mLiteral; + nsAutoString* mString; + const nsTextFragment* mTextFragment; + }; + Type mType; + uint32_t mLength; + }; +public: + StringBuilder() : mLast(this), mLength(0) + { + MOZ_COUNT_CTOR(StringBuilder); + } + + ~StringBuilder() + { + MOZ_COUNT_DTOR(StringBuilder); + } + + void Append(nsIAtom* aAtom) + { + Unit* u = AddUnit(); + u->mAtom = aAtom; + u->mType = Unit::eAtom; + uint32_t len = aAtom->GetLength(); + u->mLength = len; + mLength += len; + } + + template<int N> + void Append(const char (&aLiteral)[N]) + { + Unit* u = AddUnit(); + u->mLiteral = aLiteral; + u->mType = Unit::eLiteral; + uint32_t len = N - 1; + u->mLength = len; + mLength += len; + } + + template<int N> + void Append(char (&aLiteral)[N]) + { + Unit* u = AddUnit(); + u->mLiteral = aLiteral; + u->mType = Unit::eLiteral; + uint32_t len = N - 1; + u->mLength = len; + mLength += len; + } + + void Append(const nsAString& aString) + { + Unit* u = AddUnit(); + u->mString = new nsAutoString(aString); + u->mType = Unit::eString; + uint32_t len = aString.Length(); + u->mLength = len; + mLength += len; + } + + void Append(nsAutoString* aString) + { + Unit* u = AddUnit(); + u->mString = aString; + u->mType = Unit::eString; + uint32_t len = aString->Length(); + u->mLength = len; + mLength += len; + } + + void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen) + { + Unit* u = AddUnit(); + u->mString = aString; + u->mType = Unit::eStringWithEncode; + u->mLength = aLen; + mLength += aLen; + } + + void Append(const nsTextFragment* aTextFragment) + { + Unit* u = AddUnit(); + u->mTextFragment = aTextFragment; + u->mType = Unit::eTextFragment; + uint32_t len = aTextFragment->GetLength(); + u->mLength = len; + mLength += len; + } + + void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen) + { + Unit* u = AddUnit(); + u->mTextFragment = aTextFragment; + u->mType = Unit::eTextFragmentWithEncode; + u->mLength = aLen; + mLength += aLen; + } + + bool ToString(nsAString& aOut) + { + if (!aOut.SetCapacity(mLength, fallible)) { + return false; + } + + for (StringBuilder* current = this; current; current = current->mNext) { + uint32_t len = current->mUnits.Length(); + for (uint32_t i = 0; i < len; ++i) { + Unit& u = current->mUnits[i]; + switch (u.mType) { + case Unit::eAtom: + aOut.Append(nsDependentAtomString(u.mAtom)); + break; + case Unit::eString: + aOut.Append(*(u.mString)); + break; + case Unit::eStringWithEncode: + EncodeAttrString(*(u.mString), aOut); + break; + case Unit::eLiteral: + aOut.AppendASCII(u.mLiteral, u.mLength); + break; + case Unit::eTextFragment: + u.mTextFragment->AppendTo(aOut); + break; + case Unit::eTextFragmentWithEncode: + EncodeTextFragment(u.mTextFragment, aOut); + break; + default: + MOZ_CRASH("Unknown unit type?"); + } + } + } + return true; + } +private: + Unit* AddUnit() + { + if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) { + new StringBuilder(this); + } + return mLast->mUnits.AppendElement(); + } + + explicit StringBuilder(StringBuilder* aFirst) + : mLast(nullptr), mLength(0) + { + MOZ_COUNT_CTOR(StringBuilder); + aFirst->mLast->mNext = this; + aFirst->mLast = this; + } + + void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut) + { + const char16_t* c = aValue.BeginReading(); + const char16_t* end = aValue.EndReading(); + while (c < end) { + switch (*c) { + case '"': + aOut.AppendLiteral("""); + break; + case '&': + aOut.AppendLiteral("&"); + break; + case 0x00A0: + aOut.AppendLiteral(" "); + break; + default: + aOut.Append(*c); + break; + } + ++c; + } + } + + void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut) + { + uint32_t len = aValue->GetLength(); + if (aValue->Is2b()) { + const char16_t* data = aValue->Get2b(); + for (uint32_t i = 0; i < len; ++i) { + const char16_t c = data[i]; + switch (c) { + case '<': + aOut.AppendLiteral("<"); + break; + case '>': + aOut.AppendLiteral(">"); + break; + case '&': + aOut.AppendLiteral("&"); + break; + case 0x00A0: + aOut.AppendLiteral(" "); + break; + default: + aOut.Append(c); + break; + } + } + } else { + const char* data = aValue->Get1b(); + for (uint32_t i = 0; i < len; ++i) { + const unsigned char c = data[i]; + switch (c) { + case '<': + aOut.AppendLiteral("<"); + break; + case '>': + aOut.AppendLiteral(">"); + break; + case '&': + aOut.AppendLiteral("&"); + break; + case 0x00A0: + aOut.AppendLiteral(" "); + break; + default: + aOut.Append(c); + break; + } + } + } + } + + AutoTArray<Unit, STRING_BUFFER_UNITS> mUnits; + nsAutoPtr<StringBuilder> mNext; + StringBuilder* mLast; + // mLength is used only in the first StringBuilder object in the linked list. + uint32_t mLength; +}; + +} // namespace + +static void +AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder) +{ + uint32_t extraSpaceNeeded = 0; + uint32_t len = aText->GetLength(); + if (aText->Is2b()) { + const char16_t* data = aText->Get2b(); + for (uint32_t i = 0; i < len; ++i) { + const char16_t c = data[i]; + switch (c) { + case '<': + extraSpaceNeeded += ArrayLength("<") - 2; + break; + case '>': + extraSpaceNeeded += ArrayLength(">") - 2; + break; + case '&': + extraSpaceNeeded += ArrayLength("&") - 2; + break; + case 0x00A0: + extraSpaceNeeded += ArrayLength(" ") - 2; + break; + default: + break; + } + } + } else { + const char* data = aText->Get1b(); + for (uint32_t i = 0; i < len; ++i) { + const unsigned char c = data[i]; + switch (c) { + case '<': + extraSpaceNeeded += ArrayLength("<") - 2; + break; + case '>': + extraSpaceNeeded += ArrayLength(">") - 2; + break; + case '&': + extraSpaceNeeded += ArrayLength("&") - 2; + break; + case 0x00A0: + extraSpaceNeeded += ArrayLength(" ") - 2; + break; + default: + break; + } + } + } + + if (extraSpaceNeeded) { + aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded); + } else { + aBuilder.Append(aText); + } +} + +static void +AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder) +{ + const char16_t* c = aValue->BeginReading(); + const char16_t* end = aValue->EndReading(); + + uint32_t extraSpaceNeeded = 0; + while (c < end) { + switch (*c) { + case '"': + extraSpaceNeeded += ArrayLength(""") - 2; + break; + case '&': + extraSpaceNeeded += ArrayLength("&") - 2; + break; + case 0x00A0: + extraSpaceNeeded += ArrayLength(" ") - 2; + break; + default: + break; + } + ++c; + } + + if (extraSpaceNeeded) { + aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded); + } else { + aBuilder.Append(aValue); + } +} + +static void +StartElement(Element* aContent, StringBuilder& aBuilder) +{ + nsIAtom* localName = aContent->NodeInfo()->NameAtom(); + int32_t tagNS = aContent->GetNameSpaceID(); + + aBuilder.Append("<"); + if (aContent->IsHTMLElement() || aContent->IsSVGElement() || + aContent->IsMathMLElement()) { + aBuilder.Append(localName); + } else { + aBuilder.Append(aContent->NodeName()); + } + + int32_t count = aContent->GetAttrCount(); + for (int32_t i = 0; i < count; i++) { + const nsAttrName* name = aContent->GetAttrNameAt(i); + int32_t attNs = name->NamespaceID(); + nsIAtom* attName = name->LocalName(); + + // Filter out any attribute starting with [-|_]moz + nsDependentAtomString attrNameStr(attName); + if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) || + StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) { + continue; + } + + nsAutoString* attValue = new nsAutoString(); + aContent->GetAttr(attNs, attName, *attValue); + + // Filter out special case of <br type="_moz*"> used by the editor. + // Bug 16988. Yuck. + if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML && + attName == nsGkAtoms::type && attNs == kNameSpaceID_None && + StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) { + delete attValue; + continue; + } + + aBuilder.Append(" "); + + if (MOZ_LIKELY(attNs == kNameSpaceID_None) || + (attNs == kNameSpaceID_XMLNS && + attName == nsGkAtoms::xmlns)) { + // Nothing else required + } else if (attNs == kNameSpaceID_XML) { + aBuilder.Append("xml:"); + } else if (attNs == kNameSpaceID_XMLNS) { + aBuilder.Append("xmlns:"); + } else if (attNs == kNameSpaceID_XLink) { + aBuilder.Append("xlink:"); + } else { + nsIAtom* prefix = name->GetPrefix(); + if (prefix) { + aBuilder.Append(prefix); + aBuilder.Append(":"); + } + } + + aBuilder.Append(attName); + aBuilder.Append("=\""); + AppendEncodedAttributeValue(attValue, aBuilder); + aBuilder.Append("\""); + } + + aBuilder.Append(">"); + + /* + // Per HTML spec we should append one \n if the first child of + // pre/textarea/listing is a textnode and starts with a \n. + // But because browsers haven't traditionally had that behavior, + // we're not changing our behavior either - yet. + if (aContent->IsHTMLElement()) { + if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea || + localName == nsGkAtoms::listing) { + nsIContent* fc = aContent->GetFirstChild(); + if (fc && + (fc->NodeType() == nsIDOMNode::TEXT_NODE || + fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) { + const nsTextFragment* text = fc->GetText(); + if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) { + aBuilder.Append("\n"); + } + } + } + }*/ +} + +static inline bool +ShouldEscape(nsIContent* aParent) +{ + if (!aParent || !aParent->IsHTMLElement()) { + return true; + } + + static const nsIAtom* nonEscapingElements[] = { + nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp, + nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes, + nsGkAtoms::plaintext, + // Per the current spec noscript should be escaped in case + // scripts are disabled or if document doesn't have + // browsing context. However the latter seems to be a spec bug + // and Gecko hasn't traditionally done the former. + nsGkAtoms::noscript + }; + static mozilla::BloomFilter<12, nsIAtom> sFilter; + static bool sInitialized = false; + if (!sInitialized) { + sInitialized = true; + for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) { + sFilter.add(nonEscapingElements[i]); + } + } + + nsIAtom* tag = aParent->NodeInfo()->NameAtom(); + if (sFilter.mightContain(tag)) { + for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) { + if (tag == nonEscapingElements[i]) { + return false; + } + } + } + return true; +} + +static inline bool +IsVoidTag(Element* aElement) +{ + if (!aElement->IsHTMLElement()) { + return false; + } + return FragmentOrElement::IsHTMLVoid(aElement->NodeInfo()->NameAtom()); +} + +bool +nsContentUtils::SerializeNodeToMarkup(nsINode* aRoot, + bool aDescendentsOnly, + nsAString& aOut) +{ + // If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true + MOZ_ASSERT(aDescendentsOnly || + aRoot->NodeType() != nsIDOMNode::DOCUMENT_NODE); + + nsINode* current = aDescendentsOnly ? + nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot; + + if (!current) { + return true; + } + + StringBuilder builder; + nsIContent* next; + while (true) { + bool isVoid = false; + switch (current->NodeType()) { + case nsIDOMNode::ELEMENT_NODE: { + Element* elem = current->AsElement(); + StartElement(elem, builder); + isVoid = IsVoidTag(elem); + if (!isVoid && + (next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) { + current = next; + continue; + } + break; + } + + case nsIDOMNode::TEXT_NODE: + case nsIDOMNode::CDATA_SECTION_NODE: { + const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText(); + nsIContent* parent = current->GetParent(); + if (ShouldEscape(parent)) { + AppendEncodedCharacters(text, builder); + } else { + builder.Append(text); + } + break; + } + + case nsIDOMNode::COMMENT_NODE: { + builder.Append("<!--"); + builder.Append(static_cast<nsIContent*>(current)->GetText()); + builder.Append("-->"); + break; + } + + case nsIDOMNode::DOCUMENT_TYPE_NODE: { + builder.Append("<!DOCTYPE "); + builder.Append(current->NodeName()); + builder.Append(">"); + break; + } + + case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: { + builder.Append("<?"); + builder.Append(current->NodeName()); + builder.Append(" "); + builder.Append(static_cast<nsIContent*>(current)->GetText()); + builder.Append(">"); + break; + } + } + + while (true) { + if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) { + builder.Append("</"); + nsIContent* elem = static_cast<nsIContent*>(current); + if (elem->IsHTMLElement() || elem->IsSVGElement() || + elem->IsMathMLElement()) { + builder.Append(elem->NodeInfo()->NameAtom()); + } else { + builder.Append(current->NodeName()); + } + builder.Append(">"); + } + isVoid = false; + + if (current == aRoot) { + return builder.ToString(aOut); + } + + if ((next = current->GetNextSibling())) { + current = next; + break; + } + + current = current->GetParentNode(); + + // Handle template element. If the parent is a template's content, + // then adjust the parent to be the template element. + if (current != aRoot && + current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { + DocumentFragment* frag = static_cast<DocumentFragment*>(current); + nsIContent* fragHost = frag->GetHost(); + if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) { + current = fragHost; + } + } + + if (aDescendentsOnly && current == aRoot) { + return builder.ToString(aOut); + } + } + } +} + +bool +nsContentUtils::IsSpecificAboutPage(JSObject* aGlobal, const char* aUri) +{ + // aUri must start with about: or this isn't the right function to be using. + MOZ_ASSERT(strncmp(aUri, "about:", 6) == 0); + + // Make sure the global is a window + nsGlobalWindow* win = xpc::WindowGlobalOrNull(aGlobal); + if (!win) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal = win->GetPrincipal(); + NS_ENSURE_TRUE(principal, false); + nsCOMPtr<nsIURI> uri; + principal->GetURI(getter_AddRefs(uri)); + if (!uri) { + return false; + } + + // First check the scheme to avoid getting long specs in the common case. + bool isAbout = false; + uri->SchemeIs("about", &isAbout); + if (!isAbout) { + return false; + } + + // Now check the spec itself + nsAutoCString spec; + uri->GetSpecIgnoringRef(spec); + return spec.EqualsASCII(aUri); +} + +/* static */ void +nsContentUtils::SetScrollbarsVisibility(nsIDocShell* aDocShell, bool aVisible) +{ + nsCOMPtr<nsIScrollable> scroller = do_QueryInterface(aDocShell); + + if (scroller) { + int32_t prefValue; + + if (aVisible) { + prefValue = nsIScrollable::Scrollbar_Auto; + } else { + prefValue = nsIScrollable::Scrollbar_Never; + } + + scroller->SetDefaultScrollbarPreferences( + nsIScrollable::ScrollOrientation_Y, prefValue); + scroller->SetDefaultScrollbarPreferences( + nsIScrollable::ScrollOrientation_X, prefValue); + } +} + +/* static */ void +nsContentUtils::GetPresentationURL(nsIDocShell* aDocShell, nsAString& aPresentationUrl) +{ + MOZ_ASSERT(aDocShell); + + // Simulate receiver context for web platform test + if (Preferences::GetBool("dom.presentation.testing.simulate-receiver")) { + nsCOMPtr<nsIDocument> doc; + + nsCOMPtr<nsPIDOMWindowOuter> docShellWin = + do_QueryInterface(aDocShell->GetScriptGlobalObject()); + if (docShellWin) { + doc = docShellWin->GetExtantDoc(); + } + + if (NS_WARN_IF(!doc)) { + return; + } + + nsCOMPtr<nsIURI> uri = doc->GetDocumentURI(); + if (NS_WARN_IF(!uri)) { + return; + } + + nsAutoCString uriStr; + uri->GetSpec(uriStr); + aPresentationUrl = NS_ConvertUTF8toUTF16(uriStr); + return; + } + + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; + aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + nsCOMPtr<nsIDocShellTreeItem> root; + aDocShell->GetRootTreeItem(getter_AddRefs(root)); + if (sameTypeRoot.get() == root.get()) { + // presentation URL is stored in TabChild for the top most + // <iframe mozbrowser> in content process. + TabChild* tabChild = TabChild::GetFrom(aDocShell); + if (tabChild) { + aPresentationUrl = tabChild->PresentationURL(); + } + return; + } + } + + nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(aDocShell)); + nsCOMPtr<nsIDOMElement> topFrameElement; + loadContext->GetTopFrameElement(getter_AddRefs(topFrameElement)); + if (!topFrameElement) { + return; + } + + topFrameElement->GetAttribute(NS_LITERAL_STRING("mozpresentation"), aPresentationUrl); +} + +/* static */ nsIDocShell* +nsContentUtils::GetDocShellForEventTarget(EventTarget* aTarget) +{ + nsCOMPtr<nsPIDOMWindowInner> innerWindow; + + if (nsCOMPtr<nsINode> node = do_QueryInterface(aTarget)) { + bool ignore; + innerWindow = + do_QueryInterface(node->OwnerDoc()->GetScriptHandlingObject(ignore)); + } else if ((innerWindow = do_QueryInterface(aTarget))) { + // Nothing else to do + } else { + nsCOMPtr<DOMEventTargetHelper> helper = do_QueryInterface(aTarget); + if (helper) { + innerWindow = helper->GetOwner(); + } + } + + if (innerWindow) { + return innerWindow->GetDocShell(); + } + + return nullptr; +} + +/* + * Note: this function only relates to figuring out HTTPS state, which is an + * input to the Secure Context algorithm. We are not actually implementing any + * part of the Secure Context algorithm itself here. + * + * This is a bit of a hack. Ideally we'd propagate HTTPS state through + * nsIChannel as described in the Fetch and HTML specs, but making channels + * know about whether they should inherit HTTPS state, propagating information + * about who the channel's "client" is, exposing GetHttpsState API on channels + * and modifying the various cache implementations to store and retrieve HTTPS + * state involves a huge amount of code (see bug 1220687). We avoid that for + * now using this function. + * + * This function takes advantage of the observation that we can return true if + * nsIContentSecurityManager::IsOriginPotentiallyTrustworthy returns true for + * the document's origin (e.g. the origin has a scheme of 'https' or host + * 'localhost' etc.). Since we generally propagate a creator document's origin + * onto data:, blob:, etc. documents, this works for them too. + * + * The scenario where this observation breaks down is sandboxing without the + * 'allow-same-origin' flag, since in this case a document is given a unique + * origin (IsOriginPotentiallyTrustworthy would return false). We handle that + * by using the origin that the document would have had had it not been + * sandboxed. + * + * DEFICIENCIES: Note that this function uses nsIScriptSecurityManager's + * getChannelResultPrincipalIfNotSandboxed, and that method's ignoring of + * sandboxing is limited to the immediate sandbox. In the case that aDocument + * should inherit its origin (e.g. data: URI) but its parent has ended up + * with a unique origin due to sandboxing further up the parent chain we may + * end up returning false when we would ideally return true (since we will + * examine the parent's origin for 'https' and not finding it.) This means + * that we may restrict the privileges of some pages unnecessarily in this + * edge case. + */ +/* static */ bool +nsContentUtils::HttpsStateIsModern(nsIDocument* aDocument) +{ + if (!aDocument) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal(); + + if (principal->GetIsSystemPrincipal()) { + return true; + } + + // If aDocument is sandboxed, try and get the principal that it would have + // been given had it not been sandboxed: + if (principal->GetIsNullPrincipal() && + (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN)) { + nsIChannel* channel = aDocument->GetChannel(); + if (channel) { + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsContentUtils::GetSecurityManager(); + nsresult rv = + ssm->GetChannelResultPrincipalIfNotSandboxed(channel, + getter_AddRefs(principal)); + if (NS_FAILED(rv)) { + return false; + } + if (principal->GetIsSystemPrincipal()) { + // If a document with the system principal is sandboxing a subdocument + // that would normally inherit the embedding element's principal (e.g. + // a srcdoc document) then the embedding document does not trust the + // content that is written to the embedded document. Unlike when the + // embedding document is https, in this case we have no indication as + // to whether the embedded document's contents are delivered securely + // or not, and the sandboxing would possibly indicate that they were + // not. To play it safe we return false here. (See bug 1162772 + // comment 73-80.) + return false; + } + } + } + + if (principal->GetIsNullPrincipal()) { + return false; + } + + MOZ_ASSERT(principal->GetIsCodebasePrincipal()); + + nsCOMPtr<nsIContentSecurityManager> csm = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + NS_WARNING_ASSERTION(csm, "csm is null"); + if (csm) { + bool isTrustworthyOrigin = false; + csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); + if (isTrustworthyOrigin) { + return true; + } + } + + return false; +} + +/* static */ CustomElementDefinition* +nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc, + const nsAString& aLocalName, + uint32_t aNameSpaceID, + const nsAString* aIs) +{ + MOZ_ASSERT(aDoc); + + // To support imported document. + nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument(); + + if (aNameSpaceID != kNameSpaceID_XHTML || + !doc->GetDocShell()) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); + if (!window) { + return nullptr; + } + + RefPtr<CustomElementRegistry> registry(window->CustomElements()); + if (!registry) { + return nullptr; + } + + return registry->LookupCustomElementDefinition(aLocalName, aIs); +} + +/* static */ void +nsContentUtils::SetupCustomElement(Element* aElement, + const nsAString* aTypeExtension) +{ + MOZ_ASSERT(aElement); + + nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc(); + + if (!doc) { + return; + } + + // To support imported document. + doc = doc->MasterDocument(); + + if (aElement->GetNameSpaceID() != kNameSpaceID_XHTML || + !doc->GetDocShell()) { + return; + } + + nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); + if (!window) { + return; + } + + RefPtr<CustomElementRegistry> registry(window->CustomElements()); + if (!registry) { + return; + } + + return registry->SetupCustomElement(aElement, aTypeExtension); +} + +/* static */ void +nsContentUtils::EnqueueLifecycleCallback(nsIDocument* aDoc, + nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + CustomElementDefinition* aDefinition) +{ + MOZ_ASSERT(aDoc); + + // To support imported document. + nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument(); + + if (!doc->GetDocShell()) { + return; + } + + nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); + if (!window) { + return; + } + + RefPtr<CustomElementRegistry> registry(window->CustomElements()); + if (!registry) { + return; + } + + registry->EnqueueLifecycleCallback(aType, aCustomElement, aArgs, aDefinition); +} + +/* static */ void +nsContentUtils::GetCustomPrototype(nsIDocument* aDoc, + int32_t aNamespaceID, + nsIAtom* aAtom, + JS::MutableHandle<JSObject*> aPrototype) +{ + MOZ_ASSERT(aDoc); + + // To support imported document. + nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument(); + + if (aNamespaceID != kNameSpaceID_XHTML || + !doc->GetDocShell()) { + return; + } + + nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow()); + if (!window) { + return; + } + + RefPtr<CustomElementRegistry> registry(window->CustomElements()); + if (!registry) { + return; + } + + return registry->GetCustomPrototype(aAtom, aPrototype); +} + +/* static */ bool +nsContentUtils::AttemptLargeAllocationLoad(nsIHttpChannel* aChannel) +{ + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsILoadGroup> loadGroup; + nsresult rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (NS_WARN_IF(NS_FAILED(rv) || !loadGroup)) { + return false; + } + + nsCOMPtr<nsIInterfaceRequestor> callbacks; + rv = loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (NS_WARN_IF(NS_FAILED(rv) || !callbacks)) { + return false; + } + + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); + if (NS_WARN_IF(!loadContext)) { + return false; + } + + nsCOMPtr<mozIDOMWindowProxy> window; + rv = loadContext->GetAssociatedWindow(getter_AddRefs(window)); + if (NS_WARN_IF(NS_FAILED(rv) || !window)) { + return false; + } + + nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::From(window); + if (NS_WARN_IF(!outer)) { + return false; + } + + nsIDocShell* docShell = outer->GetDocShell(); + nsIDocument* doc = outer->GetExtantDoc(); + + // If the docshell is not allowed to change process, report an error based on + // the reason + const char* errorName = nullptr; + switch (docShell->GetProcessLockReason()) { + case nsIDocShell::PROCESS_LOCK_NON_CONTENT: + errorName = "LargeAllocationNonE10S"; + break; + case nsIDocShell::PROCESS_LOCK_IFRAME: + errorName = "LargeAllocationIFrame"; + break; + case nsIDocShell::PROCESS_LOCK_RELATED_CONTEXTS: + errorName = "LargeAllocationRelatedBrowsingContexts"; + break; + case nsIDocShell::PROCESS_LOCK_NONE: + // Don't print a warning, we're allowed to change processes! + break; + default: + MOZ_ASSERT(false, "Should be unreachable!"); + return false; + } + + if (errorName) { + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), + doc, + nsContentUtils::eDOM_PROPERTIES, + errorName); + } + + return false; + } + + // Get the request method, and check if it is a GET request. If it is not GET, + // then we cannot perform a large allocation load. + nsAutoCString requestMethod; + rv = aChannel->GetRequestMethod(requestMethod); + NS_ENSURE_SUCCESS(rv, false); + + if (NS_WARN_IF(!requestMethod.LowerCaseEqualsLiteral("get"))) { + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), + doc, + nsContentUtils::eDOM_PROPERTIES, + "LargeAllocationNonGetRequest"); + } + return false; + } + + TabChild* tabChild = TabChild::GetFrom(outer); + NS_ENSURE_TRUE(tabChild, false); + + if (tabChild->TakeIsFreshProcess()) { + NS_WARNING("Already in a fresh process, ignoring Large-Allocation header!"); + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::infoFlag, + NS_LITERAL_CSTRING("DOM"), + doc, + nsContentUtils::eDOM_PROPERTIES, + "LargeAllocationSuccess"); + } + return false; + } + + // At this point the fress process load should succeed! We just need to get + // ourselves a nsIWebBrowserChrome3 to ask to perform the reload. We should + // have one, as we have already confirmed that we are running in a content + // process. + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + NS_ENSURE_TRUE(treeOwner, false); + + nsCOMPtr<nsIWebBrowserChrome3> wbc3 = do_GetInterface(treeOwner); + NS_ENSURE_TRUE(wbc3, false); + + nsCOMPtr<nsIURI> uri; + rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_TRUE(uri, false); + + nsCOMPtr<nsIURI> referrer; + rv = aChannel->GetReferrer(getter_AddRefs(referrer)); + NS_ENSURE_SUCCESS(rv, false); + + // Actually perform the cross process load + bool reloadSucceeded = false; + rv = wbc3->ReloadInFreshProcess(docShell, uri, referrer, &reloadSucceeded); + NS_ENSURE_SUCCESS(rv, false); + + return reloadSucceeded; +} |