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 "File.h"
29#include "Frame.h"
30#include "FrameSelection.h"
31#include "FrameTree.h"
32#include "HTMLAnchorElement.h"
33#include "HTMLAttachmentElement.h"
34#include "HTMLEmbedElement.h"
35#include "HTMLImageElement.h"
36#include "HTMLInputElement.h"
37#include "HTMLMediaElement.h"
38#include "HTMLNames.h"
39#include "HTMLObjectElement.h"
40#include "HTMLParserIdioms.h"
41#include "HTMLPlugInImageElement.h"
42#include "HTMLTextAreaElement.h"
43#include "HTMLVideoElement.h"
44#include "HitTestLocation.h"
45#include "PseudoElement.h"
46#include "RenderBlockFlow.h"
47#include "RenderImage.h"
48#include "RenderInline.h"
49#include "SVGAElement.h"
50#include "SVGImageElement.h"
51#include "Scrollbar.h"
52#include "ShadowRoot.h"
53#include "TextIterator.h"
54#include "UserGestureIndicator.h"
55#include "VisibleUnits.h"
56#include "XLinkNames.h"
57
58namespace WebCore {
59
60using namespace HTMLNames;
61
62HitTestResult::HitTestResult()
63 : m_isOverWidget(false)
64{
65}
66
67HitTestResult::HitTestResult(const LayoutPoint& point)
68 : m_hitTestLocation(point)
69 , m_pointInInnerNodeFrame(point)
70 , m_isOverWidget(false)
71{
72}
73
74HitTestResult::HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
75 : m_hitTestLocation(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding)
76 , m_pointInInnerNodeFrame(centerPoint)
77 , m_isOverWidget(false)
78{
79}
80
81HitTestResult::HitTestResult(const HitTestLocation& other)
82 : m_hitTestLocation(other)
83 , m_pointInInnerNodeFrame(m_hitTestLocation.point())
84 , m_isOverWidget(false)
85{
86}
87
88HitTestResult::HitTestResult(const HitTestResult& other)
89 : m_hitTestLocation(other.m_hitTestLocation)
90 , m_innerNode(other.innerNode())
91 , m_innerNonSharedNode(other.innerNonSharedNode())
92 , m_pointInInnerNodeFrame(other.m_pointInInnerNodeFrame)
93 , m_localPoint(other.localPoint())
94 , m_innerURLElement(other.URLElement())
95 , m_scrollbar(other.scrollbar())
96 , m_isOverWidget(other.isOverWidget())
97{
98 // Only copy the NodeSet in case of list hit test.
99 m_listBasedTestResult = other.m_listBasedTestResult ? std::make_unique<NodeSet>(*other.m_listBasedTestResult) : nullptr;
100}
101
102HitTestResult::~HitTestResult() = default;
103
104HitTestResult& HitTestResult::operator=(const HitTestResult& other)
105{
106 m_hitTestLocation = other.m_hitTestLocation;
107 m_innerNode = other.innerNode();
108 m_innerNonSharedNode = other.innerNonSharedNode();
109 m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame;
110 m_localPoint = other.localPoint();
111 m_innerURLElement = other.URLElement();
112 m_scrollbar = other.scrollbar();
113 m_isOverWidget = other.isOverWidget();
114
115 // Only copy the NodeSet in case of list hit test.
116 m_listBasedTestResult = other.m_listBasedTestResult ? std::make_unique<NodeSet>(*other.m_listBasedTestResult) : nullptr;
117
118 return *this;
119}
120
121static Node* moveOutOfUserAgentShadowTree(Node& node)
122{
123 if (node.isInShadowTree()) {
124 if (ShadowRoot* root = node.containingShadowRoot()) {
125 if (root->mode() == ShadowRootMode::UserAgent)
126 return root->host();
127 }
128 }
129 return &node;
130}
131
132void HitTestResult::setToNonUserAgentShadowAncestor()
133{
134 if (Node* node = innerNode()) {
135 node = moveOutOfUserAgentShadowTree(*node);
136 setInnerNode(node);
137 }
138 if (Node *node = innerNonSharedNode()) {
139 node = moveOutOfUserAgentShadowTree(*node);
140 setInnerNonSharedNode(node);
141 }
142}
143
144void HitTestResult::setInnerNode(Node* node)
145{
146 if (is<PseudoElement>(node))
147 node = downcast<PseudoElement>(*node).hostElement();
148 m_innerNode = node;
149}
150
151void HitTestResult::setInnerNonSharedNode(Node* node)
152{
153 if (is<PseudoElement>(node))
154 node = downcast<PseudoElement>(*node).hostElement();
155 m_innerNonSharedNode = node;
156}
157
158void HitTestResult::setURLElement(Element* n)
159{
160 m_innerURLElement = n;
161}
162
163void HitTestResult::setScrollbar(Scrollbar* s)
164{
165 m_scrollbar = s;
166}
167
168Frame* HitTestResult::innerNodeFrame() const
169{
170 if (m_innerNonSharedNode)
171 return m_innerNonSharedNode->document().frame();
172 if (m_innerNode)
173 return m_innerNode->document().frame();
174 return 0;
175}
176
177Frame* HitTestResult::targetFrame() const
178{
179 if (!m_innerURLElement)
180 return nullptr;
181
182 Frame* frame = m_innerURLElement->document().frame();
183 if (!frame)
184 return nullptr;
185
186 return frame->tree().find(m_innerURLElement->target(), *frame);
187}
188
189bool HitTestResult::isSelected() const
190{
191 if (!m_innerNonSharedNode)
192 return false;
193
194 Frame* frame = m_innerNonSharedNode->document().frame();
195 if (!frame)
196 return false;
197
198 return frame->selection().contains(m_hitTestLocation.point());
199}
200
201String HitTestResult::selectedText() const
202{
203 if (!m_innerNonSharedNode)
204 return emptyString();
205
206 Frame* frame = m_innerNonSharedNode->document().frame();
207 if (!frame)
208 return emptyString();
209
210 // Look for a character that's not just a separator.
211 for (TextIterator it(frame->selection().toNormalizedRange().get()); !it.atEnd(); it.advance()) {
212 int length = it.text().length();
213 for (int i = 0; i < length; ++i) {
214 if (!(U_GET_GC_MASK(it.text()[i]) & U_GC_Z_MASK))
215 return frame->displayStringModifiedByEncoding(frame->editor().selectedText());
216 }
217 }
218 return emptyString();
219}
220
221String HitTestResult::spellingToolTip(TextDirection& dir) const
222{
223 dir = TextDirection::LTR;
224 // Return the tool tip string associated with this point, if any. Only markers associated with bad grammar
225 // currently supply strings, but maybe someday markers associated with misspelled words will also.
226 if (!m_innerNonSharedNode)
227 return String();
228
229 DocumentMarker* marker = m_innerNonSharedNode->document().markers().markerContainingPoint(m_hitTestLocation.point(), DocumentMarker::Grammar);
230 if (!marker)
231 return String();
232
233 if (auto renderer = m_innerNonSharedNode->renderer())
234 dir = renderer->style().direction();
235 return marker->description();
236}
237
238String HitTestResult::replacedString() const
239{
240 // Return the replaced string associated with this point, if any. This marker is created when a string is autocorrected,
241 // and is used for generating a contextual menu item that allows it to easily be changed back if desired.
242 if (!m_innerNonSharedNode)
243 return String();
244
245 DocumentMarker* marker = m_innerNonSharedNode->document().markers().markerContainingPoint(m_hitTestLocation.point(), DocumentMarker::Replacement);
246 if (!marker)
247 return String();
248
249 return marker->description();
250}
251
252String HitTestResult::title(TextDirection& dir) const
253{
254 dir = TextDirection::LTR;
255 // Find the title in the nearest enclosing DOM node.
256 // For <area> tags in image maps, walk the tree for the <area>, not the <img> using it.
257 for (Node* titleNode = m_innerNode.get(); titleNode; titleNode = titleNode->parentInComposedTree()) {
258 if (is<Element>(*titleNode)) {
259 Element& titleElement = downcast<Element>(*titleNode);
260 String title = titleElement.title();
261 if (!title.isEmpty()) {
262 if (auto renderer = titleElement.renderer())
263 dir = renderer->style().direction();
264 return title;
265 }
266 }
267 }
268 return String();
269}
270
271String HitTestResult::innerTextIfTruncated(TextDirection& dir) const
272{
273 for (Node* truncatedNode = m_innerNode.get(); truncatedNode; truncatedNode = truncatedNode->parentInComposedTree()) {
274 if (!is<Element>(*truncatedNode))
275 continue;
276
277 if (auto renderer = downcast<Element>(*truncatedNode).renderer()) {
278 if (is<RenderBlockFlow>(*renderer)) {
279 RenderBlockFlow& block = downcast<RenderBlockFlow>(*renderer);
280 if (block.style().textOverflow() == TextOverflow::Ellipsis) {
281 for (RootInlineBox* line = block.firstRootBox(); line; line = line->nextRootBox()) {
282 if (line->hasEllipsisBox()) {
283 dir = block.style().direction();
284 return downcast<Element>(*truncatedNode).innerText();
285 }
286 }
287 }
288 break;
289 }
290 }
291 }
292
293 dir = TextDirection::LTR;
294 return String();
295}
296
297String displayString(const String& string, const Node* node)
298{
299 if (!node)
300 return string;
301 return node->document().displayStringModifiedByEncoding(string);
302}
303
304String HitTestResult::altDisplayString() const
305{
306 if (!m_innerNonSharedNode)
307 return String();
308
309 if (is<HTMLImageElement>(*m_innerNonSharedNode)) {
310 HTMLImageElement& image = downcast<HTMLImageElement>(*m_innerNonSharedNode);
311 return displayString(image.attributeWithoutSynchronization(altAttr), m_innerNonSharedNode.get());
312 }
313
314 if (is<HTMLInputElement>(*m_innerNonSharedNode)) {
315 HTMLInputElement& input = downcast<HTMLInputElement>(*m_innerNonSharedNode);
316 return displayString(input.alt(), m_innerNonSharedNode.get());
317 }
318
319 return String();
320}
321
322Image* HitTestResult::image() const
323{
324 if (!m_innerNonSharedNode)
325 return nullptr;
326
327 auto* renderer = m_innerNonSharedNode->renderer();
328 if (is<RenderImage>(renderer)) {
329 auto& image = downcast<RenderImage>(*renderer);
330 if (image.cachedImage() && !image.cachedImage()->errorOccurred())
331 return image.cachedImage()->imageForRenderer(&image);
332 }
333
334 return nullptr;
335}
336
337IntRect HitTestResult::imageRect() const
338{
339 if (!image())
340 return IntRect();
341 return m_innerNonSharedNode->renderBox()->absoluteContentQuad().enclosingBoundingBox();
342}
343
344URL HitTestResult::absoluteImageURL() const
345{
346 if (!m_innerNonSharedNode)
347 return URL();
348
349 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isImage()))
350 return URL();
351
352 AtomicString urlString;
353 if (is<HTMLEmbedElement>(*m_innerNonSharedNode)
354 || is<HTMLImageElement>(*m_innerNonSharedNode)
355 || is<HTMLInputElement>(*m_innerNonSharedNode)
356 || is<HTMLObjectElement>(*m_innerNonSharedNode)
357 || is<SVGImageElement>(*m_innerNonSharedNode)) {
358 urlString = downcast<Element>(*m_innerNonSharedNode).imageSourceURL();
359 } else
360 return URL();
361
362 return m_innerNonSharedNode->document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
363}
364
365URL HitTestResult::absolutePDFURL() const
366{
367 if (!m_innerNonSharedNode)
368 return URL();
369
370 if (!is<HTMLEmbedElement>(*m_innerNonSharedNode) && !is<HTMLObjectElement>(*m_innerNonSharedNode))
371 return URL();
372
373 HTMLPlugInImageElement& element = downcast<HTMLPlugInImageElement>(*m_innerNonSharedNode);
374 URL url = m_innerNonSharedNode->document().completeURL(stripLeadingAndTrailingHTMLSpaces(element.url()));
375 if (!url.isValid())
376 return URL();
377
378 if (element.serviceType() == "application/pdf" || (element.serviceType().isEmpty() && url.path().endsWithIgnoringASCIICase(".pdf")))
379 return url;
380 return URL();
381}
382
383URL HitTestResult::absoluteMediaURL() const
384{
385#if ENABLE(VIDEO)
386 if (HTMLMediaElement* mediaElt = mediaElement())
387 return mediaElt->currentSrc();
388 return URL();
389#else
390 return URL();
391#endif
392}
393
394bool HitTestResult::mediaSupportsFullscreen() const
395{
396#if ENABLE(VIDEO)
397 HTMLMediaElement* mediaElt(mediaElement());
398 return is<HTMLVideoElement>(mediaElt) && mediaElt->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard);
399#else
400 return false;
401#endif
402}
403
404#if ENABLE(VIDEO)
405HTMLMediaElement* HitTestResult::mediaElement() const
406{
407 if (!m_innerNonSharedNode)
408 return nullptr;
409
410 if (!(m_innerNonSharedNode->renderer() && m_innerNonSharedNode->renderer()->isMedia()))
411 return nullptr;
412
413 if (is<HTMLMediaElement>(*m_innerNonSharedNode))
414 return downcast<HTMLMediaElement>(m_innerNonSharedNode.get());
415 return nullptr;
416}
417#endif
418
419void HitTestResult::toggleMediaControlsDisplay() const
420{
421#if ENABLE(VIDEO)
422 if (HTMLMediaElement* mediaElt = mediaElement())
423 mediaElt->setControls(!mediaElt->controls());
424#endif
425}
426
427void HitTestResult::toggleMediaLoopPlayback() const
428{
429#if ENABLE(VIDEO)
430 if (HTMLMediaElement* mediaElt = mediaElement())
431 mediaElt->setLoop(!mediaElt->loop());
432#endif
433}
434
435bool HitTestResult::mediaIsInFullscreen() const
436{
437#if ENABLE(VIDEO)
438 if (HTMLMediaElement* mediaElement = this->mediaElement())
439 return mediaElement->isVideo() && mediaElement->isStandardFullscreen();
440#endif
441 return false;
442}
443
444void HitTestResult::toggleMediaFullscreenState() const
445{
446#if ENABLE(VIDEO)
447 if (HTMLMediaElement* mediaElement = this->mediaElement()) {
448 if (mediaElement->isVideo() && mediaElement->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard)) {
449 UserGestureIndicator indicator(ProcessingUserGesture, &mediaElement->document());
450 mediaElement->toggleStandardFullscreenState();
451 }
452 }
453#endif
454}
455
456void HitTestResult::enterFullscreenForVideo() const
457{
458#if ENABLE(VIDEO)
459 HTMLMediaElement* mediaElement(this->mediaElement());
460 if (is<HTMLVideoElement>(mediaElement)) {
461 HTMLVideoElement& videoElement = downcast<HTMLVideoElement>(*mediaElement);
462 if (!videoElement.isFullscreen() && mediaElement->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard)) {
463 UserGestureIndicator indicator(ProcessingUserGesture, &mediaElement->document());
464 videoElement.enterFullscreen();
465 }
466 }
467#endif
468}
469
470bool HitTestResult::mediaControlsEnabled() const
471{
472#if ENABLE(VIDEO)
473 if (HTMLMediaElement* mediaElement = this->mediaElement())
474 return mediaElement->controls();
475#endif
476 return false;
477}
478
479bool HitTestResult::mediaLoopEnabled() const
480{
481#if ENABLE(VIDEO)
482 if (HTMLMediaElement* mediaElt = mediaElement())
483 return mediaElt->loop();
484#endif
485 return false;
486}
487
488bool HitTestResult::mediaPlaying() const
489{
490#if ENABLE(VIDEO)
491 if (HTMLMediaElement* mediaElt = mediaElement())
492 return !mediaElt->paused();
493#endif
494 return false;
495}
496
497void HitTestResult::toggleMediaPlayState() const
498{
499#if ENABLE(VIDEO)
500 if (HTMLMediaElement* mediaElt = mediaElement())
501 mediaElt->togglePlayState();
502#endif
503}
504
505bool HitTestResult::mediaHasAudio() const
506{
507#if ENABLE(VIDEO)
508 if (HTMLMediaElement* mediaElt = mediaElement())
509 return mediaElt->hasAudio();
510#endif
511 return false;
512}
513
514bool HitTestResult::mediaIsVideo() const
515{
516#if ENABLE(VIDEO)
517 if (HTMLMediaElement* mediaElt = mediaElement())
518 return is<HTMLVideoElement>(*mediaElt);
519#endif
520 return false;
521}
522
523bool HitTestResult::mediaMuted() const
524{
525#if ENABLE(VIDEO)
526 if (HTMLMediaElement* mediaElt = mediaElement())
527 return mediaElt->muted();
528#endif
529 return false;
530}
531
532void HitTestResult::toggleMediaMuteState() const
533{
534#if ENABLE(VIDEO)
535 if (HTMLMediaElement* mediaElt = mediaElement())
536 mediaElt->setMuted(!mediaElt->muted());
537#endif
538}
539
540bool HitTestResult::isDownloadableMedia() const
541{
542#if ENABLE(VIDEO)
543 if (HTMLMediaElement* mediaElt = mediaElement())
544 return mediaElt->canSaveMediaData();
545#endif
546
547 return false;
548}
549
550bool HitTestResult::isOverTextInsideFormControlElement() const
551{
552 Node* node = innerNode();
553 if (!node)
554 return false;
555
556 if (!is<Element>(*node) || !downcast<Element>(*node).isTextField())
557 return false;
558
559 Frame* frame = node->document().frame();
560 if (!frame)
561 return false;
562
563 IntPoint framePoint = roundedPointInInnerNodeFrame();
564 if (!frame->rangeForPoint(framePoint))
565 return false;
566
567 VisiblePosition position = frame->visiblePositionForPoint(framePoint);
568 if (position.isNull())
569 return false;
570
571 RefPtr<Range> wordRange = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionForward);
572 if (!wordRange)
573 return false;
574
575 return !wordRange->text().isEmpty();
576}
577
578URL HitTestResult::absoluteLinkURL() const
579{
580 if (m_innerURLElement)
581 return m_innerURLElement->absoluteLinkURL();
582 return URL();
583}
584
585bool HitTestResult::isOverLink() const
586{
587 return m_innerURLElement && m_innerURLElement->isLink();
588}
589
590String HitTestResult::titleDisplayString() const
591{
592 if (!m_innerURLElement)
593 return String();
594
595 return displayString(m_innerURLElement->title(), m_innerURLElement.get());
596}
597
598String HitTestResult::textContent() const
599{
600 if (!m_innerURLElement)
601 return String();
602 return m_innerURLElement->textContent();
603}
604
605// FIXME: This function needs a better name and may belong in a different class. It's not
606// really isContentEditable(); it's more like needsEditingContextMenu(). In many ways, this
607// function would make more sense in the ContextMenu class, except that WebElementDictionary
608// hooks into it. Anyway, we should architect this better.
609bool HitTestResult::isContentEditable() const
610{
611 if (!m_innerNonSharedNode)
612 return false;
613
614 if (is<HTMLTextAreaElement>(*m_innerNonSharedNode))
615 return true;
616
617 if (is<HTMLInputElement>(*m_innerNonSharedNode))
618 return downcast<HTMLInputElement>(*m_innerNonSharedNode).isTextField();
619
620 return m_innerNonSharedNode->hasEditableStyle();
621}
622
623HitTestProgress HitTestResult::addNodeToListBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect)
624{
625 // If it is not a list-based hit test, this method has to be no-op.
626 if (!request.resultIsElementList()) {
627 ASSERT(!isRectBasedTest());
628 return HitTestProgress::Stop;
629 }
630
631 if (!node)
632 return HitTestProgress::Continue;
633
634 if (request.disallowsUserAgentShadowContent() && node->isInUserAgentShadowTree())
635 node = node->document().ancestorNodeInThisScope(node);
636
637 mutableListBasedTestResult().add(node);
638
639 if (request.includesAllElementsUnderPoint())
640 return HitTestProgress::Continue;
641
642 bool regionFilled = rect.contains(locationInContainer.boundingBox());
643 return regionFilled ? HitTestProgress::Stop : HitTestProgress::Continue;
644}
645
646HitTestProgress HitTestResult::addNodeToListBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect)
647{
648 // If it is not a list-based hit test, this method has to be no-op.
649 if (!request.resultIsElementList()) {
650 ASSERT(!isRectBasedTest());
651 return HitTestProgress::Stop;
652 }
653
654 if (!node)
655 return HitTestProgress::Continue;
656
657 if (request.disallowsUserAgentShadowContent() && node->isInUserAgentShadowTree())
658 node = node->document().ancestorNodeInThisScope(node);
659
660 mutableListBasedTestResult().add(node);
661
662 if (request.includesAllElementsUnderPoint())
663 return HitTestProgress::Continue;
664
665 bool regionFilled = rect.contains(locationInContainer.boundingBox());
666 return regionFilled ? HitTestProgress::Stop : HitTestProgress::Continue;
667}
668
669void HitTestResult::append(const HitTestResult& other, const HitTestRequest& request)
670{
671 ASSERT_UNUSED(request, request.resultIsElementList());
672
673 if (!m_innerNode && other.innerNode()) {
674 m_innerNode = other.innerNode();
675 m_innerNonSharedNode = other.innerNonSharedNode();
676 m_localPoint = other.localPoint();
677 m_pointInInnerNodeFrame = other.m_pointInInnerNodeFrame;
678 m_innerURLElement = other.URLElement();
679 m_scrollbar = other.scrollbar();
680 m_isOverWidget = other.isOverWidget();
681 }
682
683 if (other.m_listBasedTestResult) {
684 NodeSet& set = mutableListBasedTestResult();
685 for (const auto& node : *other.m_listBasedTestResult)
686 set.add(node.get());
687 }
688}
689
690const HitTestResult::NodeSet& HitTestResult::listBasedTestResult() const
691{
692 if (!m_listBasedTestResult)
693 m_listBasedTestResult = std::make_unique<NodeSet>();
694 return *m_listBasedTestResult;
695}
696
697HitTestResult::NodeSet& HitTestResult::mutableListBasedTestResult()
698{
699 if (!m_listBasedTestResult)
700 m_listBasedTestResult = std::make_unique<NodeSet>();
701 return *m_listBasedTestResult;
702}
703
704Vector<String> HitTestResult::dictationAlternatives() const
705{
706 // Return the dictation context handle if the text at this point has DictationAlternative marker, which means this text is
707 if (!m_innerNonSharedNode)
708 return Vector<String>();
709
710 DocumentMarker* marker = m_innerNonSharedNode->document().markers().markerContainingPoint(pointInInnerNodeFrame(), DocumentMarker::DictationAlternatives);
711 if (!marker)
712 return Vector<String>();
713
714 Frame* frame = innerNonSharedNode()->document().frame();
715 if (!frame)
716 return Vector<String>();
717
718 return frame->editor().dictationAlternativesForMarker(*marker);
719}
720
721Node* HitTestResult::targetNode() const
722{
723 Node* node = innerNode();
724 if (!node)
725 return 0;
726 if (node->isConnected())
727 return node;
728
729 Element* element = node->parentElement();
730 if (element && element->isConnected())
731 return element;
732
733 return node;
734}
735
736Element* HitTestResult::targetElement() const
737{
738 for (Node* node = m_innerNode.get(); node; node = node->parentInComposedTree()) {
739 if (is<Element>(*node))
740 return downcast<Element>(node);
741 }
742 return nullptr;
743}
744
745Element* HitTestResult::innerNonSharedElement() const
746{
747 Node* node = m_innerNonSharedNode.get();
748 if (!node)
749 return nullptr;
750 if (is<Element>(*node))
751 return downcast<Element>(node);
752 return node->parentElement();
753}
754
755String HitTestResult::linkSuggestedFilename() const
756{
757 auto* urlElement = URLElement();
758 if (!is<HTMLAnchorElement>(urlElement))
759 return nullAtom();
760 return ResourceResponse::sanitizeSuggestedFilename(urlElement->attributeWithoutSynchronization(HTMLNames::downloadAttr));
761}
762
763bool HitTestResult::mediaSupportsEnhancedFullscreen() const
764{
765#if PLATFORM(MAC) && ENABLE(VIDEO) && ENABLE(VIDEO_PRESENTATION_MODE)
766 HTMLMediaElement* mediaElt(mediaElement());
767 return is<HTMLVideoElement>(mediaElt) && mediaElt->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture);
768#else
769 return false;
770#endif
771}
772
773bool HitTestResult::mediaIsInEnhancedFullscreen() const
774{
775#if PLATFORM(MAC) && ENABLE(VIDEO) && ENABLE(VIDEO_PRESENTATION_MODE)
776 HTMLMediaElement* mediaElt(mediaElement());
777 return is<HTMLVideoElement>(mediaElt) && mediaElt->fullscreenMode() == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture;
778#else
779 return false;
780#endif
781}
782
783void HitTestResult::toggleEnhancedFullscreenForVideo() const
784{
785#if PLATFORM(MAC) && ENABLE(VIDEO) && ENABLE(VIDEO_PRESENTATION_MODE)
786 HTMLMediaElement* mediaElement(this->mediaElement());
787 if (!is<HTMLVideoElement>(mediaElement) || !mediaElement->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture))
788 return;
789
790 HTMLVideoElement& videoElement = downcast<HTMLVideoElement>(*mediaElement);
791 UserGestureIndicator indicator(ProcessingUserGesture, &mediaElement->document());
792 if (videoElement.fullscreenMode() == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)
793 videoElement.exitFullscreen();
794 else
795 videoElement.enterFullscreen(HTMLMediaElementEnums::VideoFullscreenModePictureInPicture);
796#endif
797}
798
799} // namespace WebCore
800