diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index aa431cf0d0f0f..9d002caee4052 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -16734,36 +16734,46 @@ BrowsingContext* Document::GetBrowsingContext() const { } void Document::NotifyUserGestureActivation() { - if (RefPtr bc = GetBrowsingContext()) { - bc->PreOrderWalk([&](BrowsingContext* aBC) { - WindowContext* windowContext = aBC->GetCurrentWindowContext(); - if (!windowContext) { - return; - } + // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification + // 1. "Assert: document is fully active." + RefPtr currentBC = GetBrowsingContext(); + if (!currentBC) { + return; + } - nsIDocShell* docShell = aBC->GetDocShell(); - if (!docShell) { - return; - } + RefPtr currentWC = GetWindowContext(); + if (!currentWC) { + return; + } - Document* document = docShell->GetDocument(); - if (!document) { - 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(); - // XXXedgar we probably could just check `IsInProcess()` after fission - // enable. - if (NodePrincipal()->Equals(document->NodePrincipal())) { - windowContext->NotifyUserGestureActivation(); - } - }); + // 3. "...windows with the active window of each of document's ancestor + // navigables." + for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) { + wc->NotifyUserGestureActivation(); + } - for (bc = bc->GetParent(); bc; bc = bc->GetParent()) { - if (WindowContext* windowContext = bc->GetCurrentWindowContext()) { - windowContext->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() { diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index b2b57f29f085e..b5a6b751204fd 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -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 Navigator::UserActivation() { + if (!mUserActivation) { + mUserActivation = new dom::UserActivation(GetWindow()); + } + return do_AddRef(mUserActivation); +} + } // namespace mozilla::dom diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index f878c11dff3d4..c43b02b530267 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -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 UserActivation(); + private: void ValidateShareData(const ShareData& aData, ErrorResult& aRv); RefPtr mMediaKeySystemAccessManager; @@ -296,6 +299,7 @@ class Navigator final : public nsISupports, public nsWrapperCache { RefPtr mWebGpu; RefPtr mSharePromise; // Web Share API related RefPtr mLocks; + RefPtr mUserActivation; }; } // namespace mozilla::dom diff --git a/dom/base/UserActivation.cpp b/dom/base/UserActivation.cpp index 95dc5fc7d0ecb..e783166de45c9 100644 --- a/dom/base/UserActivation.cpp +++ b/dom/base/UserActivation.cpp @@ -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 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 diff --git a/dom/base/UserActivation.h b/dom/base/UserActivation.h index 75181f1b72813..c067bbc8c3f00 100644 --- a/dom/base/UserActivation.h +++ b/dom/base/UserActivation.h @@ -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 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 mWindow; }; /** @@ -83,4 +106,4 @@ class MOZ_RAII AutoHandlingUserInputStatePusher final { } // namespace mozilla::dom -#endif // mozilla_dom_UserAcitvation_h +#endif // mozilla_dom_UserActivation_h diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js index 3deef2431205d..40d418d4b1b6f 100644 --- a/dom/tests/mochitest/general/test_interfaces.js +++ b/dom/tests/mochitest/general/test_interfaces.js @@ -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 }, diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 8909e163272cd..993d9504dd81d 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -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; +}; diff --git a/dom/webidl/UserActivation.webidl b/dom/webidl/UserActivation.webidl new file mode 100644 index 0000000000000..f8f79c38721f4 --- /dev/null +++ b/dom/webidl/UserActivation.webidl @@ -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; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 01dd323538484..d347c64ee68e2 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -981,6 +981,7 @@ WEBIDL_FILES = [ "UIEvent.webidl", "URL.webidl", "URLSearchParams.webidl", + "UserActivation.webidl", "ValidityState.webidl", "VideoColorSpace.webidl", "VideoDecoder.webidl", diff --git a/testing/web-platform/meta/fullscreen/api/element-ready-check-containing-iframe.html.ini b/testing/web-platform/meta/fullscreen/api/element-ready-check-containing-iframe.html.ini deleted file mode 100644 index 708250f6186ec..0000000000000 --- a/testing/web-platform/meta/fullscreen/api/element-ready-check-containing-iframe.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[element-ready-check-containing-iframe.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [Element ready check for containing iframe] - expected: FAIL diff --git a/testing/web-platform/meta/fullscreen/api/element-request-fullscreen-consume-user-activation.html.ini b/testing/web-platform/meta/fullscreen/api/element-request-fullscreen-consume-user-activation.html.ini deleted file mode 100644 index c7dbe40b951fa..0000000000000 --- a/testing/web-platform/meta/fullscreen/api/element-request-fullscreen-consume-user-activation.html.ini +++ /dev/null @@ -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 diff --git a/testing/web-platform/meta/html/dom/idlharness.https.html.ini b/testing/web-platform/meta/html/dom/idlharness.https.html.ini index d14095ff9fb09..5cce4eb7740a9 100644 --- a/testing/web-platform/meta/html/dom/idlharness.https.html.ini +++ b/testing/web-platform/meta/html/dom/idlharness.https.html.ini @@ -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 diff --git a/testing/web-platform/meta/html/user-activation/activation-trigger-pointerevent.html.ini b/testing/web-platform/meta/html/user-activation/activation-trigger-pointerevent.html.ini index 2cb104304330b..73a1d8e015d15 100644 --- a/testing/web-platform/meta/html/user-activation/activation-trigger-pointerevent.html.ini +++ b/testing/web-platform/meta/html/user-activation/activation-trigger-pointerevent.html.ini @@ -1,12 +1,12 @@ [activation-trigger-pointerevent.html?touch] + # A webdriver bug (Bug 1856991) does not emit touch click events internally as expected + bug: 1856991 expected: TIMEOUT [Activation through touch pointerevent click] expected: 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] diff --git a/testing/web-platform/meta/html/user-activation/chained-setTimeout.html.ini b/testing/web-platform/meta/html/user-activation/chained-setTimeout.html.ini deleted file mode 100644 index 7c358af275d91..0000000000000 --- a/testing/web-platform/meta/html/user-activation/chained-setTimeout.html.ini +++ /dev/null @@ -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 diff --git a/testing/web-platform/meta/html/user-activation/detached-iframe.html.ini b/testing/web-platform/meta/html/user-activation/detached-iframe.html.ini deleted file mode 100644 index a8381e2102c42..0000000000000 --- a/testing/web-platform/meta/html/user-activation/detached-iframe.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[detached-iframe.html] - [navigator.userActivation retains state even if global is removed] - expected: FAIL diff --git a/testing/web-platform/meta/html/user-activation/message-event-init.tentative.html.ini b/testing/web-platform/meta/html/user-activation/message-event-init.tentative.html.ini index 666902f775322..ba4a18c537f31 100644 --- a/testing/web-platform/meta/html/user-activation/message-event-init.tentative.html.ini +++ b/testing/web-platform/meta/html/user-activation/message-event-init.tentative.html.ini @@ -2,3 +2,5 @@ [MessageEventInit user activation not set] expected: FAIL + [MessageEventInit user activation set] + expected: FAIL diff --git a/testing/web-platform/meta/html/user-activation/navigation-state-reset-crossorigin.sub.html.ini b/testing/web-platform/meta/html/user-activation/navigation-state-reset-crossorigin.sub.html.ini index 05e3ccca4dad1..3392401f38a6c 100644 --- a/testing/web-platform/meta/html/user-activation/navigation-state-reset-crossorigin.sub.html.ini +++ b/testing/web-platform/meta/html/user-activation/navigation-state-reset-crossorigin.sub.html.ini @@ -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 diff --git a/testing/web-platform/meta/html/user-activation/navigation-state-reset-sameorigin.html.ini b/testing/web-platform/meta/html/user-activation/navigation-state-reset-sameorigin.html.ini deleted file mode 100644 index acf17095068c1..0000000000000 --- a/testing/web-platform/meta/html/user-activation/navigation-state-reset-sameorigin.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[navigation-state-reset-sameorigin.html] - expected: TIMEOUT - [Post-navigation state reset.] - expected: TIMEOUT diff --git a/testing/web-platform/meta/html/user-activation/no-activation-thru-escape-key.html.ini b/testing/web-platform/meta/html/user-activation/no-activation-thru-escape-key.html.ini deleted file mode 100644 index bb470f5ac9b09..0000000000000 --- a/testing/web-platform/meta/html/user-activation/no-activation-thru-escape-key.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[no-activation-thru-escape-key.html] - ['Escape' key doesn't activate a page.] - expected: FAIL diff --git a/testing/web-platform/meta/html/user-activation/propagation-crossorigin.sub.html.ini b/testing/web-platform/meta/html/user-activation/propagation-crossorigin.sub.html.ini index 0a9c1e15b7835..d9ae52d61d72d 100644 --- a/testing/web-platform/meta/html/user-activation/propagation-crossorigin.sub.html.ini +++ b/testing/web-platform/meta/html/user-activation/propagation-crossorigin.sub.html.ini @@ -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 diff --git a/testing/web-platform/meta/html/user-activation/propagation-same-and-cross-origin.sub.html.ini b/testing/web-platform/meta/html/user-activation/propagation-same-and-cross-origin.sub.html.ini index d3673a710b16a..73b00b0ef1c38 100644 --- a/testing/web-platform/meta/html/user-activation/propagation-same-and-cross-origin.sub.html.ini +++ b/testing/web-platform/meta/html/user-activation/propagation-same-and-cross-origin.sub.html.ini @@ -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 diff --git a/testing/web-platform/meta/html/user-activation/propagation-sameorigin.html.ini b/testing/web-platform/meta/html/user-activation/propagation-sameorigin.html.ini deleted file mode 100644 index 5dedaba3415d9..0000000000000 --- a/testing/web-platform/meta/html/user-activation/propagation-sameorigin.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[propagation-sameorigin.html] - expected: TIMEOUT - [Propagation test] - expected: NOTRUN diff --git a/testing/web-platform/meta/html/user-activation/user-activation-interface.html.ini b/testing/web-platform/meta/html/user-activation/user-activation-interface.html.ini deleted file mode 100644 index 7c3e72e5e5a5c..0000000000000 --- a/testing/web-platform/meta/html/user-activation/user-activation-interface.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[user-activation-interface.html] - [navigator.userActivation shows correct states before/after a click] - expected: FAIL diff --git a/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html b/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html index 61debbf948a3e..94d15c2d415e3 100644 --- a/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html +++ b/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html @@ -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.