Merge inbound to mozilla-central. a=merge

This commit is contained in:
Noemi Erli 2018-11-13 18:27:16 +02:00
Родитель 6ca25ecbe6 37f442272e
Коммит 6e9e399066
23 изменённых файлов: 1049 добавлений и 116 удалений

Просмотреть файл

@ -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>