1 /* 2 * Copyright (C) 2006, 2008, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies) 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 * 20 */ 21 22 #include "config.h" 23 #include "core/rendering/HitTestResult.h" 24 25 #include "HTMLNames.h" 26 #include "SVGNames.h" 27 #include "XLinkNames.h" 28 #include "core/dom/DocumentMarkerController.h" 29 #include "core/dom/NodeRenderingTraversal.h" 30 #include "core/dom/shadow/ShadowRoot.h" 31 #include "core/editing/FrameSelection.h" 32 #include "core/html/HTMLAnchorElement.h" 33 #include "core/html/HTMLAreaElement.h" 34 #include "core/html/HTMLImageElement.h" 35 #include "core/html/HTMLInputElement.h" 36 #include "core/html/HTMLMediaElement.h" 37 #include "core/html/HTMLTextAreaElement.h" 38 #include "core/html/HTMLVideoElement.h" 39 #include "core/html/parser/HTMLParserIdioms.h" 40 #include "core/loader/cache/ImageResource.h" 41 #include "core/page/Frame.h" 42 #include "core/page/FrameTree.h" 43 #include "core/platform/Scrollbar.h" 44 #include "core/rendering/HitTestLocation.h" 45 #include "core/rendering/RenderImage.h" 46 47 namespace WebCore { 48 49 using namespace HTMLNames; 50 51 HitTestResult::HitTestResult() 52 : m_isOverWidget(false) 53 { 54 } 55 56 HitTestResult::HitTestResult(const LayoutPoint& point) 57 : m_hitTestLocation(point) 58 , m_pointInInnerNodeFrame(point) 59 , m_isOverWidget(false) 60 { 61 } 62 63 HitTestResult::HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) 64 : m_hitTestLocation(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding) 65 , m_pointInInnerNodeFrame(centerPoint) 66 , m_isOverWidget(false) 67 { 68 } 69 70 HitTestResult::HitTestResult(const HitTestLocation& other) 71 : m_hitTestLocation(other) 72 , m_pointInInnerNodeFrame(m_hitTestLocation.point()) 73 , m_isOverWidget(false) 74 { 75 } 76 77 HitTestResult::HitTestResult(const HitTestResult& other) 78 : m_hitTestLocation(other.m_hitTestLocation) 79 , m_innerNode(other.innerNode()) 80 , m_innerNonSharedNode(other.innerNonSharedNode()) 81 , m_pointInInnerNodeFrame(other.m_pointInInnerNodeFrame) 82 , m_localPoint(other.localPoint()) 83 , m_innerURLElement(other.URLElement()) 84 , m_scrollbar(other.scrollbar()) 85 , m_isOverWidget(other.isOverWidget()) 86 { 87 // Only copy the NodeSet in case of rect hit test. 88 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0); 89 } 90 91 HitTestResult::~HitTestResult() 92 { 93 } 94 95 HitTestResult& HitTestResult::operator=(const HitTestResult& other) 96 { 97 m_hitTestLocation = other.m_hitTestLocation; 98 m_innerNode = other.innerNode(); 99 m_innerNonSharedNode = other.innerNonSharedNode(); 100 m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame; 101 m_localPoint = other.localPoint(); 102 m_innerURLElement = other.URLElement(); 103 m_scrollbar = other.scrollbar(); 104 m_isOverWidget = other.isOverWidget(); 105 106 // Only copy the NodeSet in case of rect hit test. 107 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0); 108 109 return *this; 110 } 111 112 void HitTestResult::setToNodesInDocumentTreeScope() 113 { 114 if (Node* node = innerNode()) { 115 node = node->document()->ancestorInThisScope(node); 116 setInnerNode(node); 117 } 118 119 if (Node* node = innerNonSharedNode()) { 120 node = node->document()->ancestorInThisScope(node); 121 setInnerNonSharedNode(node); 122 } 123 } 124 125 void HitTestResult::setToShadowHostIfInUserAgentShadowRoot() 126 { 127 if (Node* node = innerNode()) { 128 if (ShadowRoot* containingShadowRoot = node->containingShadowRoot()) { 129 if (containingShadowRoot->type() == ShadowRoot::UserAgentShadowRoot) 130 setInnerNode(node->shadowHost()); 131 } 132 } 133 134 if (Node* node = innerNonSharedNode()) { 135 if (ShadowRoot* containingShadowRoot = node->containingShadowRoot()) { 136 if (containingShadowRoot->type() == ShadowRoot::UserAgentShadowRoot) 137 setInnerNonSharedNode(node->shadowHost()); 138 } 139 } 140 } 141 142 void HitTestResult::setInnerNode(Node* n) 143 { 144 if (n && n->isPseudoElement()) 145 n = n->parentOrShadowHostNode(); 146 m_innerNode = n; 147 } 148 149 void HitTestResult::setInnerNonSharedNode(Node* n) 150 { 151 if (n && n->isPseudoElement()) 152 n = n->parentOrShadowHostNode(); 153 m_innerNonSharedNode = n; 154 } 155 156 void HitTestResult::setURLElement(Element* n) 157 { 158 m_innerURLElement = n; 159 } 160 161 void HitTestResult::setScrollbar(Scrollbar* s) 162 { 163 m_scrollbar = s; 164 } 165 166 Frame* HitTestResult::innerNodeFrame() const 167 { 168 if (m_innerNonSharedNode) 169 return m_innerNonSharedNode->document()->frame(); 170 if (m_innerNode) 171 return m_innerNode->document()->frame(); 172 return 0; 173 } 174 175 Frame* HitTestResult::targetFrame() const 176 { 177 if (!m_innerURLElement) 178 return 0; 179 180 Frame* frame = m_innerURLElement->document()->frame(); 181 if (!frame) 182 return 0; 183 184 return frame->tree()->find(m_innerURLElement->target()); 185 } 186 187 bool HitTestResult::isSelected() const 188 { 189 if (!m_innerNonSharedNode) 190 return false; 191 192 Frame* frame = m_innerNonSharedNode->document()->frame(); 193 if (!frame) 194 return false; 195 196 return frame->selection()->contains(m_hitTestLocation.point()); 197 } 198 199 String HitTestResult::spellingToolTip(TextDirection& dir) const 200 { 201 dir = LTR; 202 // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar 203 // currently supply strings, but maybe someday markers associated with misspelled words will also. 204 if (!m_innerNonSharedNode) 205 return String(); 206 207 DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_hitTestLocation.point(), DocumentMarker::Grammar); 208 if (!marker) 209 return String(); 210 211 if (RenderObject* renderer = m_innerNonSharedNode->renderer()) 212 dir = renderer->style()->direction(); 213 return marker->description(); 214 } 215 216 String HitTestResult::title(TextDirection& dir) const 217 { 218 dir = LTR; 219 // Find the title in the nearest enclosing DOM node. 220 // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it. 221 for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) { 222 if (titleNode->isElementNode()) { 223 String title = toElement(titleNode)->title(); 224 if (!title.isEmpty()) { 225 if (RenderObject* renderer = titleNode->renderer()) 226 dir = renderer->style()->direction(); 227 return title; 228 } 229 } 230 } 231 return String(); 232 } 233 234 String displayString(const String& string, const Node* node) 235 { 236 if (!node) 237 return string; 238 return node->document()->displayStringModifiedByEncoding(string); 239 } 240 241 String HitTestResult::altDisplayString() const 242 { 243 if (!m_innerNonSharedNode) 244 return String(); 245 246 if (m_innerNonSharedNode->hasTagName(imgTag)) { 247 HTMLImageElement* image = toHTMLImageElement(m_innerNonSharedNode.get()); 248 return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get()); 249 } 250 251 if (m_innerNonSharedNode->hasTagName(inputTag)) { 252 HTMLInputElement* input = toHTMLInputElement(m_innerNonSharedNode.get()); 253 return displayString(input->alt(), m_innerNonSharedNode.get()); 254 } 255 256 return String(); 257 } 258 259 Image* HitTestResult::image() const 260 { 261 if (!m_innerNonSharedNode) 262 return 0; 263 264 RenderObject* renderer = m_innerNonSharedNode->renderer(); 265 if (renderer && renderer->isImage()) { 266 RenderImage* image = static_cast<WebCore::RenderImage*>(renderer); 267 if (image->cachedImage() && !image->cachedImage()->errorOccurred()) 268 return image->cachedImage()->imageForRenderer(image); 269 } 270 271 return 0; 272 } 273 274 IntRect HitTestResult::imageRect() const 275 { 276 if (!image()) 277 return IntRect(); 278 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox(); 279 } 280 281 KURL HitTestResult::absoluteImageURL() const 282 { 283 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) 284 return KURL(); 285 286 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage())) 287 return KURL(); 288 289 AtomicString urlString; 290 if (m_innerNonSharedNode->hasTagName(embedTag) 291 || m_innerNonSharedNode->hasTagName(imgTag) 292 || m_innerNonSharedNode->hasTagName(inputTag) 293 || m_innerNonSharedNode->hasTagName(objectTag) 294 || m_innerNonSharedNode->hasTagName(SVGNames::imageTag) 295 ) { 296 Element* element = toElement(m_innerNonSharedNode.get()); 297 urlString = element->imageSourceURL(); 298 } else 299 return KURL(); 300 301 return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 302 } 303 304 KURL HitTestResult::absoluteMediaURL() const 305 { 306 if (HTMLMediaElement* mediaElt = mediaElement()) 307 return mediaElt->currentSrc(); 308 return KURL(); 309 } 310 311 HTMLMediaElement* HitTestResult::mediaElement() const 312 { 313 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) 314 return 0; 315 316 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia())) 317 return 0; 318 319 if (isHTMLVideoElement(m_innerNonSharedNode.get()) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag)) 320 return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get()); 321 return 0; 322 } 323 324 KURL HitTestResult::absoluteLinkURL() const 325 { 326 if (!(m_innerURLElement && m_innerURLElement->document())) 327 return KURL(); 328 329 AtomicString urlString; 330 if (isHTMLAnchorElement(m_innerURLElement.get()) || isHTMLAreaElement(m_innerURLElement.get()) || m_innerURLElement->hasTagName(linkTag)) 331 urlString = m_innerURLElement->getAttribute(hrefAttr); 332 else if (m_innerURLElement->hasTagName(SVGNames::aTag)) 333 urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr); 334 else 335 return KURL(); 336 337 return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 338 } 339 340 bool HitTestResult::isLiveLink() const 341 { 342 if (!(m_innerURLElement && m_innerURLElement->document())) 343 return false; 344 345 if (isHTMLAnchorElement(m_innerURLElement.get())) 346 return toHTMLAnchorElement(m_innerURLElement.get())->isLiveLink(); 347 348 if (m_innerURLElement->hasTagName(SVGNames::aTag)) 349 return m_innerURLElement->isLink(); 350 351 return false; 352 } 353 354 bool HitTestResult::isMisspelled() const 355 { 356 if (!targetNode()) 357 return false; 358 VisiblePosition pos(targetNode()->renderer()->positionForPoint(localPoint())); 359 if (pos.isNull()) 360 return false; 361 return m_innerNonSharedNode->document()->markers()->markersInRange( 362 makeRange(pos, pos).get(), DocumentMarker::Spelling | DocumentMarker::Grammar).size() > 0; 363 } 364 365 String HitTestResult::titleDisplayString() const 366 { 367 if (!m_innerURLElement) 368 return String(); 369 370 return displayString(m_innerURLElement->title(), m_innerURLElement.get()); 371 } 372 373 String HitTestResult::textContent() const 374 { 375 if (!m_innerURLElement) 376 return String(); 377 return m_innerURLElement->textContent(); 378 } 379 380 // FIXME: This function needs a better name and may belong in a different class. It's not 381 // really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this 382 // function would make more sense in the ContextMenu class, except that WebElementDictionary 383 // hooks into it. Anyway, we should architect this better. 384 bool HitTestResult::isContentEditable() const 385 { 386 if (!m_innerNonSharedNode) 387 return false; 388 389 if (isHTMLTextAreaElement(m_innerNonSharedNode.get())) 390 return true; 391 392 if (m_innerNonSharedNode->hasTagName(inputTag)) 393 return toHTMLInputElement(m_innerNonSharedNode.get())->isTextField(); 394 395 return m_innerNonSharedNode->rendererIsEditable(); 396 } 397 398 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect) 399 { 400 // If it is not a rect-based hit test, this method has to be no-op. 401 // Return false, so the hit test stops. 402 if (!isRectBasedTest()) 403 return false; 404 405 // If node is null, return true so the hit test can continue. 406 if (!node) 407 return true; 408 409 if (request.disallowsShadowContent()) 410 node = node->document()->ancestorInThisScope(node); 411 412 mutableRectBasedTestResult().add(node); 413 414 bool regionFilled = rect.contains(locationInContainer.boundingBox()); 415 return !regionFilled; 416 } 417 418 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect) 419 { 420 // If it is not a rect-based hit test, this method has to be no-op. 421 // Return false, so the hit test stops. 422 if (!isRectBasedTest()) 423 return false; 424 425 // If node is null, return true so the hit test can continue. 426 if (!node) 427 return true; 428 429 if (request.disallowsShadowContent()) 430 node = node->document()->ancestorInThisScope(node); 431 432 mutableRectBasedTestResult().add(node); 433 434 bool regionFilled = rect.contains(locationInContainer.boundingBox()); 435 return !regionFilled; 436 } 437 438 void HitTestResult::append(const HitTestResult& other) 439 { 440 ASSERT(isRectBasedTest() && other.isRectBasedTest()); 441 442 if (!m_innerNode && other.innerNode()) { 443 m_innerNode = other.innerNode(); 444 m_innerNonSharedNode = other.innerNonSharedNode(); 445 m_localPoint = other.localPoint(); 446 m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame; 447 m_innerURLElement = other.URLElement(); 448 m_scrollbar = other.scrollbar(); 449 m_isOverWidget = other.isOverWidget(); 450 } 451 452 if (other.m_rectBasedTestResult) { 453 NodeSet& set = mutableRectBasedTestResult(); 454 for (NodeSet::const_iterator it = other.m_rectBasedTestResult->begin(), last = other.m_rectBasedTestResult->end(); it != last; ++it) 455 set.add(it->get()); 456 } 457 } 458 459 const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const 460 { 461 if (!m_rectBasedTestResult) 462 m_rectBasedTestResult = adoptPtr(new NodeSet); 463 return *m_rectBasedTestResult; 464 } 465 466 HitTestResult::NodeSet& HitTestResult::mutableRectBasedTestResult() 467 { 468 if (!m_rectBasedTestResult) 469 m_rectBasedTestResult = adoptPtr(new NodeSet); 470 return *m_rectBasedTestResult; 471 } 472 473 Node* HitTestResult::targetNode() const 474 { 475 Node* node = innerNode(); 476 if (!node) 477 return 0; 478 if (node->inDocument()) 479 return node; 480 481 Element* element = node->parentElement(); 482 if (element && element->inDocument()) 483 return element; 484 485 return node; 486 } 487 488 Element* HitTestResult::innerElement() const 489 { 490 for (Node* node = m_innerNode.get(); node; node = NodeRenderingTraversal::parent(node)) 491 if (node->isElementNode()) 492 return toElement(node); 493 494 return 0; 495 } 496 497 } // namespace WebCore 498