• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2000 Simon Hausmann <hausmann@kde.org>
5  * Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
6  *           (C) 2006 Graham Dennis (graham.dennis@gmail.com)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "config.h"
25 #include "HTMLAnchorElement.h"
26 
27 #include "DNS.h"
28 #include "EventNames.h"
29 #include "Frame.h"
30 #include "HTMLImageElement.h"
31 #include "HTMLNames.h"
32 #include "KeyboardEvent.h"
33 #include "MappedAttribute.h"
34 #include "MouseEvent.h"
35 #include "Page.h"
36 #include "RenderImage.h"
37 #include "Settings.h"
38 
39 namespace WebCore {
40 
41 using namespace HTMLNames;
42 
HTMLAnchorElement(Document * document)43 HTMLAnchorElement::HTMLAnchorElement(Document* document)
44     : HTMLElement(aTag, document)
45     , m_rootEditableElementForSelectionOnMouseDown(0)
46     , m_wasShiftKeyDownOnMouseDown(false)
47 {
48 }
49 
HTMLAnchorElement(const QualifiedName & tagName,Document * document)50 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
51     : HTMLElement(tagName, document)
52     , m_rootEditableElementForSelectionOnMouseDown(0)
53     , m_wasShiftKeyDownOnMouseDown(false)
54 {
55 }
56 
supportsFocus() const57 bool HTMLAnchorElement::supportsFocus() const
58 {
59     if (isContentEditable())
60         return HTMLElement::supportsFocus();
61     return isFocusable() || (isLink() && document() && !document()->haveStylesheetsLoaded());
62 }
63 
isFocusable() const64 bool HTMLAnchorElement::isFocusable() const
65 {
66     if (isContentEditable())
67         return HTMLElement::isFocusable();
68 
69     // FIXME: Even if we are not visible, we might have a child that is visible.
70     // Dave wants to fix that some day with a "has visible content" flag or the like.
71     if (!(isLink() && renderer() && renderer()->style()->visibility() == VISIBLE))
72         return false;
73 
74     return true;
75 }
76 
isMouseFocusable() const77 bool HTMLAnchorElement::isMouseFocusable() const
78 {
79 #if PLATFORM(GTK)
80     return HTMLElement::isMouseFocusable();
81 #else
82     return false;
83 #endif
84 }
85 
isKeyboardFocusable(KeyboardEvent * event) const86 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
87 {
88     if (!isFocusable())
89         return false;
90 
91     if (!document()->frame())
92         return false;
93 
94     if (!document()->frame()->eventHandler()->tabsToLinks(event))
95         return false;
96 
97     if (!renderer() || !renderer()->isBoxModelObject())
98         return false;
99 
100     // Before calling absoluteRects, check for the common case where the renderer
101     // is non-empty, since this is a faster check and almost always returns true.
102     RenderBoxModelObject* box = toRenderBoxModelObject(renderer());
103     if (!box->borderBoundingBox().isEmpty())
104         return true;
105 
106     Vector<IntRect> rects;
107     FloatPoint absPos = renderer()->localToAbsolute();
108     renderer()->absoluteRects(rects, absPos.x(), absPos.y());
109     size_t n = rects.size();
110     for (size_t i = 0; i < n; ++i)
111         if (!rects[i].isEmpty())
112             return true;
113 
114     return false;
115 }
116 
defaultEventHandler(Event * evt)117 void HTMLAnchorElement::defaultEventHandler(Event* evt)
118 {
119     // React on clicks and on keypresses.
120     // Don't make this KEYUP_EVENT again, it makes khtml follow links it shouldn't,
121     // when pressing Enter in the combo.
122     if (isLink() && (evt->type() == eventNames().clickEvent || (evt->type() == eventNames().keydownEvent && focused()))) {
123         MouseEvent* e = 0;
124         if (evt->type() == eventNames().clickEvent && evt->isMouseEvent())
125             e = static_cast<MouseEvent*>(evt);
126 
127         KeyboardEvent* k = 0;
128         if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent())
129             k = static_cast<KeyboardEvent*>(evt);
130 
131         if (e && e->button() == RightButton) {
132             HTMLElement::defaultEventHandler(evt);
133             return;
134         }
135 
136         // If the link is editable, then we need to check the settings to see whether or not to follow the link
137         if (isContentEditable()) {
138             EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
139             if (Settings* settings = document()->settings())
140                 editableLinkBehavior = settings->editableLinkBehavior();
141 
142             switch (editableLinkBehavior) {
143                 // Always follow the link (Safari 2.0 behavior)
144                 default:
145                 case EditableLinkDefaultBehavior:
146                 case EditableLinkAlwaysLive:
147                     break;
148 
149                 case EditableLinkNeverLive:
150                     HTMLElement::defaultEventHandler(evt);
151                     return;
152 
153                 // If the selection prior to clicking on this link resided in the same editable block as this link,
154                 // and the shift key isn't pressed, we don't want to follow the link
155                 case EditableLinkLiveWhenNotFocused:
156                     if (e && !e->shiftKey() && m_rootEditableElementForSelectionOnMouseDown == rootEditableElement()) {
157                         HTMLElement::defaultEventHandler(evt);
158                         return;
159                     }
160                     break;
161 
162                 // Only follow the link if the shift key is down (WinIE/Firefox behavior)
163                 case EditableLinkOnlyLiveWithShiftKey:
164                     if (e && !e->shiftKey()) {
165                         HTMLElement::defaultEventHandler(evt);
166                         return;
167                     }
168                     break;
169             }
170         }
171 
172         if (k) {
173             if (k->keyIdentifier() != "Enter") {
174                 HTMLElement::defaultEventHandler(evt);
175                 return;
176             }
177             evt->setDefaultHandled();
178             dispatchSimulatedClick(evt);
179             return;
180         }
181 
182         String url = deprecatedParseURL(getAttribute(hrefAttr));
183 
184         ASSERT(evt->target());
185         ASSERT(evt->target()->toNode());
186         if (evt->target()->toNode()->hasTagName(imgTag)) {
187             HTMLImageElement* img = static_cast<HTMLImageElement*>(evt->target()->toNode());
188             if (img && img->isServerMap()) {
189                 RenderImage* r = toRenderImage(img->renderer());
190                 if (r && e) {
191                     // FIXME: broken with transforms
192                     FloatPoint absPos = r->localToAbsolute();
193                     int x = e->pageX() - absPos.x();
194                     int y = e->pageY() - absPos.y();
195                     url += "?";
196                     url += String::number(x);
197                     url += ",";
198                     url += String::number(y);
199                 } else {
200                     evt->setDefaultHandled();
201                     HTMLElement::defaultEventHandler(evt);
202                     return;
203                 }
204             }
205         }
206 
207         if (!evt->defaultPrevented() && document()->frame())
208             document()->frame()->loader()->urlSelected(document()->completeURL(url), getAttribute(targetAttr), evt, false, false, true);
209 
210         evt->setDefaultHandled();
211     } else if (isLink() && isContentEditable()) {
212         // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
213         // for the LiveWhenNotFocused editable link behavior
214         if (evt->type() == eventNames().mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
215             MouseEvent* e = static_cast<MouseEvent*>(evt);
216 
217             m_rootEditableElementForSelectionOnMouseDown = document()->frame()->selection()->rootEditableElement();
218             m_wasShiftKeyDownOnMouseDown = e && e->shiftKey();
219         } else if (evt->type() == eventNames().mouseoverEvent) {
220             // These are cleared on mouseover and not mouseout because their values are needed for drag events, but these happen
221             // after mouse out events.
222             m_rootEditableElementForSelectionOnMouseDown = 0;
223             m_wasShiftKeyDownOnMouseDown = false;
224         }
225     }
226 
227     HTMLElement::defaultEventHandler(evt);
228 }
229 
setActive(bool down,bool pause)230 void HTMLAnchorElement::setActive(bool down, bool pause)
231 {
232     if (isContentEditable()) {
233         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
234         if (Settings* settings = document()->settings())
235             editableLinkBehavior = settings->editableLinkBehavior();
236 
237         switch (editableLinkBehavior) {
238             default:
239             case EditableLinkDefaultBehavior:
240             case EditableLinkAlwaysLive:
241                 break;
242 
243             case EditableLinkNeverLive:
244                 return;
245 
246             // Don't set the link to be active if the current selection is in the same editable block as
247             // this link
248             case EditableLinkLiveWhenNotFocused:
249                 if (down && document()->frame() && document()->frame()->selection() &&
250                     document()->frame()->selection()->rootEditableElement() == rootEditableElement())
251                     return;
252                 break;
253 
254             case EditableLinkOnlyLiveWithShiftKey:
255                 return;
256         }
257 
258     }
259 
260     ContainerNode::setActive(down, pause);
261 }
262 
parseMappedAttribute(MappedAttribute * attr)263 void HTMLAnchorElement::parseMappedAttribute(MappedAttribute *attr)
264 {
265     if (attr->name() == hrefAttr) {
266         bool wasLink = isLink();
267         setIsLink(!attr->isNull());
268         if (wasLink != isLink())
269             setNeedsStyleRecalc();
270         if (isLink()) {
271             String parsedURL = deprecatedParseURL(attr->value());
272             if (document()->isDNSPrefetchEnabled()) {
273                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
274                     prefetchDNS(document()->completeURL(parsedURL).host());
275             }
276             if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL)) {
277                 setIsLink(false);
278                 attr->setValue(nullAtom);
279             }
280         }
281     } else if (attr->name() == nameAttr ||
282              attr->name() == titleAttr ||
283              attr->name() == relAttr) {
284         // Do nothing.
285     } else
286         HTMLElement::parseMappedAttribute(attr);
287 }
288 
accessKeyAction(bool sendToAnyElement)289 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
290 {
291     // send the mouse button events if the caller specified sendToAnyElement
292     dispatchSimulatedClick(0, sendToAnyElement);
293 }
294 
isURLAttribute(Attribute * attr) const295 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
296 {
297     return attr->name() == hrefAttr;
298 }
299 
canStartSelection() const300 bool HTMLAnchorElement::canStartSelection() const
301 {
302     // FIXME: We probably want this same behavior in SVGAElement too
303     if (!isLink())
304         return HTMLElement::canStartSelection();
305     return isContentEditable();
306 }
307 
draggable() const308 bool HTMLAnchorElement::draggable() const
309 {
310     // Should be draggable if we have an href attribute.
311     const AtomicString& value = getAttribute(draggableAttr);
312     if (equalIgnoringCase(value, "true"))
313         return true;
314     if (equalIgnoringCase(value, "false"))
315         return false;
316     return hasAttribute(hrefAttr);
317 }
318 
href() const319 KURL HTMLAnchorElement::href() const
320 {
321     return document()->completeURL(getAttribute(hrefAttr));
322 }
323 
setHref(const AtomicString & value)324 void HTMLAnchorElement::setHref(const AtomicString& value)
325 {
326     setAttribute(hrefAttr, value);
327 }
328 
name() const329 const AtomicString& HTMLAnchorElement::name() const
330 {
331     return getAttribute(nameAttr);
332 }
333 
tabIndex() const334 short HTMLAnchorElement::tabIndex() const
335 {
336     // Skip the supportsFocus check in HTMLElement.
337     return Element::tabIndex();
338 }
339 
target() const340 String HTMLAnchorElement::target() const
341 {
342     return getAttribute(targetAttr);
343 }
344 
hash() const345 String HTMLAnchorElement::hash() const
346 {
347     String fragmentIdentifier = href().fragmentIdentifier();
348     return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
349 }
350 
host() const351 String HTMLAnchorElement::host() const
352 {
353     return href().host();
354 }
355 
hostname() const356 String HTMLAnchorElement::hostname() const
357 {
358     const KURL& url = href();
359     if (url.port() == 0)
360         return url.host();
361     return url.host() + ":" + String::number(url.port());
362 }
363 
pathname() const364 String HTMLAnchorElement::pathname() const
365 {
366     return href().path();
367 }
368 
port() const369 String HTMLAnchorElement::port() const
370 {
371     return String::number(href().port());
372 }
373 
protocol() const374 String HTMLAnchorElement::protocol() const
375 {
376     return href().protocol() + ":";
377 }
378 
search() const379 String HTMLAnchorElement::search() const
380 {
381     String query = href().query();
382     return query.isEmpty() ? "" : "?" + query;
383 }
384 
text() const385 String HTMLAnchorElement::text() const
386 {
387     return innerText();
388 }
389 
toString() const390 String HTMLAnchorElement::toString() const
391 {
392     return href().string();
393 }
394 
isLiveLink() const395 bool HTMLAnchorElement::isLiveLink() const
396 {
397     if (!isLink())
398         return false;
399     if (!isContentEditable())
400         return true;
401 
402     EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
403     if (Settings* settings = document()->settings())
404         editableLinkBehavior = settings->editableLinkBehavior();
405 
406     switch (editableLinkBehavior) {
407         default:
408         case EditableLinkDefaultBehavior:
409         case EditableLinkAlwaysLive:
410             return true;
411 
412         case EditableLinkNeverLive:
413             return false;
414 
415         // Don't set the link to be live if the current selection is in the same editable block as
416         // this link or if the shift key is down
417         case EditableLinkLiveWhenNotFocused:
418             return m_wasShiftKeyDownOnMouseDown || m_rootEditableElementForSelectionOnMouseDown != rootEditableElement();
419 
420         case EditableLinkOnlyLiveWithShiftKey:
421             return m_wasShiftKeyDownOnMouseDown;
422     }
423 }
424 
425 }
426