зеркало из 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)) {
|
||||
return StorageAccess::eDeny;
|
||||
if (!StorageDisabledByAntiTracking(aWindow, aChannel, aPrincipal, aURI)) {
|
||||
return access;
|
||||
}
|
||||
|
||||
return access;
|
||||
static const char* kPrefName =
|
||||
"privacy.restrict3rdpartystorage.partitionedHosts";
|
||||
if (IsURIInPrefList(uri, kPrefName)) {
|
||||
return StorageAccess::ePartitionedOrDeny;
|
||||
}
|
||||
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -11121,3 +11127,73 @@ nsContentUtils::StringifyJSON(JSContext* aCx, JS::MutableHandle<JS::Value> aValu
|
|||
aOutStr = serializedValue;
|
||||
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* 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:
|
||||
/**
|
||||
* 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
|
||||
// then session scoping
|
||||
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
|
||||
eDeny = 0,
|
||||
// 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/EventTarget.h"
|
||||
#include "mozilla/dom/LocalStorage.h"
|
||||
#include "mozilla/dom/PartitionedLocalStorage.h"
|
||||
#include "mozilla/dom/Storage.h"
|
||||
#include "mozilla/dom/IdleRequest.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
|
@ -4884,18 +4885,33 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mLocalStorage) {
|
||||
if (nsContentUtils::StorageAllowedForWindow(this) ==
|
||||
nsContentUtils::StorageAccess::eDeny) {
|
||||
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
// LocalStorage needs to be exposed in every context except for sandboxes and
|
||||
// NullPrincipals (data: URLs, for instance). But we need to keep data
|
||||
// separate in some scenarios: private-browsing and partitioned trackers.
|
||||
// In private-browsing, LocalStorage keeps data in memory, and it shares
|
||||
// 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();
|
||||
if (!principal) {
|
||||
return nullptr;
|
||||
}
|
||||
nsContentUtils::StorageAccess access =
|
||||
nsContentUtils::StorageAllowedForWindow(this);
|
||||
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;
|
||||
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
||||
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;
|
||||
aError = storageManager->CreateStorage(this, principal, documentURI,
|
||||
IsPrivateBrowsing(),
|
||||
|
@ -4924,6 +4946,20 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -5827,12 +5863,13 @@ nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(storage->Type() == Storage::eLocalStorage);
|
||||
RefPtr<LocalStorage> localStorage =
|
||||
static_cast<LocalStorage*>(storage.get());
|
||||
if (storage->Type() == Storage::eLocalStorage) {
|
||||
RefPtr<LocalStorage> localStorage =
|
||||
static_cast<LocalStorage*>(storage.get());
|
||||
|
||||
// We must apply the current change to the 'local' localStorage.
|
||||
localStorage->ApplyEvent(aEvent);
|
||||
// We must apply the current change to the 'local' localStorage.
|
||||
localStorage->ApplyEvent(aEvent);
|
||||
}
|
||||
} else if (storageArea->Type() == Storage::eSessionStorage) {
|
||||
storage = GetSessionStorage(aRv);
|
||||
} else {
|
||||
|
@ -5844,6 +5881,12 @@ nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
|
|||
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_IF(storageArea, storage->IsForkOf(storageArea));
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace IPC {
|
|||
template<>
|
||||
struct ParamTraits<nsContentUtils::StorageAccess> :
|
||||
public ContiguousEnumSerializer<nsContentUtils::StorageAccess,
|
||||
nsContentUtils::StorageAccess::eDeny,
|
||||
nsContentUtils::StorageAccess::ePartitionedOrDeny,
|
||||
nsContentUtils::StorageAccess::eNumValues>
|
||||
{};
|
||||
} // namespace IPC
|
||||
|
|
|
@ -319,7 +319,7 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindowInner* aWindow,
|
|||
// 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
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
~SessionStorage();
|
||||
|
||||
bool ProcessUsageDelta(int64_t aDelta);
|
||||
|
||||
void
|
||||
BroadcastChangeNotification(const nsAString& aKey,
|
||||
const nsAString& aOldValue,
|
||||
|
|
|
@ -54,7 +54,7 @@ Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal)
|
|||
nsContentUtils::StorageAccess access =
|
||||
nsContentUtils::StorageAllowedForPrincipal(Principal());
|
||||
|
||||
if (access == nsContentUtils::StorageAccess::eDeny) {
|
||||
if (access <= nsContentUtils::StorageAccess::eDeny) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
enum StorageType {
|
||||
eSessionStorage,
|
||||
eLocalStorage,
|
||||
ePartitionedLocalStorage,
|
||||
};
|
||||
|
||||
virtual StorageType Type() const = 0;
|
||||
|
|
|
@ -10,6 +10,7 @@ with Files("**"):
|
|||
EXPORTS.mozilla.dom += [
|
||||
'LocalStorage.h',
|
||||
'LocalStorageManager.h',
|
||||
'PartitionedLocalStorage.h',
|
||||
'SessionStorageManager.h',
|
||||
'Storage.h',
|
||||
'StorageActivityService.h',
|
||||
|
@ -23,6 +24,7 @@ UNIFIED_SOURCES += [
|
|||
'LocalStorage.cpp',
|
||||
'LocalStorageCache.cpp',
|
||||
'LocalStorageManager.cpp',
|
||||
'PartitionedLocalStorage.cpp',
|
||||
'SessionStorage.cpp',
|
||||
'SessionStorageCache.cpp',
|
||||
'SessionStorageManager.cpp',
|
||||
|
|
|
@ -5860,16 +5860,6 @@ GCRuntime::sweepJitDataOnMainThread(FreeOp* fop)
|
|||
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
|
||||
// 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 ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
|
||||
|
|
|
@ -102,11 +102,11 @@ BEGIN_TEST(testGCUID)
|
|||
}
|
||||
}
|
||||
vec.clear();
|
||||
MinimizeHeap(cx);
|
||||
|
||||
// Grab the last object in the vector as our object of interest.
|
||||
obj = vec2.back();
|
||||
CHECK(obj);
|
||||
CHECK(!js::gc::IsInsideNursery(obj));
|
||||
tenuredAddr = uintptr_t(obj.get());
|
||||
CHECK(obj->zone()->getOrCreateUniqueId(obj, &uid));
|
||||
|
||||
|
@ -114,7 +114,11 @@ BEGIN_TEST(testGCUID)
|
|||
// the new tenured heap location.
|
||||
JS::PrepareForFullGC(cx);
|
||||
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(obj->zone()->hasUniqueId(obj));
|
||||
CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
|
||||
|
|
|
@ -1598,8 +1598,7 @@ class DebugEnvironmentProxyHandler : public BaseProxyHandler
|
|||
CallObject& callobj = env->as<CallObject>();
|
||||
RootedFunction fun(cx, &callobj.callee());
|
||||
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
|
||||
AutoKeepTypeScripts keepTypes(cx);
|
||||
if (!script->ensureHasTypes(cx, keepTypes) || !script->ensureHasAnalyzedArgsUsage(cx)) {
|
||||
if (!script->ensureHasAnalyzedArgsUsage(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -575,6 +575,19 @@ class JSFunction : public js::NativeObject
|
|||
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
|
||||
// compilation. Such JSFunctions are only reachable via GC iteration and
|
||||
// not from script.
|
||||
|
|
|
@ -521,14 +521,7 @@ ObjectGroup::defaultNewGroup(JSContext* cx, const Class* clasp,
|
|||
if (associated->is<JSFunction>()) {
|
||||
|
||||
// Canonicalize new functions to use the original one associated with its script.
|
||||
JSFunction* fun = &associated->as<JSFunction>();
|
||||
if (fun->hasScript()) {
|
||||
associated = fun->nonLazyScript()->functionNonDelazifying();
|
||||
} else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) {
|
||||
associated = fun->lazyScript()->functionNonDelazifying();
|
||||
} else {
|
||||
associated = nullptr;
|
||||
}
|
||||
associated = associated->as<JSFunction>().maybeCanonicalFunction();
|
||||
|
||||
// If we have previously cleared the 'new' script information for this
|
||||
// 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->maybeUnboxedLayout(sweep));
|
||||
|
||||
// rollbackPartiallyInitializedObjects expects function_ to be
|
||||
// canonicalized.
|
||||
MOZ_ASSERT(fun->maybeCanonicalFunction() == fun);
|
||||
|
||||
if (group->unknownProperties(sweep)) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -7926,74 +7926,6 @@ GetDocumentURIToCompareWithBlacklist(PresShell& aPresShell)
|
|||
}
|
||||
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
|
||||
|
||||
nsresult
|
||||
|
@ -8039,10 +7971,10 @@ PresShell::DispatchEventToDOM(WidgetEvent* aEvent,
|
|||
mInitializedWithKeyPressEventDispatchingBlacklist = true;
|
||||
nsCOMPtr<nsIURI> uri = GetDocumentURIToCompareWithBlacklist(*this);
|
||||
mForceDispatchKeyPressEventsForNonPrintableKeys =
|
||||
IsURIInBlacklistPref(uri,
|
||||
nsContentUtils::IsURIInPrefList(uri,
|
||||
"dom.keyboardevent.keypress.hack.dispatch_non_printable_keys");
|
||||
mForceUseLegacyKeyCodeAndCharCodeValues =
|
||||
IsURIInBlacklistPref(uri,
|
||||
nsContentUtils::IsURIInPrefList(uri,
|
||||
"dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode");
|
||||
}
|
||||
if (mForceDispatchKeyPressEventsForNonPrintableKeys) {
|
||||
|
|
|
@ -1363,6 +1363,9 @@ pref("content.sink.pending_event_mode", 0);
|
|||
// 3 = openAbused
|
||||
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,
|
||||
// by overloading the main process as popups get blocked and when
|
||||
// users try to restore all popups, which is the most visible
|
||||
|
|
|
@ -60,3 +60,6 @@ skip-if = serviceworker_e10s
|
|||
[browser_storageAccessWithHeuristics.js]
|
||||
[browser_allowPermissionForTracker.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>
|
Загрузка…
Ссылка в новой задаче