summaryrefslogtreecommitdiffstats
path: root/dom/base/IDTracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/IDTracker.cpp')
-rw-r--r--dom/base/IDTracker.cpp283
1 files changed, 283 insertions, 0 deletions
diff --git a/dom/base/IDTracker.cpp b/dom/base/IDTracker.cpp
new file mode 100644
index 0000000000..4db592534d
--- /dev/null
+++ b/dom/base/IDTracker.cpp
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IDTracker.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentOrShadowRoot.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsAtom.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsIReferrerInfo.h"
+#include "nsEscape.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::dom {
+
+static Element* LookupElement(DocumentOrShadowRoot& aDocOrShadow,
+ const nsAString& aRef, bool aReferenceImage) {
+ if (aReferenceImage) {
+ return aDocOrShadow.LookupImageElement(aRef);
+ }
+ return aDocOrShadow.GetElementById(aRef);
+}
+
+static DocumentOrShadowRoot* FindTreeToWatch(nsIContent& aContent,
+ const nsAString& aID,
+ bool aReferenceImage) {
+ ShadowRoot* shadow = aContent.GetContainingShadow();
+
+ // We allow looking outside an <svg:use> shadow tree for backwards compat.
+ while (shadow && shadow->Host()->IsSVGElement(nsGkAtoms::use)) {
+ // <svg:use> shadow trees are immutable, so we can just early-out if we find
+ // our relevant element instead of having to support watching multiple
+ // trees.
+ if (LookupElement(*shadow, aID, aReferenceImage)) {
+ return shadow;
+ }
+ shadow = shadow->Host()->GetContainingShadow();
+ }
+
+ if (shadow) {
+ return shadow;
+ }
+
+ return aContent.OwnerDoc();
+}
+
+IDTracker::IDTracker() = default;
+
+IDTracker::~IDTracker() { Unlink(); }
+
+void IDTracker::ResetToURIFragmentID(nsIContent* aFromContent, nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ bool aWatch, bool aReferenceImage) {
+ MOZ_ASSERT(aFromContent,
+ "ResetToURIFragmentID() expects non-null content pointer");
+
+ Unlink();
+
+ if (!aURI) return;
+
+ nsAutoCString refPart;
+ aURI->GetRef(refPart);
+ // Unescape %-escapes in the reference. The result will be in the
+ // document charset, hopefully...
+ NS_UnescapeURL(refPart);
+
+ // Get the thing to observe changes to.
+ Document* doc = aFromContent->OwnerDoc();
+ auto encoding = doc->GetDocumentCharacterSet();
+
+ nsAutoString ref;
+ nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
+ if (NS_FAILED(rv) || ref.IsEmpty()) {
+ return;
+ }
+
+ if (aFromContent->IsInNativeAnonymousSubtree()) {
+ // This happens, for example, if aFromContent is part of the content
+ // inserted by a call to Document::InsertAnonymousContent, which we
+ // also want to handle. (It also happens for other native anonymous content
+ // etc.)
+ Element* anonRoot =
+ doc->GetAnonRootIfInAnonymousContentContainer(aFromContent);
+ if (anonRoot) {
+ mElement = nsContentUtils::MatchElementId(anonRoot, ref);
+ // We don't have watching working yet for anonymous content, so bail out
+ // here.
+ return;
+ }
+ }
+
+ bool isEqualExceptRef;
+ rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef);
+ DocumentOrShadowRoot* docOrShadow;
+ if (NS_FAILED(rv) || !isEqualExceptRef) {
+ RefPtr<Document::ExternalResourceLoad> load;
+ doc = doc->RequestExternalResource(aURI, aReferrerInfo, aFromContent,
+ getter_AddRefs(load));
+ docOrShadow = doc;
+ if (!doc) {
+ if (!load || !aWatch) {
+ // Nothing will ever happen here
+ return;
+ }
+
+ DocumentLoadNotification* observer =
+ new DocumentLoadNotification(this, ref);
+ mPendingNotification = observer;
+ load->AddObserver(observer);
+ // Keep going so we set up our watching stuff a bit
+ }
+ } else {
+ docOrShadow = FindTreeToWatch(*aFromContent, ref, aReferenceImage);
+ }
+
+ if (aWatch) {
+ mWatchID = NS_Atomize(ref);
+ }
+
+ mReferencingImage = aReferenceImage;
+ HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, ref);
+}
+
+void IDTracker::ResetWithLocalRef(Element& aFrom, const nsAString& aLocalRef,
+ bool aWatch) {
+ MOZ_ASSERT(nsContentUtils::IsLocalRefURL(aLocalRef));
+
+ nsAutoCString ref;
+ if (!AppendUTF16toUTF8(Substring(aLocalRef, 1), ref, mozilla::fallible)) {
+ Unlink();
+ return;
+ }
+ NS_UnescapeURL(ref);
+ RefPtr<nsAtom> idAtom = NS_Atomize(ref);
+ ResetWithID(aFrom, idAtom, aWatch);
+}
+
+void IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
+ MOZ_ASSERT(aID);
+
+ Unlink();
+
+ if (aID->IsEmpty()) {
+ return;
+ }
+
+ if (aWatch) {
+ mWatchID = aID;
+ }
+
+ mReferencingImage = false;
+
+ nsDependentAtomString str(aID);
+ DocumentOrShadowRoot* docOrShadow =
+ FindTreeToWatch(aFrom, str, /* aReferenceImage = */ false);
+ HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, str);
+}
+
+void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot* aDocOrShadow,
+ bool aWatch, const nsString& aRef) {
+ if (aWatch) {
+ mWatchDocumentOrShadowRoot = nullptr;
+ if (aDocOrShadow) {
+ mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
+ mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ return;
+ }
+
+ if (!aDocOrShadow) {
+ return;
+ }
+
+ if (Element* e = LookupElement(*aDocOrShadow, aRef, mReferencingImage)) {
+ mElement = e;
+ }
+}
+
+void IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
+ aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
+ aCB->NoteXPCOMChild(mElement);
+}
+
+void IDTracker::Unlink() {
+ if (mWatchID) {
+ if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
+ docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ }
+ if (mPendingNotification) {
+ mPendingNotification->Clear();
+ mPendingNotification = nullptr;
+ }
+ mWatchDocumentOrShadowRoot = nullptr;
+ mWatchID = nullptr;
+ mElement = nullptr;
+ mReferencingImage = false;
+}
+
+void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
+
+bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
+ void* aData) {
+ IDTracker* p = static_cast<IDTracker*>(aData);
+ if (p->mPendingNotification) {
+ p->mPendingNotification->SetTo(aNewElement);
+ } else {
+ NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!");
+ ChangeNotification* watcher =
+ new ChangeNotification(p, aOldElement, aNewElement);
+ p->mPendingNotification = watcher;
+ nsContentUtils::AddScriptRunner(watcher);
+ }
+ bool keepTracking = p->IsPersistent();
+ if (!keepTracking) {
+ p->mWatchDocumentOrShadowRoot = nullptr;
+ p->mWatchID = nullptr;
+ }
+ return keepTracking;
+}
+
+IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
+ Element* aFrom, Element* aTo)
+ : mozilla::Runnable("IDTracker::ChangeNotification"),
+ Notification(aTarget),
+ mFrom(aFrom),
+ mTo(aTo) {}
+
+IDTracker::ChangeNotification::~ChangeNotification() = default;
+
+void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
+
+void IDTracker::ChangeNotification::Clear() {
+ Notification::Clear();
+ mFrom = nullptr;
+ mTo = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
+NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
+
+NS_IMETHODIMP
+IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
+ "Unexpected topic");
+ if (mTarget) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
+ mTarget->mPendingNotification = nullptr;
+ NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
+ // If we got here, that means we had Reset*() called with
+ // aWatch == true. So keep watching if IsPersistent().
+ mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
+ mTarget->ElementChanged(nullptr, mTarget->mElement);
+ }
+ return NS_OK;
+}
+
+DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
+ if (!mWatchDocumentOrShadowRoot) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
+ mWatchDocumentOrShadowRoot->IsShadowRoot());
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
+ return shadow;
+ }
+ return mWatchDocumentOrShadowRoot->AsDocument();
+}
+
+} // namespace mozilla::dom