Bug 1812499 [wpt PR 38184] - Simplify handling of name-to-subdir mapping in canvas...
[gecko.git] / dom / base / Link.cpp
blobc36aa6ea3efef22ed9aee35447370195b2b16846
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/SVGAElement.h"
12 #include "mozilla/dom/HTMLDNSPrefetch.h"
13 #include "mozilla/IHistory.h"
14 #include "mozilla/StaticPrefs_layout.h"
15 #include "nsLayoutUtils.h"
16 #include "nsIURL.h"
17 #include "nsIURIMutator.h"
18 #include "nsISizeOf.h"
20 #include "nsEscape.h"
21 #include "nsGkAtoms.h"
22 #include "nsString.h"
23 #include "mozAutoDocUpdate.h"
25 #include "mozilla/Components.h"
26 #include "nsAttrValueInlines.h"
27 #include "HTMLLinkElement.h"
29 namespace mozilla::dom {
31 Link::Link(Element* aElement)
32 : mElement(aElement),
33 mState(State::NotLink),
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 mState(State::NotLink),
44 mNeedsRegistration(false),
45 mRegistered(false),
46 mHasPendingLinkUpdate(false),
47 mHistory(false) {}
49 Link::~Link() {
50 // !mElement is for mock_Link.
51 MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc());
52 UnregisterFromHistory();
55 bool Link::ElementHasHref() const {
56 if (mElement->HasAttr(nsGkAtoms::href)) {
57 return true;
59 if (const auto* svg = SVGAElement::FromNode(*mElement)) {
60 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
61 // SMIL is fixed to actually mutate DOM attributes rather than faking it.
62 return svg->HasHref();
64 MOZ_ASSERT(!mElement->IsSVGElement(),
65 "What other SVG element inherits from Link?");
66 return false;
69 void Link::VisitedQueryFinished(bool aVisited) {
70 MOZ_ASSERT(mRegistered, "Setting the link state of an unregistered Link!");
72 auto newState = aVisited ? State::Visited : State::Unvisited;
74 // Set our current state as appropriate.
75 mState = newState;
77 MOZ_ASSERT(LinkState() == ElementState::VISITED ||
78 LinkState() == ElementState::UNVISITED,
79 "Unexpected state obtained from LinkState()!");
81 // Tell the element to update its visited state.
82 mElement->UpdateState(true);
84 if (StaticPrefs::layout_css_always_repaint_on_unvisited()) {
85 // Even if the state didn't actually change, we need to repaint in order for
86 // the visited state not to be observable.
87 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
88 nsChangeHint_RepaintFrame);
92 ElementState Link::LinkState() const {
93 // We are a constant method, but we are just lazily doing things and have to
94 // track that state. Cast away that constness!
96 // XXX(emilio): that's evil.
97 Link* self = const_cast<Link*>(this);
99 Element* element = self->mElement;
101 // If we have not yet registered for notifications and need to,
102 // due to our href changing, register now!
103 if (!mRegistered && mNeedsRegistration && element->IsInComposedDoc() &&
104 !HasPendingLinkUpdate()) {
105 // Only try and register once.
106 self->mNeedsRegistration = false;
108 nsCOMPtr<nsIURI> hrefURI(GetURI());
110 // Assume that we are not visited until we are told otherwise.
111 self->mState = State::Unvisited;
113 // Make sure the href attribute has a valid link (bug 23209).
114 // If we have a good href, register with History if available.
115 if (mHistory && hrefURI) {
116 if (nsCOMPtr<IHistory> history = components::History::Service()) {
117 self->mRegistered = true;
118 history->RegisterVisitedCallback(hrefURI, self);
119 // And make sure we are in the document's link map.
120 element->GetComposedDoc()->AddStyleRelevantLink(self);
125 // Otherwise, return our known state.
126 if (mState == State::Visited) {
127 return ElementState::VISITED;
130 if (mState == State::Unvisited) {
131 return ElementState::UNVISITED;
134 return ElementState();
137 nsIURI* Link::GetURI() const {
138 // If we have this URI cached, use it.
139 if (mCachedURI) {
140 return mCachedURI;
143 // Otherwise obtain it.
144 Link* self = const_cast<Link*>(this);
145 Element* element = self->mElement;
146 mCachedURI = element->GetHrefURI();
148 return mCachedURI;
151 void Link::SetProtocol(const nsAString& aProtocol) {
152 nsCOMPtr<nsIURI> uri(GetURI());
153 if (!uri) {
154 // Ignore failures to be compatible with NS4.
155 return;
158 nsAString::const_iterator start, end;
159 aProtocol.BeginReading(start);
160 aProtocol.EndReading(end);
161 nsAString::const_iterator iter(start);
162 (void)FindCharInReadable(':', iter, end);
163 nsresult rv = NS_MutateURI(uri)
164 .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
165 .Finalize(uri);
166 if (NS_FAILED(rv)) {
167 return;
170 SetHrefAttribute(uri);
173 void Link::SetPassword(const nsAString& aPassword) {
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 .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
182 .Finalize(uri);
183 if (NS_SUCCEEDED(rv)) {
184 SetHrefAttribute(uri);
188 void Link::SetUsername(const nsAString& aUsername) {
189 nsCOMPtr<nsIURI> uri(GetURI());
190 if (!uri) {
191 // Ignore failures to be compatible with NS4.
192 return;
195 nsresult rv = NS_MutateURI(uri)
196 .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
197 .Finalize(uri);
198 if (NS_SUCCEEDED(rv)) {
199 SetHrefAttribute(uri);
203 void Link::SetHost(const nsAString& aHost) {
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).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri);
212 if (NS_FAILED(rv)) {
213 return;
215 SetHrefAttribute(uri);
218 void Link::SetHostname(const nsAString& aHostname) {
219 nsCOMPtr<nsIURI> uri(GetURI());
220 if (!uri) {
221 // Ignore failures to be compatible with NS4.
222 return;
225 nsresult rv =
226 NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri);
227 if (NS_FAILED(rv)) {
228 return;
230 SetHrefAttribute(uri);
233 void Link::SetPathname(const nsAString& aPathname) {
234 nsCOMPtr<nsIURI> uri(GetURI());
235 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
236 if (!url) {
237 // Ignore failures to be compatible with NS4.
238 return;
241 nsresult rv = NS_MutateURI(uri)
242 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
243 .Finalize(uri);
244 if (NS_FAILED(rv)) {
245 return;
247 SetHrefAttribute(uri);
250 void Link::SetSearch(const nsAString& aSearch) {
251 nsCOMPtr<nsIURI> uri(GetURI());
252 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
253 if (!url) {
254 // Ignore failures to be compatible with NS4.
255 return;
258 auto encoding = mElement->OwnerDoc()->GetDocumentCharacterSet();
259 nsresult rv =
260 NS_MutateURI(uri)
261 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch), encoding)
262 .Finalize(uri);
263 if (NS_FAILED(rv)) {
264 return;
266 SetHrefAttribute(uri);
269 void Link::SetPort(const nsAString& aPort) {
270 nsCOMPtr<nsIURI> uri(GetURI());
271 if (!uri) {
272 // Ignore failures to be compatible with NS4.
273 return;
276 nsresult rv;
277 nsAutoString portStr(aPort);
279 // nsIURI uses -1 as default value.
280 int32_t port = -1;
281 if (!aPort.IsEmpty()) {
282 port = portStr.ToInteger(&rv);
283 if (NS_FAILED(rv)) {
284 return;
288 rv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
289 if (NS_FAILED(rv)) {
290 return;
292 SetHrefAttribute(uri);
295 void Link::SetHash(const nsAString& aHash) {
296 nsCOMPtr<nsIURI> uri(GetURI());
297 if (!uri) {
298 // Ignore failures to be compatible with NS4.
299 return;
302 nsresult rv =
303 NS_MutateURI(uri).SetRef(NS_ConvertUTF16toUTF8(aHash)).Finalize(uri);
304 if (NS_FAILED(rv)) {
305 return;
308 SetHrefAttribute(uri);
311 void Link::GetOrigin(nsAString& aOrigin) {
312 aOrigin.Truncate();
314 nsCOMPtr<nsIURI> uri(GetURI());
315 if (!uri) {
316 return;
319 nsString origin;
320 nsContentUtils::GetUTFOrigin(uri, origin);
321 aOrigin.Assign(origin);
324 void Link::GetProtocol(nsAString& _protocol) {
325 nsCOMPtr<nsIURI> uri(GetURI());
326 if (uri) {
327 nsAutoCString scheme;
328 (void)uri->GetScheme(scheme);
329 CopyASCIItoUTF16(scheme, _protocol);
331 _protocol.Append(char16_t(':'));
334 void Link::GetUsername(nsAString& aUsername) {
335 aUsername.Truncate();
337 nsCOMPtr<nsIURI> uri(GetURI());
338 if (!uri) {
339 return;
342 nsAutoCString username;
343 uri->GetUsername(username);
344 CopyASCIItoUTF16(username, aUsername);
347 void Link::GetPassword(nsAString& aPassword) {
348 aPassword.Truncate();
350 nsCOMPtr<nsIURI> uri(GetURI());
351 if (!uri) {
352 return;
355 nsAutoCString password;
356 uri->GetPassword(password);
357 CopyASCIItoUTF16(password, aPassword);
360 void Link::GetHost(nsAString& _host) {
361 _host.Truncate();
363 nsCOMPtr<nsIURI> uri(GetURI());
364 if (!uri) {
365 // Do not throw! Not having a valid URI should result in an empty string.
366 return;
369 nsAutoCString hostport;
370 nsresult rv = uri->GetHostPort(hostport);
371 if (NS_SUCCEEDED(rv)) {
372 CopyUTF8toUTF16(hostport, _host);
376 void Link::GetHostname(nsAString& _hostname) {
377 _hostname.Truncate();
379 nsCOMPtr<nsIURI> uri(GetURI());
380 if (!uri) {
381 // Do not throw! Not having a valid URI should result in an empty string.
382 return;
385 nsContentUtils::GetHostOrIPv6WithBrackets(uri, _hostname);
388 void Link::GetPathname(nsAString& _pathname) {
389 _pathname.Truncate();
391 nsCOMPtr<nsIURI> uri(GetURI());
392 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
393 if (!url) {
394 // Do not throw! Not having a valid URI or URL should result in an empty
395 // string.
396 return;
399 nsAutoCString file;
400 nsresult rv = url->GetFilePath(file);
401 if (NS_SUCCEEDED(rv)) {
402 CopyUTF8toUTF16(file, _pathname);
406 void Link::GetSearch(nsAString& _search) {
407 _search.Truncate();
409 nsCOMPtr<nsIURI> uri(GetURI());
410 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
411 if (!url) {
412 // Do not throw! Not having a valid URI or URL should result in an empty
413 // string.
414 return;
417 nsAutoCString search;
418 nsresult rv = url->GetQuery(search);
419 if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
420 _search.Assign(u'?');
421 AppendUTF8toUTF16(search, _search);
425 void Link::GetPort(nsAString& _port) {
426 _port.Truncate();
428 nsCOMPtr<nsIURI> uri(GetURI());
429 if (!uri) {
430 // Do not throw! Not having a valid URI should result in an empty string.
431 return;
434 int32_t port;
435 nsresult rv = uri->GetPort(&port);
436 // Note that failure to get the port from the URI is not necessarily a bad
437 // thing. Some URIs do not have a port.
438 if (NS_SUCCEEDED(rv) && port != -1) {
439 nsAutoString portStr;
440 portStr.AppendInt(port, 10);
441 _port.Assign(portStr);
445 void Link::GetHash(nsAString& _hash) {
446 _hash.Truncate();
448 nsCOMPtr<nsIURI> uri(GetURI());
449 if (!uri) {
450 // Do not throw! Not having a valid URI should result in an empty
451 // string.
452 return;
455 nsAutoCString ref;
456 nsresult rv = uri->GetRef(ref);
457 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
458 _hash.Assign(char16_t('#'));
459 AppendUTF8toUTF16(ref, _hash);
463 void Link::ResetLinkState(bool aNotify, bool aHasHref) {
464 // If !mNeedsRegstration, then either we've never registered, or we're
465 // currently registered; in either case, we should remove ourself
466 // from the doc and the history.
467 if (!mNeedsRegistration && mState != State::NotLink) {
468 Document* doc = mElement->GetComposedDoc();
469 if (doc && (mRegistered || mState == State::Visited)) {
470 // Tell the document to forget about this link if we've registered
471 // with it before.
472 doc->ForgetLink(this);
476 // If we have an href, we should register with the history.
478 // FIXME(emilio): Do we really want to allow all MathML elements to be
479 // :visited? That seems not great.
480 mNeedsRegistration = aHasHref;
482 // If we've cached the URI, reset always invalidates it.
483 UnregisterFromHistory();
484 mCachedURI = nullptr;
486 // Update our state back to the default; the default state for links with an
487 // href is unvisited.
488 mState = aHasHref ? State::Unvisited : State::NotLink;
490 // We have to be very careful here: if aNotify is false we do NOT
491 // want to call UpdateState, because that will call into LinkState()
492 // and try to start off loads, etc. But ResetLinkState is called
493 // with aNotify false when things are in inconsistent states, so
494 // we'll get confused in that situation. Instead, just silently
495 // update the link state on mElement. Since we might have set the
496 // link state to unvisited, make sure to update with that state if
497 // required.
498 if (aNotify) {
499 mElement->UpdateState(aNotify);
500 } else {
501 if (mState == State::Unvisited) {
502 mElement->UpdateLinkState(ElementState::UNVISITED);
503 } else {
504 mElement->UpdateLinkState(ElementState());
509 void Link::UnregisterFromHistory() {
510 // If we are not registered, we have nothing to do.
511 if (!mRegistered) {
512 return;
515 // And tell History to stop tracking us.
516 if (mHistory && mCachedURI) {
517 if (nsCOMPtr<IHistory> history = components::History::Service()) {
518 history->UnregisterVisitedCallback(mCachedURI, this);
519 mRegistered = false;
524 void Link::SetHrefAttribute(nsIURI* aURI) {
525 NS_ASSERTION(aURI, "Null URI is illegal!");
527 // if we change this code to not reserialize we need to do something smarter
528 // in SetProtocol because changing the protocol of an URI can change the
529 // "nature" of the nsIURL/nsIURI implementation.
530 nsAutoCString href;
531 (void)aURI->GetSpec(href);
532 (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
533 NS_ConvertUTF8toUTF16(href), true);
536 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const {
537 size_t n = 0;
539 if (nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI)) {
540 n += iface->SizeOfIncludingThis(aState.mMallocSizeOf);
543 // The following members don't need to be measured:
544 // - mElement, because it is a pointer-to-self used to avoid QIs
546 return n;
549 } // namespace mozilla::dom