| /* |
| * 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/HTMLNames.h" |
| #include "core/InputTypeNames.h" |
| #include "core/dom/PseudoElement.h" |
| #include "core/dom/shadow/FlatTreeTraversal.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLAreaElement.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLMapElement.h" |
| #include "core/html/HTMLMediaElement.h" |
| #include "core/html/HTMLTextAreaElement.h" |
| #include "core/html/parser/HTMLParserIdioms.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() |
| : m_hitTestRequest(HitTestRequest::ReadOnly | HitTestRequest::Active), |
| m_cacheable(true), |
| m_isOverWidget(false) {} |
| |
| HitTestResult::HitTestResult(const HitTestRequest& request, |
| const LayoutPoint& point) |
| : m_hitTestLocation(point), |
| m_hitTestRequest(request), |
| m_cacheable(true), |
| m_pointInInnerNodeFrame(point), |
| m_isOverWidget(false) {} |
| |
| HitTestResult::HitTestResult(const HitTestRequest& request, |
| const LayoutPoint& centerPoint, |
| unsigned topPadding, |
| unsigned rightPadding, |
| unsigned bottomPadding, |
| unsigned leftPadding) |
| : m_hitTestLocation(centerPoint, |
| topPadding, |
| rightPadding, |
| bottomPadding, |
| leftPadding), |
| m_hitTestRequest(request), |
| m_cacheable(true), |
| m_pointInInnerNodeFrame(centerPoint), |
| m_isOverWidget(false) {} |
| |
| HitTestResult::HitTestResult(const HitTestRequest& otherRequest, |
| const HitTestLocation& other) |
| : m_hitTestLocation(other), |
| m_hitTestRequest(otherRequest), |
| m_cacheable(true), |
| m_pointInInnerNodeFrame(m_hitTestLocation.point()), |
| m_isOverWidget(false) {} |
| |
| HitTestResult::HitTestResult(const HitTestResult& other) |
| : m_hitTestLocation(other.m_hitTestLocation), |
| m_hitTestRequest(other.m_hitTestRequest), |
| m_cacheable(other.m_cacheable), |
| m_innerNode(other.innerNode()), |
| m_innerPossiblyPseudoNode(other.m_innerPossiblyPseudoNode), |
| m_pointInInnerNodeFrame(other.m_pointInInnerNodeFrame), |
| m_localPoint(other.localPoint()), |
| m_innerURLElement(other.URLElement()), |
| m_scrollbar(other.scrollbar()), |
| m_isOverWidget(other.isOverWidget()) { |
| // Only copy the NodeSet in case of list hit test. |
| m_listBasedTestResult = other.m_listBasedTestResult |
| ? new NodeSet(*other.m_listBasedTestResult) |
| : nullptr; |
| } |
| |
| HitTestResult::~HitTestResult() {} |
| |
| HitTestResult& HitTestResult::operator=(const HitTestResult& other) { |
| m_hitTestLocation = other.m_hitTestLocation; |
| m_hitTestRequest = other.m_hitTestRequest; |
| populateFromCachedResult(other); |
| |
| return *this; |
| } |
| |
| bool HitTestResult::equalForCacheability(const HitTestResult& other) const { |
| return m_hitTestRequest.equalForCacheability(other.m_hitTestRequest) && |
| m_innerNode == other.innerNode() && |
| m_innerPossiblyPseudoNode == other.innerPossiblyPseudoNode() && |
| m_pointInInnerNodeFrame == other.m_pointInInnerNodeFrame && |
| m_localPoint == other.localPoint() && |
| m_innerURLElement == other.URLElement() && |
| m_scrollbar == other.scrollbar() && |
| m_isOverWidget == other.isOverWidget(); |
| } |
| |
| void HitTestResult::cacheValues(const HitTestResult& other) { |
| *this = other; |
| m_hitTestRequest = |
| other.m_hitTestRequest.type() & ~HitTestRequest::AvoidCache; |
| } |
| |
| void HitTestResult::populateFromCachedResult(const HitTestResult& other) { |
| m_innerNode = other.innerNode(); |
| m_innerPossiblyPseudoNode = other.innerPossiblyPseudoNode(); |
| m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame; |
| m_localPoint = other.localPoint(); |
| m_innerURLElement = other.URLElement(); |
| m_scrollbar = other.scrollbar(); |
| m_isOverWidget = other.isOverWidget(); |
| m_cacheable = other.m_cacheable; |
| |
| // Only copy the NodeSet in case of list hit test. |
| m_listBasedTestResult = other.m_listBasedTestResult |
| ? new NodeSet(*other.m_listBasedTestResult) |
| : nullptr; |
| } |
| |
| DEFINE_TRACE(HitTestResult) { |
| visitor->trace(m_innerNode); |
| visitor->trace(m_innerPossiblyPseudoNode); |
| visitor->trace(m_innerURLElement); |
| visitor->trace(m_scrollbar); |
| visitor->trace(m_listBasedTestResult); |
| } |
| |
| PositionWithAffinity HitTestResult::position() const { |
| if (!m_innerPossiblyPseudoNode) |
| return PositionWithAffinity(); |
| LayoutObject* layoutObject = this->layoutObject(); |
| if (!layoutObject) |
| return PositionWithAffinity(); |
| if (m_innerPossiblyPseudoNode->isPseudoElement() && |
| m_innerPossiblyPseudoNode->getPseudoId() == PseudoIdBefore) |
| return mostForwardCaretPosition( |
| Position(m_innerNode, PositionAnchorType::BeforeChildren)); |
| return layoutObject->positionForPoint(localPoint()); |
| } |
| |
| LayoutObject* HitTestResult::layoutObject() const { |
| return m_innerNode ? m_innerNode->layoutObject() : 0; |
| } |
| |
| void HitTestResult::setToShadowHostIfInUserAgentShadowRoot() { |
| if (Node* node = innerNode()) { |
| if (ShadowRoot* containingShadowRoot = node->containingShadowRoot()) { |
| if (containingShadowRoot->type() == ShadowRootType::UserAgent) |
| setInnerNode(node->ownerShadowHost()); |
| } |
| } |
| } |
| |
| HTMLAreaElement* HitTestResult::imageAreaForImage() const { |
| ASSERT(m_innerNode); |
| HTMLImageElement* imageElement = nullptr; |
| if (isHTMLImageElement(m_innerNode)) { |
| imageElement = toHTMLImageElement(m_innerNode); |
| } else if (m_innerNode->isInShadowTree()) { |
| if (m_innerNode->containingShadowRoot()->type() == |
| ShadowRootType::UserAgent) { |
| if (isHTMLImageElement(m_innerNode->ownerShadowHost())) |
| imageElement = toHTMLImageElement(m_innerNode->ownerShadowHost()); |
| } |
| } |
| |
| if (!imageElement || !imageElement->layoutObject() || |
| !imageElement->layoutObject()->isBox()) |
| return nullptr; |
| |
| HTMLMapElement* map = imageElement->treeScope().getImageMap( |
| imageElement->fastGetAttribute(usemapAttr)); |
| if (!map) |
| return nullptr; |
| |
| return map->areaForPoint(localPoint(), imageElement->layoutObject()); |
| } |
| |
| void HitTestResult::setInnerNode(Node* n) { |
| m_innerPossiblyPseudoNode = n; |
| if (n && n->isPseudoElement()) |
| n = toPseudoElement(n)->findAssociatedNode(); |
| m_innerNode = n; |
| if (HTMLAreaElement* area = imageAreaForImage()) { |
| m_innerNode = area; |
| m_innerPossiblyPseudoNode = area; |
| } |
| } |
| |
| void HitTestResult::setURLElement(Element* n) { |
| m_innerURLElement = n; |
| } |
| |
| void HitTestResult::setScrollbar(Scrollbar* s) { |
| m_scrollbar = s; |
| } |
| |
| LocalFrame* HitTestResult::innerNodeFrame() const { |
| if (m_innerNode) |
| return m_innerNode->document().frame(); |
| return nullptr; |
| } |
| |
| bool HitTestResult::isSelected() const { |
| if (!m_innerNode) |
| return false; |
| |
| if (LocalFrame* frame = m_innerNode->document().frame()) |
| return frame->selection().contains(m_hitTestLocation.point()); |
| return false; |
| } |
| |
| String HitTestResult::title(TextDirection& dir) const { |
| dir = TextDirection::Ltr; |
| // 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 (m_innerNode.get()) |
| m_innerNode->updateDistribution(); |
| for (Node* titleNode = m_innerNode.get(); titleNode; |
| titleNode = FlatTreeTraversal::parent(*titleNode)) { |
| if (titleNode->isElementNode()) { |
| String title = toElement(titleNode)->title(); |
| if (!title.isNull()) { |
| if (LayoutObject* layoutObject = titleNode->layoutObject()) |
| dir = layoutObject->style()->direction(); |
| return title; |
| } |
| } |
| } |
| return String(); |
| } |
| |
| const AtomicString& HitTestResult::altDisplayString() const { |
| Node* innerNodeOrImageMapImage = this->innerNodeOrImageMapImage(); |
| if (!innerNodeOrImageMapImage) |
| return nullAtom; |
| |
| if (isHTMLImageElement(*innerNodeOrImageMapImage)) { |
| HTMLImageElement& image = toHTMLImageElement(*innerNodeOrImageMapImage); |
| return image.getAttribute(altAttr); |
| } |
| |
| if (isHTMLInputElement(*innerNodeOrImageMapImage)) { |
| HTMLInputElement& input = toHTMLInputElement(*innerNodeOrImageMapImage); |
| return input.alt(); |
| } |
| |
| return nullAtom; |
| } |
| |
| Image* HitTestResult::image() const { |
| Node* innerNodeOrImageMapImage = this->innerNodeOrImageMapImage(); |
| if (!innerNodeOrImageMapImage) |
| return nullptr; |
| |
| LayoutObject* layoutObject = innerNodeOrImageMapImage->layoutObject(); |
| if (layoutObject && layoutObject->isImage()) { |
| LayoutImage* image = toLayoutImage(layoutObject); |
| if (image->cachedImage() && !image->cachedImage()->errorOccurred()) |
| return image->cachedImage()->getImage(); |
| } |
| |
| return nullptr; |
| } |
| |
| IntRect HitTestResult::imageRect() const { |
| if (!image()) |
| return IntRect(); |
| return innerNodeOrImageMapImage() |
| ->layoutBox() |
| ->absoluteContentQuad() |
| .enclosingBoundingBox(); |
| } |
| |
| KURL HitTestResult::absoluteImageURL() const { |
| Node* innerNodeOrImageMapImage = this->innerNodeOrImageMapImage(); |
| if (!innerNodeOrImageMapImage) |
| return KURL(); |
| |
| AtomicString urlString; |
| // 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(*innerNodeOrImageMapImage) || |
| (isHTMLInputElement(*innerNodeOrImageMapImage) && |
| toHTMLInputElement(innerNodeOrImageMapImage)->type() == |
| InputTypeNames::image)) |
| urlString = toElement(*innerNodeOrImageMapImage).imageSourceURL(); |
| else if ((innerNodeOrImageMapImage->layoutObject() && |
| innerNodeOrImageMapImage->layoutObject()->isImage()) && |
| (isHTMLEmbedElement(*innerNodeOrImageMapImage) || |
| isHTMLObjectElement(*innerNodeOrImageMapImage) || |
| isSVGImageElement(*innerNodeOrImageMapImage))) |
| urlString = toElement(*innerNodeOrImageMapImage).imageSourceURL(); |
| if (urlString.isEmpty()) |
| return KURL(); |
| |
| return innerNodeOrImageMapImage->document().completeURL( |
| stripLeadingAndTrailingHTMLSpaces(urlString)); |
| } |
| |
| KURL HitTestResult::absoluteMediaURL() const { |
| if (HTMLMediaElement* mediaElt = mediaElement()) |
| return mediaElt->currentSrc(); |
| return KURL(); |
| } |
| |
| HTMLMediaElement* HitTestResult::mediaElement() const { |
| if (!m_innerNode) |
| return nullptr; |
| |
| if (!(m_innerNode->layoutObject() && m_innerNode->layoutObject()->isMedia())) |
| return nullptr; |
| |
| if (isHTMLMediaElement(*m_innerNode)) |
| return toHTMLMediaElement(m_innerNode); |
| return nullptr; |
| } |
| |
| KURL HitTestResult::absoluteLinkURL() const { |
| if (!m_innerURLElement) |
| return KURL(); |
| return m_innerURLElement->hrefURL(); |
| } |
| |
| bool HitTestResult::isLiveLink() const { |
| return m_innerURLElement && m_innerURLElement->isLiveLink(); |
| } |
| |
| bool HitTestResult::isOverLink() const { |
| return m_innerURLElement && m_innerURLElement->isLink(); |
| } |
| |
| String HitTestResult::textContent() const { |
| if (!m_innerURLElement) |
| return String(); |
| return m_innerURLElement->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 (!m_innerNode) |
| return false; |
| |
| if (isHTMLTextAreaElement(*m_innerNode)) |
| return !toHTMLTextAreaElement(*m_innerNode).isDisabledOrReadOnly(); |
| |
| if (isHTMLInputElement(*m_innerNode)) { |
| HTMLInputElement& inputElement = toHTMLInputElement(*m_innerNode); |
| return !inputElement.isDisabledOrReadOnly() && inputElement.isTextField(); |
| } |
| |
| return hasEditableStyle(*m_innerNode); |
| } |
| |
| 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 (!hitTestRequest().listBased()) |
| return StopHitTesting; |
| |
| if (!node) |
| return ContinueHitTesting; |
| |
| mutableListBasedTestResult().add(node); |
| |
| if (hitTestRequest().penetratingList()) |
| return ContinueHitTesting; |
| |
| return rect.contains(LayoutRect(location.boundingBox())) ? StopHitTesting |
| : ContinueHitTesting; |
| } |
| |
| 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 (!hitTestRequest().listBased()) |
| return StopHitTesting; |
| |
| if (!node) |
| return ContinueHitTesting; |
| |
| mutableListBasedTestResult().add(node); |
| |
| if (hitTestRequest().penetratingList()) |
| return ContinueHitTesting; |
| |
| return region.contains(location.boundingBox()) ? StopHitTesting |
| : ContinueHitTesting; |
| } |
| |
| void HitTestResult::append(const HitTestResult& other) { |
| ASSERT(hitTestRequest().listBased()); |
| |
| if (!m_scrollbar && other.scrollbar()) { |
| setScrollbar(other.scrollbar()); |
| } |
| |
| if (!m_innerNode && other.innerNode()) { |
| m_innerNode = other.innerNode(); |
| m_innerPossiblyPseudoNode = other.innerPossiblyPseudoNode(); |
| m_localPoint = other.localPoint(); |
| m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame; |
| m_innerURLElement = other.URLElement(); |
| m_isOverWidget = other.isOverWidget(); |
| } |
| |
| if (other.m_listBasedTestResult) { |
| NodeSet& set = mutableListBasedTestResult(); |
| for (NodeSet::const_iterator it = other.m_listBasedTestResult->begin(), |
| last = other.m_listBasedTestResult->end(); |
| it != last; ++it) |
| set.add(it->get()); |
| } |
| } |
| |
| const HitTestResult::NodeSet& HitTestResult::listBasedTestResult() const { |
| if (!m_listBasedTestResult) |
| m_listBasedTestResult = new NodeSet; |
| return *m_listBasedTestResult; |
| } |
| |
| HitTestResult::NodeSet& HitTestResult::mutableListBasedTestResult() { |
| if (!m_listBasedTestResult) |
| m_listBasedTestResult = new NodeSet; |
| return *m_listBasedTestResult; |
| } |
| |
| void HitTestResult::resolveRectBasedTest( |
| Node* resolvedInnerNode, |
| const LayoutPoint& resolvedPointInMainFrame) { |
| ASSERT(isRectBasedTest()); |
| ASSERT(m_hitTestLocation.containsPoint(FloatPoint(resolvedPointInMainFrame))); |
| m_hitTestLocation = HitTestLocation(resolvedPointInMainFrame); |
| m_pointInInnerNodeFrame = resolvedPointInMainFrame; |
| m_innerNode = nullptr; |
| m_innerPossiblyPseudoNode = nullptr; |
| m_listBasedTestResult = 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. |
| resolvedInnerNode->layoutObject()->updateHitTestResult(*this, LayoutPoint()); |
| ASSERT(!isRectBasedTest()); |
| } |
| |
| Element* HitTestResult::innerElement() const { |
| for (Node* node = m_innerNode.get(); node; |
| node = FlatTreeTraversal::parent(*node)) { |
| if (node->isElementNode()) |
| return toElement(node); |
| } |
| |
| return nullptr; |
| } |
| |
| Node* HitTestResult::innerNodeOrImageMapImage() const { |
| if (!m_innerNode) |
| return nullptr; |
| |
| HTMLImageElement* imageMapImageElement = nullptr; |
| if (isHTMLAreaElement(m_innerNode)) |
| imageMapImageElement = toHTMLAreaElement(m_innerNode)->imageElement(); |
| else if (isHTMLMapElement(m_innerNode)) |
| imageMapImageElement = toHTMLMapElement(m_innerNode)->imageElement(); |
| |
| if (!imageMapImageElement) |
| return m_innerNode.get(); |
| |
| return imageMapImageElement; |
| } |
| |
| } // namespace blink |