зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1491835 - Store User-Interaction for AntiTracking purposes - part 4 - update permissions after X seconds, r=ehsan
This commit is contained in:
Родитель
a7bdca9758
Коммит
25f9ad890f
|
@ -1495,6 +1495,7 @@ nsIDocument::nsIDocument()
|
|||
mChildDocumentUseCounters(0),
|
||||
mNotifiedPageForUseCounter(0),
|
||||
mUserHasInteracted(false),
|
||||
mHasUserInteractionTimerScheduled(false),
|
||||
mUserGestureActivated(false),
|
||||
mStackRefCnt(0),
|
||||
mUpdateNestLevel(0),
|
||||
|
@ -12626,6 +12627,10 @@ nsIDocument::SetUserHasInteracted()
|
|||
{
|
||||
MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
|
||||
("Document %p has been interacted by user.", this));
|
||||
|
||||
// We maybe need to update the user-interaction permission.
|
||||
MaybeStoreUserInteractionAsPermission();
|
||||
|
||||
if (mUserHasInteracted) {
|
||||
return;
|
||||
}
|
||||
|
@ -12638,7 +12643,6 @@ nsIDocument::SetUserHasInteracted()
|
|||
}
|
||||
|
||||
MaybeAllowStorageForOpener();
|
||||
MaybeStoreUserInteractionAsPermission();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -12758,13 +12762,190 @@ nsIDocument::MaybeAllowStorageForOpener()
|
|||
AntiTrackingCommon::eHeuristic);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Documents can stay alive for days. We don't want to update the permission
|
||||
// value at any user-interaction, and, using a timer triggered any X seconds
|
||||
// should be good enough. 'X' is taken from
|
||||
// privacy.userInteraction.document.interval pref.
|
||||
// We also want to store the user-interaction before shutting down, and, for
|
||||
// this reason, this class implements nsIAsyncShutdownBlocker interface.
|
||||
class UserIntractionTimer final : public Runnable
|
||||
, public nsITimerCallback
|
||||
, public nsIAsyncShutdownBlocker
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
explicit UserIntractionTimer(nsIDocument* aDocument)
|
||||
: Runnable("UserIntractionTimer")
|
||||
, mPrincipal(aDocument->NodePrincipal())
|
||||
, mDocument(do_GetWeakReference(aDocument))
|
||||
{
|
||||
static int32_t userInteractionTimerId = 0;
|
||||
// Blocker names must be unique. Let's create it now because when needed,
|
||||
// the document could be already gone.
|
||||
mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
|
||||
++userInteractionTimerId, aDocument);
|
||||
}
|
||||
|
||||
// Runnable interface
|
||||
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
{
|
||||
uint32_t interval =
|
||||
StaticPrefs::privacy_userInteraction_document_interval();
|
||||
if (!interval) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<UserIntractionTimer> self = this;
|
||||
auto raii = MakeScopeExit([self] {
|
||||
self->CancelTimerAndStoreUserInteraction();
|
||||
});
|
||||
|
||||
nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer),
|
||||
this, interval * 1000,
|
||||
nsITimer::TYPE_ONE_SHOT,
|
||||
SystemGroup::EventTargetFor(TaskCategory::Other));
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
|
||||
nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
|
||||
NS_ENSURE_TRUE(!!phase, NS_OK);
|
||||
|
||||
rv = phase->AddBlocker(this, NS_LITERAL_STRING(__FILE__), __LINE__,
|
||||
NS_LITERAL_STRING("UserIntractionTimer shutdown"));
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
|
||||
raii.release();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsITimerCallback interface
|
||||
|
||||
NS_IMETHOD
|
||||
Notify(nsITimer* aTimer) override
|
||||
{
|
||||
StoreUserInteraction();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
||||
using nsINamed::GetName;
|
||||
#endif
|
||||
|
||||
// nsIAsyncShutdownBlocker interface
|
||||
|
||||
NS_IMETHOD
|
||||
GetName(nsAString& aName) override
|
||||
{
|
||||
aName = mBlockerName;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
BlockShutdown(nsIAsyncShutdownClient* aClient) override
|
||||
{
|
||||
CancelTimerAndStoreUserInteraction();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
GetState(nsIPropertyBag**) override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~UserIntractionTimer() = default;
|
||||
|
||||
void
|
||||
StoreUserInteraction()
|
||||
{
|
||||
// Remove the shutting down blocker
|
||||
nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
|
||||
if (phase) {
|
||||
phase->RemoveBlocker(this);
|
||||
}
|
||||
|
||||
// If the document is not gone, let's reset its timer flag.
|
||||
nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocument);
|
||||
if (document) {
|
||||
AntiTrackingCommon::StoreUserInteractionFor(mPrincipal);
|
||||
document->ResetUserInteractionTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CancelTimerAndStoreUserInteraction()
|
||||
{
|
||||
if (mTimer) {
|
||||
mTimer->Cancel();
|
||||
mTimer = nullptr;
|
||||
}
|
||||
|
||||
StoreUserInteraction();
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIAsyncShutdownClient>
|
||||
GetShutdownPhase()
|
||||
{
|
||||
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
|
||||
NS_ENSURE_TRUE(!!svc, nullptr);
|
||||
|
||||
nsCOMPtr<nsIAsyncShutdownClient> phase;
|
||||
nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return phase.forget();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||
nsWeakPtr mDocument;
|
||||
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
|
||||
nsString mBlockerName;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(UserIntractionTimer, Runnable, nsITimerCallback,
|
||||
nsIAsyncShutdownBlocker)
|
||||
|
||||
} // anonymous
|
||||
|
||||
void
|
||||
nsIDocument::MaybeStoreUserInteractionAsPermission()
|
||||
{
|
||||
// We care about user-interaction stored only for top-level documents.
|
||||
if (!mParentDocument) {
|
||||
AntiTrackingCommon::StoreUserInteractionFor(NodePrincipal());
|
||||
if (GetSameTypeParentDocument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mUserHasInteracted) {
|
||||
// First interaction, let's store this info now.
|
||||
AntiTrackingCommon::StoreUserInteractionFor(NodePrincipal());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHasUserInteractionTimerScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> task = new UserIntractionTimer(this);
|
||||
nsresult rv = NS_IdleDispatchToCurrentThread(task.forget(), 2500);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This value will be reset by the timer.
|
||||
mHasUserInteractionTimerScheduled = true;
|
||||
}
|
||||
|
||||
void
|
||||
nsIDocument::ResetUserInteractionTimer()
|
||||
{
|
||||
mHasUserInteractionTimerScheduled = false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -3505,6 +3505,7 @@ public:
|
|||
{
|
||||
return mUserHasInteracted;
|
||||
}
|
||||
void ResetUserInteractionTimer();
|
||||
|
||||
// This should be called when this document receives events which are likely
|
||||
// to be user interaction with the document, rather than the byproduct of
|
||||
|
@ -4497,6 +4498,11 @@ protected:
|
|||
// Whether the user has interacted with the document or not:
|
||||
bool mUserHasInteracted;
|
||||
|
||||
// We constantly update the user-interaction anti-tracking permission at any
|
||||
// user-interaction using a timer. This boolean value is set to true when this
|
||||
// timer is scheduled.
|
||||
bool mHasUserInteractionTimerScheduled;
|
||||
|
||||
// Whether the user has interacted with the document via a restricted
|
||||
// set of gestures which are likely to be interaction with the document,
|
||||
// and not events that are fired as a byproduct of the user interacting
|
||||
|
|
|
@ -504,7 +504,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
aEvent->mMessage == eWheel || aEvent->mMessage == eTouchEnd ||
|
||||
aEvent->mMessage == ePointerUp)) {
|
||||
nsIDocument* doc = node->OwnerDoc();
|
||||
while (doc && !doc->UserHasInteracted()) {
|
||||
while (doc) {
|
||||
doc->SetUserHasInteracted();
|
||||
doc = nsContentUtils::IsChildOfSameType(doc) ?
|
||||
doc->GetParentDocument() : nullptr;
|
||||
|
|
|
@ -1628,6 +1628,13 @@ VARCACHE_PREF(
|
|||
uint32_t, 2592000 // 30 days (in seconds)
|
||||
)
|
||||
|
||||
// Anti-tracking user-interaction document interval
|
||||
VARCACHE_PREF(
|
||||
"privacy.userInteraction.document.interval",
|
||||
privacy_userInteraction_document_interval,
|
||||
uint32_t, 1800 // 30 minutes (in seconds)
|
||||
)
|
||||
|
||||
// Anti-fingerprinting, disabled by default
|
||||
VARCACHE_PREF(
|
||||
"privacy.resistFingerprinting",
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
add_task(async function() {
|
||||
|
@ -5,6 +10,7 @@ add_task(async function() {
|
|||
|
||||
await SpecialPowers.flushPrefEnv();
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["privacy.userInteraction.document.interval", 1],
|
||||
["browser.contentblocking.enabled", true],
|
||||
["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
|
||||
["privacy.trackingprotection.enabled", false],
|
||||
|
@ -22,8 +28,8 @@ add_task(async function() {
|
|||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
let uri = Services.io.newURI(TEST_DOMAIN);
|
||||
is (Services.perms.testPermission(uri, "storageAccessAPI"), Services.perms.UNKNOWN_ACTION,
|
||||
"Before user-interaction we don't have a permission");
|
||||
is(Services.perms.testPermission(uri, "storageAccessAPI"), Services.perms.UNKNOWN_ACTION,
|
||||
"Before user-interaction we don't have a permission");
|
||||
|
||||
let promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
|
||||
let permission = aSubject.QueryInterface(Ci.nsIPermission);
|
||||
|
@ -39,6 +45,56 @@ add_task(async function() {
|
|||
info("Waiting to have a permissions set.");
|
||||
await promise;
|
||||
|
||||
// Let's see if the document is able to update the permission correctly.
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
// Another perm-changed event should be triggered by the timer.
|
||||
promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
|
||||
let permission = aSubject.QueryInterface(Ci.nsIPermission);
|
||||
return permission.type == "storageAccessAPI" &&
|
||||
permission.principal.URI.equals(uri);
|
||||
});
|
||||
|
||||
info("Simulating another user-interaction.");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
content.document.userInteractionForTesting();
|
||||
});
|
||||
|
||||
info("Waiting to have a permissions set.");
|
||||
await promise;
|
||||
}
|
||||
|
||||
// Let's disable the document.interval.
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["privacy.userInteraction.document.interval", 0],
|
||||
]});
|
||||
|
||||
promise = new Promise(resolve => {
|
||||
let id;
|
||||
|
||||
function observer(subject, topic, data) {
|
||||
ok(false, "Notification received!");
|
||||
Services.obs.removeObserver(observer, "perm-changed");
|
||||
clearTimeout(id);
|
||||
resolve();
|
||||
}
|
||||
|
||||
Services.obs.addObserver(observer, "perm-changed");
|
||||
|
||||
id = setTimeout(() => {
|
||||
ok(true, "No notification received!");
|
||||
Services.obs.removeObserver(observer, "perm-changed");
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
info("Simulating another user-interaction.");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
content.document.userInteractionForTesting();
|
||||
});
|
||||
|
||||
info("Waiting to have a permissions set.");
|
||||
await promise;
|
||||
|
||||
info("Removing the tab");
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче