Link.cpp (11590B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "Link.h" 8 9 #include "mozilla/Components.h" 10 #include "mozilla/IHistory.h" 11 #include "mozilla/dom/BindContext.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/Element.h" 14 #include "mozilla/dom/HTMLDNSPrefetch.h" 15 #include "mozilla/dom/SVGAElement.h" 16 #include "nsAttrValueInlines.h" 17 #include "nsGkAtoms.h" 18 #include "nsIURIMutator.h" 19 #include "nsLayoutUtils.h" 20 #include "nsString.h" 21 22 namespace mozilla::dom { 23 24 Link::Link(Element* aElement) 25 : mElement(aElement), 26 mNeedsRegistration(false), 27 mRegistered(false), 28 mHasPendingLinkUpdate(false), 29 mHistory(true) { 30 MOZ_ASSERT(mElement, "Must have an element"); 31 } 32 33 Link::Link() 34 : mElement(nullptr), 35 mNeedsRegistration(false), 36 mRegistered(false), 37 mHasPendingLinkUpdate(false), 38 mHistory(false) {} 39 40 Link::~Link() { 41 // !mElement is for mock_Link. 42 MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc()); 43 Unregister(); 44 } 45 46 bool Link::ElementHasHref() const { 47 if (mElement->HasAttr(nsGkAtoms::href)) { 48 return true; 49 } 50 if (const auto* svg = SVGAElement::FromNode(*mElement)) { 51 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once 52 // SMIL is fixed to actually mutate DOM attributes rather than faking it. 53 return svg->HasHref(); 54 } 55 MOZ_ASSERT(!mElement->IsSVGElement(), 56 "What other SVG element inherits from Link?"); 57 return false; 58 } 59 60 void Link::SetLinkState(State aState, bool aNotify) { 61 Element::AutoStateChangeNotifier notifier(*mElement, aNotify); 62 switch (aState) { 63 case State::Visited: 64 mElement->AddStatesSilently(ElementState::VISITED); 65 mElement->RemoveStatesSilently(ElementState::UNVISITED); 66 break; 67 case State::Unvisited: 68 mElement->AddStatesSilently(ElementState::UNVISITED); 69 mElement->RemoveStatesSilently(ElementState::VISITED); 70 break; 71 case State::NotLink: 72 mElement->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED); 73 break; 74 } 75 } 76 77 void Link::TriggerLinkUpdate(bool aNotify) { 78 if (mRegistered || !mNeedsRegistration || mHasPendingLinkUpdate || 79 !mElement->IsInComposedDoc()) { 80 return; 81 } 82 83 // Only try and register once. 84 mNeedsRegistration = false; 85 86 nsCOMPtr<nsIURI> hrefURI = GetURI(); 87 88 // Assume that we are not visited until we are told otherwise. 89 SetLinkState(State::Unvisited, aNotify); 90 91 // Make sure the href attribute has a valid link (bug 23209). 92 // If we have a good href, register with History if available. 93 if (mHistory && hrefURI) { 94 if (nsCOMPtr<IHistory> history = components::History::Service()) { 95 mRegistered = true; 96 history->RegisterVisitedCallback(hrefURI, this); 97 // And make sure we are in the document's link map. 98 mElement->GetComposedDoc()->AddStyleRelevantLink(this); 99 } 100 } 101 } 102 103 void Link::VisitedQueryFinished(bool aVisited) { 104 MOZ_ASSERT(mRegistered, "Setting the link state of an unregistered Link!"); 105 106 SetLinkState(aVisited ? State::Visited : State::Unvisited, 107 /* aNotify = */ true); 108 // Even if the state didn't actually change, we need to repaint in order for 109 // the visited state not to be observable. 110 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(), 111 nsChangeHint_RepaintFrame); 112 } 113 114 nsIURI* Link::GetURI() const { 115 // If we have this URI cached, use it. 116 if (mCachedURI) { 117 return mCachedURI; 118 } 119 120 // Otherwise obtain it. 121 Link* self = const_cast<Link*>(this); 122 Element* element = self->mElement; 123 mCachedURI = element->GetHrefURI(); 124 125 return mCachedURI; 126 } 127 128 void Link::SetProtocol(const nsACString& aProtocol) { 129 nsCOMPtr<nsIURI> uri(GetURI()); 130 if (!uri) { 131 // Ignore failures to be compatible with NS4. 132 return; 133 } 134 uri = net::TryChangeProtocol(uri, aProtocol); 135 if (!uri) { 136 return; 137 } 138 SetHrefAttribute(uri); 139 } 140 141 void Link::SetPassword(const nsACString& aPassword) { 142 nsCOMPtr<nsIURI> uri(GetURI()); 143 if (!uri) { 144 // Ignore failures to be compatible with NS4. 145 return; 146 } 147 148 nsresult rv = NS_MutateURI(uri).SetPassword(aPassword).Finalize(uri); 149 if (NS_SUCCEEDED(rv)) { 150 SetHrefAttribute(uri); 151 } 152 } 153 154 void Link::SetUsername(const nsACString& aUsername) { 155 nsCOMPtr<nsIURI> uri(GetURI()); 156 if (!uri) { 157 // Ignore failures to be compatible with NS4. 158 return; 159 } 160 161 nsresult rv = NS_MutateURI(uri).SetUsername(aUsername).Finalize(uri); 162 if (NS_SUCCEEDED(rv)) { 163 SetHrefAttribute(uri); 164 } 165 } 166 167 void Link::SetHost(const nsACString& aHost) { 168 nsCOMPtr<nsIURI> uri(GetURI()); 169 if (!uri) { 170 // Ignore failures to be compatible with NS4. 171 return; 172 } 173 174 nsresult rv = NS_MutateURI(uri).SetHostPort(aHost).Finalize(uri); 175 if (NS_FAILED(rv)) { 176 return; 177 } 178 SetHrefAttribute(uri); 179 } 180 181 void Link::SetHostname(const nsACString& aHostname) { 182 nsCOMPtr<nsIURI> uri(GetURI()); 183 if (!uri) { 184 // Ignore failures to be compatible with NS4. 185 return; 186 } 187 188 nsresult rv = NS_MutateURI(uri).SetHost(aHostname).Finalize(uri); 189 if (NS_FAILED(rv)) { 190 return; 191 } 192 SetHrefAttribute(uri); 193 } 194 195 void Link::SetPathname(const nsACString& aPathname) { 196 nsCOMPtr<nsIURI> uri(GetURI()); 197 if (!uri) { 198 // Ignore failures to be compatible with NS4. 199 return; 200 } 201 202 nsresult rv = NS_MutateURI(uri).SetFilePath(aPathname).Finalize(uri); 203 if (NS_FAILED(rv)) { 204 return; 205 } 206 SetHrefAttribute(uri); 207 } 208 209 void Link::SetSearch(const nsACString& aSearch) { 210 nsCOMPtr<nsIURI> uri(GetURI()); 211 if (!uri) { 212 // Ignore failures to be compatible with NS4. 213 return; 214 } 215 216 nsresult rv = NS_MutateURI(uri).SetQuery(aSearch).Finalize(uri); 217 if (NS_FAILED(rv)) { 218 return; 219 } 220 SetHrefAttribute(uri); 221 } 222 223 void Link::SetPort(const nsACString& aPort) { 224 nsCOMPtr<nsIURI> uri(GetURI()); 225 if (!uri) { 226 // Ignore failures to be compatible with NS4. 227 return; 228 } 229 230 // nsIURI uses -1 as default value. 231 nsresult rv; 232 int32_t port = -1; 233 if (!aPort.IsEmpty()) { 234 port = aPort.ToInteger(&rv); 235 if (NS_FAILED(rv)) { 236 return; 237 } 238 } 239 240 rv = NS_MutateURI(uri).SetPort(port).Finalize(uri); 241 if (NS_FAILED(rv)) { 242 return; 243 } 244 SetHrefAttribute(uri); 245 } 246 247 void Link::SetHash(const nsACString& aHash) { 248 nsCOMPtr<nsIURI> uri(GetURI()); 249 if (!uri) { 250 // Ignore failures to be compatible with NS4. 251 return; 252 } 253 254 nsresult rv = NS_MutateURI(uri).SetRef(aHash).Finalize(uri); 255 if (NS_FAILED(rv)) { 256 return; 257 } 258 259 SetHrefAttribute(uri); 260 } 261 262 void Link::GetOrigin(nsACString& aOrigin) { 263 aOrigin.Truncate(); 264 265 nsIURI* uri = GetURI(); 266 if (!uri) { 267 return; 268 } 269 270 nsContentUtils::GetWebExposedOriginSerialization(uri, aOrigin); 271 } 272 273 void Link::GetProtocol(nsACString& aProtocol) { 274 if (nsIURI* uri = GetURI()) { 275 (void)uri->GetScheme(aProtocol); 276 } 277 aProtocol.Append(':'); 278 } 279 280 void Link::GetUsername(nsACString& aUsername) { 281 aUsername.Truncate(); 282 283 nsIURI* uri = GetURI(); 284 if (!uri) { 285 return; 286 } 287 288 uri->GetUsername(aUsername); 289 } 290 291 void Link::GetPassword(nsACString& aPassword) { 292 aPassword.Truncate(); 293 294 nsIURI* uri = GetURI(); 295 if (!uri) { 296 return; 297 } 298 299 uri->GetPassword(aPassword); 300 } 301 302 void Link::GetHost(nsACString& aHost) { 303 aHost.Truncate(); 304 305 nsIURI* uri = GetURI(); 306 if (!uri) { 307 // Do not throw! Not having a valid URI should result in an empty string. 308 return; 309 } 310 311 uri->GetHostPort(aHost); 312 } 313 314 void Link::GetHostname(nsACString& aHostname) { 315 aHostname.Truncate(); 316 317 nsIURI* uri = GetURI(); 318 if (!uri) { 319 // Do not throw! Not having a valid URI should result in an empty string. 320 return; 321 } 322 323 nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname); 324 } 325 326 void Link::GetPathname(nsACString& aPathname) { 327 aPathname.Truncate(); 328 329 nsIURI* uri = GetURI(); 330 if (!uri) { 331 // Do not throw! Not having a valid URI should result in an empty string. 332 return; 333 } 334 335 uri->GetFilePath(aPathname); 336 } 337 338 void Link::GetSearch(nsACString& aSearch) { 339 aSearch.Truncate(); 340 341 nsIURI* uri = GetURI(); 342 if (!uri) { 343 // Do not throw! Not having a valid URI or URL should result in an empty 344 // string. 345 return; 346 } 347 348 nsresult rv = uri->GetQuery(aSearch); 349 if (NS_SUCCEEDED(rv) && !aSearch.IsEmpty()) { 350 aSearch.Insert('?', 0); 351 } 352 } 353 354 void Link::GetPort(nsACString& aPort) { 355 aPort.Truncate(); 356 357 nsIURI* uri = GetURI(); 358 if (!uri) { 359 // Do not throw! Not having a valid URI should result in an empty string. 360 return; 361 } 362 363 int32_t port; 364 nsresult rv = uri->GetPort(&port); 365 // Note that failure to get the port from the URI is not necessarily a bad 366 // thing. Some URIs do not have a port. 367 if (NS_SUCCEEDED(rv) && port != -1) { 368 aPort.AppendInt(port, 10); 369 } 370 } 371 372 void Link::GetHash(nsACString& aHash) { 373 aHash.Truncate(); 374 375 nsIURI* uri = GetURI(); 376 if (!uri) { 377 // Do not throw! Not having a valid URI should result in an empty 378 // string. 379 return; 380 } 381 382 nsresult rv = uri->GetRef(aHash); 383 if (NS_SUCCEEDED(rv) && !aHash.IsEmpty()) { 384 aHash.Insert('#', 0); 385 } 386 } 387 388 void Link::BindToTree(const BindContext& aContext) { 389 if (aContext.InComposedDoc()) { 390 aContext.OwnerDoc().RegisterPendingLinkUpdate(this); 391 } 392 ResetLinkState(false); 393 } 394 395 void Link::ResetLinkState(bool aNotify, bool aHasHref) { 396 // If we have an href, we should register with the history. 397 // 398 // FIXME(emilio): Do we really want to allow all MathML elements to be 399 // :visited? That seems not great. 400 mNeedsRegistration = aHasHref; 401 402 // If we've cached the URI, reset always invalidates it. 403 Unregister(); 404 mCachedURI = nullptr; 405 406 // Update our state back to the default; the default state for links with an 407 // href is unvisited. 408 SetLinkState(aHasHref ? State::Unvisited : State::NotLink, aNotify); 409 TriggerLinkUpdate(aNotify); 410 } 411 412 void Link::Unregister() { 413 // If we are not registered, we have nothing to do. 414 if (!mRegistered) { 415 return; 416 } 417 418 MOZ_ASSERT(mHistory); 419 MOZ_ASSERT(mCachedURI, "Should unregister before invalidating the URI"); 420 421 // And tell History to stop tracking us. 422 if (nsCOMPtr<IHistory> history = components::History::Service()) { 423 history->UnregisterVisitedCallback(mCachedURI, this); 424 } 425 mElement->OwnerDoc()->ForgetLink(this); 426 mRegistered = false; 427 } 428 429 void Link::SetHrefAttribute(nsIURI* aURI) { 430 NS_ASSERTION(aURI, "Null URI is illegal!"); 431 432 // if we change this code to not reserialize we need to do something smarter 433 // in SetProtocol because changing the protocol of an URI can change the 434 // "nature" of the nsIURL/nsIURI implementation. 435 nsAutoCString href; 436 (void)aURI->GetSpec(href); 437 (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href, 438 NS_ConvertUTF8toUTF16(href), true); 439 } 440 441 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const { 442 size_t n = 0; 443 444 // It is okay to include the size of mCachedURI here even though it might have 445 // strong references from elsewhere because the URI was created for this 446 // object, in nsGenericHTMLElement::GetURIAttr(). Only objects that created 447 // their own URI will call nsIURI::SizeOfIncludingThis(). 448 if (mCachedURI) { 449 n += mCachedURI->SizeOfIncludingThis(aState.mMallocSizeOf); 450 } 451 452 // The following members don't need to be measured: 453 // - mElement, because it is a pointer-to-self used to avoid QIs 454 455 return n; 456 } 457 458 } // namespace mozilla::dom