Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/html/HTMLAnchorElement.h"
26
27 #include "core/dom/Attribute.h"
28 #include "core/editing/FrameSelection.h"
29 #include "core/events/KeyboardEvent.h"
30 #include "core/events/MouseEvent.h"
31 #include "core/frame/FrameHost.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/frame/Settings.h"
34 #include "core/frame/UseCounter.h"
35 #include "core/html/HTMLFormElement.h"
36 #include "core/html/HTMLImageElement.h"
37 #include "core/html/parser/HTMLParserIdioms.h"
38 #include "core/loader/FrameLoadRequest.h"
39 #include "core/loader/FrameLoader.h"
40 #include "core/loader/FrameLoaderClient.h"
41 #include "core/loader/FrameLoaderTypes.h"
42 #include "core/loader/PingLoader.h"
43 #include "core/page/Chrome.h"
44 #include "core/page/ChromeClient.h"
45 #include "core/rendering/RenderImage.h"
46 #include "core/svg/graphics/SVGImage.h"
47 #include "platform/PlatformMouseEvent.h"
48 #include "platform/network/DNS.h"
49 #include "platform/network/ResourceRequest.h"
50 #include "platform/weborigin/KnownPorts.h"
51 #include "platform/weborigin/SecurityOrigin.h"
52 #include "platform/weborigin/SecurityPolicy.h"
53 #include "public/platform/Platform.h"
54 #include "public/platform/WebPrescientNetworking.h"
55 #include "public/platform/WebURL.h"
56 #include "wtf/text/StringBuilder.h"
57
58 namespace WebCore {
59
60 namespace {
61
62 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation)
63 {
64     blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking();
65     if (!prescientNetworking)
66         return;
67
68     prescientNetworking->preconnect(url, motivation);
69 }
70
71 }
72
73 class HTMLAnchorElement::PrefetchEventHandler {
74 public:
75     static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
76     {
77         return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
78     }
79
80     void reset();
81
82     void handleEvent(Event* e);
83     void didChangeHREF() { m_hadHREFChanged = true; }
84     bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
85
86 private:
87     explicit PrefetchEventHandler(HTMLAnchorElement*);
88
89     void handleMouseOver(Event* event);
90     void handleMouseOut(Event* event);
91     void handleLeftMouseDown(Event* event);
92     void handleGestureTapUnconfirmed(Event*);
93     void handleGestureShowPress(Event*);
94     void handleClick(Event* event);
95
96     bool shouldPrefetch(const KURL&);
97     void prefetch(blink::WebPreconnectMotivation);
98
99     HTMLAnchorElement* m_anchorElement;
100     double m_mouseOverTimestamp;
101     double m_mouseDownTimestamp;
102     double m_tapDownTimestamp;
103     bool m_hadHREFChanged;
104     bool m_hadTapUnconfirmed;
105     bool m_hasIssuedPreconnect;
106 };
107
108 using namespace HTMLNames;
109
110 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
111     : HTMLElement(tagName, document)
112     , m_linkRelations(0)
113     , m_cachedVisitedLinkHash(0)
114 {
115     ScriptWrappable::init(this);
116 }
117
118 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
119 {
120     return adoptRefWillBeRefCountedGarbageCollected(new HTMLAnchorElement(aTag, document));
121 }
122
123 HTMLAnchorElement::~HTMLAnchorElement()
124 {
125 }
126
127 bool HTMLAnchorElement::supportsFocus() const
128 {
129     if (rendererIsEditable())
130         return HTMLElement::supportsFocus();
131     // If not a link we should still be able to focus the element if it has tabIndex.
132     return isLink() || HTMLElement::supportsFocus();
133 }
134
135 bool HTMLAnchorElement::isMouseFocusable() const
136 {
137     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
138     // https://bugs.webkit.org/show_bug.cgi?id=26856
139     if (isLink())
140         return HTMLElement::supportsFocus();
141
142     return HTMLElement::isMouseFocusable();
143 }
144
145 bool HTMLAnchorElement::isKeyboardFocusable() const
146 {
147     ASSERT(document().isActive());
148
149     if (isFocusable() && Element::supportsFocus())
150         return HTMLElement::isKeyboardFocusable();
151
152     if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
153         return false;
154     return HTMLElement::isKeyboardFocusable();
155 }
156
157 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
158 {
159     if (!event->isMouseEvent())
160         return;
161
162     ASSERT(event->target());
163     Node* target = event->target()->toNode();
164     ASSERT(target);
165     if (!isHTMLImageElement(*target))
166         return;
167
168     HTMLImageElement& imageElement = toHTMLImageElement(*target);
169     if (!imageElement.isServerMap())
170         return;
171
172     if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
173         return;
174     RenderImage* renderer = toRenderImage(imageElement.renderer());
175
176     // FIXME: This should probably pass true for useTransforms.
177     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
178     int x = absolutePosition.x();
179     int y = absolutePosition.y();
180     url.append('?');
181     url.appendNumber(x);
182     url.append(',');
183     url.appendNumber(y);
184 }
185
186 void HTMLAnchorElement::defaultEventHandler(Event* event)
187 {
188     if (isLink()) {
189         if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
190             event->setDefaultHandled();
191             dispatchSimulatedClick(event);
192             return;
193         }
194
195         prefetchEventHandler()->handleEvent(event);
196
197         if (isLinkClick(event) && isLiveLink()) {
198             handleClick(event);
199             prefetchEventHandler()->reset();
200             return;
201         }
202     }
203
204     HTMLElement::defaultEventHandler(event);
205 }
206
207 void HTMLAnchorElement::setActive(bool down)
208 {
209     if (rendererIsEditable())
210         return;
211
212     ContainerNode::setActive(down);
213 }
214
215 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
216 {
217     if (name == hrefAttr) {
218         bool wasLink = isLink();
219         setIsLink(!value.isNull());
220         if (wasLink != isLink()) {
221             didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
222             if (wasLink && treeScope().adjustedFocusedElement() == this) {
223                 // We might want to call blur(), but it's dangerous to dispatch
224                 // events here.
225                 document().setNeedsFocusedElementCheck();
226             }
227         }
228         if (isLink()) {
229             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
230             if (document().isDNSPrefetchEnabled()) {
231                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
232                     prefetchDNS(document().completeURL(parsedURL).host());
233             }
234
235             if (wasLink)
236                 prefetchEventHandler()->didChangeHREF();
237         }
238         invalidateCachedVisitedLinkHash();
239     } else if (name == nameAttr || name == titleAttr) {
240         // Do nothing.
241     } else if (name == relAttr)
242         setRel(value);
243     else
244         HTMLElement::parseAttribute(name, value);
245 }
246
247 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
248 {
249     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
250 }
251
252 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
253 {
254     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
255 }
256
257 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
258 {
259     return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
260 }
261
262 bool HTMLAnchorElement::canStartSelection() const
263 {
264     if (!isLink())
265         return HTMLElement::canStartSelection();
266     return rendererIsEditable();
267 }
268
269 bool HTMLAnchorElement::draggable() const
270 {
271     // Should be draggable if we have an href attribute.
272     const AtomicString& value = getAttribute(draggableAttr);
273     if (equalIgnoringCase(value, "true"))
274         return true;
275     if (equalIgnoringCase(value, "false"))
276         return false;
277     return hasAttribute(hrefAttr);
278 }
279
280 KURL HTMLAnchorElement::href() const
281 {
282     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
283 }
284
285 void HTMLAnchorElement::setHref(const AtomicString& value)
286 {
287     setAttribute(hrefAttr, value);
288 }
289
290 KURL HTMLAnchorElement::url() const
291 {
292     return href();
293 }
294
295 void HTMLAnchorElement::setURL(const KURL& url)
296 {
297     setHref(AtomicString(url.string()));
298 }
299
300 String HTMLAnchorElement::input() const
301 {
302     return getAttribute(hrefAttr);
303 }
304
305 void HTMLAnchorElement::setInput(const String& value)
306 {
307     setHref(AtomicString(value));
308 }
309
310 bool HTMLAnchorElement::hasRel(uint32_t relation) const
311 {
312     return m_linkRelations & relation;
313 }
314
315 void HTMLAnchorElement::setRel(const AtomicString& value)
316 {
317     m_linkRelations = 0;
318     SpaceSplitString newLinkRelations(value, true);
319     // FIXME: Add link relations as they are implemented
320     if (newLinkRelations.contains("noreferrer"))
321         m_linkRelations |= RelationNoReferrer;
322 }
323
324 const AtomicString& HTMLAnchorElement::name() const
325 {
326     return getNameAttribute();
327 }
328
329 short HTMLAnchorElement::tabIndex() const
330 {
331     // Skip the supportsFocus check in HTMLElement.
332     return Element::tabIndex();
333 }
334
335 AtomicString HTMLAnchorElement::target() const
336 {
337     return getAttribute(targetAttr);
338 }
339
340 bool HTMLAnchorElement::isLiveLink() const
341 {
342     return isLink() && !rendererIsEditable();
343 }
344
345 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
346 {
347     const AtomicString& pingValue = getAttribute(pingAttr);
348     if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
349         return;
350
351     UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
352
353     SpaceSplitString pingURLs(pingValue, false);
354     for (unsigned i = 0; i < pingURLs.size(); i++)
355         PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
356 }
357
358 void HTMLAnchorElement::handleClick(Event* event)
359 {
360     event->setDefaultHandled();
361
362     LocalFrame* frame = document().frame();
363     if (!frame)
364         return;
365
366     StringBuilder url;
367     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
368     appendServerMapMousePosition(url, event);
369     KURL completedURL = document().completeURL(url.toString());
370
371     // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
372     // sent out.
373     sendPings(completedURL);
374
375     ResourceRequest request(completedURL);
376     if (prefetchEventHandler()->hasIssuedPreconnect())
377         frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
378     if (hasAttribute(downloadAttr)) {
379         if (!hasRel(RelationNoReferrer)) {
380             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
381             if (!referrer.isEmpty())
382                 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
383         }
384
385         bool isSameOrigin = document().securityOrigin()->canRequest(completedURL);
386         const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
387
388         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
389     } else {
390         FrameLoadRequest frameRequest(&document(), request, target());
391         frameRequest.setTriggeringEvent(event);
392         if (hasRel(RelationNoReferrer))
393             frameRequest.setShouldSendReferrer(NeverSendReferrer);
394         frame->loader().load(frameRequest);
395     }
396 }
397
398 bool isEnterKeyKeydownEvent(Event* event)
399 {
400     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
401 }
402
403 bool isLinkClick(Event* event)
404 {
405     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
406 }
407
408 bool HTMLAnchorElement::willRespondToMouseClickEvents()
409 {
410     return isLink() || HTMLElement::willRespondToMouseClickEvents();
411 }
412
413 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
414 {
415     if (!m_prefetchEventHandler)
416         m_prefetchEventHandler = PrefetchEventHandler::create(this);
417
418     return m_prefetchEventHandler.get();
419 }
420
421 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
422     : m_anchorElement(anchorElement)
423 {
424     ASSERT(m_anchorElement);
425
426     reset();
427 }
428
429 void HTMLAnchorElement::PrefetchEventHandler::reset()
430 {
431     m_hadHREFChanged = false;
432     m_mouseOverTimestamp = 0;
433     m_mouseDownTimestamp = 0;
434     m_hadTapUnconfirmed = false;
435     m_tapDownTimestamp = 0;
436     m_hasIssuedPreconnect = false;
437 }
438
439 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
440 {
441     if (!shouldPrefetch(m_anchorElement->href()))
442         return;
443
444     if (event->type() == EventTypeNames::mouseover)
445         handleMouseOver(event);
446     else if (event->type() == EventTypeNames::mouseout)
447         handleMouseOut(event);
448     else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
449         handleLeftMouseDown(event);
450     else if (event->type() == EventTypeNames::gestureshowpress)
451         handleGestureShowPress(event);
452     else if (event->type() == EventTypeNames::gesturetapunconfirmed)
453         handleGestureTapUnconfirmed(event);
454     else if (isLinkClick(event))
455         handleClick(event);
456 }
457
458 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
459 {
460     if (m_mouseOverTimestamp == 0.0) {
461         m_mouseOverTimestamp = event->timeStamp();
462
463         blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
464
465         prefetch(blink::WebPreconnectMotivationLinkMouseOver);
466     }
467 }
468
469 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
470 {
471     if (m_mouseOverTimestamp > 0.0) {
472         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
473         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
474
475         m_mouseOverTimestamp = 0.0;
476     }
477 }
478
479 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
480 {
481     m_mouseDownTimestamp = event->timeStamp();
482
483     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
484
485     prefetch(blink::WebPreconnectMotivationLinkMouseDown);
486 }
487
488 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
489 {
490     m_hadTapUnconfirmed = true;
491
492     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
493
494     prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
495 }
496
497 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
498 {
499     m_tapDownTimestamp = event->timeStamp();
500
501     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
502
503     prefetch(blink::WebPreconnectMotivationLinkTapDown);
504 }
505
506 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
507 {
508     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
509     if (capturedMouseOver) {
510         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
511
512         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
513     }
514
515     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
516     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
517
518     if (capturedMouseDown) {
519         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
520
521         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
522     }
523
524     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
525     if (capturedTapDown) {
526         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
527
528         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
529     }
530
531     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
532     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
533 }
534
535 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
536 {
537     if (m_hadHREFChanged)
538         return false;
539
540     if (m_anchorElement->hasEventListeners(EventTypeNames::click))
541         return false;
542
543     if (!url.protocolIsInHTTPFamily())
544         return false;
545
546     Document& document = m_anchorElement->document();
547
548     if (!document.securityOrigin()->canDisplay(url))
549         return false;
550
551     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
552         return false;
553
554     LocalFrame* frame = document.frame();
555     if (!frame)
556         return false;
557
558     // Links which create new window/tab are avoided because they may require user approval interaction.
559     if (!m_anchorElement->target().isEmpty())
560         return false;
561
562     return true;
563 }
564
565 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
566 {
567     const KURL& url = m_anchorElement->href();
568
569     if (!shouldPrefetch(url))
570         return;
571
572     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
573     if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
574         return;
575
576     preconnectToURL(url, motivation);
577     m_hasIssuedPreconnect = true;
578 }
579
580 bool HTMLAnchorElement::isInteractiveContent() const
581 {
582     return isLink();
583 }
584
585 }