2 * Copyright (C) 2006, 2008, 2011 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 #include "HitTestResult.h"
24 #include "DocumentMarkerController.h"
26 #include "FrameSelection.h"
27 #include "FrameTree.h"
28 #include "HTMLAnchorElement.h"
29 #include "HTMLVideoElement.h"
30 #include "HTMLImageElement.h"
31 #include "HTMLInputElement.h"
32 #include "HTMLMediaElement.h"
33 #include "HTMLNames.h"
34 #include "HTMLParserIdioms.h"
35 #include "RenderBlock.h"
36 #include "RenderImage.h"
37 #include "RenderInline.h"
38 #include "Scrollbar.h"
42 #include "XLinkNames.h"
47 using namespace HTMLNames;
49 HitTestResult::HitTestResult()
50 : m_isOverWidget(false)
51 , m_isRectBased(false)
59 HitTestResult::HitTestResult(const LayoutPoint& point)
61 , m_isOverWidget(false)
62 , m_isRectBased(false)
70 HitTestResult::HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
71 : m_point(centerPoint)
72 , m_isOverWidget(false)
73 , m_topPadding(topPadding)
74 , m_rightPadding(rightPadding)
75 , m_bottomPadding(bottomPadding)
76 , m_leftPadding(leftPadding)
78 // If all padding values passed in are zero then it is not a rect based hit test.
79 m_isRectBased = topPadding || rightPadding || bottomPadding || leftPadding;
81 // Make sure all padding values are clamped to zero if it is not a rect hit test.
83 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0;
86 HitTestResult::HitTestResult(const HitTestResult& other)
87 : m_innerNode(other.innerNode())
88 , m_innerNonSharedNode(other.innerNonSharedNode())
89 , m_point(other.point())
90 , m_localPoint(other.localPoint())
91 , m_innerURLElement(other.URLElement())
92 , m_scrollbar(other.scrollbar())
93 , m_isOverWidget(other.isOverWidget())
95 // Only copy the padding and NodeSet in case of rect hit test.
96 // Copying the later is rather expensive.
97 if ((m_isRectBased = other.isRectBasedTest())) {
98 m_topPadding = other.m_topPadding;
99 m_rightPadding = other.m_rightPadding;
100 m_bottomPadding = other.m_bottomPadding;
101 m_leftPadding = other.m_leftPadding;
103 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0;
105 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0);
108 HitTestResult::~HitTestResult()
112 HitTestResult& HitTestResult::operator=(const HitTestResult& other)
114 m_innerNode = other.innerNode();
115 m_innerNonSharedNode = other.innerNonSharedNode();
116 m_point = other.point();
117 m_localPoint = other.localPoint();
118 m_innerURLElement = other.URLElement();
119 m_scrollbar = other.scrollbar();
120 m_isOverWidget = other.isOverWidget();
121 // Only copy the padding and NodeSet in case of rect hit test.
122 // Copying the later is rather expensive.
123 if ((m_isRectBased = other.isRectBasedTest())) {
124 m_topPadding = other.m_topPadding;
125 m_rightPadding = other.m_rightPadding;
126 m_bottomPadding = other.m_bottomPadding;
127 m_leftPadding = other.m_leftPadding;
129 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0;
131 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0);
135 void HitTestResult::setToNonShadowAncestor()
137 Node* node = innerNode();
139 node = node->shadowAncestorNode();
141 node = innerNonSharedNode();
143 node = node->shadowAncestorNode();
144 setInnerNonSharedNode(node);
147 void HitTestResult::setInnerNode(Node* n)
152 void HitTestResult::setInnerNonSharedNode(Node* n)
154 m_innerNonSharedNode = n;
157 void HitTestResult::setURLElement(Element* n)
159 m_innerURLElement = n;
162 void HitTestResult::setScrollbar(Scrollbar* s)
167 Frame* HitTestResult::targetFrame() const
169 if (!m_innerURLElement)
172 Frame* frame = m_innerURLElement->document()->frame();
176 return frame->tree()->find(m_innerURLElement->target());
179 bool HitTestResult::isSelected() const
181 if (!m_innerNonSharedNode)
184 Frame* frame = m_innerNonSharedNode->document()->frame();
188 return frame->selection()->contains(m_point);
191 String HitTestResult::spellingToolTip(TextDirection& dir) const
194 // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar
195 // currently supply strings, but maybe someday markers associated with misspelled words will also.
196 if (!m_innerNonSharedNode)
199 DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Grammar);
203 if (RenderObject* renderer = m_innerNonSharedNode->renderer())
204 dir = renderer->style()->direction();
205 return marker->description();
208 String HitTestResult::replacedString() const
210 // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected,
211 // and is used for generating a contextual menu item that allows it to easily be changed back if desired.
212 if (!m_innerNonSharedNode)
215 DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Replacement);
219 return marker->description();
222 String HitTestResult::title(TextDirection& dir) const
225 // Find the title in the nearest enclosing DOM node.
226 // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it.
227 for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) {
228 if (titleNode->isElementNode()) {
229 String title = static_cast<Element*>(titleNode)->title();
230 if (!title.isEmpty()) {
231 if (RenderObject* renderer = titleNode->renderer())
232 dir = renderer->style()->direction();
240 String HitTestResult::innerTextIfTruncated(TextDirection& dir) const
242 for (Node* truncatedNode = m_innerNode.get(); truncatedNode; truncatedNode = truncatedNode->parentNode()) {
243 if (!truncatedNode->isElementNode())
246 if (RenderObject* renderer = truncatedNode->renderer()) {
247 if (renderer->isRenderBlock()) {
248 RenderBlock* block = toRenderBlock(renderer);
249 if (block->style()->textOverflow()) {
250 for (RootInlineBox* line = block->firstRootBox(); line; line = line->nextRootBox()) {
251 if (line->hasEllipsisBox()) {
252 dir = block->style()->direction();
253 return toElement(truncatedNode)->innerText();
266 String displayString(const String& string, const Node* node)
270 return node->document()->displayStringModifiedByEncoding(string);
273 String HitTestResult::altDisplayString() const
275 if (!m_innerNonSharedNode)
278 if (m_innerNonSharedNode->hasTagName(imgTag)) {
279 HTMLImageElement* image = static_cast<HTMLImageElement*>(m_innerNonSharedNode.get());
280 return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get());
283 if (m_innerNonSharedNode->hasTagName(inputTag)) {
284 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_innerNonSharedNode.get());
285 return displayString(input->alt(), m_innerNonSharedNode.get());
291 Image* HitTestResult::image() const
293 if (!m_innerNonSharedNode)
296 RenderObject* renderer = m_innerNonSharedNode->renderer();
297 if (renderer && renderer->isImage()) {
298 RenderImage* image = static_cast<WebCore::RenderImage*>(renderer);
299 if (image->cachedImage() && !image->cachedImage()->errorOccurred())
300 return image->cachedImage()->image();
306 IntRect HitTestResult::imageRect() const
310 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox();
313 KURL HitTestResult::absoluteImageURL() const
315 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document()))
318 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage()))
321 AtomicString urlString;
322 if (m_innerNonSharedNode->hasTagName(embedTag)
323 || m_innerNonSharedNode->hasTagName(imgTag)
324 || m_innerNonSharedNode->hasTagName(inputTag)
325 || m_innerNonSharedNode->hasTagName(objectTag)
327 || m_innerNonSharedNode->hasTagName(SVGNames::imageTag)
330 Element* element = static_cast<Element*>(m_innerNonSharedNode.get());
331 urlString = element->getAttribute(element->imageSourceAttributeName());
335 return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
338 KURL HitTestResult::absoluteMediaURL() const
341 if (HTMLMediaElement* mediaElt = mediaElement())
342 return mediaElt->currentSrc();
349 bool HitTestResult::mediaSupportsFullscreen() const
352 HTMLMediaElement* mediaElt(mediaElement());
353 return (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag) && mediaElt->supportsFullscreen());
360 HTMLMediaElement* HitTestResult::mediaElement() const
362 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document()))
365 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia()))
368 if (m_innerNonSharedNode->hasTagName(HTMLNames::videoTag) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag))
369 return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get());
374 void HitTestResult::toggleMediaControlsDisplay() const
377 if (HTMLMediaElement* mediaElt = mediaElement())
378 mediaElt->setControls(!mediaElt->controls());
382 void HitTestResult::toggleMediaLoopPlayback() const
385 if (HTMLMediaElement* mediaElt = mediaElement())
386 mediaElt->setLoop(!mediaElt->loop());
390 void HitTestResult::enterFullscreenForVideo() const
393 HTMLMediaElement* mediaElt(mediaElement());
394 if (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag)) {
395 HTMLVideoElement* videoElt = static_cast<HTMLVideoElement*>(mediaElt);
396 if (!videoElt->isFullscreen() && mediaElt->supportsFullscreen())
397 videoElt->enterFullscreen();
402 bool HitTestResult::mediaControlsEnabled() const
405 if (HTMLMediaElement* mediaElt = mediaElement())
406 return mediaElt->controls();
411 bool HitTestResult::mediaLoopEnabled() const
414 if (HTMLMediaElement* mediaElt = mediaElement())
415 return mediaElt->loop();
420 bool HitTestResult::mediaPlaying() const
423 if (HTMLMediaElement* mediaElt = mediaElement())
424 return !mediaElt->paused();
429 void HitTestResult::toggleMediaPlayState() const
432 if (HTMLMediaElement* mediaElt = mediaElement())
433 mediaElt->togglePlayState();
437 bool HitTestResult::mediaHasAudio() const
440 if (HTMLMediaElement* mediaElt = mediaElement())
441 return mediaElt->hasAudio();
446 bool HitTestResult::mediaIsVideo() const
449 if (HTMLMediaElement* mediaElt = mediaElement())
450 return mediaElt->hasTagName(HTMLNames::videoTag);
455 bool HitTestResult::mediaMuted() const
458 if (HTMLMediaElement* mediaElt = mediaElement())
459 return mediaElt->muted();
464 void HitTestResult::toggleMediaMuteState() const
467 if (HTMLMediaElement* mediaElt = mediaElement())
468 mediaElt->setMuted(!mediaElt->muted());
472 KURL HitTestResult::absoluteLinkURL() const
474 if (!(m_innerURLElement && m_innerURLElement->document()))
477 AtomicString urlString;
478 if (m_innerURLElement->hasTagName(aTag) || m_innerURLElement->hasTagName(areaTag) || m_innerURLElement->hasTagName(linkTag))
479 urlString = m_innerURLElement->getAttribute(hrefAttr);
481 else if (m_innerURLElement->hasTagName(SVGNames::aTag))
482 urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr);
487 return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
490 bool HitTestResult::isLiveLink() const
492 if (!(m_innerURLElement && m_innerURLElement->document()))
495 if (m_innerURLElement->hasTagName(aTag))
496 return static_cast<HTMLAnchorElement*>(m_innerURLElement.get())->isLiveLink();
498 if (m_innerURLElement->hasTagName(SVGNames::aTag))
499 return m_innerURLElement->isLink();
505 String HitTestResult::titleDisplayString() const
507 if (!m_innerURLElement)
510 return displayString(m_innerURLElement->title(), m_innerURLElement.get());
513 String HitTestResult::textContent() const
515 if (!m_innerURLElement)
517 return m_innerURLElement->textContent();
520 // FIXME: This function needs a better name and may belong in a different class. It's not
521 // really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this
522 // function would make more sense in the ContextMenu class, except that WebElementDictionary
523 // hooks into it. Anyway, we should architect this better.
524 bool HitTestResult::isContentEditable() const
526 if (!m_innerNonSharedNode)
529 if (m_innerNonSharedNode->hasTagName(textareaTag) || m_innerNonSharedNode->hasTagName(isindexTag))
532 if (m_innerNonSharedNode->hasTagName(inputTag))
533 return static_cast<HTMLInputElement*>(m_innerNonSharedNode.get())->isTextField();
535 return m_innerNonSharedNode->rendererIsEditable();
538 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const LayoutPoint& pointInContainer, const IntRect& rect)
540 // If it is not a rect-based hit test, this method has to be no-op.
541 // Return false, so the hit test stops.
542 if (!isRectBasedTest())
545 // If node is null, return true so the hit test can continue.
549 node = node->shadowAncestorNode();
550 mutableRectBasedTestResult().add(node);
552 if (node->renderer()->isInline()) {
553 for (RenderObject* curr = node->renderer()->parent(); curr; curr = curr->parent()) {
554 if (!curr->isRenderInline())
557 // We need to make sure the nodes for culled inlines get included.
558 RenderInline* currInline = toRenderInline(curr);
559 if (currInline->alwaysCreateLineBoxes())
562 if (currInline->visibleToHitTesting() && currInline->node())
563 mutableRectBasedTestResult().add(currInline->node()->shadowAncestorNode());
566 return !rect.contains(rectForPoint(pointInContainer));
569 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const LayoutPoint& pointInContainer, const FloatRect& rect)
571 // If it is not a rect-based hit test, this method has to be no-op.
572 // Return false, so the hit test stops.
573 if (!isRectBasedTest())
576 // If node is null, return true so the hit test can continue.
580 node = node->shadowAncestorNode();
581 mutableRectBasedTestResult().add(node);
583 if (node->renderer()->isInline()) {
584 for (RenderObject* curr = node->renderer()->parent(); curr; curr = curr->parent()) {
585 if (!curr->isRenderInline())
588 // We need to make sure the nodes for culled inlines get included.
589 RenderInline* currInline = toRenderInline(curr);
590 if (currInline->alwaysCreateLineBoxes())
593 if (currInline->visibleToHitTesting() && currInline->node())
594 mutableRectBasedTestResult().add(currInline->node()->shadowAncestorNode());
597 return !rect.contains(rectForPoint(pointInContainer));
600 void HitTestResult::append(const HitTestResult& other)
602 ASSERT(isRectBasedTest() && other.isRectBasedTest());
604 if (!m_innerNode && other.innerNode()) {
605 m_innerNode = other.innerNode();
606 m_innerNonSharedNode = other.innerNonSharedNode();
607 m_localPoint = other.localPoint();
608 m_innerURLElement = other.URLElement();
609 m_scrollbar = other.scrollbar();
610 m_isOverWidget = other.isOverWidget();
613 if (other.m_rectBasedTestResult) {
614 NodeSet& set = mutableRectBasedTestResult();
615 for (NodeSet::const_iterator it = other.m_rectBasedTestResult->begin(), last = other.m_rectBasedTestResult->end(); it != last; ++it)
620 LayoutRect HitTestResult::rectForPoint(const LayoutPoint& point, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
622 LayoutPoint actualPoint(point);
623 actualPoint -= LayoutSize(leftPadding, topPadding);
625 IntSize actualPadding(leftPadding + rightPadding, topPadding + bottomPadding);
626 // As IntRect is left inclusive and right exclusive (seeing IntRect::contains(x, y)), adding "1".
627 actualPadding += IntSize(1, 1);
629 return LayoutRect(actualPoint, actualPadding);
632 const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const
634 if (!m_rectBasedTestResult)
635 m_rectBasedTestResult = adoptPtr(new NodeSet);
636 return *m_rectBasedTestResult;
639 HitTestResult::NodeSet& HitTestResult::mutableRectBasedTestResult()
641 if (!m_rectBasedTestResult)
642 m_rectBasedTestResult = adoptPtr(new NodeSet);
643 return *m_rectBasedTestResult;
646 } // namespace WebCore