1 /* 2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. 3 * 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. 8 * 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. 13 * 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. 18 * 19 */ 20 21 #include "config.h" 22 #include "HitTestResult.h" 23 24 #include "DocumentMarkerController.h" 25 #include "Frame.h" 26 #include "FrameTree.h" 27 #include "HTMLAnchorElement.h" 28 #include "HTMLVideoElement.h" 29 #include "HTMLImageElement.h" 30 #include "HTMLInputElement.h" 31 #include "HTMLMediaElement.h" 32 #include "HTMLNames.h" 33 #include "HTMLParserIdioms.h" 34 #include "RenderImage.h" 35 #include "RenderInline.h" 36 #include "Scrollbar.h" 37 #include "SelectionController.h" 38 39 #if ENABLE(SVG) 40 #include "SVGNames.h" 41 #include "XLinkNames.h" 42 #endif 43 44 #if ENABLE(WML) 45 #include "WMLImageElement.h" 46 #include "WMLNames.h" 47 #endif 48 49 namespace WebCore { 50 51 using namespace HTMLNames; 52 53 HitTestResult::HitTestResult() 54 : m_isOverWidget(false) 55 , m_isRectBased(false) 56 , m_topPadding(0) 57 , m_rightPadding(0) 58 , m_bottomPadding(0) 59 , m_leftPadding(0) 60 { 61 } 62 63 HitTestResult::HitTestResult(const IntPoint& point) 64 : m_point(point) 65 , m_isOverWidget(false) 66 , m_isRectBased(false) 67 , m_topPadding(0) 68 , m_rightPadding(0) 69 , m_bottomPadding(0) 70 , m_leftPadding(0) 71 { 72 } 73 74 HitTestResult::HitTestResult(const IntPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) 75 : m_point(centerPoint) 76 , m_isOverWidget(false) 77 , m_topPadding(topPadding) 78 , m_rightPadding(rightPadding) 79 , m_bottomPadding(bottomPadding) 80 , m_leftPadding(leftPadding) 81 { 82 // If all padding values passed in are zero then it is not a rect based hit test. 83 m_isRectBased = topPadding || rightPadding || bottomPadding || leftPadding; 84 85 // Make sure all padding values are clamped to zero if it is not a rect hit test. 86 if (!m_isRectBased) 87 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; 88 } 89 90 HitTestResult::HitTestResult(const HitTestResult& other) 91 : m_innerNode(other.innerNode()) 92 , m_innerNonSharedNode(other.innerNonSharedNode()) 93 , m_point(other.point()) 94 , m_localPoint(other.localPoint()) 95 , m_innerURLElement(other.URLElement()) 96 , m_scrollbar(other.scrollbar()) 97 , m_isOverWidget(other.isOverWidget()) 98 { 99 // Only copy the padding and NodeSet in case of rect hit test. 100 // Copying the later is rather expensive. 101 if ((m_isRectBased = other.isRectBasedTest())) { 102 m_topPadding = other.m_topPadding; 103 m_rightPadding = other.m_rightPadding; 104 m_bottomPadding = other.m_bottomPadding; 105 m_leftPadding = other.m_leftPadding; 106 } else 107 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; 108 109 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0); 110 } 111 112 HitTestResult::~HitTestResult() 113 { 114 } 115 116 HitTestResult& HitTestResult::operator=(const HitTestResult& other) 117 { 118 m_innerNode = other.innerNode(); 119 m_innerNonSharedNode = other.innerNonSharedNode(); 120 m_point = other.point(); 121 m_localPoint = other.localPoint(); 122 m_innerURLElement = other.URLElement(); 123 m_scrollbar = other.scrollbar(); 124 m_isOverWidget = other.isOverWidget(); 125 // Only copy the padding and NodeSet in case of rect hit test. 126 // Copying the later is rather expensive. 127 if ((m_isRectBased = other.isRectBasedTest())) { 128 m_topPadding = other.m_topPadding; 129 m_rightPadding = other.m_rightPadding; 130 m_bottomPadding = other.m_bottomPadding; 131 m_leftPadding = other.m_leftPadding; 132 } else 133 m_topPadding = m_rightPadding = m_bottomPadding = m_leftPadding = 0; 134 135 m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0); 136 return *this; 137 } 138 139 void HitTestResult::setToNonShadowAncestor() 140 { 141 Node* node = innerNode(); 142 if (node) 143 node = node->shadowAncestorNode(); 144 setInnerNode(node); 145 node = innerNonSharedNode(); 146 if (node) 147 node = node->shadowAncestorNode(); 148 setInnerNonSharedNode(node); 149 } 150 151 void HitTestResult::setInnerNode(Node* n) 152 { 153 m_innerNode = n; 154 } 155 156 void HitTestResult::setInnerNonSharedNode(Node* n) 157 { 158 m_innerNonSharedNode = n; 159 } 160 161 void HitTestResult::setURLElement(Element* n) 162 { 163 m_innerURLElement = n; 164 } 165 166 void HitTestResult::setScrollbar(Scrollbar* s) 167 { 168 m_scrollbar = s; 169 } 170 171 Frame* HitTestResult::targetFrame() const 172 { 173 if (!m_innerURLElement) 174 return 0; 175 176 Frame* frame = m_innerURLElement->document()->frame(); 177 if (!frame) 178 return 0; 179 180 return frame->tree()->find(m_innerURLElement->target()); 181 } 182 183 bool HitTestResult::isSelected() const 184 { 185 if (!m_innerNonSharedNode) 186 return false; 187 188 Frame* frame = m_innerNonSharedNode->document()->frame(); 189 if (!frame) 190 return false; 191 192 return frame->selection()->contains(m_point); 193 } 194 195 String HitTestResult::spellingToolTip(TextDirection& dir) const 196 { 197 dir = LTR; 198 // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar 199 // currently supply strings, but maybe someday markers associated with misspelled words will also. 200 if (!m_innerNonSharedNode) 201 return String(); 202 203 DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Grammar); 204 if (!marker) 205 return String(); 206 207 if (RenderObject* renderer = m_innerNonSharedNode->renderer()) 208 dir = renderer->style()->direction(); 209 return marker->description; 210 } 211 212 String HitTestResult::replacedString() const 213 { 214 // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected, 215 // and is used for generating a contextual menu item that allows it to easily be changed back if desired. 216 if (!m_innerNonSharedNode) 217 return String(); 218 219 DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_point, DocumentMarker::Replacement); 220 if (!marker) 221 return String(); 222 223 return marker->description; 224 } 225 226 String HitTestResult::title(TextDirection& dir) const 227 { 228 dir = LTR; 229 // Find the title in the nearest enclosing DOM node. 230 // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it. 231 for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) { 232 if (titleNode->isElementNode()) { 233 String title = static_cast<Element*>(titleNode)->title(); 234 if (!title.isEmpty()) { 235 if (RenderObject* renderer = titleNode->renderer()) 236 dir = renderer->style()->direction(); 237 return title; 238 } 239 } 240 } 241 return String(); 242 } 243 244 String displayString(const String& string, const Node* node) 245 { 246 if (!node) 247 return string; 248 return node->document()->displayStringModifiedByEncoding(string); 249 } 250 251 String HitTestResult::altDisplayString() const 252 { 253 if (!m_innerNonSharedNode) 254 return String(); 255 256 if (m_innerNonSharedNode->hasTagName(imgTag)) { 257 HTMLImageElement* image = static_cast<HTMLImageElement*>(m_innerNonSharedNode.get()); 258 return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get()); 259 } 260 261 if (m_innerNonSharedNode->hasTagName(inputTag)) { 262 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_innerNonSharedNode.get()); 263 return displayString(input->alt(), m_innerNonSharedNode.get()); 264 } 265 266 #if ENABLE(WML) 267 if (m_innerNonSharedNode->hasTagName(WMLNames::imgTag)) { 268 WMLImageElement* image = static_cast<WMLImageElement*>(m_innerNonSharedNode.get()); 269 return displayString(image->altText(), m_innerNonSharedNode.get()); 270 } 271 #endif 272 273 return String(); 274 } 275 276 Image* HitTestResult::image() const 277 { 278 if (!m_innerNonSharedNode) 279 return 0; 280 281 RenderObject* renderer = m_innerNonSharedNode->renderer(); 282 if (renderer && renderer->isImage()) { 283 RenderImage* image = static_cast<WebCore::RenderImage*>(renderer); 284 if (image->cachedImage() && !image->cachedImage()->errorOccurred()) 285 return image->cachedImage()->image(); 286 } 287 288 return 0; 289 } 290 291 IntRect HitTestResult::imageRect() const 292 { 293 if (!image()) 294 return IntRect(); 295 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox(); 296 } 297 298 KURL HitTestResult::absoluteImageURL() const 299 { 300 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) 301 return KURL(); 302 303 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage())) 304 return KURL(); 305 306 AtomicString urlString; 307 if (m_innerNonSharedNode->hasTagName(embedTag) 308 || m_innerNonSharedNode->hasTagName(imgTag) 309 || m_innerNonSharedNode->hasTagName(inputTag) 310 || m_innerNonSharedNode->hasTagName(objectTag) 311 #if ENABLE(SVG) 312 || m_innerNonSharedNode->hasTagName(SVGNames::imageTag) 313 #endif 314 #if ENABLE(WML) 315 || m_innerNonSharedNode->hasTagName(WMLNames::imgTag) 316 #endif 317 ) { 318 Element* element = static_cast<Element*>(m_innerNonSharedNode.get()); 319 urlString = element->getAttribute(element->imageSourceAttributeName()); 320 } else 321 return KURL(); 322 323 return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 324 } 325 326 KURL HitTestResult::absoluteMediaURL() const 327 { 328 #if ENABLE(VIDEO) 329 if (HTMLMediaElement* mediaElt = mediaElement()) 330 return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(mediaElt->currentSrc())); 331 return KURL(); 332 #else 333 return KURL(); 334 #endif 335 } 336 337 bool HitTestResult::mediaSupportsFullscreen() const 338 { 339 #if ENABLE(VIDEO) 340 HTMLMediaElement* mediaElt(mediaElement()); 341 return (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag) && mediaElt->supportsFullscreen()); 342 #else 343 return false; 344 #endif 345 } 346 347 #if ENABLE(VIDEO) 348 HTMLMediaElement* HitTestResult::mediaElement() const 349 { 350 if (!(m_innerNonSharedNode && m_innerNonSharedNode->document())) 351 return 0; 352 353 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia())) 354 return 0; 355 356 if (m_innerNonSharedNode->hasTagName(HTMLNames::videoTag) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag)) 357 return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get()); 358 return 0; 359 } 360 #endif 361 362 void HitTestResult::toggleMediaControlsDisplay() const 363 { 364 #if ENABLE(VIDEO) 365 if (HTMLMediaElement* mediaElt = mediaElement()) 366 mediaElt->setControls(!mediaElt->controls()); 367 #endif 368 } 369 370 void HitTestResult::toggleMediaLoopPlayback() const 371 { 372 #if ENABLE(VIDEO) 373 if (HTMLMediaElement* mediaElt = mediaElement()) 374 mediaElt->setLoop(!mediaElt->loop()); 375 #endif 376 } 377 378 void HitTestResult::enterFullscreenForVideo() const 379 { 380 #if ENABLE(VIDEO) 381 HTMLMediaElement* mediaElt(mediaElement()); 382 if (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag)) { 383 HTMLVideoElement* videoElt = static_cast<HTMLVideoElement*>(mediaElt); 384 if (!videoElt->isFullscreen() && mediaElt->supportsFullscreen()) 385 videoElt->enterFullscreen(); 386 } 387 #endif 388 } 389 390 bool HitTestResult::mediaControlsEnabled() const 391 { 392 #if ENABLE(VIDEO) 393 if (HTMLMediaElement* mediaElt = mediaElement()) 394 return mediaElt->controls(); 395 #endif 396 return false; 397 } 398 399 bool HitTestResult::mediaLoopEnabled() const 400 { 401 #if ENABLE(VIDEO) 402 if (HTMLMediaElement* mediaElt = mediaElement()) 403 return mediaElt->loop(); 404 #endif 405 return false; 406 } 407 408 bool HitTestResult::mediaPlaying() const 409 { 410 #if ENABLE(VIDEO) 411 if (HTMLMediaElement* mediaElt = mediaElement()) 412 return !mediaElt->paused(); 413 #endif 414 return false; 415 } 416 417 void HitTestResult::toggleMediaPlayState() const 418 { 419 #if ENABLE(VIDEO) 420 if (HTMLMediaElement* mediaElt = mediaElement()) 421 mediaElt->togglePlayState(); 422 #endif 423 } 424 425 bool HitTestResult::mediaHasAudio() const 426 { 427 #if ENABLE(VIDEO) 428 if (HTMLMediaElement* mediaElt = mediaElement()) 429 return mediaElt->hasAudio(); 430 #endif 431 return false; 432 } 433 434 bool HitTestResult::mediaIsVideo() const 435 { 436 #if ENABLE(VIDEO) 437 if (HTMLMediaElement* mediaElt = mediaElement()) 438 return mediaElt->hasTagName(HTMLNames::videoTag); 439 #endif 440 return false; 441 } 442 443 bool HitTestResult::mediaMuted() const 444 { 445 #if ENABLE(VIDEO) 446 if (HTMLMediaElement* mediaElt = mediaElement()) 447 return mediaElt->muted(); 448 #endif 449 return false; 450 } 451 452 void HitTestResult::toggleMediaMuteState() const 453 { 454 #if ENABLE(VIDEO) 455 if (HTMLMediaElement* mediaElt = mediaElement()) 456 mediaElt->setMuted(!mediaElt->muted()); 457 #endif 458 } 459 460 KURL HitTestResult::absoluteLinkURL() const 461 { 462 if (!(m_innerURLElement && m_innerURLElement->document())) 463 return KURL(); 464 465 AtomicString urlString; 466 if (m_innerURLElement->hasTagName(aTag) || m_innerURLElement->hasTagName(areaTag) || m_innerURLElement->hasTagName(linkTag)) 467 urlString = m_innerURLElement->getAttribute(hrefAttr); 468 #if ENABLE(SVG) 469 else if (m_innerURLElement->hasTagName(SVGNames::aTag)) 470 urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr); 471 #endif 472 #if ENABLE(WML) 473 else if (m_innerURLElement->hasTagName(WMLNames::aTag)) 474 urlString = m_innerURLElement->getAttribute(hrefAttr); 475 #endif 476 else 477 return KURL(); 478 479 return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 480 } 481 482 bool HitTestResult::isLiveLink() const 483 { 484 if (!(m_innerURLElement && m_innerURLElement->document())) 485 return false; 486 487 if (m_innerURLElement->hasTagName(aTag)) 488 return static_cast<HTMLAnchorElement*>(m_innerURLElement.get())->isLiveLink(); 489 #if ENABLE(SVG) 490 if (m_innerURLElement->hasTagName(SVGNames::aTag)) 491 return m_innerURLElement->isLink(); 492 #endif 493 #if ENABLE(WML) 494 if (m_innerURLElement->hasTagName(WMLNames::aTag)) 495 return m_innerURLElement->isLink(); 496 #endif 497 498 return false; 499 } 500 501 String HitTestResult::titleDisplayString() const 502 { 503 if (!m_innerURLElement) 504 return String(); 505 506 return displayString(m_innerURLElement->title(), m_innerURLElement.get()); 507 } 508 509 String HitTestResult::textContent() const 510 { 511 if (!m_innerURLElement) 512 return String(); 513 return m_innerURLElement->textContent(); 514 } 515 516 // FIXME: This function needs a better name and may belong in a different class. It's not 517 // really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this 518 // function would make more sense in the ContextMenu class, except that WebElementDictionary 519 // hooks into it. Anyway, we should architect this better. 520 bool HitTestResult::isContentEditable() const 521 { 522 if (!m_innerNonSharedNode) 523 return false; 524 525 if (m_innerNonSharedNode->hasTagName(textareaTag) || m_innerNonSharedNode->hasTagName(isindexTag)) 526 return true; 527 528 if (m_innerNonSharedNode->hasTagName(inputTag)) 529 return static_cast<HTMLInputElement*>(m_innerNonSharedNode.get())->isTextField(); 530 531 return m_innerNonSharedNode->rendererIsEditable(); 532 } 533 534 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, int x, int y, const IntRect& rect) 535 { 536 // If it is not a rect-based hit test, this method has to be no-op. 537 // Return false, so the hit test stops. 538 if (!isRectBasedTest()) 539 return false; 540 541 // If node is null, return true so the hit test can continue. 542 if (!node) 543 return true; 544 545 node = node->shadowAncestorNode(); 546 mutableRectBasedTestResult().add(node); 547 548 if (node->renderer()->isInline()) { 549 for (RenderObject* curr = node->renderer()->parent(); curr; curr = curr->parent()) { 550 if (!curr->isRenderInline()) 551 break; 552 553 // We need to make sure the nodes for culled inlines get included. 554 RenderInline* currInline = toRenderInline(curr); 555 if (currInline->alwaysCreateLineBoxes()) 556 break; 557 558 if (currInline->visibleToHitTesting() && currInline->node()) 559 mutableRectBasedTestResult().add(currInline->node()->shadowAncestorNode()); 560 } 561 } 562 return !rect.contains(rectForPoint(x, y)); 563 } 564 565 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, int x, int y, const FloatRect& rect) 566 { 567 // If it is not a rect-based hit test, this method has to be no-op. 568 // Return false, so the hit test stops. 569 if (!isRectBasedTest()) 570 return false; 571 572 // If node is null, return true so the hit test can continue. 573 if (!node) 574 return true; 575 576 node = node->shadowAncestorNode(); 577 mutableRectBasedTestResult().add(node); 578 579 if (node->renderer()->isInline()) { 580 for (RenderObject* curr = node->renderer()->parent(); curr; curr = curr->parent()) { 581 if (!curr->isRenderInline()) 582 break; 583 584 // We need to make sure the nodes for culled inlines get included. 585 RenderInline* currInline = toRenderInline(curr); 586 if (currInline->alwaysCreateLineBoxes()) 587 break; 588 589 if (currInline->visibleToHitTesting() && currInline->node()) 590 mutableRectBasedTestResult().add(currInline->node()->shadowAncestorNode()); 591 } 592 } 593 return !rect.contains(rectForPoint(x, y)); 594 } 595 596 void HitTestResult::append(const HitTestResult& other) 597 { 598 ASSERT(isRectBasedTest() && other.isRectBasedTest()); 599 600 if (!m_innerNode && other.innerNode()) { 601 m_innerNode = other.innerNode(); 602 m_innerNonSharedNode = other.innerNonSharedNode(); 603 m_localPoint = other.localPoint(); 604 m_innerURLElement = other.URLElement(); 605 m_scrollbar = other.scrollbar(); 606 m_isOverWidget = other.isOverWidget(); 607 } 608 609 if (other.m_rectBasedTestResult) { 610 NodeSet& set = mutableRectBasedTestResult(); 611 for (NodeSet::const_iterator it = other.m_rectBasedTestResult->begin(), last = other.m_rectBasedTestResult->end(); it != last; ++it) 612 set.add(it->get()); 613 } 614 } 615 616 IntRect HitTestResult::rectForPoint(const IntPoint& point, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding) 617 { 618 IntPoint actualPoint(point); 619 actualPoint -= IntSize(leftPadding, topPadding); 620 621 IntSize actualPadding(leftPadding + rightPadding, topPadding + bottomPadding); 622 // As IntRect is left inclusive and right exclusive (seeing IntRect::contains(x, y)), adding "1". 623 actualPadding += IntSize(1, 1); 624 625 return IntRect(actualPoint, actualPadding); 626 } 627 628 const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const 629 { 630 if (!m_rectBasedTestResult) 631 m_rectBasedTestResult = adoptPtr(new NodeSet); 632 return *m_rectBasedTestResult; 633 } 634 635 HitTestResult::NodeSet& HitTestResult::mutableRectBasedTestResult() 636 { 637 if (!m_rectBasedTestResult) 638 m_rectBasedTestResult = adoptPtr(new NodeSet); 639 return *m_rectBasedTestResult; 640 } 641 642 } // namespace WebCore 643