Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / Link.cpp
blob1465ea57738d0b758874d6e9225940cf871397fe
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/. */
7 #include "Link.h"
9 #include "mozilla/dom/Element.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/SVGAElement.h"
13 #include "mozilla/dom/HTMLDNSPrefetch.h"
14 #include "mozilla/IHistory.h"
15 #include "nsLayoutUtils.h"
16 #include "nsIURIMutator.h"
17 #include "nsISizeOf.h"
19 #include "nsGkAtoms.h"
20 #include "nsString.h"
22 #include "mozilla/Components.h"
23 #include "nsAttrValueInlines.h"
25 namespace mozilla::dom {
27 Link::Link(Element* aElement)
28 : mElement(aElement),
29 mNeedsRegistration(false),
30 mRegistered(false),
31 mHasPendingLinkUpdate(false),
32 mHistory(true) {
33 MOZ_ASSERT(mElement, "Must have an element");
36 Link::Link()
37 : mElement(nullptr),
38 mNeedsRegistration(false),
39 mRegistered(false),
40 mHasPendingLinkUpdate(false),
41 mHistory(false) {}
43 Link::~Link() {
44 // !mElement is for mock_Link.
45 MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc());
46 Unregister();
49 bool Link::ElementHasHref() const {
50 if (mElement->HasAttr(nsGkAtoms::href)) {
51 return true;
53 if (const auto* svg = SVGAElement::FromNode(*mElement)) {
54 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
55 // SMIL is fixed to actually mutate DOM attributes rather than faking it.
56 return svg->HasHref();
58 MOZ_ASSERT(!mElement->IsSVGElement(),
59 "What other SVG element inherits from Link?");
60 return false;
63 void Link::SetLinkState(State aState, bool aNotify) {
64 Element::AutoStateChangeNotifier notifier(*mElement, aNotify);
65 switch (aState) {
66 case State::Visited:
67 mElement->AddStatesSilently(ElementState::VISITED);
68 mElement->RemoveStatesSilently(ElementState::UNVISITED);
69 break;
70 case State::Unvisited:
71 mElement->AddStatesSilently(ElementState::UNVISITED);
72 mElement->RemoveStatesSilently(ElementState::VISITED);
73 break;
74 case State::NotLink:
75 mElement->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED);
76 break;
80 void Link::TriggerLinkUpdate(bool aNotify) {
81 if (mRegistered || !mNeedsRegistration || mHasPendingLinkUpdate ||
82 !mElement->IsInComposedDoc()) {
83 return;
86 // Only try and register once.
87 mNeedsRegistration = false;
89 nsCOMPtr<nsIURI> hrefURI = GetURI();
91 // Assume that we are not visited until we are told otherwise.
92 SetLinkState(State::Unvisited, aNotify);
94 // Make sure the href attribute has a valid link (bug 23209).
95 // If we have a good href, register with History if available.
96 if (mHistory && hrefURI) {
97 if (nsCOMPtr<IHistory> history = components::History::Service()) {
98 mRegistered = true;
99 history->RegisterVisitedCallback(hrefURI, this);
100 // And make sure we are in the document's link map.
101 mElement->GetComposedDoc()->AddStyleRelevantLink(this);
106 void Link::VisitedQueryFinished(bool aVisited) {
107 MOZ_ASSERT(mRegistered, "Setting the link state of an unregistered Link!");
109 SetLinkState(aVisited ? State::Visited : State::Unvisited,
110 /* aNotify = */ true);
111 // Even if the state didn't actually change, we need to repaint in order for
112 // the visited state not to be observable.
113 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
114 nsChangeHint_RepaintFrame);
117 nsIURI* Link::GetURI() const {
118 // If we have this URI cached, use it.
119 if (mCachedURI) {
120 return mCachedURI;
123 // Otherwise obtain it.
124 Link* self = const_cast<Link*>(this);
125 Element* element = self->mElement;
126 mCachedURI = element->GetHrefURI();
128 return mCachedURI;
131 void Link::SetProtocol(const nsACString& aProtocol) {
132 nsCOMPtr<nsIURI> uri(GetURI());
133 if (!uri) {
134 // Ignore failures to be compatible with NS4.
135 return;
137 uri = net::TryChangeProtocol(uri, aProtocol);
138 if (!uri) {
139 return;
141 SetHrefAttribute(uri);
144 void Link::SetPassword(const nsACString& aPassword) {
145 nsCOMPtr<nsIURI> uri(GetURI());
146 if (!uri) {
147 // Ignore failures to be compatible with NS4.
148 return;
151 nsresult rv = NS_MutateURI(uri).SetPassword(aPassword).Finalize(uri);
152 if (NS_SUCCEEDED(rv)) {
153 SetHrefAttribute(uri);
157 void Link::SetUsername(const nsACString& aUsername) {
158 nsCOMPtr<nsIURI> uri(GetURI());
159 if (!uri) {
160 // Ignore failures to be compatible with NS4.
161 return;
164 nsresult rv = NS_MutateURI(uri).SetUsername(aUsername).Finalize(uri);
165 if (NS_SUCCEEDED(rv)) {
166 SetHrefAttribute(uri);
170 void Link::SetHost(const nsACString& aHost) {
171 nsCOMPtr<nsIURI> uri(GetURI());
172 if (!uri) {
173 // Ignore failures to be compatible with NS4.
174 return;
177 nsresult rv = NS_MutateURI(uri).SetHostPort(aHost).Finalize(uri);
178 if (NS_FAILED(rv)) {
179 return;
181 SetHrefAttribute(uri);
184 void Link::SetHostname(const nsACString& aHostname) {
185 nsCOMPtr<nsIURI> uri(GetURI());
186 if (!uri) {
187 // Ignore failures to be compatible with NS4.
188 return;
191 nsresult rv = NS_MutateURI(uri).SetHost(aHostname).Finalize(uri);
192 if (NS_FAILED(rv)) {
193 return;
195 SetHrefAttribute(uri);
198 void Link::SetPathname(const nsACString& aPathname) {
199 nsCOMPtr<nsIURI> uri(GetURI());
200 if (!uri) {
201 // Ignore failures to be compatible with NS4.
202 return;
205 nsresult rv = NS_MutateURI(uri).SetFilePath(aPathname).Finalize(uri);
206 if (NS_FAILED(rv)) {
207 return;
209 SetHrefAttribute(uri);
212 void Link::SetSearch(const nsACString& aSearch) {
213 nsCOMPtr<nsIURI> uri(GetURI());
214 if (!uri) {
215 // Ignore failures to be compatible with NS4.
216 return;
219 nsresult rv = NS_MutateURI(uri).SetQuery(aSearch).Finalize(uri);
220 if (NS_FAILED(rv)) {
221 return;
223 SetHrefAttribute(uri);
226 void Link::SetPort(const nsACString& aPort) {
227 nsCOMPtr<nsIURI> uri(GetURI());
228 if (!uri) {
229 // Ignore failures to be compatible with NS4.
230 return;
233 // nsIURI uses -1 as default value.
234 nsresult rv;
235 int32_t port = -1;
236 if (!aPort.IsEmpty()) {
237 port = aPort.ToInteger(&rv);
238 if (NS_FAILED(rv)) {
239 return;
243 rv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
244 if (NS_FAILED(rv)) {
245 return;
247 SetHrefAttribute(uri);
250 void Link::SetHash(const nsACString& aHash) {
251 nsCOMPtr<nsIURI> uri(GetURI());
252 if (!uri) {
253 // Ignore failures to be compatible with NS4.
254 return;
257 nsresult rv = NS_MutateURI(uri).SetRef(aHash).Finalize(uri);
258 if (NS_FAILED(rv)) {
259 return;
262 SetHrefAttribute(uri);
265 void Link::GetOrigin(nsACString& aOrigin) {
266 aOrigin.Truncate();
268 nsIURI* uri = GetURI();
269 if (!uri) {
270 return;
273 nsContentUtils::GetWebExposedOriginSerialization(uri, aOrigin);
276 void Link::GetProtocol(nsACString& aProtocol) {
277 if (nsIURI* uri = GetURI()) {
278 (void)uri->GetScheme(aProtocol);
280 aProtocol.Append(':');
283 void Link::GetUsername(nsACString& aUsername) {
284 aUsername.Truncate();
286 nsIURI* uri = GetURI();
287 if (!uri) {
288 return;
291 uri->GetUsername(aUsername);
294 void Link::GetPassword(nsACString& aPassword) {
295 aPassword.Truncate();
297 nsIURI* uri = GetURI();
298 if (!uri) {
299 return;
302 uri->GetPassword(aPassword);
305 void Link::GetHost(nsACString& aHost) {
306 aHost.Truncate();
308 nsIURI* uri = GetURI();
309 if (!uri) {
310 // Do not throw! Not having a valid URI should result in an empty string.
311 return;
314 uri->GetHostPort(aHost);
317 void Link::GetHostname(nsACString& aHostname) {
318 aHostname.Truncate();
320 nsIURI* uri = GetURI();
321 if (!uri) {
322 // Do not throw! Not having a valid URI should result in an empty string.
323 return;
326 nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname);
329 void Link::GetPathname(nsACString& aPathname) {
330 aPathname.Truncate();
332 nsIURI* uri = GetURI();
333 if (!uri) {
334 // Do not throw! Not having a valid URI should result in an empty string.
335 return;
338 uri->GetFilePath(aPathname);
341 void Link::GetSearch(nsACString& aSearch) {
342 aSearch.Truncate();
344 nsIURI* uri = GetURI();
345 if (!uri) {
346 // Do not throw! Not having a valid URI or URL should result in an empty
347 // string.
348 return;
351 nsresult rv = uri->GetQuery(aSearch);
352 if (NS_SUCCEEDED(rv) && !aSearch.IsEmpty()) {
353 aSearch.Insert('?', 0);
357 void Link::GetPort(nsACString& aPort) {
358 aPort.Truncate();
360 nsIURI* uri = GetURI();
361 if (!uri) {
362 // Do not throw! Not having a valid URI should result in an empty string.
363 return;
366 int32_t port;
367 nsresult rv = uri->GetPort(&port);
368 // Note that failure to get the port from the URI is not necessarily a bad
369 // thing. Some URIs do not have a port.
370 if (NS_SUCCEEDED(rv) && port != -1) {
371 aPort.AppendInt(port, 10);
375 void Link::GetHash(nsACString& aHash) {
376 aHash.Truncate();
378 nsIURI* uri = GetURI();
379 if (!uri) {
380 // Do not throw! Not having a valid URI should result in an empty
381 // string.
382 return;
385 nsresult rv = uri->GetRef(aHash);
386 if (NS_SUCCEEDED(rv) && !aHash.IsEmpty()) {
387 aHash.Insert('#', 0);
391 void Link::BindToTree(const BindContext& aContext) {
392 if (aContext.InComposedDoc()) {
393 aContext.OwnerDoc().RegisterPendingLinkUpdate(this);
395 ResetLinkState(false);
398 void Link::ResetLinkState(bool aNotify, bool aHasHref) {
399 // If we have an href, we should register with the history.
401 // FIXME(emilio): Do we really want to allow all MathML elements to be
402 // :visited? That seems not great.
403 mNeedsRegistration = aHasHref;
405 // If we've cached the URI, reset always invalidates it.
406 Unregister();
407 mCachedURI = nullptr;
409 // Update our state back to the default; the default state for links with an
410 // href is unvisited.
411 SetLinkState(aHasHref ? State::Unvisited : State::NotLink, aNotify);
412 TriggerLinkUpdate(aNotify);
415 void Link::Unregister() {
416 // If we are not registered, we have nothing to do.
417 if (!mRegistered) {
418 return;
421 MOZ_ASSERT(mHistory);
422 MOZ_ASSERT(mCachedURI, "Should unregister before invalidating the URI");
424 // And tell History to stop tracking us.
425 if (nsCOMPtr<IHistory> history = components::History::Service()) {
426 history->UnregisterVisitedCallback(mCachedURI, this);
428 mElement->OwnerDoc()->ForgetLink(this);
429 mRegistered = false;
432 void Link::SetHrefAttribute(nsIURI* aURI) {
433 NS_ASSERTION(aURI, "Null URI is illegal!");
435 // if we change this code to not reserialize we need to do something smarter
436 // in SetProtocol because changing the protocol of an URI can change the
437 // "nature" of the nsIURL/nsIURI implementation.
438 nsAutoCString href;
439 (void)aURI->GetSpec(href);
440 (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
441 NS_ConvertUTF8toUTF16(href), true);
444 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const {
445 size_t n = 0;
447 if (nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI)) {
448 n += iface->SizeOfIncludingThis(aState.mMallocSizeOf);
451 // The following members don't need to be measured:
452 // - mElement, because it is a pointer-to-self used to avoid QIs
454 return n;
457 } // namespace mozilla::dom