Mirror of roytam1's Pale Moon fork just in case Moonchild and Tobin decide to go after him
https://github.com/roytam1/palemoon27
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8275 lines
247 KiB
8275 lines
247 KiB
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
/* This Source Code Form is subject to the terms of the Mozilla Public |
|
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
|
|
/* A namespace class for static layout utilities. */ |
|
|
|
#include "nsContentUtils.h" |
|
|
|
#include <algorithm> |
|
#include <math.h> |
|
|
|
#include "prprf.h" |
|
#include "DecoderTraits.h" |
|
#include "harfbuzz/hb.h" |
|
#include "imgICache.h" |
|
#include "imgIContainer.h" |
|
#include "imgINotificationObserver.h" |
|
#include "imgLoader.h" |
|
#include "imgRequestProxy.h" |
|
#include "jsapi.h" |
|
#include "jsfriendapi.h" |
|
#include "js/Value.h" |
|
#include "Layers.h" |
|
#include "MediaDecoder.h" |
|
// nsNPAPIPluginInstance must be included before nsIDocument.h, which is included in mozAutoDocUpdate.h. |
|
#include "nsNPAPIPluginInstance.h" |
|
#include "gfxPrefs.h" |
|
#include "mozAutoDocUpdate.h" |
|
#include "mozilla/ArrayUtils.h" |
|
#include "mozilla/Attributes.h" |
|
#include "mozilla/AutoRestore.h" |
|
#include "mozilla/AutoTimelineMarker.h" |
|
#include "mozilla/Base64.h" |
|
#include "mozilla/CheckedInt.h" |
|
#include "mozilla/DebugOnly.h" |
|
#include "mozilla/LoadInfo.h" |
|
#include "mozilla/dom/DocumentFragment.h" |
|
#include "mozilla/dom/DOMTypes.h" |
|
#include "mozilla/dom/Element.h" |
|
#include "mozilla/dom/HTMLMediaElement.h" |
|
#include "mozilla/dom/HTMLTemplateElement.h" |
|
#include "mozilla/dom/HTMLContentElement.h" |
|
#include "mozilla/dom/HTMLShadowElement.h" |
|
#include "mozilla/dom/ipc/BlobChild.h" |
|
#include "mozilla/dom/ipc/BlobParent.h" |
|
#include "mozilla/dom/Promise.h" |
|
#include "mozilla/dom/ScriptSettings.h" |
|
#include "mozilla/dom/TabParent.h" |
|
#include "mozilla/dom/TextDecoder.h" |
|
#include "mozilla/dom/TouchEvent.h" |
|
#include "mozilla/dom/ShadowRoot.h" |
|
#include "mozilla/dom/WorkerPrivate.h" |
|
#include "mozilla/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 "nsIWordBreaker.h" |
|
#include "nsIXPConnect.h" |
|
#include "nsJSUtils.h" |
|
#include "nsLWBrkCIID.h" |
|
#include "nsNetCID.h" |
|
#include "nsNetUtil.h" |
|
#include "nsNodeInfoManager.h" |
|
#include "nsNullPrincipal.h" |
|
#include "nsParserCIID.h" |
|
#include "nsParserConstants.h" |
|
#include "nsPIDOMWindow.h" |
|
#include "nsPresContext.h" |
|
#include "nsPrintfCString.h" |
|
#include "nsReferencedElement.h" |
|
#include "nsSandboxFlags.h" |
|
#include "nsScriptSecurityManager.h" |
|
#include "nsStreamUtils.h" |
|
#include "nsSVGFeatures.h" |
|
#include "nsTextEditorState.h" |
|
#include "nsTextFragment.h" |
|
#include "nsTextNode.h" |
|
#include "nsThreadUtils.h" |
|
#include "nsUnicharUtilCIID.h" |
|
#include "nsUnicodeProperties.h" |
|
#include "nsViewManager.h" |
|
#include "nsViewportInfo.h" |
|
#include "nsWidgetsCID.h" |
|
#include "nsWrapperCacheInlines.h" |
|
#include "nsXULPopupManager.h" |
|
#include "xpcprivate.h" // nsXPConnect |
|
#include "HTMLSplitOnSpacesTokenizer.h" |
|
#include "nsContentTypeParser.h" |
|
#include "nsICookiePermission.h" |
|
#include "mozIThirdPartyUtil.h" |
|
#include "nsICookieService.h" |
|
#include "mozilla/EnumSet.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::sFullscreenApiIsContentOnly = false; |
|
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: |
|
nsRefPtr<EventListenerManager> mListenerManager; |
|
}; |
|
|
|
static void |
|
EventListenerManagerHashInitEntry(PLDHashEntryHdr *entry, const void *key) |
|
{ |
|
// Initialize the entry with placement new |
|
new (entry) EventListenerManagerMapEntry(key); |
|
} |
|
|
|
static void |
|
EventListenerManagerHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) |
|
{ |
|
EventListenerManagerMapEntry *lm = |
|
static_cast<EventListenerManagerMapEntry *>(entry); |
|
|
|
// Let the EventListenerManagerMapEntry clean itself up... |
|
lm->~EventListenerManagerMapEntry(); |
|
} |
|
|
|
class SameOriginCheckerImpl final : public nsIChannelEventSink, |
|
public nsIInterfaceRequestor |
|
{ |
|
~SameOriginCheckerImpl() {} |
|
|
|
NS_DECL_ISUPPORTS |
|
NS_DECL_NSICHANNELEVENTSINK |
|
NS_DECL_NSIINTERFACEREQUESTOR |
|
}; |
|
|
|
class CharsetDetectionObserver final : public nsICharsetDetectionObserver |
|
{ |
|
public: |
|
NS_DECL_ISUPPORTS |
|
|
|
NS_IMETHOD Notify(const char *aCharset, nsDetectionConfident aConf) override |
|
{ |
|
mCharset = aCharset; |
|
return NS_OK; |
|
} |
|
|
|
const nsACString& GetResult() const |
|
{ |
|
return mCharset; |
|
} |
|
|
|
private: |
|
nsCString mCharset; |
|
}; |
|
|
|
} // namespace |
|
|
|
/* static */ |
|
TimeDuration |
|
nsContentUtils::HandlingUserInputTimeout() |
|
{ |
|
return TimeDuration::FromMilliseconds(sHandlingInputTimeout); |
|
} |
|
|
|
// static |
|
nsresult |
|
nsContentUtils::Init() |
|
{ |
|
if (sInitialized) { |
|
NS_WARNING("Init() called twice"); |
|
|
|
return NS_OK; |
|
} |
|
|
|
sNameSpaceManager = nsNameSpaceManager::GetInstance(); |
|
NS_ENSURE_TRUE(sNameSpaceManager, NS_ERROR_OUT_OF_MEMORY); |
|
|
|
sXPConnect = nsXPConnect::XPConnect(); |
|
|
|
sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager(); |
|
if(!sSecurityManager) |
|
return NS_ERROR_FAILURE; |
|
NS_ADDREF(sSecurityManager); |
|
|
|
sSecurityManager->GetSystemPrincipal(&sSystemPrincipal); |
|
MOZ_ASSERT(sSystemPrincipal); |
|
|
|
// We use the constructor here because we want infallible initialization; we |
|
// apparently don't care whether sNullSubjectPrincipal has a sane URI or not. |
|
nsRefPtr<nsNullPrincipal> nullPrincipal = new nsNullPrincipal(); |
|
nullPrincipal->Init(); |
|
nullPrincipal.forget(&sNullSubjectPrincipal); |
|
|
|
nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); |
|
if (NS_FAILED(rv)) { |
|
// This makes life easier, but we can live without it. |
|
|
|
sIOService = nullptr; |
|
} |
|
|
|
rv = CallGetService(NS_LBRK_CONTRACTID, &sLineBreaker); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
|
|
rv = CallGetService(NS_WBRK_CONTRACTID, &sWordBreaker); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
|
|
if (!InitializeEventTable()) |
|
return NS_ERROR_FAILURE; |
|
|
|
if (!sEventListenerManagersHash) { |
|
static const PLDHashTableOps hash_table_ops = |
|
{ |
|
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"); |
|
|
|
// Note: We deliberately read this pref here because this code runs |
|
// before the profile loads, so users' changes to this pref in about:config |
|
// won't have any effect on behaviour. We don't really want users messing |
|
// with this pref, as it affects the security model of the fullscreen API. |
|
sFullscreenApiIsContentOnly = Preferences::GetBool("full-screen-api.content-only", false); |
|
|
|
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", true); |
|
|
|
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 classes |
|
* which CSS says should be part of the first-letter. Currently, that is |
|
* all punctuation classes (P*). Note that this is a change from CSS2 |
|
* which excluded Pc and Pd. |
|
* |
|
* https://www.w3.org/TR/css-pseudo-4/#first-letter-pseudo |
|
* "Punctuation (i.e, characters that belong to the Punctuation (P*) Unicode |
|
* general category [UAX44]) [...]" |
|
*/ |
|
|
|
// static |
|
bool |
|
nsContentUtils::IsFirstLetterPunctuation(uint32_t aChar) |
|
{ |
|
switch (mozilla::unicode::GetGeneralCategory(aChar)) { |
|
case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ |
|
case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */ |
|
case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ |
|
case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ |
|
case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ |
|
case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ |
|
case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */ |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::IsFirstLetterPunctuationAt(const nsTextFragment* aFrag, uint32_t aOffset) |
|
{ |
|
char16_t h = aFrag->CharAt(aOffset); |
|
if (!IS_SURROGATE(h)) { |
|
return IsFirstLetterPunctuation(h); |
|
} |
|
if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) { |
|
char16_t l = aFrag->CharAt(aOffset + 1); |
|
if (NS_IS_LOW_SURROGATE(l)) { |
|
return IsFirstLetterPunctuation(SURROGATE_TO_UCS4(h, l)); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
// static |
|
bool nsContentUtils::IsAlphanumeric(uint32_t aChar) |
|
{ |
|
nsIUGenCategory::nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar); |
|
|
|
return (cat == nsIUGenCategory::kLetter || cat == nsIUGenCategory::kNumber); |
|
} |
|
|
|
// static |
|
bool nsContentUtils::IsAlphanumericAt(const nsTextFragment* aFrag, uint32_t aOffset) |
|
{ |
|
char16_t h = aFrag->CharAt(aOffset); |
|
if (!IS_SURROGATE(h)) { |
|
return IsAlphanumeric(h); |
|
} |
|
if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) { |
|
char16_t l = aFrag->CharAt(aOffset + 1); |
|
if (NS_IS_LOW_SURROGATE(l)) { |
|
return IsAlphanumeric(SURROGATE_TO_UCS4(h, l)); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::IsHTMLWhitespace(char16_t aChar) |
|
{ |
|
return aChar == char16_t(0x0009) || |
|
aChar == char16_t(0x000A) || |
|
aChar == char16_t(0x000C) || |
|
aChar == char16_t(0x000D) || |
|
aChar == char16_t(0x0020); |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::IsHTMLWhitespaceOrNBSP(char16_t aChar) |
|
{ |
|
return IsHTMLWhitespace(aChar) || aChar == char16_t(0xA0); |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::IsHTMLBlock(nsIContent* aContent) |
|
{ |
|
return aContent->IsAnyOfHTMLElements(nsGkAtoms::address, |
|
nsGkAtoms::article, |
|
nsGkAtoms::aside, |
|
nsGkAtoms::blockquote, |
|
nsGkAtoms::center, |
|
nsGkAtoms::dir, |
|
nsGkAtoms::div, |
|
nsGkAtoms::dl, // XXX why not dt and dd? |
|
nsGkAtoms::fieldset, |
|
nsGkAtoms::figure, // XXX shouldn't figcaption be on this list |
|
nsGkAtoms::footer, |
|
nsGkAtoms::form, |
|
nsGkAtoms::h1, |
|
nsGkAtoms::h2, |
|
nsGkAtoms::h3, |
|
nsGkAtoms::h4, |
|
nsGkAtoms::h5, |
|
nsGkAtoms::h6, |
|
nsGkAtoms::header, |
|
nsGkAtoms::hgroup, |
|
nsGkAtoms::hr, |
|
nsGkAtoms::li, |
|
nsGkAtoms::listing, |
|
nsGkAtoms::menu, |
|
nsGkAtoms::multicol, // XXX get rid of this one? |
|
nsGkAtoms::nav, |
|
nsGkAtoms::ol, |
|
nsGkAtoms::p, |
|
nsGkAtoms::pre, |
|
nsGkAtoms::section, |
|
nsGkAtoms::table, |
|
nsGkAtoms::ul, |
|
nsGkAtoms::xmp); |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::ParseIntMarginValue(const nsAString& aString, nsIntMargin& result) |
|
{ |
|
nsAutoString marginStr(aString); |
|
marginStr.CompressWhitespace(true, true); |
|
if (marginStr.IsEmpty()) { |
|
return false; |
|
} |
|
|
|
int32_t start = 0, end = 0; |
|
for (int count = 0; count < 4; count++) { |
|
if ((uint32_t)end >= marginStr.Length()) |
|
return false; |
|
|
|
// top, right, bottom, left |
|
if (count < 3) |
|
end = Substring(marginStr, start).FindChar(','); |
|
else |
|
end = Substring(marginStr, start).Length(); |
|
|
|
if (end <= 0) |
|
return false; |
|
|
|
nsresult ec; |
|
int32_t val = nsString(Substring(marginStr, start, end)).ToInteger(&ec); |
|
if (NS_FAILED(ec)) |
|
return false; |
|
|
|
switch(count) { |
|
case 0: |
|
result.top = val; |
|
break; |
|
case 1: |
|
result.right = val; |
|
break; |
|
case 2: |
|
result.bottom = val; |
|
break; |
|
case 3: |
|
result.left = val; |
|
break; |
|
} |
|
start += end + 1; |
|
} |
|
return true; |
|
} |
|
|
|
// static |
|
int32_t |
|
nsContentUtils::ParseLegacyFontSize(const nsAString& aValue) |
|
{ |
|
nsAString::const_iterator iter, end; |
|
aValue.BeginReading(iter); |
|
aValue.EndReading(end); |
|
|
|
while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { |
|
++iter; |
|
} |
|
|
|
if (iter == end) { |
|
return 0; |
|
} |
|
|
|
bool relative = false; |
|
bool negate = false; |
|
if (*iter == char16_t('-')) { |
|
relative = true; |
|
negate = true; |
|
++iter; |
|
} else if (*iter == char16_t('+')) { |
|
relative = true; |
|
++iter; |
|
} |
|
|
|
if (iter == end || *iter < char16_t('0') || *iter > char16_t('9')) { |
|
return 0; |
|
} |
|
|
|
// We don't have to worry about overflow, since we can bail out as soon as |
|
// we're bigger than 7. |
|
int32_t value = 0; |
|
while (iter != end && *iter >= char16_t('0') && *iter <= char16_t('9')) { |
|
value = 10*value + (*iter - char16_t('0')); |
|
if (value >= 7) { |
|
break; |
|
} |
|
++iter; |
|
} |
|
|
|
if (relative) { |
|
if (negate) { |
|
value = 3 - value; |
|
} else { |
|
value = 3 + value; |
|
} |
|
} |
|
|
|
return clamped(value, 1, 7); |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::IsControlledByServiceWorker(nsIDocument* aDocument) |
|
{ |
|
if (!ServiceWorkerInterceptionEnabled() || |
|
nsContentUtils::IsInPrivateBrowsing(aDocument)) { |
|
return false; |
|
} |
|
|
|
nsRefPtr<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); |
|
if (IsCallerChrome()) { |
|
return NS_OK; |
|
} |
|
|
|
/* |
|
* Get hold of each node's principal |
|
*/ |
|
|
|
nsIPrincipal* trustedPrincipal = aTrustedNode->NodePrincipal(); |
|
nsIPrincipal* unTrustedPrincipal = unTrustedNode->NodePrincipal(); |
|
|
|
if (trustedPrincipal == unTrustedPrincipal) { |
|
return NS_OK; |
|
} |
|
|
|
bool equal; |
|
// XXXbz should we actually have a Subsumes() check here instead? Or perhaps |
|
// a separate method for that, with callers using one or the other? |
|
if (NS_FAILED(trustedPrincipal->Equals(unTrustedPrincipal, &equal)) || |
|
!equal) { |
|
return NS_ERROR_DOM_PROP_ACCESS_DENIED; |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::CanCallerAccess(nsIPrincipal* aSubjectPrincipal, |
|
nsIPrincipal* aPrincipal) |
|
{ |
|
bool subsumes; |
|
nsresult rv = aSubjectPrincipal->Subsumes(aPrincipal, &subsumes); |
|
NS_ENSURE_SUCCESS(rv, false); |
|
|
|
if (subsumes) { |
|
return true; |
|
} |
|
|
|
// The subject doesn't subsume aPrincipal. Allow access only if the subject |
|
// is chrome. |
|
return IsCallerChrome(); |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::CanCallerAccess(nsIDOMNode *aNode) |
|
{ |
|
nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
NS_ENSURE_TRUE(node, false); |
|
return CanCallerAccess(node); |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::CanCallerAccess(nsINode* aNode) |
|
{ |
|
return CanCallerAccess(SubjectPrincipal(), aNode->NodePrincipal()); |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::CanCallerAccess(nsPIDOMWindow* aWindow) |
|
{ |
|
nsCOMPtr<nsIScriptObjectPrincipal> scriptObject = |
|
do_QueryInterface(aWindow->IsOuterWindow() ? |
|
aWindow->GetCurrentInnerWindow() : aWindow); |
|
NS_ENSURE_TRUE(scriptObject, false); |
|
|
|
return CanCallerAccess(SubjectPrincipal(), scriptObject->GetPrincipal()); |
|
} |
|
|
|
//static |
|
bool |
|
nsContentUtils::InProlog(nsINode *aNode) |
|
{ |
|
NS_PRECONDITION(aNode, "missing node to nsContentUtils::InProlog"); |
|
|
|
nsINode* parent = aNode->GetParentNode(); |
|
if (!parent || !parent->IsNodeOfType(nsINode::eDOCUMENT)) { |
|
return false; |
|
} |
|
|
|
nsIDocument* doc = static_cast<nsIDocument*>(parent); |
|
nsIContent* root = doc->GetRootElement(); |
|
|
|
return !root || doc->IndexOf(aNode) < doc->IndexOf(root); |
|
} |
|
|
|
nsIDocument* |
|
nsContentUtils::GetDocumentFromCaller() |
|
{ |
|
AutoJSContext cx; |
|
|
|
nsCOMPtr<nsPIDOMWindow> win = |
|
do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(cx))); |
|
if (!win) { |
|
return nullptr; |
|
} |
|
|
|
return win->GetExtantDoc(); |
|
} |
|
|
|
bool |
|
nsContentUtils::IsCallerChrome() |
|
{ |
|
MOZ_ASSERT(NS_IsMainThread()); |
|
if (SubjectPrincipal() == sSystemPrincipal) { |
|
return true; |
|
} |
|
|
|
// If the check failed, look for UniversalXPConnect on the cx compartment. |
|
return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext()); |
|
} |
|
|
|
bool |
|
nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell) |
|
{ |
|
if (!aDocShell) { |
|
return false; |
|
} |
|
bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument()); |
|
return !isChrome && sPrivacyResistFingerprinting; |
|
} |
|
|
|
namespace mozilla { |
|
namespace dom { |
|
namespace workers { |
|
extern bool IsCurrentThreadRunningChromeWorker(); |
|
extern JSContext* GetCurrentThreadJSContext(); |
|
} // namespace workers |
|
} // namespace dom |
|
} // namespace mozilla |
|
|
|
bool |
|
nsContentUtils::ThreadsafeIsCallerChrome() |
|
{ |
|
return NS_IsMainThread() ? |
|
IsCallerChrome() : |
|
mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); |
|
} |
|
|
|
bool |
|
nsContentUtils::IsCallerContentXBL() |
|
{ |
|
JSContext *cx = GetCurrentJSContext(); |
|
if (!cx) |
|
return false; |
|
|
|
JSCompartment *c = js::GetContextCompartment(cx); |
|
|
|
// For remote XUL, we run XBL in the XUL scope. Given that we care about |
|
// compat and not security for remote XUL, just always claim to be XBL. |
|
if (!xpc::AllowContentXBLScope(c)) { |
|
MOZ_ASSERT(nsContentUtils::AllowXULXBLForPrincipal(xpc::GetCompartmentPrincipal(c))); |
|
return true; |
|
} |
|
|
|
return xpc::IsContentXBLScope(c); |
|
} |
|
|
|
|
|
bool |
|
nsContentUtils::IsImageSrcSetDisabled() |
|
{ |
|
return Preferences::GetBool("dom.disable_image_src_set") && |
|
!IsCallerChrome(); |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::LookupBindingMember(JSContext* aCx, nsIContent *aContent, |
|
JS::Handle<jsid> aId, |
|
JS::MutableHandle<JSPropertyDescriptor> aDesc) |
|
{ |
|
nsXBLBinding* binding = aContent->GetXBLBinding(); |
|
if (!binding) |
|
return true; |
|
return binding->LookupMember(aCx, aId, aDesc); |
|
} |
|
|
|
// static |
|
nsINode* |
|
nsContentUtils::GetCrossDocParentNode(nsINode* aChild) |
|
{ |
|
NS_PRECONDITION(aChild, "The child is null!"); |
|
|
|
nsINode* parent = aChild->GetParentNode(); |
|
if (parent && parent->IsContent() && aChild->IsContent()) { |
|
parent = aChild->AsContent()->GetFlattenedTreeParent(); |
|
} |
|
|
|
if (parent || !aChild->IsNodeOfType(nsINode::eDOCUMENT)) |
|
return parent; |
|
|
|
nsIDocument* doc = static_cast<nsIDocument*>(aChild); |
|
nsIDocument* parentDoc = doc->GetParentDocument(); |
|
return parentDoc ? parentDoc->FindContentForSubDocument(doc) : nullptr; |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::ContentIsDescendantOf(const nsINode* aPossibleDescendant, |
|
const nsINode* aPossibleAncestor) |
|
{ |
|
NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); |
|
NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); |
|
|
|
do { |
|
if (aPossibleDescendant == aPossibleAncestor) |
|
return true; |
|
aPossibleDescendant = aPossibleDescendant->GetParentNode(); |
|
} while (aPossibleDescendant); |
|
|
|
return false; |
|
} |
|
|
|
bool |
|
nsContentUtils::ContentIsHostIncludingDescendantOf( |
|
const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) |
|
{ |
|
NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); |
|
NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); |
|
|
|
do { |
|
if (aPossibleDescendant == aPossibleAncestor) |
|
return true; |
|
if (aPossibleDescendant->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { |
|
aPossibleDescendant = |
|
static_cast<const DocumentFragment*>(aPossibleDescendant)->GetHost(); |
|
} else { |
|
aPossibleDescendant = aPossibleDescendant->GetParentNode(); |
|
} |
|
} while (aPossibleDescendant); |
|
|
|
return false; |
|
} |
|
|
|
// static |
|
bool |
|
nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant, |
|
nsINode* aPossibleAncestor) |
|
{ |
|
NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); |
|
NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); |
|
|
|
do { |
|
if (aPossibleDescendant == aPossibleAncestor) |
|
return true; |
|
|
|
aPossibleDescendant = GetCrossDocParentNode(aPossibleDescendant); |
|
} while (aPossibleDescendant); |
|
|
|
return false; |
|
} |
|
|
|
|
|
// static |
|
nsresult |
|
nsContentUtils::GetAncestors(nsINode* aNode, |
|
nsTArray<nsINode*>& aArray) |
|
{ |
|
while (aNode) { |
|
aArray.AppendElement(aNode); |
|
aNode = aNode->GetParentNode(); |
|
} |
|
return NS_OK; |
|
} |
|
|
|
// static |
|
nsresult |
|
nsContentUtils::GetAncestorsAndOffsets(nsIDOMNode* aNode, |
|
int32_t aOffset, |
|
nsTArray<nsIContent*>* aAncestorNodes, |
|
nsTArray<int32_t>* aAncestorOffsets) |
|
{ |
|
NS_ENSURE_ARG_POINTER(aNode); |
|
|
|
nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); |
|
|
|
if (!content) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (!aAncestorNodes->IsEmpty()) { |
|
NS_WARNING("aAncestorNodes is not empty"); |
|
aAncestorNodes->Clear(); |
|
} |
|
|
|
if (!aAncestorOffsets->IsEmpty()) { |
|
NS_WARNING("aAncestorOffsets is not empty"); |
|
aAncestorOffsets->Clear(); |
|
} |
|
|
|
// insert the node itself |
|
aAncestorNodes->AppendElement(content.get()); |
|
aAncestorOffsets->AppendElement(aOffset); |
|
|
|
// insert all the ancestors |
|
nsIContent* child = content; |
|
nsIContent* parent = child->GetParent(); |
|
while (parent) { |
|
aAncestorNodes->AppendElement(parent); |
|
aAncestorOffsets->AppendElement(parent->IndexOf(child)); |
|
child = parent; |
|
parent = parent->GetParent(); |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
// static |
|
nsresult |
|
nsContentUtils::GetCommonAncestor(nsIDOMNode *aNode, |
|
nsIDOMNode *aOther, |
|
nsIDOMNode** aCommonAncestor) |
|
{ |
|
*aCommonAncestor = nullptr; |
|
|
|
nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode); |
|
nsCOMPtr<nsINode> node2 = do_QueryInterface(aOther); |
|
|
|
NS_ENSURE_TRUE(node1 && node2, NS_ERROR_UNEXPECTED); |
|
|
|
nsINode* common = GetCommonAncestor(node1, node2); |
|
NS_ENSURE_TRUE(common, NS_ERROR_NOT_AVAILABLE); |
|
|
|
return CallQueryInterface(common, aCommonAncestor); |
|
} |
|
|
|
// static |
|
nsINode* |
|
nsContentUtils::GetCommonAncestor(nsINode* aNode1, |
|
nsINode* aNode2) |
|
{ |
|
if (aNode1 == aNode2) { |
|
return aNode1; |
|
} |
|
|
|
// Build the chain of parents |
|
nsAutoTArray<nsINode*, 30> parents1, parents2; |
|
do { |
|
parents1.AppendElement(aNode1); |
|
aNode1 = aNode1->GetParentNode(); |
|
} while (aNode1); |
|
do { |
|
parents2.AppendElement(aNode2); |
|
aNode2 = aNode2->GetParentNode(); |
|
} while (aNode2); |
|
|
|
// Find where the parent chain differs |
|
uint32_t pos1 = parents1.Length(); |
|
uint32_t pos2 = parents2.Length(); |
|
nsINode* parent = nullptr; |
|
uint32_t len; |
|
for (len = std::min(pos1, pos2); len > 0; --len) { |
|
nsINode* child1 = parents1.ElementAt(--pos1); |
|
nsINode* child2 = parents2.ElementAt(--pos2); |
|
if (child1 != child2) { |
|
break; |
|
} |
|
parent = child1; |
|
} |
|
|
|
return parent; |
|
} |
|
|
|
/* static */ |
|
bool |
|
nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2) |
|
{ |
|
return (aNode2->CompareDocumentPosition(*aNode1) & |
|
(nsIDOMNode::DOCUMENT_POSITION_PRECEDING | |
|
nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED)) == |
|
nsIDOMNode::DOCUMENT_POSITION_PRECEDING; |
|
} |
|
|
|
/* static */ |
|
int32_t |
|
nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1, |
|
nsINode* aParent2, int32_t aOffset2, |
|
bool* aDisconnected) |
|
{ |
|
if (aParent1 == aParent2) { |
|
return aOffset1 < aOffset2 ? -1 : |
|
aOffset1 > aOffset2 ? 1 : |
|
0; |
|
} |
|
|
|
nsAutoTArray<nsINode*, 32> parents1, parents2; |
|
nsINode* node1 = aParent1; |
|
nsINode* node2 = aParent2; |
|
do { |
|
parents1.AppendElement(node1); |
|
node1 = node1->GetParentNode(); |
|
} while (node1); |
|
do { |
|
parents2.AppendElement(node2); |
|
node2 = node2->GetParentNode(); |
|
} while (node2); |
|
|
|
uint32_t pos1 = parents1.Length() - 1; |
|
uint32_t pos2 = parents2.Length() - 1; |
|
|
|
bool disconnected = parents1.ElementAt(pos1) != parents2.ElementAt(pos2); |
|
if (aDisconnected) { |
|
*aDisconnected = disconnected; |
|
} |
|
if (disconnected) { |
|
NS_ASSERTION(aDisconnected, "unexpected disconnected nodes"); |
|
return 1; |
|
} |
|
|
|
// Find where the parent chains differ |
|
nsINode* parent = parents1.ElementAt(pos1); |
|
uint32_t len; |
|
for (len = std::min(pos1, pos2); len > 0; --len) { |
|
nsINode* child1 = parents1.ElementAt(--pos1); |
|
nsINode* child2 = parents2.ElementAt(--pos2); |
|
if (child1 != child2) { |
|
return parent->IndexOf(child1) < parent->IndexOf(child2) ? -1 : 1; |
|
} |
|
parent = child1; |
|
} |
|
|
|
|
|
// The parent chains never differed, so one of the nodes is an ancestor of |
|
// the other |
|
|
|
NS_ASSERTION(!pos1 || !pos2, |
|
"should have run out of parent chain for one of the nodes"); |
|
|
|
if (!pos1) { |
|
nsINode* child2 = parents2.ElementAt(--pos2); |
|
return aOffset1 <= parent->IndexOf(child2) ? -1 : 1; |
|
} |
|
|
|
nsINode* child1 = parents1.ElementAt(--pos1); |
|
return parent->IndexOf(child1) < aOffset2 ? -1 : 1; |
|
} |
|
|
|
/* static */ |
|
int32_t |
|
nsContentUtils::ComparePoints(nsIDOMNode* aParent1, int32_t aOffset1, |
|
nsIDOMNode* aParent2, int32_t aOffset2, |
|
bool* aDisconnected) |
|
{ |
|
nsCOMPtr<nsINode> parent1 = do_QueryInterface(aParent1); |
|
nsCOMPtr<nsINode> parent2 = do_QueryInterface(aParent2); |
|
NS_ENSURE_TRUE(parent1 && parent2, -1); |
|
return ComparePoints(parent1, aOffset1, parent2, aOffset2); |
|
} |
|
|
|
inline bool |
|
IsCharInSet(const char* aSet, |
|
const char16_t aChar) |
|
{ |
|
char16_t ch; |
|
while ((ch = *aSet)) { |
|
if (aChar == char16_t(ch)) { |
|
return true; |
|
} |
|
++aSet; |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* This method strips leading/trailing chars, in given set, from string. |
|
*/ |
|
|
|
// static |
|
const nsDependentSubstring |
|
nsContentUtils::TrimCharsInSet(const char* aSet, |
|
const nsAString& aValue) |
|
{ |
|
nsAString::const_iterator valueCurrent, valueEnd; |
|
|
|
aValue.BeginReading(valueCurrent); |
|
aValue.EndReading(valueEnd); |
|
|
|
// Skip characters in the beginning |
|
while (valueCurrent != valueEnd) { |
|
if (!IsCharInSet(aSet, *valueCurrent)) { |
|
break; |
|
} |
|
++valueCurrent; |
|
} |
|
|
|
if (valueCurrent != valueEnd) { |
|
for (;;) { |
|
--valueEnd; |
|
if (!IsCharInSet(aSet, *valueEnd)) { |
|
break; |
|
} |
|
} |
|
++valueEnd; // Step beyond the last character we want in the value. |
|
} |
|
|
|
// valueEnd should point to the char after the last to copy |
|
return Substring(valueCurrent, valueEnd); |
|
} |
|
|
|
/** |
|
* This method strips leading and trailing whitespace from a string. |
|
*/ |
|
|
|
// static |
|
template<bool IsWhitespace(char16_t)> |
|
const nsDependentSubstring |
|
nsContentUtils::TrimWhitespace(const nsAString& aStr, bool aTrimTrailing) |
|
{ |
|
nsAString::const_iterator start, end; |
|
|
|
aStr.BeginReading(start); |
|
aStr.EndReading(end); |
|
|
|
// Skip whitespace characters in the beginning |
|
while (start != end && IsWhitespace(*start)) { |
|
++start; |
|
} |
|
|
|
if (aTrimTrailing) { |
|
// Skip whitespace characters in the end. |
|
while (end != start) { |
|
--end; |
|
|
|
if (!IsWhitespace(*end)) { |
|
// Step back to the last non-whitespace character. |
|
++end; |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
// Return a substring for the string w/o leading and/or trailing |
|
// whitespace |
|
|
|
return Substring(start, end); |
|
} |
|
|
|
// Declaring the templates we are going to use avoid linking issues without |
|
// inlining the method. Considering there is not so much spaces checking |
|
// methods we can consider this to be better than inlining. |
|
template |
|
const nsDependentSubstring |
|
nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(const nsAString&, bool); |
|
template |
|
const nsDependentSubstring |
|
nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(const nsAString&, bool); |
|
template |
|
const nsDependentSubstring |
|
nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespaceOrNBSP>(const nsAString&, bool); |
|
|
|
static inline void KeyAppendSep(nsACString& aKey) |
|
{ |
|
if (!aKey.IsEmpty()) |