michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsReferencedElement.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIURI.h" michael@0: #include "nsBindingManager.h" michael@0: #include "nsEscape.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: michael@0: void michael@0: nsReferencedElement::Reset(nsIContent* aFromContent, nsIURI* aURI, michael@0: bool aWatch, bool aReferenceImage) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFromContent, "Reset() expects non-null content pointer"); michael@0: michael@0: Unlink(); michael@0: michael@0: if (!aURI) michael@0: return; michael@0: michael@0: nsAutoCString refPart; michael@0: aURI->GetRef(refPart); michael@0: // Unescape %-escapes in the reference. The result will be in the michael@0: // origin charset of the URL, hopefully... michael@0: NS_UnescapeURL(refPart); michael@0: michael@0: nsAutoCString charset; michael@0: aURI->GetOriginCharset(charset); michael@0: nsAutoString ref; michael@0: nsresult rv = nsContentUtils::ConvertStringFromEncoding(charset, michael@0: refPart, michael@0: ref); michael@0: if (NS_FAILED(rv)) { michael@0: // XXX Eww. If fallible malloc failed, using a conversion method that michael@0: // assumes UTF-8 and doesn't handle UTF-8 errors. michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=951082 michael@0: CopyUTF8toUTF16(refPart, ref); michael@0: } michael@0: if (ref.IsEmpty()) michael@0: return; michael@0: michael@0: // Get the current document michael@0: nsIDocument *doc = aFromContent->GetCurrentDoc(); michael@0: if (!doc) michael@0: return; michael@0: michael@0: nsIContent* bindingParent = aFromContent->GetBindingParent(); michael@0: if (bindingParent) { michael@0: nsXBLBinding* binding = bindingParent->GetXBLBinding(); michael@0: if (binding) { michael@0: bool isEqualExceptRef; michael@0: rv = aURI->EqualsExceptRef(binding->PrototypeBinding()->DocURI(), michael@0: &isEqualExceptRef); michael@0: if (NS_SUCCEEDED(rv) && isEqualExceptRef) { michael@0: // XXX sXBL/XBL2 issue michael@0: // Our content is an anonymous XBL element from a binding inside the michael@0: // same document that the referenced URI points to. In order to avoid michael@0: // the risk of ID collisions we restrict ourselves to anonymous michael@0: // elements from this binding; specifically, URIs that are relative to michael@0: // the binding document should resolve to the copy of the target michael@0: // element that has been inserted into the bound document. michael@0: // If the URI points to a different document we don't need this michael@0: // restriction. michael@0: nsINodeList* anonymousChildren = michael@0: doc->BindingManager()->GetAnonymousNodesFor(bindingParent); michael@0: michael@0: if (anonymousChildren) { michael@0: uint32_t length; michael@0: anonymousChildren->GetLength(&length); michael@0: for (uint32_t i = 0; i < length && !mElement; ++i) { michael@0: mElement = michael@0: nsContentUtils::MatchElementId(anonymousChildren->Item(i), ref); michael@0: } michael@0: } michael@0: michael@0: // We don't have watching working yet for XBL, so bail out here. michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool isEqualExceptRef; michael@0: rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef); michael@0: if (NS_FAILED(rv) || !isEqualExceptRef) { michael@0: nsRefPtr load; michael@0: doc = doc->RequestExternalResource(aURI, aFromContent, michael@0: getter_AddRefs(load)); michael@0: if (!doc) { michael@0: if (!load || !aWatch) { michael@0: // Nothing will ever happen here michael@0: return; michael@0: } michael@0: michael@0: DocumentLoadNotification* observer = michael@0: new DocumentLoadNotification(this, ref); michael@0: mPendingNotification = observer; michael@0: if (observer) { michael@0: load->AddObserver(observer); michael@0: } michael@0: // Keep going so we set up our watching stuff a bit michael@0: } michael@0: } michael@0: michael@0: if (aWatch) { michael@0: nsCOMPtr atom = do_GetAtom(ref); michael@0: if (!atom) michael@0: return; michael@0: atom.swap(mWatchID); michael@0: } michael@0: michael@0: mReferencingImage = aReferenceImage; michael@0: michael@0: HaveNewDocument(doc, aWatch, ref); michael@0: } michael@0: michael@0: void michael@0: nsReferencedElement::ResetWithID(nsIContent* aFromContent, const nsString& aID, michael@0: bool aWatch) michael@0: { michael@0: nsIDocument *doc = aFromContent->GetCurrentDoc(); michael@0: if (!doc) michael@0: return; michael@0: michael@0: // XXX Need to take care of XBL/XBL2 michael@0: michael@0: if (aWatch) { michael@0: nsCOMPtr atom = do_GetAtom(aID); michael@0: if (!atom) michael@0: return; michael@0: atom.swap(mWatchID); michael@0: } michael@0: michael@0: mReferencingImage = false; michael@0: michael@0: HaveNewDocument(doc, aWatch, aID); michael@0: } michael@0: michael@0: void michael@0: nsReferencedElement::HaveNewDocument(nsIDocument* aDocument, bool aWatch, michael@0: const nsString& aRef) michael@0: { michael@0: if (aWatch) { michael@0: mWatchDocument = aDocument; michael@0: if (mWatchDocument) { michael@0: mElement = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this, michael@0: mReferencingImage); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (!aDocument) { michael@0: return; michael@0: } michael@0: michael@0: Element *e = mReferencingImage ? aDocument->LookupImageElement(aRef) : michael@0: aDocument->GetElementById(aRef); michael@0: if (e) { michael@0: mElement = e; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsReferencedElement::Traverse(nsCycleCollectionTraversalCallback* aCB) michael@0: { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocument"); michael@0: aCB->NoteXPCOMChild(mWatchDocument); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mContent"); michael@0: aCB->NoteXPCOMChild(mElement); michael@0: } michael@0: michael@0: void michael@0: nsReferencedElement::Unlink() michael@0: { michael@0: if (mWatchDocument && mWatchID) { michael@0: mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this, michael@0: mReferencingImage); michael@0: } michael@0: if (mPendingNotification) { michael@0: mPendingNotification->Clear(); michael@0: mPendingNotification = nullptr; michael@0: } michael@0: mWatchDocument = nullptr; michael@0: mWatchID = nullptr; michael@0: mElement = nullptr; michael@0: mReferencingImage = false; michael@0: } michael@0: michael@0: bool michael@0: nsReferencedElement::Observe(Element* aOldElement, michael@0: Element* aNewElement, void* aData) michael@0: { michael@0: nsReferencedElement* p = static_cast(aData); michael@0: if (p->mPendingNotification) { michael@0: p->mPendingNotification->SetTo(aNewElement); michael@0: } else { michael@0: NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!"); michael@0: ChangeNotification* watcher = michael@0: new ChangeNotification(p, aOldElement, aNewElement); michael@0: p->mPendingNotification = watcher; michael@0: nsContentUtils::AddScriptRunner(watcher); michael@0: } michael@0: bool keepTracking = p->IsPersistent(); michael@0: if (!keepTracking) { michael@0: p->mWatchDocument = nullptr; michael@0: p->mWatchID = nullptr; michael@0: } michael@0: return keepTracking; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(nsReferencedElement::ChangeNotification, michael@0: nsRunnable) michael@0: michael@0: NS_IMPL_ISUPPORTS(nsReferencedElement::DocumentLoadNotification, michael@0: nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsReferencedElement::DocumentLoadNotification::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0, michael@0: "Unexpected topic"); michael@0: if (mTarget) { michael@0: nsCOMPtr doc = do_QueryInterface(aSubject); michael@0: mTarget->mPendingNotification = nullptr; michael@0: NS_ASSERTION(!mTarget->mElement, "Why do we have content here?"); michael@0: // If we got here, that means we had Reset() called with aWatch == michael@0: // true. So keep watching if IsPersistent(). michael@0: mTarget->HaveNewDocument(doc, mTarget->IsPersistent(), mRef); michael@0: mTarget->ElementChanged(nullptr, mTarget->mElement); michael@0: } michael@0: return NS_OK; michael@0: }