blob: f65755850523333f4743f40161bfa5e10be71f66 [file] [log] [blame]
/*
* Copyright (C) 2006, 2008, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/renderer/core/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/html_map_element.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/scroll/scrollbar.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/platform/geometry/region.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
namespace blink {
using namespace html_names;
HitTestResult::HitTestResult()
: hit_test_request_(HitTestRequest::kReadOnly | HitTestRequest::kActive),
cacheable_(true),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestRequest& other_request,
const HitTestLocation& location)
: hit_test_request_(other_request),
cacheable_(true),
point_in_inner_node_frame_(location.Point()),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestResult& other)
: hit_test_request_(other.hit_test_request_),
cacheable_(other.cacheable_),
inner_node_(other.InnerNode()),
inner_element_(other.InnerElement()),
inner_possibly_pseudo_node_(other.inner_possibly_pseudo_node_),
point_in_inner_node_frame_(other.point_in_inner_node_frame_),
local_point_(other.LocalPoint()),
inner_url_element_(other.URLElement()),
scrollbar_(other.GetScrollbar()),
is_over_embedded_content_view_(other.IsOverEmbeddedContentView()),
canvas_region_id_(other.CanvasRegionId()) {
// Only copy the NodeSet in case of list hit test.
list_based_test_result_ =
other.list_based_test_result_
? MakeGarbageCollected<NodeSet>(*other.list_based_test_result_)
: nullptr;
}
HitTestResult::~HitTestResult() = default;
HitTestResult& HitTestResult::operator=(const HitTestResult& other) {
hit_test_request_ = other.hit_test_request_;
PopulateFromCachedResult(other);
return *this;
}
bool HitTestResult::EqualForCacheability(const HitTestResult& other) const {
return hit_test_request_.EqualForCacheability(other.hit_test_request_) &&
inner_node_ == other.InnerNode() &&
inner_element_ == other.InnerElement() &&
inner_possibly_pseudo_node_ == other.InnerPossiblyPseudoNode() &&
point_in_inner_node_frame_ == other.point_in_inner_node_frame_ &&
local_point_ == other.LocalPoint() &&
inner_url_element_ == other.URLElement() &&
scrollbar_ == other.GetScrollbar() &&
is_over_embedded_content_view_ == other.IsOverEmbeddedContentView();
}
void HitTestResult::CacheValues(const HitTestResult& other) {
hit_test_request_ =
other.hit_test_request_.GetType() & ~HitTestRequest::kAvoidCache;
}
void HitTestResult::PopulateFromCachedResult(const HitTestResult& other) {
inner_node_ = other.InnerNode();
inner_element_ = other.InnerElement();
inner_possibly_pseudo_node_ = other.InnerPossiblyPseudoNode();
point_in_inner_node_frame_ = other.point_in_inner_node_frame_;
local_point_ = other.LocalPoint();
inner_url_element_ = other.URLElement();
scrollbar_ = other.GetScrollbar();
is_over_embedded_content_view_ = other.IsOverEmbeddedContentView();
cacheable_ = other.cacheable_;
canvas_region_id_ = other.CanvasRegionId();
// Only copy the NodeSet in case of list hit test.
list_based_test_result_ =
other.list_based_test_result_
? MakeGarbageCollected<NodeSet>(*other.list_based_test_result_)
: nullptr;
}
void HitTestResult::Trace(blink::Visitor* visitor) {
visitor->Trace(inner_node_);
visitor->Trace(inner_element_);
visitor->Trace(inner_possibly_pseudo_node_);
visitor->Trace(inner_url_element_);
visitor->Trace(scrollbar_);
visitor->Trace(list_based_test_result_);
}
PositionWithAffinity HitTestResult::GetPosition() const {
if (!inner_possibly_pseudo_node_)
return PositionWithAffinity();
LayoutObject* layout_object = GetLayoutObject();
if (!layout_object)
return PositionWithAffinity();
if (inner_possibly_pseudo_node_->IsPseudoElement() &&
inner_possibly_pseudo_node_->GetPseudoId() == kPseudoIdBefore) {
return PositionWithAffinity(MostForwardCaretPosition(
Position(inner_node_, PositionAnchorType::kBeforeChildren)));
}
return layout_object->PositionForPoint(LocalPoint());
}
LayoutObject* HitTestResult::GetLayoutObject() const {
return inner_node_ ? inner_node_->GetLayoutObject() : nullptr;
}
void HitTestResult::SetToShadowHostIfInRestrictedShadowRoot() {
Node* node = InnerNode();
if (!node)
return;
ShadowRoot* containing_shadow_root = node->ContainingShadowRoot();
Element* shadow_host = nullptr;
// Consider a closed shadow tree of SVG's <use> element as a special
// case so that a toolip title in the shadow tree works.
while (containing_shadow_root &&
(containing_shadow_root->IsUserAgent() ||
IsSVGUseElement(containing_shadow_root->host()))) {
shadow_host = &containing_shadow_root->host();
containing_shadow_root = shadow_host->ContainingShadowRoot();
SetInnerNode(node->OwnerShadowHost());
}
if (shadow_host)
SetInnerNode(shadow_host);
}
HTMLAreaElement* HitTestResult::ImageAreaForImage() const {
DCHECK(inner_node_);
HTMLImageElement* image_element = ToHTMLImageElementOrNull(inner_node_);
if (!image_element && inner_node_->IsInShadowTree()) {
if (inner_node_->ContainingShadowRoot()->IsUserAgent()) {
image_element = ToHTMLImageElementOrNull(inner_node_->OwnerShadowHost());
}
}
if (!image_element || !image_element->GetLayoutObject() ||
!image_element->GetLayoutObject()->IsBox())
return nullptr;
HTMLMapElement* map = image_element->GetTreeScope().GetImageMap(
image_element->FastGetAttribute(kUsemapAttr));
if (!map)
return nullptr;
return map->AreaForPoint(LocalPoint(), image_element->GetLayoutObject());
}
void HitTestResult::SetInnerNode(Node* n) {
if (!n) {
inner_possibly_pseudo_node_ = nullptr;
inner_node_ = nullptr;
inner_element_ = nullptr;
return;
}
inner_possibly_pseudo_node_ = n;
if (n->IsPseudoElement())
n = ToPseudoElement(n)->InnerNodeForHitTesting();
inner_node_ = n;
if (HTMLAreaElement* area = ImageAreaForImage()) {
inner_node_ = area;
inner_possibly_pseudo_node_ = area;
}
if (inner_node_->IsElementNode())
inner_element_ = ToElement(inner_node_);
else
inner_element_ = FlatTreeTraversal::ParentElement(*inner_node_);
}
void HitTestResult::SetURLElement(Element* n) {
inner_url_element_ = n;
}
void HitTestResult::SetScrollbar(Scrollbar* s) {
scrollbar_ = s;
}
LocalFrame* HitTestResult::InnerNodeFrame() const {
if (inner_node_)
return inner_node_->GetDocument().GetFrame();
return nullptr;
}
bool HitTestResult::IsSelected(const HitTestLocation& location) const {
if (!inner_node_)
return false;
if (LocalFrame* frame = inner_node_->GetDocument().GetFrame())
return frame->Selection().Contains(location.Point());
return false;
}
String HitTestResult::Title(TextDirection& dir) const {
dir = TextDirection::kLtr;
// Find the title in the nearest enclosing DOM node.
// For <area> tags in image maps, walk the tree for the <area>, not the <img>
// using it.
if (inner_node_.Get())
inner_node_->UpdateDistributionForFlatTreeTraversal();
for (Node* title_node = inner_node_.Get(); title_node;
title_node = FlatTreeTraversal::Parent(*title_node)) {
if (title_node->IsElementNode()) {
String title = ToElement(title_node)->title();
if (!title.IsNull()) {
if (LayoutObject* layout_object = title_node->GetLayoutObject())
dir = layout_object->StyleRef().Direction();
return title;
}
}
}
return String();
}
const AtomicString& HitTestResult::AltDisplayString() const {
Node* inner_node_or_image_map_image = InnerNodeOrImageMapImage();
if (!inner_node_or_image_map_image)
return g_null_atom;
if (auto* image = ToHTMLImageElementOrNull(*inner_node_or_image_map_image))
return image->getAttribute(kAltAttr);
if (auto* input = ToHTMLInputElementOrNull(*inner_node_or_image_map_image))
return input->Alt();
return g_null_atom;
}
Image* HitTestResult::GetImage() const {
Node* inner_node_or_image_map_image = InnerNodeOrImageMapImage();
if (!inner_node_or_image_map_image)
return nullptr;
LayoutObject* layout_object =
inner_node_or_image_map_image->GetLayoutObject();
if (layout_object && layout_object->IsImage()) {
LayoutImage* image = ToLayoutImage(layout_object);
if (image->CachedImage() && !image->CachedImage()->ErrorOccurred())
return image->CachedImage()->GetImage();
}
return nullptr;
}
IntRect HitTestResult::ImageRect() const {
if (!GetImage())
return IntRect();
return InnerNodeOrImageMapImage()
->GetLayoutBox()
->AbsoluteContentQuad()
.EnclosingBoundingBox();
}
KURL HitTestResult::AbsoluteImageURL() const {
Node* inner_node_or_image_map_image = InnerNodeOrImageMapImage();
if (!inner_node_or_image_map_image)
return KURL();
AtomicString url_string;
// Always return a url for image elements and input elements with type=image,
// even if they don't have a LayoutImage (e.g. because the image didn't load
// and we are using an alt container). For other elements we don't create alt
// containers so ensure they contain a loaded image.
if (IsHTMLImageElement(*inner_node_or_image_map_image) ||
(IsHTMLInputElement(*inner_node_or_image_map_image) &&
ToHTMLInputElement(inner_node_or_image_map_image)->type() ==
input_type_names::kImage))
url_string = ToElement(*inner_node_or_image_map_image).ImageSourceURL();
else if ((inner_node_or_image_map_image->GetLayoutObject() &&
inner_node_or_image_map_image->GetLayoutObject()->IsImage()) &&
(IsHTMLEmbedElement(*inner_node_or_image_map_image) ||
IsHTMLObjectElement(*inner_node_or_image_map_image) ||
IsSVGImageElement(*inner_node_or_image_map_image)))
url_string = ToElement(*inner_node_or_image_map_image).ImageSourceURL();
if (url_string.IsEmpty())
return KURL();
return inner_node_or_image_map_image->GetDocument().CompleteURL(
StripLeadingAndTrailingHTMLSpaces(url_string));
}
KURL HitTestResult::AbsoluteMediaURL() const {
if (HTMLMediaElement* media_elt = MediaElement())
return media_elt->currentSrc();
return KURL();
}
MediaStreamDescriptor* HitTestResult::GetMediaStreamDescriptor() const {
if (HTMLMediaElement* media_elt = MediaElement())
return media_elt->GetSrcObject();
return nullptr;
}
HTMLMediaElement* HitTestResult::MediaElement() const {
if (!inner_node_)
return nullptr;
if (!(inner_node_->GetLayoutObject() &&
inner_node_->GetLayoutObject()->IsMedia()))
return nullptr;
if (IsHTMLMediaElement(*inner_node_))
return ToHTMLMediaElement(inner_node_);
return nullptr;
}
KURL HitTestResult::AbsoluteLinkURL() const {
if (!inner_url_element_)
return KURL();
return inner_url_element_->HrefURL();
}
bool HitTestResult::IsLiveLink() const {
return inner_url_element_ && inner_url_element_->IsLiveLink();
}
bool HitTestResult::IsOverLink() const {
return inner_url_element_ && inner_url_element_->IsLink();
}
String HitTestResult::TextContent() const {
if (!inner_url_element_)
return String();
return inner_url_element_->textContent();
}
// FIXME: This function needs a better name and may belong in a different class.
// It's not really isContentEditable(); it's more like needsEditingContextMenu.
// In many ways, this function would make more sense in the ContextMenu class,
// except that WebElementDictionary hooks into it. Anyway, we should architect
// this better.
bool HitTestResult::IsContentEditable() const {
if (!inner_node_)
return false;
if (auto* textarea = ToHTMLTextAreaElementOrNull(*inner_node_))
return !textarea->IsDisabledOrReadOnly();
if (auto* input = ToHTMLInputElementOrNull(*inner_node_))
return !input->IsDisabledOrReadOnly() && input->IsTextField();
return HasEditableStyle(*inner_node_);
}
ListBasedHitTestBehavior HitTestResult::AddNodeToListBasedTestResult(
Node* node,
const HitTestLocation& location,
const LayoutRect& rect) {
// If not a list-based test, stop testing because the hit has been found.
if (!GetHitTestRequest().ListBased())
return kStopHitTesting;
if (!node)
return kContinueHitTesting;
MutableListBasedTestResult().insert(node);
if (GetHitTestRequest().PenetratingList())
return kContinueHitTesting;
return rect.Contains(location.BoundingBox()) ? kStopHitTesting
: kContinueHitTesting;
}
ListBasedHitTestBehavior HitTestResult::AddNodeToListBasedTestResult(
Node* node,
const HitTestLocation& location,
const Region& region) {
// If not a list-based test, stop testing because the hit has been found.
if (!GetHitTestRequest().ListBased())
return kStopHitTesting;
if (!node)
return kContinueHitTesting;
MutableListBasedTestResult().insert(node);
if (GetHitTestRequest().PenetratingList())
return kContinueHitTesting;
return region.Contains(location.EnclosingIntRect()) ? kStopHitTesting
: kContinueHitTesting;
}
void HitTestResult::Append(const HitTestResult& other) {
DCHECK(GetHitTestRequest().ListBased());
if (!scrollbar_ && other.GetScrollbar()) {
SetScrollbar(other.GetScrollbar());
}
if (!inner_node_ && other.InnerNode()) {
inner_node_ = other.InnerNode();
inner_element_ = other.InnerElement();
inner_possibly_pseudo_node_ = other.InnerPossiblyPseudoNode();
local_point_ = other.LocalPoint();
point_in_inner_node_frame_ = other.point_in_inner_node_frame_;
inner_url_element_ = other.URLElement();
is_over_embedded_content_view_ = other.IsOverEmbeddedContentView();
canvas_region_id_ = other.CanvasRegionId();
}
if (other.list_based_test_result_) {
NodeSet& set = MutableListBasedTestResult();
for (NodeSet::const_iterator it = other.list_based_test_result_->begin(),
last = other.list_based_test_result_->end();
it != last; ++it)
set.insert(it->Get());
}
}
const HitTestResult::NodeSet& HitTestResult::ListBasedTestResult() const {
if (!list_based_test_result_)
list_based_test_result_ = MakeGarbageCollected<NodeSet>();
return *list_based_test_result_;
}
HitTestResult::NodeSet& HitTestResult::MutableListBasedTestResult() {
if (!list_based_test_result_)
list_based_test_result_ = MakeGarbageCollected<NodeSet>();
return *list_based_test_result_;
}
HitTestLocation HitTestResult::ResolveRectBasedTest(
Node* resolved_inner_node,
const LayoutPoint& resolved_point_in_main_frame) {
point_in_inner_node_frame_ = resolved_point_in_main_frame;
SetInnerNode(nullptr);
list_based_test_result_ = nullptr;
// Update the HitTestResult as if the supplied node had been hit in normal
// point-based hit-test.
// Note that we don't know the local point after a rect-based hit-test, but we
// never use it so shouldn't bother with the cost of computing it.
DCHECK(resolved_inner_node);
if (auto* layout_object = resolved_inner_node->GetLayoutObject())
layout_object->UpdateHitTestResult(*this, LayoutPoint());
return HitTestLocation(resolved_point_in_main_frame);
}
Node* HitTestResult::InnerNodeOrImageMapImage() const {
if (!inner_node_)
return nullptr;
HTMLImageElement* image_map_image_element = nullptr;
if (auto* area = ToHTMLAreaElementOrNull(inner_node_))
image_map_image_element = area->ImageElement();
else if (auto* map = ToHTMLMapElementOrNull(inner_node_))
image_map_image_element = map->ImageElement();
if (!image_map_image_element)
return inner_node_.Get();
return image_map_image_element;
}
} // namespace blink