tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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