Commit 15534f5d authored by Jeremy Roman's avatar Jeremy Roman Committed by Commit Bot

Portals: Support default click-to-activate.

This adds a default behavior whereby clicking (or equivalent) will cause
the portal to immediately activate, unless the click or DOMActivate
event has preventDefault called by a listener.

This is guarded by a new runtime enabled feature so that this does not
affect the behavior in the current origin trial.

Bug: 1102081
Change-Id: I6a6c2409b0123f95d5068c54a423ac2f29e3e968
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2281308Reviewed-by: default avatarAdithya Srinivasan <adithyas@chromium.org>
Commit-Queue: Jeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#785812}
parent 2b47980e
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/post_message_helper.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/post_message_helper.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_portal_activate_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_portal_activate_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_window_post_message_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_window_post_message_options.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
...@@ -442,6 +443,33 @@ void HTMLPortalElement::RemovedFrom(ContainerNode& node) { ...@@ -442,6 +443,33 @@ void HTMLPortalElement::RemovedFrom(ContainerNode& node) {
} }
void HTMLPortalElement::DefaultEventHandler(Event& event) { void HTMLPortalElement::DefaultEventHandler(Event& event) {
// Support the new behavior whereby clicking (or equivalent operations via
// keyboard and other input modalities) a portal element causes it to activate
// unless prevented.
//
// TODO(jbroman): Ideally we wouldn't need to bring up a script state context
// to do it, but presently it's how errors are reported and also required for
// serialization of the (absent) activation data.
if (RuntimeEnabledFeatures::PortalsDefaultActivationEnabled() &&
event.type() == event_type_names::kDOMActivate) {
if (LocalFrame* frame = GetDocument().GetFrame()) {
ScriptState* script_state = ToScriptStateForMainWorld(frame);
ScriptState::Scope scope(script_state);
ExceptionState exception_state(script_state->GetIsolate(),
ExceptionState::kUnknownContext, nullptr,
nullptr);
activate(ToScriptStateForMainWorld(frame),
MakeGarbageCollected<PortalActivateOptions>(), exception_state);
if (exception_state.HadException()) {
// The exception will be reported as an unhandled promise. This is
// slightly weird, but gets the point across.
ScriptPromise::Reject(script_state, exception_state);
exception_state.ClearException();
}
}
event.SetDefaultHandled();
}
if (HandleKeyboardActivation(event)) if (HandleKeyboardActivation(event))
return; return;
HTMLFrameOwnerElement::DefaultEventHandler(event); HTMLFrameOwnerElement::DefaultEventHandler(event);
......
...@@ -1465,6 +1465,10 @@ ...@@ -1465,6 +1465,10 @@
status: "test", status: "test",
origin_trial_feature_name: "Portals", origin_trial_feature_name: "Portals",
}, },
{
name: "PortalsDefaultActivation",
status: "test",
},
{ {
name: "PreciseMemoryInfo", name: "PreciseMemoryInfo",
}, },
......
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/open-blank-host.js"></script>
<script>
promise_test(async t => {
assert_implements("HTMLPortalElement" in self);
const w = await openBlankPortalHost();
try {
const bc = new BroadcastChannel('click-activate');
const portal = w.document.createElement('portal');
portal.src = new URL(`resources/portal-activate-broadcastchannel.html?bc=${bc.name}`, location.href);
w.document.body.appendChild(portal);
await new Promise(resolve => portal.onload = resolve);
let activated = new Promise(resolve => bc.onmessage = e => resolve(e.data));
portal.click();
assert_equals(await activated, 'portalactivate');
} finally {
w.close();
}
}, "Clicking should activate.");
promise_test(async t => {
assert_implements("HTMLPortalElement" in self);
const w = await openBlankPortalHost();
try {
const bc = new BroadcastChannel('prevent-no-activate');
const portal = w.document.createElement('portal');
portal.src = new URL(`resources/portal-activate-broadcastchannel.html?bc=${bc.name}`, location.href);
portal.onclick = e => e.preventDefault();
w.document.body.appendChild(portal);
await new Promise(resolve => portal.onload = resolve);
let timedOut = new Promise(resolve => t.step_timeout(() => resolve('timeout'), 3000));
let activated = new Promise(resolve => bc.onmessage = e => resolve(e.data));
portal.click();
let result = await Promise.race([activated, timedOut]);
assert_equals(result, 'timeout');
} finally {
w.close();
}
}, "Clicking shouldn't activate if prevented.");
</script>
...@@ -121,7 +121,9 @@ ...@@ -121,7 +121,9 @@
try { try {
portal.focus(); portal.focus();
for (let key of [SPACE, RETURN]) { for (let key of [SPACE, RETURN]) {
let clickPromise = new Promise(r => portal.onclick = r); let clickPromise = new Promise((resolve) => {
portal.onclick = e => { e.preventDefault(); resolve(); };
});
await test_driver.send_keys(document.body, key); await test_driver.send_keys(document.body, key);
await clickPromise; await clickPromise;
} }
......
<!DOCTYPE html>
<script>
onportalactivate = () => {
let bc = new BroadcastChannel(new URL(location).searchParams.get('bc'));
bc.postMessage('portalactivate');
bc.close();
};
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment