1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=2 et :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2009
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "Link.h"
41 :
42 : #include "nsEventStates.h"
43 : #include "nsIURL.h"
44 : #include "nsISizeOf.h"
45 :
46 : #include "nsContentUtils.h"
47 : #include "nsEscape.h"
48 : #include "nsGkAtoms.h"
49 : #include "nsString.h"
50 : #include "mozAutoDocUpdate.h"
51 :
52 : #include "mozilla/Services.h"
53 :
54 : namespace mozilla {
55 : namespace dom {
56 :
57 0 : Link::Link(Element *aElement)
58 : : mLinkState(defaultState)
59 : , mRegistered(false)
60 : , mElement(aElement)
61 0 : , mHistory(services::GetHistoryService())
62 : {
63 0 : NS_ABORT_IF_FALSE(mElement, "Must have an element");
64 0 : }
65 :
66 0 : Link::~Link()
67 : {
68 0 : UnregisterFromHistory();
69 0 : }
70 :
71 : nsLinkState
72 0 : Link::GetLinkState() const
73 : {
74 0 : NS_ASSERTION(mRegistered,
75 : "Getting the link state of an unregistered Link!");
76 0 : NS_ASSERTION(mLinkState != eLinkState_Unknown,
77 : "Getting the link state with an unknown value!");
78 0 : return mLinkState;
79 : }
80 :
81 : void
82 0 : Link::SetLinkState(nsLinkState aState)
83 : {
84 0 : NS_ASSERTION(mRegistered,
85 : "Setting the link state of an unregistered Link!");
86 0 : NS_ASSERTION(mLinkState != aState,
87 : "Setting state to the currently set state!");
88 :
89 : // Set our current state as appropriate.
90 0 : mLinkState = aState;
91 :
92 : // Per IHistory interface documentation, we are no longer registered.
93 0 : mRegistered = false;
94 :
95 0 : NS_ABORT_IF_FALSE(LinkState() == NS_EVENT_STATE_VISITED ||
96 : LinkState() == NS_EVENT_STATE_UNVISITED,
97 : "Unexpected state obtained from LinkState()!");
98 :
99 : // Tell the element to update its visited state
100 0 : mElement->UpdateState(true);
101 0 : }
102 :
103 : nsEventStates
104 0 : Link::LinkState() const
105 : {
106 : // We are a constant method, but we are just lazily doing things and have to
107 : // track that state. Cast away that constness!
108 0 : Link *self = const_cast<Link *>(this);
109 :
110 : // If we are not in the document, default to not visited.
111 0 : Element *element = self->mElement;
112 0 : if (!element->IsInDoc()) {
113 0 : self->mLinkState = eLinkState_Unvisited;
114 : }
115 :
116 : // If we have not yet registered for notifications and are in an unknown
117 : // state, register now!
118 0 : if (!mRegistered && mLinkState == eLinkState_Unknown) {
119 : // First, make sure the href attribute has a valid link (bug 23209).
120 0 : nsCOMPtr<nsIURI> hrefURI(GetURI());
121 0 : if (!hrefURI) {
122 0 : self->mLinkState = eLinkState_NotLink;
123 0 : return nsEventStates();
124 : }
125 :
126 : // We have a good href, so register with History.
127 0 : nsresult rv = mHistory->RegisterVisitedCallback(hrefURI, self);
128 0 : if (NS_SUCCEEDED(rv)) {
129 0 : self->mRegistered = true;
130 :
131 : // Assume that we are not visited until we are told otherwise.
132 0 : self->mLinkState = eLinkState_Unvisited;
133 :
134 : // And make sure we are in the document's link map.
135 0 : nsIDocument *doc = element->GetCurrentDoc();
136 0 : if (doc) {
137 0 : doc->AddStyleRelevantLink(self);
138 : }
139 : }
140 : }
141 :
142 : // Otherwise, return our known state.
143 0 : if (mLinkState == eLinkState_Visited) {
144 0 : return NS_EVENT_STATE_VISITED;
145 : }
146 :
147 0 : if (mLinkState == eLinkState_Unvisited) {
148 0 : return NS_EVENT_STATE_UNVISITED;
149 : }
150 :
151 0 : return nsEventStates();
152 : }
153 :
154 : already_AddRefed<nsIURI>
155 0 : Link::GetURI() const
156 : {
157 0 : nsCOMPtr<nsIURI> uri(mCachedURI);
158 :
159 : // If we have this URI cached, use it.
160 0 : if (uri) {
161 0 : return uri.forget();
162 : }
163 :
164 : // Otherwise obtain it.
165 0 : Link *self = const_cast<Link *>(this);
166 0 : Element *element = self->mElement;
167 0 : uri = element->GetHrefURI();
168 :
169 : // We want to cache the URI if the node is in the document.
170 0 : if (uri && element->IsInDoc()) {
171 0 : mCachedURI = uri;
172 : }
173 :
174 0 : return uri.forget();
175 : }
176 :
177 : nsresult
178 0 : Link::SetProtocol(const nsAString &aProtocol)
179 : {
180 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
181 0 : if (!uri) {
182 : // Ignore failures to be compatible with NS4.
183 0 : return NS_OK;
184 : }
185 :
186 0 : nsAString::const_iterator start, end;
187 0 : aProtocol.BeginReading(start);
188 0 : aProtocol.EndReading(end);
189 0 : nsAString::const_iterator iter(start);
190 0 : (void)FindCharInReadable(':', iter, end);
191 0 : (void)uri->SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)));
192 :
193 0 : SetHrefAttribute(uri);
194 0 : return NS_OK;
195 : }
196 :
197 : nsresult
198 0 : Link::SetHost(const nsAString &aHost)
199 : {
200 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
201 0 : if (!uri) {
202 : // Ignore failures to be compatible with NS4.
203 0 : return NS_OK;
204 : }
205 :
206 : // We cannot simply call nsIURI::SetHost because that would treat the name as
207 : // an IPv6 address (like http:://[server:443]/). We also cannot call
208 : // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
209 :
210 : // First set the hostname.
211 0 : nsAString::const_iterator start, end;
212 0 : aHost.BeginReading(start);
213 0 : aHost.EndReading(end);
214 0 : nsAString::const_iterator iter(start);
215 0 : (void)FindCharInReadable(':', iter, end);
216 0 : NS_ConvertUTF16toUTF8 host(Substring(start, iter));
217 0 : (void)uri->SetHost(host);
218 :
219 : // Also set the port if needed.
220 0 : if (iter != end) {
221 0 : iter++;
222 0 : if (iter != end) {
223 0 : nsAutoString portStr(Substring(iter, end));
224 : nsresult rv;
225 0 : PRInt32 port = portStr.ToInteger((PRInt32 *)&rv);
226 0 : if (NS_SUCCEEDED(rv)) {
227 0 : (void)uri->SetPort(port);
228 : }
229 : }
230 : };
231 :
232 0 : SetHrefAttribute(uri);
233 0 : return NS_OK;
234 : }
235 :
236 : nsresult
237 0 : Link::SetHostname(const nsAString &aHostname)
238 : {
239 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
240 0 : if (!uri) {
241 : // Ignore failures to be compatible with NS4.
242 0 : return NS_OK;
243 : }
244 :
245 0 : (void)uri->SetHost(NS_ConvertUTF16toUTF8(aHostname));
246 0 : SetHrefAttribute(uri);
247 0 : return NS_OK;
248 : }
249 :
250 : nsresult
251 0 : Link::SetPathname(const nsAString &aPathname)
252 : {
253 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
254 0 : nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
255 0 : if (!url) {
256 : // Ignore failures to be compatible with NS4.
257 0 : return NS_OK;
258 : }
259 :
260 0 : (void)url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
261 0 : SetHrefAttribute(uri);
262 0 : return NS_OK;
263 : }
264 :
265 : nsresult
266 0 : Link::SetSearch(const nsAString &aSearch)
267 : {
268 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
269 0 : nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
270 0 : if (!url) {
271 : // Ignore failures to be compatible with NS4.
272 0 : return NS_OK;
273 : }
274 :
275 0 : (void)url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
276 0 : SetHrefAttribute(uri);
277 0 : return NS_OK;
278 : }
279 :
280 : nsresult
281 0 : Link::SetPort(const nsAString &aPort)
282 : {
283 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
284 0 : if (!uri) {
285 : // Ignore failures to be compatible with NS4.
286 0 : return NS_OK;
287 : }
288 :
289 : nsresult rv;
290 0 : nsAutoString portStr(aPort);
291 0 : PRInt32 port = portStr.ToInteger((PRInt32 *)&rv);
292 0 : if (NS_FAILED(rv)) {
293 0 : return NS_OK;
294 : }
295 :
296 0 : (void)uri->SetPort(port);
297 0 : SetHrefAttribute(uri);
298 0 : return NS_OK;
299 : }
300 :
301 : nsresult
302 0 : Link::SetHash(const nsAString &aHash)
303 : {
304 0 : nsCOMPtr<nsIURI> uri(GetURIToMutate());
305 0 : if (!uri) {
306 : // Ignore failures to be compatible with NS4.
307 0 : return NS_OK;
308 : }
309 :
310 0 : (void)uri->SetRef(NS_ConvertUTF16toUTF8(aHash));
311 0 : SetHrefAttribute(uri);
312 0 : return NS_OK;
313 : }
314 :
315 : nsresult
316 0 : Link::GetProtocol(nsAString &_protocol)
317 : {
318 0 : nsCOMPtr<nsIURI> uri(GetURI());
319 0 : if (!uri) {
320 0 : _protocol.AssignLiteral("http");
321 : }
322 : else {
323 0 : nsCAutoString scheme;
324 0 : (void)uri->GetScheme(scheme);
325 0 : CopyASCIItoUTF16(scheme, _protocol);
326 : }
327 0 : _protocol.Append(PRUnichar(':'));
328 0 : return NS_OK;
329 : }
330 :
331 : nsresult
332 0 : Link::GetHost(nsAString &_host)
333 : {
334 0 : _host.Truncate();
335 :
336 0 : nsCOMPtr<nsIURI> uri(GetURI());
337 0 : if (!uri) {
338 : // Do not throw! Not having a valid URI should result in an empty string.
339 0 : return NS_OK;
340 : }
341 :
342 0 : nsCAutoString hostport;
343 0 : nsresult rv = uri->GetHostPort(hostport);
344 0 : if (NS_SUCCEEDED(rv)) {
345 0 : CopyUTF8toUTF16(hostport, _host);
346 : }
347 0 : return NS_OK;
348 : }
349 :
350 : nsresult
351 0 : Link::GetHostname(nsAString &_hostname)
352 : {
353 0 : _hostname.Truncate();
354 :
355 0 : nsCOMPtr<nsIURI> uri(GetURI());
356 0 : if (!uri) {
357 : // Do not throw! Not having a valid URI should result in an empty string.
358 0 : return NS_OK;
359 : }
360 :
361 0 : nsCAutoString host;
362 0 : nsresult rv = uri->GetHost(host);
363 : // Note that failure to get the host from the URI is not necessarily a bad
364 : // thing. Some URIs do not have a host.
365 0 : if (NS_SUCCEEDED(rv)) {
366 0 : CopyUTF8toUTF16(host, _hostname);
367 : }
368 0 : return NS_OK;
369 : }
370 :
371 : nsresult
372 0 : Link::GetPathname(nsAString &_pathname)
373 : {
374 0 : _pathname.Truncate();
375 :
376 0 : nsCOMPtr<nsIURI> uri(GetURI());
377 0 : nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
378 0 : if (!url) {
379 : // Do not throw! Not having a valid URI or URL should result in an empty
380 : // string.
381 0 : return NS_OK;
382 : }
383 :
384 0 : nsCAutoString file;
385 0 : nsresult rv = url->GetFilePath(file);
386 0 : NS_ENSURE_SUCCESS(rv, rv);
387 0 : CopyUTF8toUTF16(file, _pathname);
388 0 : return NS_OK;
389 : }
390 :
391 : nsresult
392 0 : Link::GetSearch(nsAString &_search)
393 : {
394 0 : _search.Truncate();
395 :
396 0 : nsCOMPtr<nsIURI> uri(GetURI());
397 0 : nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
398 0 : if (!url) {
399 : // Do not throw! Not having a valid URI or URL should result in an empty
400 : // string.
401 0 : return NS_OK;
402 : }
403 :
404 0 : nsCAutoString search;
405 0 : nsresult rv = url->GetQuery(search);
406 0 : if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
407 0 : CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, _search);
408 : }
409 0 : return NS_OK;
410 : }
411 :
412 : nsresult
413 0 : Link::GetPort(nsAString &_port)
414 : {
415 0 : _port.Truncate();
416 :
417 0 : nsCOMPtr<nsIURI> uri(GetURI());
418 0 : if (!uri) {
419 : // Do not throw! Not having a valid URI should result in an empty string.
420 0 : return NS_OK;
421 : }
422 :
423 : PRInt32 port;
424 0 : nsresult rv = uri->GetPort(&port);
425 : // Note that failure to get the port from the URI is not necessarily a bad
426 : // thing. Some URIs do not have a port.
427 0 : if (NS_SUCCEEDED(rv) && port != -1) {
428 0 : nsAutoString portStr;
429 0 : portStr.AppendInt(port, 10);
430 0 : _port.Assign(portStr);
431 : }
432 0 : return NS_OK;
433 : }
434 :
435 : nsresult
436 0 : Link::GetHash(nsAString &_hash)
437 : {
438 0 : _hash.Truncate();
439 :
440 0 : nsCOMPtr<nsIURI> uri(GetURI());
441 0 : if (!uri) {
442 : // Do not throw! Not having a valid URI should result in an empty
443 : // string.
444 0 : return NS_OK;
445 : }
446 :
447 0 : nsCAutoString ref;
448 0 : nsresult rv = uri->GetRef(ref);
449 0 : if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
450 0 : NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
451 0 : _hash.Assign(PRUnichar('#'));
452 0 : AppendUTF8toUTF16(ref, _hash);
453 : }
454 0 : return NS_OK;
455 : }
456 :
457 : void
458 0 : Link::ResetLinkState(bool aNotify)
459 : {
460 : // If we are in our default state, bail early.
461 0 : if (mLinkState == defaultState) {
462 0 : return;
463 : }
464 :
465 0 : Element *element = mElement;
466 :
467 : // Tell the document to forget about this link if we were a link before.
468 0 : nsIDocument *doc = element->GetCurrentDoc();
469 0 : if (doc && mLinkState != eLinkState_NotLink) {
470 0 : doc->ForgetLink(this);
471 : }
472 :
473 0 : UnregisterFromHistory();
474 :
475 : // Update our state back to the default.
476 0 : mLinkState = defaultState;
477 :
478 : // Get rid of our cached URI.
479 0 : mCachedURI = nsnull;
480 :
481 : // We have to be very careful here: if aNotify is false we do NOT
482 : // want to call UpdateState, because that will call into LinkState()
483 : // and try to start off loads, etc. But ResetLinkState is called
484 : // with aNotify false when things are in inconsistent states, so
485 : // we'll get confused in that situation. Instead, just silently
486 : // update the link state on mElement.
487 0 : if (aNotify) {
488 0 : mElement->UpdateState(aNotify);
489 : } else {
490 0 : mElement->UpdateLinkState(nsEventStates());
491 : }
492 : }
493 :
494 : void
495 0 : Link::UnregisterFromHistory()
496 : {
497 : // If we are not registered, we have nothing to do.
498 0 : if (!mRegistered) {
499 0 : return;
500 : }
501 :
502 0 : NS_ASSERTION(mCachedURI, "mRegistered is true, but we have no cached URI?!");
503 :
504 : // And tell History to stop tracking us.
505 0 : nsresult rv = mHistory->UnregisterVisitedCallback(mCachedURI, this);
506 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "This should only fail if we misuse the API!");
507 0 : if (NS_SUCCEEDED(rv)) {
508 0 : mRegistered = false;
509 : }
510 : }
511 :
512 : already_AddRefed<nsIURI>
513 0 : Link::GetURIToMutate()
514 : {
515 0 : nsCOMPtr<nsIURI> uri(GetURI());
516 0 : if (!uri) {
517 0 : return nsnull;
518 : }
519 0 : nsCOMPtr<nsIURI> clone;
520 0 : (void)uri->Clone(getter_AddRefs(clone));
521 0 : return clone.forget();
522 : }
523 :
524 : void
525 0 : Link::SetHrefAttribute(nsIURI *aURI)
526 : {
527 0 : NS_ASSERTION(aURI, "Null URI is illegal!");
528 :
529 0 : nsCAutoString href;
530 0 : (void)aURI->GetSpec(href);
531 : (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
532 0 : NS_ConvertUTF8toUTF16(href), true);
533 0 : }
534 :
535 : size_t
536 0 : Link::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const
537 : {
538 0 : size_t n = 0;
539 :
540 0 : if (mCachedURI) {
541 0 : nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI);
542 0 : if (iface) {
543 0 : n += iface->SizeOfIncludingThis(aMallocSizeOf);
544 : }
545 : }
546 :
547 : // The following members don't need to be measured:
548 : // - mElement, because it is a pointer-to-self used to avoid QIs
549 : // - mHistory, because it is non-owning
550 :
551 0 : return n;
552 : }
553 :
554 : } // namespace dom
555 : } // namespace mozilla
|