| // Copyright 2017 Google Inc. 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 <string> |
| |
| #include "cobalt/base/token.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/cssom/keyword_value.h" |
| #include "cobalt/dom/document.h" |
| #include "cobalt/dom/event.h" |
| #include "cobalt/dom/html_element.h" |
| #include "cobalt/dom/html_html_element.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/ui_event.h" |
| #include "cobalt/dom/wheel_event.h" |
| #include "cobalt/layout/container_box.h" |
| #include "cobalt/layout/used_style.h" |
| #include "cobalt/math/vector2d.h" |
| #include "cobalt/math/vector2d_f.h" |
| |
| namespace cobalt { |
| namespace layout { |
| |
| void TopmostEventTarget::FindTopmostEventTarget( |
| const scoped_refptr<dom::Document>& document, |
| const math::Vector2dF& coordinate) { |
| const scoped_refptr<dom::HTMLElement>& html_element = document->html(); |
| DCHECK(!box_); |
| DCHECK(render_sequence_.empty()); |
| html_element_ = html_element; |
| if (html_element) { |
| dom::LayoutBoxes* boxes = html_element->layout_boxes(); |
| if (boxes && boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) { |
| LayoutBoxes* layout_boxes = base::polymorphic_downcast<LayoutBoxes*>( |
| html_element->layout_boxes()); |
| if (!layout_boxes->boxes().empty()) { |
| ConsiderElement(html_element, coordinate); |
| } |
| } |
| } |
| box_ = NULL; |
| render_sequence_.clear(); |
| } |
| |
| void TopmostEventTarget::ConsiderElement( |
| const scoped_refptr<dom::HTMLElement>& html_element, |
| const math::Vector2dF& coordinate) { |
| if (!html_element) return; |
| math::Vector2dF element_coordinate(coordinate); |
| if (html_element->CanbeDesignatedByPointerIfDisplayed()) { |
| dom::LayoutBoxes* dom_layout_boxes = html_element->layout_boxes(); |
| if (dom_layout_boxes && |
| dom_layout_boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) { |
| DCHECK(html_element->computed_style()); |
| LayoutBoxes* layout_boxes = |
| base::polymorphic_downcast<LayoutBoxes*>(dom_layout_boxes); |
| const Boxes& boxes = layout_boxes->boxes(); |
| if (!boxes.empty()) { |
| const Box* box = boxes.front(); |
| box->UpdateCoordinateForTransform(&element_coordinate); |
| |
| if (box->computed_style()->position() == |
| cssom::KeywordValue::GetAbsolute()) { |
| // The containing block for position:absolute elements is formed by |
| // the padding box instead of the content box, as described in |
| // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details. |
| element_coordinate += |
| box->GetContainingBlock()->GetContentBoxOffsetFromPaddingBox(); |
| } |
| ConsiderBoxes(html_element, layout_boxes, element_coordinate); |
| } |
| } |
| } |
| |
| for (dom::Element* element = html_element->first_element_child(); element; |
| element = element->next_element_sibling()) { |
| dom::HTMLElement* child_html_element = element->AsHTMLElement(); |
| if (child_html_element && child_html_element->computed_style()) { |
| ConsiderElement(child_html_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) { |
| const scoped_refptr<Box>& box = *box_iterator; |
| 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); |
| } |
| } |
| } |
| } |
| |
| void TopmostEventTarget::MaybeSendPointerEvents( |
| const scoped_refptr<dom::Event>& event) { |
| const dom::MouseEvent* const mouse_event = |
| base::polymorphic_downcast<const dom::MouseEvent* const>(event.get()); |
| DCHECK(mouse_event); |
| DCHECK(!html_element_); |
| scoped_refptr<dom::Window> view = mouse_event->view(); |
| |
| math::Vector2dF coordinate(static_cast<float>(mouse_event->client_x()), |
| static_cast<float>(mouse_event->client_y())); |
| FindTopmostEventTarget(view->document(), coordinate); |
| |
| if (html_element_) { |
| html_element_->DispatchEvent(event); |
| } |
| if (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) { |
| const dom::PointerEvent* const pointer_event = |
| base::polymorphic_downcast<const dom::PointerEvent* const>(event.get()); |
| |
| // Send compatibility mapping mouse events if needed. |
| // https://www.w3.org/TR/2015/REC-pointerevents-20150224/#compatibility-mapping-with-mouse-events |
| if (html_element_) { |
| bool has_compatibility_mouse_event = false; |
| base::Token type; |
| if (pointer_event->type() == base::Tokens::pointerdown()) { |
| type = base::Tokens::mousedown(); |
| has_compatibility_mouse_event = true; |
| } else if (pointer_event->type() == base::Tokens::pointerup()) { |
| type = base::Tokens::mouseup(); |
| has_compatibility_mouse_event = true; |
| } else if (pointer_event->type() == base::Tokens::pointermove()) { |
| type = base::Tokens::mousemove(); |
| has_compatibility_mouse_event = true; |
| } |
| if (has_compatibility_mouse_event) { |
| dom::MouseEventInit mouse_event_init; |
| mouse_event_init.set_screen_x(pointer_event->screen_x()); |
| mouse_event_init.set_screen_y(pointer_event->screen_y()); |
| mouse_event_init.set_client_x(pointer_event->screen_x()); |
| mouse_event_init.set_client_y(pointer_event->screen_y()); |
| mouse_event_init.set_button(pointer_event->button()); |
| mouse_event_init.set_buttons(pointer_event->buttons()); |
| html_element_->DispatchEvent( |
| new dom::MouseEvent(type, view, mouse_event_init)); |
| if (pointer_event->type() == base::Tokens::pointerup()) { |
| type = base::Tokens::click(); |
| html_element_->DispatchEvent( |
| new dom::MouseEvent(type, view, mouse_event_init)); |
| } |
| } |
| } |
| |
| scoped_refptr<dom::HTMLElement> previous_html_element( |
| previous_html_element_weak_); |
| |
| // Send enter/leave/over/out (status change) events when needed. |
| if (previous_html_element != html_element_) { |
| // Store the data for the status change event(s). |
| dom::PointerEventInit event_init; |
| event_init.set_related_target(previous_html_element); |
| 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()); |
| if (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) { |
| 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()); |
| } |
| |
| // 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; |
| |
| if (previous_html_element) { |
| previous_html_element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerout(), view, event_init)); |
| previous_html_element->DispatchEvent( |
| new dom::MouseEvent(base::Tokens::mouseout(), view, event_init)); |
| |
| // Find the nearest common ancestor, if there is any. |
| dom::Document* previous_document = |
| previous_html_element->node_document(); |
| if (previous_document) { |
| if (html_element_ && |
| previous_document == html_element_->node_document()) { |
| // The nearest ancestor of the current element that is already |
| // designated is the nearest common ancestor of it and the previous |
| // element. |
| nearest_common_ancestor = html_element_; |
| while (nearest_common_ancestor && |
| nearest_common_ancestor->AsHTMLElement() && |
| !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) { |
| nearest_common_ancestor = |
| nearest_common_ancestor->parent_element(); |
| } |
| } |
| |
| for (scoped_refptr<dom::Element> element = previous_html_element; |
| element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerleave(), dom::Event::kNotBubbles, |
| dom::Event::kNotCancelable, view, event_init)); |
| element->DispatchEvent(new dom::MouseEvent( |
| base::Tokens::mouseleave(), dom::Event::kNotBubbles, |
| dom::Event::kNotCancelable, view, event_init)); |
| } |
| |
| if (!html_element_ || |
| previous_document != html_element_->node_document()) { |
| previous_document->SetIndicatedElement(NULL); |
| } |
| } |
| } |
| if (html_element_) { |
| html_element_->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerover(), view, event_init)); |
| html_element_->DispatchEvent( |
| new dom::MouseEvent(base::Tokens::mouseover(), view, event_init)); |
| |
| for (scoped_refptr<dom::Element> element = html_element_; |
| element != nearest_common_ancestor; |
| element = element->parent_element()) { |
| element->DispatchEvent(new dom::PointerEvent( |
| base::Tokens::pointerenter(), dom::Event::kNotBubbles, |
| dom::Event::kNotCancelable, view, event_init)); |
| element->DispatchEvent(new dom::MouseEvent( |
| base::Tokens::mouseenter(), dom::Event::kNotBubbles, |
| dom::Event::kNotCancelable, view, event_init)); |
| } |
| |
| dom::Document* document = html_element_->node_document(); |
| if (document) { |
| document->SetIndicatedElement(html_element_); |
| } |
| } |
| previous_html_element_weak_ = base::AsWeakPtr(html_element_.get()); |
| } |
| } |
| html_element_ = NULL; |
| } |
| |
| } // namespace layout |
| } // namespace cobalt |