Bug 1724386 - Cache InternalStorageAllowedCheck on inner window. r=timhuang,nika

Differential Revision: https://phabricator.services.mozilla.com/D120833
This commit is contained in:
Paul Zuehlcke 2021-08-09 16:50:42 +00:00
parent 247bd59b37
commit 2cffa3f5c4
6 changed files with 89 additions and 6 deletions

View File

@ -919,6 +919,7 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
mHasSeenGamepadInput(false),
mHintedWasLoading(false),
mHasOpenedExternalProtocolFrame(false),
mStorageAllowedReasonCache(0),
mSuspendDepth(0),
mFreezeDepth(0),
#ifdef DEBUG
@ -7546,6 +7547,10 @@ void nsGlobalWindowInner::ForgetSharedWorker(SharedWorker* aSharedWorker) {
}
void nsGlobalWindowInner::StorageAccessPermissionGranted() {
// Invalidate cached StorageAllowed field so that calls to GetLocalStorage
// give us the updated localStorage object.
ClearStorageAllowedCache();
PropagateStorageAccessPermissionGrantedToWorkers(*this);
// If we have a partitioned localStorage, it's time to replace it with a real

View File

@ -44,6 +44,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/TimeStamp.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/dom/EventTarget.h"
@ -1295,6 +1296,25 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
nsTArray<uint32_t>& GetScrollMarks() { return mScrollMarks; }
void SetScrollMarks(const nsTArray<uint32_t>& aScrollMarks);
// Don't use this value directly, call StorageAccess::StorageAllowedForWindow
// instead.
mozilla::Maybe<mozilla::StorageAccess> GetStorageAllowedCache(
uint32_t& aRejectedReason) {
if (mStorageAllowedCache.isSome()) {
aRejectedReason = mStorageAllowedReasonCache;
}
return mStorageAllowedCache;
}
void SetStorageAllowedCache(const mozilla::StorageAccess& storageAllowed,
uint32_t aRejectedReason) {
mStorageAllowedCache = Some(storageAllowed);
mStorageAllowedReasonCache = aRejectedReason;
}
void ClearStorageAllowedCache() {
mStorageAllowedCache = mozilla::Nothing();
mStorageAllowedReasonCache = 0;
}
private:
RefPtr<mozilla::dom::ContentMediaController> mContentMediaController;
@ -1408,6 +1428,12 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
nsCOMPtr<nsIPrincipal> mDocumentPartitionedPrincipal;
nsCOMPtr<nsIContentSecurityPolicy> mDocumentCsp;
// Used to cache the result of StorageAccess::StorageAllowedForWindow.
// Don't use this field directly, use StorageAccess::StorageAllowedForWindow
// instead.
mozilla::Maybe<mozilla::StorageAccess> mStorageAllowedCache;
uint32_t mStorageAllowedReasonCache;
RefPtr<mozilla::dom::DebuggerNotificationManager>
mDebuggerNotificationManager;

View File

@ -2403,6 +2403,11 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
if (!aState) {
if (reUseInnerWindow) {
// The StorageAccess state may have changed. Invalidate the cached
// StorageAllowed field, so that the next call to StorageAllowedForWindow
// recomputes it.
newInnerWindow->ClearStorageAllowedCache();
// The storage objects contain the URL of the window. We have to
// recreate them when the innerWindow is reused.
newInnerWindow->mLocalStorage = nullptr;

View File

@ -10435,6 +10435,12 @@
value: false
mirror: always
# Whether the storage allowed check should be cached on the inner window.
- name: privacy.antitracking.cacheStorageAllowedForWindow
type: bool
value: true
mirror: always
# Enable the heuristic to allow storage access for recent visited pages
- name: privacy.restrict3rdpartystorage.heuristic.recently_visited
type: bool

View File

@ -67,7 +67,8 @@ static void GetCookieLifetimePolicyFromCookieJarSettings(
* status.
*
* Used in the implementation of StorageAllowedForWindow,
* StorageAllowedForChannel and StorageAllowedForServiceWorker.
* StorageAllowedForDocument, StorageAllowedForChannel and
* StorageAllowedForServiceWorker.
*/
static StorageAccess InternalStorageAllowedCheck(
nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI,
@ -179,6 +180,43 @@ static StorageAccess InternalStorageAllowedCheck(
return StorageAccess::eDeny;
}
/**
* Wrapper around InternalStorageAllowedCheck which caches the check result on
* the inner window to improve performance. nsGlobalWindowInner is responsible
* for invalidating the cache state if storage access changes during window
* lifetime.
*/
static StorageAccess InternalStorageAllowedCheckCached(
nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI,
nsIChannel* aChannel, nsICookieJarSettings* aCookieJarSettings,
uint32_t& aRejectedReason) {
// If enabled, check if we have already computed the storage access field
// for this window. This avoids repeated calls to
// InternalStorageAllowedCheck.
nsGlobalWindowInner* win = nullptr;
if (aWindow &&
StaticPrefs::privacy_antitracking_cacheStorageAllowedForWindow()) {
win = nsGlobalWindowInner::Cast(aWindow);
Maybe<StorageAccess> storageAccess =
win->GetStorageAllowedCache(aRejectedReason);
if (storageAccess.isSome()) {
return storageAccess.value();
}
}
StorageAccess result = InternalStorageAllowedCheck(
aPrincipal, aWindow, aURI, aChannel, aCookieJarSettings, aRejectedReason);
if (win) {
// Remember check result for the lifetime of the window. It's the windows
// responsibility to invalidate this field if storage access changes
// because a storage access permission is granted.
win->SetStorageAllowedCache(result, aRejectedReason);
}
return result;
}
static bool StorageDisabledByAntiTrackingInternal(
nsPIDOMWindowInner* aWindow, nsIChannel* aChannel, nsIPrincipal* aPrincipal,
nsIURI* aURI, nsICookieJarSettings* aCookieJarSettings,
@ -223,9 +261,9 @@ StorageAccess StorageAllowedForWindow(nsPIDOMWindowInner* aWindow,
// callee is able to deal with a null channel argument, and if passed null,
// will only fail to notify the UI in case storage gets blocked.
nsIChannel* channel = document->GetChannel();
return InternalStorageAllowedCheck(principal, aWindow, nullptr, channel,
document->CookieJarSettings(),
*aRejectedReason);
return InternalStorageAllowedCheckCached(
principal, aWindow, nullptr, channel, document->CookieJarSettings(),
*aRejectedReason);
}
// No document? Let's return a generic rejected reason.
@ -243,7 +281,7 @@ StorageAccess StorageAllowedForDocument(const Document* aDoc) {
nsIChannel* channel = aDoc->GetChannel();
uint32_t rejectedReason = 0;
return InternalStorageAllowedCheck(
return InternalStorageAllowedCheckCached(
principal, inner, nullptr, channel,
const_cast<Document*>(aDoc)->CookieJarSettings(), rejectedReason);
}

View File

@ -121,6 +121,7 @@ runTest({
],
});
let numBlockEvents = Services.prefs.getBoolPref("privacy.antitracking.cacheStorageAllowedForWindow") ? 1 : 2;
runTest({
testName:
"Socialtracking-annotation feature enabled and considered for tracking detection.",
@ -132,7 +133,9 @@ runTest({
[Ci.nsIWebProgressListener.STATE_COOKIES_LOADED, true, 1],
[Ci.nsIWebProgressListener.STATE_COOKIES_LOADED_SOCIALTRACKER, true, 1],
[Ci.nsIWebProgressListener.STATE_LOADED_SOCIALTRACKING_CONTENT, true, 2],
[Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER, true, 2],
// If we cache the storage allowed decision, we will only get one block
// event per window and origin.
[Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER, true, numBlockEvents],
],
});