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