gecko/dom/base/nsContentUtils.cpp
Nicholas Nethercote d130e7465e Bug 1189156 (part 1) - Don't use enumeration style for PLDHashTable::SizeOf{In,Ex}cludingThis(). r=froydnj.
After this change, we have PLDHashTable::ShallowSizeOf{In,Ex}cludingThis(),
which don't do anything to measure children. (They can be combined with
iteration to measure children.)

This patch also removes the PL_DHashTableSizeOf{In,Ex}cludingThis() functions.
They're not necessary because the methods can be used instead.

Finally, the patch deliberately converts some SizeOfExcludingThis() calls to
SizeOfIncludingThis(). These are all done on heap pointers so this change is
valid.
2015-07-29 22:28:20 -07:00

7996 lines
238 KiB
C++

/* -*- 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 "prprf.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 "gfxPrefs.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/DocumentFragment.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.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/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.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 "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 "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 "nsIAppShell.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsICategoryManager.h"
#include "nsIChannelEventSink.h"
#include "nsICharsetDetectionObserver.h"
#include "nsIChromeRegistry.h"
#include "nsIConsoleService.h"
#include "nsIContent.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIContentSink.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "nsIDocumentEncoder.h"
#include "nsIDOMChromeWindow.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentType.h"
#include "nsIDOMEvent.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMHTMLFormElement.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsIDOMNode.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMScriptObjectFactory.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 "nsIStreamConverterService.h"
#include "nsIStringBundle.h"
#include "nsIURI.h"
#include "nsIURIWithPrincipal.h"
#include "nsIURL.h"
#include "nsIWebNavigation.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 "nsSVGFeatures.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 "nsWrapperCacheInlines.h"
#include "nsXULPopupManager.h"
#include "xpcprivate.h" // nsXPConnect
#include "HTMLSplitOnSpacesTokenizer.h"
#include "nsContentTypeParser.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::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;
nsTArray< nsCOMPtr<nsIRunnable> >* 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::sTrustedFullScreenOnly = true;
bool nsContentUtils::sIsCutCopyAllowed = true;
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;
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
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
// Subset of http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
enum AutocompleteFieldName
{
#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
{
#define AUTOCOMPLETE_FIELD_HINT(name_, value_) \
eAutocompleteFieldHint_##name_,
#include "AutocompleteFieldList.h"
#undef AUTOCOMPLETE_FIELD_HINT
};
enum AutocompleteFieldContactHint
{
#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
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteContactFieldNameTable[] = {
#define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
{ value_, eAutocompleteFieldName_##name_ },
#include "AutocompleteFieldList.h"
#undef AUTOCOMPLETE_CONTACT_FIELD_NAME
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteFieldHintTable[] = {
#define AUTOCOMPLETE_FIELD_HINT(name_, value_) \
{ value_, eAutocompleteFieldHint_##name_ },
#include "AutocompleteFieldList.h"
#undef AUTOCOMPLETE_FIELD_HINT
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteContactFieldHintTable[] = {
#define AUTOCOMPLETE_FIELD_CONTACT_HINT(name_, value_) \
{ value_, eAutocompleteFieldContactHint_##name_ },
#include "AutocompleteFieldList.h"
#undef AUTOCOMPLETE_FIELD_CONTACT_HINT
{ 0 }
};
namespace {
static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_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;
return MOZ_COLLECT_REPORT(
"explicit/dom/event-listener-managers-hash", KIND_HEAP, UNITS_BYTES,
amount,
"Memory used by the event listener manager's hash table.");
}
};
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:
nsRefPtr<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.
nsRefPtr<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 =
{
PL_DHashVoidPtrKeyStub,
PL_DHashMatchEntryStub,
PL_DHashMoveEntryStub,
EventListenerManagerHashClearEntry,
EventListenerManagerHashInitEntry
};
sEventListenerManagersHash =
new PLDHashTable(&hash_table_ops, sizeof(EventListenerManagerMapEntry));
RegisterStrongMemoryReporter(new DOMEventListenerManagersHashReporter());
}
sBlockedScriptRunners = new nsTArray< nsCOMPtr<nsIRunnable> >;
Preferences::AddBoolVarCache(&sAllowXULXBL_for_file,
"dom.allow_XUL_XBL_for_file");
Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled,
"full-screen-api.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(&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);
#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
"browser.dom.window.dump.enabled");
#endif
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(MOZ_UTF16("VK_SHIFT"), getter_Copies(shiftModifier));
bundle->GetStringFromName(MOZ_UTF16("VK_META"), getter_Copies(metaModifier));
bundle->GetStringFromName(MOZ_UTF16("VK_WIN"), getter_Copies(osModifier));
bundle->GetStringFromName(MOZ_UTF16("VK_ALT"), getter_Copies(altModifier));
bundle->GetStringFromName(MOZ_UTF16("VK_CONTROL"), getter_Copies(controlModifier));
bundle->GetStringFromName(MOZ_UTF16("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 ID_TO_EVENT to map id to only one atom.
static bool
ShouldAddEventToStringEventTable(const EventNameMapping& aMapping)
{
switch(aMapping.mId) {
#define ID_TO_EVENT(name_, id_, type_, struct_) \
case id_: return nsGkAtoms::on##name_ == aMapping.mAtom;
#include "mozilla/EventNameList.h"
#undef ID_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_, _id, _type, _class) \
{ nsGkAtoms::on##name_, _id, _type, _class },
#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_, _id, _type, _class)
#define TOUCH_EVENT(name_, _id, _type, _class) \
{ nsGkAtoms::on##name_, _id, _type, _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);
nsAString::const_iterator done_reading;
aString.EndReading(done_reading);
// for each chunk of |aString|...
uint32_t fragmentLength = 0;
nsAString::const_iterator iter;
for (aString.BeginReading(iter); iter != done_reading;
iter.advance(int32_t(fragmentLength))) {
fragmentLength = uint32_t(iter.size_forward());
const char16_t* c = iter.get();
const char16_t* fragmentEnd = c + fragmentLength;
// for each character in this chunk...
while (c < fragmentEnd) {
if (*c++ & 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;
}
bool negate = false;
if (*iter == char16_t('-')) {
negate = true;
++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'));
++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() && negate) {
value = -value;
// Checking the special case of -0.
if (value == 0) {
result |= eParseHTMLInteger_NonStandard;
}
}
if (value.isValid() &&
(leadingZeros > 1 || (leadingZeros == 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);
// Point to first character after the ampersand.
++chunkEnd;
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 sandboxAttr the sandbox attribute
* @return the set of flags (0 if sandboxAttr is null)
*/
uint32_t
nsContentUtils::ParseSandboxAttributeToFlags(const nsAttrValue* sandboxAttr)
{
// No sandbox attribute, no sandbox flags.
if (!sandboxAttr) { return 0; }
// Start off by setting all the restriction flags.
uint32_t out = SANDBOXED_NAVIGATION
| SANDBOXED_AUXILIARY_NAVIGATION
| SANDBOXED_TOPLEVEL_NAVIGATION
| SANDBOXED_PLUGINS
| SANDBOXED_ORIGIN
| SANDBOXED_FORMS
| SANDBOXED_SCRIPTS
| SANDBOXED_AUTOMATIC_FEATURES
| SANDBOXED_POINTER_LOCK
| SANDBOXED_DOMAIN;
// Macro for updating the flag according to the keywords
#define IF_KEYWORD(atom, flags) \
if (sandboxAttr->Contains(nsGkAtoms::atom, eIgnoreCase)) { out &= ~(flags); }
IF_KEYWORD(allowsameorigin, SANDBOXED_ORIGIN)
IF_KEYWORD(allowforms, SANDBOXED_FORMS)
IF_KEYWORD(allowscripts, SANDBOXED_SCRIPTS | SANDBOXED_AUTOMATIC_FEATURES)
IF_KEYWORD(allowtopnavigation, SANDBOXED_TOPLEVEL_NAVIGATION)
IF_KEYWORD(allowpointerlock, SANDBOXED_POINTER_LOCK)
IF_KEYWORD(allowpopups, SANDBOXED_AUXILIARY_NAVIGATION)
return out;
#undef IF_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 punctuation
* mark classes which CSS says should be part of the first-letter.
* See http://www.w3.org/TR/CSS2/selector.html#first-letter and
* http://www.w3.org/TR/selectors/#first-letter
*/
// static
bool
nsContentUtils::IsFirstLetterPunctuation(uint32_t aChar)
{
uint8_t cat = mozilla::unicode::GetGeneralCategory(aChar);
return (cat == HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION || // Ps
cat == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION || // Pe
cat == HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION || // Pi
cat == HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION || // Pf
cat == HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION); // Po
}
// 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 */
void
nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI)
{
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.FindChar('#') != kNotFound) {
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,
nsIDOMWindow *aWindow)
{
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(aWindow, 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);
if (IsCallerChrome()) {
return NS_OK;
}
/*
* 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(nsPIDOMWindow* aWindow)
{
nsCOMPtr<nsIScriptObjectPrincipal> scriptObject =
do_QueryInterface(aWindow->IsOuterWindow() ?
aWindow->GetCurrentInnerWindow() : 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<nsPIDOMWindow> 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);
}
bool
nsContentUtils::IsImageSrcSetDisabled()
{
return Preferences::GetBool("dom.disable_image_src_set") &&
!IsCallerChrome();
}
// static
bool
nsContentUtils::LookupBindingMember(JSContext* aCx, nsIContent *aContent,
JS::Handle<jsid> aId,
JS::MutableHandle<JSPropertyDescriptor> 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
nsAutoTArray<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;
}
nsAutoTArray<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) {
return GetSystemPrincipal();
}
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());
MOZ_ASSERT(JS_GetObjectRuntime(aObj) == CycleCollectedJSRuntime::Get()->Runtime());
// 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)
{
// The custom element name identifies a custom element and is a sequence of
// alphanumeric ASCII characters that must match the NCName production and
// contain a U+002D HYPHEN-MINUS character.
nsDependentAtomString str(aName);
const char16_t* colon;
if (NS_FAILED(nsContentUtils::CheckQName(str, false, &colon)) || colon ||
str.FindChar('-') == -1) {
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);
if (*aNamespace == kNameSpaceID_Unknown)
return NS_ERROR_FAILURE;
*aLocalName = NS_NewAtom(Substring(colon + 1, end)).take();
}
else {
*aNamespace = kNameSpaceID_None;
*aLocalName = NS_NewAtom(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 = do_GetAtom(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_NewAtom(Substring(prefixStart, pos)).take();
}
else {
nameEnd = pos;
*aPrefix = nullptr;
}
}
else {
*aNameSpaceID = kNameSpaceID_None;
nameStart = aExpatName;
nameEnd = pos;
*aPrefix = nullptr;
}
*aLocalName = NS_NewAtom(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
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<nsPIDOMWindow> 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::Singleton();
}
bool isPrivate = IsInPrivateBrowsing(aDoc);
return isPrivate ? imgLoader::PBSingleton() : imgLoader::Singleton();
}
// static
imgLoader*
nsContentUtils::GetImgLoaderForChannel(nsIChannel* aChannel,
nsIDocument* aContext)
{
NS_ENSURE_TRUE(!DocumentInactiveForImageLoads(aContext), nullptr);
if (!aChannel)
return imgLoader::Singleton();
nsCOMPtr<nsILoadContext> context;
NS_QueryNotificationCallbacks(aChannel, context);
return context && context->UsePrivateBrowsing() ? imgLoader::PBSingleton() : imgLoader::Singleton();
}
// 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;
nsresult rv = cache->FindEntryProperties(aURI, getter_AddRefs(props));
return (NS_SUCCEEDED(rv) && props);
}
// static
nsresult
nsContentUtils::LoadImage(nsIURI* aURI, 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(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 */
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);
nsRefPtr<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 */ 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::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument,
nsresult aError)
{
const char* messageName = nullptr;
if (aError == NS_ERROR_INTERCEPTION_FAILED) {
messageName = "InterceptionFailed";
} else if (aError == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED) {
messageName = "OpaqueInterceptionDisabled";
} else if (aError == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE) {
messageName = "BadOpaqueInterceptionRequestMode";
} else if (aError == NS_ERROR_INTERCEPTED_ERROR_RESPONSE) {
messageName = "InterceptedErrorResponse";
} else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) {
messageName = "InterceptedUsedResponse";
} else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) {
messageName = "ClientRequestOpaqueInterception";
}
if (messageName) {
return ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Service Worker Interception"),
aDocument,
nsContentUtils::eDOM_PROPERTIES,
messageName);
}
return NS_OK;
}
/* 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)
{
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) {
JSContext *cx = GetCurrentJSContext();
if (cx) {
nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber);
}
}
if (spec.IsEmpty() && aURI)
aURI->GetSpec(spec);
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;
}
}
va_list args;
va_start(args, aMsg);
char* formatted = PR_vsmprintf(aMsg, args);
va_end(args);
if (!formatted) {
return;
}
sConsoleService->LogStringMessage(NS_ConvertUTF8toUTF16(formatted).get());
PR_smprintf_free(formatted);
}
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::IsPlainTextType(const nsACString& aContentType)
{
return aContentType.EqualsLiteral(TEXT_PLAIN) ||
aContentType.EqualsLiteral(TEXT_CSS) ||
aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
aContentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
aContentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_JSON);
}
bool
nsContentUtils::GetWrapperSafeScriptFilename(nsIDocument *aDocument,
nsIURI *aURI,
nsACString& aScriptURI)
{
bool scriptFileNameModified = false;
aURI->GetSpec(aScriptURI);
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;
docURI->GetSpec(spec);
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
uint32_t
nsContentUtils::GetEventId(nsIAtom* aName)
{
if (aName) {
EventNameMapping mapping;
if (sAtomEventTable->Get(aName, &mapping)) {
return mapping.mId;
}
}
return NS_USER_DEFINED_EVENT;
}
// static
mozilla::EventClassID
nsContentUtils::GetEventClassID(const nsAString& aName)
{
EventNameMapping mapping;
if (sStringEventTable->Get(aName, &mapping))
return mapping.mEventClassID;
return eBasicEventClass;
}
nsIAtom*
nsContentUtils::GetEventIdAndAtom(const nsAString& aName,
mozilla::EventClassID aEventClassID,
uint32_t* aEventID)
{
EventNameMapping mapping;
if (sStringEventTable->Get(aName, &mapping)) {
*aEventID = mapping.mEventClassID == aEventClassID ? mapping.mId :
NS_USER_DEFINED_EVENT;
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);
}
}
*aEventID = NS_USER_DEFINED_EVENT;
nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aName);
sUserDefinedEvents->AppendObject(atom);
mapping.mAtom = atom;
mapping.mId = NS_USER_DEFINED_EVENT;
mapping.mType = EventNameType_None;
mapping.mEventClassID = eBasicEventClass;
sStringEventTable->Put(aName, mapping);
return mapping.mAtom;
}
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);
rv = event->InitEvent(aEventName, aCanBubble, aCancelable);
NS_ENSURE_SUCCESS(rv, rv);
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->GetInternalNSEvent()->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;
}
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(do_GetAtom(aId));
if (!id) {
// OOM, so just bail
return nullptr;
}
return MatchElementId(aContent, id);
}
// 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.
nsPIDOMWindow* 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->IsInDoc()) {
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)
{
nsPIDOMWindow* 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, NS_MUTATION_NODEREMOVED);
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->IsInDoc() &&
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;
}
EventListenerManagerMapEntry *entry =
static_cast<EventListenerManagerMapEntry *>
(PL_DHashTableSearch(sEventListenerManagersHash, 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;
}
EventListenerManagerMapEntry *entry =
static_cast<EventListenerManagerMapEntry *>
(PL_DHashTableAdd(sEventListenerManagersHash, 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;
}
EventListenerManagerMapEntry *entry =
static_cast<EventListenerManagerMapEntry *>
(PL_DHashTableSearch(sEventListenerManagersHash, aNode));
if (entry) {
return entry->mListenerManager;
}
return nullptr;
}
/* static */
void
nsContentUtils::RemoveListenerManager(nsINode *aNode)
{
if (sEventListenerManagersHash) {
EventListenerManagerMapEntry *entry =
static_cast<EventListenerManagerMapEntry *>
(PL_DHashTableSearch(sEventListenerManagersHash, aNode));
if (entry) {
nsRefPtr<EventListenerManager> listenerManager;
listenerManager.swap(entry->mListenerManager);
// Remove the entry and *then* do operations that could cause further
// modification of sEventListenerManagersHash. See bug 334177.
PL_DHashTableRawRemove(sEventListenerManagersHash, 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) {
nsRefPtr<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();
}
nsAutoTArray<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 nsAttrName* name = content->GetAttrNameAt(index);
if (name->NamespaceEquals(kNameSpaceID_XMLNS)) {
content->GetAttr(kNameSpaceID_XMLNS, name->LocalName(), 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 =
do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID);
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;
}
nsRefPtr<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 nsRunnable {
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() {
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;
}
/* static */
nsresult
nsContentUtils::CheckSecurityBeforeLoad(nsIURI* aURIToLoad,
nsIPrincipal* aLoadingPrincipal,
uint32_t aCheckLoadFlags,
bool aAllowData,
uint32_t aContentPolicyType,
nsISupports* aContext,
const nsAFlatCString& aMimeGuess,
nsISupports* aExtra)
{
NS_PRECONDITION(aLoadingPrincipal, "Must have a loading principal here");
if (aLoadingPrincipal == sSystemPrincipal) {
return NS_OK;
}
// XXXbz do we want to fast-path skin stylesheets loading XBL here somehow?
// CheckLoadURIWithPrincipal
nsresult rv = sSecurityManager->
CheckLoadURIWithPrincipal(aLoadingPrincipal, aURIToLoad, aCheckLoadFlags);
NS_ENSURE_SUCCESS(rv, rv);
// Content Policy
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(aContentPolicyType,
aURIToLoad,
aLoadingPrincipal,
aContext,
aMimeGuess,
aExtra,
&shouldLoad,
GetContentPolicy(),
sSecurityManager);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_CONTENT_BLOCKED;
}
// Same Origin
if ((aAllowData && SchemeIs(aURIToLoad, "data")) ||
((aCheckLoadFlags & nsIScriptSecurityManager::ALLOW_CHROME) &&
SchemeIs(aURIToLoad, "chrome"))) {
return NS_OK;
}
return aLoadingPrincipal->CheckMayLoad(aURIToLoad, true, false);
}
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 bool
HasASCIIDigit(const nsTArray<nsShortcutCandidate>& aCandidates)
{
for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
uint32_t ch = aCandidates[i].mCharCode;
if (ch >= '0' && ch <= '9')
return true;
}
return false;
}
static bool
CharsCaseInsensitiveEqual(uint32_t aChar1, uint32_t aChar2)
{
return aChar1 == aChar2 ||
(IS_IN_BMP(aChar1) && IS_IN_BMP(aChar2) &&
ToLowerCase(char16_t(aChar1)) == ToLowerCase(char16_t(aChar2)));
}
static bool
IsCaseChangeableChar(uint32_t aChar)
{
return IS_IN_BMP(aChar) &&
ToLowerCase(char16_t(aChar)) != ToUpperCase(char16_t(aChar));
}
/* static */
void
nsContentUtils::GetAccelKeyCandidates(nsIDOMKeyEvent* aDOMKeyEvent,
nsTArray<nsShortcutCandidate>& aCandidates)
{
NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty");
nsAutoString eventType;
aDOMKeyEvent->GetType(eventType);
// Don't process if aDOMKeyEvent is not a keypress event.
if (!eventType.EqualsLiteral("keypress"))
return;
WidgetKeyboardEvent* nativeKeyEvent =
aDOMKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
if (nativeKeyEvent) {
NS_ASSERTION(nativeKeyEvent->mClass == eKeyboardEventClass,
"wrong type of native event");
// nsShortcutCandidate::mCharCode is a candidate charCode.
// nsShoftcutCandidate::mIgnoreShift means the mCharCode should be tried to
// execute a command with/without shift key state. If this is TRUE, the
// shifted key state should be ignored. Otherwise, don't ignore the state.
// the priority of the charCodes are (shift key is not pressed):
// 0: charCode/false,
// 1: unshiftedCharCodes[0]/false, 2: unshiftedCharCodes[1]/false...
// the priority of the charCodes are (shift key is pressed):
// 0: charCode/false,
// 1: shiftedCharCodes[0]/false, 2: shiftedCharCodes[0]/true,
// 3: shiftedCharCodes[1]/false, 4: shiftedCharCodes[1]/true...
if (nativeKeyEvent->charCode) {
nsShortcutCandidate key(nativeKeyEvent->charCode, false);
aCandidates.AppendElement(key);
}
uint32_t len = nativeKeyEvent->alternativeCharCodes.Length();
if (!nativeKeyEvent->IsShift()) {
for (uint32_t i = 0; i < len; ++i) {
uint32_t ch =
nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode;
if (!ch || ch == nativeKeyEvent->charCode)
continue;
nsShortcutCandidate key(ch, false);
aCandidates.AppendElement(key);
}
// If unshiftedCharCodes doesn't have numeric but shiftedCharCode has it,
// this keyboard layout is AZERTY or similar layout, probably.
// In this case, Accel+[0-9] should be accessible without shift key.
// However, the priority should be lowest.
if (!HasASCIIDigit(aCandidates)) {
for (uint32_t i = 0; i < len; ++i) {
uint32_t ch =
nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode;
if (ch >= '0' && ch <= '9') {
nsShortcutCandidate key(ch, false);
aCandidates.AppendElement(key);
break;
}
}
}
} else {
for (uint32_t i = 0; i < len; ++i) {
uint32_t ch = nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode;
if (!ch)
continue;
if (ch != nativeKeyEvent->charCode) {
nsShortcutCandidate key(ch, false);
aCandidates.AppendElement(key);
}
// If the char is an alphabet, the shift key state should not be
// ignored. E.g., Ctrl+Shift+C should not execute Ctrl+C.
// And checking the charCode is same as unshiftedCharCode too.
// E.g., for Ctrl+Shift+(Plus of Numpad) should not run Ctrl+Plus.
uint32_t unshiftCh =
nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode;
if (CharsCaseInsensitiveEqual(ch, unshiftCh))
continue;
// On the Hebrew keyboard layout on Windows, the unshifted char is a
// localized character but the shifted char is a Latin alphabet,
// then, we should not execute without the shift state. See bug 433192.
if (IsCaseChangeableChar(ch))
continue;
// Setting the alternative charCode candidates for retry without shift
// key state only when the shift key is pressed.
nsShortcutCandidate key(ch, true);
aCandidates.AppendElement(key);
}
}
// Special case for "Space" key. With some keyboard layouts, "Space" with
// or without Shift key causes non-ASCII space. For such keyboard layouts,
// we should guarantee that the key press works as an ASCII white space key
// press.
if (nativeKeyEvent->mCodeNameIndex == CODE_NAME_INDEX_Space &&
nativeKeyEvent->charCode != static_cast<uint32_t>(' ')) {
nsShortcutCandidate spaceKey(static_cast<uint32_t>(' '), false);
aCandidates.AppendElement(spaceKey);
}
} else {
uint32_t charCode;
aDOMKeyEvent->GetCharCode(&charCode);
if (charCode) {
nsShortcutCandidate key(charCode, false);
aCandidates.AppendElement(key);
}
}
}
/* static */
void
nsContentUtils::GetAccessKeyCandidates(WidgetKeyboardEvent* aNativeKeyEvent,
nsTArray<uint32_t>& aCandidates)
{
NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty");
// return the lower cased charCode candidates for access keys.
// the priority of the charCodes are:
// 0: charCode, 1: unshiftedCharCodes[0], 2: shiftedCharCodes[0]
// 3: unshiftedCharCodes[1], 4: shiftedCharCodes[1],...
if (aNativeKeyEvent->charCode) {
uint32_t ch = aNativeKeyEvent->charCode;
if (IS_IN_BMP(ch))
ch = ToLowerCase(char16_t(ch));
aCandidates.AppendElement(ch);
}
for (uint32_t i = 0;
i < aNativeKeyEvent->alternativeCharCodes.Length(); ++i) {
uint32_t ch[2] =
{ aNativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode,
aNativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode };
for (uint32_t j = 0; j < 2; ++j) {
if (!ch[j])
continue;
if (IS_IN_BMP(ch[j]))
ch[j] = ToLowerCase(char16_t(ch[j]));
// Don't append the charCode that was already appended.
if (aCandidates.IndexOf(ch[j]) == aCandidates.NoIndex)
aCandidates.AppendElement(ch[j]);
}
}
// Special case for "Space" key. With some keyboard layouts, "Space" with
// or without Shift key causes non-ASCII space. For such keyboard layouts,
// we should guarantee that the key press works as an ASCII white space key
// press.
if (aNativeKeyEvent->mCodeNameIndex == CODE_NAME_INDEX_Space &&
aNativeKeyEvent->charCode != static_cast<uint32_t>(' ')) {
aCandidates.AppendElement(static_cast<uint32_t>(' '));
}
return;
}
/* 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 */
void
nsContentUtils::WarnScriptWasIgnored(nsIDocument* aDocument)
{
nsAutoString msg;
if (aDocument) {
nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
if (uri) {
nsCString spec;
uri->GetSpec(spec);
msg.Append(NS_ConvertUTF8toUTF16(spec));
msg.AppendLiteral(" : ");
}
}
msg.AppendLiteral("Unable to run script because scripts are blocked internally.");
LogSimpleConsoleError(msg, "DOM");
}
/* static */
bool
nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable)
{
if (!aRunnable) {
return false;
}
if (sScriptBlockerCount) {
return sBlockedScriptRunners->AppendElement(aRunnable) != nullptr;
}
nsCOMPtr<nsIRunnable> run = aRunnable;
run->Run();
return true;
}
/* static */
void
nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
DispatchFailureHandling aHandling)
{
nsCOMPtr<nsIRunnable> runnable = aRunnable;
nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
if (!appShell) {
MOZ_ASSERT(aHandling == DispatchFailureHandling::IgnoreFailure);
return;
}
appShell->RunInStableState(runnable.forget());
}
void
nsContentUtils::EnterMicroTask()
{
MOZ_ASSERT(NS_IsMainThread());
++sMicroTaskLevel;
}
void
nsContentUtils::LeaveMicroTask()
{
MOZ_ASSERT(NS_IsMainThread());
if (--sMicroTaskLevel == 0) {
PerformMainThreadMicroTaskCheckpoint();
nsDocument::ProcessBaseElementQueue();
}
}
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 = do_GetAtom(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 */
nsViewportInfo
nsContentUtils::GetViewportInfo(nsIDocument *aDocument,
const ScreenIntSize& aDisplaySize)
{
return aDocument->GetViewportInfo(aDisplaySize);
}
/* 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->dataTransfer || !aDragEvent->mFlags.mIsTrusted)
return NS_OK;
// For draggesture and 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->message != NS_DRAGDROP_GESTURE &&
aDragEvent->message != NS_DRAGDROP_START,
"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->target, aDragEvent->message, 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->message == NS_DRAGDROP_DROP ||
aDragEvent->message == NS_DRAGDROP_DRAGDROP) {
isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent);
}
// each event should use a clone of the original dataTransfer.
initialDataTransfer->Clone(aDragEvent->target, aDragEvent->message, aDragEvent->userCancelled,
isCrossDomainSubFrameDrop,
getter_AddRefs(aDragEvent->dataTransfer));
NS_ENSURE_TRUE(aDragEvent->dataTransfer, 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->message == NS_DRAGDROP_ENTER ||
aDragEvent->message == NS_DRAGDROP_OVER) {
uint32_t action, effectAllowed;
dragSession->GetDragAction(&action);
aDragEvent->dataTransfer->GetEffectAllowedInt(&effectAllowed);
aDragEvent->dataTransfer->SetDropEffectInt(FilterDropEffect(action, effectAllowed));
}
else if (aDragEvent->message == NS_DRAGDROP_DROP ||
aDragEvent->message == NS_DRAGDROP_DRAGDROP ||
aDragEvent->message == NS_DRAGDROP_END) {
// 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->dataTransfer->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->originalTarget);
if (!target) {
return true;
}
nsIDocument* targetDoc = target->OwnerDoc();
nsPIDOMWindow* 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;
}
nsresult
nsContentUtils::SplitURIAtHash(nsIURI *aURI,
nsACString &aBeforeHash,
nsACString &aAfterHash)
{
// See bug 225910 for why we can't do this using nsIURL.
aBeforeHash.Truncate();
aAfterHash.Truncate();
NS_ENSURE_ARG_POINTER(aURI);
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
int32_t index = spec.FindChar('#');
if (index == -1) {
index = spec.Length();
}
aBeforeHash.Assign(Substring(spec, 0, index));
aAfterHash.Assign(Substring(spec, index));
return NS_OK;
}
/* 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());
return sXPConnect->GetCurrentJSContext();
}
/* static */
JSContext *
nsContentUtils::GetSafeJSContext()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsInitialized());
return sXPConnect->GetSafeJSContext();
}
/* static */
JSContext *
nsContentUtils::GetDefaultJSContextForThread()
{
MOZ_ASSERT(IsInitialized());
if (MOZ_LIKELY(NS_IsMainThread())) {
return GetSafeJSContext();
} else {
return workers::GetCurrentThreadJSContext();
}
}
/* static */
JSContext *
nsContentUtils::GetCurrentJSContextForThread()
{
MOZ_ASSERT(IsInitialized());
if (MOZ_LIKELY(NS_IsMainThread())) {
return GetCurrentJSContext();
} else {
return workers::GetCurrentThreadJSContext();
}
}
/* static */
void
nsContentUtils::ASCIIToLower(nsAString& aStr)
{
char16_t* iter = aStr.BeginWriting();
char16_t* end = aStr.EndWriting();
MOZ_ASSERT(iter && end);
while (iter != end) {
char16_t c = *iter;
if (c >= 'A' && c <= 'Z') {
*iter = c + ('a' - 'A');
}
++iter;
}
}
/* static */
void
nsContentUtils::ASCIIToLower(const nsAString& aSource, nsAString& aDest)
{
uint32_t len = aSource.Length();
aDest.SetLength(len);
MOZ_ASSERT(aDest.Length() == len);
char16_t* dest = aDest.BeginWriting();
MOZ_ASSERT(dest);
const char16_t* iter = aSource.BeginReading();
const char16_t* end = aSource.EndReading();
while (iter != end) {
char16_t c = *iter;
*dest = (c >= 'A' && c <= 'Z') ?
c + ('a' - 'A') : c;
++iter;
++dest;
}
}
/* static */
void
nsContentUtils::ASCIIToUpper(nsAString& aStr)
{
char16_t* iter = aStr.BeginWriting();
char16_t* end = aStr.EndWriting();
MOZ_ASSERT(iter && end);
while (iter != end) {
char16_t c = *iter;
if (c >= 'a' && c <= 'z') {
*iter = c + ('A' - 'a');
}
++iter;
}
}
/* static */
void
nsContentUtils::ASCIIToUpper(const nsAString& aSource, nsAString& aDest)
{
uint32_t len = aSource.Length();
aDest.SetLength(len);
MOZ_ASSERT(aDest.Length() == len);
char16_t* dest = aDest.BeginWriting();
MOZ_ASSERT(dest);
const char16_t* iter = aSource.BeginReading();
const char16_t* end = aSource.EndReading();
while (iter != end) {
char16_t c = *iter;
*dest = (c >= 'a' && c <= 'z') ?
c + ('A' - 'a') : c;
++iter;
++dest;
}
}
/* 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);
}
}
}
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 */
nsIDocument*
nsContentUtils::GetDocumentFromScriptContext(nsIScriptContext *aScriptContext)
{
if (!aScriptContext) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(aScriptContext->GetGlobalObject());
if (!window) {
return nullptr;
}
return window->GetDoc();
}
/* 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)
{
NS_ENSURE_TRUE(mService, NS_ERROR_FAILURE);
return mService->GetParameterHTTP(mString, aParameterName,
EmptyCString(), false, nullptr,
aResult);
}
/* static */
bool
nsContentUtils::CanAccessNativeAnon()
{
return IsCallerChrome() || 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->GetWindow(),
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;
memcpy(JS_GetArrayBufferData(*aResult, nogc), aData.BeginReading(), dataLen);
}
return NS_OK;
}
// Initial implementation: only stores to RAM, not file
// TODO: bug 704447: large file support
nsresult
nsContentUtils::CreateBlobBuffer(JSContext* aCx,
nsISupports* aParent,
const nsACString& aData,
JS::MutableHandle<JS::Value> aBlob)
{
uint32_t blobLen = aData.Length();
void* blobData = malloc(blobLen);
nsRefPtr<Blob> blob;
if (blobData) {
memcpy(blobData, aData.BeginReading(), blobLen);
blob = mozilla::dom::Blob::CreateMemoryBlob(aParent, blobData, blobLen,
EmptyString());
} else {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!ToJSValue(aCx, blob, aBlob)) {
return NS_ERROR_FAILURE;
}
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;
}
nsPIDOMWindow* 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(nsIDOMWindow* aWindow)
{
nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
if (!piWin)
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 = piWin->GetDoc()) {
doc->FlushPendingNotifications(Flush_Layout);
}
nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell();
if (docShell) {
int32_t i = 0, i_end;
docShell->GetChildCount(&i_end);
for (; i < i_end; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
docShell->GetChildAt(i, getter_AddRefs(item));
nsCOMPtr<nsIDOMWindow> win = item->GetWindow();
if (win) {
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(MOZ_UTF16("\r\n"),
MOZ_UTF16("\n"),
aFallible)) {
return false;
}
// Mac linebreaks: Map any remaining CR to LF:
if (!aString.ReplaceSubstring(MOZ_UTF16("\r"),
MOZ_UTF16("\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,
bool* aAllowRetaining)
{
nsIWidget *widget = nsContentUtils::WidgetForDocument(aDoc);
if (widget) {
nsRefPtr<LayerManager> manager =
widget->GetLayerManager(aRequirePersistent ? nsIWidget::LAYER_MANAGER_PERSISTENT :
nsIWidget::LAYER_MANAGER_CURRENT,
aAllowRetaining);
return manager.forget();
}
return nullptr;
}
already_AddRefed<LayerManager>
nsContentUtils::LayerManagerForDocument(const nsIDocument *aDoc, bool *aAllowRetaining)
{
return LayerManagerForDocumentInternal(aDoc, false, aAllowRetaining);
}
already_AddRefed<LayerManager>
nsContentUtils::PersistentLayerManagerForDocument(nsIDocument *aDoc, bool *aAllowRetaining)
{
return LayerManagerForDocumentInternal(aDoc, true, aAllowRetaining);
}
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;
}
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;
}
bool
nsContentUtils::GetContentSecurityPolicy(nsIContentSecurityPolicy** aCSP)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = SubjectPrincipal()->GetCsp(getter_AddRefs(csp));
if (NS_FAILED(rv)) {
NS_ERROR("CSP: Failed to get CSP from principal.");
return false;
}
csp.forget(aCSP);
return true;
}
// 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();
// Failure to create or run the regexp results in the invalid pattern
// matching, but we can still report the error to the console.
jsapi.TakeOwnershipOfErrorReporting();
// 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_NewUCRegExpObjectNoStatics(cx,
static_cast<char16_t*>(aPattern.BeginWriting()),
aPattern.Length(), 0));
if (!re) {
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()
{
return (!IsCutCopyRestricted() &&
EventStateManager::IsHandlingUserInput()) ||
IsCallerChrome();
}
/* 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 void
CheckForWindowedPlugins(nsISupports* aSupports, void* aResult)
{
nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
if (!content || !content->IsInDoc()) {
return;
}
nsCOMPtr<nsIObjectLoadingContent> olc(do_QueryInterface(content));
if (!olc) {
return;
}
nsRefPtr<nsNPAPIPluginInstance> plugin;
olc->GetPluginInstance(getter_AddRefs(plugin));
if (!plugin) {
return;
}
bool isWindowless = false;
nsresult res = plugin->IsWindowless(&isWindowless);
if (NS_SUCCEEDED(res) && !isWindowless) {
*static_cast<bool*>(aResult) = true;
}
}
static bool
DocTreeContainsWindowedPlugins(nsIDocument* aDoc, void* aResult)
{
if (!nsContentUtils::IsChromeDoc(aDoc)) {
aDoc->EnumerateActivityObservers(CheckForWindowedPlugins, aResult);
}
if (*static_cast<bool*>(aResult)) {
// Return false to stop iteration, we found a windowed plugin.
return false;
}
aDoc->EnumerateSubDocuments(DocTreeContainsWindowedPlugins, aResult);
// Return false to stop iteration if we found a windowed plugin in
// the sub documents.
return !*static_cast<bool*>(aResult);
}
/* static */
bool
nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc)
{
#ifdef XP_MACOSX
// We control dispatch to all mac plugins.
return false;
#endif
bool result = false;
// Find the top of the document's branch, the child of the chrome document.
nsIDocument* doc = aDoc;
nsIDocument* parent = nullptr;
while (doc && (parent = doc->GetParentDocument()) && !IsChromeDoc(parent)) {
doc = parent;
}
DocTreeContainsWindowedPlugins(doc, &result);
return result;
}
/* static */
bool
nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
{
#ifdef XP_MACOSX
// We control dispatch to all mac plugins.
return false;
#endif
bool result = false;
CheckForWindowedPlugins(aContent, &result);
return result;
}
/* 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)) {
nsAutoTArray<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(nsIDOMWindow* aWin)
{
if (!aWin) {
return false;
}
nsCOMPtr<nsIDocument> pointerLockedDoc =
do_QueryReferent(EventStateManager::sPointerLockedDoc);
if (!pointerLockedDoc || !pointerLockedDoc->GetWindow()) {
return false;
}
nsCOMPtr<nsIDOMWindow> lockTop;
pointerLockedDoc->GetWindow()->GetScriptableTop(getter_AddRefs(lockTop));
nsCOMPtr<nsIDOMWindow> top;
aWin->GetScriptableTop(getter_AddRefs(top));
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->GetFirstPrincipalChild();
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()->GetFirstPrincipalChild()->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);
}
/* static */
nsRect
nsContentUtils::GetSelectionBoundingRect(Selection* aSel)
{
nsRect res;
// Bounding client rect may be empty after calling GetBoundingClientRect
// when range is collapsed. So we get caret's rect when range is
// collapsed.
if (aSel->IsCollapsed()) {
nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
if (frame) {
nsIFrame* relativeTo =
nsLayoutUtils::GetContainingBlockForClientRect(frame);
res = nsLayoutUtils::TransformFrameRectToAncestor(frame, res, relativeTo);
}
} else {
int32_t rangeCount = aSel->RangeCount();
nsLayoutUtils::RectAccumulator accumulator;
for (int32_t idx = 0; idx < rangeCount; ++idx) {
nsRange* range = aSel->GetRangeAt(idx);
nsRange::CollectClientRects(&accumulator, range,
range->GetStartParent(), range->StartOffset(),
range->GetEndParent(), range->EndOffset(),
true, false);
}
res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
accumulator.mResultRect;
}
return res;
}
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::InternalIsSupported(nsISupports* aObject,
const nsAString& aFeature,
const nsAString& aVersion)
{
// If it looks like an SVG feature string, forward to nsSVGFeatures
if (StringBeginsWith(aFeature,
NS_LITERAL_STRING("http://www.w3.org/TR/SVG"),
nsASCIICaseInsensitiveStringComparator()) ||
StringBeginsWith(aFeature, NS_LITERAL_STRING("org.w3c.dom.svg"),
nsASCIICaseInsensitiveStringComparator()) ||
StringBeginsWith(aFeature, NS_LITERAL_STRING("org.w3c.svg"),
nsASCIICaseInsensitiveStringComparator())) {
return (aVersion.IsEmpty() || aVersion.EqualsLiteral("1.0") ||
aVersion.EqualsLiteral("1.1")) &&
nsSVGFeatures::HasFeature(aObject, aFeature);
}
// Otherwise, we claim to support everything
return true;
}
bool
nsContentUtils::IsContentInsertionPoint(const 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.
if (aContent->IsHTMLElement(nsGkAtoms::content)) {
return static_cast<const HTMLContentElement*>(aContent)->IsInsertionPoint();
}
return false;
}
// 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", "content-transfer-encoding", "date", "dnt",
"expect", "host", "keep-alive", "origin", "referer", "te", "trailer",
"transfer-encoding", "upgrade", "user-agent", "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_ParseContentType(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::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;
}
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<nsIDOMWindow> window;
rv = loadContext->GetAssociatedWindow(getter_AddRefs(window));
if (NS_FAILED(rv) || !window) {
return 0;
}
nsCOMPtr<nsPIDOMWindow> pwindow = do_QueryInterface(window);
if (!pwindow) {
return 0;
}
nsPIDOMWindow* inner = pwindow->IsInnerWindow() ? pwindow.get() : pwindow->GetCurrentInnerWindow();
return inner ? inner->WindowID() : 0;
}
void
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;
}
if (aHost.FindChar(':') != -1) { // Escape IPv6 address
MOZ_ASSERT(!aHost.Length() ||
(aHost[0] !='[' && aHost[aHost.Length() - 1] != ']'));
aHost.Insert('[', 0);
aHost.Append(']');
}
}
void
nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost)
{
nsAutoCString hostname;
GetHostOrIPv6WithBrackets(aURI, hostname);
CopyUTF8toUTF16(hostname, aHost);
}
void
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) {
CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg);
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) {
aCallback(remote, aArg);
}
}
}
}
void
nsContentUtils::CallOnAllRemoteChildren(nsIDOMWindow* 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);
}
}
}
void
nsContentUtils::TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
nsTArray<IPCDataTransfer>& aIPC,
mozilla::dom::nsIContentChild* aChild,
mozilla::dom::nsIContentParent* aParent)
{
aIPC.Clear();
if (aTransferables) {
uint32_t transferableCount = 0;
aTransferables->Count(&transferableCount);
for (uint32_t i = 0; i < transferableCount; ++i) {
IPCDataTransfer* dt = aIPC.AppendElement();
nsCOMPtr<nsISupports> genericItem;
aTransferables->GetElementAt(i, getter_AddRefs(genericItem));
nsCOMPtr<nsITransferable> transferable(do_QueryInterface(genericItem));
TransferableToIPCTransferable(transferable, dt, aChild, aParent);
}
}
}
void
nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
IPCDataTransfer* aIPCDataTransfer,
mozilla::dom::nsIContentChild* aChild,
mozilla::dom::nsIContentParent* aParent)
{
MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
if (aTransferable) {
nsCOMPtr<nsISupportsArray> flavorList;
aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
if (flavorList) {
uint32_t flavorCount = 0;
flavorList->Count(&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() = nsCString(flavorStr);
item->data() = nsString(dataAsString);
} else if (ctext) {
nsAutoCString dataAsString;
ctext->GetData(dataAsString);
IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
item->flavor() = nsCString(flavorStr);
item->data() = nsCString(dataAsString);
} 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() = nsCString(flavorStr);
nsCString imageData;
NS_ConsumeStream(stream, UINT32_MAX, imageData);
item->data() = imageData;
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) {
mozilla::RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
surface->GetDataSurface();
size_t length;
int32_t stride;
mozilla::UniquePtr<char[]> surfaceData =
nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
item->flavor() = nsCString(flavorStr);
item->data() = nsCString(surfaceData.get(), length);
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) {
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);
blobImpl->LookupAndCacheIsDirectory();
} else {
blobImpl = do_QueryInterface(data);
}
if (blobImpl) {
IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
item->flavor() = nsCString(flavorStr);
if (aChild) {
item->data() =
mozilla::dom::BlobChild::GetOrCreate(aChild,
static_cast<BlobImpl*>(blobImpl.get()));
} else if (aParent) {
item->data() =
mozilla::dom::BlobParent::GetOrCreate(aParent,
static_cast<BlobImpl*>(blobImpl.get()));
}
} 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() = nsCString(flavorStr);
item->data() = NS_ConvertUTF8toUTF16(flavorStr);
}
}
}
}
}
}
}
mozilla::UniquePtr<char[]>
nsContentUtils::GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
size_t* aLength, int32_t* aStride)
{
mozilla::gfx::DataSourceSurface::MappedSurface map;
if (NS_WARN_IF(!aSurface->Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map))) {
return nullptr;
}
mozilla::gfx::IntSize size = aSurface->GetSize();
mozilla::CheckedInt32 requiredBytes =
mozilla::CheckedInt32(map.mStride) * mozilla::CheckedInt32(size.height);
size_t maxBufLen = requiredBytes.isValid() ? requiredBytes.value() : 0;
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.
mozilla::UniquePtr<char[]> surfaceData(new char[maxBufLen + 1]);
memcpy(surfaceData.get(), reinterpret_cast<char*>(map.mData), bufLen);
memset(surfaceData.get() + bufLen, 0, maxBufLen - bufLen + 1);
*aLength = maxBufLen;
*aStride = map.mStride;
aSurface->Unmap();
return surfaceData;
}
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 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,
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;
int32_t msg;
if (aType.EqualsLiteral("keydown"))
msg = NS_KEY_DOWN;
else if (aType.EqualsLiteral("keyup"))
msg = NS_KEY_UP;
else if (aType.EqualsLiteral("keypress"))
msg = NS_KEY_PRESS;
else
return NS_ERROR_FAILURE;
WidgetKeyboardEvent event(true, msg, aWidget);
event.modifiers = GetWidgetModifiers(aModifiers);
if (msg == NS_KEY_PRESS) {
event.keyCode = aCharCode ? 0 : aKeyCode;
event.charCode = aCharCode;
} else {
event.keyCode = aKeyCode;
event.charCode = 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.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
break;
case nsIDOMWindowUtils::KEY_FLAG_LOCATION_LEFT:
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
break;
case nsIDOMWindowUtils::KEY_FLAG_LOCATION_RIGHT:
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
break;
case nsIDOMWindowUtils::KEY_FLAG_LOCATION_NUMPAD:
event.location = 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.location = 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.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
break;
default:
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
break;
}
break;
}
event.refPoint.x = event.refPoint.y = 0;
event.time = PR_IntervalNow();
if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) {
event.mFlags.mIsSynthesizedForTests = true;
}
if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) {
event.mFlags.mDefaultPrevented = true;
}
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 aClickCount,
int32_t aModifiers,
bool aIgnoreRootScrollFrame,
float aPressure,
unsigned short aInputSourceArg,
bool aToWindow,
bool *aPreventDefault,
bool aIsSynthesized)
{
nsPoint offset;
nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
if (!widget)
return NS_ERROR_FAILURE;
int32_t msg;
bool contextMenuKey = false;
if (aType.EqualsLiteral("mousedown"))
msg = NS_MOUSE_BUTTON_DOWN;
else if (aType.EqualsLiteral("mouseup"))
msg = NS_MOUSE_BUTTON_UP;
else if (aType.EqualsLiteral("mousemove"))
msg = NS_MOUSE_MOVE;
else if (aType.EqualsLiteral("mouseover"))
msg = NS_MOUSE_ENTER_WIDGET;
else if (aType.EqualsLiteral("mouseout"))
msg = NS_MOUSE_EXIT_WIDGET;
else if (aType.EqualsLiteral("contextmenu")) {
msg = NS_CONTEXTMENU;
contextMenuKey = (aButton == 0);
} else if (aType.EqualsLiteral("MozMouseHittest"))
msg = NS_MOUSE_MOZHITTEST;
else
return NS_ERROR_FAILURE;
if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
}
WidgetMouseEvent event(true, msg, widget, WidgetMouseEvent::eReal,
contextMenuKey ? WidgetMouseEvent::eContextMenuKey :
WidgetMouseEvent::eNormal);
event.modifiers = GetWidgetModifiers(aModifiers);
event.button = aButton;
event.buttons = GetButtonsFlagForButton(aButton);
event.widget = widget;
event.pressure = aPressure;
event.inputSource = aInputSourceArg;
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
nsPresContext* presContext = aPresShell->GetPresContext();
if (!presContext)
return NS_ERROR_FAILURE;
event.refPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
event.ignoreRootScrollFrame = 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);
nsAutoTArray<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);
nsAutoTArray<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) {
nsPIDOMWindow* win = aDoc->GetWindow();
if (win) {
return win->GetTopWindowRoot();
}
}
return nullptr;
}
/* static */
nsContentPolicyType
nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType)
{
switch (aType) {
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
case nsIContentPolicy::TYPE_INTERNAL_SHARED_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;
default:
return aType;
}
}
nsresult
nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
nsIDocument* aDoc,
nsIHttpChannel* aChannel)
{
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, net::RP_Default);
}
// 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 = aDoc->GetReferrerPolicy();
return aChannel->SetReferrerWithPolicy(referrerURI, 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();
}