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
HitTestResult()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
HitTestResult(const IntPoint & point)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
HitTestResult(const IntPoint & centerPoint,unsigned topPadding,unsigned rightPadding,unsigned bottomPadding,unsigned leftPadding)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
HitTestResult(const HitTestResult & other)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
~HitTestResult()112 HitTestResult::~HitTestResult()
113 {
114 }
115
operator =(const HitTestResult & other)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
setToNonShadowAncestor()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
setInnerNode(Node * n)151 void HitTestResult::setInnerNode(Node* n)
152 {
153 m_innerNode = n;
154 }
155
setInnerNonSharedNode(Node * n)156 void HitTestResult::setInnerNonSharedNode(Node* n)
157 {
158 m_innerNonSharedNode = n;
159 }
160
setURLElement(Element * n)161 void HitTestResult::setURLElement(Element* n)
162 {
163 m_innerURLElement = n;
164 }
165
setScrollbar(Scrollbar * s)166 void HitTestResult::setScrollbar(Scrollbar* s)
167 {
168 m_scrollbar = s;
169 }
170
targetFrame() const171 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
isSelected() const183 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
spellingToolTip(TextDirection & dir) const195 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
replacedString() const212 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
title(TextDirection & dir) const226 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
displayString(const String & string,const Node * node)244 String displayString(const String& string, const Node* node)
245 {
246 if (!node)
247 return string;
248 return node->document()->displayStringModifiedByEncoding(string);
249 }
250
altDisplayString() const251 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
image() const276 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
imageRect() const291 IntRect HitTestResult::imageRect() const
292 {
293 if (!image())
294 return IntRect();
295 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox();
296 }
297
absoluteImageURL() const298 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
absoluteMediaURL() const326 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
mediaSupportsFullscreen() const337 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)
mediaElement() const348 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
toggleMediaControlsDisplay() const362 void HitTestResult::toggleMediaControlsDisplay() const
363 {
364 #if ENABLE(VIDEO)
365 if (HTMLMediaElement* mediaElt = mediaElement())
366 mediaElt->setControls(!mediaElt->controls());
367 #endif
368 }
369
toggleMediaLoopPlayback() const370 void HitTestResult::toggleMediaLoopPlayback() const
371 {
372 #if ENABLE(VIDEO)
373 if (HTMLMediaElement* mediaElt = mediaElement())
374 mediaElt->setLoop(!mediaElt->loop());
375 #endif
376 }
377
enterFullscreenForVideo() const378 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
mediaControlsEnabled() const390 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
mediaLoopEnabled() const399 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
mediaPlaying() const408 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
toggleMediaPlayState() const417 void HitTestResult::toggleMediaPlayState() const
418 {
419 #if ENABLE(VIDEO)
420 if (HTMLMediaElement* mediaElt = mediaElement())
421 mediaElt->togglePlayState();
422 #endif
423 }
424
mediaHasAudio() const425 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
mediaIsVideo() const434 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
mediaMuted() const443 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
toggleMediaMuteState() const452 void HitTestResult::toggleMediaMuteState() const
453 {
454 #if ENABLE(VIDEO)
455 if (HTMLMediaElement* mediaElt = mediaElement())
456 mediaElt->setMuted(!mediaElt->muted());
457 #endif
458 }
459
absoluteLinkURL() const460 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
isLiveLink() const482 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
titleDisplayString() const501 String HitTestResult::titleDisplayString() const
502 {
503 if (!m_innerURLElement)
504 return String();
505
506 return displayString(m_innerURLElement->title(), m_innerURLElement.get());
507 }
508
textContent() const509 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.
isContentEditable() const520 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
addNodeToRectBasedTestResult(Node * node,int x,int y,const IntRect & rect)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
addNodeToRectBasedTestResult(Node * node,int x,int y,const FloatRect & rect)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
append(const HitTestResult & other)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
rectForPoint(const IntPoint & point,unsigned topPadding,unsigned rightPadding,unsigned bottomPadding,unsigned leftPadding)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
rectBasedTestResult() const628 const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const
629 {
630 if (!m_rectBasedTestResult)
631 m_rectBasedTestResult = adoptPtr(new NodeSet);
632 return *m_rectBasedTestResult;
633 }
634
mutableRectBasedTestResult()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