mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-09 04:54:33 +00:00
8913 lines
263 KiB
C++
8913 lines
263 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 "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/ContentChild.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/dom/workers/ServiceWorkerManager.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 "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 "nsIWindowMediator.h"
|
|
#include "nsIWordBreaker.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsLWBrkCIID.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsNodeInfoManager.h"
|
|
#include "nsNullPrincipal.h"
|
|
#include "nsParserCIID.h"
|
|
#include "nsParserConstants.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsReferencedElement.h"
|
|
#include "nsSandboxFlags.h"
|
|
#include "nsScriptSecurityManager.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "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 "nsIWindowProvider.h"
|
|
#include "nsWrapperCacheInlines.h"
|
|
#include "nsXULPopupManager.h"
|
|
#include "xpcprivate.h" // nsXPConnect
|
|
#include "HTMLSplitOnSpacesTokenizer.h"
|
|
#include "nsContentTypeParser.h"
|
|
#include "nsICookiePermission.h"
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "nsICookieService.h"
|
|
#include "mozilla/EnumSet.h"
|
|
#include "mozilla/BloomFilter.h"
|
|
|
|
#include "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::sIsFrameTimingPrefEnabled = false;
|
|
bool nsContentUtils::sIsPerformanceTimingEnabled = false;
|
|
bool nsContentUtils::sIsResourceTimingEnabled = false;
|
|
bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
|
|
bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
|
|
bool nsContentUtils::sEncodeDecodeURLHash = false;
|
|
bool nsContentUtils::sGettersDecodeURLHash = false;
|
|
bool nsContentUtils::sPrivacyResistFingerprinting = false;
|
|
bool nsContentUtils::sSendPerformanceTimingNotifications = false;
|
|
bool nsContentUtils::sSWInterceptionEnabled = false;
|
|
|
|
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
|
|
|
|
uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
|
uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT;
|
|
|
|
nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
|
|
nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
|
|
nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
|
|
bool nsContentUtils::sFragmentParsingActive = false;
|
|
|
|
#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
|
|
bool nsContentUtils::sDOMWindowDumpEnabled;
|
|
#endif
|
|
|
|
// 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 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:
|
|
RefPtr<EventListenerManager> mListenerManager;
|
|
};
|
|
|
|
static void
|
|
EventListenerManagerHashInitEntry(PLDHashEntryHdr *entry, const void *key)
|
|
{
|
|
// Initialize the entry with placement new
|
|
new (entry) EventListenerManagerMapEntry(key);
|
|
}
|
|
|
|
static void
|
|
EventListenerManagerHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
|
|
{
|
|
EventListenerManagerMapEntry *lm =
|
|
static_cast<EventListenerManagerMapEntry *>(entry);
|
|
|
|
// Let the EventListenerManagerMapEntry clean itself up...
|
|
lm->~EventListenerManagerMapEntry();
|
|
}
|
|
|
|
class SameOriginCheckerImpl final : public nsIChannelEventSink,
|
|
public nsIInterfaceRequestor
|
|
{
|
|
~SameOriginCheckerImpl() {}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSICHANNELEVENTSINK
|
|
NS_DECL_NSIINTERFACEREQUESTOR
|
|
};
|
|
|
|
class CharsetDetectionObserver final : public nsICharsetDetectionObserver
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD Notify(const char *aCharset, nsDetectionConfident aConf) override
|
|
{
|
|
mCharset = aCharset;
|
|
return NS_OK;
|
|
}
|
|
|
|
const nsACString& GetResult() const
|
|
{
|
|
return mCharset;
|
|
}
|
|
|
|
private:
|
|
nsCString mCharset;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
/* static */
|
|
TimeDuration
|
|
nsContentUtils::HandlingUserInputTimeout()
|
|
{
|
|
return TimeDuration::FromMilliseconds(sHandlingInputTimeout);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::Init()
|
|
{
|
|
if (sInitialized) {
|
|
NS_WARNING("Init() called twice");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
sNameSpaceManager = nsNameSpaceManager::GetInstance();
|
|
NS_ENSURE_TRUE(sNameSpaceManager, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
sXPConnect = nsXPConnect::XPConnect();
|
|
|
|
sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
|
|
if(!sSecurityManager)
|
|
return NS_ERROR_FAILURE;
|
|
NS_ADDREF(sSecurityManager);
|
|
|
|
sSecurityManager->GetSystemPrincipal(&sSystemPrincipal);
|
|
MOZ_ASSERT(sSystemPrincipal);
|
|
|
|
// We use the constructor here because we want infallible initialization; we
|
|
// apparently don't care whether sNullSubjectPrincipal has a sane URI or not.
|
|
RefPtr<nsNullPrincipal> nullPrincipal = new nsNullPrincipal();
|
|
nullPrincipal->Init();
|
|
nullPrincipal.forget(&sNullSubjectPrincipal);
|
|
|
|
nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
|
|
if (NS_FAILED(rv)) {
|
|
// This makes life easier, but we can live without it.
|
|
|
|
sIOService = nullptr;
|
|
}
|
|
|
|
rv = CallGetService(NS_LBRK_CONTRACTID, &sLineBreaker);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = CallGetService(NS_WBRK_CONTRACTID, &sWordBreaker);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!InitializeEventTable())
|
|
return NS_ERROR_FAILURE;
|
|
|
|
if (!sEventListenerManagersHash) {
|
|
static const PLDHashTableOps hash_table_ops =
|
|
{
|
|
PLDHashTable::HashVoidPtrKeyStub,
|
|
PLDHashTable::MatchEntryStub,
|
|
PLDHashTable::MoveEntryStub,
|
|
EventListenerManagerHashClearEntry,
|
|
EventListenerManagerHashInitEntry
|
|
};
|
|
|
|
sEventListenerManagersHash =
|
|
new PLDHashTable(&hash_table_ops, sizeof(EventListenerManagerMapEntry));
|
|
|
|
RegisterStrongMemoryReporter(new DOMEventListenerManagersHashReporter());
|
|
}
|
|
|
|
sBlockedScriptRunners = new 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(&sIsFrameTimingPrefEnabled,
|
|
"dom.enable_frame_timing", false);
|
|
|
|
Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled,
|
|
"dom.forms.autocomplete.experimental", false);
|
|
|
|
Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
|
|
"dom.url.encode_decode_hash", false);
|
|
|
|
Preferences::AddBoolVarCache(&sGettersDecodeURLHash,
|
|
"dom.url.getters_decode_hash", false);
|
|
|
|
Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
|
|
"privacy.resistFingerprinting", false);
|
|
|
|
Preferences::AddBoolVarCache(&sSWInterceptionEnabled,
|
|
"dom.serviceWorkers.interception.enabled",
|
|
false);
|
|
|
|
Preferences::AddUintVarCache(&sHandlingInputTimeout,
|
|
"dom.event.handling-user-input-time-limit",
|
|
1000);
|
|
|
|
Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications,
|
|
"dom.performance.enable_notify_performance_timing", false);
|
|
|
|
Preferences::AddUintVarCache(&sCookiesLifetimePolicy,
|
|
"network.cookie.lifetimePolicy",
|
|
nsICookieService::ACCEPT_NORMALLY);
|
|
|
|
Preferences::AddUintVarCache(&sCookiesBehavior,
|
|
"network.cookie.cookieBehavior",
|
|
nsICookieService::BEHAVIOR_ACCEPT);
|
|
|
|
#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
|
|
Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
|
|
"browser.dom.window.dump.enabled");
|
|
#endif
|
|
|
|
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 MESSAGE_TO_EVENT to map id to only one atom.
|
|
static bool
|
|
ShouldAddEventToStringEventTable(const EventNameMapping& aMapping)
|
|
{
|
|
switch(aMapping.mMessage) {
|
|
#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
|
|
case message_: return nsGkAtoms::on##name_ == aMapping.mAtom;
|
|
#include "mozilla/EventNameList.h"
|
|
#undef MESSAGE_TO_EVENT
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::InitializeEventTable() {
|
|
NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
|
|
NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
|
|
|
|
static const EventNameMapping eventArray[] = {
|
|
#define EVENT(name_, _message, _type, _class) \
|
|
{ nsGkAtoms::on##name_, _type, _message, _class },
|
|
#define WINDOW_ONLY_EVENT EVENT
|
|
#define NON_IDL_EVENT EVENT
|
|
#include "mozilla/EventNameList.h"
|
|
#undef WINDOW_ONLY_EVENT
|
|
#undef NON_IDL_EVENT
|
|
#undef EVENT
|
|
{ nullptr }
|
|
};
|
|
|
|
sAtomEventTable = new nsDataHashtable<nsISupportsHashKey, EventNameMapping>(
|
|
ArrayLength(eventArray));
|
|
sStringEventTable = new nsDataHashtable<nsStringHashKey, EventNameMapping>(
|
|
ArrayLength(eventArray));
|
|
sUserDefinedEvents = new nsCOMArray<nsIAtom>(64);
|
|
|
|
// Subtract one from the length because of the trailing null
|
|
for (uint32_t i = 0; i < ArrayLength(eventArray) - 1; ++i) {
|
|
sAtomEventTable->Put(eventArray[i].mAtom, eventArray[i]);
|
|
if (ShouldAddEventToStringEventTable(eventArray[i])) {
|
|
sStringEventTable->Put(
|
|
Substring(nsDependentAtomString(eventArray[i].mAtom), 2),
|
|
eventArray[i]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
nsContentUtils::InitializeTouchEventTable()
|
|
{
|
|
static bool sEventTableInitialized = false;
|
|
if (!sEventTableInitialized && sAtomEventTable && sStringEventTable) {
|
|
sEventTableInitialized = true;
|
|
static const EventNameMapping touchEventArray[] = {
|
|
#define EVENT(name_, _message, _type, _class)
|
|
#define TOUCH_EVENT(name_, _message, _type, _class) \
|
|
{ nsGkAtoms::on##name_, _type, _message, _class },
|
|
#include "mozilla/EventNameList.h"
|
|
#undef TOUCH_EVENT
|
|
#undef EVENT
|
|
{ nullptr }
|
|
};
|
|
// Subtract one from the length because of the trailing null
|
|
for (uint32_t i = 0; i < ArrayLength(touchEventArray) - 1; ++i) {
|
|
sAtomEventTable->Put(touchEventArray[i].mAtom, touchEventArray[i]);
|
|
sStringEventTable->Put(Substring(nsDependentAtomString(touchEventArray[i].mAtom), 2),
|
|
touchEventArray[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
Is8bit(const nsAString& aString)
|
|
{
|
|
static const char16_t EIGHT_BIT = char16_t(~0x00FF);
|
|
|
|
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_ORIENTATION_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(alloworientationlock, SANDBOXED_ORIENTATION_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 */
|
|
bool
|
|
nsContentUtils::IsControlledByServiceWorker(nsIDocument* aDocument)
|
|
{
|
|
if (!ServiceWorkerInterceptionEnabled() ||
|
|
nsContentUtils::IsInPrivateBrowsing(aDocument)) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<workers::ServiceWorkerManager> swm =
|
|
workers::ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
ErrorResult rv;
|
|
bool controlled = swm->IsControlled(aDocument, rv);
|
|
NS_WARN_IF(rv.Failed());
|
|
|
|
return !rv.Failed() && controlled;
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aDocument);
|
|
*aURI = nullptr;
|
|
|
|
if (IsControlledByServiceWorker(aDocument)) {
|
|
return;
|
|
}
|
|
|
|
Element* docElement = aDocument->GetRootElement();
|
|
if (!docElement) {
|
|
return;
|
|
}
|
|
|
|
nsAutoString manifestSpec;
|
|
docElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
|
|
|
|
// Manifest URIs can't have fragment identifiers.
|
|
if (manifestSpec.IsEmpty() ||
|
|
manifestSpec.Contains('#')) {
|
|
return;
|
|
}
|
|
|
|
nsContentUtils::NewURIWithDocumentCharset(aURI, manifestSpec,
|
|
aDocument,
|
|
aDocument->GetDocBaseURI());
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::OfflineAppAllowed(nsIURI *aURI)
|
|
{
|
|
nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
|
|
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
|
|
if (!updateService) {
|
|
return false;
|
|
}
|
|
|
|
bool allowed;
|
|
nsresult rv =
|
|
updateService->OfflineAppAllowedForURI(aURI,
|
|
Preferences::GetRootBranch(),
|
|
&allowed);
|
|
return NS_SUCCEEDED(rv) && allowed;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::OfflineAppAllowed(nsIPrincipal *aPrincipal)
|
|
{
|
|
nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
|
|
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
|
|
if (!updateService) {
|
|
return false;
|
|
}
|
|
|
|
bool allowed;
|
|
nsresult rv = updateService->OfflineAppAllowed(aPrincipal,
|
|
Preferences::GetRootBranch(),
|
|
&allowed);
|
|
return NS_SUCCEEDED(rv) && allowed;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::MaybeAllowOfflineAppByDefault(nsIPrincipal *aPrincipal,
|
|
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);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
// 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) {
|
|
MOZ_CRASH("Accessing the Subject Principal without an AutoJSAPI on the stack is forbidden");
|
|
}
|
|
|
|
JSCompartment *compartment = js::GetContextCompartment(cx);
|
|
|
|
// When an AutoJSAPI is instantiated, we are in a null compartment until the
|
|
// first JSAutoCompartment, which is kind of a purgatory as far as permissions
|
|
// go. It would be nice to just hard-abort if somebody does a security check
|
|
// in this purgatory zone, but that would be too fragile, since it could be
|
|
// triggered by random IsCallerChrome() checks 20-levels deep.
|
|
//
|
|
// So we want to return _something_ here - and definitely not the System
|
|
// Principal, since that would make an AutoJSAPI a very dangerous thing to
|
|
// instantiate.
|
|
//
|
|
// The natural thing to return is a null principal. Ideally, we'd return a
|
|
// different null principal each time, to avoid any unexpected interactions
|
|
// when the principal accidentally gets inherited somewhere. But
|
|
// GetSubjectPrincipal doesn't return strong references, so there's no way to
|
|
// sanely manage the lifetime of multiple null principals.
|
|
//
|
|
// So we use a singleton null principal. To avoid it being accidentally
|
|
// inherited and becoming a "real" subject or object principal, we do a
|
|
// release-mode assert during compartment creation against using this
|
|
// principal on an actual global.
|
|
if (!compartment) {
|
|
return sNullSubjectPrincipal;
|
|
}
|
|
|
|
JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
|
|
return nsJSPrincipals::get(principals);
|
|
}
|
|
|
|
// static
|
|
nsIPrincipal*
|
|
nsContentUtils::ObjectPrincipal(JSObject* aObj)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
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;
|
|
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument);
|
|
nsresult rv = cache->FindEntryProperties(aURI, domDoc, getter_AddRefs(props));
|
|
return (NS_SUCCEEDED(rv) && props);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::LoadImage(nsIURI* aURI, 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);
|
|
RefPtr<imgRequestProxy> retval;
|
|
aRequest->GetStaticRequest(getter_AddRefs(retval));
|
|
return retval.forget();
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::ContentIsDraggable(nsIContent* aContent)
|
|
{
|
|
nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aContent);
|
|
if (htmlElement) {
|
|
bool draggable = false;
|
|
htmlElement->GetDraggable(&draggable);
|
|
if (draggable)
|
|
return true;
|
|
|
|
if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
|
|
nsGkAtoms::_false, eIgnoreCase))
|
|
return false;
|
|
}
|
|
|
|
// special handling for content area image and link dragging
|
|
return IsDraggableImage(aContent) || IsDraggableLink(aContent);
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::IsDraggableImage(nsIContent* aContent)
|
|
{
|
|
NS_PRECONDITION(aContent, "Must have content node to test");
|
|
|
|
nsCOMPtr<nsIImageLoadingContent> imageContent(do_QueryInterface(aContent));
|
|
if (!imageContent) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<imgIRequest> imgRequest;
|
|
imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
|
|
getter_AddRefs(imgRequest));
|
|
|
|
// XXXbz It may be draggable even if the request resulted in an error. Why?
|
|
// Not sure; that's what the old nsContentAreaDragDrop/nsFrame code did.
|
|
return imgRequest != nullptr;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::IsDraggableLink(const nsIContent* aContent) {
|
|
nsCOMPtr<nsIURI> absURI;
|
|
return aContent->IsLink(getter_AddRefs(absURI));
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::NameChanged(mozilla::dom::NodeInfo* aNodeInfo, nsIAtom* aName,
|
|
mozilla::dom::NodeInfo** aResult)
|
|
{
|
|
nsNodeInfoManager *niMgr = aNodeInfo->NodeInfoManager();
|
|
|
|
*aResult = niMgr->GetNodeInfo(aName, aNodeInfo->GetPrefixAtom(),
|
|
aNodeInfo->NamespaceID(),
|
|
aNodeInfo->NodeType(),
|
|
aNodeInfo->GetExtraName()).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
static bool
|
|
TestSitePerm(nsIPrincipal* aPrincipal, const char* aType, uint32_t aPerm, bool aExactHostMatch)
|
|
{
|
|
if (!aPrincipal) {
|
|
// We always deny (i.e. don't allow) the permission if we don't have a
|
|
// principal.
|
|
return aPerm != nsIPermissionManager::ALLOW_ACTION;
|
|
}
|
|
|
|
nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
|
|
NS_ENSURE_TRUE(permMgr, false);
|
|
|
|
uint32_t perm;
|
|
nsresult rv;
|
|
if (aExactHostMatch) {
|
|
rv = permMgr->TestExactPermissionFromPrincipal(aPrincipal, aType, &perm);
|
|
} else {
|
|
rv = permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &perm);
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
return perm == aPerm;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsSitePermAllow(nsIPrincipal* aPrincipal, const char* aType)
|
|
{
|
|
return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, false);
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsSitePermDeny(nsIPrincipal* aPrincipal, const char* aType)
|
|
{
|
|
return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, false);
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsExactSitePermAllow(nsIPrincipal* aPrincipal, const char* aType)
|
|
{
|
|
return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, true);
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsExactSitePermDeny(nsIPrincipal* aPrincipal, const char* aType)
|
|
{
|
|
return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, true);
|
|
}
|
|
|
|
static const char *gEventNames[] = {"event"};
|
|
static const char *gSVGEventNames[] = {"evt"};
|
|
// for b/w compat, the first name to onerror is still 'event', even though it
|
|
// is actually the error message
|
|
static const char *gOnErrorNames[] = {"event", "source", "lineno",
|
|
"colno", "error"};
|
|
|
|
// static
|
|
void
|
|
nsContentUtils::GetEventArgNames(int32_t aNameSpaceID,
|
|
nsIAtom *aEventName,
|
|
bool aIsForWindow,
|
|
uint32_t *aArgCount,
|
|
const char*** aArgArray)
|
|
{
|
|
#define SET_EVENT_ARG_NAMES(names) \
|
|
*aArgCount = sizeof(names)/sizeof(names[0]); \
|
|
*aArgArray = names;
|
|
|
|
// JSEventHandler is what does the arg magic for onerror, and it does
|
|
// not seem to take the namespace into account. So we let onerror in all
|
|
// namespaces get the 3 arg names.
|
|
if (aEventName == nsGkAtoms::onerror && aIsForWindow) {
|
|
SET_EVENT_ARG_NAMES(gOnErrorNames);
|
|
} else if (aNameSpaceID == kNameSpaceID_SVG) {
|
|
SET_EVENT_ARG_NAMES(gSVGEventNames);
|
|
} else {
|
|
SET_EVENT_ARG_NAMES(gEventNames);
|
|
}
|
|
}
|
|
|
|
static const char gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT][56] = {
|
|
// Must line up with the enum values in |PropertiesFile| enum.
|
|
"chrome://global/locale/css.properties",
|
|
"chrome://global/locale/xbl.properties",
|
|
"chrome://global/locale/xul.properties",
|
|
"chrome://global/locale/layout_errors.properties",
|
|
"chrome://global/locale/layout/HtmlForm.properties",
|
|
"chrome://global/locale/printing.properties",
|
|
"chrome://global/locale/dom/dom.properties",
|
|
"chrome://global/locale/layout/htmlparser.properties",
|
|
"chrome://global/locale/svg/svg.properties",
|
|
"chrome://branding/locale/brand.properties",
|
|
"chrome://global/locale/commonDialogs.properties",
|
|
"chrome://global/locale/mathml/mathml.properties",
|
|
"chrome://global/locale/security/security.properties",
|
|
"chrome://necko/locale/necko.properties"
|
|
};
|
|
|
|
/* static */ nsresult
|
|
nsContentUtils::EnsureStringBundle(PropertiesFile aFile)
|
|
{
|
|
if (!sStringBundles[aFile]) {
|
|
if (!sStringBundleService) {
|
|
nsresult rv =
|
|
CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
nsIStringBundle *bundle;
|
|
nsresult rv =
|
|
sStringBundleService->CreateBundle(gPropertiesFiles[aFile], &bundle);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
sStringBundles[aFile] = bundle; // transfer ownership
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
|
|
const char* aKey,
|
|
nsXPIDLString& aResult)
|
|
{
|
|
nsresult rv = EnsureStringBundle(aFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsIStringBundle *bundle = sStringBundles[aFile];
|
|
|
|
return bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aKey).get(),
|
|
getter_Copies(aResult));
|
|
}
|
|
|
|
/* static */
|
|
nsresult nsContentUtils::FormatLocalizedString(PropertiesFile aFile,
|
|
const char* aKey,
|
|
const char16_t **aParams,
|
|
uint32_t aParamsLength,
|
|
nsXPIDLString& aResult)
|
|
{
|
|
nsresult rv = EnsureStringBundle(aFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsIStringBundle *bundle = sStringBundles[aFile];
|
|
|
|
return bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aKey).get(),
|
|
aParams, aParamsLength,
|
|
getter_Copies(aResult));
|
|
}
|
|
|
|
/* static */ void
|
|
nsContentUtils::LogSimpleConsoleError(const nsAString& aErrorText,
|
|
const char * classification)
|
|
{
|
|
nsCOMPtr<nsIScriptError> scriptError =
|
|
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
|
if (scriptError) {
|
|
nsCOMPtr<nsIConsoleService> console =
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
|
if (console && NS_SUCCEEDED(scriptError->Init(aErrorText, EmptyString(),
|
|
EmptyString(), 0, 0,
|
|
nsIScriptError::errorFlag,
|
|
classification))) {
|
|
console->LogMessage(scriptError);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */ nsresult
|
|
nsContentUtils::ReportToConsole(uint32_t aErrorFlags,
|
|
const nsACString& aCategory,
|
|
const nsIDocument* aDocument,
|
|
PropertiesFile aFile,
|
|
const char *aMessageName,
|
|
const char16_t **aParams,
|
|
uint32_t aParamsLength,
|
|
nsIURI* aURI,
|
|
const nsAFlatString& aSourceLine,
|
|
uint32_t aLineNumber,
|
|
uint32_t aColumnNumber)
|
|
{
|
|
NS_ASSERTION((aParams && aParamsLength) || (!aParams && !aParamsLength),
|
|
"Supply either both parameters and their number or no"
|
|
"parameters and 0.");
|
|
|
|
nsresult rv;
|
|
nsXPIDLString errorText;
|
|
if (aParams) {
|
|
rv = FormatLocalizedString(aFile, aMessageName, aParams, aParamsLength,
|
|
errorText);
|
|
}
|
|
else {
|
|
rv = GetLocalizedString(aFile, aMessageName, errorText);
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return ReportToConsoleNonLocalized(errorText, aErrorFlags, aCategory,
|
|
aDocument, aURI, aSourceLine,
|
|
aLineNumber, aColumnNumber);
|
|
}
|
|
|
|
|
|
/* static */ nsresult
|
|
nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText,
|
|
uint32_t aErrorFlags,
|
|
const nsACString& aCategory,
|
|
const nsIDocument* aDocument,
|
|
nsIURI* aURI,
|
|
const nsAFlatString& aSourceLine,
|
|
uint32_t aLineNumber,
|
|
uint32_t aColumnNumber)
|
|
{
|
|
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, &aColumnNumber);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
sConsoleService->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsChromeDoc(nsIDocument *aDocument)
|
|
{
|
|
if (!aDocument) {
|
|
return false;
|
|
}
|
|
return aDocument->NodePrincipal() == sSystemPrincipal;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsChildOfSameType(nsIDocument* aDoc)
|
|
{
|
|
nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(aDoc->GetDocShell());
|
|
nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
|
|
if (docShellAsItem) {
|
|
docShellAsItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
|
|
}
|
|
return sameTypeParent != nullptr;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsPlainTextType(const nsACString& aContentType)
|
|
{
|
|
// NOTE: if you add a type here, add it to the CONTENTDLF_CATEGORIES
|
|
// define in nsContentDLF.h as well.
|
|
return aContentType.EqualsLiteral(TEXT_PLAIN) ||
|
|
aContentType.EqualsLiteral(TEXT_CSS) ||
|
|
aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
|
|
aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
|
|
aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
|
|
aContentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
|
|
aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
|
|
aContentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
|
|
aContentType.EqualsLiteral(APPLICATION_JSON) ||
|
|
aContentType.EqualsLiteral(TEXT_JSON) ||
|
|
aContentType.EqualsLiteral(TEXT_VTT);
|
|
}
|
|
|
|
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
|
|
EventMessage
|
|
nsContentUtils::GetEventMessage(nsIAtom* aName)
|
|
{
|
|
if (aName) {
|
|
EventNameMapping mapping;
|
|
if (sAtomEventTable->Get(aName, &mapping)) {
|
|
return mapping.mMessage;
|
|
}
|
|
}
|
|
|
|
return eUnidentifiedEvent;
|
|
}
|
|
|
|
// static
|
|
mozilla::EventClassID
|
|
nsContentUtils::GetEventClassID(const nsAString& aName)
|
|
{
|
|
EventNameMapping mapping;
|
|
if (sStringEventTable->Get(aName, &mapping))
|
|
return mapping.mEventClassID;
|
|
|
|
return eBasicEventClass;
|
|
}
|
|
|
|
nsIAtom*
|
|
nsContentUtils::GetEventMessageAndAtom(const nsAString& aName,
|
|
mozilla::EventClassID aEventClassID,
|
|
EventMessage* aEventMessage)
|
|
{
|
|
EventNameMapping mapping;
|
|
if (sStringEventTable->Get(aName, &mapping)) {
|
|
*aEventMessage =
|
|
mapping.mEventClassID == aEventClassID ? mapping.mMessage :
|
|
eUnidentifiedEvent;
|
|
return mapping.mAtom;
|
|
}
|
|
|
|
// If we have cached lots of user defined event names, clear some of them.
|
|
if (sUserDefinedEvents->Count() > 127) {
|
|
while (sUserDefinedEvents->Count() > 64) {
|
|
nsIAtom* first = sUserDefinedEvents->ObjectAt(0);
|
|
sStringEventTable->Remove(Substring(nsDependentAtomString(first), 2));
|
|
sUserDefinedEvents->RemoveObjectAt(0);
|
|
}
|
|
}
|
|
|
|
*aEventMessage = eUnidentifiedEvent;
|
|
nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aName);
|
|
sUserDefinedEvents->AppendObject(atom);
|
|
mapping.mAtom = atom;
|
|
mapping.mMessage = eUnidentifiedEvent;
|
|
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);
|
|
|
|
event->InitEvent(aEventName, aCanBubble, aCancelable);
|
|
event->SetTrusted(aTrusted);
|
|
|
|
rv = event->SetTarget(target);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
event.forget(aEvent);
|
|
target.forget(aTargetOut);
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
|
|
const nsAString& aEventName,
|
|
bool aCanBubble, bool aCancelable,
|
|
bool *aDefaultAction)
|
|
{
|
|
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
|
|
true, aDefaultAction);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
|
|
const nsAString& aEventName,
|
|
bool aCanBubble, bool aCancelable,
|
|
bool *aDefaultAction)
|
|
{
|
|
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
|
|
false, aDefaultAction);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
|
|
const nsAString& aEventName,
|
|
bool aCanBubble, bool aCancelable,
|
|
bool aTrusted, bool *aDefaultAction,
|
|
bool aOnlyChromeDispatch)
|
|
{
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
nsCOMPtr<EventTarget> target;
|
|
nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble,
|
|
aCancelable, aTrusted, getter_AddRefs(event),
|
|
getter_AddRefs(target));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
event->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);
|
|
}
|
|
|
|
/* static */
|
|
nsIDocument*
|
|
nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
|
|
uint64_t aOuterWindowId)
|
|
{
|
|
if (!aDocument || !aOuterWindowId) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId);
|
|
if (!window) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> foundDoc = window->GetDoc();
|
|
if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) {
|
|
// Note that ContentIsCrossDocDescendantOf will return true if
|
|
// foundDoc == aDocument.
|
|
return foundDoc;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Convert the string from the given encoding to Unicode.
|
|
/* static */
|
|
nsresult
|
|
nsContentUtils::ConvertStringFromEncoding(const nsACString& aEncoding,
|
|
const nsACString& aInput,
|
|
nsAString& aOutput)
|
|
{
|
|
nsAutoCString encoding;
|
|
if (aEncoding.IsEmpty()) {
|
|
encoding.AssignLiteral("UTF-8");
|
|
} else {
|
|
encoding.Assign(aEncoding);
|
|
}
|
|
|
|
ErrorResult rv;
|
|
nsAutoPtr<TextDecoder> decoder(new TextDecoder());
|
|
decoder->InitWithEncoding(encoding, false);
|
|
|
|
decoder->Decode(aInput.BeginReading(), aInput.Length(), false,
|
|
aOutput, rv);
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::CheckForBOM(const unsigned char* aBuffer, uint32_t aLength,
|
|
nsACString& aCharset)
|
|
{
|
|
bool found = true;
|
|
aCharset.Truncate();
|
|
if (aLength >= 3 &&
|
|
aBuffer[0] == 0xEF &&
|
|
aBuffer[1] == 0xBB &&
|
|
aBuffer[2] == 0xBF) {
|
|
aCharset = "UTF-8";
|
|
}
|
|
else if (aLength >= 2 &&
|
|
aBuffer[0] == 0xFE && aBuffer[1] == 0xFF) {
|
|
aCharset = "UTF-16BE";
|
|
}
|
|
else if (aLength >= 2 &&
|
|
aBuffer[0] == 0xFF && aBuffer[1] == 0xFE) {
|
|
aCharset = "UTF-16LE";
|
|
} else {
|
|
found = false;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::RegisterShutdownObserver(nsIObserver* aObserver)
|
|
{
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (observerService) {
|
|
observerService->AddObserver(aObserver,
|
|
NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
|
false);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::UnregisterShutdownObserver(nsIObserver* aObserver)
|
|
{
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (observerService) {
|
|
observerService->RemoveObserver(aObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::HasNonEmptyAttr(const nsIContent* aContent, int32_t aNameSpaceID,
|
|
nsIAtom* aName)
|
|
{
|
|
static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::_empty, nullptr};
|
|
return aContent->FindAttrValueIn(aNameSpaceID, aName, strings, eCaseMatters)
|
|
== nsIContent::ATTR_VALUE_NO_MATCH;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::HasMutationListeners(nsINode* aNode,
|
|
uint32_t aType,
|
|
nsINode* aTargetForSubtreeModified)
|
|
{
|
|
nsIDocument* doc = aNode->OwnerDoc();
|
|
|
|
// global object will be null for documents that don't have windows.
|
|
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, eLegacyNodeRemoved);
|
|
mutation.mRelatedNode = do_QueryInterface(aParent);
|
|
|
|
mozAutoSubtreeModified subtree(aOwnerDoc, aParent);
|
|
EventDispatcher::Dispatch(aChild, nullptr, &mutation);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments()
|
|
{
|
|
if (!sEventListenerManagersHash) {
|
|
return;
|
|
}
|
|
|
|
for (auto i = sEventListenerManagersHash->Iter(); !i.Done(); i.Next()) {
|
|
auto entry = static_cast<EventListenerManagerMapEntry*>(i.Get());
|
|
nsINode* n = static_cast<nsINode*>(entry->mListenerManager->GetTarget());
|
|
if (n && n->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;
|
|
}
|
|
|
|
auto entry = static_cast<EventListenerManagerMapEntry*>
|
|
(sEventListenerManagersHash->Search(aNode));
|
|
if (entry) {
|
|
CycleCollectionNoteChild(cb, entry->mListenerManager.get(),
|
|
"[via hash] mListenerManager");
|
|
}
|
|
}
|
|
|
|
EventListenerManager*
|
|
nsContentUtils::GetListenerManagerForNode(nsINode *aNode)
|
|
{
|
|
if (!sEventListenerManagersHash) {
|
|
// We're already shut down, don't bother creating an event listener
|
|
// manager.
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
auto entry =
|
|
static_cast<EventListenerManagerMapEntry*>
|
|
(sEventListenerManagersHash->Add(aNode, fallible));
|
|
|
|
if (!entry) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!entry->mListenerManager) {
|
|
entry->mListenerManager = new EventListenerManager(aNode);
|
|
|
|
aNode->SetFlags(NODE_HAS_LISTENERMANAGER);
|
|
}
|
|
|
|
return entry->mListenerManager;
|
|
}
|
|
|
|
EventListenerManager*
|
|
nsContentUtils::GetExistingListenerManagerForNode(const nsINode *aNode)
|
|
{
|
|
if (!aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!sEventListenerManagersHash) {
|
|
// We're already shut down, don't bother creating an event listener
|
|
// manager.
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
auto entry = static_cast<EventListenerManagerMapEntry*>
|
|
(sEventListenerManagersHash->Search(aNode));
|
|
if (entry) {
|
|
return entry->mListenerManager;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::RemoveListenerManager(nsINode *aNode)
|
|
{
|
|
if (sEventListenerManagersHash) {
|
|
auto entry = static_cast<EventListenerManagerMapEntry*>
|
|
(sEventListenerManagersHash->Search(aNode));
|
|
if (entry) {
|
|
RefPtr<EventListenerManager> listenerManager;
|
|
listenerManager.swap(entry->mListenerManager);
|
|
// Remove the entry and *then* do operations that could cause further
|
|
// modification of sEventListenerManagersHash. See bug 334177.
|
|
sEventListenerManagersHash->RawRemove(entry);
|
|
if (listenerManager) {
|
|
listenerManager->Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::IsValidNodeName(nsIAtom *aLocalName, nsIAtom *aPrefix,
|
|
int32_t aNamespaceID)
|
|
{
|
|
if (aNamespaceID == kNameSpaceID_Unknown) {
|
|
return false;
|
|
}
|
|
|
|
if (!aPrefix) {
|
|
// If the prefix is null, then either the QName must be xmlns or the
|
|
// namespace must not be XMLNS.
|
|
return (aLocalName == nsGkAtoms::xmlns) ==
|
|
(aNamespaceID == kNameSpaceID_XMLNS);
|
|
}
|
|
|
|
// If the prefix is non-null then the namespace must not be null.
|
|
if (aNamespaceID == kNameSpaceID_None) {
|
|
return false;
|
|
}
|
|
|
|
// If the namespace is the XMLNS namespace then the prefix must be xmlns,
|
|
// but the localname must not be xmlns.
|
|
if (aNamespaceID == kNameSpaceID_XMLNS) {
|
|
return aPrefix == nsGkAtoms::xmlns && aLocalName != nsGkAtoms::xmlns;
|
|
}
|
|
|
|
// If the namespace is not the XMLNS namespace then the prefix must not be
|
|
// xmlns.
|
|
// If the namespace is the XML namespace then the prefix can be anything.
|
|
// If the namespace is not the XML namespace then the prefix must not be xml.
|
|
return aPrefix != nsGkAtoms::xmlns &&
|
|
(aNamespaceID == kNameSpaceID_XML || aPrefix != nsGkAtoms::xml);
|
|
}
|
|
|
|
/* static */
|
|
nsresult
|
|
nsContentUtils::CreateContextualFragment(nsINode* aContextNode,
|
|
const nsAString& aFragment,
|
|
bool aPreventScriptExecution,
|
|
nsIDOMDocumentFragment** aReturn)
|
|
{
|
|
ErrorResult rv;
|
|
*aReturn = CreateContextualFragment(aContextNode, aFragment,
|
|
aPreventScriptExecution, rv).take();
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
already_AddRefed<DocumentFragment>
|
|
nsContentUtils::CreateContextualFragment(nsINode* aContextNode,
|
|
const nsAString& aFragment,
|
|
bool aPreventScriptExecution,
|
|
ErrorResult& aRv)
|
|
{
|
|
if (!aContextNode) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return nullptr;
|
|
}
|
|
|
|
// If we don't have a document here, we can't get the right security context
|
|
// for compiling event handlers... so just bail out.
|
|
nsCOMPtr<nsIDocument> document = aContextNode->OwnerDoc();
|
|
bool isHTML = document->IsHTMLDocument();
|
|
#ifdef DEBUG
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
|
|
NS_ASSERTION(!isHTML || htmlDoc, "Should have HTMLDocument here!");
|
|
#endif
|
|
|
|
if (isHTML) {
|
|
RefPtr<DocumentFragment> frag =
|
|
new DocumentFragment(document->NodeInfoManager());
|
|
|
|
nsCOMPtr<nsIContent> contextAsContent = do_QueryInterface(aContextNode);
|
|
if (contextAsContent && !contextAsContent->IsElement()) {
|
|
contextAsContent = contextAsContent->GetParent();
|
|
if (contextAsContent && !contextAsContent->IsElement()) {
|
|
// can this even happen?
|
|
contextAsContent = nullptr;
|
|
}
|
|
}
|
|
|
|
if (contextAsContent && !contextAsContent->IsHTMLElement(nsGkAtoms::html)) {
|
|
aRv = ParseFragmentHTML(aFragment, frag,
|
|
contextAsContent->NodeInfo()->NameAtom(),
|
|
contextAsContent->GetNameSpaceID(),
|
|
(document->GetCompatibilityMode() ==
|
|
eCompatibility_NavQuirks),
|
|
aPreventScriptExecution);
|
|
} else {
|
|
aRv = ParseFragmentHTML(aFragment, frag,
|
|
nsGkAtoms::body,
|
|
kNameSpaceID_XHTML,
|
|
(document->GetCompatibilityMode() ==
|
|
eCompatibility_NavQuirks),
|
|
aPreventScriptExecution);
|
|
}
|
|
|
|
return frag.forget();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
RefPtr<nsTextNode> textContent =
|
|
new nsTextNode(aContent->NodeInfo()->NodeInfoManager());
|
|
|
|
textContent->SetText(aValue, true);
|
|
|
|
nsresult rv = aContent->AppendChildTo(textContent, true);
|
|
mb.NodesAdded();
|
|
return rv;
|
|
}
|
|
|
|
static bool
|
|
AppendNodeTextContentsRecurse(nsINode* aNode, nsAString& aResult,
|
|
const fallible_t& aFallible)
|
|
{
|
|
for (nsIContent* child = aNode->GetFirstChild();
|
|
child;
|
|
child = child->GetNextSibling()) {
|
|
if (child->IsElement()) {
|
|
bool ok = AppendNodeTextContentsRecurse(child, aResult,
|
|
aFallible);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
else if (child->IsNodeOfType(nsINode::eTEXT)) {
|
|
bool ok = child->AppendTextTo(aResult, aFallible);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::AppendNodeTextContent(nsINode* aNode, bool aDeep,
|
|
nsAString& aResult,
|
|
const fallible_t& aFallible)
|
|
{
|
|
if (aNode->IsNodeOfType(nsINode::eTEXT)) {
|
|
return static_cast<nsIContent*>(aNode)->AppendTextTo(aResult,
|
|
aFallible);
|
|
}
|
|
else if (aDeep) {
|
|
return AppendNodeTextContentsRecurse(aNode, aResult, aFallible);
|
|
}
|
|
else {
|
|
for (nsIContent* child = aNode->GetFirstChild();
|
|
child;
|
|
child = child->GetNextSibling()) {
|
|
if (child->IsNodeOfType(nsINode::eTEXT)) {
|
|
bool ok = child->AppendTextTo(aResult, fallible);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::HasNonEmptyTextContent(nsINode* aNode,
|
|
TextContentDiscoverMode aDiscoverMode)
|
|
{
|
|
for (nsIContent* child = aNode->GetFirstChild();
|
|
child;
|
|
child = child->GetNextSibling()) {
|
|
if (child->IsNodeOfType(nsINode::eTEXT) &&
|
|
child->TextLength() > 0) {
|
|
return true;
|
|
}
|
|
|
|
if (aDiscoverMode == eRecurseIntoChildren &&
|
|
HasNonEmptyTextContent(child, aDiscoverMode)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode,
|
|
const nsIContent* aContent)
|
|
{
|
|
NS_PRECONDITION(aNode,
|
|
"Must have a node to work with");
|
|
NS_PRECONDITION(aContent,
|
|
"Must have a content to work with");
|
|
|
|
if (!aNode->IsNodeOfType(nsINode::eCONTENT)) {
|
|
/**
|
|
* The root isn't an nsIContent, so it's a document or attribute. The only
|
|
* nodes in the same anonymous subtree as it will have a null
|
|
* bindingParent.
|
|
*
|
|
* XXXbz strictly speaking, that's not true for attribute nodes.
|
|
*/
|
|
return aContent->GetBindingParent() == nullptr;
|
|
}
|
|
|
|
const nsIContent* nodeAsContent = static_cast<const nsIContent*>(aNode);
|
|
|
|
// For nodes in a shadow tree, it is insufficient to simply compare
|
|
// the binding parent because a node may host multiple ShadowRoots,
|
|
// thus nodes in different shadow tree may have the same binding parent.
|
|
if (aNode->IsInShadowTree()) {
|
|
return nodeAsContent->GetContainingShadow() ==
|
|
aContent->GetContainingShadow();
|
|
}
|
|
|
|
return nodeAsContent->GetBindingParent() == aContent->GetBindingParent();
|
|
}
|
|
|
|
class AnonymousContentDestroyer : public 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;
|
|
}
|
|
|
|
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 */
|
|
nsIWindowProvider*
|
|
nsContentUtils::GetWindowProviderForContentProcess()
|
|
{
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
return ContentChild::GetSingleton();
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<nsPIDOMWindow>
|
|
nsContentUtils::GetMostRecentNonPBWindow()
|
|
{
|
|
nsCOMPtr<nsIWindowMediator> windowMediator =
|
|
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
|
|
nsCOMPtr<nsIWindowMediator_44> wm = do_QueryInterface(windowMediator);
|
|
|
|
nsCOMPtr<nsIDOMWindow> window;
|
|
wm->GetMostRecentNonPBWindow(MOZ_UTF16("navigator:browser"),
|
|
getter_AddRefs(window));
|
|
nsCOMPtr<nsPIDOMWindow> pwindow;
|
|
pwindow = do_QueryInterface(window);
|
|
|
|
return pwindow.forget();
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::WarnScriptWasIgnored(nsIDocument* aDocument)
|
|
{
|
|
nsAutoString msg;
|
|
if (aDocument) {
|
|
nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
|
|
if (uri) {
|
|
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)
|
|
{
|
|
MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
|
|
CycleCollectedJSRuntime::Get()->RunInStableState(Move(aRunnable));
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
|
|
{
|
|
MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
|
|
CycleCollectedJSRuntime::Get()->RunInMetastableState(Move(aRunnable));
|
|
}
|
|
|
|
void
|
|
nsContentUtils::EnterMicroTask()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
++sMicroTaskLevel;
|
|
}
|
|
|
|
void
|
|
nsContentUtils::LeaveMicroTask()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (--sMicroTaskLevel == 0) {
|
|
PerformMainThreadMicroTaskCheckpoint();
|
|
}
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsInMicroTask()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return sMicroTaskLevel != 0;
|
|
}
|
|
|
|
uint32_t
|
|
nsContentUtils::MicroTaskLevel()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return sMicroTaskLevel;
|
|
}
|
|
|
|
void
|
|
nsContentUtils::SetMicroTaskLevel(uint32_t aLevel)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
sMicroTaskLevel = aLevel;
|
|
}
|
|
|
|
void
|
|
nsContentUtils::PerformMainThreadMicroTaskCheckpoint()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsDOMMutationObserver::HandleMutations();
|
|
}
|
|
|
|
/*
|
|
* Helper function for nsContentUtils::ProcessViewportInfo.
|
|
*
|
|
* Handles a single key=value pair. If it corresponds to a valid viewport
|
|
* attribute, add it to the document header data. No validation is done on the
|
|
* value itself (this is done at display time).
|
|
*/
|
|
static void ProcessViewportToken(nsIDocument *aDocument,
|
|
const nsAString &token) {
|
|
|
|
/* Iterators. */
|
|
nsAString::const_iterator tip, tail, end;
|
|
token.BeginReading(tip);
|
|
tail = tip;
|
|
token.EndReading(end);
|
|
|
|
/* Move tip to the '='. */
|
|
while ((tip != end) && (*tip != '='))
|
|
++tip;
|
|
|
|
/* If we didn't find an '=', punt. */
|
|
if (tip == end)
|
|
return;
|
|
|
|
/* Extract the key and value. */
|
|
const nsAString &key =
|
|
nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(Substring(tail, tip),
|
|
true);
|
|
const nsAString &value =
|
|
nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(Substring(++tip, end),
|
|
true);
|
|
|
|
/* Check for known keys. If we find a match, insert the appropriate
|
|
* information into the document header. */
|
|
nsCOMPtr<nsIAtom> key_atom = 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 */
|
|
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->mMessage != eLegacyDragGesture &&
|
|
aDragEvent->mMessage != eDragStart,
|
|
"draggesture event created without a dataTransfer");
|
|
|
|
nsCOMPtr<nsIDragSession> dragSession = GetDragSession();
|
|
NS_ENSURE_TRUE(dragSession, NS_OK); // no drag in progress
|
|
|
|
nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
|
|
nsCOMPtr<DataTransfer> initialDataTransfer;
|
|
dragSession->GetDataTransfer(getter_AddRefs(dataTransfer));
|
|
if (dataTransfer) {
|
|
initialDataTransfer = do_QueryInterface(dataTransfer);
|
|
if (!initialDataTransfer) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
} else {
|
|
// A dataTransfer won't exist when a drag was started by some other
|
|
// means, for instance calling the drag service directly, or a drag
|
|
// from another application. In either case, a new dataTransfer should
|
|
// be created that reflects the data.
|
|
initialDataTransfer =
|
|
new DataTransfer(aDragEvent->target, aDragEvent->mMessage, true, -1);
|
|
|
|
// now set it in the drag session so we don't need to create it again
|
|
dragSession->SetDataTransfer(initialDataTransfer);
|
|
}
|
|
|
|
bool isCrossDomainSubFrameDrop = false;
|
|
if (aDragEvent->mMessage == eDrop ||
|
|
aDragEvent->mMessage == eLegacyDragDrop) {
|
|
isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent);
|
|
}
|
|
|
|
// each event should use a clone of the original dataTransfer.
|
|
initialDataTransfer->Clone(aDragEvent->target, aDragEvent->mMessage,
|
|
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->mMessage == eDragEnter || aDragEvent->mMessage == eDragOver) {
|
|
uint32_t action, effectAllowed;
|
|
dragSession->GetDragAction(&action);
|
|
aDragEvent->dataTransfer->GetEffectAllowedInt(&effectAllowed);
|
|
aDragEvent->dataTransfer->SetDropEffectInt(FilterDropEffect(action, effectAllowed));
|
|
}
|
|
else if (aDragEvent->mMessage == eDrop ||
|
|
aDragEvent->mMessage == eLegacyDragDrop ||
|
|
aDragEvent->mMessage == eDragEnd) {
|
|
// For the drop and dragend events, set the drop effect based on the
|
|
// last value that the dropEffect had. This will have been set in
|
|
// EventStateManager::PostHandleEvent for the last dragenter or
|
|
// dragover event.
|
|
uint32_t dropEffect;
|
|
initialDataTransfer->GetDropEffectInt(&dropEffect);
|
|
aDragEvent->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);
|
|
}
|
|
|
|
nsresult
|
|
nsContentTypeParser::GetType(nsAString& aResult)
|
|
{
|
|
nsresult rv = GetParameter(nullptr, aResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsContentUtils::ASCIIToLower(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
|
|
bool
|
|
nsContentUtils::CanAccessNativeAnon()
|
|
{
|
|
return LegacyIsCallerChromeOrNativeCode() || IsCallerContentXBL();
|
|
}
|
|
|
|
/* static */ nsresult
|
|
nsContentUtils::DispatchXULCommand(nsIContent* aTarget,
|
|
bool aTrusted,
|
|
nsIDOMEvent* aSourceEvent,
|
|
nsIPresShell* aShell,
|
|
bool aCtrl,
|
|
bool aAlt,
|
|
bool aShift,
|
|
bool aMeta)
|
|
{
|
|
NS_ENSURE_STATE(aTarget);
|
|
nsIDocument* doc = aTarget->OwnerDoc();
|
|
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
|
|
NS_ENSURE_STATE(domDoc);
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
|
|
getter_AddRefs(event));
|
|
nsCOMPtr<nsIDOMXULCommandEvent> xulCommand = do_QueryInterface(event);
|
|
nsresult rv = xulCommand->InitCommandEvent(NS_LITERAL_STRING("command"),
|
|
true, true, doc->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;
|
|
bool isShared;
|
|
memcpy(JS_GetArrayBufferData(*aResult, &isShared, nogc), aData.BeginReading(), dataLen);
|
|
MOZ_ASSERT(!isShared);
|
|
}
|
|
|
|
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);
|
|
RefPtr<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) {
|
|
RefPtr<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;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsSWFPlayerEnabled()
|
|
{
|
|
nsCOMPtr<nsIStreamConverterService> convServ =
|
|
do_GetService("@mozilla.org/streamConverters;1");
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
bool canConvert = false;
|
|
if (convServ) {
|
|
rv = convServ->CanConvert("application/x-shockwave-flash",
|
|
"text/html", &canConvert);
|
|
}
|
|
return NS_SUCCEEDED(rv) && canConvert;
|
|
}
|
|
|
|
already_AddRefed<nsIDocumentLoaderFactory>
|
|
nsContentUtils::FindInternalContentViewer(const nsACString& aType,
|
|
ContentViewerType* aLoaderType)
|
|
{
|
|
if (aLoaderType) {
|
|
*aLoaderType = TYPE_UNSUPPORTED;
|
|
}
|
|
|
|
// one helper factory, please
|
|
nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
|
|
if (!catMan)
|
|
return nullptr;
|
|
|
|
nsCOMPtr<nsIDocumentLoaderFactory> docFactory;
|
|
|
|
nsXPIDLCString contractID;
|
|
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
|
|
PromiseFlatCString(aType).get(),
|
|
getter_Copies(contractID));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
docFactory = do_GetService(contractID);
|
|
if (docFactory && aLoaderType) {
|
|
if (contractID.EqualsLiteral(CONTENT_DLF_CONTRACTID))
|
|
*aLoaderType = TYPE_CONTENT;
|
|
else if (contractID.EqualsLiteral(PLUGIN_DLF_CONTRACTID))
|
|
*aLoaderType = TYPE_PLUGIN;
|
|
else
|
|
*aLoaderType = TYPE_UNKNOWN;
|
|
}
|
|
return docFactory.forget();
|
|
}
|
|
|
|
if (DecoderTraits::IsSupportedInVideoDocument(aType)) {
|
|
docFactory = do_GetService("@mozilla.org/content/document-loader-factory;1");
|
|
if (docFactory && aLoaderType) {
|
|
*aLoaderType = TYPE_CONTENT;
|
|
}
|
|
return docFactory.forget();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// static
|
|
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::IsFrameTimingEnabled()
|
|
{
|
|
return sIsFrameTimingPrefEnabled;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
|
|
{
|
|
if (!aDoc1 || !aDoc2) {
|
|
return false;
|
|
}
|
|
bool principalsEqual = false;
|
|
aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
|
|
return principalsEqual;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
RefPtr<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(nsPIDOMWindow* aWin)
|
|
{
|
|
if (!aWin) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> pointerLockedDoc =
|
|
do_QueryReferent(EventStateManager::sPointerLockedDoc);
|
|
if (!pointerLockedDoc || !pointerLockedDoc->GetWindow()) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindow> lockTop = pointerLockedDoc->GetWindow()->GetScriptableTop();
|
|
nsCOMPtr<nsPIDOMWindow> top = aWin->GetScriptableTop();
|
|
|
|
return top == lockTop;
|
|
}
|
|
|
|
// static
|
|
int32_t
|
|
nsContentUtils::GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame,
|
|
int32_t aOffset)
|
|
{
|
|
// The structure of the anonymous frames within a text control frame is
|
|
// an optional block frame, followed by an optional br frame.
|
|
|
|
// If the offset frame has a child, then this frame is the block which
|
|
// has the text frames (containing the content) as its children. This will
|
|
// be the case if we click to the right of any of the text frames, or at the
|
|
// bottom of the text area.
|
|
nsIFrame* firstChild = aOffsetFrame->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);
|
|
}
|
|
|
|
|
|
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", "via"
|
|
};
|
|
for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) {
|
|
if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader)
|
|
{
|
|
return (aHeader.LowerCaseEqualsASCII("set-cookie") ||
|
|
aHeader.LowerCaseEqualsASCII("set-cookie2"));
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::IsAllowedNonCorsContentType(const nsACString& aHeaderValue)
|
|
{
|
|
nsAutoCString contentType;
|
|
nsAutoCString unused;
|
|
|
|
nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
|
|
return contentType.LowerCaseEqualsLiteral("text/plain") ||
|
|
contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") ||
|
|
contentType.LowerCaseEqualsLiteral("multipart/form-data");
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::DOMWindowDumpEnabled()
|
|
{
|
|
#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
|
|
// In optimized builds we check a pref that controls if we should
|
|
// enable output from dump() or not, in debug builds it's always
|
|
// enabled.
|
|
return nsContentUtils::sDOMWindowDumpEnabled;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::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,
|
|
bool aInSyncMessage,
|
|
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, aInSyncMessage, aChild, aParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsContentUtils::SlurpFileToString(nsIFile* aFile, nsACString& aString)
|
|
{
|
|
aString.Truncate();
|
|
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
rv = NS_NewChannel(getter_AddRefs(channel),
|
|
fileURI,
|
|
nsContentUtils::GetSystemPrincipal(),
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> stream;
|
|
rv = channel->Open2(getter_AddRefs(stream));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
rv = NS_ConsumeStream(stream, UINT32_MAX, aString);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stream->Close();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::IsFileImage(nsIFile* aFile, nsACString& aType)
|
|
{
|
|
nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
|
|
if (!mime) {
|
|
return false;
|
|
}
|
|
|
|
nsresult rv = mime->GetTypeFromFile(aFile, aType);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
|
|
return StringBeginsWith(aType, NS_LITERAL_CSTRING("image/"));
|
|
}
|
|
|
|
void
|
|
nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
|
|
IPCDataTransfer* aIPCDataTransfer,
|
|
bool aInSyncMessage,
|
|
mozilla::dom::nsIContentChild* aChild,
|
|
mozilla::dom::nsIContentParent* aParent)
|
|
{
|
|
MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
|
|
|
|
if (aTransferable) {
|
|
nsCOMPtr<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) {
|
|
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) {
|
|
// If we can send this over as a blob, do so. Otherwise, we're
|
|
// responding to a sync message and the child can't process the blob
|
|
// constructor before processing our response, which would crash. In
|
|
// that case, hope that the caller is nsClipboardProxy::GetData,
|
|
// called from editor and send over images as raw data.
|
|
if (aInSyncMessage) {
|
|
nsAutoCString type;
|
|
if (IsFileImage(file, type)) {
|
|
IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
|
|
item->flavor() = type;
|
|
nsAutoCString data;
|
|
SlurpFileToString(file, data);
|
|
item->data() = data;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
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 {
|
|
if (aInSyncMessage) {
|
|
// Can't do anything.
|
|
continue;
|
|
}
|
|
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);
|
|
if (!requiredBytes.isValid()) {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t maxBufLen = requiredBytes.value();
|
|
mozilla::gfx::SurfaceFormat format = aSurface->GetFormat();
|
|
|
|
// Surface data handling is totally nuts. This is the magic one needs to
|
|
// know to access the data.
|
|
size_t bufLen = maxBufLen - map.mStride + (size.width * BytesPerPixel(format));
|
|
|
|
// nsDependentCString wants null-terminated string.
|
|
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).ApplyResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(aPresContext->PresShell())),
|
|
aPresContext->AppUnitsPerDevPixel());
|
|
}
|
|
|
|
nsView*
|
|
nsContentUtils::GetViewToDispatchEvent(nsPresContext* presContext,
|
|
nsIPresShell** presShell)
|
|
{
|
|
if (presContext && presShell) {
|
|
*presShell = presContext->PresShell();
|
|
if (*presShell) {
|
|
NS_ADDREF(*presShell);
|
|
if (nsViewManager* viewManager = (*presShell)->GetViewManager()) {
|
|
if (nsView* view = viewManager->GetRootView()) {
|
|
return view;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult
|
|
nsContentUtils::SendKeyEvent(nsIWidget* aWidget,
|
|
const nsAString& aType,
|
|
int32_t aKeyCode,
|
|
int32_t aCharCode,
|
|
int32_t aModifiers,
|
|
uint32_t aAdditionalFlags,
|
|
bool* aDefaultActionTaken)
|
|
{
|
|
// get the widget to send the event to
|
|
if (!aWidget)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
EventMessage msg;
|
|
if (aType.EqualsLiteral("keydown"))
|
|
msg = eKeyDown;
|
|
else if (aType.EqualsLiteral("keyup"))
|
|
msg = eKeyUp;
|
|
else if (aType.EqualsLiteral("keypress"))
|
|
msg = eKeyPress;
|
|
else
|
|
return NS_ERROR_FAILURE;
|
|
|
|
WidgetKeyboardEvent event(true, msg, aWidget);
|
|
event.modifiers = GetWidgetModifiers(aModifiers);
|
|
|
|
if (msg == eKeyPress) {
|
|
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;
|
|
|
|
EventMessage msg;
|
|
bool contextMenuKey = false;
|
|
if (aType.EqualsLiteral("mousedown")) {
|
|
msg = eMouseDown;
|
|
} else if (aType.EqualsLiteral("mouseup")) {
|
|
msg = eMouseUp;
|
|
} else if (aType.EqualsLiteral("mousemove")) {
|
|
msg = eMouseMove;
|
|
} else if (aType.EqualsLiteral("mouseover")) {
|
|
msg = eMouseEnterIntoWidget;
|
|
} else if (aType.EqualsLiteral("mouseout")) {
|
|
msg = eMouseExitFromWidget;
|
|
} else if (aType.EqualsLiteral("mouselongtap")) {
|
|
msg = eMouseLongTap;
|
|
} else if (aType.EqualsLiteral("contextmenu")) {
|
|
msg = eContextMenu;
|
|
contextMenuKey = (aButton == 0);
|
|
} else if (aType.EqualsLiteral("MozMouseHittest")) {
|
|
msg = eMouseHitTest;
|
|
} else {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
|
|
aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
|
|
}
|
|
|
|
WidgetMouseEvent event(true, msg, widget, 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_SCRIPT_PRELOAD:
|
|
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
|
|
return nsIContentPolicy::TYPE_SCRIPT;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_EMBED:
|
|
case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
|
|
return nsIContentPolicy::TYPE_OBJECT;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_FRAME:
|
|
case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
|
|
return nsIContentPolicy::TYPE_SUBDOCUMENT;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
|
|
case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
|
|
case nsIContentPolicy::TYPE_INTERNAL_TRACK:
|
|
return nsIContentPolicy::TYPE_MEDIA;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
|
|
case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
|
|
return nsIContentPolicy::TYPE_XMLHTTPREQUEST;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
|
|
case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
|
|
return nsIContentPolicy::TYPE_IMAGE;
|
|
|
|
case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
|
|
case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
|
|
return nsIContentPolicy::TYPE_STYLESHEET;
|
|
|
|
default:
|
|
return aType;
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
nsContentPolicyType
|
|
nsContentUtils::InternalContentPolicyTypeToExternalOrMCBInternal(nsContentPolicyType aType)
|
|
{
|
|
switch (aType) {
|
|
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
|
|
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
|
|
return aType;
|
|
|
|
default:
|
|
return InternalContentPolicyTypeToExternalOrPreload(aType);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
nsContentPolicyType
|
|
nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(nsContentPolicyType aType)
|
|
{
|
|
if (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
|
|
aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
|
|
aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD) {
|
|
return aType;
|
|
}
|
|
return InternalContentPolicyTypeToExternal(aType);
|
|
}
|
|
|
|
|
|
/* static */
|
|
nsContentPolicyType
|
|
nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType)
|
|
{
|
|
switch (aType) {
|
|
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
|
|
case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
|
|
return aType;
|
|
|
|
default:
|
|
return InternalContentPolicyTypeToExternal(aType);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
nsContentUtils::IsPreloadType(nsContentPolicyType aType)
|
|
{
|
|
if (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
|
|
aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
|
|
aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
nsContentPolicyType
|
|
nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(nsContentPolicyType aType)
|
|
{
|
|
if (aType == InternalContentPolicyTypeToExternalOrWorker(aType) ||
|
|
aType == InternalContentPolicyTypeToExternalOrPreload(aType)) {
|
|
return aType;
|
|
}
|
|
return InternalContentPolicyTypeToExternal(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();
|
|
}
|
|
|
|
// static
|
|
bool
|
|
nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel)
|
|
{
|
|
nsLoadFlags loadFlags = 0;
|
|
aChannel->GetLoadFlags(&loadFlags);
|
|
if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
|
|
if (!loadInfo) {
|
|
return false;
|
|
}
|
|
nsContentPolicyType type = loadInfo->InternalContentPolicyType();
|
|
return type == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
|
|
type == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
|
|
}
|
|
|
|
// static, public
|
|
nsContentUtils::StorageAccess
|
|
nsContentUtils::StorageAllowedForWindow(nsPIDOMWindow* aWindow)
|
|
{
|
|
MOZ_ASSERT(aWindow->IsInnerWindow());
|
|
|
|
nsIDocument* document = aWindow->GetExtantDoc();
|
|
if (document) {
|
|
nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
|
|
return InternalStorageAllowedForPrincipal(principal, aWindow);
|
|
}
|
|
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
// static, public
|
|
nsContentUtils::StorageAccess
|
|
nsContentUtils::StorageAllowedForPrincipal(nsIPrincipal* aPrincipal)
|
|
{
|
|
return InternalStorageAllowedForPrincipal(aPrincipal, nullptr);
|
|
}
|
|
|
|
// static, private
|
|
nsContentUtils::StorageAccess
|
|
nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
|
nsPIDOMWindow* aWindow)
|
|
{
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow());
|
|
|
|
StorageAccess access = StorageAccess::eAllow;
|
|
|
|
// We don't allow storage on the null principal, in general. Even if the
|
|
// calling context is chrome.
|
|
bool isNullPrincipal;
|
|
if (NS_WARN_IF(NS_FAILED(aPrincipal->GetIsNullPrincipal(&isNullPrincipal))) ||
|
|
isNullPrincipal) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
if (aWindow) {
|
|
// If the document is sandboxed, then it is not permitted to use storage
|
|
nsIDocument* document = aWindow->GetExtantDoc();
|
|
if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
// Check if we are in private browsing, and record that fact
|
|
if (IsInPrivateBrowsing(document)) {
|
|
access = StorageAccess::ePrivateBrowsing;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIPermissionManager> permissionManager =
|
|
services::GetPermissionManager();
|
|
if (!permissionManager) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
// check the permission manager for any allow or deny permissions
|
|
// for cookies for the window.
|
|
uint32_t perm;
|
|
permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
|
|
if (perm == nsIPermissionManager::DENY_ACTION) {
|
|
return StorageAccess::eDeny;
|
|
} else if (perm == nsICookiePermission::ACCESS_SESSION) {
|
|
return std::min(access, StorageAccess::eSessionScoped);
|
|
} else if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
|
return access;
|
|
}
|
|
|
|
// Check if we should only allow storage for the session, and record that fact
|
|
if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) {
|
|
// Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
|
|
// so perform a std::min comparison to make sure we preserve ePrivateBrowsing
|
|
// if it has been set.
|
|
access = std::min(StorageAccess::eSessionScoped, access);
|
|
}
|
|
|
|
// About URIs are allowed to access storage, even if they don't have chrome
|
|
// privileges. If this is not desired, than the consumer will have to
|
|
// implement their own restriction functionality.
|
|
//
|
|
// This is due to backwards-compatibility and the state of storage access before
|
|
// the introducton of nsContentUtils::InternalStorageAllowedForPrincipal:
|
|
//
|
|
// BEFORE:
|
|
// localStorage, caches: allowed in 3rd-party iframes always
|
|
// IndexedDB: allowed in 3rd-party iframes only if 3rd party URI is an about:
|
|
// URI within a specific whitelist
|
|
//
|
|
// AFTER:
|
|
// localStorage, caches: allowed in 3rd-party iframes by default. Preference
|
|
// can be set to disable in 3rd-party, which will not disallow in about: URIs.
|
|
// IndexedDB: allowed in 3rd-party iframes by default. Preference can be set to
|
|
// disable in 3rd-party, which will disallow in about: URIs, unless they are
|
|
// within a specific whitelist.
|
|
//
|
|
// This means that behavior for storage with internal about: URIs should not be
|
|
// affected, which is desireable due to the lack of automated testing for about:
|
|
// URIs with these preferences set, and the importance of the correct functioning
|
|
// of these URIs even with custom preferences.
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
|
|
if (NS_SUCCEEDED(rv) && uri) {
|
|
bool isAbout = false;
|
|
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)));
|
|
if (isAbout) {
|
|
return access;
|
|
}
|
|
}
|
|
|
|
// We don't want to prompt for every attempt to access permissions.
|
|
if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
// In the absense of a window, we assume that we are first-party.
|
|
if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
|
sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
|
|
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
|
|
do_GetService(THIRDPARTYUTIL_CONTRACTID);
|
|
MOZ_ASSERT(thirdPartyUtil);
|
|
|
|
bool thirdPartyWindow = false;
|
|
if (NS_SUCCEEDED(thirdPartyUtil->IsThirdPartyWindow(
|
|
aWindow, nullptr, &thirdPartyWindow)) && thirdPartyWindow) {
|
|
// XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
|
|
// simply rejecting the request to use the storage. In the future, if we
|
|
// change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
|
|
// for non-cookie storage types, this may change.
|
|
|
|
return StorageAccess::eDeny;
|
|
}
|
|
}
|
|
|
|
return access;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// We put StringBuilder in the anonymous namespace to prevent anything outside
|
|
// this file from accidentally being linked against it.
|
|
|
|
class StringBuilder
|
|
{
|
|
private:
|
|
// Try to keep the size of StringBuilder close to a jemalloc bucket size.
|
|
static const uint32_t STRING_BUFFER_UNITS = 1020;
|
|
class Unit
|
|
{
|
|
public:
|
|
Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
|
|
{
|
|
MOZ_COUNT_CTOR(StringBuilder::Unit);
|
|
}
|
|
~Unit()
|
|
{
|
|
if (mType == eString || mType == eStringWithEncode) {
|
|
delete mString;
|
|
}
|
|
MOZ_COUNT_DTOR(StringBuilder::Unit);
|
|
}
|
|
|
|
enum Type
|
|
{
|
|
eUnknown,
|
|
eAtom,
|
|
eString,
|
|
eStringWithEncode,
|
|
eLiteral,
|
|
eTextFragment,
|
|
eTextFragmentWithEncode,
|
|
};
|
|
|
|
union
|
|
{
|
|
nsIAtom* mAtom;
|
|
const char* mLiteral;
|
|
nsAutoString* mString;
|
|
const nsTextFragment* mTextFragment;
|
|
};
|
|
Type mType;
|
|
uint32_t mLength;
|
|
};
|
|
public:
|
|
StringBuilder() : mLast(this), mLength(0)
|
|
{
|
|
MOZ_COUNT_CTOR(StringBuilder);
|
|
}
|
|
|
|
~StringBuilder()
|
|
{
|
|
MOZ_COUNT_DTOR(StringBuilder);
|
|
}
|
|
|
|
void Append(nsIAtom* aAtom)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mAtom = aAtom;
|
|
u->mType = Unit::eAtom;
|
|
uint32_t len = aAtom->GetLength();
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
template<int N>
|
|
void Append(const char (&aLiteral)[N])
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mLiteral = aLiteral;
|
|
u->mType = Unit::eLiteral;
|
|
uint32_t len = N - 1;
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
template<int N>
|
|
void Append(char (&aLiteral)[N])
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mLiteral = aLiteral;
|
|
u->mType = Unit::eLiteral;
|
|
uint32_t len = N - 1;
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
void Append(const nsAString& aString)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mString = new nsAutoString(aString);
|
|
u->mType = Unit::eString;
|
|
uint32_t len = aString.Length();
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
void Append(nsAutoString* aString)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mString = aString;
|
|
u->mType = Unit::eString;
|
|
uint32_t len = aString->Length();
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mString = aString;
|
|
u->mType = Unit::eStringWithEncode;
|
|
u->mLength = aLen;
|
|
mLength += aLen;
|
|
}
|
|
|
|
void Append(const nsTextFragment* aTextFragment)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mTextFragment = aTextFragment;
|
|
u->mType = Unit::eTextFragment;
|
|
uint32_t len = aTextFragment->GetLength();
|
|
u->mLength = len;
|
|
mLength += len;
|
|
}
|
|
|
|
void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen)
|
|
{
|
|
Unit* u = AddUnit();
|
|
u->mTextFragment = aTextFragment;
|
|
u->mType = Unit::eTextFragmentWithEncode;
|
|
u->mLength = aLen;
|
|
mLength += aLen;
|
|
}
|
|
|
|
bool ToString(nsAString& aOut)
|
|
{
|
|
if (!aOut.SetCapacity(mLength, fallible)) {
|
|
return false;
|
|
}
|
|
|
|
for (StringBuilder* current = this; current; current = current->mNext) {
|
|
uint32_t len = current->mUnits.Length();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
Unit& u = current->mUnits[i];
|
|
switch (u.mType) {
|
|
case Unit::eAtom:
|
|
aOut.Append(nsDependentAtomString(u.mAtom));
|
|
break;
|
|
case Unit::eString:
|
|
aOut.Append(*(u.mString));
|
|
break;
|
|
case Unit::eStringWithEncode:
|
|
EncodeAttrString(*(u.mString), aOut);
|
|
break;
|
|
case Unit::eLiteral:
|
|
aOut.AppendASCII(u.mLiteral, u.mLength);
|
|
break;
|
|
case Unit::eTextFragment:
|
|
u.mTextFragment->AppendTo(aOut);
|
|
break;
|
|
case Unit::eTextFragmentWithEncode:
|
|
EncodeTextFragment(u.mTextFragment, aOut);
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Unknown unit type?");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
private:
|
|
Unit* AddUnit()
|
|
{
|
|
if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
|
|
new StringBuilder(this);
|
|
}
|
|
return mLast->mUnits.AppendElement();
|
|
}
|
|
|
|
explicit StringBuilder(StringBuilder* aFirst)
|
|
: mLast(nullptr), mLength(0)
|
|
{
|
|
MOZ_COUNT_CTOR(StringBuilder);
|
|
aFirst->mLast->mNext = this;
|
|
aFirst->mLast = this;
|
|
}
|
|
|
|
void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
|
|
{
|
|
const char16_t* c = aValue.BeginReading();
|
|
const char16_t* end = aValue.EndReading();
|
|
while (c < end) {
|
|
switch (*c) {
|
|
case '"':
|
|
aOut.AppendLiteral(""");
|
|
break;
|
|
case '&':
|
|
aOut.AppendLiteral("&");
|
|
break;
|
|
case 0x00A0:
|
|
aOut.AppendLiteral(" ");
|
|
break;
|
|
default:
|
|
aOut.Append(*c);
|
|
break;
|
|
}
|
|
++c;
|
|
}
|
|
}
|
|
|
|
void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
|
|
{
|
|
uint32_t len = aValue->GetLength();
|
|
if (aValue->Is2b()) {
|
|
const char16_t* data = aValue->Get2b();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
const char16_t c = data[i];
|
|
switch (c) {
|
|
case '<':
|
|
aOut.AppendLiteral("<");
|
|
break;
|
|
case '>':
|
|
aOut.AppendLiteral(">");
|
|
break;
|
|
case '&':
|
|
aOut.AppendLiteral("&");
|
|
break;
|
|
case 0x00A0:
|
|
aOut.AppendLiteral(" ");
|
|
break;
|
|
default:
|
|
aOut.Append(c);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
const char* data = aValue->Get1b();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
const unsigned char c = data[i];
|
|
switch (c) {
|
|
case '<':
|
|
aOut.AppendLiteral("<");
|
|
break;
|
|
case '>':
|
|
aOut.AppendLiteral(">");
|
|
break;
|
|
case '&':
|
|
aOut.AppendLiteral("&");
|
|
break;
|
|
case 0x00A0:
|
|
aOut.AppendLiteral(" ");
|
|
break;
|
|
default:
|
|
aOut.Append(c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
|
|
nsAutoPtr<StringBuilder> mNext;
|
|
StringBuilder* mLast;
|
|
// mLength is used only in the first StringBuilder object in the linked list.
|
|
uint32_t mLength;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static void
|
|
AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
|
|
{
|
|
uint32_t extraSpaceNeeded = 0;
|
|
uint32_t len = aText->GetLength();
|
|
if (aText->Is2b()) {
|
|
const char16_t* data = aText->Get2b();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
const char16_t c = data[i];
|
|
switch (c) {
|
|
case '<':
|
|
extraSpaceNeeded += ArrayLength("<") - 2;
|
|
break;
|
|
case '>':
|
|
extraSpaceNeeded += ArrayLength(">") - 2;
|
|
break;
|
|
case '&':
|
|
extraSpaceNeeded += ArrayLength("&") - 2;
|
|
break;
|
|
case 0x00A0:
|
|
extraSpaceNeeded += ArrayLength(" ") - 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
const char* data = aText->Get1b();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
const unsigned char c = data[i];
|
|
switch (c) {
|
|
case '<':
|
|
extraSpaceNeeded += ArrayLength("<") - 2;
|
|
break;
|
|
case '>':
|
|
extraSpaceNeeded += ArrayLength(">") - 2;
|
|
break;
|
|
case '&':
|
|
extraSpaceNeeded += ArrayLength("&") - 2;
|
|
break;
|
|
case 0x00A0:
|
|
extraSpaceNeeded += ArrayLength(" ") - 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (extraSpaceNeeded) {
|
|
aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
|
|
} else {
|
|
aBuilder.Append(aText);
|
|
}
|
|
}
|
|
|
|
static void
|
|
AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
|
|
{
|
|
const char16_t* c = aValue->BeginReading();
|
|
const char16_t* end = aValue->EndReading();
|
|
|
|
uint32_t extraSpaceNeeded = 0;
|
|
while (c < end) {
|
|
switch (*c) {
|
|
case '"':
|
|
extraSpaceNeeded += ArrayLength(""") - 2;
|
|
break;
|
|
case '&':
|
|
extraSpaceNeeded += ArrayLength("&") - 2;
|
|
break;
|
|
case 0x00A0:
|
|
extraSpaceNeeded += ArrayLength(" ") - 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
++c;
|
|
}
|
|
|
|
if (extraSpaceNeeded) {
|
|
aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
|
|
} else {
|
|
aBuilder.Append(aValue);
|
|
}
|
|
}
|
|
|
|
static void
|
|
StartElement(Element* aContent, StringBuilder& aBuilder)
|
|
{
|
|
nsIAtom* localName = aContent->NodeInfo()->NameAtom();
|
|
int32_t tagNS = aContent->GetNameSpaceID();
|
|
|
|
aBuilder.Append("<");
|
|
if (aContent->IsHTMLElement() || aContent->IsSVGElement() ||
|
|
aContent->IsMathMLElement()) {
|
|
aBuilder.Append(localName);
|
|
} else {
|
|
aBuilder.Append(aContent->NodeName());
|
|
}
|
|
|
|
int32_t count = aContent->GetAttrCount();
|
|
for (int32_t i = count; i > 0;) {
|
|
--i;
|
|
const nsAttrName* name = aContent->GetAttrNameAt(i);
|
|
int32_t attNs = name->NamespaceID();
|
|
nsIAtom* attName = name->LocalName();
|
|
|
|
// Filter out any attribute starting with [-|_]moz
|
|
nsDependentAtomString attrNameStr(attName);
|
|
if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
|
|
StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
|
|
continue;
|
|
}
|
|
|
|
nsAutoString* attValue = new nsAutoString();
|
|
aContent->GetAttr(attNs, attName, *attValue);
|
|
|
|
// Filter out special case of <br type="_moz*"> used by the editor.
|
|
// Bug 16988. Yuck.
|
|
if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
|
|
attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
|
|
StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
|
|
delete attValue;
|
|
continue;
|
|
}
|
|
|
|
aBuilder.Append(" ");
|
|
|
|
if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
|
|
(attNs == kNameSpaceID_XMLNS &&
|
|
attName == nsGkAtoms::xmlns)) {
|
|
// Nothing else required
|
|
} else if (attNs == kNameSpaceID_XML) {
|
|
aBuilder.Append("xml:");
|
|
} else if (attNs == kNameSpaceID_XMLNS) {
|
|
aBuilder.Append("xmlns:");
|
|
} else if (attNs == kNameSpaceID_XLink) {
|
|
aBuilder.Append("xlink:");
|
|
} else {
|
|
nsIAtom* prefix = name->GetPrefix();
|
|
if (prefix) {
|
|
aBuilder.Append(prefix);
|
|
aBuilder.Append(":");
|
|
}
|
|
}
|
|
|
|
aBuilder.Append(attName);
|
|
aBuilder.Append("=\"");
|
|
AppendEncodedAttributeValue(attValue, aBuilder);
|
|
aBuilder.Append("\"");
|
|
}
|
|
|
|
aBuilder.Append(">");
|
|
|
|
/*
|
|
// Per HTML spec we should append one \n if the first child of
|
|
// pre/textarea/listing is a textnode and starts with a \n.
|
|
// But because browsers haven't traditionally had that behavior,
|
|
// we're not changing our behavior either - yet.
|
|
if (aContent->IsHTMLElement()) {
|
|
if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
|
|
localName == nsGkAtoms::listing) {
|
|
nsIContent* fc = aContent->GetFirstChild();
|
|
if (fc &&
|
|
(fc->NodeType() == nsIDOMNode::TEXT_NODE ||
|
|
fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
|
|
const nsTextFragment* text = fc->GetText();
|
|
if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) {
|
|
aBuilder.Append("\n");
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
static inline bool
|
|
ShouldEscape(nsIContent* aParent)
|
|
{
|
|
if (!aParent || !aParent->IsHTMLElement()) {
|
|
return true;
|
|
}
|
|
|
|
static const nsIAtom* nonEscapingElements[] = {
|
|
nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
|
|
nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
|
|
nsGkAtoms::plaintext,
|
|
// Per the current spec noscript should be escaped in case
|
|
// scripts are disabled or if document doesn't have
|
|
// browsing context. However the latter seems to be a spec bug
|
|
// and Gecko hasn't traditionally done the former.
|
|
nsGkAtoms::noscript
|
|
};
|
|
static mozilla::BloomFilter<12, nsIAtom> sFilter;
|
|
static bool sInitialized = false;
|
|
if (!sInitialized) {
|
|
sInitialized = true;
|
|
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
|
|
sFilter.add(nonEscapingElements[i]);
|
|
}
|
|
}
|
|
|
|
nsIAtom* tag = aParent->NodeInfo()->NameAtom();
|
|
if (sFilter.mightContain(tag)) {
|
|
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
|
|
if (tag == nonEscapingElements[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
IsVoidTag(Element* aElement)
|
|
{
|
|
if (!aElement->IsHTMLElement()) {
|
|
return false;
|
|
}
|
|
return FragmentOrElement::IsHTMLVoid(aElement->NodeInfo()->NameAtom());
|
|
}
|
|
|
|
bool
|
|
nsContentUtils::SerializeNodeToMarkup(nsINode* aRoot,
|
|
bool aDescendentsOnly,
|
|
nsAString& aOut)
|
|
{
|
|
// If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true
|
|
MOZ_ASSERT(aDescendentsOnly ||
|
|
aRoot->NodeType() != nsIDOMNode::DOCUMENT_NODE);
|
|
|
|
nsINode* current = aDescendentsOnly ?
|
|
nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
|
|
|
|
if (!current) {
|
|
return true;
|
|
}
|
|
|
|
StringBuilder builder;
|
|
nsIContent* next;
|
|
while (true) {
|
|
bool isVoid = false;
|
|
switch (current->NodeType()) {
|
|
case nsIDOMNode::ELEMENT_NODE: {
|
|
Element* elem = current->AsElement();
|
|
StartElement(elem, builder);
|
|
isVoid = IsVoidTag(elem);
|
|
if (!isVoid &&
|
|
(next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
|
|
current = next;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case nsIDOMNode::TEXT_NODE:
|
|
case nsIDOMNode::CDATA_SECTION_NODE: {
|
|
const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
|
|
nsIContent* parent = current->GetParent();
|
|
if (ShouldEscape(parent)) {
|
|
AppendEncodedCharacters(text, builder);
|
|
} else {
|
|
builder.Append(text);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case nsIDOMNode::COMMENT_NODE: {
|
|
builder.Append("<!--");
|
|
builder.Append(static_cast<nsIContent*>(current)->GetText());
|
|
builder.Append("-->");
|
|
break;
|
|
}
|
|
|
|
case nsIDOMNode::DOCUMENT_TYPE_NODE: {
|
|
builder.Append("<!DOCTYPE ");
|
|
builder.Append(current->NodeName());
|
|
builder.Append(">");
|
|
break;
|
|
}
|
|
|
|
case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
|
|
builder.Append("<?");
|
|
builder.Append(current->NodeName());
|
|
builder.Append(" ");
|
|
builder.Append(static_cast<nsIContent*>(current)->GetText());
|
|
builder.Append(">");
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
|
|
builder.Append("</");
|
|
nsIContent* elem = static_cast<nsIContent*>(current);
|
|
if (elem->IsHTMLElement() || elem->IsSVGElement() ||
|
|
elem->IsMathMLElement()) {
|
|
builder.Append(elem->NodeInfo()->NameAtom());
|
|
} else {
|
|
builder.Append(current->NodeName());
|
|
}
|
|
builder.Append(">");
|
|
}
|
|
isVoid = false;
|
|
|
|
if (current == aRoot) {
|
|
return builder.ToString(aOut);
|
|
}
|
|
|
|
if ((next = current->GetNextSibling())) {
|
|
current = next;
|
|
break;
|
|
}
|
|
|
|
current = current->GetParentNode();
|
|
|
|
// Handle template element. If the parent is a template's content,
|
|
// then adjust the parent to be the template element.
|
|
if (current != aRoot &&
|
|
current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
|
|
DocumentFragment* frag = static_cast<DocumentFragment*>(current);
|
|
nsIContent* fragHost = frag->GetHost();
|
|
if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
|
|
current = fragHost;
|
|
}
|
|
}
|
|
|
|
if (aDescendentsOnly && current == aRoot) {
|
|
return builder.ToString(aOut);
|
|
}
|
|
}
|
|
}
|
|
}
|