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