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 49namespace WebCore { 50 51using namespace HTMLNames; 52 53HitTestResult::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 63HitTestResult::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 74HitTestResult::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 90HitTestResult::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 112HitTestResult::~HitTestResult() 113{ 114} 115 116HitTestResult& 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 139void 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 151void HitTestResult::setInnerNode(Node* n) 152{ 153 m_innerNode = n; 154} 155 156void HitTestResult::setInnerNonSharedNode(Node* n) 157{ 158 m_innerNonSharedNode = n; 159} 160 161void HitTestResult::setURLElement(Element* n) 162{ 163 m_innerURLElement = n; 164} 165 166void HitTestResult::setScrollbar(Scrollbar* s) 167{ 168 m_scrollbar = s; 169} 170 171Frame* 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 183bool 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 195String 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 212String 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 226String 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 244String displayString(const String& string, const Node* node) 245{ 246 if (!node) 247 return string; 248 return node->document()->displayStringModifiedByEncoding(string); 249} 250 251String 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 276Image* 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 291IntRect HitTestResult::imageRect() const 292{ 293 if (!image()) 294 return IntRect(); 295 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox(); 296} 297 298KURL 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 326KURL 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 337bool 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) 348HTMLMediaElement* 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 362void HitTestResult::toggleMediaControlsDisplay() const 363{ 364#if ENABLE(VIDEO) 365 if (HTMLMediaElement* mediaElt = mediaElement()) 366 mediaElt->setControls(!mediaElt->controls()); 367#endif 368} 369 370void HitTestResult::toggleMediaLoopPlayback() const 371{ 372#if ENABLE(VIDEO) 373 if (HTMLMediaElement* mediaElt = mediaElement()) 374 mediaElt->setLoop(!mediaElt->loop()); 375#endif 376} 377 378void 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 390bool HitTestResult::mediaControlsEnabled() const 391{ 392#if ENABLE(VIDEO) 393 if (HTMLMediaElement* mediaElt = mediaElement()) 394 return mediaElt->controls(); 395#endif 396 return false; 397} 398 399bool HitTestResult::mediaLoopEnabled() const 400{ 401#if ENABLE(VIDEO) 402 if (HTMLMediaElement* mediaElt = mediaElement()) 403 return mediaElt->loop(); 404#endif 405 return false; 406} 407 408bool HitTestResult::mediaPlaying() const 409{ 410#if ENABLE(VIDEO) 411 if (HTMLMediaElement* mediaElt = mediaElement()) 412 return !mediaElt->paused(); 413#endif 414 return false; 415} 416 417void HitTestResult::toggleMediaPlayState() const 418{ 419#if ENABLE(VIDEO) 420 if (HTMLMediaElement* mediaElt = mediaElement()) 421 mediaElt->togglePlayState(); 422#endif 423} 424 425bool HitTestResult::mediaHasAudio() const 426{ 427#if ENABLE(VIDEO) 428 if (HTMLMediaElement* mediaElt = mediaElement()) 429 return mediaElt->hasAudio(); 430#endif 431 return false; 432} 433 434bool 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 443bool HitTestResult::mediaMuted() const 444{ 445#if ENABLE(VIDEO) 446 if (HTMLMediaElement* mediaElt = mediaElement()) 447 return mediaElt->muted(); 448#endif 449 return false; 450} 451 452void HitTestResult::toggleMediaMuteState() const 453{ 454#if ENABLE(VIDEO) 455 if (HTMLMediaElement* mediaElt = mediaElement()) 456 mediaElt->setMuted(!mediaElt->muted()); 457#endif 458} 459 460KURL 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 482bool 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 501String HitTestResult::titleDisplayString() const 502{ 503 if (!m_innerURLElement) 504 return String(); 505 506 return displayString(m_innerURLElement->title(), m_innerURLElement.get()); 507} 508 509String 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. 520bool 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 534bool 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 565bool 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 596void 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 616IntRect 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 628const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const 629{ 630 if (!m_rectBasedTestResult) 631 m_rectBasedTestResult = adoptPtr(new NodeSet); 632 return *m_rectBasedTestResult; 633} 634 635HitTestResult::NodeSet& HitTestResult::mutableRectBasedTestResult() 636{ 637 if (!m_rectBasedTestResult) 638 m_rectBasedTestResult = adoptPtr(new NodeSet); 639 return *m_rectBasedTestResult; 640} 641 642} // namespace WebCore 643