Bug 1525245 - Stabilize cookiePolicy/cookiePermission for live documents - part 3 - LocalStorage and SessionStorage, r=asuth
☠☠ backed out by bc51c190590a ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 26 Feb 2019 17:35:49 +0000
changeset 519087 1c359cdcf69f276b4a58de7b8c11a07f7e4e8b9c
parent 519086 d65fb2d2a243d183ee084b84b1d898573dbf04dc
child 519088 858d08371107c0d06f02e57acc65f6edb8b85803
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1525245
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1525245 - Stabilize cookiePolicy/cookiePermission for live documents - part 3 - LocalStorage and SessionStorage, r=asuth Differential Revision: https://phabricator.services.mozilla.com/D18951
dom/base/nsGlobalWindowInner.cpp
dom/storage/LocalStorageCache.cpp
dom/storage/LocalStorageCache.h
dom/storage/SessionStorage.cpp
dom/storage/SessionStorage.h
dom/storage/SessionStorageCache.cpp
dom/storage/SessionStorageCache.h
dom/storage/Storage.cpp
dom/storage/Storage.h
dom/tests/mochitest/localstorage/mochitest.ini
dom/tests/mochitest/localstorage/test_cookieSession.html
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -4344,16 +4344,58 @@ Storage* nsGlobalWindowInner::GetSession
       return nullptr;
     }
 
     if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
       aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
       return nullptr;
     }
 
+    uint32_t rejectedReason = 0;
+    nsContentUtils::StorageAccess access =
+        nsContentUtils::StorageAllowedForWindow(this, &rejectedReason);
+
+    // SessionStorage is an ephemeral per-tab per-origin storage that only lives
+    // as long as the tab is open, although it may survive browser restarts
+    // thanks to the session store. So we interpret storage access differently
+    // than we would for persistent per-origin storage like LocalStorage and so
+    // it may be okay to provide SessionStorage even when we receive a value of
+    // eDeny.
+    //
+    // AntiTrackingCommon::IsFirstPartyStorageAccessGranted will return false
+    // for 3 main reasons.
+    //
+    // 1. Cookies are entirely blocked due to a per-origin permission
+    // (nsICookiePermission::ACCESS_DENY for the top-level principal or this
+    // window's principal) or the very broad BEHAVIOR_REJECT. This will return
+    // eDeny with a reason of STATE_COOKIES_BLOCKED_BY_PERMISSION or
+    // STATE_COOKIES_BLOCKED_ALL.
+    //
+    // 2. Third-party cookies are limited via BEHAVIOR_REJECT_FOREIGN and
+    // BEHAVIOR_LIMIT_FOREIGN and this is a third-party window. This will return
+    // eDeny with a reason of STATE_COOKIES_BLOCKED_FOREIGN.
+    //
+    // 3. Tracking protection (BEHAVIOR_REJECT_TRACKER) is in effect and
+    // IsThirdPartyTrackingResourceWindow() returned true and there wasn't a
+    // permission that allows it. This will return ePartitionedOrDeny with a
+    // reason of STATE_COOKIES_BLOCKED_TRACKER.
+    //
+    // In the 1st case, the user has explicitly indicated that they don't want
+    // to allow any storage to the origin or all origins and so we throw an
+    // error and deny access to SessionStorage. In the 2nd case, a legacy
+    // decision reasoned that there's no harm in providing SessionStorage
+    // because the information is not durable and cannot escape the current tab.
+    // The rationale is similar for the 3rd case.
+    if (access == nsContentUtils::StorageAccess::eDeny &&
+        rejectedReason !=
+            nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
+      aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+      return nullptr;
+    }
+
     nsresult rv;
 
     nsCOMPtr<nsIDOMStorageManager> storageManager =
         do_QueryInterface(docShell, &rv);
     if (NS_FAILED(rv)) {
       aError.Throw(rv);
       return nullptr;
     }
--- a/dom/storage/LocalStorageCache.cpp
+++ b/dom/storage/LocalStorageCache.cpp
@@ -72,17 +72,16 @@ NS_IMETHODIMP_(void) LocalStorageCacheBr
 LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
     : mActor(nullptr),
       mOriginNoSuffix(*aOriginNoSuffix),
       mMonitor("LocalStorageCache"),
       mLoaded(false),
       mLoadResult(NS_OK),
       mInitialized(false),
       mPersistent(false),
-      mSessionOnlyDataSetActive(false),
       mPreloadTelemetryRecorded(false) {
   MOZ_COUNT_CTOR(LocalStorageCache);
 }
 
 LocalStorageCache::~LocalStorageCache() {
   if (mActor) {
     mActor->SendDeleteMeInternal();
     MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
@@ -178,39 +177,17 @@ inline bool LocalStorageCache::Persist(c
 }
 
 const nsCString LocalStorageCache::Origin() const {
   return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
 }
 
 LocalStorageCache::Data& LocalStorageCache::DataSet(
     const LocalStorage* aStorage) {
-  uint32_t index = GetDataSetIndex(aStorage);
-
-  if (index == kSessionSet && !mSessionOnlyDataSetActive) {
-    // Session only data set is demanded but not filled with
-    // current data set, copy to session only set now.
-
-    WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS);
-
-    Data& defaultSet = mData[kDefaultSet];
-    Data& sessionSet = mData[kSessionSet];
-
-    for (auto iter = defaultSet.mKeys.Iter(); !iter.Done(); iter.Next()) {
-      sessionSet.mKeys.Put(iter.Key(), iter.UserData());
-    }
-
-    mSessionOnlyDataSetActive = true;
-
-    // This updates sessionSet.mOriginQuotaUsage and also updates global usage
-    // for all session only data
-    ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage);
-  }
-
-  return mData[index];
+  return mData[GetDataSetIndex(aStorage)];
 }
 
 bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
                                           int64_t aDelta,
                                           const MutationSource aSource) {
   return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
 }
 
@@ -536,17 +513,16 @@ void LocalStorageCache::UnloadItems(uint
   if (aUnloadFlags & kUnloadPrivate) {
     mData[kPrivateSet].mKeys.Clear();
     ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
   }
 
   if (aUnloadFlags & kUnloadSession) {
     mData[kSessionSet].mKeys.Clear();
     ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
-    mSessionOnlyDataSetActive = false;
   }
 
 #ifdef DOM_STORAGE_TESTS
   if (aUnloadFlags & kTestReload) {
     WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
 
     mData[kDefaultSet].mKeys.Clear();
     mLoaded = false;  // This is only used in testing code
--- a/dom/storage/LocalStorageCache.h
+++ b/dom/storage/LocalStorageCache.h
@@ -262,22 +262,16 @@ class LocalStorageCache : public LocalSt
   // Init() method has been called
   bool mInitialized : 1;
 
   // This cache is about to be bound with the database (i.e. it has
   // to load from the DB first and has to persist when modifying the
   // default data set.)
   bool mPersistent : 1;
 
-  // - False when the session-only data set was never used.
-  // - True after access to session-only data has been made for the first time.
-  // We also fill session-only data set with the default one at that moment.
-  // Drops back to false when session-only data are cleared from chrome.
-  bool mSessionOnlyDataSetActive : 1;
-
   // Whether we have already captured state of the cache preload on our first
   // access.
   bool mPreloadTelemetryRecorded : 1;
 };
 
 // StorageUsage
 // Infrastructure to manage and check eTLD+1 quota
 class StorageUsageBridge {
--- a/dom/storage/SessionStorage.cpp
+++ b/dom/storage/SessionStorage.cpp
@@ -159,16 +159,10 @@ bool SessionStorage::IsForkOf(const Stor
   MOZ_ASSERT(aOther);
   if (aOther->Type() != eSessionStorage) {
     return false;
   }
 
   return mCache == static_cast<const SessionStorage*>(aOther)->mCache;
 }
 
-bool SessionStorage::ShouldThrowWhenStorageAccessDenied(
-    uint32_t aRejectedReason) {
-  return aRejectedReason !=
-         nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/storage/SessionStorage.h
+++ b/dom/storage/SessionStorage.h
@@ -60,18 +60,16 @@ class SessionStorage final : public Stor
 
  private:
   ~SessionStorage();
 
   void BroadcastChangeNotification(const nsAString& aKey,
                                    const nsAString& aOldValue,
                                    const nsAString& aNewValue);
 
-  bool ShouldThrowWhenStorageAccessDenied(uint32_t aRejectedReason) override;
-
   RefPtr<SessionStorageCache> mCache;
   RefPtr<SessionStorageManager> mManager;
 
   nsString mDocumentURI;
   bool mIsPrivate;
 };
 
 }  // namespace dom
--- a/dom/storage/SessionStorageCache.cpp
+++ b/dom/storage/SessionStorageCache.cpp
@@ -4,36 +4,26 @@
  * 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 "SessionStorageCache.h"
 
 namespace mozilla {
 namespace dom {
 
-SessionStorageCache::SessionStorageCache() : mSessionDataSetActive(false) {}
+SessionStorageCache::SessionStorageCache() = default;
 
 SessionStorageCache::DataSet* SessionStorageCache::Set(
     DataSetType aDataSetType) {
   if (aDataSetType == eDefaultSetType) {
     return &mDefaultSet;
   }
 
   MOZ_ASSERT(aDataSetType == eSessionSetType);
 
-  if (!mSessionDataSetActive) {
-    mSessionSet.mOriginQuotaUsage = mDefaultSet.mOriginQuotaUsage;
-
-    for (auto iter = mDefaultSet.mKeys.ConstIter(); !iter.Done(); iter.Next()) {
-      mSessionSet.mKeys.Put(iter.Key(), iter.Data());
-    }
-
-    mSessionDataSetActive = true;
-  }
-
   return &mSessionSet;
 }
 
 int64_t SessionStorageCache::GetOriginQuotaUsage(DataSetType aDataSetType) {
   return Set(aDataSetType)->mOriginQuotaUsage;
 }
 
 uint32_t SessionStorageCache::Length(DataSetType aDataSetType) {
@@ -116,27 +106,21 @@ nsresult SessionStorageCache::RemoveItem
   return NS_OK;
 }
 
 void SessionStorageCache::Clear(DataSetType aDataSetType,
                                 bool aByUserInteraction) {
   DataSet* dataSet = Set(aDataSetType);
   dataSet->ProcessUsageDelta(-dataSet->mOriginQuotaUsage);
   dataSet->mKeys.Clear();
-
-  if (!aByUserInteraction && aDataSetType == eSessionSetType) {
-    mSessionDataSetActive = false;
-  }
 }
 
 already_AddRefed<SessionStorageCache> SessionStorageCache::Clone() const {
   RefPtr<SessionStorageCache> cache = new SessionStorageCache();
 
-  cache->mSessionDataSetActive = mSessionDataSetActive;
-
   cache->mDefaultSet.mOriginQuotaUsage = mDefaultSet.mOriginQuotaUsage;
   for (auto iter = mDefaultSet.mKeys.ConstIter(); !iter.Done(); iter.Next()) {
     cache->mDefaultSet.mKeys.Put(iter.Key(), iter.Data());
   }
 
   cache->mSessionSet.mOriginQuotaUsage = mSessionSet.mOriginQuotaUsage;
   for (auto iter = mSessionSet.mKeys.ConstIter(); !iter.Done(); iter.Next()) {
     cache->mSessionSet.mKeys.Put(iter.Key(), iter.Data());
--- a/dom/storage/SessionStorageCache.h
+++ b/dom/storage/SessionStorageCache.h
@@ -55,15 +55,14 @@ class SessionStorageCache final {
     int64_t mOriginQuotaUsage;
     nsDataHashtable<nsStringHashKey, nsString> mKeys;
   };
 
   DataSet* Set(DataSetType aDataSetType);
 
   DataSet mDefaultSet;
   DataSet mSessionSet;
-  bool mSessionDataSetActive;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SessionStorageCache_h
--- a/dom/storage/Storage.cpp
+++ b/dom/storage/Storage.cpp
@@ -24,47 +24,43 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LA
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Storage)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Storage::Storage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal)
     : mWindow(aWindow), mPrincipal(aPrincipal), mIsSessionOnly(false) {
   MOZ_ASSERT(aPrincipal);
+
+  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+    mIsSessionOnly = false;
+  } else if (mWindow) {
+    uint32_t rejectedReason = 0;
+    nsContentUtils::StorageAccess access =
+        nsContentUtils::StorageAllowedForWindow(mWindow, &rejectedReason);
+
+    MOZ_ASSERT(access != nsContentUtils::StorageAccess::eDeny ||
+               rejectedReason ==
+                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
+
+    mIsSessionOnly = access <= nsContentUtils::StorageAccess::eSessionScoped;
+  }
 }
 
 Storage::~Storage() {}
 
 /* static */ bool Storage::StoragePrefIsEnabled() {
   return mozilla::Preferences::GetBool(kStorageEnabled);
 }
 
 bool Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal) {
-  // This method is responsible for correct setting of mIsSessionOnly.
   if (!StoragePrefIsEnabled()) {
     return false;
   }
 
-  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
-    mIsSessionOnly = false;
-  } else if (mWindow) {
-    uint32_t rejectedReason = 0;
-    nsContentUtils::StorageAccess access =
-        nsContentUtils::StorageAllowedForWindow(mWindow, &rejectedReason);
-
-    // Note that we allow StorageAccess::ePartitionedOrDeny because we want
-    // tracker to have access to their sessionStorage.
-    if (access == nsContentUtils::StorageAccess::eDeny &&
-        ShouldThrowWhenStorageAccessDenied(rejectedReason)) {
-      return false;
-    }
-
-    mIsSessionOnly = access <= nsContentUtils::StorageAccess::eSessionScoped;
-  }
-
   return aSubjectPrincipal.Subsumes(mPrincipal);
 }
 
 /* virtual */ JSObject* Storage::WrapObject(JSContext* aCx,
                                             JS::Handle<JSObject*> aGivenProto) {
   return Storage_Binding::Wrap(aCx, this, aGivenProto);
 }
 
--- a/dom/storage/Storage.h
+++ b/dom/storage/Storage.h
@@ -129,33 +129,20 @@ class Storage : public nsISupports, publ
                            const char16_t* aStorageType,
                            const nsAString& aDocumentURI, bool aIsPrivate,
                            bool aImmediateDispatch);
 
  protected:
   virtual ~Storage();
 
   // The method checks whether the caller can use a storage.
-  // CanUseStorage is called before any DOM initiated operation
-  // on a storage is about to happen and ensures that the storage's
-  // session-only flag is properly set according the current settings.
-  // It is an optimization since the privileges check and session only
-  // state determination are complex and share the code (comes hand in
-  // hand together).
   bool CanUseStorage(nsIPrincipal& aSubjectPrincipal);
 
   virtual void LastRelease() {}
 
-  // This method is called when StorageAccess is not granted for the owning
-  // window. aRejectedReason is one of the possible blocking states from
-  // nsIWebProgressListener.
-  virtual bool ShouldThrowWhenStorageAccessDenied(uint32_t aRejectedReason) {
-    return true;
-  }
-
  private:
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // Whether storage is set to persist data only per session, may change
   // dynamically and is set by CanUseStorage function that is called
   // before any operation on the storage.
   bool mIsSessionOnly : 1;
--- a/dom/tests/mochitest/localstorage/mochitest.ini
+++ b/dom/tests/mochitest/localstorage/mochitest.ini
@@ -19,17 +19,16 @@ support-files =
   file_tryAccessSessionStorage.html
 
 [test_brokenUTF-16.html]
 [test_bug600307-DBOps.html]
 [test_bug746272-1.html]
 [test_bug746272-2.html]
 skip-if = os == "android" || verify # bug 962029
 [test_cookieBlock.html]
-[test_cookieSession.html]
 [test_embededNulls.html]
 [test_keySync.html]
 [test_localStorageBase.html]
 skip-if = e10s
 [test_localStorageBaseSessionOnly.html]
 [test_localStorageCookieSettings.html]
 [test_localStorageEnablePref.html]
 [test_localStorageKeyOrder.html]
deleted file mode 100644
--- a/dom/tests/mochitest/localstorage/test_cookieSession.html
+++ /dev/null
@@ -1,139 +0,0 @@
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>cookie per-session only test</title>
-
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-
-<script type="text/javascript">
-
-/*
-  Set cookie access to be just per session and store to the localStorage.
-  Content stored must prevail only for session of the browser, so it must
-  be accessible in another window we try to access that key in the same
-  storage.
- */
-
-function pushCookie(aPermission, aNext) {
-  SpecialPowers.pushPermissions([{'type': 'cookie', 'allow': aPermission, 'context': document}], aNext);
-}
-
-function test1() {
-  localStorage.setItem("persistent1", "persistent value 1");
-  localStorage.setItem("persistent2", "persistent value 2");
-
-  pushCookie(SpecialPowers.Ci.nsICookiePermission.ACCESS_SESSION, test1_b);
-}
-
-function test1_b() {
-  localStorage.setItem("session only", "session value");
-  parent.is(localStorage.getItem("session only"), "session value");
-  parent.is(localStorage.getItem("persistent1"), "persistent value 1");
-  parent.is(localStorage.getItem("persistent2"), "persistent value 2");
-
-  window.location.search = '?2';
-}
-
-function test2()
-{
-  parent.is(localStorage.getItem("session only"), "session value", "Value present when cookies in session-only mode");
-  parent.is(localStorage.getItem("persistent1"), "persistent value 1", "Persistent value present");
-  parent.is(localStorage.getItem("persistent2"), "persistent value 2", "Persistent value present");
-
-  localStorage.setItem("persistent1", "changed persistent value 1");
-  localStorage.removeItem("persistent2");
-
-  parent.is(localStorage.getItem("session only"), "session value", "Value present when cookies in session-only mode");
-  parent.is(localStorage.getItem("persistent1"), "changed persistent value 1", "Persistent value present");
-  parent.is(localStorage.getItem("persistent2"), null, "Persistent value removed");
-
-  // This clear has to delete only changes made in session only mode
-  localStorage.clear();
-
-  parent.is(localStorage.getItem("session only"), null, "Value not present when cookies in session-only mode after delete");
-  parent.is(localStorage.getItem("persistent1"), null, "Persistent value not present in session only after delete");
-  parent.is(localStorage.getItem("persistent2"), null, "Persistent value not present in session only after delete");
-
-  localStorage.setItem("session only 2", "must be deleted on drop of session-only cookies permissions");
-
-  pushCookie(SpecialPowers.Ci.nsICookiePermission.ACCESS_DEFAULT, function() { window.location.search = '?3'; });
-}
-
-function test3() {
-  parent.is(localStorage.getItem("session only"), null, "No value when cookies are in default mode");
-  parent.is(localStorage.getItem("session only 2"), null, "No value when cookies are in default mode");
-  parent.is(localStorage.getItem("persistent1"), "persistent value 1", "Persistent value present");
-  parent.is(localStorage.getItem("persistent2"), "persistent value 2", "Persistent value present");
-
-  pushCookie(SpecialPowers.Ci.nsICookiePermission.ACCESS_SESSION, function() { window.location.search = '?4'; });
-}
-
-function test4() {
-  parent.is(localStorage.getItem("session only"), null, "Value not present when cookies in session-only mode after delete");
-  parent.is(localStorage.getItem("session only 2"), null, "Value not present when cookies in session-only mode after delete");
-  parent.is(localStorage.getItem("persistent1"), "persistent value 1", "Persistent value present again");
-  parent.is(localStorage.getItem("persistent2"), "persistent value 2", "Persistent value present again");
-
-  pushCookie(SpecialPowers.Ci.nsICookiePermission.ACCESS_DEFAULT, function() { window.location.search = '?5'; });
-}
-
-function test5() {
-  localStorage.clear();
-
-  parent.is(localStorage.getItem("session only"), null, "No value when cookies are in default mode");
-  parent.is(localStorage.getItem("persistent1"), null, "Persistent value not present after delete");
-  parent.is(localStorage.getItem("persistent2"), null, "Persistent value not present after delete");
-
-  pushCookie(SpecialPowers.Ci.nsICookiePermission.ACCESS_SESSION, function() { window.location.search = '?6'; });
-}
-
-function test6() {
-  parent.is(localStorage.getItem("session only"), null, "Value not present when cookies in session-only mode after delete");
-  parent.is(localStorage.getItem("session only 2"), null, "No value when cookies are in default mode");
-  parent.is(localStorage.getItem("persistent1"), null, "Persistent value not present in session only after delete");
-  parent.is(localStorage.getItem("persistent2"), null, "Persistent value not present in session only after delete");
-
-  parent.SimpleTest.finish();
-}
-
-function startTest() {
-  switch (location.search) {
-    case '?1':
-      test1();
-      break;
-    case '?2':
-      test2();
-      break;
-    case '?3':
-      test3();
-      break;
-    case '?4':
-      test4();
-      break;
-    case '?5':
-      test5();
-      break;
-    case '?6':
-      test6();
-      break;
-    default:
-      SimpleTest.waitForExplicitFinish();
-
-      if (SpecialPowers.Services.lsm.nextGenLocalStorageEnabled) {
-        ok(true, "Test ignored when the next gen local storage is enabled.");
-        SimpleTest.finish();
-        return;
-      }
-
-      var iframe = document.createElement('iframe');
-      iframe.src = 'test_cookieSession.html?1';
-      document.body.appendChild(iframe);
-  }
-}
-</script>
-
-</head>
-
-<body onload="startTest()">
-</body>
-</html>