blob: cdc086548f4eb1b111ac3c68fb0f46452fc6ead6 [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 "core/layout/HitTestResult.h"
#include "core/dom/FlatTreeTraversal.h"
#include "core/dom/PseudoElement.h"
#include "core/dom/ShadowRoot.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/editing/VisibleUnits.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLAreaElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLMapElement.h"
#include "core/html/forms/HTMLInputElement.h"
#include "core/html/forms/HTMLTextAreaElement.h"
#include "core/html/media/HTMLMediaElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html_names.h"
#include "core/input_type_names.h"
#include "core/layout/LayoutImage.h"
#include "core/svg/SVGElement.h"
#include "platform/geometry/Region.h"
#include "platform/scroll/Scrollbar.h"
namespace blink {
using namespace HTMLNames;
HitTestResult::HitTestResult()
: hit_test_request_(HitTestRequest::kReadOnly | HitTestRequest::kActive),
cacheable_(true),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestRequest& request,
const LayoutPoint& point)
: hit_test_location_(point),
hit_test_request_(request),
cacheable_(true),
point_in_inner_node_frame_(point),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestRequest& request,
const LayoutPoint& center_point,
unsigned top_padding,
unsigned right_padding,
unsigned bottom_padding,
unsigned left_padding)
: hit_test_location_(center_point,
top_padding,
right_padding,
bottom_padding,
left_padding),
hit_test_request_(request),
cacheable_(true),
point_in_inner_node_frame_(center_point),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestRequest& other_request,
const HitTestLocation& other)
: hit_test_location_(other),
hit_test_request_(other_request),
cacheable_(true),
point_in_inner_node_frame_(hit_test_location_.Point()),
is_over_embedded_content_view_(false) {}
HitTestResult::HitTestResult(const HitTestResult& other)
: hit_test_location_(other.hit_test_location_),
hit_test_request_(other.hit_test_request_),
cacheable_(other.cacheable_),
inner_node_(other.InnerNode()),
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_
? new NodeSet(*other.list_based_test_result_)
: nullptr;
}
HitTestResult::~HitTestResult() = default;
HitTestResult& HitTestResult::operator=(const HitTestResult& other) {
hit_test_location_ = other.hit_test_location_;
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_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) {
*this = other;
hit_test_request_ =
other.hit_test_request_.GetType() & ~HitTestRequest::kAvoidCache;
}
void HitTestResult::PopulateFromCachedResult(const HitTestResult& other) {
inner_node_ = other.InnerNode();
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_
? new NodeSet(*other.list_based_test_result_)
: nullptr;
}
void HitTestResult::Trace(blink::Visitor* visitor) {
visitor->Trace(inner_node_);
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 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(usemapAttr));
if (!map)
return nullptr;
return map->AreaForPoint(LocalPoint(), image_element->GetLayoutObject());
}
void HitTestResult::SetInnerNode(Node* n) {
inner_possibly_pseudo_node_ = n;
if (!n) {
inner_node_ = n;
return;
}
if (n->IsPseudoElement())
n = ToPseudoElement(n)->FindAssociatedNode();
inner_node_ = n;
if (HTMLAreaElement* area = ImageAreaForImage()) {
inner_node_ = area;
inner_possibly_pseudo_node_ = area;
}
}
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 {
if (!inner_node_)
return false;
if (LocalFrame* frame = inner_node_->GetDocument().GetFrame())
return frame->Selection().Contains(hit_test_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_->UpdateDistribution();
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->Style()->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(altAttr);
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() ==
InputTypeNames::image))
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();
}
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(LayoutRect(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.BoundingBox()) ? 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_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_ = new NodeSet;
return *list_based_test_result_;
}
HitTestResult::NodeSet& HitTestResult::MutableListBasedTestResult() {
if (!list_based_test_result_)
list_based_test_result_ = new NodeSet;
return *list_based_test_result_;
}
void HitTestResult::ResolveRectBasedTest(
Node* resolved_inner_node,
const LayoutPoint& resolved_point_in_main_frame) {
DCHECK(IsRectBasedTest());
DCHECK(hit_test_location_.ContainsPoint(
FloatPoint(resolved_point_in_main_frame)));
hit_test_location_ = HitTestLocation(resolved_point_in_main_frame);
point_in_inner_node_frame_ = resolved_point_in_main_frame;
inner_node_ = nullptr;
inner_possibly_pseudo_node_ = 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.
resolved_inner_node->GetLayoutObject()->UpdateHitTestResult(*this,
LayoutPoint());
DCHECK(!IsRectBasedTest());
}
Element* HitTestResult::InnerElement() const {
if (!inner_node_)
return nullptr;
if (inner_node_->IsElementNode())
return ToElement(inner_node_);
return FlatTreeTraversal::ParentElement(*inner_node_);
}
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