author | Andrea Marchesini <amarchesini@mozilla.com> |
Fri, 08 Mar 2019 09:00:46 +0000 | |
changeset 463100 | bfab5b97ae49984ad48ae2e99dec041b43064067 |
parent 463099 | c08a35a9c0d6482a7dd85f00ee0d3b1b685c9a84 |
child 463101 | 7d3090c3f30b5c156182420c405dc61974f07714 |
push id | 35666 |
push user | shindli@mozilla.com |
push date | Fri, 08 Mar 2019 13:49:21 +0000 |
treeherder | mozilla-central@3cddc7cd4da5 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | asuth |
bugs | 1525245 |
milestone | 67.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
|
--- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -4260,16 +4260,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,48 +24,44 @@ 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>