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