Bug 1791079 - Implement User Activation API r=dom-core,webidl,saschanaz,edgar

Rewrote internal user activation tree (spreading state to other elements)
logic to match HTML spec:
https://html.spec.whatwg.org/multipage/interaction.html#user-activation-processing-model

Added navigator.userActivation API to expose internal user activation.

Also fixed a WPT test to conform to spec (siblings are not activated),
see also spec issue: https://github.com/whatwg/html/issues/9831

Co-authored-by: Tom Schuster <evilpies@gmail.com>

Differential Revision: https://phabricator.services.mozilla.com/D185348
This commit is contained in:
CanadaHonk 2023-10-07 23:12:29 +00:00
Родитель 5553a8b85d
Коммит abbea4195b
23 изменённых файлов: 168 добавлений и 125 удалений

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

@ -16734,36 +16734,46 @@ BrowsingContext* Document::GetBrowsingContext() const {
}
void Document::NotifyUserGestureActivation() {
if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
bc->PreOrderWalk([&](BrowsingContext* aBC) {
WindowContext* windowContext = aBC->GetCurrentWindowContext();
if (!windowContext) {
return;
}
nsIDocShell* docShell = aBC->GetDocShell();
if (!docShell) {
return;
}
Document* document = docShell->GetDocument();
if (!document) {
return;
}
// XXXedgar we probably could just check `IsInProcess()` after fission
// enable.
if (NodePrincipal()->Equals(document->NodePrincipal())) {
windowContext->NotifyUserGestureActivation();
}
});
for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
windowContext->NotifyUserGestureActivation();
}
}
// https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
// 1. "Assert: document is fully active."
RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
if (!currentBC) {
return;
}
RefPtr<WindowContext> currentWC = GetWindowContext();
if (!currentWC) {
return;
}
// 2. "Let windows be « document's relevant global object"
// Instead of assembling a list, we just call notify for wanted windows as we
// find them
currentWC->NotifyUserGestureActivation();
// 3. "...windows with the active window of each of document's ancestor
// navigables."
for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
wc->NotifyUserGestureActivation();
}
// 4. "windows with the active window of each of document's descendant
// navigables, filtered to include only those navigables whose active
// document's origin is same origin with document's origin"
currentBC->PreOrderWalk([&](BrowsingContext* bc) {
WindowContext* wc = bc->GetCurrentWindowContext();
if (!wc) {
return;
}
// Check same-origin as current document
WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
return;
}
wc->NotifyUserGestureActivation();
});
}
bool Document::HasBeenUserGestureActivated() {

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

@ -54,6 +54,7 @@
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/TCPSocket.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/VRDisplayEvent.h"
#include "mozilla/dom/VRServiceTest.h"
@ -159,6 +160,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddonManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebGpu)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUserActivation)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
@ -245,6 +247,8 @@ void Navigator::Invalidate() {
mLocks = nullptr;
}
mUserActivation = nullptr;
mSharePromise = nullptr;
}
@ -2288,4 +2292,11 @@ AutoplayPolicy Navigator::GetAutoplayPolicy(AudioContext& aContext) {
return media::AutoplayPolicy::GetAutoplayPolicy(aContext);
}
already_AddRefed<dom::UserActivation> Navigator::UserActivation() {
if (!mUserActivation) {
mUserActivation = new dom::UserActivation(GetWindow());
}
return do_AddRef(mUserActivation);
}
} // namespace mozilla::dom

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

@ -85,6 +85,7 @@ class XRSystem;
class StorageManager;
class MediaCapabilities;
class MediaSession;
class UserActivation;
struct ShareData;
class WindowGlobalChild;
@ -249,6 +250,8 @@ class Navigator final : public nsISupports, public nsWrapperCache {
AutoplayPolicy GetAutoplayPolicy(HTMLMediaElement& aElement);
AutoplayPolicy GetAutoplayPolicy(AudioContext& aContext);
already_AddRefed<dom::UserActivation> UserActivation();
private:
void ValidateShareData(const ShareData& aData, ErrorResult& aRv);
RefPtr<MediaKeySystemAccessManager> mMediaKeySystemAccessManager;
@ -296,6 +299,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
RefPtr<webgpu::Instance> mWebGpu;
RefPtr<Promise> mSharePromise; // Web Share API related
RefPtr<dom::LockManager> mLocks;
RefPtr<dom::UserActivation> mUserActivation;
};
} // namespace mozilla::dom

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

@ -4,12 +4,50 @@
* 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 "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/UserActivationBinding.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/TextEvents.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(UserActivation, mWindow)
NS_IMPL_CYCLE_COLLECTING_ADDREF(UserActivation)
NS_IMPL_CYCLE_COLLECTING_RELEASE(UserActivation)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UserActivation)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
UserActivation::UserActivation(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow) {}
JSObject* UserActivation::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return UserActivation_Binding::Wrap(aCx, this, aGivenProto);
};
// https://html.spec.whatwg.org/multipage/interaction.html#dom-useractivation-hasbeenactive
bool UserActivation::HasBeenActive() const {
// The hasBeenActive getter steps are to return true if this's relevant global
// object has sticky activation, and false otherwise.
WindowContext* wc = mWindow->GetWindowContext();
return wc && wc->HasBeenUserGestureActivated();
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-useractivation-isactive
bool UserActivation::IsActive() const {
// The isActive getter steps are to return true if this's relevant global
// object has transient activation, and false otherwise.
WindowContext* wc = mWindow->GetWindowContext();
return wc && wc->HasValidTransientUserGestureActivation();
}
namespace {
// The current depth of user and keyboard inputs. sUserInputEventDepth

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

@ -4,16 +4,34 @@
* 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_UserAcitvation_h
#define mozilla_dom_UserAcitvation_h
#ifndef mozilla_dom_UserActivation_h
#define mozilla_dom_UserActivation_h
#include "mozilla/EventForwards.h"
#include "mozilla/TimeStamp.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "nsPIDOMWindow.h"
namespace mozilla::dom {
class UserActivation final {
class UserActivation final : public nsISupports, public nsWrapperCache {
public:
// WebIDL UserActivation
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(UserActivation)
explicit UserActivation(nsPIDOMWindowInner* aWindow);
nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
bool HasBeenActive() const;
bool IsActive() const;
// End of WebIDL UserActivation
enum class State : uint8_t {
// Not activated.
None,
@ -64,6 +82,11 @@ class UserActivation final {
* the epoch.
*/
static TimeStamp LatestUserInputStart();
private:
~UserActivation() = default;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
};
/**
@ -83,4 +106,4 @@ class MOZ_RAII AutoHandlingUserInputStatePusher final {
} // namespace mozilla::dom
#endif // mozilla_dom_UserAcitvation_h
#endif // mozilla_dom_UserActivation_h

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

@ -1407,6 +1407,8 @@ let interfaceNamesInGlobalScope = [
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "URLSearchParams", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "UserActivation", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "UserProximityEvent", insecureContext: true, disabled: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "ValidityState", insecureContext: true },

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

@ -373,3 +373,8 @@ partial interface Navigator {
[Pref="dom.media.autoplay-policy-detection.enabled"]
AutoplayPolicy getAutoplayPolicy(AudioContext context);
};
// https://html.spec.whatwg.org/multipage/interaction.html#the-useractivation-interface
partial interface Navigator {
[SameObject] readonly attribute UserActivation userActivation;
};

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

@ -0,0 +1,14 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*
* The origin of this IDL file is
* https://html.spec.whatwg.org/multipage/interaction.html#the-useractivation-interface
*/
[Exposed=Window]
interface UserActivation {
readonly attribute boolean hasBeenActive;
readonly attribute boolean isActive;
};

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

@ -981,6 +981,7 @@ WEBIDL_FILES = [
"UIEvent.webidl",
"URL.webidl",
"URLSearchParams.webidl",
"UserActivation.webidl",
"ValidityState.webidl",
"VideoColorSpace.webidl",
"VideoDecoder.webidl",

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

@ -1,5 +0,0 @@
[element-request-fullscreen-consume-user-activation.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Element#requestFullscreen() consumes user activation]
expected: FAIL

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

@ -300,36 +300,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
[SVGElement interface: attribute onbeforematch]
expected: FAIL
[UserActivation interface: existence and properties of interface object]
expected: FAIL
[UserActivation interface object length]
expected: FAIL
[UserActivation interface object name]
expected: FAIL
[UserActivation interface: existence and properties of interface prototype object]
expected: FAIL
[UserActivation interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[UserActivation interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[UserActivation interface: attribute hasBeenActive]
expected: FAIL
[UserActivation interface: attribute isActive]
expected: FAIL
[Navigator interface: attribute userActivation]
expected: FAIL
[Navigator interface: window.navigator must inherit property "userActivation" with the proper type]
expected: FAIL
[SVGElement interface: attribute onbeforetoggle]
expected: FAIL

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

@ -1,12 +1,19 @@
[activation-trigger-pointerevent.html?touch]
expected: TIMEOUT
# A webdriver bug (Bug 1856991) does not emit touch click events internally as expected
bug: 1856991
expected:
if os == "android": [OK, TIMEOUT]
if os == "linux": [OK, TIMEOUT]
TIMEOUT
[Activation through touch pointerevent click]
expected: TIMEOUT
expected:
if os == "android": [FAIL, TIMEOUT]
if os == "linux": [FAIL, TIMEOUT]
TIMEOUT
[activation-trigger-pointerevent.html?pen]
# Pen touch type is not supported by webdriver
[Activation through pen pointerevent click]
expected: FAIL
[activation-trigger-pointerevent.html?mouse]

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

@ -1,18 +0,0 @@
[chained-setTimeout.html]
[Call-depth=1: initial activation states are false]
expected: FAIL
[Call-depth=2: initial activation states are false]
expected: FAIL
[Call-depth=3: initial activation states are false]
expected: FAIL
[Call-depth=1: after-click activation states are true]
expected: FAIL
[Call-depth=2: after-click activation states are true]
expected: FAIL
[Call-depth=3: after-click activation states are true]
expected: FAIL

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

@ -1,3 +0,0 @@
[detached-iframe.html]
[navigator.userActivation retains state even if global is removed]
expected: FAIL

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

@ -2,3 +2,5 @@
[MessageEventInit user activation not set]
expected: FAIL
[MessageEventInit user activation set]
expected: FAIL

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

@ -1,4 +1,4 @@
[navigation-state-reset-crossorigin.sub.html]
expected: TIMEOUT
[Post-navigation state reset.]
expected: TIMEOUT
# There is a webdriver bug (Bug 1856989) which breaks cross-process iframe clicks
expected:
if fission: TIMEOUT

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

@ -1,4 +0,0 @@
[navigation-state-reset-sameorigin.html]
expected: TIMEOUT
[Post-navigation state reset.]
expected: TIMEOUT

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

@ -1,3 +0,0 @@
[no-activation-thru-escape-key.html]
['Escape' key doesn't activate a page.]
expected: FAIL

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

@ -1,4 +1,4 @@
[propagation-crossorigin.sub.html]
expected: TIMEOUT
[Propagation test]
expected: NOTRUN
# There is a webdriver bug (Bug 1856989) which breaks cross-process iframe clicks
expected:
if fission: TIMEOUT

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

@ -1,10 +1,4 @@
[propagation-same-and-cross-origin.sub.html]
expected: TIMEOUT
[Check Initial states of user activation are all false]
expected: NOTRUN
[Check that activating a same-origin navigable doesn't activate a cross origin navigable]
expected: NOTRUN
[Clicking on the cross-origin navigable activates parent navigable.]
expected: NOTRUN
# There is a webdriver bug (Bug 1856989) which breaks cross-process iframe clicks
expected:
if fission: TIMEOUT

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

@ -1,4 +0,0 @@
[propagation-sameorigin.html]
expected: TIMEOUT
[Propagation test]
expected: NOTRUN

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

@ -1,3 +0,0 @@
[user-activation-interface.html]
[navigator.userActivation shows correct states before/after a click]
expected: FAIL

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

@ -62,9 +62,11 @@
assert_false(msg.hasBeenActive);
}, "Grandchild frame initial state");
} else if (msg.type == 'child-one-report') {
// Siblings (same or cross origin) should not be activated per spec
// Spec issue discussing: https://github.com/whatwg/html/issues/9831
test(() => {
assert_true(msg.isActive);
assert_true(msg.hasBeenActive);
assert_false(msg.isActive);
assert_false(msg.hasBeenActive);
}, "Child1 frame final state");
} else if (msg.type == 'child-sameorigin-report') {
// This msg was triggered by a user click.