Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / base / Link.cpp
blobaf8e4d38def2a1a4766bcaf1e7f048f0b944a501
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/MemoryReporting.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/BindContext.h"
12 #include "mozilla/dom/SVGAElement.h"
13 #include "mozilla/dom/HTMLDNSPrefetch.h"
14 #include "mozilla/IHistory.h"
15 #include "mozilla/StaticPrefs_layout.h"
16 #include "nsLayoutUtils.h"
17 #include "nsIURL.h"
18 #include "nsIURIMutator.h"
19 #include "nsISizeOf.h"
21 #include "nsEscape.h"
22 #include "nsGkAtoms.h"
23 #include "nsString.h"
24 #include "mozAutoDocUpdate.h"
26 #include "mozilla/Components.h"
27 #include "nsAttrValueInlines.h"
28 #include "HTMLLinkElement.h"
30 namespace mozilla::dom {
32 Link::Link(Element* aElement)
33 : mElement(aElement),
34 mNeedsRegistration(false),
35 mRegistered(false),
36 mHasPendingLinkUpdate(false),
37 mHistory(true) {
38 MOZ_ASSERT(mElement, "Must have an element");
41 Link::Link()
42 : mElement(nullptr),
43 mNeedsRegistration(false),
44 mRegistered(false),
45 mHasPendingLinkUpdate(false),
46 mHistory(false) {}
48 Link::~Link() {
49 // !mElement is for mock_Link.
50 MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc());
51 Unregister();
54 bool Link::ElementHasHref() const {
55 if (mElement->HasAttr(nsGkAtoms::href)) {
56 return true;
58 if (const auto* svg = SVGAElement::FromNode(*mElement)) {
59 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
60 // SMIL is fixed to actually mutate DOM attributes rather than faking it.
61 return svg->HasHref();
63 MOZ_ASSERT(!mElement->IsSVGElement(),
64 "What other SVG element inherits from Link?");
65 return false;
68 void Link::SetLinkState(State aState, bool aNotify) {
69 Element::AutoStateChangeNotifier notifier(*mElement, aNotify);
70 switch (aState) {
71 case State::Visited:
72 mElement->AddStatesSilently(ElementState::VISITED);
73 mElement->RemoveStatesSilently(ElementState::UNVISITED);
74 break;
75 case State::Unvisited:
76 mElement->AddStatesSilently(ElementState::UNVISITED);
77 mElement->RemoveStatesSilently(ElementState::VISITED);
78 break;
79 case State::NotLink:
80 mElement->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED);
81 break;
85 void Link::TriggerLinkUpdate(bool aNotify) {
86 if (mRegistered || !mNeedsRegistration || mHasPendingLinkUpdate ||
87 !mElement->IsInComposedDoc()) {
88 return;
91 // Only try and register once.
92 mNeedsRegistration = false;
94 nsCOMPtr<nsIURI> hrefURI = GetURI();
96 // Assume that we are not visited until we are told otherwise.
97 SetLinkState(State::Unvisited, aNotify);
99 // Make sure the href attribute has a valid link (bug 23209).
100 // If we have a good href, register with History if available.
101 if (mHistory && hrefURI) {
102 if (nsCOMPtr<IHistory> history = components::History::Service()) {
103 mRegistered = true;
104 history->RegisterVisitedCallback(hrefURI, this);
105 // And make sure we are in the document's link map.
106 mElement->GetComposedDoc()->AddStyleRelevantLink(this);
111 void Link::VisitedQueryFinished(bool aVisited) {
112 MOZ_ASSERT(mRegistered, "Setting the link state of an unregistered Link!");
114 SetLinkState(aVisited ? State::Visited : State::Unvisited,
115 /* aNotify = */ true);
116 // Even if the state didn't actually change, we need to repaint in order for
117 // the visited state not to be observable.
118 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
119 nsChangeHint_RepaintFrame);
122 nsIURI* Link::GetURI() const {
123 // If we have this URI cached, use it.
124 if (mCachedURI) {
125 return mCachedURI;
128 // Otherwise obtain it.
129 Link* self = const_cast<Link*>(this);
130 Element* element = self->mElement;
131 mCachedURI = element->GetHrefURI();
133 return mCachedURI;
136 void Link::SetProtocol(const nsAString& aProtocol) {
137 nsCOMPtr<nsIURI> uri(GetURI());
138 if (!uri) {
139 // Ignore failures to be compatible with NS4.
140 return;
143 nsAString::const_iterator start, end;
144 aProtocol.BeginReading(start);
145 aProtocol.EndReading(end);
146 nsAString::const_iterator iter(start);
147 (void)FindCharInReadable(':', iter, end);
148 nsresult rv = NS_MutateURI(uri)
149 .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
150 .Finalize(uri);
151 if (NS_FAILED(rv)) {
152 return;
155 SetHrefAttribute(uri);
158 void Link::SetPassword(const nsAString& aPassword) {
159 nsCOMPtr<nsIURI> uri(GetURI());
160 if (!uri) {
161 // Ignore failures to be compatible with NS4.
162 return;
165 nsresult rv = NS_MutateURI(uri)
166 .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
167 .Finalize(uri);
168 if (NS_SUCCEEDED(rv)) {
169 SetHrefAttribute(uri);
173 void Link::SetUsername(const nsAString& aUsername) {
174 nsCOMPtr<nsIURI> uri(GetURI());
175 if (!uri) {
176 // Ignore failures to be compatible with NS4.
177 return;
180 nsresult rv = NS_MutateURI(uri)
181 .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
182 .Finalize(uri);
183 if (NS_SUCCEEDED(rv)) {
184 SetHrefAttribute(uri);
188 void Link::SetHost(const nsAString& aHost) {
189 nsCOMPtr<nsIURI> uri(GetURI());
190 if (!uri) {
191 // Ignore failures to be compatible with NS4.
192 return;
195 nsresult rv =
196 NS_MutateURI(uri).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri);
197 if (NS_FAILED(rv)) {
198 return;
200 SetHrefAttribute(uri);
203 void Link::SetHostname(const nsAString& aHostname) {
204 nsCOMPtr<nsIURI> uri(GetURI());
205 if (!uri) {
206 // Ignore failures to be compatible with NS4.
207 return;
210 nsresult rv =
211 NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri);
212 if (NS_FAILED(rv)) {
213 return;
215 SetHrefAttribute(uri);
218 void Link::SetPathname(const nsAString& aPathname) {
219 nsCOMPtr<nsIURI> uri(GetURI());
220 if (!uri) {
221 // Ignore failures to be compatible with NS4.
222 return;
225 nsresult rv = NS_MutateURI(uri)
226 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
227 .Finalize(uri);
228 if (NS_FAILED(rv)) {
229 return;
231 SetHrefAttribute(uri);
234 void Link::SetSearch(const nsAString& aSearch) {
235 nsCOMPtr<nsIURI> uri(GetURI());
236 if (!uri) {
237 // Ignore failures to be compatible with NS4.
238 return;
241 auto encoding = mElement->OwnerDoc()->GetDocumentCharacterSet();
242 nsresult rv =
243 NS_MutateURI(uri)
244 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch), encoding)
245 .Finalize(uri);
246 if (NS_FAILED(rv)) {
247 return;
249 SetHrefAttribute(uri);
252 void Link::SetPort(const nsAString& aPort) {
253 nsCOMPtr<nsIURI> uri(GetURI());
254 if (!uri) {
255 // Ignore failures to be compatible with NS4.
256 return;
259 nsresult rv;
260 nsAutoString portStr(aPort);
262 // nsIURI uses -1 as default value.
263 int32_t port = -1;
264 if (!aPort.IsEmpty()) {
265 port = portStr.ToInteger(&rv);
266 if (NS_FAILED(rv)) {
267 return;
271 rv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
272 if (NS_FAILED(rv)) {
273 return;
275 SetHrefAttribute(uri);
278 void Link::SetHash(const nsAString& aHash) {
279 nsCOMPtr<nsIURI> uri(GetURI());
280 if (!uri) {
281 // Ignore failures to be compatible with NS4.
282 return;
285 nsresult rv =
286 NS_MutateURI(uri).SetRef(NS_ConvertUTF16toUTF8(aHash)).Finalize(uri);
287 if (NS_FAILED(rv)) {
288 return;
291 SetHrefAttribute(uri);
294 void Link::GetOrigin(nsAString& aOrigin) {
295 aOrigin.Truncate();
297 nsCOMPtr<nsIURI> uri(GetURI());
298 if (!uri) {
299 return;
302 nsString origin;
303 nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
304 aOrigin.Assign(origin);
307 void Link::GetProtocol(nsAString& _protocol) {
308 nsCOMPtr<nsIURI> uri(GetURI());
309 if (uri) {
310 nsAutoCString scheme;
311 (void)uri->GetScheme(scheme);
312 CopyASCIItoUTF16(scheme, _protocol);
314 _protocol.Append(char16_t(':'));
317 void Link::GetUsername(nsAString& aUsername) {
318 aUsername.Truncate();
320 nsCOMPtr<nsIURI> uri(GetURI());
321 if (!uri) {
322 return;
325 nsAutoCString username;
326 uri->GetUsername(username);
327 CopyASCIItoUTF16(username, aUsername);
330 void Link::GetPassword(nsAString& aPassword) {
331 aPassword.Truncate();
333 nsCOMPtr<nsIURI> uri(GetURI());
334 if (!uri) {
335 return;
338 nsAutoCString password;
339 uri->GetPassword(password);
340 CopyASCIItoUTF16(password, aPassword);
343 void Link::GetHost(nsAString& _host) {
344 _host.Truncate();
346 nsCOMPtr<nsIURI> uri(GetURI());
347 if (!uri) {
348 // Do not throw! Not having a valid URI should result in an empty string.
349 return;
352 nsAutoCString hostport;
353 nsresult rv = uri->GetHostPort(hostport);
354 if (NS_SUCCEEDED(rv)) {
355 CopyUTF8toUTF16(hostport, _host);
359 void Link::GetHostname(nsAString& _hostname) {
360 _hostname.Truncate();
362 nsCOMPtr<nsIURI> uri(GetURI());
363 if (!uri) {
364 // Do not throw! Not having a valid URI should result in an empty string.
365 return;
368 nsContentUtils::GetHostOrIPv6WithBrackets(uri, _hostname);
371 void Link::GetPathname(nsAString& _pathname) {
372 _pathname.Truncate();
374 nsCOMPtr<nsIURI> uri(GetURI());
375 if (!uri) {
376 // Do not throw! Not having a valid URI should result in an empty string.
377 return;
380 nsAutoCString file;
381 nsresult rv = uri->GetFilePath(file);
382 if (NS_SUCCEEDED(rv)) {
383 CopyUTF8toUTF16(file, _pathname);
387 void Link::GetSearch(nsAString& _search) {
388 _search.Truncate();
390 nsCOMPtr<nsIURI> uri(GetURI());
391 if (!uri) {
392 // Do not throw! Not having a valid URI or URL should result in an empty
393 // string.
394 return;
397 nsAutoCString search;
398 nsresult rv = uri->GetQuery(search);
399 if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
400 _search.Assign(u'?');
401 AppendUTF8toUTF16(search, _search);
405 void Link::GetPort(nsAString& _port) {
406 _port.Truncate();
408 nsCOMPtr<nsIURI> uri(GetURI());
409 if (!uri) {
410 // Do not throw! Not having a valid URI should result in an empty string.
411 return;
414 int32_t port;
415 nsresult rv = uri->GetPort(&port);
416 // Note that failure to get the port from the URI is not necessarily a bad
417 // thing. Some URIs do not have a port.
418 if (NS_SUCCEEDED(rv) && port != -1) {
419 nsAutoString portStr;
420 portStr.AppendInt(port, 10);
421 _port.Assign(portStr);
425 void Link::GetHash(nsAString& _hash) {
426 _hash.Truncate();
428 nsCOMPtr<nsIURI> uri(GetURI());
429 if (!uri) {
430 // Do not throw! Not having a valid URI should result in an empty
431 // string.
432 return;
435 nsAutoCString ref;
436 nsresult rv = uri->GetRef(ref);
437 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
438 _hash.Assign(char16_t('#'));
439 AppendUTF8toUTF16(ref, _hash);
443 void Link::BindToTree(const BindContext& aContext) {
444 if (aContext.InComposedDoc()) {
445 aContext.OwnerDoc().RegisterPendingLinkUpdate(this);
447 ResetLinkState(false);
450 void Link::ResetLinkState(bool aNotify, bool aHasHref) {
451 // If we have an href, we should register with the history.
453 // FIXME(emilio): Do we really want to allow all MathML elements to be
454 // :visited? That seems not great.
455 mNeedsRegistration = aHasHref;
457 // If we've cached the URI, reset always invalidates it.
458 Unregister();
459 mCachedURI = nullptr;
461 // Update our state back to the default; the default state for links with an
462 // href is unvisited.
463 SetLinkState(aHasHref ? State::Unvisited : State::NotLink, aNotify);
464 TriggerLinkUpdate(aNotify);
467 void Link::Unregister() {
468 // If we are not registered, we have nothing to do.
469 if (!mRegistered) {
470 return;
473 MOZ_ASSERT(mHistory);
474 MOZ_ASSERT(mCachedURI, "Should unregister before invalidating the URI");
476 // And tell History to stop tracking us.
477 if (nsCOMPtr<IHistory> history = components::History::Service()) {
478 history->UnregisterVisitedCallback(mCachedURI, this);
480 mElement->OwnerDoc()->ForgetLink(this);
481 mRegistered = false;
484 void Link::SetHrefAttribute(nsIURI* aURI) {
485 NS_ASSERTION(aURI, "Null URI is illegal!");
487 // if we change this code to not reserialize we need to do something smarter
488 // in SetProtocol because changing the protocol of an URI can change the
489 // "nature" of the nsIURL/nsIURI implementation.
490 nsAutoCString href;
491 (void)aURI->GetSpec(href);
492 (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
493 NS_ConvertUTF8toUTF16(href), true);
496 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const {
497 size_t n = 0;
499 if (nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI)) {
500 n += iface->SizeOfIncludingThis(aState.mMallocSizeOf);
503 // The following members don't need to be measured:
504 // - mElement, because it is a pointer-to-self used to avoid QIs
506 return n;
509 } // namespace mozilla::dom