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