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