| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/layout/topmost_event_target.h" |
| |
| #include <vector> |
| |
| #include "base/optional.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/base/token.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/cssom/computed_style_utils.h" |
| #include "cobalt/cssom/keyword_value.h" |
| #include "cobalt/dom/document.h" |
| #include "cobalt/dom/html_element.h" |
| #include "cobalt/dom/html_html_element.h" |
| #include "cobalt/dom/lottie_player.h" |
| #include "cobalt/dom/mouse_event.h" |
| #include "cobalt/dom/mouse_event_init.h" |
| #include "cobalt/dom/pointer_event.h" |
| #include "cobalt/dom/pointer_event_init.h" |
| #include "cobalt/dom/pointer_state.h" |
| #include "cobalt/dom/ui_event.h" |
| #include "cobalt/dom/wheel_event.h" |
| #include "cobalt/layout/layout_unit.h" |
| #include "cobalt/math/rect_f.h" |
| #include "cobalt/math/vector2d.h" |
| #include "cobalt/math/vector2d_f.h" |
| #include "cobalt/web/event.h" |
| |
| namespace cobalt { |
| namespace layout { |
| |
| scoped_refptr<dom::HTMLElement> TopmostEventTarget::FindTopmostEventTarget( |
| const scoped_refptr<dom::Document>& document, |
| const math::Vector2dF& coordinate) { |
| TRACE_EVENT0("cobalt::layout", |
| "TopmostEventTarget::FindTopmostEventTarget()"); |
| DCHECK(document); |
| DCHECK(!box_); |
| DCHECK(render_sequence_.empty()); |
| |
| // Make sure the document's layout box tree is up-to-date. |
| document->DoSynchronousLayout(); |
| |
| html_element_ = document->html(); |
| ConsiderElement(html_element_, coordinate); |
| box_ = NULL; |
| render_sequence_.clear(); |
| scoped_refptr<dom::HTMLElement> topmost_element; |
| topmost_element.swap(html_element_); |
| DCHECK(!html_element_); |
| return topmost_element; |
| } |
| |
| namespace { |
| |
| LayoutBoxes* GetLayoutBoxesIfNotEmpty(dom::Element* element) { |
| dom::HTMLElement* html_element = element->AsHTMLElement(); |
| if (html_element && html_element->computed_style()) { |
| dom::LayoutBoxes* dom_layout_boxes = html_element->layout_boxes(); |
| if (dom_layout_boxes && |
| dom_layout_boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) { |
| LayoutBoxes* layout_boxes = |
| base::polymorphic_downcast<LayoutBoxes*>(dom_layout_boxes); |
| if (!layout_boxes->boxes().empty()) { |
| return layout_boxes; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| scoped_refptr<dom::HTMLElement> FindFirstElementWithScrollType( |
| scoped_refptr<dom::HTMLElement> target_element, |
| ui_navigation::scroll_engine::ScrollType major_scroll_axis, |
| bool scrolling_right, bool scrolling_down) { |
| auto current_element = target_element; |
| bool scrolling_left = !scrolling_right; |
| bool scrolling_up = !scrolling_down; |
| bool horizontal_scroll_axis = |
| major_scroll_axis == ui_navigation::scroll_engine::ScrollType::Horizontal; |
| bool vertical_scroll_axis = |
| major_scroll_axis == ui_navigation::scroll_engine::ScrollType::Vertical; |
| |
| while (true) { |
| float scroll_top_lower_bound; |
| float scroll_left_lower_bound; |
| float scroll_top_upper_bound; |
| float scroll_left_upper_bound; |
| float offset_x; |
| float offset_y; |
| |
| if (!current_element->parent_element()) { |
| break; |
| } |
| current_element = current_element->parent_element()->AsHTMLElement(); |
| auto current_ui_nav_item = current_element->GetUiNavItem(); |
| if (!current_ui_nav_item) continue; |
| |
| current_ui_nav_item->GetBounds( |
| &scroll_top_lower_bound, &scroll_left_lower_bound, |
| &scroll_top_upper_bound, &scroll_left_upper_bound); |
| current_ui_nav_item->GetContentOffset(&offset_x, &offset_y); |
| |
| bool can_scroll_left = scroll_left_lower_bound < offset_x; |
| bool can_scroll_right = scroll_left_upper_bound > offset_x; |
| bool can_scroll_up = scroll_top_lower_bound < offset_y; |
| bool can_scroll_down = scroll_top_upper_bound > offset_y; |
| |
| if ((scrolling_left && can_scroll_left && horizontal_scroll_axis) || |
| (scrolling_right && can_scroll_right && horizontal_scroll_axis) || |
| (scrolling_up && can_scroll_up && vertical_scroll_axis) || |
| (scrolling_down && can_scroll_down && vertical_scroll_axis)) { |
| return current_element; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool TransformCanBeAppliedToBox(const Box* box, math::Vector2dF* coordinate) { |
| return !box->IsTransformed() || box->ApplyTransformActionToCoordinate( |
| Box::kEnterTransform, coordinate); |
| } |
| |
| bool CoordinateCanTargetBox(const Box* box, math::Vector2dF* coordinate) { |
| if (!cssom::IsOverflowCropped(box->computed_style())) { |
| return true; |
| } |
| LayoutUnit coordinate_x(coordinate->x()); |
| LayoutUnit coordinate_y(coordinate->y()); |
| |
| bool transform_forms_root = false; |
| auto padding_box = box->GetClampedPaddingBox(transform_forms_root); |
| return padding_box.Contains(coordinate_x, coordinate_y); |
| } |
| |
| bool ShouldConsiderElementAndChildren(dom::Element* element, |
| math::Vector2dF* coordinate) { |
| LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element); |
| const Box* box = layout_boxes->boxes().front(); |
| if (!box->computed_style()) { |
| return true; |
| } |
| |
| return TransformCanBeAppliedToBox(box, coordinate) && |
| CoordinateCanTargetBox(box, coordinate); |
| } |
| |
| } // namespace |
| void TopmostEventTarget::ConsiderElement(dom::Element* element, |
| const math::Vector2dF& coordinate) { |
| if (!element) return; |
| math::Vector2dF element_coordinate(coordinate); |
| LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element); |
| if (layout_boxes) { |
| if (!ShouldConsiderElementAndChildren(element, &element_coordinate)) { |
| return; |
| } |
| scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement(); |
| if (html_element && html_element->CanBeDesignatedByPointerIfDisplayed()) { |
| ConsiderBoxes(html_element, layout_boxes, element_coordinate); |
| } |
| } |
| |
| for (dom::Element* child_element = element->first_element_child(); |
| child_element; child_element = child_element->next_element_sibling()) { |
| ConsiderElement(child_element, element_coordinate); |
| } |
| } |
| |
| void TopmostEventTarget::ConsiderBoxes( |
| const scoped_refptr<dom::HTMLElement>& html_element, |
| LayoutBoxes* layout_boxes, const math::Vector2dF& coordinate) { |
| const Boxes& boxes = layout_boxes->boxes(); |
| Vector2dLayoutUnit layout_coordinate(LayoutUnit(coordinate.x()), |
| LayoutUnit(coordinate.y())); |
| for (Boxes::const_iterator box_iterator = boxes.begin(); |
| box_iterator != boxes.end(); ++box_iterator) { |
| Box* box = *box_iterator; |
| do { |
| if (box->IsUnderCoordinate(layout_coordinate)) { |
| Box::RenderSequence render_sequence = box->GetRenderSequence(); |
| if (Box::IsRenderedLater(render_sequence, render_sequence_)) { |
| html_element_ = html_element; |
| box_ = box; |
| render_sequence_.swap(render_sequence); |
| } |
| } |
| box = box->GetSplitSibling(); |
| } while (box != NULL); |
| } |
| } |
| |
| void TopmostEventTarget::CancelScrollsInParentNavItems( |
| scoped_refptr<dom::HTMLElement> target_element) { |
| // Cancel any scrolls in the tree. |
| std::vector<scoped_refptr<ui_navigation::NavItem>> scrolls_to_cancel; |
| auto current_element = target_element; |
| while (true) { |
| if (!current_element->parent_element()) { |
| break; |
| } |
| current_element = current_element->parent_element()->AsHTMLElement(); |
| auto current_ui_nav_item = current_element->GetUiNavItem(); |
| if (current_ui_nav_item) { |
| scrolls_to_cancel.push_back(current_ui_nav_item); |
| } |
| } |
| |
| scroll_engine_->thread()->message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&ui_navigation::scroll_engine::ScrollEngine:: |
| CancelActiveScrollsForNavItems, |
| base::Unretained(scroll_engine_), scrolls_to_cancel)); |
| } |
| |
| void TopmostEventTarget::HandleScrollState( |
| scoped_refptr<dom::HTMLElement> target_element, |
| const dom::PointerEvent* pointer_event, dom::PointerState* pointer_state, |
| dom::PointerEventInit* event_init) { |
| // On pointer down, cancel any scrolls happening for UI nav items above |
| // that element. Additionally, save the pointer coordinates. |
| // |
| // On pointer move, check if we've reached the threshold to start |
| // scrolling. If we have, find the first scroll container we can scroll. |
| // Then send that scroll container, initial pointer event coords, current |
| // pointer event coords, scroll direction. |
| bool pointer_type_is_accepted = pointer_event->pointer_type() == "mouse" || |
| pointer_event->pointer_type() == "pen" || |
| pointer_event->pointer_type() == "touch"; |
| if (!scroll_engine_ || !pointer_event || !pointer_type_is_accepted) { |
| return; |
| } |
| |
| bool should_clear_pointer_state = |
| pointer_event->type() == base::Tokens::pointerup(); |
| |
| auto pointer_id = pointer_event->pointer_id(); |
| auto pointer_coordinates = |
| math::Vector2dF(pointer_event->client_x(), pointer_event->client_y()); |
| |
| if (pointer_event->type() == base::Tokens::pointerdown()) { |
| CancelScrollsInParentNavItems(target_element); |
| pointer_state->SetClientCoordinates(pointer_id, pointer_coordinates); |
| pointer_state->SetClientTimeStamp(pointer_id, pointer_event->time_stamp()); |
| return; |
| } |
| |
| auto initial_coordinates = pointer_state->GetClientCoordinates(pointer_id); |
| auto initial_time_stamp = pointer_state->GetClientTimeStamp(pointer_id); |
| if (pointer_event->type() == base::Tokens::pointermove() && |
| initial_coordinates.has_value() && initial_time_stamp.has_value()) { |
| cobalt::math::Vector2dF drag_vector = |
| initial_coordinates.value() - pointer_coordinates; |
| float x = drag_vector.x(); |
| float y = drag_vector.y(); |
| |
| if (drag_vector.Length() >= |
| ui_navigation::scroll_engine::kDragDistanceThreshold) { |
| // Get major scroll direction. |
| ui_navigation::scroll_engine::ScrollType scroll_type = |
| std::abs(x) > std::abs(y) |
| ? ui_navigation::scroll_engine::ScrollType::Horizontal |
| : ui_navigation::scroll_engine::ScrollType::Vertical; |
| auto element_to_scroll = FindFirstElementWithScrollType( |
| target_element, scroll_type, x > 0, y > 0); |
| if (!element_to_scroll) { |
| return; |
| } |
| |
| const scoped_refptr<dom::Window>& view = event_init->view(); |
| element_to_scroll->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointercancel(), web::Event::kBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| element_to_scroll->DispatchEvent( |
| new dom::PointerEvent(base::Tokens::pointerout(), view, *event_init)); |
| element_to_scroll->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerleave(), web::Event::kNotBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| pointer_state->SetWasCancelled(pointer_id); |
| |
| should_clear_pointer_state = true; |
| scroll_engine_->thread()->message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &ui_navigation::scroll_engine::ScrollEngine::HandleScrollStart, |
| base::Unretained(scroll_engine_), |
| element_to_scroll->GetUiNavItem(), scroll_type, pointer_id, |
| initial_coordinates.value(), initial_time_stamp.value(), |
| pointer_coordinates, pointer_event->time_stamp())); |
| } |
| } |
| |
| if (should_clear_pointer_state) { |
| pointer_state->ClearClientCoordinates(pointer_id); |
| pointer_state->ClearTimeStamp(pointer_id); |
| } |
| } |
| |
| namespace { |
| // Return the nearest common ancestor of previous_element and target_element |
| scoped_refptr<dom::Element> GetNearestCommonAncestor( |
| scoped_refptr<dom::HTMLElement> previous_element, |
| scoped_refptr<dom::HTMLElement> target_element) { |
| scoped_refptr<dom::Element> nearest_common_ancestor; |
| if (previous_element == target_element) { |
| nearest_common_ancestor = target_element; |
| } else { |
| if (previous_element && target_element) { |
| // Find the nearest common ancestor, if there is any. |
| dom::Document* previous_document = previous_element->node_document(); |
| // The elements only have a common ancestor if they are both in the same |
| // document. |
| if (previous_document && |
| previous_document == target_element->node_document()) { |
| // The nearest ancestor of the target element that is already |
| // designated is the nearest common ancestor of it and the previous |
| // element. |
| nearest_common_ancestor = target_element; |
| while (nearest_common_ancestor && |
| nearest_common_ancestor->AsHTMLElement() && |
| !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) { |
| nearest_common_ancestor = nearest_common_ancestor->parent_element(); |
| } |
| } |
| } |
| } |
| return nearest_common_ancestor; |
| } |
| |
| void SendStateChangeLeaveEvents( |
| bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element, |
| scoped_refptr<dom::HTMLElement> target_element, |
| scoped_refptr<dom::Element> nearest_common_ancestor, |
| dom::PointerEventInit* event_init) { |
| // Send enter/leave/over/out (status change) events when needed. |
| if (previous_element != target_element) { |
| const scoped_refptr<dom::Window>& view = event_init->view(); |
| |
| // Send out and leave events. |
| if (previous_element) { |
| // LottiePlayer elements may change playback state. |
| if (previous_element->AsLottiePlayer()) { |
| previous_element->AsLottiePlayer()->OnUnHover(); |
| } |
| |
| dom::Document* previous_document = previous_element->node_document(); |
| |
| event_init->set_related_target(target_element); |
| if (is_pointer_event) { |
| previous_element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerout(), view, *event_init)); |
| if (previous_document) { |
| for (scoped_refptr<dom::Element> element = previous_element; |
| element && element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| DCHECK(element->AsHTMLElement()->IsDesignated()); |
| element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerleave(), web::Event::kNotBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| } |
| } |
| } |
| |
| // Send compatibility mapping mouse events for state changes. |
| // https://www.w3.org/TR/pointerevents/#mapping-for-devices-that-do-not-support-hover |
| previous_element->DispatchEvent( |
| new dom::MouseEvent(base::Tokens::mouseout(), view, *event_init)); |
| |
| if (previous_document) { |
| for (scoped_refptr<dom::Element> element = previous_element; |
| element && element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| DCHECK(element->AsHTMLElement()->IsDesignated()); |
| element->DispatchEvent(new dom::MouseEvent( |
| base::Tokens::mouseleave(), web::Event::kNotBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| } |
| |
| if (!target_element || |
| previous_document != target_element->node_document()) { |
| previous_document->SetIndicatedElement(NULL); |
| } |
| } |
| } |
| } |
| } |
| |
| void SendStateChangeEnterEvents( |
| bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element, |
| scoped_refptr<dom::HTMLElement> target_element, |
| scoped_refptr<dom::Element> nearest_common_ancestor, |
| dom::PointerEventInit* event_init) { |
| // Send enter/leave/over/out (status change) events when needed. |
| if (previous_element != target_element) { |
| const scoped_refptr<dom::Window>& view = event_init->view(); |
| |
| // Send over and enter events. |
| if (target_element) { |
| // LottiePlayer elements may change playback state. |
| if (target_element->AsLottiePlayer()) { |
| target_element->AsLottiePlayer()->OnHover(); |
| } |
| |
| event_init->set_related_target(previous_element); |
| if (is_pointer_event) { |
| target_element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerover(), view, *event_init)); |
| for (scoped_refptr<dom::Element> element = target_element; |
| element && element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerenter(), web::Event::kNotBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| } |
| } |
| |
| // Send compatibility mapping mouse events for state changes. |
| // https://www.w3.org/TR/pointerevents/#mapping-for-devices-that-do-not-support-hover |
| target_element->DispatchEvent( |
| new dom::MouseEvent(base::Tokens::mouseover(), view, *event_init)); |
| for (scoped_refptr<dom::Element> element = target_element; |
| element && element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| element->DispatchEvent(new dom::MouseEvent( |
| base::Tokens::mouseenter(), web::Event::kNotBubbles, |
| web::Event::kNotCancelable, view, *event_init)); |
| } |
| } |
| } |
| } |
| |
| void SendCompatibilityMappingMouseEvent( |
| const scoped_refptr<dom::HTMLElement>& target_element, |
| const scoped_refptr<web::Event>& event, |
| const dom::PointerEvent* pointer_event, |
| const dom::PointerEventInit& event_init, |
| std::set<std::string>* mouse_event_prevent_flags) { |
| // Send compatibility mapping mouse event if needed. |
| // https://www.w3.org/TR/2015/REC-pointerevents-20150224/#compatibility-mapping-with-mouse-events |
| bool has_compatibility_mouse_event = true; |
| base::Token type = pointer_event->type(); |
| if (type == base::Tokens::pointerdown()) { |
| // If the pointer event dispatched was pointerdown and the event was |
| // canceled, then set the PREVENT MOUSE EVENT flag for this pointerType. |
| if (event->default_prevented()) { |
| mouse_event_prevent_flags->insert(pointer_event->pointer_type()); |
| has_compatibility_mouse_event = false; |
| } else { |
| type = base::Tokens::mousedown(); |
| } |
| } else { |
| has_compatibility_mouse_event = |
| mouse_event_prevent_flags->find(pointer_event->pointer_type()) == |
| mouse_event_prevent_flags->end(); |
| if (type == base::Tokens::pointerup()) { |
| // If the pointer event dispatched was pointerup, clear the PREVENT |
| // MOUSE EVENT flag for this pointerType. |
| mouse_event_prevent_flags->erase(pointer_event->pointer_type()); |
| type = base::Tokens::mouseup(); |
| } else if (type == base::Tokens::pointermove()) { |
| type = base::Tokens::mousemove(); |
| } else { |
| has_compatibility_mouse_event = false; |
| } |
| } |
| if (has_compatibility_mouse_event) { |
| target_element->DispatchEvent( |
| new dom::MouseEvent(type, event_init.view(), event_init)); |
| } |
| } |
| |
| void InitializePointerEventInitFromEvent( |
| const dom::MouseEvent* const mouse_event, |
| const dom::PointerEvent* pointer_event, dom::PointerEventInit* event_init) { |
| // For EventInit |
| event_init->set_bubbles(mouse_event->bubbles()); |
| event_init->set_cancelable(mouse_event->cancelable()); |
| |
| // For UIEventInit |
| event_init->set_view(mouse_event->view()); |
| event_init->set_detail(mouse_event->detail()); |
| event_init->set_which(mouse_event->which()); |
| |
| // For EventModifierInit |
| event_init->set_ctrl_key(mouse_event->ctrl_key()); |
| event_init->set_shift_key(mouse_event->shift_key()); |
| event_init->set_alt_key(mouse_event->alt_key()); |
| event_init->set_meta_key(mouse_event->meta_key()); |
| |
| // For MouseEventInit |
| event_init->set_screen_x(mouse_event->screen_x()); |
| event_init->set_screen_y(mouse_event->screen_y()); |
| event_init->set_client_x(mouse_event->screen_x()); |
| event_init->set_client_y(mouse_event->screen_y()); |
| event_init->set_button(mouse_event->button()); |
| event_init->set_buttons(mouse_event->buttons()); |
| event_init->set_related_target(mouse_event->related_target()); |
| if (pointer_event) { |
| // For PointerEventInit |
| event_init->set_pointer_id(pointer_event->pointer_id()); |
| event_init->set_width(pointer_event->width()); |
| event_init->set_height(pointer_event->height()); |
| event_init->set_pressure(pointer_event->pressure()); |
| event_init->set_tilt_x(pointer_event->tilt_x()); |
| event_init->set_tilt_y(pointer_event->tilt_y()); |
| event_init->set_pointer_type(pointer_event->pointer_type()); |
| event_init->set_is_primary(pointer_event->is_primary()); |
| } |
| } |
| } // namespace |
| |
| TopmostEventTarget::TopmostEventTarget( |
| ui_navigation::scroll_engine::ScrollEngine* scroll_engine) |
| : scroll_engine_(scroll_engine) {} |
| |
| void TopmostEventTarget::MaybeSendPointerEvents( |
| const scoped_refptr<web::Event>& event) { |
| TRACE_EVENT0("cobalt::layout", |
| "TopmostEventTarget::MaybeSendPointerEvents()"); |
| |
| const dom::MouseEvent* const mouse_event = |
| base::polymorphic_downcast<const dom::MouseEvent* const>(event.get()); |
| DCHECK(mouse_event); |
| DCHECK(!html_element_); |
| const dom::PointerEvent* pointer_event = |
| (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) |
| ? base::polymorphic_downcast<const dom::PointerEvent* const>( |
| event.get()) |
| : NULL; |
| bool is_touchpad_event = false; |
| |
| // The target override element for the pointer event. This may not be the same |
| // as the hit test target, and it also may not be set. |
| scoped_refptr<dom::HTMLElement> target_override_element; |
| |
| // Store the data for the status change and pointer capture event(s). |
| dom::PointerEventInit event_init; |
| InitializePointerEventInitFromEvent(mouse_event, pointer_event, &event_init); |
| const scoped_refptr<dom::Window>& view = event_init.view(); |
| if (!view) { |
| return; |
| } |
| dom::PointerState* pointer_state = view->document()->pointer_state(); |
| if (pointer_event) { |
| pointer_state->SetActiveButtonsState(pointer_event->pointer_id(), |
| pointer_event->buttons()); |
| is_touchpad_event = pointer_event->pointer_type() == "touchpad"; |
| if (is_touchpad_event) { |
| if (pointer_event->type() == base::Tokens::pointerdown()) { |
| pointer_state->SetActive(pointer_event->pointer_id()); |
| // Implicitly capture the pointer to the active element. |
| // https://www.w3.org/TR/pointerevents/#implicit-pointer-capture |
| scoped_refptr<dom::HTMLElement> html_element; |
| if (view->document()->active_element()) { |
| html_element = view->document()->active_element()->AsHTMLElement(); |
| } |
| if (html_element) { |
| pointer_state->SetPendingPointerCaptureTargetOverride( |
| pointer_event->pointer_id(), html_element); |
| } |
| } |
| } else { |
| pointer_state->SetActive(pointer_event->pointer_id()); |
| } |
| target_override_element = pointer_state->GetPointerCaptureOverrideElement( |
| pointer_event->pointer_id(), &event_init); |
| } |
| |
| scoped_refptr<dom::HTMLElement> target_element; |
| if (target_override_element) { |
| target_element = target_override_element; |
| } else { |
| // Do a hit test if there is no target override element. |
| math::Vector2dF coordinate(static_cast<float>(event_init.client_x()), |
| static_cast<float>(event_init.client_y())); |
| target_element = FindTopmostEventTarget(view->document(), coordinate); |
| } |
| |
| if (target_element && pointer_event) { |
| HandleScrollState(target_element, pointer_event, pointer_state, |
| &event_init); |
| } |
| |
| scoped_refptr<dom::HTMLElement> previous_html_element( |
| previous_html_element_weak_); |
| |
| // The enter/leave status change events apply to all ancestors up to the |
| // nearest common ancestor between the previous and current element. |
| scoped_refptr<dom::Element> nearest_common_ancestor( |
| GetNearestCommonAncestor(previous_html_element, target_element)); |
| |
| SendStateChangeLeaveEvents(pointer_event, previous_html_element, |
| target_element, nearest_common_ancestor, |
| &event_init); |
| |
| bool event_was_cancelled = pointer_event && pointer_state->GetWasCancelled( |
| pointer_event->pointer_id()); |
| if (pointer_event && pointer_event->type() == base::Tokens::pointerup()) { |
| pointer_state->ClearWasCancelled(pointer_event->pointer_id()); |
| } |
| |
| if (target_element) { |
| if (!event_was_cancelled) { |
| target_element->DispatchEvent(event); |
| } |
| } |
| |
| if (pointer_event) { |
| if (pointer_event->type() == base::Tokens::pointerup()) { |
| if (is_touchpad_event) { |
| // A touchpad becomes inactive after a pointerup. |
| pointer_state->ClearActive(pointer_event->pointer_id()); |
| } |
| // Implicit release of pointer capture. |
| // https://www.w3.org/TR/pointerevents/#implicit-release-of-pointer-capture |
| pointer_state->ClearPendingPointerCaptureTargetOverride( |
| pointer_event->pointer_id()); |
| } |
| if (target_element && !is_touchpad_event && !event_was_cancelled) { |
| SendCompatibilityMappingMouseEvent(target_element, event, pointer_event, |
| event_init, |
| &mouse_event_prevent_flags_); |
| } |
| } |
| |
| if (event_init.button() == 0 && |
| ((mouse_event->type() == base::Tokens::pointerup()) || |
| (mouse_event->type() == base::Tokens::mouseup()))) { |
| // This is an 'up' event for the last pressed button indicating that no |
| // more buttons are pressed. |
| if (target_element && !is_touchpad_event) { |
| // Send the click event if needed, which is not prevented by cancelling |
| // the pointerdown event. |
| // https://www.w3.org/TR/uievents/#event-type-click |
| // https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events |
| target_element->DispatchEvent( |
| new dom::MouseEvent(base::Tokens::click(), view, event_init)); |
| } |
| if (target_element && (pointer_event->pointer_type() != "mouse")) { |
| // If it's not a mouse event, then releasing the last button means |
| // that there is no longer an indicated element. |
| dom::Document* document = target_element->node_document(); |
| if (document) { |
| document->SetIndicatedElement(NULL); |
| target_element = NULL; |
| } |
| } |
| } |
| |
| SendStateChangeEnterEvents(pointer_event, previous_html_element, |
| target_element, nearest_common_ancestor, |
| &event_init); |
| |
| if (target_element) { |
| // Touchpad input never indicates document elements. |
| if (!is_touchpad_event) { |
| dom::Document* document = target_element->node_document(); |
| if (document) { |
| document->SetIndicatedElement(target_element); |
| } |
| } |
| previous_html_element_weak_ = base::AsWeakPtr(target_element.get()); |
| } else { |
| previous_html_element_weak_.reset(); |
| } |
| DCHECK(!html_element_); |
| } |
| |
| } // namespace layout |
| } // namespace cobalt |