diff options
Diffstat (limited to 'Source/WebCore/rendering/HitTestResult.cpp')
-rw-r--r-- | Source/WebCore/rendering/HitTestResult.cpp | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/HitTestResult.cpp b/Source/WebCore/rendering/HitTestResult.cpp new file mode 100644 index 0000000..5d9b3df --- /dev/null +++ b/Source/WebCore/rendering/HitTestResult.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * 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 "config.h" +#include "HitTestResult.h" + +#include "DocumentMarkerController.h" +#include "Frame.h" +#include "FrameTree.h" +#include "HTMLAnchorElement.h" +#include "HTMLVideoElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "RenderImage.h" +#include "Scrollbar.h" +#include "SelectionController.h" + +#if ENABLE(SVG) +#include "SVGNames.h" +#include "XLinkNames.h" +#endif + +#if ENABLE(WML) +#include "WMLImageElement.h" +#include "WMLNames.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +HitTestResult::HitTestResult() + : m_isOverWidget(false) + , m_isRectBased(false) + , m_topPadding(0) + , m_rightPadding(0) + , m_bottomPadding(0) + , m_leftPadding(0) +{ +} + +HitTestResult::HitTestResult(const IntPoint& point) + : m_point(point) + , m_isOverWidget(false) + , m_isRectBased(false) + , m_topPadding(0) + , m_rightPadding(0) + , m_bottomPadding(0) + , m_leftPadding(0) +{ +} + +HitTestResult::HitTestResult(const IntPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) + : m_point(centerPoint) + , m_isOverWidget(false) + , m_topPadding(topPadding) + , m_rightPadding(rightPadding) + , m_bottomPadding(bottomPadding) + , m_leftPadding(leftPadding) +{ + // If all padding values passed in are zero then it is not a rect based hit test. + m_isRectBased = topPadding || rightPadding || bottomPadding || leftPadding; + + // Make sure all padding values are clamped to zero if it is not a rect hit test. + if (!m_isRectBased) + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; +} + +HitTestResult::HitTestResult(const HitTestResult& other) + : m_innerNode(other.innerNode()) + , m_innerNonSharedNode(other.innerNonSharedNode()) + , m_point(other.point()) + , m_localPoint(other.localPoint()) + , m_innerURLElement(other.URLElement()) + , m_scrollbar(other.scrollbar()) + , m_isOverWidget(other.isOverWidget()) +{ + // Only copy the padding and ListHashSet in case of rect hit test. + // Copying the later is rather expensive. + if ((m_isRectBased = other.isRectBasedTest())) { + m_topPadding = other.m_topPadding; + m_rightPadding = other.m_rightPadding; + m_bottomPadding = other.m_bottomPadding; + m_leftPadding = other.m_leftPadding; + m_rectBasedTestResult = other.rectBasedTestResult(); + } else + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; +} + +HitTestResult::~HitTestResult() +{ +} + +HitTestResult& HitTestResult::operator=(const HitTestResult& other) +{ + m_innerNode = other.innerNode(); + m_innerNonSharedNode = other.innerNonSharedNode(); + m_point = other.point(); + m_localPoint = other.localPoint(); + m_innerURLElement = other.URLElement(); + m_scrollbar = other.scrollbar(); + m_isOverWidget = other.isOverWidget(); + // Only copy the padding and ListHashSet in case of rect hit test. + // Copying the later is rather expensive. + if ((m_isRectBased = other.isRectBasedTest())) { + m_topPadding = other.m_topPadding; + m_rightPadding = other.m_rightPadding; + m_bottomPadding = other.m_bottomPadding; + m_leftPadding = other.m_leftPadding; + m_rectBasedTestResult = other.rectBasedTestResult(); + } else + m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; + return *this; +} + +void HitTestResult::setToNonShadowAncestor() +{ + Node* node = innerNode(); + if (node) + node = node->shadowAncestorNode(); + setInnerNode(node); + node = innerNonSharedNode(); + if (node) + node = node->shadowAncestorNode(); + setInnerNonSharedNode(node); +} + +void HitTestResult::setInnerNode(Node* n) +{ + m_innerNode = n; +} + +void HitTestResult::setInnerNonSharedNode(Node* n) +{ + m_innerNonSharedNode = n; +} + +void HitTestResult::setURLElement(Element* n) +{ + m_innerURLElement = n; +} + +void HitTestResult::setScrollbar(Scrollbar* s) +{ + m_scrollbar = s; +} + +Frame* HitTestResult::targetFrame() const +{ + if (!m_innerURLElement) + return 0; + + Frame* frame = m_innerURLElement->document()->frame(); + if (!frame) + return 0; + + return frame->tree()->find(m_innerURLElement->target()); +} + +bool HitTestResult::isSelected() const +{ + if (!m_innerNonSharedNode) + return false; + + Frame* frame = m_innerNonSharedNode->document()->frame(); + if (!frame) + return false; + + return frame->selection()->contains(m_point); +} + +String HitTestResult::spellingToolTip(TextDirection& dir) const +{ + dir = LTR; + // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar + // currently supply strings, but maybe someday markers associated with misspelled words will also. + if (!m_innerNonSharedNode) + return String(); + + DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Grammar); + if (!marker) + return String(); + + if (RenderObject* renderer = m_innerNonSharedNode->renderer()) + dir = renderer->style()->direction(); + return marker->description; +} + +String HitTestResult::replacedString() const +{ + // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected, + // and is used for generating a contextual menu item that allows it to easily be changed back if desired. + if (!m_innerNonSharedNode) + return String(); + + DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Replacement); + if (!marker) + return String(); + + return marker->description; +} + +String HitTestResult::title(TextDirection& dir) const +{ + dir = 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. + for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) { + if (titleNode->isElementNode()) { + String title = static_cast<Element*>(titleNode)->title(); + if (!title.isEmpty()) { + if (RenderObject* renderer = titleNode->renderer()) + dir = renderer->style()->direction(); + return title; + } + } + } + return String(); +} + +String displayString(const String& string, const Node* node) +{ + if (!node) + return string; + return node->document()->displayStringModifiedByEncoding(string); +} + +String HitTestResult::altDisplayString() const +{ + if (!m_innerNonSharedNode) + return String(); + + if (m_innerNonSharedNode->hasTagName(imgTag)) { + HTMLImageElement* image = static_cast<HTMLImageElement*>(m_innerNonSharedNode.get()); + return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get()); + } + + if (m_innerNonSharedNode->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_innerNonSharedNode.get()); + return displayString(input->alt(), m_innerNonSharedNode.get()); + } + +#if ENABLE(WML) + if (m_innerNonSharedNode->hasTagName(WMLNames::imgTag)) { + WMLImageElement* image = static_cast<WMLImageElement*>(m_innerNonSharedNode.get()); + return displayString(image->altText(), m_innerNonSharedNode.get()); + } +#endif + + return String(); +} + +Image* HitTestResult::image() const +{ + if (!m_innerNonSharedNode) + return 0; + + RenderObject* renderer = m_innerNonSharedNode->renderer(); + if (renderer && renderer->isImage()) { + RenderImage* image = static_cast<WebCore::RenderImage*>(renderer); + if (image->cachedImage() && !image->cachedImage()->errorOccurred()) + return image->cachedImage()->image(); + } + + return 0; +} + +IntRect HitTestResult::imageRect() const +{ + if (!image()) + return IntRect(); + return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox(); +} + +KURL HitTestResult::absoluteImageURL() const +{ + if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) + return KURL(); + + if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage())) + return KURL(); + + AtomicString urlString; + if (m_innerNonSharedNode->hasTagName(embedTag) + || m_innerNonSharedNode->hasTagName(imgTag) + || m_innerNonSharedNode->hasTagName(inputTag) + || m_innerNonSharedNode->hasTagName(objectTag) +#if ENABLE(SVG) + || m_innerNonSharedNode->hasTagName(SVGNames::imageTag) +#endif +#if ENABLE(WML) + || m_innerNonSharedNode->hasTagName(WMLNames::imgTag) +#endif + ) { + Element* element = static_cast<Element*>(m_innerNonSharedNode.get()); + urlString = element->getAttribute(element->imageSourceAttributeName()); + } else + return KURL(); + + return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); +} + +KURL HitTestResult::absoluteMediaURL() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(mediaElt->currentSrc())); + return KURL(); +#else + return KURL(); +#endif +} + +bool HitTestResult::mediaSupportsFullscreen() const +{ +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElt(mediaElement()); + return (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag) && mediaElt->supportsFullscreen()); +#else + return false; +#endif +} + +#if ENABLE(VIDEO) +HTMLMediaElement* HitTestResult::mediaElement() const +{ + if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) + return 0; + + if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia())) + return 0; + + if (m_innerNonSharedNode->hasTagName(HTMLNames::videoTag) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag)) + return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get()); + return 0; +} +#endif + +void HitTestResult::toggleMediaControlsDisplay() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setControls(!mediaElt->controls()); +#endif +} + +void HitTestResult::toggleMediaLoopPlayback() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setLoop(!mediaElt->loop()); +#endif +} + +void HitTestResult::enterFullscreenForVideo() const +{ +#if ENABLE(VIDEO) + HTMLMediaElement* mediaElt(mediaElement()); + if (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag)) { + HTMLVideoElement* videoElt = static_cast<HTMLVideoElement*>(mediaElt); + if (!videoElt->isFullscreen() && mediaElt->supportsFullscreen()) + videoElt->enterFullscreen(); + } +#endif +} + +bool HitTestResult::mediaControlsEnabled() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->controls(); +#endif + return false; +} + +bool HitTestResult::mediaLoopEnabled() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->loop(); +#endif + return false; +} + +bool HitTestResult::mediaPlaying() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return !mediaElt->paused(); +#endif + return false; +} + +void HitTestResult::toggleMediaPlayState() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->togglePlayState(); +#endif +} + +bool HitTestResult::mediaHasAudio() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->hasAudio(); +#endif + return false; +} + +bool HitTestResult::mediaIsVideo() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->hasTagName(HTMLNames::videoTag); +#endif + return false; +} + +bool HitTestResult::mediaMuted() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + return mediaElt->muted(); +#endif + return false; +} + +void HitTestResult::toggleMediaMuteState() const +{ +#if ENABLE(VIDEO) + if (HTMLMediaElement* mediaElt = mediaElement()) + mediaElt->setMuted(!mediaElt->muted()); +#endif +} + +KURL HitTestResult::absoluteLinkURL() const +{ + if (!(m_innerURLElement && m_innerURLElement->document())) + return KURL(); + + AtomicString urlString; + if (m_innerURLElement->hasTagName(aTag) || m_innerURLElement->hasTagName(areaTag) || m_innerURLElement->hasTagName(linkTag)) + urlString = m_innerURLElement->getAttribute(hrefAttr); +#if ENABLE(SVG) + else if (m_innerURLElement->hasTagName(SVGNames::aTag)) + urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr); +#endif +#if ENABLE(WML) + else if (m_innerURLElement->hasTagName(WMLNames::aTag)) + urlString = m_innerURLElement->getAttribute(hrefAttr); +#endif + else + return KURL(); + + return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); +} + +bool HitTestResult::isLiveLink() const +{ + if (!(m_innerURLElement && m_innerURLElement->document())) + return false; + + if (m_innerURLElement->hasTagName(aTag)) + return static_cast<HTMLAnchorElement*>(m_innerURLElement.get())->isLiveLink(); +#if ENABLE(SVG) + if (m_innerURLElement->hasTagName(SVGNames::aTag)) + return m_innerURLElement->isLink(); +#endif +#if ENABLE(WML) + if (m_innerURLElement->hasTagName(WMLNames::aTag)) + return m_innerURLElement->isLink(); +#endif + + return false; +} + +String HitTestResult::titleDisplayString() const +{ + if (!m_innerURLElement) + return String(); + + return displayString(m_innerURLElement->title(), m_innerURLElement.get()); +} + +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_innerNonSharedNode) + return false; + + if (m_innerNonSharedNode->hasTagName(textareaTag) || m_innerNonSharedNode->hasTagName(isindexTag)) + return true; + + if (m_innerNonSharedNode->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(m_innerNonSharedNode.get())->isTextField(); + + return m_innerNonSharedNode->isContentEditable(); +} + +bool HitTestResult::addNodeToRectBasedTestResult(Node* node, int x, int y, const IntRect& rect) +{ + // If it is not a rect-based hit test, this method has to be no-op. + // Return false, so the hit test stops. + if (!isRectBasedTest()) + return false; + + // If node is null, return true so the hit test can continue. + if (!node) + return true; + + node = node->shadowAncestorNode(); + m_rectBasedTestResult.add(node); + + return !rect.contains(rectForPoint(x, y)); +} + +void HitTestResult::append(const HitTestResult& other) +{ + ASSERT(isRectBasedTest() && other.isRectBasedTest()); + + if (!m_innerNode && other.innerNode()) { + m_innerNode = other.innerNode(); + m_innerNonSharedNode = other.innerNonSharedNode(); + m_localPoint = other.localPoint(); + m_innerURLElement = other.URLElement(); + m_scrollbar = other.scrollbar(); + m_isOverWidget = other.isOverWidget(); + } + + const ListHashSet<RefPtr<Node> >& list = other.rectBasedTestResult(); + ListHashSet<RefPtr<Node> >::const_iterator last = list.end(); + for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) + m_rectBasedTestResult.add(it->get()); +} + +IntRect HitTestResult::rectForPoint(const IntPoint& point, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) +{ + IntPoint actualPoint(point); + actualPoint -= IntSize(leftPadding, topPadding); + + IntSize actualPadding(leftPadding + rightPadding, topPadding + bottomPadding); + // As IntRect is left inclusive and right exclusive (seeing IntRect::contains(x, y)), adding "1". + actualPadding += IntSize(1, 1); + + return IntRect(actualPoint, actualPadding); +} + +} // namespace WebCore |