Bug 1766582 - Expose last user gesture timestamp; r=sgalich,smaug

Differential Revision: https://phabricator.services.mozilla.com/D144590
This commit is contained in:
Edgar Chen 2022-04-27 08:26:56 +00:00
parent 102a6112e6
commit b838774cf3
8 changed files with 95 additions and 49 deletions

View File

@ -340,10 +340,11 @@ void WindowContext::DidSet(FieldIndex<IDX_UserActivationState>) {
USER_ACTIVATION_LOG(
"Set user gesture start time for %s browsing context 0x%08" PRIx64,
XRE_IsParentProcess() ? "Parent" : "Child", Id());
mUserGestureStart =
(GetUserActivationState() == UserActivation::State::FullActivated)
? TimeStamp::Now()
: TimeStamp();
if (GetUserActivationState() == UserActivation::State::FullActivated) {
mUserGestureStart = TimeStamp::Now();
} else if (GetUserActivationState() == UserActivation::State::None) {
mUserGestureStart = TimeStamp();
}
}
}
@ -459,13 +460,26 @@ bool WindowContext::HasBeenUserGestureActivated() {
return GetUserActivationState() != UserActivation::State::None;
}
DOMHighResTimeStamp WindowContext::LastUserGestureTimeStamp() {
MOZ_ASSERT(IsInProcess());
if (nsGlobalWindowInner* innerWindow = GetInnerWindow()) {
if (Performance* perf = innerWindow->GetPerformance()) {
return perf->GetDOMTiming()->TimeStampToDOMHighRes(mUserGestureStart);
}
}
NS_WARNING(
"Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
return 0;
}
bool WindowContext::HasValidTransientUserGestureActivation() {
MOZ_ASSERT(IsInProcess());
if (GetUserActivationState() != UserActivation::State::FullActivated) {
MOZ_ASSERT(mUserGestureStart.IsNull(),
"mUserGestureStart should be null if the document hasn't ever "
"been activated by user gesture");
// mUserGestureStart should be null if the document hasn't ever been
// activated by user gesture
MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None,
mUserGestureStart.IsNull());
return false;
}

View File

@ -13,6 +13,7 @@
#include "mozilla/dom/MaybeDiscarded.h"
#include "mozilla/dom/SyncedContext.h"
#include "mozilla/dom/UserActivation.h"
#include "nsDOMNavigationTiming.h"
#include "nsILoadInfo.h"
#include "nsWrapperCache.h"
@ -188,6 +189,10 @@ class WindowContext : public nsISupports, public nsWrapperCache {
// out.
bool HasValidTransientUserGestureActivation();
// Reture timestamp of last user gesture in milliseconds relative to
// navigation start timestamp.
DOMHighResTimeStamp LastUserGestureTimeStamp();
// Return true if the corresponding window has valid transient user gesture
// activation and the transient user gesture activation had been consumed
// successfully.

View File

@ -16261,6 +16261,13 @@ bool Document::HasBeenUserGestureActivated() {
return wc && wc->HasBeenUserGestureActivated();
}
DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
if (RefPtr<WindowContext> wc = GetWindowContext()) {
return wc->LastUserGestureTimeStamp();
}
return 0;
}
void Document::ClearUserGestureActivation() {
if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
bc = bc->Top();

View File

@ -3669,6 +3669,10 @@ class Document : public nsINode,
// document in the document tree.
bool HasBeenUserGestureActivated();
// Reture timestamp of last user gesture in milliseconds relative to
// navigation start timestamp.
DOMHighResTimeStamp LastUserGestureTimeStamp();
// Return true if there is transient user gesture activation and it hasn't yet
// timed out.
bool HasValidTransientUserGestureActivation() const;

View File

@ -12,7 +12,8 @@ onmessage = function(e) {
if (e.data === "get") {
parent.postMessage({
isActivated: SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
hasBeenActivated: SpecialPowers.wrap(document).hasBeenUserGestureActivated
hasBeenActivated: SpecialPowers.wrap(document).hasBeenUserGestureActivated,
lastActivationTimestamp: SpecialPowers.wrap(document).lastUserGestureTimeStamp,
}, "*");
}
};

View File

@ -22,25 +22,32 @@ function waitForEvent(aTarget, aEvent, aCallback) {
let [iframe0, iframe1] = document.querySelectorAll("iframe");
function doCheck(aDocument, aName, aHasBeenUserGestureActivated) {
is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
aHasBeenUserGestureActivated,
`check has-been-user-activated on the ${aName}`);
if (aHasBeenUserGestureActivated) {
ok(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp > 0,
`check last-user-gesture-timestamp on the ${aName}`);
} else {
is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp, 0,
`check last-user-gesture-timestamp on the ${aName}`);
}
}
add_task(async function checkInitialStatus() {
ok(!SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on top-level document");
ok(!SpecialPowers.wrap(frames[0].document).hasBeenUserGestureActivated,
"check has-been-user-activated on first iframe");
ok(!SpecialPowers.wrap(frames[1].document).hasBeenUserGestureActivated,
"check has-been-user-activated on second iframe");
doCheck(document, "top-level document", false);
doCheck(frames[0].document, "first iframe", false);
doCheck(frames[1].document, "second iframe", false);
});
add_task(async function triggerUserActivation() {
// Trigger user activation on the first iframe.
SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
// We should also propagate to all the ancestors.
ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on the top-level document");
ok(SpecialPowers.wrap(frames[0].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the first iframe");
ok(!SpecialPowers.wrap(frames[1].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the second iframe");
doCheck(document, "top-level document", true);
doCheck(frames[0].document, "first iframe", true);
doCheck(frames[1].document, "second iframe", false);
});
add_task(async function iframeNavigation() {
@ -48,26 +55,22 @@ add_task(async function iframeNavigation() {
await waitForEvent(frames[0].frameElement, "load", () => {});
// We should reset the flag on iframe that navigates away from current page,
// but the flag on its ancestor isn't changed.
ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on the top-level document");
ok(!SpecialPowers.wrap(frames[0].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the first iframe");
ok(!SpecialPowers.wrap(frames[1].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the second iframe");
doCheck(document, "top-level document", true);
doCheck(frames[0].document, "first iframe", false);
doCheck(frames[1].document, "second iframe", false);
});
add_task(async function triggerUserActivationOnCrossOriginFrame() {
// Reset the activation flag.
SpecialPowers.wrap(document).clearUserGestureActivation();
doCheck(document, "top-level document", false);
// load cross-origin test page on iframe.
frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_user_activated.html";
await waitForEvent(window, "message", (event) => {
if (event.data === "done") {
ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on the top-level document");
ok(!SpecialPowers.wrap(frames[1].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the second iframe");
doCheck(document, "top-level document", true);
doCheck(frames[1].document, "second iframe", false);
} else {
ok(false, "receive unexpected message: " + event.data);
}
@ -88,15 +91,14 @@ add_task(async function propagateToSameOriginConnectedSubframe() {
// Trigger user activation on top-level document.
SpecialPowers.wrap(document).notifyUserGestureActivation();
ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on the top-level document");
ok(SpecialPowers.wrap(iframe1.contentDocument).hasBeenUserGestureActivated,
"check has-been-user-activated on the second iframe");
doCheck(document, "top-level document", true);
doCheck(iframe1.contentDocument, "second iframe", true);
iframe0.contentWindow.postMessage("get", "*");
await waitForEvent(window, "message", (event) => {
if (typeof event.data === "object") {
ok(!event.data.hasBeenActivated, "check has-been-user-activated on the first iframe");
is(event.data.lastActivationTimestamp, 0, "check last-user-gesture-timestamp on the first iframe");
} else {
ok(false, "receive unexpected message: " + event.data);
}

View File

@ -24,11 +24,30 @@ function waitForEvent(aTarget, aEvent, aCallback) {
});
}
function doCheck(aDocument, aName, aHasBeenUserGestureActivated,
aHasValidTransientUserGestureActivation,
aLastUserGestureTimeStamp) {
is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
aHasBeenUserGestureActivated,
`check has-been-user-activated on the ${aName}`);
is(SpecialPowers.wrap(aDocument).hasValidTransientUserGestureActivation,
aHasValidTransientUserGestureActivation,
`check has-valid-transient-user-activation on the ${aName}`);
is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp,
aLastUserGestureTimeStamp,
`check last-user-gesture-timestamp on the ${aName}`);
}
add_task(async function checkInitialStatus() {
doCheck(document, "top-level document", false, false, 0);
ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
"consume transient-user-activation on top-level document");
doCheck(frames[0].document, "first iframe", false, false, 0);
ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
"consume transient-user-activation on first iframe");
doCheck(frames[1].document, "second iframe", false, false, 0);
ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
"consume transient-user-activation on second iframe");
});
@ -36,6 +55,8 @@ add_task(async function checkInitialStatus() {
add_task(async function consumeTransientUserActivation() {
// Trigger user activation on the first iframe.
SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
let lastTimeStampTop = SpecialPowers.wrap(document).lastUserGestureTimeStamp;
let lastTimeStampFirst = SpecialPowers.wrap(frames[0].document).lastUserGestureTimeStamp;
// Try to consume transient user activation.
ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
@ -46,21 +67,11 @@ add_task(async function consumeTransientUserActivation() {
ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
"consume transient-user-activation on top-level document");
// Check has-valid-transient-user-activation
ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
"check has-valid-transient-user-activation on the top-level document");
ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
"check has-valid-transient-user-activation on the first iframe");
ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
"check has-valid-transient-user-activation on the second iframe");
// Should not affect has-been-user-activated
ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
"check has-been-user-activated on the top-level document");
ok(SpecialPowers.wrap(frames[0].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the first iframe");
ok(!SpecialPowers.wrap(frames[1].document).hasBeenUserGestureActivated,
"check has-been-user-activated on the second iframe");
// Check has-valid-transient-user-activation and should not affect
// has-been-user-activated
doCheck(document, "top-level document", true, false, lastTimeStampTop);
doCheck(frames[0].document, "first iframe", true, false, lastTimeStampFirst);
doCheck(frames[1].document, "second iframe", false, false, 0);
});
add_task(async function consumeTransientUserActivationTimeout() {

View File

@ -585,6 +585,8 @@ partial interface Document {
[ChromeOnly]
readonly attribute boolean hasValidTransientUserGestureActivation;
[ChromeOnly]
readonly attribute DOMHighResTimeStamp lastUserGestureTimeStamp;
[ChromeOnly]
boolean consumeTransientUserGestureActivation();
};