diff --git a/browser/base/content/test/general/browser_bug839103.js b/browser/base/content/test/general/browser_bug839103.js new file mode 100644 index 0000000000..5f6455b162 --- /dev/null +++ b/browser/base/content/test/general/browser_bug839103.js @@ -0,0 +1,159 @@ +const gTestRoot = getRootDirectory(gTestPath); +const gStyleSheet = "bug839103.css"; + +var gTab = null; +var needsInitialApplicableStateEvent = false; +var needsInitialApplicableStateEventFor = null; + +function test() { + waitForExplicitFinish(); + gBrowser.addEventListener("StyleSheetAdded", initialStylesheetAdded, true); + gTab = gBrowser.selectedTab = gBrowser.addTab(gTestRoot + "test_bug839103.html"); + gTab.linkedBrowser.addEventListener("load", tabLoad, true); +} + +function initialStylesheetAdded(evt) { + gBrowser.removeEventListener("StyleSheetAdded", initialStylesheetAdded, true); + ok(true, "received initial style sheet event"); + is(evt.type, "StyleSheetAdded", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.documentSheet, "style sheet is a document sheet"); +} + +function tabLoad(evt) { + gTab.linkedBrowser.removeEventListener(evt.type, tabLoad, true); + executeSoon(continueTest); +} + +var gLinkElement = null; + +function unexpectedContentEvent(evt) { + ok(false, "Received a " + evt.type + " event on content"); +} + +// We've seen the original stylesheet in the document. +// Now add a stylesheet on the fly and make sure we see it. +function continueTest() { + info("continuing test"); + + let doc = gBrowser.contentDocument; + doc.styleSheetChangeEventsEnabled = true; + doc.addEventListener("StyleSheetAdded", unexpectedContentEvent, false); + doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent, false); + doc.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false); + doc.defaultView.addEventListener("StyleSheetAdded", unexpectedContentEvent, false); + doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent, false); + doc.defaultView.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false); + let link = doc.createElement('link'); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('type', 'text/css'); + link.setAttribute('href', gTestRoot + gStyleSheet); + gLinkElement = link; + + gBrowser.addEventListener("StyleSheetAdded", dynamicStylesheetAdded, true); + gBrowser.addEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true); + doc.body.appendChild(link); +} + +function dynamicStylesheetAdded(evt) { + gBrowser.removeEventListener("StyleSheetAdded", dynamicStylesheetAdded, true); + ok(true, "received dynamic style sheet event"); + is(evt.type, "StyleSheetAdded", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.documentSheet, "style sheet is a document sheet"); +} + +function dynamicStylesheetApplicableStateChanged(evt) { + gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true); + ok(true, "received dynamic style sheet applicable state change event"); + is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value"); + is(evt.applicable, true, "evt.applicable has the right value"); + + gBrowser.addEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true); + gLinkElement.disabled = true; +} + +function dynamicStylesheetApplicableStateChangedToFalse(evt) { + gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true); + is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value"); + ok(true, "received dynamic style sheet applicable state change event after media=\"\" changed"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value"); + is(evt.applicable, false, "evt.applicable has the right value"); + + gBrowser.addEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true); + gBrowser.contentDocument.body.removeChild(gLinkElement); +} + +function dynamicStylesheetRemoved(evt) { + gBrowser.removeEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true); + ok(true, "received dynamic style sheet removal"); + is(evt.type, "StyleSheetRemoved", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.stylesheet.href.includes(gStyleSheet), "evt.stylesheet is the removed stylesheet"); + + gBrowser.addEventListener("StyleRuleAdded", styleRuleAdded, true); + gBrowser.contentDocument.querySelector("style").sheet.insertRule("*{color:black}", 0); +} + +function styleRuleAdded(evt) { + gBrowser.removeEventListener("StyleRuleAdded", styleRuleAdded, true); + ok(true, "received style rule added event"); + is(evt.type, "StyleRuleAdded", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); + is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value"); + + gBrowser.addEventListener("StyleRuleChanged", styleRuleChanged, true); + evt.rule.style.cssText = "color:green"; +} + +function styleRuleChanged(evt) { + gBrowser.removeEventListener("StyleRuleChanged", styleRuleChanged, true); + ok(true, "received style rule changed event"); + is(evt.type, "StyleRuleChanged", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); + is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value"); + + gBrowser.addEventListener("StyleRuleRemoved", styleRuleRemoved, true); + evt.stylesheet.deleteRule(0); +} + +function styleRuleRemoved(evt) { + gBrowser.removeEventListener("StyleRuleRemoved", styleRuleRemoved, true); + ok(true, "received style rule removed event"); + is(evt.type, "StyleRuleRemoved", "evt.type has expected value"); + is(evt.target, gBrowser.contentDocument, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); + + executeSoon(concludeTest); +} + +function concludeTest() { + let doc = gBrowser.contentDocument; + doc.removeEventListener("StyleSheetAdded", unexpectedContentEvent, false); + doc.removeEventListener("StyleSheetRemoved", unexpectedContentEvent, false); + doc.removeEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false); + doc.defaultView.removeEventListener("StyleSheetAdded", unexpectedContentEvent, false); + doc.defaultView.removeEventListener("StyleSheetRemoved", unexpectedContentEvent, false); + doc.defaultView.removeEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false); + gBrowser.removeCurrentTab(); + gLinkElement = null; + gTab = null; + finish(); +} diff --git a/dom/base/nsCCUncollectableMarker.cpp b/dom/base/nsCCUncollectableMarker.cpp index 08f71593d9..9a525229e1 100644 --- a/dom/base/nsCCUncollectableMarker.cpp +++ b/dom/base/nsCCUncollectableMarker.cpp @@ -437,7 +437,7 @@ nsCCUncollectableMarker::Observe(nsISupports* aSubject, const char* aTopic, switch(sFSState) { case eUnmarkJSEventListeners: { - nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(sGeneration); + nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(); break; } case eUnmarkMessageManagers: { diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp index 0477d3f38d..804ad79d39 100644 --- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -33,7 +33,6 @@ #include "nsIApplicationCacheContainer.h" #include "nsIApplicationCacheChannel.h" #include "nsIScriptSecurityManager.h" -#include "nsISpeculativeConnect.h" #include "nsICookieService.h" #include "nsContentUtils.h" #include "nsNodeInfoManager.h" @@ -876,20 +875,15 @@ nsContentSink::PrefetchDNS(const nsAString &aHref) void nsContentSink::Preconnect(const nsAString &aHref) { - nsCOMPtr - speculator(do_QueryInterface(nsContentUtils::GetIOService())); - if (!speculator) { - return; - } - // construct URI using document charset const nsACString& charset = mDocument->GetDocumentCharacterSet(); nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), aHref, charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(), mDocument->GetDocBaseURI()); - if (uri) { - speculator->SpeculativeConnect(uri, nullptr); + + if (uri && mDocument) { + mDocument->MaybePreconnect(uri); } } diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h index 641e9f3a3f..bf7b1ae100 100644 --- a/dom/base/nsContentSink.h +++ b/dom/base/nsContentSink.h @@ -163,10 +163,9 @@ protected: void PrefetchHref(const nsAString &aHref, nsINode *aSource, bool aExplicit); - // For both PrefetchDNS() and Preconnect() aHref can either be the usual + // For PrefetchDNS() aHref can either be the usual // URI format or of the form "//www.hostname.com" without a scheme. void PrefetchDNS(const nsAString &aHref); - void Preconnect(const nsAString &aHref); // Gets the cache key (used to identify items in a cache) of the channel. nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey); @@ -224,6 +223,10 @@ public: // element and calls the above method. void ProcessOfflineManifest(nsIContent *aElement); + // For Preconnect() aHref can either be the usual + // URI format or of the form "//www.hostname.com" without a scheme. + void Preconnect(const nsAString &aHref); + protected: // Tries to scroll to the URI's named anchor. Once we've successfully // done that, further calls to this method will be ignored. diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index c325693e24..425467b3c0 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -93,6 +93,7 @@ #include "nsHostObjectProtocolHandler.h" #include "nsHtml5Module.h" #include "nsHtml5StringParser.h" +#include "nsIAppShell.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsICategoryManager.h" #include "nsIChannelEventSink.h" @@ -183,6 +184,7 @@ #include "nsUnicodeProperties.h" #include "nsViewManager.h" #include "nsViewportInfo.h" +#include "nsWidgetsCID.h" #include "nsWrapperCacheInlines.h" #include "nsXULPopupManager.h" #include "xpcprivate.h" // nsXPConnect @@ -256,6 +258,7 @@ bool nsContentUtils::sIsUserTimingLoggingEnabled = false; bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; bool nsContentUtils::sGettersDecodeURLHash = false; +bool nsContentUtils::sPrivacyResistFingerprinting = false; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; @@ -339,6 +342,7 @@ namespace { static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID); static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); static PLDHashTable* sEventListenerManagersHash; @@ -545,6 +549,9 @@ nsContentUtils::Init() Preferences::AddBoolVarCache(&sGettersDecodeURLHash, "dom.url.getters_decode_hash", false); + Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting, + "privacy.resistFingerprinting", false); + Preferences::AddUintVarCache(&sHandlingInputTimeout, "dom.event.handling-user-input-time-limit", 1000); @@ -2010,6 +2017,16 @@ nsContentUtils::IsCallerChrome() 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 { @@ -3993,29 +4010,22 @@ nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent, } } -PLDHashOperator -ListenerEnumerator(PLDHashTable* aTable, PLDHashEntryHdr* aEntry, - uint32_t aNumber, void* aArg) +void +nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments() { - EventListenerManagerMapEntry* entry = - static_cast(aEntry); - if (entry) { + if (!sEventListenerManagersHash) { + return; + } + + PLDHashTable::Iterator iter(sEventListenerManagersHash); + while (iter.HasMoreEntries()) { + auto entry = static_cast(iter.NextEntry()); nsINode* n = static_cast(entry->mListenerManager->GetTarget()); if (n && n->IsInDoc() && nsCCUncollectableMarker::InGeneration(n->OwnerDoc()->GetMarkedCCGeneration())) { entry->mListenerManager->MarkForCC(); } } - return PL_DHASH_NEXT; -} - -void -nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(uint32_t aGeneration) -{ - if (sEventListenerManagersHash) { - PL_DHashTableEnumerate(sEventListenerManagersHash, ListenerEnumerator, - &aGeneration); - } } /* static */ @@ -5165,6 +5175,20 @@ nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable) return true; } +/* static */ +void +nsContentUtils::RunInStableState(already_AddRefed aRunnable, + DispatchFailureHandling aHandling) +{ + nsCOMPtr runnable = aRunnable; + nsCOMPtr appShell(do_GetService(kAppShellCID)); + if (!appShell) { + MOZ_ASSERT(aHandling == DispatchFailureHandling::IgnoreFailure); + return; + } + appShell->RunInStableState(runnable.forget()); +} + void nsContentUtils::EnterMicroTask() { diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 5595e04b38..eb55d356ef 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -198,6 +198,9 @@ public: JS::Handle aId, JS::MutableHandle aDesc); + // Check whether we should avoid leaking distinguishing information to JS/CSS. + static bool ShouldResistFingerprinting(nsIDocShell* aDocShell); + /** * Returns the parent node of aChild crossing document boundaries. * Uses the parent node in the composed document. @@ -1180,7 +1183,7 @@ public: static mozilla::EventListenerManager* GetExistingListenerManagerForNode(const nsINode* aNode); - static void UnmarkGrayJSListenersInCCGenerationDocuments(uint32_t aGeneration); + static void UnmarkGrayJSListenersInCCGenerationDocuments(); /** * Remove the eventlistener manager for aNode. @@ -1633,6 +1636,27 @@ public: */ static void WarnScriptWasIgnored(nsIDocument* aDocument); + /** + * Whether to assert that RunInStableState() succeeds, or ignore failure, + * which may happen late in shutdown. + */ + enum class DispatchFailureHandling { AssertSuccess, IgnoreFailure }; + + /** + * Add a "synchronous section", in the form of an nsIRunnable run once the + * event loop has reached a "stable state". |aRunnable| must not cause any + * queued events to be processed (i.e. must not spin the event loop). + * We've reached a stable state when the currently executing task/event has + * finished, see + * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section + * In practice this runs aRunnable once the currently executing event + * finishes. If called multiple times per task/event, all the runnables will + * be executed, in the order in which RunInStableState() was called. + */ + static void RunInStableState(already_AddRefed aRunnable, + DispatchFailureHandling aHandling = + DispatchFailureHandling::AssertSuccess); + /** * Retrieve information about the viewport as a data structure. * This will return information in the viewport META data section @@ -2006,6 +2030,16 @@ public: return sGettersDecodeURLHash && sEncodeDecodeURLHash; } + /* + * Returns true if the browser should attempt to prevent content scripts + * from collecting distinctive information about the browser that could + * be used to "fingerprint" and track the user across websites. + */ + static bool ResistFingerprinting() + { + return sPrivacyResistFingerprinting; + } + /** * Returns true if the doc tree branch which contains aDoc contains any * plugins which we don't control event dispatch for, i.e. do any plugins @@ -2548,6 +2582,7 @@ private: static bool sIsExperimentalAutocompleteEnabled; static bool sEncodeDecodeURLHash; static bool sGettersDecodeURLHash; + static bool sPrivacyResistFingerprinting; static nsHtml5StringParser* sHTMLFragmentParser; static nsIParser* sXMLFragmentParser; diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 6d2f681fe0..3ba802464b 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -3432,16 +3432,14 @@ nsDOMWindowUtils::DispatchEventToChromeOnly(nsIDOMEventTarget* aTarget, } NS_IMETHODIMP -nsDOMWindowUtils::RunInStableState(nsIRunnable *runnable) +nsDOMWindowUtils::RunInStableState(nsIRunnable *aRunnable) { MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); - nsCOMPtr appShell(do_GetService(kAppShellCID)); - if (!appShell) { - return NS_ERROR_NOT_AVAILABLE; - } + nsCOMPtr runnable = aRunnable; + nsContentUtils::RunInStableState(runnable.forget()); - return appShell->RunInStableState(runnable); + return NS_OK; } NS_IMETHODIMP diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index f3244484b9..8b8fbdc45a 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -229,6 +229,8 @@ #include "mozilla/dom/BoxObject.h" #include "gfxVR.h" +#include "nsISpeculativeConnect.h" + #ifdef MOZ_MEDIA_NAVIGATOR #include "mozilla/MediaManager.h" #endif // MOZ_MEDIA_NAVIGATOR @@ -268,14 +270,14 @@ nsIdentifierMapEntry::Traverse(nsCycleCollectionTraversalCallback* aCallback) bool nsIdentifierMapEntry::IsEmpty() { - return mIdContentList.Count() == 0 && !mNameContentList && + return mIdContentList.IsEmpty() && !mNameContentList && !mChangeCallbacks && !mImageElement; } Element* nsIdentifierMapEntry::GetIdElement() { - return static_cast(mIdContentList.SafeElementAt(0)); + return mIdContentList.SafeElementAt(0); } Element* @@ -287,8 +289,8 @@ nsIdentifierMapEntry::GetImageIdElement() void nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray* aElements) { - for (int32_t i = 0; i < mIdContentList.Count(); ++i) { - aElements->AppendObject(static_cast(mIdContentList[i])); + for (size_t i = 0; i < mIdContentList.Length(); ++i) { + aElements->AppendObject(mIdContentList[i]); } } @@ -575,16 +577,15 @@ bool nsIdentifierMapEntry::AddIdElement(Element* aElement) { NS_PRECONDITION(aElement, "Must have element"); - NS_PRECONDITION(mIdContentList.IndexOf(nullptr) < 0, + NS_PRECONDITION(!mIdContentList.Contains(nullptr), "Why is null in our list?"); #ifdef DEBUG - Element* currentElement = - static_cast(mIdContentList.SafeElementAt(0)); + Element* currentElement = mIdContentList.SafeElementAt(0); #endif // Common case - if (mIdContentList.Count() == 0) { + if (mIdContentList.IsEmpty()) { if (!mIdContentList.AppendElement(aElement)) return false; NS_ASSERTION(currentElement == nullptr, "How did that happen?"); @@ -596,7 +597,7 @@ nsIdentifierMapEntry::AddIdElement(Element* aElement) // with us. Search for the right place to insert the content. size_t idx; - if (BinarySearchIf(mIdContentList, 0, mIdContentList.Count(), + if (BinarySearchIf(mIdContentList, 0, mIdContentList.Length(), PositionComparator(aElement), &idx)) { // Already in the list, so already in the right spot. Get out of here. // XXXbz this only happens because XUL does all sorts of random @@ -604,12 +605,12 @@ nsIdentifierMapEntry::AddIdElement(Element* aElement) return true; } - if (!mIdContentList.InsertElementAt(aElement, idx)) + if (!mIdContentList.InsertElementAt(idx, aElement)) { return false; + } if (idx == 0) { - Element* oldElement = - static_cast(mIdContentList.SafeElementAt(1)); + Element* oldElement = mIdContentList.SafeElementAt(1); NS_ASSERTION(currentElement == oldElement, "How did that happen?"); FireChangeCallbacks(oldElement, aElement); } @@ -628,17 +629,15 @@ nsIdentifierMapEntry::RemoveIdElement(Element* aElement) // Only assert this in HTML documents for now as XUL does all sorts of weird // crap. NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() || - mIdContentList.IndexOf(aElement) >= 0, + mIdContentList.Contains(aElement), "Removing id entry that doesn't exist"); // XXXbz should this ever Compact() I guess when all the content is gone // we'll just get cleaned up in the natural order of things... - Element* currentElement = - static_cast(mIdContentList.SafeElementAt(0)); + Element* currentElement = mIdContentList.SafeElementAt(0); mIdContentList.RemoveElement(aElement); if (currentElement == aElement) { - FireChangeCallbacks(currentElement, - static_cast(mIdContentList.SafeElementAt(0))); + FireChangeCallbacks(currentElement, mIdContentList.SafeElementAt(0)); } } @@ -695,17 +694,6 @@ public: nsIDocument *mSubDocument; }; -struct FindContentData -{ - explicit FindContentData(nsIDocument* aSubDoc) - : mSubDocument(aSubDoc), mResult(nullptr) - { - } - - nsISupports *mSubDocument; - Element *mResult; -}; - /** * A struct that holds all the information about a radio group. @@ -1839,22 +1827,6 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDocument) return Element::CanSkipThis(tmp); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END -static PLDHashOperator -SubDocTraverser(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number, - void *arg) -{ - SubDocMapEntry *entry = static_cast(hdr); - nsCycleCollectionTraversalCallback *cb = - static_cast(arg); - - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mSubDocuments entry->mKey"); - cb->NoteXPCOMChild(entry->mKey); - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mSubDocuments entry->mSubDocument"); - cb->NoteXPCOMChild(entry->mSubDocument); - - return PL_DHASH_NEXT; -} - static PLDHashOperator RadioGroupsTraverser(const nsAString& aKey, nsRadioGroupStruct* aData, void* aClosure) @@ -2014,7 +1986,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) } if (tmp->mSubDocuments) { - PL_DHashTableEnumerate(tmp->mSubDocuments, SubDocTraverser, &cb); + PLDHashTable::Iterator iter(tmp->mSubDocuments); + while (iter.HasMoreEntries()) { + auto entry = static_cast(iter.NextEntry()); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mSubDocuments entry->mKey"); + cb.NoteXPCOMChild(entry->mKey); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mSubDocuments entry->mSubDocument"); + cb.NoteXPCOMChild(entry->mSubDocument); + } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader) @@ -4085,22 +4067,6 @@ nsDocument::GetSubDocumentFor(nsIContent *aContent) const return nullptr; } -static PLDHashOperator -FindContentEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr, - uint32_t number, void *arg) -{ - SubDocMapEntry *entry = static_cast(hdr); - FindContentData *data = static_cast(arg); - - if (entry->mSubDocument == data->mSubDocument) { - data->mResult = entry->mKey; - - return PL_DHASH_STOP; - } - - return PL_DHASH_NEXT; -} - Element* nsDocument::FindContentForSubDocument(nsIDocument *aDocument) const { @@ -4110,10 +4076,14 @@ nsDocument::FindContentForSubDocument(nsIDocument *aDocument) const return nullptr; } - FindContentData data(aDocument); - PL_DHashTableEnumerate(mSubDocuments, FindContentEnumerator, &data); - - return data.mResult; + PLDHashTable::Iterator iter(mSubDocuments); + while (iter.HasMoreEntries()) { + auto entry = static_cast(iter.NextEntry()); + if (entry->mSubDocument == aDocument) { + return entry->mKey; + } + } + return nullptr; } bool @@ -5018,7 +4988,7 @@ nsDocument::GetElementById(const nsAString& aElementId) return entry ? entry->GetIdElement() : nullptr; } -const nsSmallVoidArray* +const nsTArray* nsDocument::GetAllElementsForId(const nsAString& aElementId) const { if (aElementId.IsEmpty()) { @@ -5026,7 +4996,7 @@ nsDocument::GetAllElementsForId(const nsAString& aElementId) const } nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId); - return entry ? entry->GetIdElements() : nullptr; + return entry ? &entry->GetIdElements() : nullptr; } NS_IMETHODIMP @@ -5123,6 +5093,10 @@ nsDocument::DispatchContentLoadedEvents() // Unpin references to preloaded images mPreloadingImages.Clear(); + // DOM manipulation after content loaded should not care if the element + // came from the preloader. + mPreloadedPreconnects.Clear(); + if (mTiming) { mTiming->NotifyDOMContentLoadedStart(nsIDocument::GetDocumentURI()); } @@ -8741,45 +8715,22 @@ struct SubDocEnumArgs void *data; }; -static PLDHashOperator -SubDocHashEnum(PLDHashTable *table, PLDHashEntryHdr *hdr, - uint32_t number, void *arg) -{ - SubDocMapEntry *entry = static_cast(hdr); - SubDocEnumArgs *args = static_cast(arg); - - nsIDocument *subdoc = entry->mSubDocument; - bool next = subdoc ? args->callback(subdoc, args->data) : true; - - return next ? PL_DHASH_NEXT : PL_DHASH_STOP; -} - void nsDocument::EnumerateSubDocuments(nsSubDocEnumFunc aCallback, void *aData) { - if (mSubDocuments) { - SubDocEnumArgs args = { aCallback, aData }; - PL_DHashTableEnumerate(mSubDocuments, SubDocHashEnum, &args); - } -} - -static PLDHashOperator -CanCacheSubDocument(PLDHashTable *table, PLDHashEntryHdr *hdr, - uint32_t number, void *arg) -{ - SubDocMapEntry *entry = static_cast(hdr); - bool *canCacheArg = static_cast(arg); - - nsIDocument *subdoc = entry->mSubDocument; - - // The aIgnoreRequest we were passed is only for us, so don't pass it on. - bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false; - if (!canCache) { - *canCacheArg = false; - return PL_DHASH_STOP; + if (!mSubDocuments) { + return; } - return PL_DHASH_NEXT; + PLDHashTable::Iterator iter(mSubDocuments); + while (iter.HasMoreEntries()) { + auto entry = static_cast(iter.NextEntry()); + nsIDocument* subdoc = entry->mSubDocument; + bool next = subdoc ? aCallback(subdoc, aData) : true; + if (!next) { + break; + } + } } #ifdef DEBUG_bryner @@ -8872,11 +8823,21 @@ nsDocument::CanSavePresentation(nsIRequest *aNewRequest) return false; } - bool canCache = true; - if (mSubDocuments) - PL_DHashTableEnumerate(mSubDocuments, CanCacheSubDocument, &canCache); + if (mSubDocuments) { + PLDHashTable::Iterator iter(mSubDocuments); + while (iter.HasMoreEntries()) { + auto entry = static_cast(iter.NextEntry()); + nsIDocument* subdoc = entry->mSubDocument; - return canCache; + // The aIgnoreRequest we were passed is only for us, so don't pass it on. + bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false; + if (!canCache) { + return false; + } + } + } + + return true; } void @@ -9804,6 +9765,23 @@ nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr, } } +void +nsDocument::MaybePreconnect(nsIURI* uri) +{ + if (mPreloadedPreconnects.Contains(uri)) { + return; + } + mPreloadedPreconnects.Put(uri, true); + + nsCOMPtr + speculator(do_QueryInterface(nsContentUtils::GetIOService())); + if (!speculator) { + return; + } + + speculator->SpeculativeConnect(uri, nullptr); +} + void nsDocument::ForgetImagePreload(nsIURI* aURI) { @@ -10580,6 +10558,38 @@ nsDocument::GetPlugins(nsTArray& aPlugins) EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins); } +nsresult +nsDocument::AddResponsiveContent(nsIContent* aContent) +{ + MOZ_ASSERT(aContent); + MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::img)); + mResponsiveContent.PutEntry(aContent); + return NS_OK; +} + +void +nsDocument::RemoveResponsiveContent(nsIContent* aContent) +{ + MOZ_ASSERT(aContent); + mResponsiveContent.RemoveEntry(aContent); +} + +static PLDHashOperator +NotifyMediaFeatureEnum(nsPtrHashKey* aContent, void* userArg) +{ + nsCOMPtr content = aContent->GetKey(); + if (content->IsHTMLElement(nsGkAtoms::img)) { + static_cast(content.get())->MediaFeatureValuesChanged(); + } + return PL_DHASH_NEXT; +} + +void +nsDocument::NotifyMediaFeatureValuesChanged() +{ + mResponsiveContent.EnumerateEntries(NotifyMediaFeatureEnum, nullptr); +} + PLDHashOperator LockEnumerator(imgIRequest* aKey, uint32_t aData, void* userArg) diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index c5cb1b2a53..9d5cb36c22 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -18,7 +18,6 @@ #include "nsCRT.h" #include "nsWeakReference.h" #include "nsWeakPtr.h" -#include "nsVoidArray.h" #include "nsTArray.h" #include "nsIDOMXMLDocument.h" #include "nsIDOMDocumentXBL.h" @@ -151,8 +150,8 @@ public: /** * Returns the list of all elements associated with this id. */ - const nsSmallVoidArray* GetIdElements() const { - return &mIdContentList; + const nsTArray& GetIdElements() const { + return mIdContentList; } /** * If this entry has a non-null image element set (using SetImageElement), @@ -228,7 +227,7 @@ private: // empty if there are no elements with this ID. // The elements are stored as weak pointers. - nsSmallVoidArray mIdContentList; + nsTArray mIdContentList; nsRefPtr mNameContentList; nsAutoPtr > mChangeCallbacks; nsRefPtr mImageElement; @@ -1119,6 +1118,8 @@ public: ReferrerPolicy aReferrerPolicy) override; virtual void ForgetImagePreload(nsIURI* aURI) override; + virtual void MaybePreconnect(nsIURI* uri) override; + virtual void PreloadStyle(nsIURI* uri, const nsAString& charset, const nsAString& aCrossOriginAttr, ReferrerPolicy aReferrerPolicy) override; @@ -1139,7 +1140,7 @@ public: virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override; virtual Element *GetElementById(const nsAString& aElementId) override; - virtual const nsSmallVoidArray* GetAllElementsForId(const nsAString& aElementId) const override; + virtual const nsTArray* GetAllElementsForId(const nsAString& aElementId) const override; virtual Element *LookupImageElement(const nsAString& aElementId) override; virtual void MozSetImageElement(const nsAString& aImageElementId, @@ -1159,6 +1160,16 @@ public: // the frame and any subframes. virtual void GetPlugins(nsTArray& aPlugins) override; + // Adds an element to mResponsiveContent when the element is + // added to the tree. + virtual nsresult AddResponsiveContent(nsIContent* aContent) override; + // Removes an element from mResponsiveContent when the element is + // removed from the tree. + virtual void RemoveResponsiveContent(nsIContent* aContent) override; + // Notifies any responsive content added by AddResponsiveContent upon media + // features values changing. + virtual void NotifyMediaFeatureValuesChanged() override; + virtual nsresult GetStateObject(nsIVariant** aResult) override; virtual nsDOMNavigationTiming* GetNavigationTiming() const override; @@ -1756,6 +1767,9 @@ private: bool mStyledLinksCleared; #endif + // A set of responsive images keyed by address pointer. + nsTHashtable< nsPtrHashKey > mResponsiveContent; + // Member to store out last-selected stylesheet set. nsString mLastStyleSheetSet; @@ -1774,6 +1788,11 @@ private: // about it anymore. nsRefPtrHashtable mPreloadingImages; + // A list of preconnects initiated by the preloader. This prevents + // the same uri from being used more than once, and allows the dom + // builder to not repeat the work of the preloader. + nsDataHashtable< nsURIHashKey, bool> mPreloadedPreconnects; + // Current depth of picture elements from parser int32_t mPreloadPictureDepth; diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 2c85451574..d8bcb49baa 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -5012,6 +5012,12 @@ nsGlobalWindow::GetOuterSize(ErrorResult& aError) { MOZ_ASSERT(IsOuterWindow()); + if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) { + CSSIntSize size; + aError = GetInnerSize(size); + return nsIntSize(size.width, size.height); + } + nsCOMPtr treeOwnerAsWin = GetTreeOwnerWindow(); if (!treeOwnerAsWin) { aError.Throw(NS_ERROR_FAILURE); @@ -5176,6 +5182,11 @@ nsGlobalWindow::GetScreenXY(ErrorResult& aError) { MOZ_ASSERT(IsOuterWindow()); + // When resisting fingerprinting, always return (0,0) + if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) { + return nsIntPoint(0, 0); + } + nsCOMPtr treeOwnerAsWin = GetTreeOwnerWindow(); if (!treeOwnerAsWin) { aError.Throw(NS_ERROR_FAILURE); @@ -5249,6 +5260,11 @@ nsGlobalWindow::GetMozInnerScreenX(ErrorResult& aError) { FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenX, (aError), aError, 0); + // When resisting fingerprinting, always return 0. + if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) { + return 0.0; + } + nsRect r = GetInnerScreenRect(); return nsPresContext::AppUnitsToFloatCSSPixels(r.x); } @@ -5267,6 +5283,11 @@ nsGlobalWindow::GetMozInnerScreenY(ErrorResult& aError) { FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenY, (aError), aError, 0); + // Return 0 to prevent fingerprinting. + if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) { + return 0.0; + } + nsRect r = GetInnerScreenRect(); return nsPresContext::AppUnitsToFloatCSSPixels(r.y); } @@ -5295,6 +5316,10 @@ nsGlobalWindow::GetDevicePixelRatio(ErrorResult& aError) return 1.0; } + if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) { + return 1.0; + } + return float(nsPresContext::AppUnitsPerCSSPixel())/ presContext->AppUnitsPerDevPixel(); } diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 8976b98342..e0322cfb16 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -1051,6 +1051,8 @@ public: bool aShowDialog, mozilla::ErrorResult& aError); uint64_t GetMozPaintCount(mozilla::ErrorResult& aError); + bool ShouldResistFingerprinting(); + mozilla::dom::MozSelfSupport* GetMozSelfSupport(mozilla::ErrorResult& aError); already_AddRefed OpenDialog(JSContext* aCx, diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index 733fc17276..0dfdf9f6d5 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -80,7 +80,6 @@ class nsSMILAnimationController; class nsStyleSet; class nsTextNode; class nsWindowSizes; -class nsSmallVoidArray; class nsDOMCaretPosition; class nsViewportInfo; class nsIGlobalObject; @@ -2075,6 +2074,11 @@ public: */ virtual bool IsDocumentRightToLeft() { return false; } + /** + * Called by Parser for link rel=preconnect + */ + virtual void MaybePreconnect(nsIURI* uri) = 0; + enum DocumentTheme { Doc_Theme_Uninitialized, // not determined yet Doc_Theme_None, @@ -2122,9 +2126,8 @@ public: /** * This method returns _all_ the elements in this document which * have id aElementId, if there are any. Otherwise it returns null. - * The entries of the nsSmallVoidArray are Element* */ - virtual const nsSmallVoidArray* GetAllElementsForId(const nsAString& aElementId) const = 0; + virtual const nsTArray* GetAllElementsForId(const nsAString& aElementId) const = 0; /** * Lookup an image element using its associated ID, which is usually provided @@ -2191,6 +2194,10 @@ public: virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0; virtual void GetPlugins(nsTArray& aPlugins) = 0; + virtual nsresult AddResponsiveContent(nsIContent* aContent) = 0; + virtual void RemoveResponsiveContent(nsIContent* aContent) = 0; + virtual void NotifyMediaFeatureValuesChanged() = 0; + virtual nsresult GetStateObject(nsIVariant** aResult) = 0; virtual nsDOMNavigationTiming* GetNavigationTiming() const = 0; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index c78062ac42..a0dcbb46e6 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -2716,8 +2716,7 @@ FindMatchingElementsWithId(const nsAString& aId, nsINode* aRoot, "document if it's in the document. Note that document fragments " "can't be IsInDoc(), so should never show up here."); - const nsSmallVoidArray* elements = aRoot->OwnerDoc()->GetAllElementsForId(aId); - + const nsTArray* elements = aRoot->OwnerDoc()->GetAllElementsForId(aId); if (!elements) { // Nothing to do; we're done return; @@ -2725,8 +2724,8 @@ FindMatchingElementsWithId(const nsAString& aId, nsINode* aRoot, // XXXbz: Should we fall back to the tree walk if aRoot is not the // document and |elements| is long, for some value of "long"? - for (int32_t i = 0; i < elements->Count(); ++i) { - Element *element = static_cast(elements->ElementAt(i)); + for (size_t i = 0; i < elements->Length(); ++i) { + Element* element = (*elements)[i]; if (!aRoot->IsElement() || (element != aRoot && nsContentUtils::ContentIsDescendantOf(element, aRoot))) { diff --git a/dom/base/nsScreen.cpp b/dom/base/nsScreen.cpp index de20c20476..b2c57faf3b 100644 --- a/dom/base/nsScreen.cpp +++ b/dom/base/nsScreen.cpp @@ -68,6 +68,11 @@ NS_IMPL_RELEASE_INHERITED(nsScreen, DOMEventTargetHelper) int32_t nsScreen::GetPixelDepth(ErrorResult& aRv) { + // Return 24 to prevent fingerprinting. + if (ShouldResistFingerprinting()) { + return 24; + } + nsDeviceContext* context = GetDeviceContext(); if (!context) { @@ -111,6 +116,11 @@ nsScreen::GetDeviceContext() nsresult nsScreen::GetRect(nsRect& aRect) { + // Return window inner rect to prevent fingerprinting. + if (ShouldResistFingerprinting()) { + return GetWindowInnerRect(aRect); + } + nsDeviceContext *context = GetDeviceContext(); if (!context) { @@ -130,6 +140,11 @@ nsScreen::GetRect(nsRect& aRect) nsresult nsScreen::GetAvailRect(nsRect& aRect) { + // Return window inner rect to prevent fingerprinting. + if (ShouldResistFingerprinting()) { + return GetWindowInnerRect(aRect); + } + nsDeviceContext *context = GetDeviceContext(); if (!context) { @@ -166,22 +181,26 @@ nsScreen::Notify(const hal::ScreenConfiguration& aConfiguration) void nsScreen::GetMozOrientation(nsString& aOrientation) { - switch (mOrientation) { - case eScreenOrientation_PortraitPrimary: - aOrientation.AssignLiteral("portrait-primary"); - break; - case eScreenOrientation_PortraitSecondary: - aOrientation.AssignLiteral("portrait-secondary"); - break; - case eScreenOrientation_LandscapePrimary: + if (ShouldResistFingerprinting()) { aOrientation.AssignLiteral("landscape-primary"); - break; - case eScreenOrientation_LandscapeSecondary: - aOrientation.AssignLiteral("landscape-secondary"); - break; - case eScreenOrientation_None: - default: - MOZ_CRASH("Unacceptable mOrientation value"); + } else { + switch (mOrientation) { + case eScreenOrientation_PortraitPrimary: + aOrientation.AssignLiteral("portrait-primary"); + break; + case eScreenOrientation_PortraitSecondary: + aOrientation.AssignLiteral("portrait-secondary"); + break; + case eScreenOrientation_LandscapePrimary: + aOrientation.AssignLiteral("landscape-primary"); + break; + case eScreenOrientation_LandscapeSecondary: + aOrientation.AssignLiteral("landscape-secondary"); + break; + case eScreenOrientation_None: + default: + MOZ_CRASH("Unacceptable mOrientation value"); + } } } @@ -373,3 +392,27 @@ nsScreen::FullScreenEventListener::HandleEvent(nsIDOMEvent* aEvent) return NS_OK; } + +nsresult +nsScreen::GetWindowInnerRect(nsRect& aRect) +{ + aRect.x = 0; + aRect.y = 0; + nsCOMPtr win = GetOwner(); + if (!win) { + return NS_ERROR_FAILURE; + } + nsresult rv = win->GetInnerWidth(&aRect.width); + NS_ENSURE_SUCCESS(rv, rv); + return win->GetInnerHeight(&aRect.height); +} + +bool nsScreen::ShouldResistFingerprinting() const +{ + bool resist = false; + nsCOMPtr owner = GetOwner(); + if (owner) { + resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell()); + } + return resist; +} diff --git a/dom/base/nsScreen.h b/dom/base/nsScreen.h index a686cec7e0..d13dc6c0f3 100644 --- a/dom/base/nsScreen.h +++ b/dom/base/nsScreen.h @@ -131,6 +131,7 @@ protected: nsDeviceContext* GetDeviceContext(); nsresult GetRect(nsRect& aRect); nsresult GetAvailRect(nsRect& aRect); + nsresult GetWindowInnerRect(nsRect& aRect); mozilla::dom::ScreenOrientation mOrientation; @@ -158,6 +159,8 @@ private: bool IsDeviceSizePageSize(); + bool ShouldResistFingerprinting() const; + nsRefPtr mEventListener; }; diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js new file mode 100644 index 0000000000..8ba0626d1a --- /dev/null +++ b/dom/base/test/chrome/bug418986-1.js @@ -0,0 +1,71 @@ +// The main test function. +let test = function (isContent) { + SimpleTest.waitForExplicitFinish(); + + let { ww } = SpecialPowers.Services; + window.chromeWindow = ww.activeWindow; + + // The pairs of values expected to be the same when + // fingerprinting resistance is enabled. + let pairs = [ + ["screenX", 0], + ["screenY", 0], + ["mozInnerScreenX", 0], + ["mozInnerScreenY", 0], + ["screen.pixelDepth", 24], + ["screen.colorDepth", 24], + ["screen.availWidth", "innerWidth"], + ["screen.availHeight", "innerHeight"], + ["screen.left", 0], + ["screen.top", 0], + ["screen.availLeft", 0], + ["screen.availTop", 0], + ["screen.width", "innerWidth"], + ["screen.height", "innerHeight"], + ["screen.mozOrientation", "'landscape-primary'"], + ["devicePixelRatio", 1] + ]; + + // checkPair: tests if members of pair [a, b] are equal when evaluated. + let checkPair = function (a, b) { + is(eval(a), eval(b), a + " should be equal to " + b); + }; + + // Returns generator object that iterates through pref values. + let prefVals = (for (prefVal of [false, true]) prefVal); + + // The main test function, runs until all pref values are exhausted. + let nextTest = function () { + let {value : prefValue, done} = prefVals.next(); + if (done) { + SimpleTest.finish(); + return; + } + SpecialPowers.pushPrefEnv({set : [["privacy.resistFingerprinting", prefValue]]}, + function () { + // We will be resisting fingerprinting if the pref is enabled, + // and we are in a content script (not chrome). + let resisting = prefValue && isContent; + // Check each of the pairs. + pairs.map(function ([item, onVal]) { + if (resisting) { + checkPair("window." + item, onVal); + } else { + if (!item.startsWith("moz")) { + checkPair("window." + item, "chromeWindow." + item); + } + } + }); + if (!resisting) { + // Hard to predict these values, but we can enforce constraints: + ok(window.mozInnerScreenX >= chromeWindow.mozInnerScreenX, + "mozInnerScreenX"); + ok(window.mozInnerScreenY >= chromeWindow.mozInnerScreenY, + "mozInnerScreenY"); + } + nextTest(); + }); + } + + nextTest(); +} diff --git a/dom/base/test/chrome/chrome.ini b/dom/base/test/chrome/chrome.ini index 8b13589ba5..410a2610db 100644 --- a/dom/base/test/chrome/chrome.ini +++ b/dom/base/test/chrome/chrome.ini @@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g' support-files = blockNoPlugins.xml blockPluginHard.xml + bug418986-1.js cpows_child.js cpows_parent.xul file_bug391728.html @@ -29,6 +30,7 @@ support-files = [test_bug380418.html^headers^] [test_bug383430.html] [test_bug391728.html] +[test_bug418986-1.xul] [test_bug421622.xul] [test_bug429785.xul] [test_bug430050.xul] diff --git a/dom/base/test/chrome/test_bug418986-1.xul b/dom/base/test/chrome/test_bug418986-1.xul new file mode 100644 index 0000000000..aa0c340771 --- /dev/null +++ b/dom/base/test/chrome/test_bug418986-1.xul @@ -0,0 +1,26 @@ + + + + + + + + + + Mozilla Bug 418986 (Part 1) + + + + + + diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 96f1e8adf0..cd356d61cf 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -56,6 +56,7 @@ support-files = bug704320.sjs bug704320_counter.sjs bug819051.sjs + chrome/bug418986-1.js copypaste.js delayedServerEvents.sjs echo.sjs @@ -446,6 +447,7 @@ support-files = test_bug402150.html^headers^ [test_bug417255.html] [test_bug417384.html] [test_bug418214.html] +[test_bug418986-1.html] [test_bug419132.html] [test_bug419527.xhtml] [test_bug420609.xhtml] diff --git a/dom/base/test/test_bug418986-1.html b/dom/base/test/test_bug418986-1.html new file mode 100644 index 0000000000..3ffa19fa98 --- /dev/null +++ b/dom/base/test/test_bug418986-1.html @@ -0,0 +1,24 @@ + + + + + + Test 1/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info + + + + + + Bug 418986 +

+ +

+  
+
+
diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp
index 31e07dc291..b285583670 100644
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -883,6 +883,13 @@ Event::GetScreenCoords(nsPresContext* aPresContext,
                        WidgetEvent* aEvent,
                        LayoutDeviceIntPoint aPoint)
 {
+  if (!nsContentUtils::IsCallerChrome() &&
+      nsContentUtils::ResistFingerprinting()) {
+    // When resisting fingerprinting, return client coordinates instead.
+    CSSIntPoint clientCoords = GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0));
+    return LayoutDeviceIntPoint(clientCoords.x, clientCoords.y);
+  }
+
   if (EventStateManager::sIsPointerLocked) {
     return EventStateManager::sLastScreenPoint;
   }
diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp
index 194ce410e7..49a07e976e 100644
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -380,6 +380,10 @@ EventListenerManager::AddEventListenerInternal(
   if (aTypeAtom && mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
   }
+
+  if (mIsMainThreadELM && mTarget) {
+    EventListenerService::NotifyAboutMainThreadListenerChange(mTarget);
+  }
 }
 
 bool
@@ -497,6 +501,9 @@ EventListenerManager::RemoveEventListenerInternal(
         if (mTarget && aUserType) {
           mTarget->EventListenerRemoved(aUserType);
         }
+        if (mIsMainThreadELM && mTarget) {
+          EventListenerService::NotifyAboutMainThreadListenerChange(mTarget);
+        }
 
         if (!deviceType
 #ifdef MOZ_B2G
@@ -630,6 +637,9 @@ EventListenerManager::SetEventHandlerInternal(
       mTarget->EventListenerRemoved(aName);
       mTarget->EventListenerAdded(aName);
     }
+    if (mIsMainThreadELM && mTarget) {
+      EventListenerService::NotifyAboutMainThreadListenerChange(mTarget);
+    }
   }
 
   // Set flag to indicate possible need for compilation later
@@ -757,6 +767,9 @@ EventListenerManager::RemoveEventHandler(nsIAtom* aName,
     if (mTarget && aName) {
       mTarget->EventListenerRemoved(aName);
     }
+    if (mIsMainThreadELM && mTarget) {
+      EventListenerService::NotifyAboutMainThreadListenerChange(mTarget);
+    }
   }
 }
 
diff --git a/dom/events/EventListenerService.cpp b/dom/events/EventListenerService.cpp
index fc6ebe0e4d..7d2f437131 100644
--- a/dom/events/EventListenerService.cpp
+++ b/dom/events/EventListenerService.cpp
@@ -16,6 +16,8 @@
 #include "nsJSUtils.h"
 #include "nsMemory.h"
 #include "nsServiceManagerUtils.h"
+#include "nsArray.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 
@@ -128,6 +130,21 @@ EventListenerInfo::ToSource(nsAString& aResult)
   return NS_OK;
 }
 
+EventListenerService*
+EventListenerService::sInstance = nullptr;
+
+EventListenerService::EventListenerService()
+{
+  MOZ_ASSERT(!sInstance);
+  sInstance = this;
+}
+
+EventListenerService::~EventListenerService()
+{
+  MOZ_ASSERT(sInstance == this);
+  sInstance = nullptr;
+}
+
 NS_IMETHODIMP
 EventListenerService::GetListenerInfoFor(nsIDOMEventTarget* aEventTarget,
                                          uint32_t* aCount,
@@ -289,6 +306,58 @@ EventListenerService::RemoveListenerForAllEvents(nsIDOMEventTarget* aTarget,
   return NS_OK;
 }
 
+NS_IMETHODIMP
+EventListenerService::AddListenerChangeListener(nsIListenerChangeListener* aListener)
+{
+  if (!mChangeListeners.Contains(aListener)) {
+    mChangeListeners.AppendElement(aListener);
+  }
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+EventListenerService::RemoveListenerChangeListener(nsIListenerChangeListener* aListener)
+{
+  mChangeListeners.RemoveElement(aListener);
+  return NS_OK;
+};
+
+void
+EventListenerService::NotifyAboutMainThreadListenerChangeInternal(dom::EventTarget* aTarget)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mChangeListeners.IsEmpty()) {
+    return;
+  }
+
+  if (!mPendingListenerChanges) {
+    mPendingListenerChanges = nsArrayBase::Create();
+    nsCOMPtr runnable = NS_NewRunnableMethod(this,
+      &EventListenerService::NotifyPendingChanges);
+    NS_DispatchToCurrentThread(runnable);
+  }
+
+  if (!mPendingListenerChangesSet.Get(aTarget)) {
+    mPendingListenerChanges->AppendElement(aTarget, false);
+    mPendingListenerChangesSet.Put(aTarget, true);
+  }
+}
+
+void
+EventListenerService::NotifyPendingChanges()
+{
+  nsCOMPtr changes;
+  mPendingListenerChanges.swap(changes);
+  mPendingListenerChangesSet.Clear();
+
+  nsTObserverArray>::EndLimitedIterator
+    iter(mChangeListeners);
+  while (iter.HasMore()) {
+    nsCOMPtr listener = iter.GetNext();
+    listener->ListenersChanged(changes);
+  }
+}
+
 } // namespace mozilla
 
 nsresult
diff --git a/dom/events/EventListenerService.h b/dom/events/EventListenerService.h
index 8d9a46090c..4fb3684db6 100644
--- a/dom/events/EventListenerService.h
+++ b/dom/events/EventListenerService.h
@@ -14,8 +14,15 @@
 #include "nsIDOMEventListener.h"
 #include "nsIEventListenerService.h"
 #include "nsString.h"
+#include "nsTObserverArray.h"
+#include "nsDataHashtable.h"
+
+class nsIMutableArray;
 
 namespace mozilla {
+namespace dom {
+class EventTarget;
+};
 
 template
 class Maybe;
@@ -57,10 +64,27 @@ protected:
 
 class EventListenerService final : public nsIEventListenerService
 {
-  ~EventListenerService() {}
+  ~EventListenerService();
 public:
+  EventListenerService();
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEVENTLISTENERSERVICE
+
+  static void NotifyAboutMainThreadListenerChange(dom::EventTarget* aTarget)
+  {
+    if (sInstance) {
+      sInstance->NotifyAboutMainThreadListenerChangeInternal(aTarget);
+    }
+  }
+
+  void NotifyPendingChanges();
+private:
+  void NotifyAboutMainThreadListenerChangeInternal(dom::EventTarget* aTarget);
+  nsTObserverArray> mChangeListeners;
+  nsCOMPtr mPendingListenerChanges;
+  nsDataHashtable mPendingListenerChangesSet;
+
+  static EventListenerService* sInstance;
 };
 
 } // namespace mozilla
diff --git a/dom/events/nsIEventListenerService.idl b/dom/events/nsIEventListenerService.idl
index ab85b98472..250f208bfe 100644
--- a/dom/events/nsIEventListenerService.idl
+++ b/dom/events/nsIEventListenerService.idl
@@ -7,6 +7,13 @@
 
 interface nsIDOMEventListener;
 interface nsIDOMEventTarget;
+interface nsIArray;
+
+[scriptable, function, uuid(8d5b5a6b-dec0-473d-86c4-591801dfaac1)]
+interface nsIListenerChangeListener : nsISupports
+{
+  void listenersChanged(in nsIArray aEventTargets);
+};
 
 /**
  * An instance of this interface describes how an event listener
@@ -39,7 +46,7 @@ interface nsIEventListenerInfo : nsISupports
   AString toSource();
 };
 
-[scriptable, uuid(f6964bfb-dabe-4cab-9733-be0ee2bf8171)]
+[scriptable, uuid(77aab5f7-213d-4db4-9f22-e46dfb774f15)]
 interface nsIEventListenerService : nsISupports
 {
   /**
@@ -96,5 +103,8 @@ interface nsIEventListenerService : nsISupports
                                   in nsIDOMEventListener listener,
                                   [optional] in boolean aUseCapture,
                                   [optional] in boolean aSystemEventGroup);
+
+  void addListenerChangeListener(in nsIListenerChangeListener aListener);
+  void removeListenerChangeListener(in nsIListenerChangeListener aListener);
 };
 
diff --git a/dom/events/test/bug418986-3.js b/dom/events/test/bug418986-3.js
new file mode 100644
index 0000000000..317b5c7ad1
--- /dev/null
+++ b/dom/events/test/bug418986-3.js
@@ -0,0 +1,69 @@
+SimpleTest.waitForExplicitFinish();
+
+// The main testing function.
+let test = function (isContent) {
+  // Each definition is [eventType, prefSetting]
+  // Where we are setting the "privacy.resistFingerprinting" pref.
+  let eventDefs = [["mousedown", true],
+                   ["mouseup", true],
+                   ["mousedown", false],
+                   ["mouseup", false]];
+
+  let testCounter = 0;
+
+  // Declare ahead of time.
+  let setup;
+
+  // This function is called when the event handler fires.
+  let handleEvent = function (event, prefVal) {
+    let resisting = prefVal && isContent;
+    if (resisting) {
+      is(event.screenX, event.clientX, "event.screenX and event.clientX should be the same");
+      is(event.screenY, event.clientY, "event.screenY and event.clientY should be the same");
+    } else {
+      // We can't be sure about X coordinates not being equal, but we can test Y.
+      isnot(event.screenY, event.clientY, "event.screenY !== event.clientY");
+    }
+    ++testCounter;
+    if (testCounter < eventDefs.length) {
+      nextTest();
+    } else {
+      SimpleTest.finish();
+    }
+  };
+
+  // In this function, we set up the nth div and event handler,
+  // and then synthesize a mouse event in the div, to test
+  // whether the resulting events resist fingerprinting by
+  // suppressing absolute screen coordinates.
+  nextTest = function () {
+    let [eventType, prefVal] = eventDefs[testCounter];
+    SpecialPowers.pushPrefEnv({set:[["privacy.resistFingerprinting", prefVal]]},
+      function () {
+        // The following code creates a new div for each event in eventDefs,
+        // attaches a listener to listen for the event, and then generates
+        // a fake event at the center of the div.
+        let div = document.createElement("div");
+        div.style.width = "10px";
+        div.style.height = "10px";
+        div.style.backgroundColor = "red";
+        // Name the div after the event we're listening for.
+        div.id = eventType;
+        document.getElementById("body").appendChild(div);
+        // Seems we can't add an event listener in chrome unless we run
+        // it in a later task.
+        window.setTimeout(function() {
+          div.addEventListener(eventType, event => handleEvent(event, prefVal), false);
+          // For some reason, the following synthesizeMouseAtCenter call only seems to run if we
+          // wrap it in a window.setTimeout(..., 0).
+          window.setTimeout(function () {
+            synthesizeMouseAtCenter(div, {type : eventType});
+          }, 0);
+        }, 0);
+      });
+  };
+
+  // Now run by starting with the 0th event.
+  nextTest();
+
+};
diff --git a/dom/events/test/chrome.ini b/dom/events/test/chrome.ini
index e29a393428..a42b9e612d 100644
--- a/dom/events/test/chrome.ini
+++ b/dom/events/test/chrome.ini
@@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g'
 support-files =
   bug415498-doc1.html
   bug415498-doc2.html
+  bug418986-3.js
   bug591249_iframe.xul
   bug602962.xul
   file_bug679494.html
@@ -12,6 +13,8 @@ support-files =
 [test_bug336682_2.xul]
 [test_bug368835.html]
 [test_bug415498.xul]
+[test_bug418986-3.xul]
+[test_bug524674.xul]
 [test_bug586961.xul]
 [test_bug591249.xul]
 [test_bug602962.xul]
diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini
index dfbb9834ea..c4738d144e 100644
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -7,6 +7,7 @@ support-files =
   bug426082.html
   bug648573.html
   bug656379-1.html
+  bug418986-3.js
   error_event_worker.js
   empty.js
   window_bug493251.html
@@ -38,6 +39,9 @@ support-files = test_bug336682.js
 [test_bug409604.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_bug412567.html]
+[test_bug418986-3.html]
+# Sometimes fails to finish after tests pass on 'B2G ICS Emulator'.
+skip-if = (os == 'b2g')
 [test_bug422132.html]
 skip-if = buildapp == 'b2g' || e10s # b2g(2 failures out of 8, mousewheel test) b2g-debug(2 failures out of 8, mousewheel test) b2g-desktop(2 failures out of 8, mousewheel test)
 [test_bug426082.html]
diff --git a/dom/events/test/test_bug418986-3.html b/dom/events/test/test_bug418986-3.html
new file mode 100644
index 0000000000..a92b1e0f53
--- /dev/null
+++ b/dom/events/test/test_bug418986-3.html
@@ -0,0 +1,25 @@
+
+
+
+
+  
+  Test 3/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info
+  
+  
+  
+
+
+Bug 418986
+

+

+
+
+
+
diff --git a/dom/events/test/test_bug418986-3.xul b/dom/events/test/test_bug418986-3.xul
new file mode 100644
index 0000000000..574cda0cee
--- /dev/null
+++ b/dom/events/test/test_bug418986-3.xul
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+        
+  
+
+
+
+Mozilla Bug 418986
+
+
+
+  
+
+
diff --git a/dom/events/test/test_bug524674.xul b/dom/events/test/test_bug524674.xul
new file mode 100644
index 0000000000..80ed35fb9d
--- /dev/null
+++ b/dom/events/test/test_bug524674.xul
@@ -0,0 +1,106 @@
+
+
+
+
+
+  
+
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp
index 5a2c4994d2..a46c0ee99f 100644
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -17,8 +17,6 @@
 #include "nsIURL.h"
 #include "nsIIOService.h"
 #include "nsIServiceManager.h"
-#include "nsIAppShell.h"
-#include "nsWidgetsCID.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsContainerFrame.h"
@@ -53,8 +51,6 @@
 #include "mozilla/Preferences.h"
 static const char *kPrefSrcsetEnabled = "dom.image.srcset.enabled";
 
-static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-
 NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
 
 #ifdef DEBUG
@@ -591,6 +587,9 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
   bool addedToPicture = aParent && aParent->IsHTMLElement(nsGkAtoms::picture) &&
                         HTMLPictureElement::IsPictureEnabled();
   if (addedToPicture) {
+    if (aDocument) {
+      aDocument->AddResponsiveContent(this);
+    }
     QueueImageLoadTask();
   } else if (!InResponsiveMode() &&
              HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
@@ -632,12 +631,18 @@ HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
     }
   }
 
-  if (aNullParent && GetParent() &&
+  if (GetParent() &&
       GetParent()->IsHTMLElement(nsGkAtoms::picture) &&
       HTMLPictureElement::IsPictureEnabled()) {
+    nsIDocument* doc = GetOurOwnerDoc();
+    if (doc) {
+      doc->RemoveResponsiveContent(this);
+    }
     // Being removed from picture re-triggers selection, even if we
     // weren't using a  peer
-    QueueImageLoadTask();
+    if (aNullParent) {
+      QueueImageLoadTask();
+    }
   }
 
   nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
@@ -888,15 +893,11 @@ HTMLImageElement::QueueImageLoadTask()
     return;
   }
 
-  // The task checks this to determine if it was the last queued event, so this
-  // implicitly cancels earlier tasks
-  mPendingImageLoadTask = new ImageLoadTask(this);
-  nsCOMPtr appShell = do_GetService(kAppShellCID);
-  if (appShell) {
-    appShell->RunInStableState(mPendingImageLoadTask);
-  } else {
-    MOZ_ASSERT(false, "expect appshell for HTMLImageElement");
-  }
+  nsCOMPtr task = new ImageLoadTask(this);
+  // The task checks this to determine if it was the last
+  // queued event, and so earlier tasks are implicitly canceled.
+  mPendingImageLoadTask = task;
+  nsContentUtils::RunInStableState(task.forget());
 }
 
 bool
@@ -1082,7 +1083,18 @@ HTMLImageElement::UpdateResponsiveSource()
       // that and keep it if it's still usable.
       mResponsiveSelector->SelectImage(true);
       if (mResponsiveSelector->NumCandidates()) {
-        break;
+        bool isUsableCandidate = true;
+
+        // an otherwise-usable source element may still have a media query that may not
+        // match any more.
+        if (candidateSource->IsHTMLElement(nsGkAtoms::source) &&
+            !SourceElementMatches(candidateSource->AsContent())) {
+          isUsableCandidate = false;
+        }
+
+        if (isUsableCandidate) {
+          break;
+        }
       }
 
       // no longer valid
@@ -1120,6 +1132,31 @@ HTMLImageElement::SupportedPictureSourceType(const nsAString& aType)
                                         AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
 }
 
+bool
+HTMLImageElement::SourceElementMatches(nsIContent* aSourceNode)
+{
+  MOZ_ASSERT(aSourceNode->IsHTMLElement(nsGkAtoms::source));
+
+  DebugOnly parent(nsINode::GetParentElement());
+  MOZ_ASSERT(parent && parent->IsHTMLElement(nsGkAtoms::picture));
+  MOZ_ASSERT(IsPreviousSibling(aSourceNode, this));
+  MOZ_ASSERT(HTMLPictureElement::IsPictureEnabled());
+
+  // Check media and type
+  HTMLSourceElement *src = static_cast(aSourceNode);
+  if (!src->MatchesCurrentMedia()) {
+    return false;
+  }
+
+  nsAutoString type;
+  if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
+      !SupportedPictureSourceType(type)) {
+    return false;
+  }
+
+  return true;
+}
+
 bool
 HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode,
                                               const nsAString *aSrcset,
@@ -1133,20 +1170,7 @@ HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode,
   // Skip if this is not a  with matching media query
   bool isSourceTag = aSourceNode->IsHTMLElement(nsGkAtoms::source);
   if (isSourceTag) {
-    DebugOnly parent(nsINode::GetParentElement());
-    MOZ_ASSERT(parent && parent->IsHTMLElement(nsGkAtoms::picture));
-    MOZ_ASSERT(IsPreviousSibling(aSourceNode, this));
-    MOZ_ASSERT(pictureEnabled);
-
-    // Check media and type
-    HTMLSourceElement *src = static_cast(aSourceNode);
-    if (!src->MatchesCurrentMedia()) {
-      return false;
-    }
-
-    nsAutoString type;
-    if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
-        !SupportedPictureSourceType(type)) {
+    if (!SourceElementMatches(aSourceNode)) {
       return false;
     }
   } else if (aSourceNode->IsHTMLElement(nsGkAtoms::img)) {
@@ -1267,6 +1291,12 @@ HTMLImageElement::DestroyContent()
   mResponsiveSelector = nullptr;
 }
 
+void
+HTMLImageElement::MediaFeatureValuesChanged()
+{
+  QueueImageLoadTask();
+}
+
 } // namespace dom
 } // namespace mozilla
 
diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h
index 625926b7e9..9a5a3ab632 100644
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -206,6 +206,8 @@ public:
 
   virtual void DestroyContent() override;
 
+  void MediaFeatureValuesChanged();
+
   /**
    * Given a hypothetical  or  tag with the given parameters,
    * return what URI we would attempt to use, if any.  Used by the preloader to
@@ -335,6 +337,8 @@ protected:
   nsRefPtr mResponsiveSelector;
 
 private:
+  bool SourceElementMatches(nsIContent* aSourceNode);
+
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     nsRuleData* aData);
 
diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp
index f80ab6c81a..2b2422227d 100644
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -21,7 +21,6 @@
 #include "nsIDOMEvent.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsINode.h"
-#include "nsISpeculativeConnect.h"
 #include "nsIStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
@@ -182,6 +181,8 @@ HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
+  // If this is reinserted back into the document it will not be
+  // from the parser.
   nsCOMPtr oldDoc = GetUncomposedDoc();
 
   // Check for a ShadowRoot because link elements are inert in a
@@ -311,12 +312,11 @@ HTMLLinkElement::UpdatePreconnect()
     return;
   }
 
-  nsCOMPtr
-    speculator(do_QueryInterface(nsContentUtils::GetIOService()));
-  if (speculator) {
+  nsIDocument *owner = OwnerDoc();
+  if (owner) {
     nsCOMPtr uri = GetHrefURI();
     if (uri) {
-      speculator->SpeculativeConnect(uri, nullptr);
+      owner->MaybePreconnect(uri);
     }
   }
 }
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
index 29720eb435..15e2636185 100644
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -60,8 +60,6 @@
 #include "Layers.h"
 #include 
 #include "nsIAsyncVerifyRedirectCallback.h"
-#include "nsIAppShell.h"
-#include "nsWidgetsCID.h"
 #include "nsMediaFragmentURIParser.h"
 #include "nsURIHashKey.h"
 #include "nsJSUtils.h"
@@ -741,13 +739,10 @@ public:
   }
 };
 
-static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-
 void HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable)
 {
   nsCOMPtr event = new nsSyncSection(this, aRunnable);
-  nsCOMPtr appShell = do_GetService(kAppShellCID);
-  appShell->RunInStableState(event);
+  nsContentUtils::RunInStableState(event.forget());
 }
 
 void HTMLMediaElement::QueueLoadFromSourceTask()
diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini
index 28d4fb6b04..a4963aad97 100644
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -591,5 +591,6 @@ support-files = file_bug871161-1.html file_bug871161-2.html
 skip-if = buildapp == 'b2g' # bug 1129014
 [test_img_complete.html]
 [test_extapp.html]
+[test_viewport_resize.html]
 [test_image_clone_load.html]
 [test_bug1203668.html]
diff --git a/dom/html/test/test_viewport_resize.html b/dom/html/test/test_viewport_resize.html
new file mode 100644
index 0000000000..711d2afa03
--- /dev/null
+++ b/dom/html/test/test_viewport_resize.html
@@ -0,0 +1,44 @@
+
+
+
+
+  Test for Bug 1135812
+          
+  
+
+
+Mozilla Bug 1135812
+

+ +
+
+
+
+
+ + diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 7ccfa158d7..e95649f23c 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -1697,9 +1697,11 @@ interface nsIDOMWindowUtils : nsISupports { attribute boolean paintFlashing; /** - * Allows running of a "synchronous section", in the form of an nsIRunnable - * once the event loop has reached a "stable state". We've reached a stable - * state when the currently executing task/event has finished, see: + * Add a "synchronous section", in the form of an nsIRunnable run once the + * event loop has reached a "stable state". |runnable| must not cause any + * queued events to be processed (i.e. must not spin the event loop). + * We've reached a stable state when the currently executing task/event has + * finished, see: * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section * In practice this runs aRunnable once the currently executing event * finishes. If called multiple times per task/event, all the runnables will diff --git a/dom/media/AbstractThread.cpp b/dom/media/AbstractThread.cpp index e6ff97b80b..fc52bf8ffd 100644 --- a/dom/media/AbstractThread.cpp +++ b/dom/media/AbstractThread.cpp @@ -10,8 +10,7 @@ #include "nsThreadUtils.h" #include "TaskDispatcher.h" -#include "nsIAppShell.h" -#include "nsWidgetsCID.h" +#include "nsContentUtils.h" #include "nsServiceManagerUtils.h" #include "mozilla/ClearOnShutdown.h" @@ -24,8 +23,6 @@ namespace mozilla { StaticRefPtr sMainThread; ThreadLocal AbstractThread::sCurrentThreadTLS; -static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); - class XPCOMThreadWrapper : public AbstractThread { public: @@ -87,8 +84,7 @@ public: mTailDispatcher.emplace(/* aIsTailDispatcher = */ true); nsCOMPtr event = NS_NewRunnableMethod(this, &XPCOMThreadWrapper::FireTailDispatcher); - nsCOMPtr appShell = do_GetService(kAppShellCID); - appShell->RunInStableState(event); + nsContentUtils::RunInStableState(event.forget()); } return mTailDispatcher.ref(); diff --git a/dom/media/CanvasCaptureMediaStream.cpp b/dom/media/CanvasCaptureMediaStream.cpp index 1d7f2e52cd..d8f06b790e 100644 --- a/dom/media/CanvasCaptureMediaStream.cpp +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -11,10 +11,7 @@ #include "mozilla/Mutex.h" #include "mozilla/dom/CanvasCaptureMediaStreamBinding.h" #include "mozilla/dom/HTMLCanvasElement.h" -#include "nsIAppShell.h" -#include "nsWidgetsCID.h" - -static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); +#include "nsContentUtils.h" using namespace mozilla::layers; using namespace mozilla::gfx; @@ -113,8 +110,7 @@ OutputStreamDriver::Start() // Run StartInternal() in stable state to allow it to directly capture a frame nsCOMPtr runnable = NS_NewRunnableMethod(this, &OutputStreamDriver::StartInternal); - nsCOMPtr appShell = do_GetService(kAppShellCID); - appShell->RunInStableState(runnable); + nsContentUtils::RunInStableState(runnable.forget()); mStarted = true; return NS_OK; diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 2f48144a9d..99419778d0 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -10,11 +10,9 @@ #include "AudioSegment.h" #include "VideoSegment.h" #include "nsContentUtils.h" -#include "nsIAppShell.h" #include "nsIObserver.h" #include "nsPrintfCString.h" #include "nsServiceManagerUtils.h" -#include "nsWidgetsCID.h" #include "prerror.h" #include "mozilla/Logging.h" #include "mozilla/Attributes.h" @@ -1853,8 +1851,6 @@ MediaStreamGraphImpl::RunInStableState(bool aSourceIsMSG) } -static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); - void MediaStreamGraphImpl::EnsureRunInStableState() { @@ -1864,12 +1860,7 @@ MediaStreamGraphImpl::EnsureRunInStableState() return; mPostedRunInStableState = true; nsCOMPtr event = new MediaStreamGraphStableStateRunnable(this, false); - nsCOMPtr appShell = do_GetService(kAppShellCID); - if (appShell) { - appShell->RunInStableState(event); - } else { - NS_ERROR("Appshell already destroyed?"); - } + nsContentUtils::RunInStableState(event.forget()); } void diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp index b4a771102a..b60ecdcf3a 100644 --- a/dom/media/webaudio/AudioDestinationNode.cpp +++ b/dom/media/webaudio/AudioDestinationNode.cpp @@ -16,13 +16,12 @@ #include "AudioNodeStream.h" #include "MediaStreamGraph.h" #include "OfflineAudioCompletionEvent.h" +#include "nsContentUtils.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIDocShell.h" #include "nsIPermissionManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsServiceManagerUtils.h" -#include "nsIAppShell.h" -#include "nsWidgetsCID.h" #include "mozilla/dom/Promise.h" namespace mozilla { @@ -686,17 +685,16 @@ AudioDestinationNode::NotifyStableState() mExtraCurrentTimeUpdatedSinceLastStableState = false; } -static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); - void AudioDestinationNode::ScheduleStableStateNotification() { - nsCOMPtr appShell = do_GetService(kAppShellCID); - if (appShell) { - nsCOMPtr event = - NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState); - appShell->RunInStableState(event); - } + nsCOMPtr event = + NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState); + // Dispatch will fail if this is called on AudioNode destruction during + // shutdown, in which case failure can be ignored. + nsContentUtils::RunInStableState(event.forget(), + nsContentUtils:: + DispatchFailureHandling::IgnoreFailure); } double diff --git a/dom/tests/mochitest/general/test_picture_mutations.html b/dom/tests/mochitest/general/test_picture_mutations.html index 7dee62456b..bdfd9f7db5 100644 --- a/dom/tests/mochitest/general/test_picture_mutations.html +++ b/dom/tests/mochitest/general/test_picture_mutations.html @@ -163,25 +163,27 @@ expectEvents(1, 0, function() { is(img.currentSrc, testPNG200, "Should have selected testPNG200"); - // Switch DPI to match the first source, then add a source - // *also* wanting that DPI *just before* the selected - // source. Properly re-running the algorithm should - // re-consider all sources and thus go back to the first - // source, not just the valid source just inserted before us. - SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }, - function() { - // When we add dynamic reaction to MQs, this test will need to be updated. - is(img.currentSrc, testPNG200, "Should still have testPNG200"); + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should have switched to testPNG50"); +Math.sin(90); + + // Now add a source *also* wanting that DPI *just before* the + // selected source. Properly re-running the algorithm should + // re-consider all sources and thus go back to the first + // source, not just the valid source just inserted before us. source3.media = source1.media; source3.srcset = testPNG100; picture.insertBefore(source3, source2); // This should trigger a reload, but we should re-consider - // source1 and select that, not just the newly added source2 + // source1 and remain with that, not just the newly added source2 expectEvents(1, 0, function() { - is(img.currentSrc, testPNG50, "Should have selected testPNG50"); + is(img.currentSrc, testPNG50, "Should have remained on testPNG50"); expectEvents(0, 0, nextTest); }); }); + + // Switch DPI to match the first source. + SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }); }); }); @@ -283,6 +285,10 @@ (tests.shift())(); }, 0); } else { + // We'll get a flood of load events due to prefs being popped while cleaning up. + // Ignore it all. + img.removeEventListener("load", onImgLoad); + img.removeEventListener("error", onImgError); SimpleTest.finish(); } } diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp index e34fff7d46..8dd90c4f49 100644 --- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -135,35 +135,37 @@ PRLogModuleInfo* XULDocument::gXULLog; //---------------------------------------------------------------------- -struct BroadcasterMapEntry : public PLDHashEntryHdr { - Element* mBroadcaster; // [WEAK] - nsSmallVoidArray mListeners; // [OWNING] of BroadcastListener objects -}; - struct BroadcastListener { nsWeakPtr mListener; nsCOMPtr mAttribute; }; +struct BroadcasterMapEntry : public PLDHashEntryHdr +{ + Element* mBroadcaster; // [WEAK] + nsTArray mListeners; // [OWNING] of BroadcastListener objects +}; + Element* nsRefMapEntry::GetFirstElement() { - return static_cast(mRefContentList.SafeElementAt(0)); + return mRefContentList.SafeElementAt(0); } void nsRefMapEntry::AppendAll(nsCOMArray* aElements) { - for (int32_t i = 0; i < mRefContentList.Count(); ++i) { - aElements->AppendObject(static_cast(mRefContentList[i])); + for (size_t i = 0; i < mRefContentList.Length(); ++i) { + aElements->AppendObject(mRefContentList[i]); } } bool nsRefMapEntry::AddElement(Element* aElement) { - if (mRefContentList.IndexOf(aElement) >= 0) + if (mRefContentList.Contains(aElement)) { return true; + } return mRefContentList.AppendElement(aElement); } @@ -171,7 +173,7 @@ bool nsRefMapEntry::RemoveElement(Element* aElement) { mRefContentList.RemoveElement(aElement); - return mRefContentList.Count() == 0; + return mRefContentList.IsEmpty(); } //---------------------------------------------------------------------- @@ -612,13 +614,14 @@ ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) { BroadcasterMapEntry* entry = static_cast(aEntry); - for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) { - delete (BroadcastListener*)entry->mListeners[i]; + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + delete entry->mListeners[i]; } + entry->mListeners.Clear(); // N.B. that we need to manually run the dtor because we - // constructed the nsSmallVoidArray object in-place. - entry->mListeners.~nsSmallVoidArray(); + // constructed the nsTArray object in-place. + entry->mListeners.~nsTArray(); } static bool @@ -780,26 +783,22 @@ XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener, entry->mBroadcaster = &aBroadcaster; - // N.B. placement new to construct the nsSmallVoidArray object - // in-place - new (&entry->mListeners) nsSmallVoidArray(); + // N.B. placement new to construct the nsTArray object in-place + new (&entry->mListeners) nsTArray(); } // Only add the listener if it's not there already! nsCOMPtr attr = do_GetAtom(aAttr); - BroadcastListener* bl; - for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) { - bl = static_cast(entry->mListeners[i]); - + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; nsCOMPtr blListener = do_QueryReferent(bl->mListener); if (blListener == &aListener && bl->mAttribute == attr) return; } - bl = new BroadcastListener; - + BroadcastListener* bl = new BroadcastListener; bl->mListener = do_GetWeakReference(&aListener); bl->mAttribute = attr; @@ -836,17 +835,15 @@ XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster, if (entry) { nsCOMPtr attr = do_GetAtom(aAttr); - for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) { - BroadcastListener* bl = - static_cast(entry->mListeners[i]); - + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; nsCOMPtr blListener = do_QueryReferent(bl->mListener); if (blListener == &aListener && bl->mAttribute == attr) { entry->mListeners.RemoveElementAt(i); delete bl; - if (entry->mListeners.Count() == 0) + if (entry->mListeners.IsEmpty()) PL_DHashTableRemove(mBroadcasterMap, &aBroadcaster); break; @@ -961,11 +958,8 @@ XULDocument::AttributeChanged(nsIDocument* aDocument, nsAutoString value; bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value); - int32_t i; - for (i = entry->mListeners.Count() - 1; i >= 0; --i) { - BroadcastListener* bl = - static_cast(entry->mListeners[i]); - + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; if ((bl->mAttribute == aAttribute) || (bl->mAttribute == nsGkAtoms::_asterisk)) { nsCOMPtr listenerEl @@ -4139,10 +4133,8 @@ XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode, return rv; // We've got listeners: push the value. - int32_t i; - for (i = entry->mListeners.Count() - 1; i >= 0; --i) { - BroadcastListener* bl = static_cast - (entry->mListeners[i]); + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; if ((bl->mAttribute != aAttribute) && (bl->mAttribute != nsGkAtoms::_asterisk)) diff --git a/dom/xul/XULDocument.h b/dom/xul/XULDocument.h index 57bf76225f..401cfa5d87 100644 --- a/dom/xul/XULDocument.h +++ b/dom/xul/XULDocument.h @@ -75,7 +75,7 @@ public: bool RemoveElement(mozilla::dom::Element* aElement); private: - nsSmallVoidArray mRefContentList; + nsTArray mRefContentList; }; /** diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index 0b6e2cb82b..46298ca352 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -150,7 +150,6 @@ namespace JS { _(CantInlineClassConstructor) \ _(CantInlineDisabledIon) \ _(CantInlineTooManyArgs) \ - _(CantInlineHeavyweight) \ _(CantInlineNeedsArgsObj) \ _(CantInlineDebuggee) \ _(CantInlineUnknownProps) \ diff --git a/js/src/jit-test/tests/debug/optimized-out-01.js b/js/src/jit-test/tests/debug/optimized-out-01.js index 6820cba7d9..da5d83efa0 100644 --- a/js/src/jit-test/tests/debug/optimized-out-01.js +++ b/js/src/jit-test/tests/debug/optimized-out-01.js @@ -25,13 +25,14 @@ withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { } }; - g.eval("" + function f(d, x) { "use strict"; g(d, x); }); + g.eval("" + function f(d, x) { + "use strict"; + eval("g(d, x)"); // `eval` to avoid inlining g. + }); g.eval("" + function g(d, x) { "use strict"; for (var i = 0; i < 200; i++); - // Hack to prevent inlining. - function inner() { i = 42; }; toggle(d); }); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index c742ae428d..fc9e505283 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -527,11 +527,6 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo) return DontInline(inlineScript, "Common inlining path"); } - if (target->isHeavyweight()) { - trackOptimizationOutcome(TrackedOutcome::CantInlineHeavyweight); - return DontInline(inlineScript, "Heavyweight function"); - } - if (inlineScript->uninlineable()) { trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); return DontInline(inlineScript, "Uninlineable script"); diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index bb2c0878d4..8df6813357 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -1893,6 +1893,8 @@ nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint, return; } + mDocument->NotifyMediaFeatureValuesChanged(); + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); // Media query list listeners should be notified from a queued task diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index a5d68dcdc2..756239a426 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -1447,6 +1447,10 @@ nsComputedDOMStyle::DoGetFontSizeAdjust() CSSValue* nsComputedDOMStyle::DoGetOsxFontSmoothing() { + if (nsContentUtils::ShouldResistFingerprinting( + mPresShell->GetPresContext()->GetDocShell())) + return nullptr; + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; val->SetIdent(nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.smoothing, nsCSSProps::kFontSmoothingKTable)); diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp index 8c46587d94..b000a57865 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -104,13 +104,19 @@ GetDeviceContextFor(nsPresContext* aPresContext) return aPresContext->DeviceContext(); } +static bool +ShouldResistFingerprinting(nsPresContext* aPresContext) +{ + return nsContentUtils::ShouldResistFingerprinting(aPresContext->GetDocShell()); +} + // A helper for three features below. static nsSize GetDeviceSize(nsPresContext* aPresContext) { nsSize size; - if (aPresContext->IsDeviceSizePageSize()) { + if (ShouldResistFingerprinting(aPresContext) || aPresContext->IsDeviceSizePageSize()) { size = GetSize(aPresContext); } else if (aPresContext->IsRootPaginatedDocument()) { // We want the page size, including unprintable areas and margins. @@ -218,13 +224,17 @@ static nsresult GetColor(nsPresContext* aPresContext, const nsMediaFeature*, nsCSSValue& aResult) { - // FIXME: This implementation is bogus. nsDeviceContext - // doesn't provide reliable information (should be fixed in bug - // 424386). - // FIXME: On a monochrome device, return 0! - nsDeviceContext *dx = GetDeviceContextFor(aPresContext); - uint32_t depth; - dx->GetDepth(depth); + uint32_t depth = 24; // Use depth of 24 when resisting fingerprinting. + + if (!ShouldResistFingerprinting(aPresContext)) { + // FIXME: This implementation is bogus. nsDeviceContext + // doesn't provide reliable information (should be fixed in bug + // 424386). + // FIXME: On a monochrome device, return 0! + nsDeviceContext *dx = GetDeviceContextFor(aPresContext); + dx->GetDepth(depth); + } + // The spec says to use bits *per color component*, so divide by 3, // and round down, since the spec says to use the smallest when the // color components differ. @@ -262,10 +272,15 @@ static nsresult GetResolution(nsPresContext* aPresContext, const nsMediaFeature*, nsCSSValue& aResult) { - // Resolution measures device pixels per CSS (inch/cm/pixel). We - // return it in device pixels per CSS inches. - float dpi = float(nsPresContext::AppUnitsPerCSSInch()) / - float(aPresContext->AppUnitsPerDevPixel()); + float dpi = 96; // Use 96 when resisting fingerprinting. + + if (!ShouldResistFingerprinting(aPresContext)) { + // Resolution measures device pixels per CSS (inch/cm/pixel). We + // return it in device pixels per CSS inches. + dpi = float(nsPresContext::AppUnitsPerCSSInch()) / + float(aPresContext->AppUnitsPerDevPixel()); + } + aResult.SetFloatValue(dpi, eCSSUnit_Inch); return NS_OK; } @@ -294,15 +309,26 @@ static nsresult GetDevicePixelRatio(nsPresContext* aPresContext, const nsMediaFeature*, nsCSSValue& aResult) { - float ratio = aPresContext->CSSPixelsToDevPixels(1.0f); - aResult.SetFloatValue(ratio, eCSSUnit_Number); - return NS_OK; + if (!ShouldResistFingerprinting(aPresContext)) { + float ratio = aPresContext->CSSPixelsToDevPixels(1.0f); + aResult.SetFloatValue(ratio, eCSSUnit_Number); + } else { + aResult.SetFloatValue(1.0, eCSSUnit_Number); + } + return NS_OK; } static nsresult GetSystemMetric(nsPresContext* aPresContext, const nsMediaFeature* aFeature, nsCSSValue& aResult) { + aResult.Reset(); + if (ShouldResistFingerprinting(aPresContext)) { + // If "privacy.resistFingerprinting" is enabled, then we simply don't + // return any system-backed media feature values. (No spoofed values returned.) + return NS_OK; + } + MOZ_ASSERT(aFeature->mValueType == nsMediaFeature::eBoolInteger, "unexpected type"); nsIAtom *metricAtom = *aFeature->mData.mMetric; @@ -316,6 +342,10 @@ GetWindowsTheme(nsPresContext* aPresContext, const nsMediaFeature* aFeature, nsCSSValue& aResult) { aResult.Reset(); + if (ShouldResistFingerprinting(aPresContext)) { + return NS_OK; + } + #ifdef XP_WIN uint8_t windowsThemeId = nsCSSRuleProcessor::GetWindowsThemeIdentifier(); @@ -341,6 +371,10 @@ GetOperatinSystemVersion(nsPresContext* aPresContext, const nsMediaFeature* aFea nsCSSValue& aResult) { aResult.Reset(); + if (ShouldResistFingerprinting(aPresContext)) { + return NS_OK; + } + #ifdef XP_WIN int32_t metricResult; if (NS_SUCCEEDED( diff --git a/layout/style/test/chrome/bug418986-2.js b/layout/style/test/chrome/bug418986-2.js new file mode 100644 index 0000000000..694f212928 --- /dev/null +++ b/layout/style/test/chrome/bug418986-2.js @@ -0,0 +1,274 @@ +// # Bug 418986, part 2. + +/* jshint esnext:true */ +/* jshint loopfunc:true */ +/* global window, screen, ok, SpecialPowers, matchMedia */ + +SimpleTest.waitForExplicitFinish(); + +// Expected values. Format: [name, pref_off_value, pref_on_value] +// If pref_*_value is an array with two values, then we will match +// any value in between those two values. If a value is null, then +// we skip the media query. +let expected_values = [ + ["color", null, 8], + ["color-index", null, 0], + ["aspect-ratio", null, window.innerWidth + "/" + window.innerHeight], + ["device-aspect-ratio", screen.width + "/" + screen.height, + window.innerWidth + "/" + window.innerHeight], + ["device-height", screen.height + "px", window.innerHeight + "px"], + ["device-width", screen.width + "px", window.innerWidth + "px"], + ["grid", null, 0], + ["height", window.innerHeight + "px", window.innerHeight + "px"], + ["monochrome", null, 0], + // Square is defined as portrait: + ["orientation", null, + window.innerWidth > window.innerHeight ? + "landscape" : "portrait"], + ["resolution", null, "96dpi"], + ["resolution", [0.999 * window.devicePixelRatio + "dppx", + 1.001 * window.devicePixelRatio + "dppx"], "1dppx"], + ["width", window.innerWidth + "px", window.innerWidth + "px"], + ["-moz-device-pixel-ratio", window.devicePixelRatio, 1], + ["-moz-device-orientation", screen.width > screen.height ? + "landscape" : "portrait", + window.innerWidth > window.innerHeight ? + "landscape" : "portrait"] +]; + +// These media queries return value 0 or 1 when the pref is off. +// When the pref is on, they should not match. +let suppressed_toggles = [ + "-moz-images-in-menus", + "-moz-mac-graphite-theme", + // Not available on most OSs. +// "-moz-maemo-classic", + "-moz-scrollbar-end-backward", + "-moz-scrollbar-end-forward", + "-moz-scrollbar-start-backward", + "-moz-scrollbar-start-forward", + "-moz-scrollbar-thumb-proportional", + "-moz-touch-enabled", + "-moz-windows-compositor", + "-moz-windows-default-theme", + "-moz-windows-glass", +]; + +// Possible values for '-moz-os-version' +let windows_versions = [ + "windows-xp", + "windows-vista", + "windows-win7", + "windows-win8"]; + +// Possible values for '-moz-windows-theme' +let windows_themes = [ + "aero", + "luna-blue", + "luna-olive", + "luna-silver", + "royale", + "generic", + "zune" +]; + +// Read the current OS. +let OS = SpecialPowers.Services.appinfo.OS; + +// If we are using Windows, add an extra toggle only +// available on that OS. +if (OS === "WINNT") { + suppressed_toggles.push("-moz-windows-classic"); +} + +// __keyValMatches(key, val)__. +// Runs a media query and returns true if key matches to val. +let keyValMatches = (key, val) => matchMedia("(" + key + ":" + val +")").matches; + +// __testMatch(key, val)__. +// Attempts to run a media query match for the given key and value. +// If value is an array of two elements [min max], then matches any +// value in-between. +let testMatch = function (key, val) { + if (val === null) { + return; + } else if (Array.isArray(val)) { + ok(keyValMatches("min-" + key, val[0]) && keyValMatches("max-" + key, val[1]), + "Expected " + key + " between " + val[0] + " and " + val[1]); + } else { + ok(keyValMatches(key, val), "Expected " + key + ":" + val); + } +}; + +// __testToggles(resisting)__. +// Test whether we are able to match the "toggle" media queries. +let testToggles = function (resisting) { + suppressed_toggles.forEach( + function (key) { + var exists = keyValMatches(key, 0) || keyValMatches(key, 1); + if (resisting) { + ok(!exists, key + " should not exist."); + } else { + ok(exists, key + " should exist."); + } + }); +}; + +// __testWindowsSpecific__. +// Runs a media query on the queryName with the given possible matching values. +let testWindowsSpecific = function (resisting, queryName, possibleValues) { + let found = false; + possibleValues.forEach(function (val) { + found = found || keyValMatches(queryName, val); + }); + if (resisting) { + ok(!found, queryName + " should have no match"); + } else { + ok(found, queryName + " should match"); + } +}; + +// __generateHtmlLines(resisting)__. +// Create a series of div elements that look like: +// `
resolution
`, +// where each line corresponds to a different media query. +let generateHtmlLines = function (resisting) { + let lines = ""; + expected_values.forEach( + function ([key, offVal, onVal]) { + let val = resisting ? onVal : offVal; + if (val) { + lines += "
" + key + "
\n"; + } + }); + suppressed_toggles.forEach( + function (key) { + lines += "
" + key + "
\n"; + }); + if (OS === "WINNT") { + lines += "
-moz-os-version
"; + lines += "
-moz-windows-theme
"; + } + return lines; +}; + +// __cssLine__. +// Creates a line of css that looks something like +// `@media (resolution: 1ppx) { .spoof#resolution { background-color: green; } }`. +let cssLine = function (query, clazz, id, color) { + return "@media " + query + " { ." + clazz + "#" + id + + " { background-color: " + color + "; } }\n"; +}; + +// __mediaQueryCSSLine(key, val, color)__. +// Creates a line containing a CSS media query and a CSS expression. +let mediaQueryCSSLine = function (key, val, color) { + if (val === null) { + return ""; + } + let query; + if (Array.isArray(val)) { + query = "(min-" + key + ": " + val[0] + ") and (max-" + key + ": " + val[1] + ")"; + } else { + query = "(" + key + ": " + val + ")"; + } + return cssLine(query, "spoof", key, color); +}; + +// __suppressedMediaQueryCSSLine(key, color)__. +// Creates a CSS line that matches the existence of a +// media query that is supposed to be suppressed. +let suppressedMediaQueryCSSLine = function (key, color, suppressed) { + let query = "(" + key + ": 0), (" + key + ": 1)"; + return cssLine(query, "suppress", key, color); +}; + +// __generateCSSLines(resisting)__. +// Creates a series of lines of CSS, each of which corresponds to +// a different media query. If the query produces a match to the +// expected value, then the element will be colored green. +let generateCSSLines = function (resisting) { + let lines = ".spoof { background-color: red;}\n"; + expected_values.forEach( + function ([key, offVal, onVal]) { + lines += mediaQueryCSSLine(key, resisting ? onVal : offVal, "green"); + }); + lines += ".suppress { background-color: " + (resisting ? "green" : "red") + ";}\n"; + suppressed_toggles.forEach( + function (key) { + lines += suppressedMediaQueryCSSLine(key, resisting ? "red" : "green"); + }); + if (OS === "WINNT") { + lines += ".windows { background-color: " + (resisting ? "green" : "red") + ";}\n"; + lines += windows_versions.map(val => "(-moz-os-version: " + val + ")").join(", ") + + " { #-moz-os-version { background-color: " + (resisting ? "red" : "green") + ";} }\n"; + lines += windows_themes.map(val => "(-moz-windows-theme: " + val + ")").join(",") + + " { #-moz-windows-theme { background-color: " + (resisting ? "red" : "green") + ";} }\n"; + } + return lines; +}; + +// __green__. +// Returns the computed color style corresponding to green. +let green = (function () { + let temp = document.createElement("span"); + temp.style.backgroundColor = "green"; + return getComputedStyle(temp).backgroundColor; +})(); + +// __testCSS(resisting)__. +// Creates a series of divs and CSS using media queries to set their +// background color. If all media queries match as expected, then +// all divs should have a green background color. +let testCSS = function (resisting) { + document.getElementById("display").innerHTML = generateHtmlLines(resisting); + document.getElementById("test-css").innerHTML = generateCSSLines(resisting); + let cssTestDivs = document.querySelectorAll(".spoof,.suppress"); + for (let div of cssTestDivs) { + let color = window.getComputedStyle(div).backgroundColor; + ok(color === green, "CSS for '" + div.id + "'"); + } +}; + +// __testOSXFontSmoothing(resisting)__. +// When fingerprinting resistance is enabled, the `getComputedStyle` +// should always return `undefined` for `MozOSXFontSmoothing`. +let testOSXFontSmoothing = function (resisting) { + let div = document.createElement("div"); + div.style.MozOsxFontSmoothing = "unset"; + let readBack = window.getComputedStyle(div).MozOsxFontSmoothing; + let smoothingPref = SpecialPowers.getBoolPref("layout.css.osx-font-smoothing.enabled", false); + is(readBack, resisting ? "" : (smoothingPref ? "auto" : ""), + "-moz-osx-font-smoothing"); +}; + +// An iterator yielding pref values for two consecutive tests. +let prefVals = (for (prefVal of [false, true]) prefVal); + +// __test(isContent)__. +// Run all tests. +let test = function(isContent) { + let {value: prefValue, done} = prefVals.next(); + if (done) { + SimpleTest.finish(); + return; + } + SpecialPowers.pushPrefEnv({set: [["privacy.resistFingerprinting", prefValue]]}, + function () { + let resisting = prefValue && isContent; + expected_values.forEach( + function ([key, offVal, onVal]) { + testMatch(key, resisting ? onVal : offVal); + }); + testToggles(resisting); + if (OS === "WINNT") { + testWindowsSpecific(resisting, "-moz-os-version", windows_versions); + testWindowsSpecific(resisting, "-moz-windows-theme", windows_themes); + } + testCSS(resisting); + if (OS === "Darwin") { + testOSXFontSmoothing(resisting); + } + test(isContent); + }); +}; diff --git a/layout/style/test/chrome/chrome.ini b/layout/style/test/chrome/chrome.ini index 46796ab13e..61f0244e69 100644 --- a/layout/style/test/chrome/chrome.ini +++ b/layout/style/test/chrome/chrome.ini @@ -1,6 +1,7 @@ [DEFAULT] skip-if = buildapp == 'b2g' support-files = + bug418986-2.js bug535806-css.css bug535806-html.html bug535806-xul.xul @@ -9,6 +10,7 @@ support-files = [test_addSheet.html] [test_additional_sheets.html] [test_author_specified_style.html] +[test_bug418986-2.xul] [test_bug1157097.html] [test_bug1160724.xul] [test_bug535806.xul] diff --git a/layout/style/test/chrome/test_bug418986-2.xul b/layout/style/test/chrome/test_bug418986-2.xul new file mode 100644 index 0000000000..8424e15ac1 --- /dev/null +++ b/layout/style/test/chrome/test_bug418986-2.xul @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini index 742c2dcf57..7f39389a56 100644 --- a/layout/style/test/mochitest.ini +++ b/layout/style/test/mochitest.ini @@ -5,6 +5,7 @@ support-files = ccd.sjs ccd-standards.html css_properties.js + chrome/bug418986-2.js descriptor_database.js empty.html media_queries_dynamic_xbl_binding.xml @@ -74,6 +75,7 @@ skip-if = true # Bug 701060 [test_bug412901.html] skip-if = android_version == '18' # bug 1147986 [test_bug413958.html] +[test_bug418986-2.html] [test_bug437915.html] [test_bug450191.html] [test_bug453896_deck.html] diff --git a/layout/style/test/test_bug418986-2.html b/layout/style/test/test_bug418986-2.html new file mode 100644 index 0000000000..15d7c71ce4 --- /dev/null +++ b/layout/style/test/test_bug418986-2.html @@ -0,0 +1,29 @@ + + + + + + Test 2/3 for Bug #418986: Resist fingerprinting by preventing exposure of screen and system info + + + + + + + +Bug 418986 +

TEST

+ +
+
+ + diff --git a/parser/html/nsHtml5SpeculativeLoad.cpp b/parser/html/nsHtml5SpeculativeLoad.cpp index f3d562f8b5..ee15804a95 100644 --- a/parser/html/nsHtml5SpeculativeLoad.cpp +++ b/parser/html/nsHtml5SpeculativeLoad.cpp @@ -67,6 +67,9 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) intSource); } break; + case eSpeculativeLoadPreconnect: + aExecutor->Preconnect(mUrl); + break; default: NS_NOTREACHED("Bogus speculative load."); break; diff --git a/parser/html/nsHtml5SpeculativeLoad.h b/parser/html/nsHtml5SpeculativeLoad.h index e279e76c61..70ac54ebc5 100644 --- a/parser/html/nsHtml5SpeculativeLoad.h +++ b/parser/html/nsHtml5SpeculativeLoad.h @@ -24,7 +24,8 @@ enum eHtml5SpeculativeLoad { eSpeculativeLoadScriptFromHead, eSpeculativeLoadStyle, eSpeculativeLoadManifest, - eSpeculativeLoadSetDocumentCharset + eSpeculativeLoadSetDocumentCharset, + eSpeculativeLoadPreconnect }; class nsHtml5SpeculativeLoad { @@ -163,6 +164,14 @@ class nsHtml5SpeculativeLoad { mTypeOrCharsetSource.Assign((char16_t)aCharsetSource); } + inline void InitPreconnect(const nsAString& aUrl) + { + NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized, + "Trying to reinitialize a speculative load!"); + mOpCode = eSpeculativeLoadPreconnect; + mUrl.Assign(aUrl); + } + void Perform(nsHtml5TreeOpExecutor* aExecutor); private: diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h index 14c0664380..c044b16483 100644 --- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -176,16 +176,24 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName, nsString* rel = aAttributes->getValue(nsHtml5AttributeName::ATTR_REL); // Not splitting on space here is bogus but the old parser didn't even // do a case-insensitive check. - if (rel && rel->LowerCaseEqualsASCII("stylesheet")) { - nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF); - if (url) { - nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET); - nsString* crossOrigin = - aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN); - mSpeculativeLoadQueue.AppendElement()-> - InitStyle(*url, - (charset) ? *charset : EmptyString(), - (crossOrigin) ? *crossOrigin : NullString()); + if (rel) { + if (rel->LowerCaseEqualsASCII("stylesheet")) { + nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF); + if (url) { + nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET); + nsString* crossOrigin = + aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN); + mSpeculativeLoadQueue.AppendElement()-> + InitStyle(*url, + (charset) ? *charset : EmptyString(), + (crossOrigin) ? *crossOrigin : NullString()); + } + } else if (rel->LowerCaseEqualsASCII("preconnect")) { + nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF); + if (url) { + mSpeculativeLoadQueue.AppendElement()-> + InitPreconnect(*url); + } } } } else if (nsHtml5Atoms::video == aName) { diff --git a/parser/htmlparser/tests/mochitest/test_img_picture_preload.html b/parser/htmlparser/tests/mochitest/test_img_picture_preload.html index 7a925b1b13..8193dd2a82 100644 --- a/parser/htmlparser/tests/mochitest/test_img_picture_preload.html +++ b/parser/htmlparser/tests/mochitest/test_img_picture_preload.html @@ -61,15 +61,21 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1067345 if (testDPIs.length) { currentDPI = testDPIs.pop(); info("Starting test for DPI: " + currentDPI); - SpecialPowers.pushPrefEnv({'set': [ [ "layout.css.devPixelsPerPx", currentDPI ]] }, - function() { - // Clear image cache for next run (we don't try to validate cached items - // in preload). - SpecialPowers.Cc["@mozilla.org/image/tools;1"] - .getService(SpecialPowers.Ci.imgITools) - .getImgCacheForDocument(iframe.contentDocument) - .clearCache(false); - iframe.src = "./file_img_picture_preload.html?" + currentDPI; + // To avoid spurious image loads being reported when the resolution changes, + // load an intermediate iframe. + iframe.src = "about:blank"; + iframe.addEventListener('load', function on_iframe_load() { + iframe.removeEventListener('load', on_iframe_load); + SpecialPowers.pushPrefEnv({'set': [ [ "layout.css.devPixelsPerPx", currentDPI ]] }, + function() { + // Clear image cache for next run (we don't try to validate cached items + // in preload). + SpecialPowers.Cc["@mozilla.org/image/tools;1"] + .getService(SpecialPowers.Ci.imgITools) + .getImgCacheForDocument(iframe.contentDocument) + .clearCache(false); + iframe.src = "./file_img_picture_preload.html?" + currentDPI; + }); }); } else { SimpleTest.finish(); diff --git a/widget/ScreenProxy.cpp b/widget/ScreenProxy.cpp index f1673af736..936841431f 100644 --- a/widget/ScreenProxy.cpp +++ b/widget/ScreenProxy.cpp @@ -171,9 +171,9 @@ ScreenProxy::InvalidateCacheOnNextTick() nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) { - appShell->RunInStableState( - NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache) - ); + nsCOMPtr r = + NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache); + appShell->RunInStableState(r.forget()); } else { // It's pretty bad news if we can't get the appshell. In that case, // let's just invalidate the cache right away. diff --git a/widget/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp index 88c11cb5ab..8e5ddaa428 100644 --- a/widget/nsBaseAppShell.cpp +++ b/widget/nsBaseAppShell.cpp @@ -378,7 +378,8 @@ nsBaseAppShell::RunSyncSectionsInternal(bool aStable, } void -nsBaseAppShell::ScheduleSyncSection(nsIRunnable* aRunnable, bool aStable) +nsBaseAppShell::ScheduleSyncSection(already_AddRefed aRunnable, + bool aStable) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); @@ -435,16 +436,16 @@ nsBaseAppShell::Observe(nsISupports *subject, const char *topic, return NS_OK; } -NS_IMETHODIMP -nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable) +void +nsBaseAppShell::RunInStableState(already_AddRefed aRunnable) { - ScheduleSyncSection(aRunnable, true); - return NS_OK; + ScheduleSyncSection(mozilla::Move(aRunnable), true); } NS_IMETHODIMP nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable) { - ScheduleSyncSection(aRunnable, false); + nsCOMPtr runnable = aRunnable; + ScheduleSyncSection(runnable.forget(), false); return NS_OK; } diff --git a/widget/nsBaseAppShell.h b/widget/nsBaseAppShell.h index b528e88e48..09f8e47b71 100644 --- a/widget/nsBaseAppShell.h +++ b/widget/nsBaseAppShell.h @@ -25,6 +25,8 @@ class nsBaseAppShell : public nsIAppShell, public nsIThreadObserver, public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIAPPSHELL + void RunInStableState(already_AddRefed runnable) override; + NS_DECL_NSITHREADOBSERVER NS_DECL_NSIOBSERVER @@ -94,7 +96,7 @@ private: } } - void ScheduleSyncSection(nsIRunnable* runnable, bool stable); + void ScheduleSyncSection(already_AddRefed runnable, bool stable); struct SyncSection { SyncSection() diff --git a/widget/nsIAppShell.idl b/widget/nsIAppShell.idl index 3c0af793c6..bb089d20ae 100644 --- a/widget/nsIAppShell.idl +++ b/widget/nsIAppShell.idl @@ -7,12 +7,15 @@ #include "nsISupports.idl" interface nsIRunnable; +%{ C++ +template struct already_AddRefed; +%} /** * Interface for the native event system layer. This interface is designed * to be used on the main application thread only. */ -[uuid(2d10ca53-f143-439a-bb2e-c1fbc71f6a05)] +[uuid(3d09973e-3975-4fd4-b103-276300cc8437)] interface nsIAppShell : nsISupports { /** @@ -71,16 +74,20 @@ interface nsIAppShell : nsISupports */ readonly attribute unsigned long eventloopNestingLevel; +%{ C++ /** - * Allows running of a "synchronous section", in the form of an nsIRunnable - * once the event loop has reached a "stable state". We've reached a stable - * state when the currently executing task/event has finished, see: + * Add a "synchronous section", in the form of an nsIRunnable run once the + * event loop has reached a "stable state". |runnable| must not cause any + * queued events to be processed (i.e. must not spin the event loop). We've + * reached a stable state when the currently executing task/event has + * finished, see: * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section * In practice this runs aRunnable once the currently executing event * finishes. If called multiple times per task/event, all the runnables will * be executed, in the order in which runInStableState() was called. */ - void runInStableState(in nsIRunnable runnable); + virtual void RunInStableState(already_AddRefed runnable) = 0; +%} /** * Run the given runnable before the next iteration of the event loop (this diff --git a/widget/nsScreenManagerProxy.cpp b/widget/nsScreenManagerProxy.cpp index e014940ade..b768a53091 100644 --- a/widget/nsScreenManagerProxy.cpp +++ b/widget/nsScreenManagerProxy.cpp @@ -200,9 +200,9 @@ nsScreenManagerProxy::InvalidateCacheOnNextTick() nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) { - appShell->RunInStableState( - NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache) - ); + nsCOMPtr r = + NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache); + appShell->RunInStableState(r.forget()); } else { // It's pretty bad news if we can't get the appshell. In that case, // let's just invalidate the cache right away. diff --git a/xpcom/glue/moz.build b/xpcom/glue/moz.build index 797cd8b0c4..872fb04fcf 100644 --- a/xpcom/glue/moz.build +++ b/xpcom/glue/moz.build @@ -60,7 +60,6 @@ EXPORTS += [ 'nsTPriorityQueue.h', 'nsTWeakRef.h', 'nsVersionComparator.h', - 'nsVoidArray.h', 'nsWeakReference.h', 'nsXPTCUtils.h', 'pldhash.h', diff --git a/xpcom/glue/nsCOMArray.h b/xpcom/glue/nsCOMArray.h index 8222654cb1..16a4fc1da8 100644 --- a/xpcom/glue/nsCOMArray.h +++ b/xpcom/glue/nsCOMArray.h @@ -216,10 +216,9 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, // * modified/removed. Be careful not to NS_RELEASE(foo)! // T* foo = array[i]; // -// This array will accept null as an argument for any object, and will -// store null in the array, just like nsVoidArray. But that also means -// that methods like ObjectAt() may return null when referring to an -// existing, but null entry in the array. +// This array will accept null as an argument for any object, and will store +// null in the array. But that also means that methods like ObjectAt() may +// return null when referring to an existing, but null entry in the array. template class nsCOMArray : public nsCOMArray_base { diff --git a/xpcom/glue/nsVoidArray.cpp b/xpcom/glue/nsVoidArray.cpp deleted file mode 100644 index 3d9804006d..0000000000 --- a/xpcom/glue/nsVoidArray.cpp +++ /dev/null @@ -1,1007 +0,0 @@ -/* -*- 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/. */ - -#include "mozilla/MathAlgorithms.h" -#include "mozilla/MemoryReporting.h" -#include - -#include "nsVoidArray.h" -#include "nsQuickSort.h" -#include "nsISupportsImpl.h" // for nsTraceRefcnt -#include "nsAlgorithm.h" - -/** - * Grow the array by at least this many elements at a time. - */ -static const int32_t kMinGrowArrayBy = 8; -static const int32_t kMaxGrowArrayBy = 1024; - -/** - * This is the threshold (in bytes) of the mImpl struct, past which - * we'll force the array to grow geometrically - */ -static const int32_t kLinearThreshold = 24 * sizeof(void*); - -/** - * Compute the number of bytes requires for the mImpl struct that will - * hold |n| elements. - */ -#define SIZEOF_IMPL(n_) (sizeof(Impl) + sizeof(void *) * ((n_) - 1)) - -/** - * Compute the number of elements that an mImpl struct of |n| bytes - * will hold. - */ -#define CAPACITYOF_IMPL(n_) ((((n_) - sizeof(Impl)) / sizeof(void *)) + 1) - -#if DEBUG_VOIDARRAY -#define MAXVOID 10 - -class VoidStats -{ -public: - VoidStats(); - ~VoidStats(); -}; - -static int sizesUsed; // number of the elements of the arrays used -static int sizesAlloced[MAXVOID]; // sizes of the allocations. sorted -static int NumberOfSize[MAXVOID]; // number of this allocation size (1 per array) -static int AllocedOfSize[MAXVOID]; // number of this allocation size (each size for array used) -static int MaxAuto[MAXVOID]; // AutoArrays that maxed out at this size -static int GrowInPlace[MAXVOID]; // arrays this size that grew in-place via realloc - -// these are per-allocation -static int MaxElements[2000]; // # of arrays that maxed out at each size. - -// statistics macros -#define ADD_TO_STATS(x,size) do {int i; for (i = 0; i < sizesUsed; i++) \ - { \ - if (sizesAlloced[i] == (int)(size)) \ - { ((x)[i])++; break; } \ - } \ - if (i >= sizesUsed && sizesUsed < MAXVOID) \ - { sizesAlloced[sizesUsed] = (size); \ - ((x)[sizesUsed++])++; break; \ - } \ - } while (0) - -#define SUB_FROM_STATS(x,size) do {int i; for (i = 0; i < sizesUsed; i++) \ - { \ - if (sizesAlloced[i] == (int)(size)) \ - { ((x)[i])--; break; } \ - } \ - } while (0) - - -VoidStats::VoidStats() -{ - sizesUsed = 1; - sizesAlloced[0] = 0; -} - -VoidStats::~VoidStats() -{ - int i; - for (i = 0; i < sizesUsed; ++i) { - printf("Size %d:\n",sizesAlloced[i]); - printf("\tNumber of VoidArrays this size (max): %d\n",NumberOfSize[i]-MaxAuto[i]); - printf("\tNumber of AutoVoidArrays this size (max): %d\n",MaxAuto[i]); - printf("\tNumber of allocations this size (total): %d\n",AllocedOfSize[i]); - printf("\tNumber of GrowsInPlace this size (total): %d\n",GrowInPlace[i]); - } - printf("Max Size of VoidArray:\n"); - for (i = 0; i < (int)(sizeof(MaxElements) / sizeof(MaxElements[0])); ++i) { - if (MaxElements[i]) { - printf("\t%d: %d\n", i, MaxElements[i]); - } - } -} - -// Just so constructor/destructor's get called -VoidStats gVoidStats; -#endif - -void -nsVoidArray::SetArray(Impl* aNewImpl, int32_t aSize, int32_t aCount) -{ - // old mImpl has been realloced and so we don't free/delete it - NS_PRECONDITION(aNewImpl, "can't set size"); - mImpl = aNewImpl; - mImpl->mCount = aCount; - mImpl->mSize = aSize; -} - -// This does all allocation/reallocation of the array. -// It also will compact down to N - good for things that might grow a lot -// at times, but usually are smaller, like JS deferred GC releases. -bool -nsVoidArray::SizeTo(int32_t aSize) -{ - uint32_t oldsize = GetArraySize(); - - if (aSize == (int32_t)oldsize) { - return true; // no change - } - - if (aSize <= 0) { - // free the array if allocated - if (mImpl) { - free(reinterpret_cast(mImpl)); - mImpl = nullptr; - } - return true; - } - - if (mImpl) { - // We currently own an array impl. Resize it appropriately. - if (aSize < mImpl->mCount) { - // XXX Note: we could also just resize to mCount - return true; // can't make it that small, ignore request - } - - char* bytes = (char*)realloc(mImpl, SIZEOF_IMPL(aSize)); - Impl* newImpl = reinterpret_cast(bytes); - if (!newImpl) { - return false; - } - -#if DEBUG_VOIDARRAY - if (mImpl == newImpl) { - ADD_TO_STATS(GrowInPlace, oldsize); - } - ADD_TO_STATS(AllocedOfSize, SIZEOF_IMPL(aSize)); - if (aSize > mMaxSize) { - ADD_TO_STATS(NumberOfSize, SIZEOF_IMPL(aSize)); - if (oldsize) { - SUB_FROM_STATS(NumberOfSize, oldsize); - } - mMaxSize = aSize; - if (mIsAuto) { - ADD_TO_STATS(MaxAuto, SIZEOF_IMPL(aSize)); - SUB_FROM_STATS(MaxAuto, oldsize); - } - } -#endif - SetArray(newImpl, aSize, newImpl->mCount); - return true; - } - - if ((uint32_t)aSize < oldsize) { - // No point in allocating if it won't free the current Impl anyway. - return true; - } - - // just allocate an array - // allocate the exact size requested - char* bytes = (char*)malloc(SIZEOF_IMPL(aSize)); - Impl* newImpl = reinterpret_cast(bytes); - if (!newImpl) { - return false; - } - -#if DEBUG_VOIDARRAY - ADD_TO_STATS(AllocedOfSize, SIZEOF_IMPL(aSize)); - if (aSize > mMaxSize) { - ADD_TO_STATS(NumberOfSize, SIZEOF_IMPL(aSize)); - if (oldsize && !mImpl) { - SUB_FROM_STATS(NumberOfSize, oldsize); - } - mMaxSize = aSize; - } -#endif - if (mImpl) { -#if DEBUG_VOIDARRAY - ADD_TO_STATS(MaxAuto, SIZEOF_IMPL(aSize)); - SUB_FROM_STATS(MaxAuto, 0); - SUB_FROM_STATS(NumberOfSize, 0); - mIsAuto = true; -#endif - // We must be growing an nsAutoVoidArray - copy since we didn't - // realloc. - memcpy(newImpl->mArray, mImpl->mArray, - mImpl->mCount * sizeof(mImpl->mArray[0])); - } - - SetArray(newImpl, aSize, mImpl ? mImpl->mCount : 0); - // no memset; handled later in ReplaceElementAt if needed - return true; -} - -bool -nsVoidArray::GrowArrayBy(int32_t aGrowBy) -{ - // We have to grow the array. Grow by kMinGrowArrayBy slots if we're - // smaller than kLinearThreshold bytes, or a power of two if we're - // larger. This is much more efficient with most memory allocators, - // especially if it's very large, or of the allocator is binned. - if (aGrowBy < kMinGrowArrayBy) { - aGrowBy = kMinGrowArrayBy; - } - - uint32_t newCapacity = GetArraySize() + aGrowBy; // Minimum increase - uint32_t newSize = SIZEOF_IMPL(newCapacity); - - if (newSize >= (uint32_t)kLinearThreshold) { - // newCount includes enough space for at least kMinGrowArrayBy new - // slots. Select the next power-of-two size in bytes above or - // equal to that. - // Also, limit the increase in size to about a VM page or two. - if (GetArraySize() >= kMaxGrowArrayBy) { - newCapacity = GetArraySize() + XPCOM_MAX(kMaxGrowArrayBy, aGrowBy); - newSize = SIZEOF_IMPL(newCapacity); - } else { - newSize = mozilla::CeilingLog2(newSize); - newCapacity = CAPACITYOF_IMPL(size_t(1) << newSize); - } - } - // frees old mImpl IF this succeeds - if (!SizeTo(newCapacity)) { - return false; - } - - return true; -} - -nsVoidArray::nsVoidArray() - : mImpl(nullptr) -{ - MOZ_COUNT_CTOR(nsVoidArray); -#if DEBUG_VOIDARRAY - mMaxCount = 0; - mMaxSize = 0; - mIsAuto = false; - ADD_TO_STATS(NumberOfSize, 0); - MaxElements[0]++; -#endif -} - -nsVoidArray::nsVoidArray(int32_t aCount) - : mImpl(nullptr) -{ - MOZ_COUNT_CTOR(nsVoidArray); -#if DEBUG_VOIDARRAY - mMaxCount = 0; - mMaxSize = 0; - mIsAuto = false; - MaxElements[0]++; -#endif - SizeTo(aCount); -} - -nsVoidArray& -nsVoidArray::operator=(const nsVoidArray& aOther) -{ - int32_t otherCount = aOther.Count(); - int32_t maxCount = GetArraySize(); - if (otherCount) { - if (otherCount > maxCount) { - // frees old mImpl IF this succeeds - if (!GrowArrayBy(otherCount - maxCount)) { - return *this; // XXX The allocation failed - don't do anything - } - - memcpy(mImpl->mArray, aOther.mImpl->mArray, - otherCount * sizeof(mImpl->mArray[0])); - mImpl->mCount = otherCount; - } else { - // the old array can hold the new array - memcpy(mImpl->mArray, aOther.mImpl->mArray, - otherCount * sizeof(mImpl->mArray[0])); - mImpl->mCount = otherCount; - // if it shrank a lot, compact it anyways - if ((otherCount * 2) < maxCount && maxCount > 100) { - Compact(); // shrank by at least 50 entries - } - } -#if DEBUG_VOIDARRAY - if (mImpl->mCount > mMaxCount && - mImpl->mCount < (int32_t)(sizeof(MaxElements) / sizeof(MaxElements[0]))) { - MaxElements[mImpl->mCount]++; - MaxElements[mMaxCount]--; - mMaxCount = mImpl->mCount; - } -#endif - } else { - // Why do we drop the buffer here when we don't in Clear()? - SizeTo(0); - } - - return *this; -} - -nsVoidArray::~nsVoidArray() -{ - MOZ_COUNT_DTOR(nsVoidArray); - if (mImpl) { - free(reinterpret_cast(mImpl)); - } -} - -bool -nsVoidArray::SetCount(int32_t aNewCount) -{ - NS_ASSERTION(aNewCount >= 0, "SetCount(negative index)"); - if (aNewCount < 0) { - return false; - } - - if (aNewCount == 0) { - Clear(); - return true; - } - - if (uint32_t(aNewCount) > uint32_t(GetArraySize())) { - int32_t oldCount = Count(); - int32_t growDelta = aNewCount - oldCount; - - // frees old mImpl IF this succeeds - if (!GrowArrayBy(growDelta)) { - return false; - } - } - - if (aNewCount > mImpl->mCount) { - // Make sure that new entries added to the array by this - // SetCount are cleared to 0. Some users of this assume that. - // This code means we don't have to memset when we allocate an array. - memset(&mImpl->mArray[mImpl->mCount], 0, - (aNewCount - mImpl->mCount) * sizeof(mImpl->mArray[0])); - } - - mImpl->mCount = aNewCount; - -#if DEBUG_VOIDARRAY - if (mImpl->mCount > mMaxCount && - mImpl->mCount < (int32_t)(sizeof(MaxElements) / sizeof(MaxElements[0]))) { - MaxElements[mImpl->mCount]++; - MaxElements[mMaxCount]--; - mMaxCount = mImpl->mCount; - } -#endif - - return true; -} - -int32_t -nsVoidArray::IndexOf(void* aPossibleElement) const -{ - if (mImpl) { - void** ap = mImpl->mArray; - void** end = ap + mImpl->mCount; - while (ap < end) { - if (*ap == aPossibleElement) { - return ap - mImpl->mArray; - } - ap++; - } - } - return -1; -} - -bool -nsVoidArray::InsertElementAt(void* aElement, int32_t aIndex) -{ - int32_t oldCount = Count(); - NS_ASSERTION(aIndex >= 0, "InsertElementAt(negative index)"); - if (uint32_t(aIndex) > uint32_t(oldCount)) { - // An invalid index causes the insertion to fail - // Invalid indexes are ones that add more than one entry to the - // array (i.e., they can append). - return false; - } - - if (oldCount >= GetArraySize()) { - if (!GrowArrayBy(1)) { - return false; - } - } - // else the array is already large enough - - int32_t slide = oldCount - aIndex; - if (0 != slide) { - // Slide data over to make room for the insertion - memmove(mImpl->mArray + aIndex + 1, mImpl->mArray + aIndex, - slide * sizeof(mImpl->mArray[0])); - } - - mImpl->mArray[aIndex] = aElement; - mImpl->mCount++; - -#if DEBUG_VOIDARRAY - if (mImpl->mCount > mMaxCount && - mImpl->mCount < (int32_t)(sizeof(MaxElements) / sizeof(MaxElements[0]))) { - MaxElements[mImpl->mCount]++; - MaxElements[mMaxCount]--; - mMaxCount = mImpl->mCount; - } -#endif - - return true; -} - -bool -nsVoidArray::InsertElementsAt(const nsVoidArray& aOther, int32_t aIndex) -{ - int32_t oldCount = Count(); - int32_t otherCount = aOther.Count(); - - NS_ASSERTION(aIndex >= 0, "InsertElementsAt(negative index)"); - if (uint32_t(aIndex) > uint32_t(oldCount)) { - // An invalid index causes the insertion to fail - // Invalid indexes are ones that are more than one entry past the end of - // the array (i.e., they can append). - return false; - } - - if (oldCount + otherCount > GetArraySize()) { - if (!GrowArrayBy(otherCount)) { - return false; - } - } - // else the array is already large enough - - int32_t slide = oldCount - aIndex; - if (slide != 0) { - // Slide data over to make room for the insertion - memmove(mImpl->mArray + aIndex + otherCount, mImpl->mArray + aIndex, - slide * sizeof(mImpl->mArray[0])); - } - - for (int32_t i = 0; i < otherCount; ++i) { - // copy all the elements (destroys aIndex) - mImpl->mArray[aIndex++] = aOther.mImpl->mArray[i]; - mImpl->mCount++; - } - -#if DEBUG_VOIDARRAY - if (mImpl->mCount > mMaxCount && - mImpl->mCount < (int32_t)(sizeof(MaxElements) / sizeof(MaxElements[0]))) { - MaxElements[mImpl->mCount]++; - MaxElements[mMaxCount]--; - mMaxCount = mImpl->mCount; - } -#endif - - return true; -} - -bool -nsVoidArray::ReplaceElementAt(void* aElement, int32_t aIndex) -{ - NS_ASSERTION(aIndex >= 0, "ReplaceElementAt(negative index)"); - if (aIndex < 0) { - return false; - } - - // Unlike InsertElementAt, ReplaceElementAt can implicitly add more - // than just the one element to the array. - if (uint32_t(aIndex) >= uint32_t(GetArraySize())) { - int32_t oldCount = Count(); - int32_t requestedCount = aIndex + 1; - int32_t growDelta = requestedCount - oldCount; - - // frees old mImpl IF this succeeds - if (!GrowArrayBy(growDelta)) { - return false; - } - } - - mImpl->mArray[aIndex] = aElement; - if (aIndex >= mImpl->mCount) { - // Make sure that any entries implicitly added to the array by this - // ReplaceElementAt are cleared to 0. Some users of this assume that. - // This code means we don't have to memset when we allocate an array. - if (aIndex > mImpl->mCount) { // note: not >= - // For example, if mCount is 2, and we do a ReplaceElementAt for - // element[5], then we need to set three entries ([2], [3], and [4]) - // to 0. - memset(&mImpl->mArray[mImpl->mCount], 0, - (aIndex - mImpl->mCount) * sizeof(mImpl->mArray[0])); - } - - mImpl->mCount = aIndex + 1; - -#if DEBUG_VOIDARRAY - if (mImpl->mCount > mMaxCount && - mImpl->mCount < (int32_t)(sizeof(MaxElements) / sizeof(MaxElements[0]))) { - MaxElements[mImpl->mCount]++; - MaxElements[mMaxCount]--; - mMaxCount = mImpl->mCount; - } -#endif - } - - return true; -} - -// useful for doing LRU arrays -bool -nsVoidArray::MoveElement(int32_t aFrom, int32_t aTo) -{ - void* tempElement; - - if (aTo == aFrom) { - return true; - } - - NS_ASSERTION(aTo >= 0 && aFrom >= 0, "MoveElement(negative index)"); - if (aTo >= Count() || aFrom >= Count()) { - // can't extend the array when moving an element. Also catches mImpl = null - return false; - } - tempElement = mImpl->mArray[aFrom]; - - if (aTo < aFrom) { - // Moving one element closer to the head; the elements inbetween move down - memmove(mImpl->mArray + aTo + 1, mImpl->mArray + aTo, - (aFrom - aTo) * sizeof(mImpl->mArray[0])); - mImpl->mArray[aTo] = tempElement; - } else { // already handled aFrom == aTo - // Moving one element closer to the tail; the elements inbetween move up - memmove(mImpl->mArray + aFrom, mImpl->mArray + aFrom + 1, - (aTo - aFrom) * sizeof(mImpl->mArray[0])); - mImpl->mArray[aTo] = tempElement; - } - - return true; -} - -void -nsVoidArray::RemoveElementsAt(int32_t aIndex, int32_t aCount) -{ - int32_t oldCount = Count(); - NS_ASSERTION(aIndex >= 0, "RemoveElementsAt(negative index)"); - if (uint32_t(aIndex) >= uint32_t(oldCount)) { - return; - } - // Limit to available entries starting at aIndex - if (aCount + aIndex > oldCount) { - aCount = oldCount - aIndex; - } - - // We don't need to move any elements if we're removing the - // last element in the array - if (aIndex < (oldCount - aCount)) { - memmove(mImpl->mArray + aIndex, mImpl->mArray + aIndex + aCount, - (oldCount - (aIndex + aCount)) * sizeof(mImpl->mArray[0])); - } - - mImpl->mCount -= aCount; - return; -} - -bool -nsVoidArray::RemoveElement(void* aElement) -{ - int32_t theIndex = IndexOf(aElement); - if (theIndex != -1) { - RemoveElementAt(theIndex); - return true; - } - - return false; -} - -void -nsVoidArray::Clear() -{ - if (mImpl) { - mImpl->mCount = 0; - } -} - -void -nsVoidArray::Compact() -{ - if (mImpl) { - // XXX NOTE: this is quite inefficient in many cases if we're only - // compacting by a little, but some callers care more about memory use. - int32_t count = Count(); - if (GetArraySize() > count) { - SizeTo(Count()); - } - } -} - -// Needed because we want to pass the pointer to the item in the array -// to the comparator function, not a pointer to the pointer in the array. -struct VoidArrayComparatorContext -{ - nsVoidArrayComparatorFunc mComparatorFunc; - void* mData; -}; - -static int -VoidArrayComparator(const void* aElement1, const void* aElement2, void* aData) -{ - VoidArrayComparatorContext* ctx = static_cast(aData); - return (*ctx->mComparatorFunc)(*static_cast(aElement1), - *static_cast(aElement2), - ctx->mData); -} - -void -nsVoidArray::Sort(nsVoidArrayComparatorFunc aFunc, void* aData) -{ - if (mImpl && mImpl->mCount > 1) { - VoidArrayComparatorContext ctx = {aFunc, aData}; - NS_QuickSort(mImpl->mArray, mImpl->mCount, sizeof(mImpl->mArray[0]), - VoidArrayComparator, &ctx); - } -} - -bool -nsVoidArray::EnumerateForwards(nsVoidArrayEnumFunc aFunc, void* aData) -{ - int32_t index = -1; - bool running = true; - - if (mImpl) { - while (running && (++index < mImpl->mCount)) { - running = (*aFunc)(mImpl->mArray[index], aData); - } - } - return running; -} - -bool -nsVoidArray::EnumerateForwards(nsVoidArrayEnumFuncConst aFunc, - void* aData) const -{ - int32_t index = -1; - bool running = true; - - if (mImpl) { - while (running && (++index < mImpl->mCount)) { - running = (*aFunc)(mImpl->mArray[index], aData); - } - } - return running; -} - -bool -nsVoidArray::EnumerateBackwards(nsVoidArrayEnumFunc aFunc, void* aData) -{ - bool running = true; - - if (mImpl) { - int32_t index = Count(); - while (running && (--index >= 0)) { - running = (*aFunc)(mImpl->mArray[index], aData); - } - } - return running; -} - -struct SizeOfElementIncludingThisData -{ - size_t mSize; - nsVoidArraySizeOfElementIncludingThisFunc mSizeOfElementIncludingThis; - mozilla::MallocSizeOf mMallocSizeOf; - void* mData; // the arg passed by the user -}; - -static bool -SizeOfElementIncludingThisEnumerator(const void* aElement, void* aData) -{ - SizeOfElementIncludingThisData* d = (SizeOfElementIncludingThisData*)aData; - d->mSize += d->mSizeOfElementIncludingThis(aElement, d->mMallocSizeOf, d->mData); - return true; -} - -size_t -nsVoidArray::SizeOfExcludingThis( - nsVoidArraySizeOfElementIncludingThisFunc aSizeOfElementIncludingThis, - mozilla::MallocSizeOf aMallocSizeOf, void* aData) const -{ - size_t n = 0; - // Measure the element storage. - if (mImpl) { - n += aMallocSizeOf(mImpl); - } - // Measure things pointed to by the elements. - if (aSizeOfElementIncludingThis) { - SizeOfElementIncludingThisData data2 = - { 0, aSizeOfElementIncludingThis, aMallocSizeOf, aData }; - EnumerateForwards(SizeOfElementIncludingThisEnumerator, &data2); - n += data2.mSize; - } - return n; -} - -//---------------------------------------------------------------------- -// NOTE: nsSmallVoidArray elements MUST all have the low bit as 0. -// This means that normally it's only used for pointers, and in particular -// structures or objects. -nsSmallVoidArray::~nsSmallVoidArray() -{ - if (HasSingle()) { - // Have to null out mImpl before the nsVoidArray dtor runs. - mImpl = nullptr; - } -} - -nsSmallVoidArray& -nsSmallVoidArray::operator=(nsSmallVoidArray& aOther) -{ - int32_t count = aOther.Count(); - switch (count) { - case 0: - Clear(); - break; - case 1: - Clear(); - AppendElement(aOther.ElementAt(0)); - break; - default: - if (GetArraySize() >= count || SizeTo(count)) { - *AsArray() = *aOther.AsArray(); - } - } - - return *this; -} - -int32_t -nsSmallVoidArray::GetArraySize() const -{ - if (HasSingle()) { - return 1; - } - - return AsArray()->GetArraySize(); -} - -int32_t -nsSmallVoidArray::Count() const -{ - if (HasSingle()) { - return 1; - } - - return AsArray()->Count(); -} - -void* -nsSmallVoidArray::FastElementAt(int32_t aIndex) const -{ - NS_ASSERTION(aIndex >= 0 && aIndex < Count(), - "nsSmallVoidArray::FastElementAt: index out of range"); - - if (HasSingle()) { - return GetSingle(); - } - - return AsArray()->FastElementAt(aIndex); -} - -int32_t -nsSmallVoidArray::IndexOf(void* aPossibleElement) const -{ - if (HasSingle()) { - return aPossibleElement == GetSingle() ? 0 : -1; - } - - return AsArray()->IndexOf(aPossibleElement); -} - -bool -nsSmallVoidArray::InsertElementAt(void* aElement, int32_t aIndex) -{ - NS_ASSERTION(!(NS_PTR_TO_INT32(aElement) & 0x1), - "Attempt to add element with 0x1 bit set to nsSmallVoidArray"); - - if (aIndex == 0 && IsEmpty()) { - SetSingle(aElement); - - return true; - } - - if (!EnsureArray()) { - return false; - } - - return AsArray()->InsertElementAt(aElement, aIndex); -} - -bool -nsSmallVoidArray::InsertElementsAt(const nsVoidArray& aOther, int32_t aIndex) -{ -#ifdef DEBUG - for (int i = 0; i < aOther.Count(); ++i) { - NS_ASSERTION(!(NS_PTR_TO_INT32(aOther.ElementAt(i)) & 0x1), - "Attempt to add element with 0x1 bit set to nsSmallVoidArray"); - } -#endif - - if (aIndex == 0 && IsEmpty() && aOther.Count() == 1) { - SetSingle(aOther.FastElementAt(0)); - - return true; - } - - if (!EnsureArray()) { - return false; - } - - return AsArray()->InsertElementsAt(aOther, aIndex); -} - -bool -nsSmallVoidArray::ReplaceElementAt(void* aElement, int32_t aIndex) -{ - NS_ASSERTION(!(NS_PTR_TO_INT32(aElement) & 0x1), - "Attempt to add element with 0x1 bit set to nsSmallVoidArray"); - - if (aIndex == 0 && (IsEmpty() || HasSingle())) { - SetSingle(aElement); - - return true; - } - - if (!EnsureArray()) { - return false; - } - - return AsArray()->ReplaceElementAt(aElement, aIndex); -} - -bool -nsSmallVoidArray::AppendElement(void* aElement) -{ - NS_ASSERTION(!(NS_PTR_TO_INT32(aElement) & 0x1), - "Attempt to add element with 0x1 bit set to nsSmallVoidArray"); - - if (IsEmpty()) { - SetSingle(aElement); - - return true; - } - - if (!EnsureArray()) { - return false; - } - - return AsArray()->AppendElement(aElement); -} - -bool -nsSmallVoidArray::RemoveElement(void* aElement) -{ - if (HasSingle()) { - if (aElement == GetSingle()) { - mImpl = nullptr; - return true; - } - - return false; - } - - return AsArray()->RemoveElement(aElement); -} - -void -nsSmallVoidArray::RemoveElementAt(int32_t aIndex) -{ - if (HasSingle()) { - if (aIndex == 0) { - mImpl = nullptr; - } - - return; - } - - AsArray()->RemoveElementAt(aIndex); -} - -void -nsSmallVoidArray::RemoveElementsAt(int32_t aIndex, int32_t aCount) -{ - if (HasSingle()) { - if (aIndex == 0) { - if (aCount > 0) { - mImpl = nullptr; - } - } - - return; - } - - AsArray()->RemoveElementsAt(aIndex, aCount); -} - -void -nsSmallVoidArray::Clear() -{ - if (HasSingle()) { - mImpl = nullptr; - } else { - AsArray()->Clear(); - } -} - -bool -nsSmallVoidArray::SizeTo(int32_t aMin) -{ - if (!HasSingle()) { - return AsArray()->SizeTo(aMin); - } - - if (aMin <= 0) { - mImpl = nullptr; - - return true; - } - - if (aMin == 1) { - return true; - } - - void* single = GetSingle(); - mImpl = nullptr; - if (!AsArray()->SizeTo(aMin)) { - SetSingle(single); - - return false; - } - - AsArray()->AppendElement(single); - - return true; -} - -void -nsSmallVoidArray::Compact() -{ - if (!HasSingle()) { - AsArray()->Compact(); - } -} - -void -nsSmallVoidArray::Sort(nsVoidArrayComparatorFunc aFunc, void* aData) -{ - if (!HasSingle()) { - AsArray()->Sort(aFunc, aData); - } -} - -bool -nsSmallVoidArray::EnumerateForwards(nsVoidArrayEnumFunc aFunc, void* aData) -{ - if (HasSingle()) { - return (*aFunc)(GetSingle(), aData); - } - return AsArray()->EnumerateForwards(aFunc, aData); -} - -bool -nsSmallVoidArray::EnumerateBackwards(nsVoidArrayEnumFunc aFunc, void* aData) -{ - if (HasSingle()) { - return (*aFunc)(GetSingle(), aData); - } - return AsArray()->EnumerateBackwards(aFunc, aData); -} - -bool -nsSmallVoidArray::EnsureArray() -{ - if (!HasSingle()) { - return true; - } - - void* single = GetSingle(); - mImpl = nullptr; - if (!AsArray()->AppendElement(single)) { - SetSingle(single); - - return false; - } - - return true; -} diff --git a/xpcom/glue/nsVoidArray.h b/xpcom/glue/nsVoidArray.h deleted file mode 100644 index 314ad57f27..0000000000 --- a/xpcom/glue/nsVoidArray.h +++ /dev/null @@ -1,265 +0,0 @@ -/* -*- 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/. */ -#ifndef nsVoidArray_h___ -#define nsVoidArray_h___ - -//#define DEBUG_VOIDARRAY 1 - -#include "nsDebug.h" - -#include "mozilla/MemoryReporting.h" -#include - -// Comparator callback function for sorting array values. -typedef int (*nsVoidArrayComparatorFunc)(const void* aElement1, - const void* aElement2, void* aData); - -// Enumerator callback function. Return false to stop -typedef bool (*nsVoidArrayEnumFunc)(void* aElement, void* aData); -typedef bool (*nsVoidArrayEnumFuncConst)(const void* aElement, void* aData); - -// SizeOfExcludingThis callback function. -typedef size_t (*nsVoidArraySizeOfElementIncludingThisFunc)( - const void* aElement, mozilla::MallocSizeOf aMallocSizeOf, void* aData); - -/// A basic zero-based array of void*'s that manages its own memory -class nsVoidArray -{ -public: - nsVoidArray(); - explicit nsVoidArray(int32_t aCount); // initial count of aCount elements set to nullptr - ~nsVoidArray(); - - nsVoidArray& operator=(const nsVoidArray& aOther); - - inline int32_t Count() const { return mImpl ? mImpl->mCount : 0; } - // If the array grows, the newly created entries will all be null - bool SetCount(int32_t aNewCount); - // returns the max number that can be held without allocating - inline int32_t GetArraySize() const { return mImpl ? mImpl->mSize : 0; } - - void* FastElementAt(int32_t aIndex) const - { - NS_ASSERTION(aIndex >= 0 && aIndex < Count(), - "nsVoidArray::FastElementAt: index out of range"); - return mImpl->mArray[aIndex]; - } - - // This both asserts and bounds-checks, because (1) we don't want - // people to write bad code, but (2) we don't want to change it to - // crashing for backwards compatibility. See bug 96108. - void* ElementAt(int32_t aIndex) const - { - NS_ASSERTION(aIndex >= 0 && aIndex < Count(), - "nsVoidArray::ElementAt: index out of range"); - return SafeElementAt(aIndex); - } - - // bounds-checked version - void* SafeElementAt(int32_t aIndex) const - { - if (uint32_t(aIndex) >= uint32_t(Count())) { // handles aIndex < 0 too - return nullptr; - } - // The bounds check ensures mImpl is non-null. - return mImpl->mArray[aIndex]; - } - - void* operator[](int32_t aIndex) const { return ElementAt(aIndex); } - - int32_t IndexOf(void* aPossibleElement) const; - - bool InsertElementAt(void* aElement, int32_t aIndex); - bool InsertElementsAt(const nsVoidArray& aOther, int32_t aIndex); - - bool ReplaceElementAt(void* aElement, int32_t aIndex); - - // useful for doing LRU arrays, sorting, etc - bool MoveElement(int32_t aFrom, int32_t aTo); - - bool AppendElement(void* aElement) - { - return InsertElementAt(aElement, Count()); - } - - bool AppendElements(nsVoidArray& aElements) - { - return InsertElementsAt(aElements, Count()); - } - - bool RemoveElement(void* aElement); - void RemoveElementsAt(int32_t aIndex, int32_t aCount); - void RemoveElementAt(int32_t aIndex) - { - return RemoveElementsAt(aIndex, 1); - } - - void Clear(); - - bool SizeTo(int32_t aMin); - // Subtly different - Compact() tries to be smart about whether we - // should reallocate the array; SizeTo() always reallocates. - void Compact(); - - void Sort(nsVoidArrayComparatorFunc aFunc, void* aData); - - bool EnumerateForwards(nsVoidArrayEnumFunc aFunc, void* aData); - bool EnumerateForwards(nsVoidArrayEnumFuncConst aFunc, void* aData) const; - bool EnumerateBackwards(nsVoidArrayEnumFunc aFunc, void* aData); - - // Measures the size of the array's element storage, and if - // |aSizeOfElementIncludingThis| is non-nullptr, measures the size of things - // pointed to by elements. - size_t SizeOfExcludingThis( - nsVoidArraySizeOfElementIncludingThisFunc aSizeOfElementIncludingThis, - mozilla::MallocSizeOf aMallocSizeOf, void* aData = nullptr) const; - -protected: - bool GrowArrayBy(int32_t aGrowBy); - - struct Impl - { - /** - * The actual array size. - */ - int32_t mSize; - - /** - * The number of elements in the array - */ - int32_t mCount; - - /** - * Array data, padded out to the actual size of the array. - */ - void* mArray[1]; - }; - - Impl* mImpl; -#if DEBUG_VOIDARRAY - int32_t mMaxCount; - int32_t mMaxSize; - bool mIsAuto; -#endif - - // bit twiddlers - void SetArray(Impl* aNewImpl, int32_t aSize, int32_t aCount); - -private: - /// Copy constructors are not allowed - nsVoidArray(const nsVoidArray& aOther); -}; - -//=================================================================== -// nsSmallVoidArray is not a general-purpose replacement for -// ns(Auto)VoidArray because there is (some) extra CPU overhead for arrays -// larger than 1 element, though not a lot. It is appropriate for -// space-sensitive uses where sizes of 0 or 1 are moderately common or -// more, and where we're NOT storing arbitrary integers or arbitrary -// pointers. - -// NOTE: nsSmallVoidArray can ONLY be used for holding items that always -// have the low bit as a 0 - i.e. element & 1 == 0. This happens to be -// true for allocated and object pointers for all the architectures we run -// on, but conceivably there might be some architectures/compilers for -// which it is NOT true. We know this works for all existing architectures -// because if it didn't then nsCheapVoidArray would have failed. Also note -// that we will ASSERT if this assumption is violated in DEBUG builds. - -// XXX we're really re-implementing the whole nsVoidArray interface here - -// some form of abstract class would be useful - -// I disagree on the abstraction here. If the point of this class is to be -// as small as possible, and no one will ever derive from it, as I found -// today, there should not be any virtualness to it to avoid the vtable -// ptr overhead. - -class nsSmallVoidArray : private nsVoidArray -{ -public: - ~nsSmallVoidArray(); - - nsSmallVoidArray& operator=(nsSmallVoidArray& aOther); - void* operator[](int32_t aIndex) const { return ElementAt(aIndex); } - - int32_t GetArraySize() const; - - int32_t Count() const; - void* FastElementAt(int32_t aIndex) const; - // This both asserts and bounds-checks, because (1) we don't want - // people to write bad code, but (2) we don't want to change it to - // crashing for backwards compatibility. See bug 96108. - void* ElementAt(int32_t aIndex) const - { - NS_ASSERTION(aIndex >= 0 && aIndex < Count(), - "nsSmallVoidArray::ElementAt: index out of range"); - return SafeElementAt(aIndex); - } - void* SafeElementAt(int32_t aIndex) const - { - // let compiler inline; it may be able to remove these checks - if (uint32_t(aIndex) >= uint32_t(Count())) { // handles aIndex < 0 too - return nullptr; - } - return FastElementAt(aIndex); - } - int32_t IndexOf(void* aPossibleElement) const; - bool InsertElementAt(void* aElement, int32_t aIndex); - bool InsertElementsAt(const nsVoidArray& aOther, int32_t aIndex); - bool ReplaceElementAt(void* aElement, int32_t aIndex); - bool MoveElement(int32_t aFrom, int32_t aTo); - bool AppendElement(void* aElement); - bool AppendElements(nsVoidArray& aElements) - { - return InsertElementsAt(aElements, Count()); - } - bool RemoveElement(void* aElement); - void RemoveElementsAt(int32_t aIndex, int32_t aCount); - void RemoveElementAt(int32_t aIndex); - - void Clear(); - bool SizeTo(int32_t aMin); - void Compact(); - void Sort(nsVoidArrayComparatorFunc aFunc, void* aData); - - bool EnumerateForwards(nsVoidArrayEnumFunc aFunc, void* aData); - bool EnumerateBackwards(nsVoidArrayEnumFunc aFunc, void* aData); - -private: - - bool HasSingle() const - { - return !!(reinterpret_cast(mImpl) & 0x1); - } - void* GetSingle() const - { - NS_ASSERTION(HasSingle(), "wrong type"); - return reinterpret_cast(reinterpret_cast(mImpl) & ~0x1); - } - void SetSingle(void* aChild) - { - NS_ASSERTION(HasSingle() || !mImpl, "overwriting array"); - mImpl = reinterpret_cast(reinterpret_cast(aChild) | 0x1); - } - bool IsEmpty() const - { - // Note that this isn't the same as Count()==0 - return !mImpl; - } - const nsVoidArray* AsArray() const - { - NS_ASSERTION(!HasSingle(), "This is a single"); - return this; - } - nsVoidArray* AsArray() - { - NS_ASSERTION(!HasSingle(), "This is a single"); - return this; - } - bool EnsureArray(); -}; - -#endif /* nsVoidArray_h___ */ diff --git a/xpcom/glue/objs.mozbuild b/xpcom/glue/objs.mozbuild index 85f1a503b9..0b21f405e4 100644 --- a/xpcom/glue/objs.mozbuild +++ b/xpcom/glue/objs.mozbuild @@ -29,7 +29,6 @@ xpcom_glue_src_lcppsrcs = [ 'nsThreadUtils.cpp', 'nsTObserverArray.cpp', 'nsVersionComparator.cpp', - 'nsVoidArray.cpp', 'nsWeakReference.cpp', 'pldhash.cpp', ]