зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
6e9e399066
|
@ -8997,11 +8997,17 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StorageDisabledByAntiTracking(aWindow, aChannel, aPrincipal, aURI)) {
|
if (!StorageDisabledByAntiTracking(aWindow, aChannel, aPrincipal, aURI)) {
|
||||||
return StorageAccess::eDeny;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
return access;
|
static const char* kPrefName =
|
||||||
|
"privacy.restrict3rdpartystorage.partitionedHosts";
|
||||||
|
if (IsURIInPrefList(uri, kPrefName)) {
|
||||||
|
return StorageAccess::ePartitionedOrDeny;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StorageAccess::eDeny;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -11121,3 +11127,73 @@ nsContentUtils::StringifyJSON(JSContext* aCx, JS::MutableHandle<JS::Value> aValu
|
||||||
aOutStr = serializedValue;
|
aOutStr = serializedValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static */ bool
|
||||||
|
nsContentUtils::IsURIInPrefList(nsIURI* aURI, const char* aPrefName)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aPrefName);
|
||||||
|
|
||||||
|
if (!aURI) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsAutoCString scheme;
|
||||||
|
aURI->GetScheme(scheme);
|
||||||
|
if (!scheme.EqualsLiteral("http") &&
|
||||||
|
!scheme.EqualsLiteral("https")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsAutoCString host;
|
||||||
|
aURI->GetHost(host);
|
||||||
|
if (host.IsEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsAutoCString blackList;
|
||||||
|
Preferences::GetCString(aPrefName, blackList);
|
||||||
|
if (blackList.IsEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list is comma separated domain list. Each item may start with "*.".
|
||||||
|
// If starts with "*.", it matches any sub-domains.
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int32_t index = blackList.Find(host, false);
|
||||||
|
if (index >= 0 &&
|
||||||
|
static_cast<uint32_t>(index) + host.Length() <= blackList.Length() &&
|
||||||
|
// If start of the black list or next to ","?
|
||||||
|
(!index || blackList[index - 1] == ',')) {
|
||||||
|
// If end of the black list or immediately before ","?
|
||||||
|
size_t indexAfterHost = index + host.Length();
|
||||||
|
if (indexAfterHost == blackList.Length() ||
|
||||||
|
blackList[indexAfterHost] == ',') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If next character is '/', we need to check the path too.
|
||||||
|
// We assume the path in blacklist means "/foo" + "*".
|
||||||
|
if (blackList[indexAfterHost] == '/') {
|
||||||
|
int32_t endOfPath = blackList.Find(",", false, indexAfterHost);
|
||||||
|
nsDependentCSubstring::size_type length =
|
||||||
|
endOfPath < 0 ? static_cast<nsDependentCSubstring::size_type>(-1) :
|
||||||
|
endOfPath - indexAfterHost;
|
||||||
|
nsDependentCSubstring pathInBlackList(blackList,
|
||||||
|
indexAfterHost, length);
|
||||||
|
nsAutoCString filePath;
|
||||||
|
aURI->GetFilePath(filePath);
|
||||||
|
if (StringBeginsWith(filePath, pathInBlackList)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int32_t startIndexOfCurrentLevel = host[0] == '*' ? 1 : 0;
|
||||||
|
int32_t startIndexOfNextLevel =
|
||||||
|
host.Find(".", false, startIndexOfCurrentLevel + 1);
|
||||||
|
if (startIndexOfNextLevel <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
host = NS_LITERAL_CSTRING("*") +
|
||||||
|
nsDependentCSubstring(host, startIndexOfNextLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1210,6 +1210,12 @@ public:
|
||||||
size_t* aMaxBufferSize,
|
size_t* aMaxBufferSize,
|
||||||
size_t* aUsedBufferSize);
|
size_t* aUsedBufferSize);
|
||||||
|
|
||||||
|
// Returns true if the URI's host is contained in a pref list which is a comma
|
||||||
|
// separated domain list. Each item may start with "*.". If starts with
|
||||||
|
// "*.", it matches any sub-domains.
|
||||||
|
static bool
|
||||||
|
IsURIInPrefList(nsIURI* aURI, const char* aPrefName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Fill (with the parameters given) the localized string named |aKey| in
|
* Fill (with the parameters given) the localized string named |aKey| in
|
||||||
|
@ -2966,6 +2972,9 @@ public:
|
||||||
// of permissions. Private Browsing is considered to be more limiting
|
// of permissions. Private Browsing is considered to be more limiting
|
||||||
// then session scoping
|
// then session scoping
|
||||||
enum class StorageAccess {
|
enum class StorageAccess {
|
||||||
|
// The storage should be partitioned. if the caller is unable to do it, deny
|
||||||
|
// the storage access.
|
||||||
|
ePartitionedOrDeny = -1,
|
||||||
// Don't allow access to the storage
|
// Don't allow access to the storage
|
||||||
eDeny = 0,
|
eDeny = 0,
|
||||||
// Allow access to the storage, but only if it is secure to do so in a
|
// Allow access to the storage, but only if it is secure to do so in a
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "mozilla/dom/DOMPrefs.h"
|
#include "mozilla/dom/DOMPrefs.h"
|
||||||
#include "mozilla/dom/EventTarget.h"
|
#include "mozilla/dom/EventTarget.h"
|
||||||
#include "mozilla/dom/LocalStorage.h"
|
#include "mozilla/dom/LocalStorage.h"
|
||||||
|
#include "mozilla/dom/PartitionedLocalStorage.h"
|
||||||
#include "mozilla/dom/Storage.h"
|
#include "mozilla/dom/Storage.h"
|
||||||
#include "mozilla/dom/IdleRequest.h"
|
#include "mozilla/dom/IdleRequest.h"
|
||||||
#include "mozilla/dom/Performance.h"
|
#include "mozilla/dom/Performance.h"
|
||||||
|
@ -4884,18 +4885,33 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mLocalStorage) {
|
// LocalStorage needs to be exposed in every context except for sandboxes and
|
||||||
if (nsContentUtils::StorageAllowedForWindow(this) ==
|
// NullPrincipals (data: URLs, for instance). But we need to keep data
|
||||||
nsContentUtils::StorageAccess::eDeny) {
|
// separate in some scenarios: private-browsing and partitioned trackers.
|
||||||
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
// In private-browsing, LocalStorage keeps data in memory, and it shares
|
||||||
return nullptr;
|
// StorageEvents just with other origins in the same private-browsing
|
||||||
}
|
// environment.
|
||||||
|
// For Partitioned Trackers, we expose a partitioned LocalStorage, which
|
||||||
|
// doesn't share data with other contexts, and it's just in memory.
|
||||||
|
// Partitioned localStorage is available only for trackers listed in the
|
||||||
|
// privacy.restrict3rdpartystorage.partitionedHosts pref. See
|
||||||
|
// nsContentUtils::IsURIInPrefList to know the syntax for the pref value.
|
||||||
|
// This is a temporary web-compatibility hack.
|
||||||
|
|
||||||
nsIPrincipal *principal = GetPrincipal();
|
nsContentUtils::StorageAccess access =
|
||||||
if (!principal) {
|
nsContentUtils::StorageAllowedForWindow(this);
|
||||||
return nullptr;
|
if (access == nsContentUtils::StorageAccess::eDeny) {
|
||||||
}
|
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this behavior is observable: if we grant storage permission to a
|
||||||
|
// tracker, we pass from the partitioned LocalStorage to the 'normal'
|
||||||
|
// LocalStorage. The previous data is lost and the 2 window.localStorage
|
||||||
|
// objects, before and after the permission granted, will be different.
|
||||||
|
if (access != nsContentUtils::StorageAccess::ePartitionedOrDeny &&
|
||||||
|
(!mLocalStorage ||
|
||||||
|
mLocalStorage->Type() == Storage::ePartitionedLocalStorage)) {
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
||||||
do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
|
do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
|
||||||
|
@ -4912,6 +4928,12 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsIPrincipal *principal = GetPrincipal();
|
||||||
|
if (!principal) {
|
||||||
|
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<Storage> storage;
|
RefPtr<Storage> storage;
|
||||||
aError = storageManager->CreateStorage(this, principal, documentURI,
|
aError = storageManager->CreateStorage(this, principal, documentURI,
|
||||||
IsPrivateBrowsing(),
|
IsPrivateBrowsing(),
|
||||||
|
@ -4924,6 +4946,20 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
||||||
MOZ_ASSERT(mLocalStorage);
|
MOZ_ASSERT(mLocalStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (access == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
|
||||||
|
!mLocalStorage) {
|
||||||
|
nsIPrincipal *principal = GetPrincipal();
|
||||||
|
if (!principal) {
|
||||||
|
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLocalStorage = new PartitionedLocalStorage(this, principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_ASSERT((access == nsContentUtils::StorageAccess::ePartitionedOrDeny) ==
|
||||||
|
(mLocalStorage->Type() == Storage::ePartitionedLocalStorage));
|
||||||
|
|
||||||
return mLocalStorage;
|
return mLocalStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5827,12 +5863,13 @@ nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(storage->Type() == Storage::eLocalStorage);
|
if (storage->Type() == Storage::eLocalStorage) {
|
||||||
RefPtr<LocalStorage> localStorage =
|
RefPtr<LocalStorage> localStorage =
|
||||||
static_cast<LocalStorage*>(storage.get());
|
static_cast<LocalStorage*>(storage.get());
|
||||||
|
|
||||||
// We must apply the current change to the 'local' localStorage.
|
// We must apply the current change to the 'local' localStorage.
|
||||||
localStorage->ApplyEvent(aEvent);
|
localStorage->ApplyEvent(aEvent);
|
||||||
|
}
|
||||||
} else if (storageArea->Type() == Storage::eSessionStorage) {
|
} else if (storageArea->Type() == Storage::eSessionStorage) {
|
||||||
storage = GetSessionStorage(aRv);
|
storage = GetSessionStorage(aRv);
|
||||||
} else {
|
} else {
|
||||||
|
@ -5844,6 +5881,12 @@ nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storage->Type() == Storage::ePartitionedLocalStorage) {
|
||||||
|
// This error message is not exposed.
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(storage);
|
MOZ_ASSERT(storage);
|
||||||
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
|
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace IPC {
|
||||||
template<>
|
template<>
|
||||||
struct ParamTraits<nsContentUtils::StorageAccess> :
|
struct ParamTraits<nsContentUtils::StorageAccess> :
|
||||||
public ContiguousEnumSerializer<nsContentUtils::StorageAccess,
|
public ContiguousEnumSerializer<nsContentUtils::StorageAccess,
|
||||||
nsContentUtils::StorageAccess::eDeny,
|
nsContentUtils::StorageAccess::ePartitionedOrDeny,
|
||||||
nsContentUtils::StorageAccess::eNumValues>
|
nsContentUtils::StorageAccess::eNumValues>
|
||||||
{};
|
{};
|
||||||
} // namespace IPC
|
} // namespace IPC
|
||||||
|
|
|
@ -319,7 +319,7 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindowInner* aWindow,
|
||||||
// the factory callsite records whether the browser is in private browsing.
|
// the factory callsite records whether the browser is in private browsing.
|
||||||
// and thus we don't have to respect that setting here. IndexedDB has no
|
// and thus we don't have to respect that setting here. IndexedDB has no
|
||||||
// concept of session-local storage, and thus ignores it.
|
// concept of session-local storage, and thus ignores it.
|
||||||
if (access == nsContentUtils::StorageAccess::eDeny) {
|
if (access <= nsContentUtils::StorageAccess::eDeny) {
|
||||||
return NS_ERROR_DOM_SECURITY_ERR;
|
return NS_ERROR_DOM_SECURITY_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/* -*- 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 "PartitionedLocalStorage.h"
|
||||||
|
#include "SessionStorageCache.h"
|
||||||
|
|
||||||
|
#include "mozilla/dom/StorageBinding.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(PartitionedLocalStorage, Storage);
|
||||||
|
|
||||||
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartitionedLocalStorage)
|
||||||
|
NS_INTERFACE_MAP_END_INHERITING(Storage)
|
||||||
|
|
||||||
|
NS_IMPL_ADDREF_INHERITED(PartitionedLocalStorage, Storage)
|
||||||
|
NS_IMPL_RELEASE_INHERITED(PartitionedLocalStorage, Storage)
|
||||||
|
|
||||||
|
PartitionedLocalStorage::PartitionedLocalStorage(nsPIDOMWindowInner* aWindow,
|
||||||
|
nsIPrincipal* aPrincipal)
|
||||||
|
: Storage(aWindow, aPrincipal)
|
||||||
|
, mCache(new SessionStorageCache())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionedLocalStorage::~PartitionedLocalStorage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t
|
||||||
|
PartitionedLocalStorage::GetOriginQuotaUsage() const
|
||||||
|
{
|
||||||
|
return mCache->GetOriginQuotaUsage(SessionStorageCache::eSessionSetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
PartitionedLocalStorage::GetLength(nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(aSubjectPrincipal)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCache->Length(SessionStorageCache::eSessionSetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::Key(uint32_t aIndex, nsAString& aResult,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(aSubjectPrincipal)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCache->Key(SessionStorageCache::eSessionSetType, aIndex, aResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::GetItem(const nsAString& aKey, nsAString& aResult,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(aSubjectPrincipal)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCache->GetItem(SessionStorageCache::eSessionSetType, aKey, aResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::GetSupportedNames(nsTArray<nsString>& aKeys)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
|
||||||
|
// return just an empty array
|
||||||
|
aKeys.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCache->GetKeys(SessionStorageCache::eSessionSetType, aKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::SetItem(const nsAString& aKey, const nsAString& aValue,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(aSubjectPrincipal)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsString oldValue;
|
||||||
|
nsresult rv = mCache->SetItem(SessionStorageCache::eSessionSetType, aKey,
|
||||||
|
aValue, oldValue);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
aRv.Throw(rv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::RemoveItem(const nsAString& aKey,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (!CanUseStorage(aSubjectPrincipal)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsString oldValue;
|
||||||
|
nsresult rv = mCache->RemoveItem(SessionStorageCache::eSessionSetType, aKey,
|
||||||
|
oldValue);
|
||||||
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||||
|
|
||||||
|
if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PartitionedLocalStorage::Clear(nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
uint32_t length = GetLength(aSubjectPrincipal, aRv);
|
||||||
|
if (!length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCache->Clear(SessionStorageCache::eSessionSetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
PartitionedLocalStorage::IsForkOf(const Storage* aOther) const
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aOther);
|
||||||
|
if (aOther->Type() != eLocalStorage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCache == static_cast<const PartitionedLocalStorage*>(aOther)->mCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // dom namespace
|
||||||
|
} // mozilla namespace
|
|
@ -0,0 +1,71 @@
|
||||||
|
/* -*- 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 mozilla_dom_PartitionedLocalStorage_h
|
||||||
|
#define mozilla_dom_PartitionedLocalStorage_h
|
||||||
|
|
||||||
|
#include "Storage.h"
|
||||||
|
|
||||||
|
class nsIPrincipal;
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
class SessionStorageCache;
|
||||||
|
|
||||||
|
// PartitionedLocalStorage is a in-memory-only storage exposed to trackers. It
|
||||||
|
// doesn't share data with other contexts.
|
||||||
|
|
||||||
|
class PartitionedLocalStorage final : public Storage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NS_DECL_ISUPPORTS_INHERITED
|
||||||
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PartitionedLocalStorage, Storage)
|
||||||
|
|
||||||
|
PartitionedLocalStorage(nsPIDOMWindowInner* aWindow,
|
||||||
|
nsIPrincipal* aPrincipal);
|
||||||
|
|
||||||
|
StorageType Type() const override { return ePartitionedLocalStorage; }
|
||||||
|
|
||||||
|
int64_t GetOriginQuotaUsage() const override;
|
||||||
|
|
||||||
|
bool IsForkOf(const Storage* aStorage) const override;
|
||||||
|
|
||||||
|
// WebIDL
|
||||||
|
uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
void Key(uint32_t aIndex, nsAString& aResult,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
void GetItem(const nsAString& aKey, nsAString& aResult,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
void GetSupportedNames(nsTArray<nsString>& aKeys) override;
|
||||||
|
|
||||||
|
void SetItem(const nsAString& aKey, const nsAString& aValue,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
void RemoveItem(const nsAString& aKey,
|
||||||
|
nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
void Clear(nsIPrincipal& aSubjectPrincipal,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~PartitionedLocalStorage();
|
||||||
|
|
||||||
|
RefPtr<SessionStorageCache> mCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // dom namespace
|
||||||
|
} // mozilla namespace
|
||||||
|
|
||||||
|
#endif //mozilla_dom_PartitionedLocalStorage_h
|
|
@ -71,8 +71,6 @@ public:
|
||||||
private:
|
private:
|
||||||
~SessionStorage();
|
~SessionStorage();
|
||||||
|
|
||||||
bool ProcessUsageDelta(int64_t aDelta);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
BroadcastChangeNotification(const nsAString& aKey,
|
BroadcastChangeNotification(const nsAString& aKey,
|
||||||
const nsAString& aOldValue,
|
const nsAString& aOldValue,
|
||||||
|
|
|
@ -54,7 +54,7 @@ Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal)
|
||||||
nsContentUtils::StorageAccess access =
|
nsContentUtils::StorageAccess access =
|
||||||
nsContentUtils::StorageAllowedForPrincipal(Principal());
|
nsContentUtils::StorageAllowedForPrincipal(Principal());
|
||||||
|
|
||||||
if (access == nsContentUtils::StorageAccess::eDeny) {
|
if (access <= nsContentUtils::StorageAccess::eDeny) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ public:
|
||||||
enum StorageType {
|
enum StorageType {
|
||||||
eSessionStorage,
|
eSessionStorage,
|
||||||
eLocalStorage,
|
eLocalStorage,
|
||||||
|
ePartitionedLocalStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual StorageType Type() const = 0;
|
virtual StorageType Type() const = 0;
|
||||||
|
|
|
@ -10,6 +10,7 @@ with Files("**"):
|
||||||
EXPORTS.mozilla.dom += [
|
EXPORTS.mozilla.dom += [
|
||||||
'LocalStorage.h',
|
'LocalStorage.h',
|
||||||
'LocalStorageManager.h',
|
'LocalStorageManager.h',
|
||||||
|
'PartitionedLocalStorage.h',
|
||||||
'SessionStorageManager.h',
|
'SessionStorageManager.h',
|
||||||
'Storage.h',
|
'Storage.h',
|
||||||
'StorageActivityService.h',
|
'StorageActivityService.h',
|
||||||
|
@ -23,6 +24,7 @@ UNIFIED_SOURCES += [
|
||||||
'LocalStorage.cpp',
|
'LocalStorage.cpp',
|
||||||
'LocalStorageCache.cpp',
|
'LocalStorageCache.cpp',
|
||||||
'LocalStorageManager.cpp',
|
'LocalStorageManager.cpp',
|
||||||
|
'PartitionedLocalStorage.cpp',
|
||||||
'SessionStorage.cpp',
|
'SessionStorage.cpp',
|
||||||
'SessionStorageCache.cpp',
|
'SessionStorageCache.cpp',
|
||||||
'SessionStorageManager.cpp',
|
'SessionStorageManager.cpp',
|
||||||
|
|
|
@ -5860,16 +5860,6 @@ GCRuntime::sweepJitDataOnMainThread(FreeOp* fop)
|
||||||
js::CancelOffThreadIonCompile(rt, JS::Zone::Sweep);
|
js::CancelOffThreadIonCompile(rt, JS::Zone::Sweep);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SweepGroupRealmsIter r(rt); !r.done(); r.next()) {
|
|
||||||
r->sweepJitRealm();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
|
|
||||||
if (jit::JitZone* jitZone = zone->jitZone()) {
|
|
||||||
jitZone->sweep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug 1071218: the following method has not yet been refactored to
|
// Bug 1071218: the following method has not yet been refactored to
|
||||||
// work on a single zone-group at once.
|
// work on a single zone-group at once.
|
||||||
|
|
||||||
|
@ -5885,6 +5875,22 @@ GCRuntime::sweepJitDataOnMainThread(FreeOp* fop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JitZone/JitRealm must be swept *after* discarding JIT code, because
|
||||||
|
// Zone::discardJitCode might access CacheIRStubInfos deleted here.
|
||||||
|
{
|
||||||
|
gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_JIT_DATA);
|
||||||
|
|
||||||
|
for (SweepGroupRealmsIter r(rt); !r.done(); r.next()) {
|
||||||
|
r->sweepJitRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
|
||||||
|
if (jit::JitZone* jitZone = zone->jitZone()) {
|
||||||
|
jitZone->sweep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_TYPES);
|
gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_TYPES);
|
||||||
gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
|
gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
|
||||||
|
|
|
@ -102,11 +102,11 @@ BEGIN_TEST(testGCUID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec.clear();
|
vec.clear();
|
||||||
MinimizeHeap(cx);
|
|
||||||
|
|
||||||
// Grab the last object in the vector as our object of interest.
|
// Grab the last object in the vector as our object of interest.
|
||||||
obj = vec2.back();
|
obj = vec2.back();
|
||||||
CHECK(obj);
|
CHECK(obj);
|
||||||
|
CHECK(!js::gc::IsInsideNursery(obj));
|
||||||
tenuredAddr = uintptr_t(obj.get());
|
tenuredAddr = uintptr_t(obj.get());
|
||||||
CHECK(obj->zone()->getOrCreateUniqueId(obj, &uid));
|
CHECK(obj->zone()->getOrCreateUniqueId(obj, &uid));
|
||||||
|
|
||||||
|
@ -114,7 +114,11 @@ BEGIN_TEST(testGCUID)
|
||||||
// the new tenured heap location.
|
// the new tenured heap location.
|
||||||
JS::PrepareForFullGC(cx);
|
JS::PrepareForFullGC(cx);
|
||||||
JS::NonIncrementalGC(cx, GC_SHRINK, JS::gcreason::API);
|
JS::NonIncrementalGC(cx, GC_SHRINK, JS::gcreason::API);
|
||||||
MinimizeHeap(cx);
|
|
||||||
|
// There's a very low probability that this check could fail, but it is
|
||||||
|
// possible. If it becomes an annoying intermittent then we should make
|
||||||
|
// this test more robust by recording IDs of many objects and then checking
|
||||||
|
// that some have moved.
|
||||||
CHECK(uintptr_t(obj.get()) != tenuredAddr);
|
CHECK(uintptr_t(obj.get()) != tenuredAddr);
|
||||||
CHECK(obj->zone()->hasUniqueId(obj));
|
CHECK(obj->zone()->hasUniqueId(obj));
|
||||||
CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
|
CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
|
||||||
|
|
|
@ -1598,8 +1598,7 @@ class DebugEnvironmentProxyHandler : public BaseProxyHandler
|
||||||
CallObject& callobj = env->as<CallObject>();
|
CallObject& callobj = env->as<CallObject>();
|
||||||
RootedFunction fun(cx, &callobj.callee());
|
RootedFunction fun(cx, &callobj.callee());
|
||||||
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
|
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
|
||||||
AutoKeepTypeScripts keepTypes(cx);
|
if (!script->ensureHasAnalyzedArgsUsage(cx)) {
|
||||||
if (!script->ensureHasTypes(cx, keepTypes) || !script->ensureHasAnalyzedArgsUsage(cx)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -575,6 +575,19 @@ class JSFunction : public js::NativeObject
|
||||||
return nonLazyScript();
|
return nonLazyScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a scripted function, returns its canonical function (the
|
||||||
|
// original function allocated by the frontend). Note that lazy self-hosted
|
||||||
|
// builtins don't have a lazy script so in that case we also return nullptr.
|
||||||
|
JSFunction* maybeCanonicalFunction() const {
|
||||||
|
if (hasScript()) {
|
||||||
|
return nonLazyScript()->functionNonDelazifying();
|
||||||
|
}
|
||||||
|
if (isInterpretedLazy() && !isSelfHostedBuiltin()) {
|
||||||
|
return lazyScript()->functionNonDelazifying();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// The state of a JSFunction whose script errored out during bytecode
|
// The state of a JSFunction whose script errored out during bytecode
|
||||||
// compilation. Such JSFunctions are only reachable via GC iteration and
|
// compilation. Such JSFunctions are only reachable via GC iteration and
|
||||||
// not from script.
|
// not from script.
|
||||||
|
|
|
@ -521,14 +521,7 @@ ObjectGroup::defaultNewGroup(JSContext* cx, const Class* clasp,
|
||||||
if (associated->is<JSFunction>()) {
|
if (associated->is<JSFunction>()) {
|
||||||
|
|
||||||
// Canonicalize new functions to use the original one associated with its script.
|
// Canonicalize new functions to use the original one associated with its script.
|
||||||
JSFunction* fun = &associated->as<JSFunction>();
|
associated = associated->as<JSFunction>().maybeCanonicalFunction();
|
||||||
if (fun->hasScript()) {
|
|
||||||
associated = fun->nonLazyScript()->functionNonDelazifying();
|
|
||||||
} else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) {
|
|
||||||
associated = fun->lazyScript()->functionNonDelazifying();
|
|
||||||
} else {
|
|
||||||
associated = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have previously cleared the 'new' script information for this
|
// If we have previously cleared the 'new' script information for this
|
||||||
// function, don't try to construct another one.
|
// function, don't try to construct another one.
|
||||||
|
|
|
@ -4025,6 +4025,10 @@ TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun)
|
||||||
MOZ_ASSERT(!group->newScript(sweep));
|
MOZ_ASSERT(!group->newScript(sweep));
|
||||||
MOZ_ASSERT(!group->maybeUnboxedLayout(sweep));
|
MOZ_ASSERT(!group->maybeUnboxedLayout(sweep));
|
||||||
|
|
||||||
|
// rollbackPartiallyInitializedObjects expects function_ to be
|
||||||
|
// canonicalized.
|
||||||
|
MOZ_ASSERT(fun->maybeCanonicalFunction() == fun);
|
||||||
|
|
||||||
if (group->unknownProperties(sweep)) {
|
if (group->unknownProperties(sweep)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4404,7 +4408,13 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!iter.isConstructing() || !iter.matchCallee(cx, function)) {
|
if (!iter.isConstructing()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_ASSERT(iter.calleeTemplate()->maybeCanonicalFunction());
|
||||||
|
|
||||||
|
if (iter.calleeTemplate()->maybeCanonicalFunction() != function) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7926,74 +7926,6 @@ GetDocumentURIToCompareWithBlacklist(PresShell& aPresShell)
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
IsURIInBlacklistPref(nsIURI* aURI,
|
|
||||||
const char* aBlacklistPrefName)
|
|
||||||
{
|
|
||||||
if (!aURI) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsAutoCString scheme;
|
|
||||||
aURI->GetScheme(scheme);
|
|
||||||
if (!scheme.EqualsLiteral("http") &&
|
|
||||||
!scheme.EqualsLiteral("https")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsAutoCString host;
|
|
||||||
aURI->GetHost(host);
|
|
||||||
if (host.IsEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The black list is comma separated domain list. Each item may start with
|
|
||||||
// "*.". If starts with "*.", it matches any sub-domains.
|
|
||||||
nsAutoCString blackList;
|
|
||||||
Preferences::GetCString(aBlacklistPrefName, blackList);
|
|
||||||
if (blackList.IsEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
int32_t index = blackList.Find(host, false);
|
|
||||||
if (index >= 0 &&
|
|
||||||
static_cast<uint32_t>(index) + host.Length() <= blackList.Length() &&
|
|
||||||
// If start of the black list or next to ","?
|
|
||||||
(!index || blackList[index - 1] == ',')) {
|
|
||||||
// If end of the black list or immediately before ","?
|
|
||||||
size_t indexAfterHost = index + host.Length();
|
|
||||||
if (indexAfterHost == blackList.Length() ||
|
|
||||||
blackList[indexAfterHost] == ',') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If next character is '/', we need to check the path too.
|
|
||||||
// We assume the path in blacklist means "/foo" + "*".
|
|
||||||
if (blackList[indexAfterHost] == '/') {
|
|
||||||
int32_t endOfPath = blackList.Find(",", false, indexAfterHost);
|
|
||||||
nsDependentCSubstring::size_type length =
|
|
||||||
endOfPath < 0 ? static_cast<nsDependentCSubstring::size_type>(-1) :
|
|
||||||
endOfPath - indexAfterHost;
|
|
||||||
nsDependentCSubstring pathInBlackList(blackList,
|
|
||||||
indexAfterHost, length);
|
|
||||||
nsAutoCString filePath;
|
|
||||||
aURI->GetFilePath(filePath);
|
|
||||||
if (StringBeginsWith(filePath, pathInBlackList)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int32_t startIndexOfCurrentLevel = host[0] == '*' ? 1 : 0;
|
|
||||||
int32_t startIndexOfNextLevel =
|
|
||||||
host.Find(".", false, startIndexOfCurrentLevel + 1);
|
|
||||||
if (startIndexOfNextLevel <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
host = NS_LITERAL_CSTRING("*") +
|
|
||||||
nsDependentCSubstring(host, startIndexOfNextLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // #ifdef NIGHTLY_BUILD
|
#endif // #ifdef NIGHTLY_BUILD
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -8039,10 +7971,10 @@ PresShell::DispatchEventToDOM(WidgetEvent* aEvent,
|
||||||
mInitializedWithKeyPressEventDispatchingBlacklist = true;
|
mInitializedWithKeyPressEventDispatchingBlacklist = true;
|
||||||
nsCOMPtr<nsIURI> uri = GetDocumentURIToCompareWithBlacklist(*this);
|
nsCOMPtr<nsIURI> uri = GetDocumentURIToCompareWithBlacklist(*this);
|
||||||
mForceDispatchKeyPressEventsForNonPrintableKeys =
|
mForceDispatchKeyPressEventsForNonPrintableKeys =
|
||||||
IsURIInBlacklistPref(uri,
|
nsContentUtils::IsURIInPrefList(uri,
|
||||||
"dom.keyboardevent.keypress.hack.dispatch_non_printable_keys");
|
"dom.keyboardevent.keypress.hack.dispatch_non_printable_keys");
|
||||||
mForceUseLegacyKeyCodeAndCharCodeValues =
|
mForceUseLegacyKeyCodeAndCharCodeValues =
|
||||||
IsURIInBlacklistPref(uri,
|
nsContentUtils::IsURIInPrefList(uri,
|
||||||
"dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode");
|
"dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode");
|
||||||
}
|
}
|
||||||
if (mForceDispatchKeyPressEventsForNonPrintableKeys) {
|
if (mForceDispatchKeyPressEventsForNonPrintableKeys) {
|
||||||
|
|
|
@ -1363,6 +1363,9 @@ pref("content.sink.pending_event_mode", 0);
|
||||||
// 3 = openAbused
|
// 3 = openAbused
|
||||||
pref("privacy.popups.disable_from_plugins", 3);
|
pref("privacy.popups.disable_from_plugins", 3);
|
||||||
|
|
||||||
|
// Enable Paritioned LocalStorage for a list of hosts.
|
||||||
|
pref("privacy.restrict3rdpartystorage.partitionedHosts", "accounts.google.com/o/oauth2/");
|
||||||
|
|
||||||
// Excessive reporting of blocked popups can be a DOS vector,
|
// Excessive reporting of blocked popups can be a DOS vector,
|
||||||
// by overloading the main process as popups get blocked and when
|
// by overloading the main process as popups get blocked and when
|
||||||
// users try to restore all popups, which is the most visible
|
// users try to restore all popups, which is the most visible
|
||||||
|
|
|
@ -60,3 +60,6 @@ skip-if = serviceworker_e10s
|
||||||
[browser_storageAccessWithHeuristics.js]
|
[browser_storageAccessWithHeuristics.js]
|
||||||
[browser_allowPermissionForTracker.js]
|
[browser_allowPermissionForTracker.js]
|
||||||
[browser_denyPermissionForTracker.js]
|
[browser_denyPermissionForTracker.js]
|
||||||
|
[browser_partitionedLocalStorage.js]
|
||||||
|
[browser_partitionedLocalStorage_events.js]
|
||||||
|
support-files = localStorageEvents.html
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
AntiTracking.runTest("localStorage and Storage Access API",
|
||||||
|
async _ => {
|
||||||
|
/* import-globals-from storageAccessAPIHelpers.js */
|
||||||
|
await noStorageAccessInitially();
|
||||||
|
|
||||||
|
localStorage.foo = 42;
|
||||||
|
ok(true, "LocalStorage is allowed");
|
||||||
|
is(localStorage.foo, "42", "The value matches");
|
||||||
|
|
||||||
|
var prevLocalStorage = localStorage;
|
||||||
|
|
||||||
|
/* import-globals-from storageAccessAPIHelpers.js */
|
||||||
|
await callRequestStorageAccess();
|
||||||
|
|
||||||
|
ok(localStorage != prevLocalStorage, "We have a new localStorage");
|
||||||
|
is(localStorage.foo, undefined, "Undefined value after.");
|
||||||
|
|
||||||
|
localStorage.foo = 42;
|
||||||
|
ok(true, "LocalStorage is still allowed");
|
||||||
|
is(localStorage.foo, "42", "The value matches");
|
||||||
|
},
|
||||||
|
async _ => {
|
||||||
|
/* import-globals-from storageAccessAPIHelpers.js */
|
||||||
|
await noStorageAccessInitially();
|
||||||
|
|
||||||
|
localStorage.foo = 42;
|
||||||
|
ok(true, "LocalStorage is allowed");
|
||||||
|
is(localStorage.foo, "42", "The value matches");
|
||||||
|
|
||||||
|
var prevLocalStorage = localStorage;
|
||||||
|
|
||||||
|
/* import-globals-from storageAccessAPIHelpers.js */
|
||||||
|
await callRequestStorageAccess();
|
||||||
|
|
||||||
|
// For non-tracking windows, calling the API is a no-op
|
||||||
|
ok(localStorage == prevLocalStorage, "We have a new localStorage");
|
||||||
|
is(localStorage.foo, "42", "The value matches");
|
||||||
|
ok(true, "LocalStorage is allowed");
|
||||||
|
},
|
||||||
|
async _ => {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"]],
|
||||||
|
false, false);
|
|
@ -0,0 +1,537 @@
|
||||||
|
// A same origin (and same-process via setting "dom.ipc.processCount" to 1)
|
||||||
|
// top-level window with access to real localStorage does not share storage
|
||||||
|
// with an ePartitionOrDeny iframe that should have PartitionedLocalStorage and
|
||||||
|
// no storage events are received in either direction. (Same-process in order
|
||||||
|
// to avoid having to worry about any e10s propagation issues.)
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("Creating a tracker top-level context");
|
||||||
|
let trackerTab = BrowserTestUtils.addTab(gBrowser, TEST_3RD_PARTY_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let trackerBrowser = gBrowser.getBrowserForTab(trackerTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(trackerBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr = content.document.createElement("iframe");
|
||||||
|
ifr.setAttribute("id", "ifr");
|
||||||
|
ifr.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value...");
|
||||||
|
ifr.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set by the tracker");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
info("The tracker page should not have received events");
|
||||||
|
await ContentTask.spawn(trackerBrowser, null, async _ => {
|
||||||
|
is(content.localStorage.foo, undefined, "Undefined value!");
|
||||||
|
content.localStorage.foo = "normal-" + Math.random();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Let's see if non-tracker page has received events");
|
||||||
|
await ContentTask.spawn(normalBrowser, null, async _ => {
|
||||||
|
let ifr = content.document.getElementById("ifr");
|
||||||
|
|
||||||
|
info("Getting the value...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set by the tracker");
|
||||||
|
|
||||||
|
info("Getting the events...");
|
||||||
|
let events = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getEvents", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(events, 0, "No events");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(trackerTab);
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Two ePartitionOrDeny iframes in the same tab in the same origin don"t see
|
||||||
|
// the same localStorage values and no storage events are received from each
|
||||||
|
// other.
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr1 = content.document.createElement("iframe");
|
||||||
|
ifr1.setAttribute("id", "ifr1");
|
||||||
|
ifr1.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe 1 loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr1.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let ifr2 = content.document.createElement("iframe");
|
||||||
|
ifr2.setAttribute("id", "ifr2");
|
||||||
|
ifr2.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe 2 loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr2.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr2);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr1...");
|
||||||
|
ifr1.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr1...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr1.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set in ifr1");
|
||||||
|
|
||||||
|
info("Getting the value from ifr2...");
|
||||||
|
value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr2.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value, null, "The value is nt set in ifr2");
|
||||||
|
|
||||||
|
info("Getting the events received by ifr2...");
|
||||||
|
let events = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr2.contentWindow.postMessage("getEvents", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(events, 0, "No events");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same as the previous test but with a cookie behavior of BEHAVIOR_ACCEPT
|
||||||
|
// instead of BEHAVIOR_REJECT_TRACKER so the iframes get real, persistent
|
||||||
|
// localStorage instead of partitioned localStorage.
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr1 = content.document.createElement("iframe");
|
||||||
|
ifr1.setAttribute("id", "ifr1");
|
||||||
|
ifr1.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe 1 loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr1.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let ifr2 = content.document.createElement("iframe");
|
||||||
|
ifr2.setAttribute("id", "ifr2");
|
||||||
|
ifr2.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe 2 loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr2.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr2);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr1...");
|
||||||
|
ifr1.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr1...");
|
||||||
|
let value1 = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr1.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value1.startsWith("tracker-"), "The value is correctly set in ifr1");
|
||||||
|
|
||||||
|
info("Getting the value from ifr2...");
|
||||||
|
let value2 = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr2.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value2, value1, "The values match");
|
||||||
|
|
||||||
|
info("Getting the events received by ifr2...");
|
||||||
|
let events = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr2.contentWindow.postMessage("getEvents", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(events, 1, "One event");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// An ePartitionOrDeny iframe navigated between two distinct pages on the same
|
||||||
|
// origin does not see the values stored by the previous iframe.
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr = content.document.createElement("iframe");
|
||||||
|
ifr.setAttribute("id", "ifr");
|
||||||
|
ifr.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr...");
|
||||||
|
ifr.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
|
||||||
|
|
||||||
|
info("Navigate...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
ifr.setAttribute("src", obj.page + "?" + Math.random());
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value, null, "The value is undefined");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Like the previous test, but accepting trackers
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr = content.document.createElement("iframe");
|
||||||
|
ifr.setAttribute("id", "ifr");
|
||||||
|
ifr.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr...");
|
||||||
|
ifr.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
|
||||||
|
|
||||||
|
info("Navigate...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
ifr.setAttribute("src", obj.page + "?" + Math.random());
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value2 = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value, value2, "The value is undefined");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// An ePartitionOrDeny iframe on the same origin that is navigated to itself
|
||||||
|
// via window.location.reload() or equivalent does not see the values stored by
|
||||||
|
// its previous self.
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr = content.document.createElement("iframe");
|
||||||
|
ifr.setAttribute("id", "ifr");
|
||||||
|
ifr.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr...");
|
||||||
|
ifr.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
|
||||||
|
|
||||||
|
info("Reload...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
ifr.contentWindow.postMessage("reload", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value, null, "The value is undefined");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Like the previous test, but accepting trackers
|
||||||
|
add_task(async _ => {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["dom.ipc.processCount", 1],
|
||||||
|
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_ACCEPT],
|
||||||
|
["privacy.trackingprotection.enabled", false],
|
||||||
|
["privacy.trackingprotection.pbmode.enabled", false],
|
||||||
|
["privacy.trackingprotection.annotate_channels", true],
|
||||||
|
["browser.fastblock.enabled", false], // prevent intermittent failures
|
||||||
|
["privacy.restrict3rdpartystorage.partitionedHosts", "tracking.example.org,tracking.example.com"],
|
||||||
|
]});
|
||||||
|
|
||||||
|
await UrlClassifierTestUtils.addTestTrackers();
|
||||||
|
|
||||||
|
info("Creating a non-tracker top-level context");
|
||||||
|
let normalTab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "page.html");
|
||||||
|
let normalBrowser = gBrowser.getBrowserForTab(normalTab);
|
||||||
|
await BrowserTestUtils.browserLoaded(normalBrowser);
|
||||||
|
|
||||||
|
info("The non-tracker page opens a tracker iframe");
|
||||||
|
await ContentTask.spawn(normalBrowser, {
|
||||||
|
page: TEST_3RD_PARTY_DOMAIN + TEST_PATH + "localStorageEvents.html",
|
||||||
|
}, async obj => {
|
||||||
|
let ifr = content.document.createElement("iframe");
|
||||||
|
ifr.setAttribute("id", "ifr");
|
||||||
|
ifr.setAttribute("src", obj.page);
|
||||||
|
|
||||||
|
info("Iframe loading...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
content.document.body.appendChild(ifr);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Setting localStorage value in ifr...");
|
||||||
|
ifr.contentWindow.postMessage("setValue", "*");
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(value.startsWith("tracker-"), "The value is correctly set in ifr");
|
||||||
|
|
||||||
|
info("Reload...");
|
||||||
|
await new content.Promise(resolve => {
|
||||||
|
ifr.onload = resolve;
|
||||||
|
ifr.contentWindow.postMessage("reload", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Getting the value from ifr...");
|
||||||
|
let value2 = await new Promise(resolve => {
|
||||||
|
content.addEventListener("message", e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}, {once: true});
|
||||||
|
ifr.contentWindow.postMessage("getValue", "*");
|
||||||
|
});
|
||||||
|
|
||||||
|
is(value, value2, "The value is undefined");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(normalTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup data.
|
||||||
|
add_task(async _ => {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let eventCounter = 0;
|
||||||
|
|
||||||
|
onmessage = e => {
|
||||||
|
if (e.data == "getValue") {
|
||||||
|
parent.postMessage(localStorage.foo, "*");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.data == "setValue") {
|
||||||
|
localStorage.foo = "tracker-" + Math.random();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.data == "getEvents") {
|
||||||
|
parent.postMessage(eventCounter, "*");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.data == "reload") {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addEventListener("storage", _ => {
|
||||||
|
++eventCounter;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
Загрузка…
Ссылка в новой задаче