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