Allow for toggling fullscreen on <video> elements
[WebKit-https.git] / Source / WebCore / rendering / HitTestResult.cpp
1 /*
2  * Copyright (C) 2006, 2008, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20 */
21
22 #include "config.h"
23 #include "HitTestResult.h"
24
25 #include "CachedImage.h"
26 #include "DocumentMarkerController.h"
27 #include "Editor.h"
28 #include "Frame.h"
29 #include "FrameSelection.h"
30 #include "FrameTree.h"
31 #include "HTMLAnchorElement.h"
32 #include "HTMLImageElement.h"
33 #include "HTMLInputElement.h"
34 #include "HTMLMediaElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "HTMLPlugInImageElement.h"
38 #include "HTMLVideoElement.h"
39 #include "HitTestLocation.h"
40 #include "RenderBlock.h"
41 #include "RenderImage.h"
42 #include "RenderInline.h"
43 #include "Scrollbar.h"
44 #include "UserGestureIndicator.h"
45
46 #if ENABLE(SVG)
47 #include "SVGNames.h"
48 #include "XLinkNames.h"
49 #endif
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 HitTestResult::HitTestResult()
56     : m_isOverWidget(false)
57 {
58 }
59
60 HitTestResult::HitTestResult(const LayoutPoint& point)
61     : m_hitTestLocation(point)
62     , m_pointInInnerNodeFrame(point)
63     , m_isOverWidget(false)
64 {
65 }
66
67 HitTestResult::HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
68     : m_hitTestLocation(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding)
69     , m_pointInInnerNodeFrame(centerPoint)
70     , m_isOverWidget(false)
71 {
72 }
73
74 HitTestResult::HitTestResult(const HitTestLocation& other)
75     : m_hitTestLocation(other)
76     , m_pointInInnerNodeFrame(m_hitTestLocation.point())
77     , m_isOverWidget(false)
78 {
79 }
80
81 HitTestResult::HitTestResult(const HitTestResult& other)
82     : m_hitTestLocation(other.m_hitTestLocation)
83     , m_innerNode(other.innerNode())
84     , m_innerNonSharedNode(other.innerNonSharedNode())
85     , m_pointInInnerNodeFrame(other.m_pointInInnerNodeFrame)
86     , m_localPoint(other.localPoint())
87     , m_innerURLElement(other.URLElement())
88     , m_scrollbar(other.scrollbar())
89     , m_isOverWidget(other.isOverWidget())
90 {
91     // Only copy the NodeSet in case of rect hit test.
92     m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0);
93 }
94
95 HitTestResult::~HitTestResult()
96 {
97 }
98
99 HitTestResult& HitTestResult::operator=(const HitTestResult& other)
100 {
101     m_hitTestLocation = other.m_hitTestLocation;
102     m_innerNode = other.innerNode();
103     m_innerNonSharedNode = other.innerNonSharedNode();
104     m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame;
105     m_localPoint = other.localPoint();
106     m_innerURLElement = other.URLElement();
107     m_scrollbar = other.scrollbar();
108     m_isOverWidget = other.isOverWidget();
109
110     // Only copy the NodeSet in case of rect hit test.
111     m_rectBasedTestResult = adoptPtr(other.m_rectBasedTestResult ? new NodeSet(*other.m_rectBasedTestResult) : 0);
112
113     return *this;
114 }
115
116 void HitTestResult::setToNonShadowAncestor()
117 {
118     Node* node = innerNode();
119     if (node)
120         node = node->document()->ancestorInThisScope(node);
121     setInnerNode(node);
122     node = innerNonSharedNode();
123     if (node)
124         node = node->document()->ancestorInThisScope(node);
125     setInnerNonSharedNode(node);
126 }
127
128 void HitTestResult::setInnerNode(Node* n)
129 {
130     if (n && n->isPseudoElement())
131         n = n->parentOrShadowHostNode();
132     m_innerNode = n;
133 }
134     
135 void HitTestResult::setInnerNonSharedNode(Node* n)
136 {
137     if (n && n->isPseudoElement())
138         n = n->parentOrShadowHostNode();
139     m_innerNonSharedNode = n;
140 }
141
142 void HitTestResult::setURLElement(Element* n) 
143
144     m_innerURLElement = n; 
145 }
146
147 void HitTestResult::setScrollbar(Scrollbar* s)
148 {
149     m_scrollbar = s;
150 }
151
152 Frame* HitTestResult::innerNodeFrame() const
153 {
154     if (m_innerNonSharedNode)
155         return m_innerNonSharedNode->document()->frame();
156     if (m_innerNode)
157         return m_innerNode->document()->frame();
158     return 0;
159 }
160
161 Frame* HitTestResult::targetFrame() const
162 {
163     if (!m_innerURLElement)
164         return 0;
165
166     Frame* frame = m_innerURLElement->document()->frame();
167     if (!frame)
168         return 0;
169
170     return frame->tree()->find(m_innerURLElement->target());
171 }
172
173 bool HitTestResult::isSelected() const
174 {
175     if (!m_innerNonSharedNode)
176         return false;
177
178     Frame* frame = m_innerNonSharedNode->document()->frame();
179     if (!frame)
180         return false;
181
182     return frame->selection()->contains(m_hitTestLocation.point());
183 }
184
185 String HitTestResult::spellingToolTip(TextDirection& dir) const
186 {
187     dir = LTR;
188     // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar
189     // currently supply strings, but maybe someday markers associated with misspelled words will also.
190     if (!m_innerNonSharedNode)
191         return String();
192     
193     DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_hitTestLocation.point(), DocumentMarker::Grammar);
194     if (!marker)
195         return String();
196
197     if (RenderObject* renderer = m_innerNonSharedNode->renderer())
198         dir = renderer->style()->direction();
199     return marker->description();
200 }
201
202 String HitTestResult::replacedString() const
203 {
204     // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected, 
205     // and is used for generating a contextual menu item that allows it to easily be changed back if desired.
206     if (!m_innerNonSharedNode)
207         return String();
208     
209     DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(m_hitTestLocation.point(), DocumentMarker::Replacement);
210     if (!marker)
211         return String();
212     
213     return marker->description();
214 }    
215     
216 String HitTestResult::title(TextDirection& dir) const
217 {
218     dir = LTR;
219     // Find the title in the nearest enclosing DOM node.
220     // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it.
221     for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentNode()) {
222         if (titleNode->isElementNode()) {
223             String title = toElement(titleNode)->title();
224             if (!title.isEmpty()) {
225                 if (RenderObject* renderer = titleNode->renderer())
226                     dir = renderer->style()->direction();
227                 return title;
228             }
229         }
230     }
231     return String();
232 }
233
234 String HitTestResult::innerTextIfTruncated(TextDirection& dir) const
235 {
236     for (Node* truncatedNode = m_innerNode.get(); truncatedNode; truncatedNode = truncatedNode->parentNode()) {
237         if (!truncatedNode->isElementNode())
238             continue;
239
240         if (RenderObject* renderer = truncatedNode->renderer()) {
241             if (renderer->isRenderBlock()) {
242                 RenderBlock* block = toRenderBlock(renderer);
243                 if (block->style()->textOverflow()) {
244                     for (RootInlineBox* line = block->firstRootBox(); line; line = line->nextRootBox()) {
245                         if (line->hasEllipsisBox()) {
246                             dir = block->style()->direction();
247                             return toElement(truncatedNode)->innerText();
248                         }
249                     }
250                 }
251                 break;
252             }
253         }
254     }
255
256     dir = LTR;
257     return String();
258 }
259
260 String displayString(const String& string, const Node* node)
261 {
262     if (!node)
263         return string;
264     return node->document()->displayStringModifiedByEncoding(string);
265 }
266
267 String HitTestResult::altDisplayString() const
268 {
269     if (!m_innerNonSharedNode)
270         return String();
271     
272     if (m_innerNonSharedNode->hasTagName(imgTag)) {
273         HTMLImageElement* image = static_cast<HTMLImageElement*>(m_innerNonSharedNode.get());
274         return displayString(image->getAttribute(altAttr), m_innerNonSharedNode.get());
275     }
276     
277     if (m_innerNonSharedNode->hasTagName(inputTag)) {
278         HTMLInputElement* input = static_cast<HTMLInputElement*>(m_innerNonSharedNode.get());
279         return displayString(input->alt(), m_innerNonSharedNode.get());
280     }
281
282     return String();
283 }
284
285 Image* HitTestResult::image() const
286 {
287     if (!m_innerNonSharedNode)
288         return 0;
289     
290     RenderObject* renderer = m_innerNonSharedNode->renderer();
291     if (renderer && renderer->isImage()) {
292         RenderImage* image = static_cast<WebCore::RenderImage*>(renderer);
293         if (image->cachedImage() && !image->cachedImage()->errorOccurred())
294             return image->cachedImage()->imageForRenderer(image);
295     }
296
297     return 0;
298 }
299
300 IntRect HitTestResult::imageRect() const
301 {
302     if (!image())
303         return IntRect();
304     return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox();
305 }
306
307 KURL HitTestResult::absoluteImageURL() const
308 {
309     if (!(m_innerNonSharedNode && m_innerNonSharedNode->document()))
310         return KURL();
311
312     if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage()))
313         return KURL();
314
315     AtomicString urlString;
316     if (m_innerNonSharedNode->hasTagName(embedTag)
317         || m_innerNonSharedNode->hasTagName(imgTag)
318         || m_innerNonSharedNode->hasTagName(inputTag)
319         || m_innerNonSharedNode->hasTagName(objectTag)    
320 #if ENABLE(SVG)
321         || m_innerNonSharedNode->hasTagName(SVGNames::imageTag)
322 #endif
323        ) {
324         Element* element = toElement(m_innerNonSharedNode.get());
325         urlString = element->imageSourceURL();
326     } else
327         return KURL();
328
329     return m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
330 }
331
332 KURL HitTestResult::absolutePDFURL() const
333 {
334     if (!(m_innerNonSharedNode && m_innerNonSharedNode->document()))
335         return KURL();
336
337     if (!m_innerNonSharedNode->hasTagName(embedTag) && !m_innerNonSharedNode->hasTagName(objectTag))
338         return KURL();
339
340     HTMLPlugInImageElement* element = toHTMLPlugInImageElement(m_innerNonSharedNode.get());
341     KURL url = m_innerNonSharedNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(element->url()));
342     if (!url.isValid())
343         return KURL();
344
345     if (element->serviceType() == "application/pdf" || (element->serviceType().isEmpty() && url.path().lower().endsWith(".pdf")))
346         return url;
347     return KURL();
348 }
349
350 KURL HitTestResult::absoluteMediaURL() const
351 {
352 #if ENABLE(VIDEO)
353     if (HTMLMediaElement* mediaElt = mediaElement())
354         return mediaElt->currentSrc();
355     return KURL();
356 #else
357     return KURL();
358 #endif
359 }
360
361 bool HitTestResult::mediaSupportsFullscreen() const
362 {
363 #if ENABLE(VIDEO)
364     HTMLMediaElement* mediaElt(mediaElement());
365     return (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag) && mediaElt->supportsFullscreen());
366 #else
367     return false;
368 #endif
369 }
370
371 #if ENABLE(VIDEO)
372 HTMLMediaElement* HitTestResult::mediaElement() const
373 {
374     if (!(m_innerNonSharedNode && m_innerNonSharedNode->document()))
375         return 0;
376
377     if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia()))
378         return 0;
379
380     if (m_innerNonSharedNode->hasTagName(HTMLNames::videoTag) || m_innerNonSharedNode->hasTagName(HTMLNames::audioTag))
381         return static_cast<HTMLMediaElement*>(m_innerNonSharedNode.get());
382     return 0;
383 }
384 #endif
385
386 void HitTestResult::toggleMediaControlsDisplay() const
387 {
388 #if ENABLE(VIDEO)
389     if (HTMLMediaElement* mediaElt = mediaElement())
390         mediaElt->setControls(!mediaElt->controls());
391 #endif
392 }
393
394 void HitTestResult::toggleMediaLoopPlayback() const
395 {
396 #if ENABLE(VIDEO)
397     if (HTMLMediaElement* mediaElt = mediaElement())
398         mediaElt->setLoop(!mediaElt->loop());
399 #endif
400 }
401
402 bool HitTestResult::mediaIsInFullscreen() const
403 {
404 #if ENABLE(VIDEO)
405     if (HTMLMediaElement* mediaElement = this->mediaElement())
406         return mediaElement->isVideo() && mediaElement->isFullscreen();
407 #endif
408     return false;
409 }
410
411 void HitTestResult::toggleMediaFullscreenState() const
412 {
413 #if ENABLE(VIDEO)
414     if (HTMLMediaElement* mediaElement = this->mediaElement()) {
415         if (mediaElement->isVideo() && mediaElement->supportsFullscreen()) {
416             UserGestureIndicator indicator(DefinitelyProcessingNewUserGesture);
417             mediaElement->toggleFullscreenState();
418         }
419     }
420 #endif
421 }
422
423 void HitTestResult::enterFullscreenForVideo() const
424 {
425 #if ENABLE(VIDEO)
426     HTMLMediaElement* mediaElt(mediaElement());
427     if (mediaElt && mediaElt->hasTagName(HTMLNames::videoTag)) {
428         HTMLVideoElement* videoElt = static_cast<HTMLVideoElement*>(mediaElt);
429         if (!videoElt->isFullscreen() && mediaElt->supportsFullscreen()) {
430             UserGestureIndicator indicator(DefinitelyProcessingNewUserGesture);
431             videoElt->enterFullscreen();
432         }
433     }
434 #endif
435 }
436
437 bool HitTestResult::mediaControlsEnabled() const
438 {
439 #if ENABLE(VIDEO)
440     if (HTMLMediaElement* mediaElt = mediaElement())
441         return mediaElt->controls();
442 #endif
443     return false;
444 }
445
446 bool HitTestResult::mediaLoopEnabled() const
447 {
448 #if ENABLE(VIDEO)
449     if (HTMLMediaElement* mediaElt = mediaElement())
450         return mediaElt->loop();
451 #endif
452     return false;
453 }
454
455 bool HitTestResult::mediaPlaying() const
456 {
457 #if ENABLE(VIDEO)
458     if (HTMLMediaElement* mediaElt = mediaElement())
459         return !mediaElt->paused();
460 #endif
461     return false;
462 }
463
464 void HitTestResult::toggleMediaPlayState() const
465 {
466 #if ENABLE(VIDEO)
467     if (HTMLMediaElement* mediaElt = mediaElement())
468         mediaElt->togglePlayState();
469 #endif
470 }
471
472 bool HitTestResult::mediaHasAudio() const
473 {
474 #if ENABLE(VIDEO)
475     if (HTMLMediaElement* mediaElt = mediaElement())
476         return mediaElt->hasAudio();
477 #endif
478     return false;
479 }
480
481 bool HitTestResult::mediaIsVideo() const
482 {
483 #if ENABLE(VIDEO)
484     if (HTMLMediaElement* mediaElt = mediaElement())
485         return mediaElt->hasTagName(HTMLNames::videoTag);
486 #endif
487     return false;
488 }
489
490 bool HitTestResult::mediaMuted() const
491 {
492 #if ENABLE(VIDEO)
493     if (HTMLMediaElement* mediaElt = mediaElement())
494         return mediaElt->muted();
495 #endif
496     return false;
497 }
498
499 void HitTestResult::toggleMediaMuteState() const
500 {
501 #if ENABLE(VIDEO)
502     if (HTMLMediaElement* mediaElt = mediaElement())
503         mediaElt->setMuted(!mediaElt->muted());
504 #endif
505 }
506
507 KURL HitTestResult::absoluteLinkURL() const
508 {
509     if (!(m_innerURLElement && m_innerURLElement->document()))
510         return KURL();
511
512     AtomicString urlString;
513     if (m_innerURLElement->hasTagName(aTag) || m_innerURLElement->hasTagName(areaTag) || m_innerURLElement->hasTagName(linkTag))
514         urlString = m_innerURLElement->getAttribute(hrefAttr);
515 #if ENABLE(SVG)
516     else if (m_innerURLElement->hasTagName(SVGNames::aTag))
517         urlString = m_innerURLElement->getAttribute(XLinkNames::hrefAttr);
518 #endif
519     else
520         return KURL();
521
522     return m_innerURLElement->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
523 }
524
525 bool HitTestResult::isLiveLink() const
526 {
527     if (!(m_innerURLElement && m_innerURLElement->document()))
528         return false;
529
530     if (m_innerURLElement->hasTagName(aTag))
531         return static_cast<HTMLAnchorElement*>(m_innerURLElement.get())->isLiveLink();
532 #if ENABLE(SVG)
533     if (m_innerURLElement->hasTagName(SVGNames::aTag))
534         return m_innerURLElement->isLink();
535 #endif
536
537     return false;
538 }
539
540 bool HitTestResult::isOverLink() const
541 {
542     return m_innerURLElement && m_innerURLElement->isLink();
543 }
544
545 String HitTestResult::titleDisplayString() const
546 {
547     if (!m_innerURLElement)
548         return String();
549     
550     return displayString(m_innerURLElement->title(), m_innerURLElement.get());
551 }
552
553 String HitTestResult::textContent() const
554 {
555     if (!m_innerURLElement)
556         return String();
557     return m_innerURLElement->textContent();
558 }
559
560 // FIXME: This function needs a better name and may belong in a different class. It's not
561 // really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this
562 // function would make more sense in the ContextMenu class, except that WebElementDictionary 
563 // hooks into it. Anyway, we should architect this better. 
564 bool HitTestResult::isContentEditable() const
565 {
566     if (!m_innerNonSharedNode)
567         return false;
568
569     if (m_innerNonSharedNode->hasTagName(textareaTag))
570         return true;
571
572     if (m_innerNonSharedNode->hasTagName(inputTag))
573         return static_cast<HTMLInputElement*>(m_innerNonSharedNode.get())->isTextField();
574
575     return m_innerNonSharedNode->rendererIsEditable();
576 }
577
578 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect)
579 {
580     // If it is not a rect-based hit test, this method has to be no-op.
581     // Return false, so the hit test stops.
582     if (!isRectBasedTest())
583         return false;
584
585     // If node is null, return true so the hit test can continue.
586     if (!node)
587         return true;
588
589     if (request.disallowsShadowContent())
590         node = node->document()->ancestorInThisScope(node);
591
592     mutableRectBasedTestResult().add(node);
593
594     bool regionFilled = rect.contains(locationInContainer.boundingBox());
595     return !regionFilled;
596 }
597
598 bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect)
599 {
600     // If it is not a rect-based hit test, this method has to be no-op.
601     // Return false, so the hit test stops.
602     if (!isRectBasedTest())
603         return false;
604
605     // If node is null, return true so the hit test can continue.
606     if (!node)
607         return true;
608
609     if (request.disallowsShadowContent())
610         node = node->document()->ancestorInThisScope(node);
611
612     mutableRectBasedTestResult().add(node);
613
614     bool regionFilled = rect.contains(locationInContainer.boundingBox());
615     return !regionFilled;
616 }
617
618 void HitTestResult::append(const HitTestResult& other)
619 {
620     ASSERT(isRectBasedTest() && other.isRectBasedTest());
621
622     if (!m_innerNode && other.innerNode()) {
623         m_innerNode = other.innerNode();
624         m_innerNonSharedNode = other.innerNonSharedNode();
625         m_localPoint = other.localPoint();
626         m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame;
627         m_innerURLElement = other.URLElement();
628         m_scrollbar = other.scrollbar();
629         m_isOverWidget = other.isOverWidget();
630     }
631
632     if (other.m_rectBasedTestResult) {
633         NodeSet& set = mutableRectBasedTestResult();
634         for (NodeSet::const_iterator it = other.m_rectBasedTestResult->begin(), last = other.m_rectBasedTestResult->end(); it != last; ++it)
635             set.add(it->get());
636     }
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 Vector<String> HitTestResult::dictationAlternatives() const
654 {
655     // Return the dictation context handle if the text at this point has DictationAlternative marker, which means this text is
656     if (!m_innerNonSharedNode)
657         return Vector<String>();
658
659     DocumentMarker* marker = m_innerNonSharedNode->document()->markers()->markerContainingPoint(pointInInnerNodeFrame(), DocumentMarker::DictationAlternatives);
660     if (!marker)
661         return Vector<String>();
662
663     Frame* frame = innerNonSharedNode()->document()->frame();
664     if (!frame)
665         return Vector<String>();
666
667     return frame->editor().dictationAlternativesForMarker(marker);
668 }
669
670 Node* HitTestResult::targetNode() const
671 {
672     Node* node = innerNode();
673     if (!node)
674         return 0;
675     if (node->inDocument())
676         return node;
677
678     Element* element = node->parentElement();
679     if (element && element->inDocument())
680         return element;
681
682     return node;
683 }
684
685 Element* HitTestResult::innerElement() const
686 {
687     for (Node* node = m_innerNode.get(); node; node = node->parentNode()) {
688         if (node->isElementNode())
689             return toElement(node);
690     }
691
692     return 0;
693 }
694
695 } // namespace WebCore