diff options
Diffstat (limited to 'dom/base/nsReferencedElement.cpp')
-rw-r--r-- | dom/base/nsReferencedElement.cpp | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/dom/base/nsReferencedElement.cpp b/dom/base/nsReferencedElement.cpp new file mode 100644 index 000000000..4d708eb16 --- /dev/null +++ b/dom/base/nsReferencedElement.cpp @@ -0,0 +1,247 @@ +/* -*- 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 "nsReferencedElement.h" +#include "nsContentUtils.h" +#include "nsIURI.h" +#include "nsBindingManager.h" +#include "nsEscape.h" +#include "nsXBLPrototypeBinding.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsCycleCollectionParticipant.h" + +void +nsReferencedElement::Reset(nsIContent* aFromContent, nsIURI* aURI, + bool aWatch, bool aReferenceImage) +{ + MOZ_ASSERT(aFromContent, "Reset() 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 + // origin charset of the URL, hopefully... + NS_UnescapeURL(refPart); + + nsAutoCString charset; + aURI->GetOriginCharset(charset); + nsAutoString ref; + nsresult rv = nsContentUtils::ConvertStringFromEncoding(charset, + refPart, + ref); + if (NS_FAILED(rv) || ref.IsEmpty()) { + return; + } + + // Get the current document + nsIDocument *doc = aFromContent->OwnerDoc(); + if (!doc) + return; + + nsIContent* bindingParent = aFromContent->GetBindingParent(); + if (bindingParent) { + nsXBLBinding* binding = bindingParent->GetXBLBinding(); + if (!binding) { + // This happens, for example, if aFromContent is part of the content + // inserted by a call to nsIDocument::InsertAnonymousContent, which we + // also want to handle. (It also happens for <use>'s 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; + } + } else { + bool isEqualExceptRef; + rv = aURI->EqualsExceptRef(binding->PrototypeBinding()->DocURI(), + &isEqualExceptRef); + if (NS_SUCCEEDED(rv) && isEqualExceptRef) { + // XXX sXBL/XBL2 issue + // Our content is an anonymous XBL element from a binding inside the + // same document that the referenced URI points to. In order to avoid + // the risk of ID collisions we restrict ourselves to anonymous + // elements from this binding; specifically, URIs that are relative to + // the binding document should resolve to the copy of the target + // element that has been inserted into the bound document. + // If the URI points to a different document we don't need this + // restriction. + nsINodeList* anonymousChildren = + doc->BindingManager()->GetAnonymousNodesFor(bindingParent); + + if (anonymousChildren) { + uint32_t length; + anonymousChildren->GetLength(&length); + for (uint32_t i = 0; i < length && !mElement; ++i) { + mElement = + nsContentUtils::MatchElementId(anonymousChildren->Item(i), ref); + } + } + + // We don't have watching working yet for XBL, so bail out here. + return; + } + } + } + + bool isEqualExceptRef; + rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef); + if (NS_FAILED(rv) || !isEqualExceptRef) { + RefPtr<nsIDocument::ExternalResourceLoad> load; + doc = doc->RequestExternalResource(aURI, aFromContent, + getter_AddRefs(load)); + if (!doc) { + if (!load || !aWatch) { + // Nothing will ever happen here + return; + } + + DocumentLoadNotification* observer = + new DocumentLoadNotification(this, ref); + mPendingNotification = observer; + if (observer) { + load->AddObserver(observer); + } + // Keep going so we set up our watching stuff a bit + } + } + + if (aWatch) { + nsCOMPtr<nsIAtom> atom = NS_Atomize(ref); + if (!atom) + return; + atom.swap(mWatchID); + } + + mReferencingImage = aReferenceImage; + + HaveNewDocument(doc, aWatch, ref); +} + +void +nsReferencedElement::ResetWithID(nsIContent* aFromContent, const nsString& aID, + bool aWatch) +{ + nsIDocument *doc = aFromContent->OwnerDoc(); + if (!doc) + return; + + // XXX Need to take care of XBL/XBL2 + + if (aWatch) { + nsCOMPtr<nsIAtom> atom = NS_Atomize(aID); + if (!atom) + return; + atom.swap(mWatchID); + } + + mReferencingImage = false; + + HaveNewDocument(doc, aWatch, aID); +} + +void +nsReferencedElement::HaveNewDocument(nsIDocument* aDocument, bool aWatch, + const nsString& aRef) +{ + if (aWatch) { + mWatchDocument = aDocument; + if (mWatchDocument) { + mElement = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this, + mReferencingImage); + } + return; + } + + if (!aDocument) { + return; + } + + Element *e = mReferencingImage ? aDocument->LookupImageElement(aRef) : + aDocument->GetElementById(aRef); + if (e) { + mElement = e; + } +} + +void +nsReferencedElement::Traverse(nsCycleCollectionTraversalCallback* aCB) +{ + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocument"); + aCB->NoteXPCOMChild(mWatchDocument); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mContent"); + aCB->NoteXPCOMChild(mElement); +} + +void +nsReferencedElement::Unlink() +{ + if (mWatchDocument && mWatchID) { + mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this, + mReferencingImage); + } + if (mPendingNotification) { + mPendingNotification->Clear(); + mPendingNotification = nullptr; + } + mWatchDocument = nullptr; + mWatchID = nullptr; + mElement = nullptr; + mReferencingImage = false; +} + +bool +nsReferencedElement::Observe(Element* aOldElement, + Element* aNewElement, void* aData) +{ + nsReferencedElement* p = static_cast<nsReferencedElement*>(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->mWatchDocument = nullptr; + p->mWatchID = nullptr; + } + return keepTracking; +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsReferencedElement::ChangeNotification, + mozilla::Runnable) + +NS_IMPL_ISUPPORTS(nsReferencedElement::DocumentLoadNotification, + nsIObserver) + +NS_IMETHODIMP +nsReferencedElement::DocumentLoadNotification::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0, + "Unexpected topic"); + if (mTarget) { + nsCOMPtr<nsIDocument> 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->HaveNewDocument(doc, mTarget->IsPersistent(), mRef); + mTarget->ElementChanged(nullptr, mTarget->mElement); + } + return NS_OK; +} |