blob: 5f4181a078256353963ac5e2f84d5a5d5629cc9f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/events/pointer_event_factory.h"
#include "third_party/blink/public/common/widget/screen_info.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_pointer_event_init.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/geometry/float_size.h"
namespace blink {
namespace {
inline int ToInt(WebPointerProperties::PointerType t) {
return static_cast<int>(t);
}
const char* PointerTypeNameForWebPointPointerType(
WebPointerProperties::PointerType type) {
// TODO(mustaq): Fix when the spec starts supporting hovering erasers.
switch (type) {
case WebPointerProperties::PointerType::kUnknown:
return "";
case WebPointerProperties::PointerType::kTouch:
return "touch";
case WebPointerProperties::PointerType::kPen:
return "pen";
case WebPointerProperties::PointerType::kMouse:
return "mouse";
default:
NOTREACHED();
return "";
}
}
uint16_t ButtonToButtonsBitfield(WebPointerProperties::Button button) {
#define CASE_BUTTON_TO_BUTTONS(enumLabel) \
case WebPointerProperties::Button::enumLabel: \
return static_cast<uint16_t>(WebPointerProperties::Buttons::enumLabel)
switch (button) {
CASE_BUTTON_TO_BUTTONS(kNoButton);
CASE_BUTTON_TO_BUTTONS(kLeft);
CASE_BUTTON_TO_BUTTONS(kRight);
CASE_BUTTON_TO_BUTTONS(kMiddle);
CASE_BUTTON_TO_BUTTONS(kBack);
CASE_BUTTON_TO_BUTTONS(kForward);
CASE_BUTTON_TO_BUTTONS(kEraser);
}
#undef CASE_BUTTON_TO_BUTTONS
NOTREACHED();
return 0;
}
const AtomicString& PointerEventNameForEventType(WebInputEvent::Type type) {
switch (type) {
case WebInputEvent::Type::kPointerDown:
return event_type_names::kPointerdown;
case WebInputEvent::Type::kPointerUp:
return event_type_names::kPointerup;
case WebInputEvent::Type::kPointerMove:
return event_type_names::kPointermove;
case WebInputEvent::Type::kPointerRawUpdate:
return event_type_names::kPointerrawupdate;
case WebInputEvent::Type::kPointerCancel:
return event_type_names::kPointercancel;
default:
NOTREACHED();
return g_empty_atom;
}
}
float GetPointerEventPressure(float force, uint16_t buttons) {
if (!buttons)
return 0;
if (std::isnan(force))
return 0.5;
return force;
}
void UpdateCommonPointerEventInit(const WebPointerEvent& web_pointer_event,
const FloatPoint& last_global_position,
LocalDOMWindow* dom_window,
PointerEventInit* pointer_event_init) {
// This function should not update attributes like pointerId, isPrimary,
// and pointerType which is the same among the coalesced events and the
// dispatched event.
WebPointerEvent web_pointer_event_in_root_frame =
web_pointer_event.WebPointerEventInRootFrame();
MouseEvent::SetCoordinatesFromWebPointerProperties(
web_pointer_event_in_root_frame, dom_window, pointer_event_init);
if (RuntimeEnabledFeatures::ConsolidatedMovementXYEnabled() &&
!web_pointer_event.is_raw_movement_event &&
(web_pointer_event.GetType() == WebInputEvent::Type::kPointerMove ||
web_pointer_event.GetType() == WebInputEvent::Type::kPointerRawUpdate)) {
// TODO(crbug.com/907309): Current movementX/Y is in physical pixel when
// zoom-for-dsf is enabled. Here we apply the device-scale-factor to align
// with the current behavior. We need to figure out what is the best
// behavior here.
float device_scale_factor = 1;
if (dom_window && dom_window->GetFrame()) {
LocalFrame* frame = dom_window->GetFrame();
if (frame->GetPage()->DeviceScaleFactorDeprecated() == 1) {
ChromeClient& chrome_client = frame->GetPage()->GetChromeClient();
device_scale_factor =
chrome_client.GetScreenInfo(*frame).device_scale_factor;
}
}
// movementX/Y is type int for pointerevent, so we still need to truncated
// the coordinates before calculate movement.
pointer_event_init->setMovementX(
base::saturated_cast<int>(web_pointer_event.PositionInScreen().x() *
device_scale_factor) -
base::saturated_cast<int>(last_global_position.X() *
device_scale_factor));
pointer_event_init->setMovementY(
base::saturated_cast<int>(web_pointer_event.PositionInScreen().y() *
device_scale_factor) -
base::saturated_cast<int>(last_global_position.Y() *
device_scale_factor));
}
// If width/height is unknown we let PointerEventInit set it to 1.
// See https://w3c.github.io/pointerevents/#dom-pointerevent-width
if (web_pointer_event_in_root_frame.HasWidth() &&
web_pointer_event_in_root_frame.HasHeight()) {
float scale_factor = 1.0f;
if (dom_window && dom_window->GetFrame())
scale_factor = 1.0f / dom_window->GetFrame()->PageZoomFactor();
FloatSize point_shape = FloatSize(web_pointer_event_in_root_frame.width,
web_pointer_event_in_root_frame.height)
.ScaledBy(scale_factor);
pointer_event_init->setWidth(point_shape.Width());
pointer_event_init->setHeight(point_shape.Height());
}
pointer_event_init->setPressure(GetPointerEventPressure(
web_pointer_event.force, pointer_event_init->buttons()));
pointer_event_init->setTiltX(web_pointer_event.tilt_x);
pointer_event_init->setTiltY(web_pointer_event.tilt_y);
pointer_event_init->setTangentialPressure(
web_pointer_event.tangential_pressure);
pointer_event_init->setTwist(web_pointer_event.twist);
}
} // namespace
HeapVector<Member<PointerEvent>> PointerEventFactory::CreateEventSequence(
const WebPointerEvent& web_pointer_event,
const PointerEventInit* pointer_event_init,
const Vector<WebPointerEvent>& event_list,
LocalDOMWindow* view) {
AtomicString type = PointerEventNameForEventType(web_pointer_event.GetType());
HeapVector<Member<PointerEvent>> result;
if (!event_list.IsEmpty()) {
// Make a copy of LastPointerPosition so we can modify it after creating
// each coalesced event.
FloatPoint last_global_position =
GetLastPointerPosition(pointer_event_init->pointerId(),
event_list.front(), web_pointer_event.GetType());
for (const auto& event : event_list) {
DCHECK_EQ(web_pointer_event.id, event.id);
DCHECK_EQ(web_pointer_event.GetType(), event.GetType());
DCHECK_EQ(web_pointer_event.pointer_type, event.pointer_type);
PointerEventInit* new_event_init = PointerEventInit::Create();
if (pointer_event_init->hasButton())
new_event_init->setButton(pointer_event_init->button());
if (pointer_event_init->hasButtons())
new_event_init->setButtons(pointer_event_init->buttons());
if (pointer_event_init->hasIsPrimary())
new_event_init->setIsPrimary(pointer_event_init->isPrimary());
if (pointer_event_init->hasPointerId())
new_event_init->setPointerId(pointer_event_init->pointerId());
if (pointer_event_init->hasPointerType())
new_event_init->setPointerType(pointer_event_init->pointerType());
if (pointer_event_init->hasView())
new_event_init->setView(pointer_event_init->view());
new_event_init->setCancelable(false);
new_event_init->setBubbles(false);
UpdateCommonPointerEventInit(event, last_global_position, view,
new_event_init);
UIEventWithKeyState::SetFromWebInputEventModifiers(
new_event_init,
static_cast<WebInputEvent::Modifiers>(event.GetModifiers()));
last_global_position = FloatPoint(event.PositionInScreen());
PointerEvent* pointer_event =
PointerEvent::Create(type, new_event_init, event.TimeStamp());
// Set the trusted flag for these events at the creation time as oppose to
// the normal events which is done at the dispatch time. This is because
// we don't want to go over all these events at every dispatch and add the
// implementation complexity while it has no sensible usecase at this
// time.
pointer_event->SetTrusted(true);
result.push_back(pointer_event);
}
}
return result;
}
const PointerId PointerEventFactory::kInvalidId = 0;
// Mouse id is 1 to behave the same as MS Edge for compatibility reasons.
const PointerId PointerEventFactory::kMouseId = 1;
PointerEventInit* PointerEventFactory::ConvertIdTypeButtonsEvent(
const WebPointerEvent& web_pointer_event) {
WebPointerProperties::PointerType pointer_type =
web_pointer_event.pointer_type;
unsigned buttons;
if (web_pointer_event.hovering) {
buttons = MouseEvent::WebInputEventModifiersToButtons(
static_cast<WebInputEvent::Modifiers>(
web_pointer_event.GetModifiers()));
} else {
// TODO(crbug.com/816504): This is incorrect as we are assuming pointers
// that don't hover have no other buttons except left which represents
// touching the screen. This misconception comes from the touch devices and
// is not correct for stylus.
buttons = static_cast<unsigned>(
(web_pointer_event.GetType() == WebInputEvent::Type::kPointerUp ||
web_pointer_event.GetType() == WebInputEvent::Type::kPointerCancel)
? WebPointerProperties::Buttons::kNoButton
: WebPointerProperties::Buttons::kLeft);
}
// Tweak the |buttons| to reflect pen eraser mode only if the pen is in
// active buttons state w/o even considering the eraser button.
// TODO(mustaq): Fix when the spec starts supporting hovering erasers.
if (pointer_type == WebPointerProperties::PointerType::kEraser) {
if (buttons != 0) {
buttons |= static_cast<unsigned>(WebPointerProperties::Buttons::kEraser);
buttons &= ~static_cast<unsigned>(WebPointerProperties::Buttons::kLeft);
}
pointer_type = WebPointerProperties::PointerType::kPen;
}
const IncomingId incoming_id(pointer_type, web_pointer_event.id);
PointerId pointer_id = AddIdAndActiveButtons(incoming_id, buttons != 0,
web_pointer_event.hovering,
web_pointer_event.GetType());
if (pointer_id == kInvalidId)
return nullptr;
PointerEventInit* pointer_event_init = PointerEventInit::Create();
pointer_event_init->setButtons(buttons);
pointer_event_init->setPointerId(pointer_id);
pointer_event_init->setPointerType(
PointerTypeNameForWebPointPointerType(pointer_type));
pointer_event_init->setIsPrimary(IsPrimary(pointer_id));
return pointer_event_init;
}
void PointerEventFactory::SetEventSpecificFields(
PointerEventInit* pointer_event_init,
const AtomicString& type) {
pointer_event_init->setBubbles(type != event_type_names::kPointerenter &&
type != event_type_names::kPointerleave);
pointer_event_init->setCancelable(
type != event_type_names::kPointerenter &&
type != event_type_names::kPointerleave &&
type != event_type_names::kPointercancel &&
type != event_type_names::kPointerrawupdate &&
type != event_type_names::kGotpointercapture &&
type != event_type_names::kLostpointercapture);
pointer_event_init->setComposed(true);
pointer_event_init->setDetail(0);
}
PointerEvent* PointerEventFactory::Create(
const WebPointerEvent& web_pointer_event,
const Vector<WebPointerEvent>& coalesced_events,
const Vector<WebPointerEvent>& predicted_events,
LocalDOMWindow* view) {
const WebInputEvent::Type event_type = web_pointer_event.GetType();
DCHECK(event_type == WebInputEvent::Type::kPointerDown ||
event_type == WebInputEvent::Type::kPointerUp ||
event_type == WebInputEvent::Type::kPointerMove ||
event_type == WebInputEvent::Type::kPointerRawUpdate ||
event_type == WebInputEvent::Type::kPointerCancel);
PointerEventInit* pointer_event_init =
ConvertIdTypeButtonsEvent(web_pointer_event);
if (!pointer_event_init)
return nullptr;
AtomicString type = PointerEventNameForEventType(event_type);
if (event_type == WebInputEvent::Type::kPointerDown ||
event_type == WebInputEvent::Type::kPointerUp) {
WebPointerProperties::Button button = web_pointer_event.button;
// TODO(mustaq): Fix when the spec starts supporting hovering erasers.
if (web_pointer_event.pointer_type ==
WebPointerProperties::PointerType::kEraser &&
button == WebPointerProperties::Button::kLeft)
button = WebPointerProperties::Button::kEraser;
pointer_event_init->setButton(static_cast<int>(button));
// Make sure chorded buttons fire pointermove instead of pointerup/down.
if ((event_type == WebInputEvent::Type::kPointerDown &&
(pointer_event_init->buttons() & ~ButtonToButtonsBitfield(button)) !=
0) ||
(event_type == WebInputEvent::Type::kPointerUp &&
pointer_event_init->buttons() != 0))
type = event_type_names::kPointermove;
} else {
pointer_event_init->setButton(
static_cast<int16_t>(WebPointerProperties::Button::kNoButton));
}
pointer_event_init->setView(view);
UpdateCommonPointerEventInit(
web_pointer_event,
GetLastPointerPosition(pointer_event_init->pointerId(), web_pointer_event,
event_type),
view, pointer_event_init);
UIEventWithKeyState::SetFromWebInputEventModifiers(
pointer_event_init,
static_cast<WebInputEvent::Modifiers>(web_pointer_event.GetModifiers()));
SetEventSpecificFields(pointer_event_init, type);
HeapVector<Member<PointerEvent>> coalesced_pointer_events,
predicted_pointer_events;
if (type == event_type_names::kPointermove ||
type == event_type_names::kPointerrawupdate) {
coalesced_pointer_events = CreateEventSequence(
web_pointer_event, pointer_event_init, coalesced_events, view);
}
if (type == event_type_names::kPointermove) {
predicted_pointer_events = CreateEventSequence(
web_pointer_event, pointer_event_init, predicted_events, view);
}
pointer_event_init->setCoalescedEvents(coalesced_pointer_events);
pointer_event_init->setPredictedEvents(predicted_pointer_events);
SetLastPosition(pointer_event_init->pointerId(),
FloatPoint(web_pointer_event.PositionInScreen()), event_type);
return PointerEvent::Create(type, pointer_event_init,
web_pointer_event.TimeStamp());
}
void PointerEventFactory::SetLastPosition(int pointer_id,
const FloatPoint& position_in_screen,
WebInputEvent::Type event_type) {
if (event_type == WebInputEvent::Type::kPointerRawUpdate)
pointerrawupdate_last_position_mapping_.Set(pointer_id, position_in_screen);
else
pointer_id_last_position_mapping_.Set(pointer_id, position_in_screen);
}
void PointerEventFactory::RemoveLastPosition(const int pointer_id) {
pointer_id_last_position_mapping_.erase(pointer_id);
pointerrawupdate_last_position_mapping_.erase(pointer_id);
}
FloatPoint PointerEventFactory::GetLastPointerPosition(
int pointer_id,
const WebPointerProperties& event,
WebInputEvent::Type event_type) const {
if (event_type == WebInputEvent::Type::kPointerRawUpdate) {
if (pointerrawupdate_last_position_mapping_.Contains(pointer_id))
return pointerrawupdate_last_position_mapping_.at(pointer_id);
} else {
if (pointer_id_last_position_mapping_.Contains(pointer_id))
return pointer_id_last_position_mapping_.at(pointer_id);
}
// If pointer_id is not in the map, returns the current position so the
// movement will be zero.
return FloatPoint(event.PositionInScreen());
}
PointerEvent* PointerEventFactory::CreatePointerCancelEvent(
const int pointer_id,
base::TimeTicks platfrom_time_stamp) {
DCHECK(pointer_id_mapping_.Contains(pointer_id));
pointer_id_mapping_.Set(
pointer_id,
PointerAttributes(pointer_id_mapping_.at(pointer_id).incoming_id, false,
true));
PointerEventInit* pointer_event_init = PointerEventInit::Create();
pointer_event_init->setPointerId(pointer_id);
pointer_event_init->setPointerType(PointerTypeNameForWebPointPointerType(
pointer_id_mapping_.at(pointer_id).incoming_id.GetPointerType()));
pointer_event_init->setIsPrimary(IsPrimary(pointer_id));
SetEventSpecificFields(pointer_event_init, event_type_names::kPointercancel);
return PointerEvent::Create(event_type_names::kPointercancel,
pointer_event_init, platfrom_time_stamp);
}
PointerEvent* PointerEventFactory::CreatePointerEventFrom(
PointerEvent* pointer_event,
const AtomicString& type,
EventTarget* related_target) {
PointerEventInit* pointer_event_init = PointerEventInit::Create();
pointer_event_init->setPointerId(pointer_event->pointerId());
pointer_event_init->setPointerType(pointer_event->pointerType());
pointer_event_init->setIsPrimary(pointer_event->isPrimary());
pointer_event_init->setWidth(pointer_event->width());
pointer_event_init->setHeight(pointer_event->height());
pointer_event_init->setScreenX(pointer_event->screenX());
pointer_event_init->setScreenY(pointer_event->screenY());
pointer_event_init->setClientX(pointer_event->clientX());
pointer_event_init->setClientY(pointer_event->clientY());
pointer_event_init->setButton(pointer_event->button());
pointer_event_init->setButtons(pointer_event->buttons());
pointer_event_init->setPressure(pointer_event->pressure());
pointer_event_init->setTiltX(pointer_event->tiltX());
pointer_event_init->setTiltY(pointer_event->tiltY());
pointer_event_init->setTangentialPressure(
pointer_event->tangentialPressure());
pointer_event_init->setTwist(pointer_event->twist());
pointer_event_init->setView(pointer_event->view());
SetEventSpecificFields(pointer_event_init, type);
if (const UIEventWithKeyState* key_state_event =
FindEventWithKeyState(pointer_event)) {
UIEventWithKeyState::SetFromWebInputEventModifiers(
pointer_event_init, key_state_event->GetModifiers());
}
if (related_target)
pointer_event_init->setRelatedTarget(related_target);
return PointerEvent::Create(type, pointer_event_init,
pointer_event->PlatformTimeStamp());
}
PointerEvent* PointerEventFactory::CreatePointerRawUpdateEvent(
PointerEvent* pointer_event) {
// This function is for creating pointerrawupdate event from a pointerdown/up
// event that caused by chorded buttons and hence its type is changed to
// pointermove.
DCHECK(pointer_event->type() == event_type_names::kPointermove &&
(pointer_event->buttons() &
~ButtonToButtonsBitfield(static_cast<WebPointerProperties::Button>(
pointer_event->button()))) != 0 &&
pointer_event->button() != 0);
return CreatePointerEventFrom(pointer_event,
event_type_names::kPointerrawupdate,
pointer_event->relatedTarget());
}
PointerEvent* PointerEventFactory::CreatePointerCaptureEvent(
PointerEvent* pointer_event,
const AtomicString& type) {
DCHECK(type == event_type_names::kGotpointercapture ||
type == event_type_names::kLostpointercapture);
return CreatePointerEventFrom(pointer_event, type,
pointer_event->relatedTarget());
}
PointerEvent* PointerEventFactory::CreatePointerBoundaryEvent(
PointerEvent* pointer_event,
const AtomicString& type,
EventTarget* related_target) {
DCHECK(type == event_type_names::kPointerout ||
type == event_type_names::kPointerleave ||
type == event_type_names::kPointerover ||
type == event_type_names::kPointerenter);
return CreatePointerEventFrom(pointer_event, type, related_target);
}
PointerEventFactory::PointerEventFactory() {
Clear();
}
PointerEventFactory::~PointerEventFactory() {
Clear();
}
void PointerEventFactory::Clear() {
for (int type = 0;
type <= ToInt(WebPointerProperties::PointerType::kMaxValue); type++) {
primary_id_[type] = kInvalidId;
id_count_[type] = 0;
}
pointer_incoming_id_mapping_.clear();
pointer_id_mapping_.clear();
pointer_id_last_position_mapping_.clear();
// Always add mouse pointer in initialization and never remove it.
// No need to add it to |pointer_incoming_id_mapping_| as it is not going to
// be used with the existing APIs
primary_id_[ToInt(WebPointerProperties::PointerType::kMouse)] = kMouseId;
pointer_id_mapping_.insert(
kMouseId, PointerAttributes(
IncomingId(WebPointerProperties::PointerType::kMouse, 0),
false, true));
current_id_ = PointerEventFactory::kMouseId + 1;
}
PointerId PointerEventFactory::AddIdAndActiveButtons(
const IncomingId p,
bool is_active_buttons,
bool hovering,
WebInputEvent::Type event_type) {
// Do not add extra mouse pointer as it was added in initialization.
if (p.GetPointerType() == WebPointerProperties::PointerType::kMouse) {
pointer_id_mapping_.Set(kMouseId,
PointerAttributes(p, is_active_buttons, true));
return kMouseId;
}
if (pointer_incoming_id_mapping_.Contains(p)) {
PointerId mapped_id = pointer_incoming_id_mapping_.at(p);
pointer_id_mapping_.Set(mapped_id,
PointerAttributes(p, is_active_buttons, hovering));
return mapped_id;
}
// TODO(crbug.com/1141595): We should filter out bad pointercancel events
// further upstream.
if (event_type == WebInputEvent::Type::kPointerCancel)
return kInvalidId;
int type_int = p.PointerTypeInt();
// We do not handle the overflow of |current_id_| as it should be very rare.
PointerId mapped_id = current_id_++;
if (!id_count_[type_int])
primary_id_[type_int] = mapped_id;
id_count_[type_int]++;
pointer_incoming_id_mapping_.insert(p, mapped_id);
pointer_id_mapping_.insert(mapped_id,
PointerAttributes(p, is_active_buttons, hovering));
return mapped_id;
}
bool PointerEventFactory::Remove(const PointerId mapped_id) {
// Do not remove mouse pointer id as it should always be there.
if (mapped_id == kMouseId || !pointer_id_mapping_.Contains(mapped_id))
return false;
IncomingId p = pointer_id_mapping_.at(mapped_id).incoming_id;
int type_int = p.PointerTypeInt();
pointer_id_mapping_.erase(mapped_id);
pointer_incoming_id_mapping_.erase(p);
RemoveLastPosition(mapped_id);
if (primary_id_[type_int] == mapped_id)
primary_id_[type_int] = kInvalidId;
id_count_[type_int]--;
return true;
}
Vector<PointerId> PointerEventFactory::GetPointerIdsOfNonHoveringPointers()
const {
Vector<PointerId> mapped_ids;
for (auto iter = pointer_id_mapping_.begin();
iter != pointer_id_mapping_.end(); ++iter) {
PointerId mapped_id = static_cast<PointerId>(iter->key);
if (!iter->value.hovering)
mapped_ids.push_back(mapped_id);
}
// Sorting for a predictable ordering.
std::sort(mapped_ids.begin(), mapped_ids.end());
return mapped_ids;
}
bool PointerEventFactory::IsPrimary(PointerId mapped_id) const {
if (!pointer_id_mapping_.Contains(mapped_id))
return false;
IncomingId p = pointer_id_mapping_.at(mapped_id).incoming_id;
return primary_id_[p.PointerTypeInt()] == mapped_id;
}
bool PointerEventFactory::IsActive(const PointerId pointer_id) const {
return pointer_id_mapping_.Contains(pointer_id);
}
bool PointerEventFactory::IsPrimary(
const WebPointerProperties& properties) const {
// Mouse event is always primary.
if (properties.pointer_type == WebPointerProperties::PointerType::kMouse)
return true;
// If !id_count, no pointer active, current WebPointerEvent will
// be primary pointer when added to map.
if (!id_count_[static_cast<int>(properties.pointer_type)])
return true;
PointerId pointer_id = GetPointerEventId(properties);
return (pointer_id != kInvalidId && IsPrimary(pointer_id));
}
bool PointerEventFactory::IsActiveButtonsState(
const PointerId pointer_id) const {
return pointer_id_mapping_.Contains(pointer_id) &&
pointer_id_mapping_.at(pointer_id).is_active_buttons;
}
WebPointerProperties::PointerType PointerEventFactory::GetPointerType(
PointerId pointer_id) const {
if (!IsActive(pointer_id))
return WebPointerProperties::PointerType::kUnknown;
return pointer_id_mapping_.at(pointer_id).incoming_id.GetPointerType();
}
PointerId PointerEventFactory::GetPointerEventId(
const WebPointerProperties& properties) const {
if (properties.pointer_type == WebPointerProperties::PointerType::kMouse)
return PointerEventFactory::kMouseId;
IncomingId id(properties.pointer_type, properties.id);
if (pointer_incoming_id_mapping_.Contains(id))
return pointer_incoming_id_mapping_.at(id);
return kInvalidId;
}
} // namespace blink