Reviewed by Darin Adler.
[WebKit-https.git] / WebCore / html / HTMLAnchorElement.cpp
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 "FrameLoaderTypes.h"
31 #include "HTMLImageElement.h"
32 #include "HTMLNames.h"
33 #include "KeyboardEvent.h"
34 #include "MappedAttribute.h"
35 #include "MouseEvent.h"
36 #include "Page.h"
37 #include "RenderImage.h"
38 #include "Settings.h"
39
40 namespace WebCore {
41
42 using namespace HTMLNames;
43
44 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
45     : HTMLElement(tagName, document, CreateElement)
46     , m_wasShiftKeyDownOnMouseDown(false)
47     , m_linkRelations(0)
48 {
49 }
50
51 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
52 {
53     return adoptRef(new HTMLAnchorElement(aTag, document));
54 }
55
56 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
57 {
58     return adoptRef(new HTMLAnchorElement(tagName, document));
59 }
60
61 // This function does not allow leading spaces before the port number.
62 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
63 {
64     portEnd = portStart;
65     while (isASCIIDigit(value[portEnd]))
66         ++portEnd;
67     return value.substring(portStart, portEnd - portStart).toUInt();
68 }
69
70 bool HTMLAnchorElement::supportsFocus() const
71 {
72     if (isContentEditable())
73         return HTMLElement::supportsFocus();
74     // If not a link we should still be able to focus the element if it has tabIndex.
75     return isLink() || HTMLElement::supportsFocus();
76 }
77
78 bool HTMLAnchorElement::isMouseFocusable() const
79 {
80     // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
81 #if !PLATFORM(GTK) && !PLATFORM(QT)
82     if (isLink())
83         // Only allow links with tabIndex or contentEditable to be mouse focusable.
84         return HTMLElement::supportsFocus();
85 #endif
86
87     // Allow tab index etc to control focus.
88     return HTMLElement::isMouseFocusable();
89 }
90
91 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
92 {
93     if (!isLink())
94         return HTMLElement::isKeyboardFocusable(event);
95
96     if (!isFocusable())
97         return false;
98     
99     if (!document()->frame())
100         return false;
101
102     if (!document()->frame()->eventHandler()->tabsToLinks(event))
103         return false;
104
105     if (!renderer() || !renderer()->isBoxModelObject())
106         return false;
107     
108     // Before calling absoluteRects, check for the common case where the renderer
109     // is non-empty, since this is a faster check and almost always returns true.
110     RenderBoxModelObject* box = toRenderBoxModelObject(renderer());
111     if (!box->borderBoundingBox().isEmpty())
112         return true;
113
114     Vector<IntRect> rects;
115     FloatPoint absPos = renderer()->localToAbsolute();
116     renderer()->absoluteRects(rects, absPos.x(), absPos.y());
117     size_t n = rects.size();
118     for (size_t i = 0; i < n; ++i)
119         if (!rects[i].isEmpty())
120             return true;
121
122     return false;
123 }
124
125 void HTMLAnchorElement::defaultEventHandler(Event* evt)
126 {
127     // React on clicks and on keypresses.
128     // Don't make this KEYUP_EVENT again, it makes khtml follow links it shouldn't,
129     // when pressing Enter in the combo.
130     if (isLink() && (evt->type() == eventNames().clickEvent || (evt->type() == eventNames().keydownEvent && focused()))) {
131         MouseEvent* e = 0;
132         if (evt->type() == eventNames().clickEvent && evt->isMouseEvent())
133             e = static_cast<MouseEvent*>(evt);
134
135         KeyboardEvent* k = 0;
136         if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent())
137             k = static_cast<KeyboardEvent*>(evt);
138
139         if (e && e->button() == RightButton) {
140             HTMLElement::defaultEventHandler(evt);
141             return;
142         }
143
144         // If the link is editable, then we need to check the settings to see whether or not to follow the link
145         if (isContentEditable()) {
146             EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
147             if (Settings* settings = document()->settings())
148                 editableLinkBehavior = settings->editableLinkBehavior();
149                 
150             switch (editableLinkBehavior) {
151                 // Always follow the link (Safari 2.0 behavior)
152                 default:
153                 case EditableLinkDefaultBehavior:
154                 case EditableLinkAlwaysLive:
155                     break;
156
157                 case EditableLinkNeverLive:
158                     HTMLElement::defaultEventHandler(evt);
159                     return;
160
161                 // If the selection prior to clicking on this link resided in the same editable block as this link,
162                 // and the shift key isn't pressed, we don't want to follow the link
163                 case EditableLinkLiveWhenNotFocused:
164                     if (e && !e->shiftKey() && m_rootEditableElementForSelectionOnMouseDown == rootEditableElement()) {
165                         HTMLElement::defaultEventHandler(evt);
166                         return;
167                     }
168                     break;
169
170                 // Only follow the link if the shift key is down (WinIE/Firefox behavior)
171                 case EditableLinkOnlyLiveWithShiftKey:
172                     if (e && !e->shiftKey()) {
173                         HTMLElement::defaultEventHandler(evt);
174                         return;
175                     }
176                     break;
177             }
178         }
179
180         if (k) {
181             if (k->keyIdentifier() != "Enter") {
182                 HTMLElement::defaultEventHandler(evt);
183                 return;
184             }
185             evt->setDefaultHandled();
186             dispatchSimulatedClick(evt);
187             return;
188         }
189
190         String url = deprecatedParseURL(getAttribute(hrefAttr));
191
192         ASSERT(evt->target());
193         ASSERT(evt->target()->toNode());
194         if (evt->target()->toNode()->hasTagName(imgTag)) {
195             HTMLImageElement* img = static_cast<HTMLImageElement*>(evt->target()->toNode());
196             if (img && img->isServerMap()) {
197                 RenderImage* r = toRenderImage(img->renderer());
198                 if (r && e) {
199                     // FIXME: broken with transforms
200                     FloatPoint absPos = r->localToAbsolute();
201                     int x = e->pageX() - absPos.x();
202                     int y = e->pageY() - absPos.y();
203                     url += "?";
204                     url += String::number(x);
205                     url += ",";
206                     url += String::number(y);
207                 } else {
208                     evt->setDefaultHandled();
209                     HTMLElement::defaultEventHandler(evt);
210                     return;
211                 }
212             }
213         }
214
215         if (!evt->defaultPrevented() && document()->frame())
216             document()->frame()->loader()->urlSelected(document()->completeURL(url), getAttribute(targetAttr), evt, false, false, true, hasRel(RelationNoReferrer) ? NoReferrer : SendReferrer);
217
218         evt->setDefaultHandled();
219     } else if (isLink() && isContentEditable()) {
220         // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
221         // for the LiveWhenNotFocused editable link behavior
222         if (evt->type() == eventNames().mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
223             MouseEvent* e = static_cast<MouseEvent*>(evt);
224
225             m_rootEditableElementForSelectionOnMouseDown = document()->frame()->selection()->rootEditableElement();
226             m_wasShiftKeyDownOnMouseDown = e && e->shiftKey();
227         } else if (evt->type() == eventNames().mouseoverEvent) {
228             // These are cleared on mouseover and not mouseout because their values are needed for drag events, but these happen
229             // after mouse out events.
230             m_rootEditableElementForSelectionOnMouseDown = 0;
231             m_wasShiftKeyDownOnMouseDown = false;
232         }
233     }
234
235     HTMLElement::defaultEventHandler(evt);
236 }
237
238 void HTMLAnchorElement::setActive(bool down, bool pause)
239 {
240     if (isContentEditable()) {
241         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
242         if (Settings* settings = document()->settings())
243             editableLinkBehavior = settings->editableLinkBehavior();
244             
245         switch (editableLinkBehavior) {
246             default:
247             case EditableLinkDefaultBehavior:
248             case EditableLinkAlwaysLive:
249                 break;
250
251             case EditableLinkNeverLive:
252                 return;
253
254             // Don't set the link to be active if the current selection is in the same editable block as
255             // this link
256             case EditableLinkLiveWhenNotFocused:
257                 if (down && document()->frame() && document()->frame()->selection() &&
258                     document()->frame()->selection()->rootEditableElement() == rootEditableElement())
259                     return;
260                 break;
261             
262             case EditableLinkOnlyLiveWithShiftKey:
263                 return;
264         }
265
266     }
267     
268     ContainerNode::setActive(down, pause);
269 }
270
271 void HTMLAnchorElement::parseMappedAttribute(MappedAttribute *attr)
272 {
273     if (attr->name() == hrefAttr) {
274         bool wasLink = isLink();
275         setIsLink(!attr->isNull());
276         if (wasLink != isLink())
277             setNeedsStyleRecalc();
278         if (isLink()) {
279             String parsedURL = deprecatedParseURL(attr->value());
280             if (document()->isDNSPrefetchEnabled()) {
281                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
282                     prefetchDNS(document()->completeURL(parsedURL).host());
283             }
284             if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL)) {
285                 setIsLink(false);
286                 attr->setValue(nullAtom);
287             }
288         }
289     } else if (attr->name() == nameAttr ||
290              attr->name() == titleAttr) {
291         // Do nothing.
292     } else if (attr->name() == relAttr)
293         setRel(attr->value());
294     else
295         HTMLElement::parseMappedAttribute(attr);
296 }
297
298 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
299 {
300     // send the mouse button events if the caller specified sendToAnyElement
301     dispatchSimulatedClick(0, sendToAnyElement);
302 }
303
304 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
305 {
306     return attr->name() == hrefAttr;
307 }
308
309 bool HTMLAnchorElement::canStartSelection() const
310 {
311     // FIXME: We probably want this same behavior in SVGAElement too
312     if (!isLink())
313         return HTMLElement::canStartSelection();
314     return isContentEditable();
315 }
316
317 bool HTMLAnchorElement::draggable() const
318 {
319     // Should be draggable if we have an href attribute.
320     const AtomicString& value = getAttribute(draggableAttr);
321     if (equalIgnoringCase(value, "true"))
322         return true;
323     if (equalIgnoringCase(value, "false"))
324         return false;
325     return hasAttribute(hrefAttr);
326 }
327
328 KURL HTMLAnchorElement::href() const
329 {
330     return document()->completeURL(deprecatedParseURL(getAttribute(hrefAttr)));
331 }
332
333 void HTMLAnchorElement::setHref(const AtomicString& value)
334 {
335     setAttribute(hrefAttr, value);
336 }
337
338 bool HTMLAnchorElement::hasRel(uint32_t relation) const
339 {
340     return m_linkRelations & relation;
341 }
342
343 void HTMLAnchorElement::setRel(const String& value)
344 {
345     m_linkRelations = 0;
346     ClassNames newLinkRelations(value, true);
347     // FIXME: Add link relations as they are implemented
348     if (newLinkRelations.contains("noreferrer"))
349         m_linkRelations |= RelationNoReferrer;
350 }
351
352 const AtomicString& HTMLAnchorElement::name() const
353 {
354     return getAttribute(nameAttr);
355 }
356
357 short HTMLAnchorElement::tabIndex() const
358 {
359     // Skip the supportsFocus check in HTMLElement.
360     return Element::tabIndex();
361 }
362
363 String HTMLAnchorElement::target() const
364 {
365     return getAttribute(targetAttr);
366 }
367
368 String HTMLAnchorElement::hash() const
369 {
370     String fragmentIdentifier = href().fragmentIdentifier();
371     return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
372 }
373
374 void HTMLAnchorElement::setHash(const String& value)
375 {
376     KURL url = href();
377     if (value[0] == '#')
378         url.setFragmentIdentifier(value.substring(1));
379     else
380         url.setFragmentIdentifier(value);
381     setHref(url.string());
382 }
383
384 String HTMLAnchorElement::host() const
385 {
386     const KURL& url = href();
387     if (url.hostEnd() == url.pathStart())
388         return url.host();
389     if (isDefaultPortForProtocol(url.port(), url.protocol()))
390         return url.host();
391     return url.host() + ":" + String::number(url.port());
392 }
393
394 void HTMLAnchorElement::setHost(const String& value)
395 {
396     if (value.isEmpty())
397         return;
398     KURL url = href();
399     if (!url.canSetHostOrPort())
400         return;
401
402     int separator = value.find(':');
403     if (!separator)
404         return;
405
406     if (separator == -1)
407         url.setHostAndPort(value);
408     else {
409         unsigned portEnd;
410         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
411         if (!port) {
412             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
413             // specifically goes against RFC 3986 (p3.2) and
414             // requires setting the port to "0" if it is set to empty string.
415             url.setHostAndPort(value.substring(0, separator + 1) + "0");
416         } else {
417             if (isDefaultPortForProtocol(port, url.protocol()))
418                 url.setHostAndPort(value.substring(0, separator));
419             else
420                 url.setHostAndPort(value.substring(0, portEnd));
421         }
422     }
423     setHref(url.string());
424 }
425
426 String HTMLAnchorElement::hostname() const
427 {
428     return href().host();
429 }
430
431 void HTMLAnchorElement::setHostname(const String& value)
432 {
433     // Before setting new value:
434     // Remove all leading U+002F SOLIDUS ("/") characters.
435     unsigned i = 0;
436     unsigned hostLength = value.length();
437     while (value[i] == '/')
438         i++;
439
440     if (i == hostLength)
441         return;
442
443     KURL url = href();
444     if (!url.canSetHostOrPort())
445         return;
446
447     url.setHost(value.substring(i));
448     setHref(url.string());
449 }
450
451 String HTMLAnchorElement::pathname() const
452 {
453     return href().path();
454 }
455
456 void HTMLAnchorElement::setPathname(const String& value)
457 {
458     KURL url = href();
459     if (!url.canSetPathname())
460         return;
461
462     if (value[0] == '/')
463         url.setPath(value);
464     else
465         url.setPath("/" + value);
466
467     setHref(url.string());
468 }
469
470 String HTMLAnchorElement::port() const
471 {
472     return String::number(href().port());
473 }
474
475 void HTMLAnchorElement::setPort(const String& value)
476 {
477     KURL url = href();
478     if (!url.canSetHostOrPort())
479         return;
480
481     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
482     // specifically goes against RFC 3986 (p3.2) and
483     // requires setting the port to "0" if it is set to empty string.
484     unsigned port = value.toUInt();
485     if (isDefaultPortForProtocol(port, url.protocol()))
486         url.removePort();
487     else
488         url.setPort(port);
489
490     setHref(url.string());
491 }
492
493 String HTMLAnchorElement::protocol() const
494 {
495     return href().protocol() + ":";
496 }
497
498 void HTMLAnchorElement::setProtocol(const String& value)
499 {
500     int separator = value.find(':');
501
502     if (!separator)
503         return;
504     if (value.isEmpty())
505         return;
506
507     KURL url = href();
508     // Following Firefox 3.5.2 which removes anything after the first ":"
509     String newProtocol = value.substring(0, separator);
510     if (!isValidProtocol(newProtocol))
511         return;
512     url.setProtocol(newProtocol);
513
514     setHref(url.string());
515 }
516
517 String HTMLAnchorElement::search() const
518 {
519     String query = href().query();
520     return query.isEmpty() ? "" : "?" + query;
521 }
522
523 void HTMLAnchorElement::setSearch(const String& value)
524 {
525     KURL url = href();
526     String newSearch = (value[0] == '?') ? value.substring(1) : value;
527     // Make sure that '#' in the query does not leak to the hash.
528     url.setQuery(newSearch.replace('#', "%23"));
529
530     setHref(url.string());
531 }
532
533 String HTMLAnchorElement::text() const
534 {
535     return innerText();
536 }
537
538 String HTMLAnchorElement::toString() const
539 {
540     return href().string();
541 }
542
543 bool HTMLAnchorElement::isLiveLink() const
544 {
545     if (!isLink())
546         return false;
547     if (!isContentEditable())
548         return true;
549     
550     EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
551     if (Settings* settings = document()->settings())
552         editableLinkBehavior = settings->editableLinkBehavior();
553         
554     switch (editableLinkBehavior) {
555         default:
556         case EditableLinkDefaultBehavior:
557         case EditableLinkAlwaysLive:
558             return true;
559
560         case EditableLinkNeverLive:
561             return false;
562
563         // Don't set the link to be live if the current selection is in the same editable block as
564         // this link or if the shift key is down
565         case EditableLinkLiveWhenNotFocused:
566             return m_wasShiftKeyDownOnMouseDown || m_rootEditableElementForSelectionOnMouseDown != rootEditableElement();
567             
568         case EditableLinkOnlyLiveWithShiftKey:
569             return m_wasShiftKeyDownOnMouseDown;
570     }
571 }
572
573 }