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