tor-browser

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

IDTracker.cpp (10390B)


      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 "IDTracker.h"
      8 
      9 #include "mozilla/Encoding.h"
     10 #include "mozilla/dom/Document.h"
     11 #include "mozilla/dom/DocumentOrShadowRoot.h"
     12 #include "mozilla/dom/SVGUseElement.h"
     13 #include "mozilla/dom/ShadowRoot.h"
     14 #include "nsAtom.h"
     15 #include "nsContentUtils.h"
     16 #include "nsCycleCollectionParticipant.h"
     17 #include "nsEscape.h"
     18 #include "nsIReferrerInfo.h"
     19 #include "nsIURI.h"
     20 #include "nsStringFwd.h"
     21 
     22 namespace mozilla::dom {
     23 
     24 static Element* LookupElement(DocumentOrShadowRoot& aDocOrShadow, nsAtom* aRef,
     25                              bool aReferenceImage) {
     26  if (aReferenceImage) {
     27    return aDocOrShadow.LookupImageElement(aRef);
     28  }
     29  return aDocOrShadow.GetElementById(aRef);
     30 }
     31 
     32 static DocumentOrShadowRoot* FindTreeToWatch(nsIContent& aContent, nsAtom* aID,
     33                                             bool aReferenceImage) {
     34  ShadowRoot* shadow = aContent.GetContainingShadow();
     35 
     36  // We allow looking outside an <svg:use> shadow tree for backwards compat.
     37  while (shadow && shadow->Host()->IsSVGElement(nsGkAtoms::use)) {
     38    // <svg:use> shadow trees are immutable, so we can just early-out if we find
     39    // our relevant element instead of having to support watching multiple
     40    // trees.
     41    if (LookupElement(*shadow, aID, aReferenceImage)) {
     42      return shadow;
     43    }
     44    shadow = shadow->Host()->GetContainingShadow();
     45  }
     46 
     47  if (shadow) {
     48    return shadow;
     49  }
     50 
     51  return aContent.OwnerDoc();
     52 }
     53 
     54 IDTracker::IDTracker() = default;
     55 
     56 IDTracker::~IDTracker() { Unlink(); }
     57 
     58 void IDTracker::ResetToURIWithFragmentID(Element& aFrom, nsIURI* aURI,
     59                                         nsIReferrerInfo* aReferrerInfo,
     60                                         bool aReferenceImage) {
     61  Unlink();
     62 
     63  if (!aURI) {
     64    return;
     65  }
     66 
     67  nsAutoCString refPart;
     68  aURI->GetRef(refPart);
     69  // Unescape %-escapes in the reference. The result will be in the
     70  // document charset, hopefully...
     71  NS_UnescapeURL(refPart);
     72 
     73  // Get the thing to observe changes to.
     74  Document* doc = aFrom.OwnerDoc();
     75  auto encoding = doc->GetDocumentCharacterSet();
     76 
     77  nsAutoString ref;
     78  nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
     79  if (NS_FAILED(rv) || ref.IsEmpty()) {
     80    return;
     81  }
     82 
     83  bool isEqualExceptRef;
     84  rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef);
     85  RefPtr<nsAtom> refAtom = NS_Atomize(ref);
     86  if (NS_FAILED(rv) || !isEqualExceptRef) {
     87    return ResetToExternalResource(aURI, aReferrerInfo, refAtom, aFrom,
     88                                   aReferenceImage);
     89  }
     90  ResetToID(aFrom, refAtom, aReferenceImage);
     91 }
     92 
     93 void IDTracker::ResetToExternalResource(nsIURI* aURI,
     94                                        nsIReferrerInfo* aReferrerInfo,
     95                                        nsAtom* aRef, Element& aFrom,
     96                                        bool aReferenceImage) {
     97  Unlink();
     98 
     99  RefPtr<Document::ExternalResourceLoad> load;
    100  Document* resourceDoc = aFrom.OwnerDoc()->RequestExternalResource(
    101      aURI, aReferrerInfo, &aFrom, getter_AddRefs(load));
    102  if (!resourceDoc) {
    103    if (!load) {
    104      // Nothing will ever happen here
    105      return;
    106    }
    107    auto* observer = new DocumentLoadNotification(this, aRef);
    108    mPendingNotification = observer;
    109    load->AddObserver(observer);
    110  }
    111 
    112  mWatchID = aRef;
    113  mReferencingImage = aReferenceImage;
    114  HaveNewDocumentOrShadowRoot(resourceDoc, /* aWatch = */ true, mWatchID);
    115 }
    116 
    117 static nsIURI* GetExternalResourceURIIfNeeded(nsIURI* aBaseURI,
    118                                              Element& aFrom) {
    119  if (!aBaseURI) {
    120    // We don't know where this URI came from.
    121    return nullptr;
    122  }
    123  SVGUseElement* use = aFrom.GetContainingSVGUseShadowHost();
    124  if (!use) {
    125    return nullptr;
    126  }
    127  Document* doc = use->GetSourceDocument();
    128  if (!doc || doc == aFrom.OwnerDoc()) {
    129    return nullptr;
    130  }
    131  nsIURI* originalURI = doc->GetDocumentURI();
    132  if (!originalURI) {
    133    return nullptr;
    134  }
    135  // Content is in a shadow tree of an external resource.  If this URL was
    136  // specified in the subtree referenced by the <use> element, then we want the
    137  // fragment-only URL to resolve to an element from the resource document.
    138  // Otherwise, the URL was specified somewhere in the document with the <use>
    139  // element, and we want the fragment-only URL to resolve to an element in that
    140  // document.
    141  bool equals = false;
    142  if (NS_FAILED(aBaseURI->EqualsExceptRef(originalURI, &equals)) || !equals) {
    143    return nullptr;
    144  }
    145  return originalURI;
    146 }
    147 
    148 void IDTracker::ResetToLocalFragmentID(Element& aFrom,
    149                                       const nsAString& aLocalRef,
    150                                       nsIURI* aBaseURI,
    151                                       nsIReferrerInfo* aReferrerInfo,
    152                                       bool aReferenceImage) {
    153  MOZ_ASSERT(nsContentUtils::IsLocalRefURL(aLocalRef));
    154 
    155  auto ref = Substring(aLocalRef, 1);
    156  if (ref.IsEmpty()) {
    157    Unlink();
    158    return;
    159  }
    160 
    161  nsAutoCString utf8Ref;
    162  if (!AppendUTF16toUTF8(ref, utf8Ref, mozilla::fallible)) {
    163    Unlink();
    164    return;
    165  }
    166 
    167  // Only unescape ASCII characters; if we were to unescape arbitrary bytes,
    168  // we'd potentially end up with invalid UTF-8.
    169  nsAutoCString unescaped;
    170  bool appended;
    171  if (NS_FAILED(NS_UnescapeURL(utf8Ref.BeginReading(), utf8Ref.Length(),
    172                               esc_OnlyASCII | esc_AlwaysCopy, unescaped,
    173                               appended, mozilla::fallible))) {
    174    Unlink();
    175    return;
    176  }
    177 
    178  RefPtr<nsAtom> refAtom = NS_Atomize(unescaped);
    179  if (nsIURI* resourceUri = GetExternalResourceURIIfNeeded(aBaseURI, aFrom)) {
    180    return ResetToExternalResource(resourceUri, aReferrerInfo, refAtom, aFrom,
    181                                   aReferenceImage);
    182  }
    183  ResetToID(aFrom, refAtom, aReferenceImage);
    184 }
    185 
    186 void IDTracker::ResetToID(Element& aFrom, nsAtom* aID, bool aReferenceImage) {
    187  MOZ_ASSERT(aID);
    188 
    189  Unlink();
    190 
    191  if (aID->IsEmpty()) {
    192    return;
    193  }
    194 
    195  mWatchID = aID;
    196  mReferencingImage = aReferenceImage;
    197 
    198  DocumentOrShadowRoot* docOrShadow =
    199      FindTreeToWatch(aFrom, aID, aReferenceImage);
    200  HaveNewDocumentOrShadowRoot(docOrShadow, /*aWatch*/ true, aID);
    201 }
    202 
    203 void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot* aDocOrShadow,
    204                                            bool aWatch, nsAtom* aID) {
    205  if (aWatch) {
    206    mWatchDocumentOrShadowRoot = nullptr;
    207    if (aDocOrShadow) {
    208      mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
    209      mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
    210                                                   mReferencingImage);
    211    }
    212    return;
    213  }
    214 
    215  if (!aDocOrShadow) {
    216    return;
    217  }
    218 
    219  if (Element* e = LookupElement(*aDocOrShadow, aID, mReferencingImage)) {
    220    mElement = e;
    221  }
    222 }
    223 
    224 void IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB) {
    225  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
    226  aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
    227  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
    228  aCB->NoteXPCOMChild(mElement);
    229 }
    230 
    231 void IDTracker::Unlink() {
    232  if (mWatchID) {
    233    if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
    234      docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
    235                                          mReferencingImage);
    236    }
    237  }
    238  if (mPendingNotification) {
    239    mPendingNotification->Clear();
    240    mPendingNotification = nullptr;
    241  }
    242  mWatchDocumentOrShadowRoot = nullptr;
    243  mWatchID = nullptr;
    244  mElement = nullptr;
    245  mReferencingImage = false;
    246 }
    247 
    248 void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
    249 
    250 bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
    251                        void* aData) {
    252  IDTracker* p = static_cast<IDTracker*>(aData);
    253  if (p->mPendingNotification) {
    254    p->mPendingNotification->SetTo(aNewElement);
    255  } else {
    256    NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!");
    257    ChangeNotification* watcher =
    258        new ChangeNotification(p, aOldElement, aNewElement);
    259    p->mPendingNotification = watcher;
    260    nsContentUtils::AddScriptRunner(watcher);
    261  }
    262  bool keepTracking = p->IsPersistent();
    263  if (!keepTracking) {
    264    p->mWatchDocumentOrShadowRoot = nullptr;
    265    p->mWatchID = nullptr;
    266  }
    267  return keepTracking;
    268 }
    269 
    270 IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
    271                                                  Element* aFrom, Element* aTo)
    272    : mozilla::Runnable("IDTracker::ChangeNotification"),
    273      Notification(aTarget),
    274      mFrom(aFrom),
    275      mTo(aTo) {}
    276 
    277 IDTracker::ChangeNotification::~ChangeNotification() = default;
    278 
    279 void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
    280 
    281 void IDTracker::ChangeNotification::Clear() {
    282  Notification::Clear();
    283  mFrom = nullptr;
    284  mTo = nullptr;
    285 }
    286 
    287 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
    288 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
    289 
    290 NS_IMETHODIMP
    291 IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
    292                                             const char* aTopic,
    293                                             const char16_t* aData) {
    294  NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
    295               "Unexpected topic");
    296  if (mTarget) {
    297    nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
    298    mTarget->mPendingNotification = nullptr;
    299    NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
    300    // Keep watching if IsPersistent().
    301    mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
    302    mTarget->ElementChanged(nullptr, mTarget->mElement);
    303  }
    304  return NS_OK;
    305 }
    306 
    307 DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
    308  if (!mWatchDocumentOrShadowRoot) {
    309    return nullptr;
    310  }
    311  MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
    312             mWatchDocumentOrShadowRoot->IsShadowRoot());
    313  if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
    314    return shadow;
    315  }
    316  return mWatchDocumentOrShadowRoot->AsDocument();
    317 }
    318 
    319 }  // namespace mozilla::dom