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