summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc')
-rw-r--r--chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc287
1 files changed, 274 insertions, 13 deletions
diff --git a/chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc b/chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc
index af14ac91d1f..b393c0cb3c6 100644
--- a/chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc
+++ b/chromium/third_party/blink/renderer/modules/xr/xr_input_source.cc
@@ -5,12 +5,19 @@
#include "third_party/blink/renderer/modules/xr/xr_input_source.h"
#include "base/time/time.h"
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/events/event_dispatcher.h"
+#include "third_party/blink/renderer/core/dom/events/event_path.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/modules/xr/xr.h"
+#include "third_party/blink/renderer/core/html/html_frame_element_base.h"
+#include "third_party/blink/renderer/core/input/event_handling_util.h"
+#include "third_party/blink/renderer/core/layout/hit_test_location.h"
#include "third_party/blink/renderer/modules/xr/xr_grip_space.h"
#include "third_party/blink/renderer/modules/xr/xr_input_source_event.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
+#include "third_party/blink/renderer/modules/xr/xr_session_event.h"
#include "third_party/blink/renderer/modules/xr/xr_space.h"
+#include "third_party/blink/renderer/modules/xr/xr_system.h"
#include "third_party/blink/renderer/modules/xr/xr_target_ray_space.h"
#include "third_party/blink/renderer/modules/xr/xr_utils.h"
@@ -69,7 +76,9 @@ XRInputSource* XRInputSource::CreateOrUpdateFrom(
updated_source = MakeGarbageCollected<XRInputSource>(*other);
}
- updated_source->UpdateGamepad(state->gamepad);
+ if (updated_source->state_.is_visible) {
+ updated_source->UpdateGamepad(state->gamepad);
+ }
// Update the input source's description if this state update includes them.
if (state->description) {
@@ -79,8 +88,10 @@ XRInputSource* XRInputSource::CreateOrUpdateFrom(
updated_source->state_.target_ray_mode = desc->target_ray_mode;
updated_source->state_.handedness = desc->handedness;
- updated_source->input_from_pointer_ =
- TryGetTransformationMatrix(desc->input_from_pointer);
+ if (updated_source->state_.is_visible) {
+ updated_source->input_from_pointer_ =
+ TryGetTransformationMatrix(desc->input_from_pointer);
+ }
updated_source->state_.profiles.clear();
for (const auto& name : state->description->profiles) {
@@ -88,8 +99,10 @@ XRInputSource* XRInputSource::CreateOrUpdateFrom(
}
}
- updated_source->mojo_from_input_ =
- TryGetTransformationMatrix(state->mojo_from_input);
+ if (updated_source->state_.is_visible) {
+ updated_source->mojo_from_input_ =
+ TryGetTransformationMatrix(state->mojo_from_input);
+ }
updated_source->state_.emulated_position = state->emulated_position;
@@ -151,6 +164,9 @@ XRSpace* XRInputSource::targetRaySpace() const {
}
XRSpace* XRInputSource::gripSpace() const {
+ if (!state_.is_visible)
+ return nullptr;
+
if (state_.target_ray_mode == device::mojom::XRTargetRayMode::POINTING) {
return grip_space_;
}
@@ -189,7 +205,9 @@ bool XRInputSource::InvalidatesSameObject(
void XRInputSource::SetInputFromPointer(
const TransformationMatrix* input_from_pointer) {
- input_from_pointer_ = TryGetTransformationMatrix(input_from_pointer);
+ if (state_.is_visible) {
+ input_from_pointer_ = TryGetTransformationMatrix(input_from_pointer);
+ }
}
void XRInputSource::SetGamepadConnected(bool state) {
@@ -216,6 +234,7 @@ base::Optional<XRNativeOriginInformation> XRInputSource::nativeOrigin() const {
}
void XRInputSource::OnSelectStart() {
+ DVLOG(3) << __func__;
// Discard duplicate events and ones after the session has ended.
if (state_.primary_input_pressed || session_->ended())
return;
@@ -223,6 +242,7 @@ void XRInputSource::OnSelectStart() {
state_.primary_input_pressed = true;
state_.selection_cancelled = false;
+ DVLOG(3) << __func__ << ": dispatch selectstart event";
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelectstart);
session_->DispatchEvent(*event);
@@ -235,6 +255,7 @@ void XRInputSource::OnSelectStart() {
}
void XRInputSource::OnSelectEnd() {
+ DVLOG(3) << __func__;
// Discard duplicate events and ones after the session has ended.
if (!state_.primary_input_pressed || session_->ended())
return;
@@ -245,6 +266,7 @@ void XRInputSource::OnSelectEnd() {
if (!frame)
return;
+ DVLOG(3) << __func__ << ": dispatch selectend event";
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelectend);
session_->DispatchEvent(*event);
@@ -257,6 +279,7 @@ void XRInputSource::OnSelectEnd() {
}
void XRInputSource::OnSelect() {
+ DVLOG(3) << __func__;
// If a select was fired but we had not previously started the selection it
// indicates a sub-frame or instantaneous select event, and we should fire a
// selectstart prior to the selectend.
@@ -272,6 +295,7 @@ void XRInputSource::OnSelect() {
if (!state_.selection_cancelled && !session_->ended()) {
if (!frame)
return;
+ DVLOG(3) << __func__ << ": dispatch select event";
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelect);
session_->DispatchEvent(*event);
@@ -283,16 +307,114 @@ void XRInputSource::OnSelect() {
OnSelectEnd();
}
-void XRInputSource::UpdateSelectState(
- const device::mojom::blink::XRInputSourceStatePtr& state) {
- if (!state)
+void XRInputSource::OnSqueezeStart() {
+ DVLOG(3) << __func__;
+ // Discard duplicate events and ones after the session has ended.
+ if (state_.primary_squeeze_pressed || session_->ended())
+ return;
+
+ state_.primary_squeeze_pressed = true;
+ state_.squeezing_cancelled = false;
+
+ XRInputSourceEvent* event =
+ CreateInputSourceEvent(event_type_names::kSqueezestart);
+ session_->DispatchEvent(*event);
+
+ if (event->defaultPrevented())
+ state_.squeezing_cancelled = true;
+
+ // Ensure the frame cannot be used outside of the event handler.
+ event->frame()->Deactivate();
+}
+
+void XRInputSource::OnSqueezeEnd() {
+ DVLOG(3) << __func__;
+ // Discard duplicate events and ones after the session has ended.
+ if (!state_.primary_squeeze_pressed || session_->ended())
+ return;
+
+ state_.primary_squeeze_pressed = false;
+
+ LocalFrame* frame = session_->xr()->GetFrame();
+ if (!frame)
+ return;
+
+ DVLOG(3) << __func__ << ": dispatch squeezeend event";
+ XRInputSourceEvent* event =
+ CreateInputSourceEvent(event_type_names::kSqueezeend);
+ session_->DispatchEvent(*event);
+
+ if (event->defaultPrevented())
+ state_.squeezing_cancelled = true;
+
+ // Ensure the frame cannot be used outside of the event handler.
+ event->frame()->Deactivate();
+}
+
+void XRInputSource::OnSqueeze() {
+ DVLOG(3) << __func__;
+ // If a squeeze was fired but we had not previously started the squeezing it
+ // indicates a sub-frame or instantaneous squeeze event, and we should fire a
+ // squeezestart prior to the squeezeend.
+ if (!state_.primary_squeeze_pressed) {
+ OnSqueezeStart();
+ }
+
+ LocalFrame* frame = session_->xr()->GetFrame();
+ LocalFrame::NotifyUserActivation(frame);
+
+ // If SelectStart caused the session to end, we shouldn't try to fire the
+ // select event.
+ if (!state_.squeezing_cancelled && !session_->ended()) {
+ if (!frame)
+ return;
+ DVLOG(3) << __func__ << ": dispatch squeeze event";
+ XRInputSourceEvent* event =
+ CreateInputSourceEvent(event_type_names::kSqueeze);
+ session_->DispatchEvent(*event);
+
+ // Ensure the frame cannot be used outside of the event handler.
+ event->frame()->Deactivate();
+ }
+
+ OnSqueezeEnd();
+}
+
+void XRInputSource::UpdateButtonStates(
+ const device::mojom::blink::XRInputSourceStatePtr& new_state) {
+ if (!new_state)
return;
+ DVLOG(3) << __func__ << ": state_.is_visible=" << state_.is_visible
+ << ", state_.xr_select_events_suppressed="
+ << state_.xr_select_events_suppressed
+ << ", new_state->primary_input_clicked="
+ << new_state->primary_input_clicked;
+
+ if (!state_.is_visible) {
+ DVLOG(3) << __func__ << ": input NOT VISIBLE";
+ if (new_state->primary_input_clicked) {
+ DVLOG(3) << __func__ << ": got click while invisible, SUPPRESS end";
+ state_.xr_select_events_suppressed = false;
+ }
+ return;
+ }
+ if (state_.xr_select_events_suppressed) {
+ if (new_state->primary_input_clicked) {
+ DVLOG(3) << __func__ << ": got click, SUPPRESS end";
+ state_.xr_select_events_suppressed = false;
+ }
+ DVLOG(3) << __func__ << ": overlay input select SUPPRESSED";
+ return;
+ }
+
+ DCHECK(!state_.xr_select_events_suppressed);
+
// Handle state change of the primary input, which may fire events
- if (state->primary_input_clicked)
+ if (new_state->primary_input_clicked)
OnSelect();
- if (state->primary_input_pressed) {
+ if (new_state->primary_input_pressed) {
OnSelectStart();
} else if (state_.primary_input_pressed) {
// May get here if the input source was previously pressed but now isn't,
@@ -302,6 +424,131 @@ void XRInputSource::UpdateSelectState(
// usual select event.
OnSelectEnd();
}
+
+ // Handle state change of the primary input, which may fire events
+ if (new_state->primary_squeeze_clicked)
+ OnSqueeze();
+
+ if (new_state->primary_squeeze_pressed) {
+ OnSqueezeStart();
+ } else if (state_.primary_squeeze_pressed) {
+ // May get here if the input source was previously pressed but now isn't,
+ // but the input source did not set primary_squeeze_clicked to true. We will
+ // treat this as a cancelled squeezeing, firing the squeezeend event so the
+ // page stays in sync with the controller state but won't fire the
+ // usual squeeze event.
+ OnSqueezeEnd();
+ }
+}
+
+void XRInputSource::ProcessOverlayHitTest(
+ Element* overlay_element,
+ const device::mojom::blink::XRInputSourceStatePtr& new_state) {
+ DVLOG(3) << __func__ << ": state_.xr_select_events_suppressed="
+ << state_.xr_select_events_suppressed;
+
+ DCHECK(overlay_element);
+ DCHECK(new_state->overlay_pointer_position);
+
+ // Do a hit test at the overlay pointer position to see if the pointer
+ // intersects a cross origin iframe. If yes, set the visibility to false which
+ // causes targetRaySpace and gripSpace to return null poses.
+ FloatPoint point(new_state->overlay_pointer_position->x(),
+ new_state->overlay_pointer_position->y());
+ DVLOG(3) << __func__ << ": hit test point=" << point;
+
+ HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kTouchEvent |
+ HitTestRequest::kReadOnly |
+ HitTestRequest::kActive;
+
+ HitTestResult result = event_handling_util::HitTestResultInFrame(
+ overlay_element->GetDocument().GetFrame(), HitTestLocation(point),
+ hit_type);
+ DVLOG(3) << __func__ << ": hit test InnerElement=" << result.InnerElement();
+
+ Element* hit_element = result.InnerElement();
+ if (!hit_element) {
+ return;
+ }
+
+ // Check if the hit element is cross-origin content. In addition to an iframe,
+ // this could potentially be an old-style frame in a frameset, so check for
+ // the common base class to cover both. (There's no intention to actively
+ // support framesets for DOM Overlay, but this helps prevent them from
+ // being used as a mechanism for information leaks.)
+ HTMLFrameElementBase* frame = DynamicTo<HTMLFrameElementBase>(hit_element);
+ if (frame) {
+ Document* hit_document = frame->contentDocument();
+ if (hit_document) {
+ Frame* hit_frame = hit_document->GetFrame();
+ DCHECK(hit_frame);
+ if (hit_frame->IsCrossOriginToMainFrame()) {
+ // Mark the input source as invisible until the primary button is
+ // released.
+ state_.is_visible = false;
+
+ // If this is the first touch, also suppress events, even if it
+ // ends up being released outside the frame later.
+ if (!state_.primary_input_pressed) {
+ state_.xr_select_events_suppressed = true;
+ }
+
+ DVLOG(3)
+ << __func__
+ << ": input source overlaps with cross origin content, is_visible="
+ << state_.is_visible << ", xr_select_events_suppressed="
+ << state_.xr_select_events_suppressed;
+ return;
+ }
+ }
+ }
+
+ // If we get here, the touch didn't hit a cross origin frame. Set the
+ // controller spaces visible.
+ state_.is_visible = true;
+
+ // Now that the visibility check has finished, mark non-primary input sources
+ // as suppressed.
+ if (new_state->is_auxiliary) {
+ state_.xr_select_events_suppressed = true;
+ }
+
+ // Now check if this is a new primary button press. If yes, send a
+ // beforexrselect event to give the application an opportunity to cancel the
+ // XR input "select" sequence that would normally be caused by this.
+
+ if (state_.xr_select_events_suppressed) {
+ DVLOG(3) << __func__ << ": using overlay input provider: SUPPRESS ongoing";
+ return;
+ }
+
+ if (state_.primary_input_pressed) {
+ DVLOG(3) << __func__ << ": ongoing press, not checking again";
+ return;
+ }
+
+ bool is_primary_press =
+ new_state->primary_input_pressed || new_state->primary_input_clicked;
+ if (!is_primary_press) {
+ DVLOG(3) << __func__ << ": no button press, ignoring";
+ return;
+ }
+
+ // The event needs to be cancelable (obviously), bubble (so that parent
+ // elements can handle it), and composed (so that it crosses shadow DOM
+ // boundaries, including UA-added shadow DOM).
+ Event* event = MakeGarbageCollected<XRSessionEvent>(
+ event_type_names::kBeforexrselect, session_, Event::Bubbles::kYes,
+ Event::Cancelable::kYes, Event::ComposedMode::kComposed);
+
+ hit_element->DispatchEvent(*event);
+ bool default_prevented = event->defaultPrevented();
+
+ // Keep the input source visible, so it's exposed in the input sources array,
+ // but don't generate XR select events for the current button sequence.
+ state_.xr_select_events_suppressed = default_prevented;
+ DVLOG(3) << __func__ << ": state_.xr_select_events_suppressed="
+ << state_.xr_select_events_suppressed;
}
void XRInputSource::OnRemoved() {
@@ -319,6 +566,20 @@ void XRInputSource::OnRemoved() {
event->frame()->Deactivate();
}
+ if (state_.primary_squeeze_pressed) {
+ state_.primary_squeeze_pressed = false;
+
+ XRInputSourceEvent* event =
+ CreateInputSourceEvent(event_type_names::kSqueezeend);
+ session_->DispatchEvent(*event);
+
+ if (event->defaultPrevented())
+ state_.squeezing_cancelled = true;
+
+ // Ensure the frame cannot be used outside of the event handler.
+ event->frame()->Deactivate();
+ }
+
SetGamepadConnected(false);
}
@@ -328,7 +589,7 @@ XRInputSourceEvent* XRInputSource::CreateInputSourceEvent(
return XRInputSourceEvent::Create(type, presentation_frame, this);
}
-void XRInputSource::Trace(blink::Visitor* visitor) {
+void XRInputSource::Trace(Visitor* visitor) {
visitor->Trace(session_);
visitor->Trace(target_ray_space_);
visitor->Trace(grip_space_);