Bug 433616 part 3. Integration of externa resource documents into nsReferencedElement, plus some SVG fixups needed to handle nsReferencedElement returning an element from a different document, r+sr=roc
authorBoris Zbarsky <bzbarsky@mit.edu>
Sun, 28 Sep 2008 15:16:15 -0400
changeset 20088 68fcaa71a5f2a4a36536da2f70d9a6988dfc3be9
parent 20087 060b7352c2adc2cd68419dbd46ba79e35a3af0a1
child 20089 06169936afdbcaa84e438b0244fe072fc579ac86
push id2633
push userbzbarsky@mozilla.com
push dateMon, 06 Oct 2008 16:08:22 +0000
treeherdermozilla-central@ffbab1273403 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs433616
milestone1.9.1b1pre
Bug 433616 part 3. Integration of externa resource documents into nsReferencedElement, plus some SVG fixups needed to handle nsReferencedElement returning an element from a different document, r+sr=roc
content/base/public/nsReferencedElement.h
content/base/src/nsReferencedElement.cpp
content/svg/content/src/nsSVGUseElement.cpp
layout/svg/base/src/nsSVGUtils.cpp
--- a/content/base/public/nsReferencedElement.h
+++ b/content/base/public/nsReferencedElement.h
@@ -65,19 +65,16 @@ class nsCycleCollectionCallback;
  * Override IsPersistent to return PR_TRUE if you want to keep tracking after
  * the first change.
  */
 class nsReferencedElement {
 public:
   nsReferencedElement() {}
   ~nsReferencedElement() {
     Unlink();
-    if (mPendingNotification) {
-      mPendingNotification->Clear();
-    }
   }
 
   /**
    * Find which element, if any, is referenced.
    */
   nsIContent* get() { return mContent; }
 
   /**
@@ -109,40 +106,92 @@ protected:
     mContent = aTo;
   }
 
   /**
    * Override this to convert from a single-shot notification to
    * a persistent notification.
    */
   virtual PRBool IsPersistent() { return PR_FALSE; }
+
+  /**
+   * Set ourselves up with our new document.  Note that aDocument might be
+   * null.  Either aWatch must be false or aRef must be empty.
+   */
+  void HaveNewDocument(nsIDocument* aDocument, PRBool aWatch,
+                       const nsString& aRef);
   
 private:
   static PRBool Observe(nsIContent* aOldContent,
                         nsIContent* aNewContent, void* aData);
 
-  class Notification : public nsRunnable {
+  class Notification : public nsISupports {
   public:
-    Notification(nsReferencedElement* aTarget, nsIContent* aFrom, nsIContent* aTo)
-      : mTarget(aTarget), mFrom(aFrom), mTo(aTo) {}
+    virtual void SetTo(nsIContent* aTo) = 0;
+    virtual void Clear() { mTarget = nsnull; }
+    virtual ~Notification() {}
+  protected:
+    Notification(nsReferencedElement* aTarget)
+      : mTarget(aTarget)
+    {
+      NS_PRECONDITION(aTarget, "Must have a target");
+    }
+    nsReferencedElement* mTarget;
+  };
+
+  class ChangeNotification : public nsRunnable,
+                             public Notification
+  {
+  public:
+    ChangeNotification(nsReferencedElement* aTarget, nsIContent* aFrom, nsIContent* aTo)
+      : Notification(aTarget), mFrom(aFrom), mTo(aTo)
+    {}
+    virtual ~ChangeNotification() {}
+
+    NS_DECL_ISUPPORTS_INHERITED
     NS_IMETHOD Run() {
       if (mTarget) {
         mTarget->mPendingNotification = nsnull;
         mTarget->ContentChanged(mFrom, mTo);
       }
       return NS_OK;
     }
-    void SetTo(nsIContent* aTo) { mTo = aTo; }
-    void Clear() { mTarget = nsnull; mFrom = nsnull; mTo = nsnull; }
-  private:
-    nsReferencedElement* mTarget;
+    virtual void SetTo(nsIContent* aTo) { mTo = aTo; }
+    virtual void Clear()
+    {
+      Notification::Clear(); mFrom = nsnull; mTo = nsnull;
+    }
+  protected:
     nsCOMPtr<nsIContent> mFrom;
     nsCOMPtr<nsIContent> mTo;
   };
-  friend class Notification;
+  friend class ChangeNotification;
 
+  class DocumentLoadNotification : public Notification,
+                                   public nsIObserver
+  {
+  public:
+    DocumentLoadNotification(nsReferencedElement* aTarget,
+                             const nsString& aRef) :
+      Notification(aTarget)
+    {
+      if (!mTarget->IsPersistent()) {
+        mRef = aRef;
+      }
+    }
+    virtual ~DocumentLoadNotification() {}
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+  private:
+    virtual void SetTo(nsIContent* aTo) { }
+
+    nsString mRef;
+  };
+  friend class DocumentLoadNotification;
+  
   nsCOMPtr<nsIAtom>      mWatchID;
   nsCOMPtr<nsIDocument>  mWatchDocument;
   nsCOMPtr<nsIContent>   mContent;
   nsRefPtr<Notification> mPendingNotification;
 };
 
 #endif /*NSREFERENCEDELEMENT_H_*/
--- a/content/base/src/nsReferencedElement.cpp
+++ b/content/base/src/nsReferencedElement.cpp
@@ -123,18 +123,36 @@ nsReferencedElement::Reset(nsIContent* a
       documentURL = do_QueryInterface(binding->PrototypeBinding()->DocURI());
       isXBL = PR_TRUE;
     }
   }
   if (!documentURL)
     return;
 
   if (!EqualExceptRef(url, documentURL)) {
-    // Oops -- we don't support off-document references
-    return;
+    // Don't take the XBL codepath here, since we'll want to just
+    // normally set up our external resource document and then watch
+    // it as needed.
+    isXBL = PR_FALSE;
+    nsRefPtr<nsIDocument::ExternalResourceLoad> load;
+    doc = doc->RequestExternalResource(url, 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
+    }
   }
 
   // Get the element
   if (isXBL) {
     nsCOMPtr<nsIDOMNodeList> anonymousChildren;
     doc->BindingManager()->
       GetAnonymousNodesFor(bindingParent, getter_AddRefs(anonymousChildren));
 
@@ -145,34 +163,51 @@ nsReferencedElement::Reset(nsIContent* a
         nsCOMPtr<nsIDOMNode> node;
         anonymousChildren->Item(i, getter_AddRefs(node));
         nsCOMPtr<nsIContent> c = do_QueryInterface(node);
         if (c) {
           mContent = nsContentUtils::MatchElementId(c, ref);
         }
       }
     }
+
+    // We don't have watching working yet for XBL, so bail out here.
     return;
   }
 
   if (aWatch) {
     nsCOMPtr<nsIAtom> atom = do_GetAtom(ref);
     if (!atom)
       return;
     atom.swap(mWatchID);
-    mWatchDocument = doc;
-    mContent = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this);
+  }
+
+  HaveNewDocument(doc, aWatch, ref);
+}
+
+void
+nsReferencedElement::HaveNewDocument(nsIDocument* aDocument, PRBool aWatch,
+                                     const nsString& aRef)
+{
+  if (aWatch) {
+    mWatchDocument = aDocument;
+    if (mWatchDocument) {
+      mContent = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this);
+    }
     return;
   }
   
-  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
+  if (!aDocument) {
+    return;
+  }
+  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument);
   NS_ASSERTION(domDoc, "Content doesn't reference a dom Document");
 
   nsCOMPtr<nsIDOMElement> element;
-  rv = domDoc->GetElementById(ref, getter_AddRefs(element));
+  domDoc->GetElementById(aRef, getter_AddRefs(element));
   if (element) {
     mContent = do_QueryInterface(element);
   }
 }
 
 void
 nsReferencedElement::Traverse(nsCycleCollectionTraversalCallback* aCB)
 {
@@ -181,32 +216,62 @@ nsReferencedElement::Traverse(nsCycleCol
 }
 
 void
 nsReferencedElement::Unlink()
 {
   if (mWatchDocument && mWatchID) {
     mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this);
   }
+  if (mPendingNotification) {
+    mPendingNotification->Clear();
+  }
   mWatchDocument = nsnull;
   mWatchID = nsnull;
   mContent = nsnull;
 }
 
 PRBool
 nsReferencedElement::Observe(nsIContent* aOldContent,
                              nsIContent* aNewContent, void* aData)
 {
   nsReferencedElement* p = static_cast<nsReferencedElement*>(aData);
   if (p->mPendingNotification) {
     p->mPendingNotification->SetTo(aNewContent);
   } else {
     NS_ASSERTION(aOldContent == p->mContent, "Failed to track content!");
-    p->mPendingNotification = new Notification(p, aOldContent, aNewContent);
-    nsContentUtils::AddScriptRunner(p->mPendingNotification);
+    ChangeNotification* watcher =
+      new ChangeNotification(p, aOldContent, aNewContent);
+    p->mPendingNotification = watcher;
+    nsContentUtils::AddScriptRunner(watcher);
   }
   PRBool keepTracking = p->IsPersistent();
   if (!keepTracking) {
     p->mWatchDocument = nsnull;
     p->mWatchID = nsnull;
   }
   return keepTracking;
 }
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsReferencedElement::ChangeNotification,
+                             nsRunnable)
+
+NS_IMPL_ISUPPORTS1(nsReferencedElement::DocumentLoadNotification,
+                   nsIObserver)
+
+NS_IMETHODIMP
+nsReferencedElement::DocumentLoadNotification::Observe(nsISupports* aSubject,
+                                                       const char* aTopic,
+                                                       const PRUnichar* aData)
+{
+  NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0,
+               "Unexpected topic");
+  if (mTarget) {
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(aSubject);
+    mTarget->mPendingNotification = nsnull;
+    NS_ASSERTION(!mTarget->mContent, "Why do we have content here?");
+    // If we got here, that means we had Reset() called with aWatch ==
+    // PR_TRUE.  So keep watching if IsPersistent().
+    mTarget->HaveNewDocument(doc, mTarget->IsPersistent(), mRef);
+    mTarget->ContentChanged(nsnull, mTarget->mContent);
+  }
+  return NS_OK;
+}
--- a/content/svg/content/src/nsSVGUseElement.cpp
+++ b/content/svg/content/src/nsSVGUseElement.cpp
@@ -295,17 +295,20 @@ nsSVGUseElement::CreateAnonymousContent(
         if (useImpl && useImpl->mOriginal == mOriginal)
           return nsnull;
       }
     }
   }
 
   nsCOMPtr<nsIDOMNode> newnode;
   nsCOMArray<nsINode> unused;
-  nsNodeUtils::Clone(targetContent, PR_TRUE, nsnull, unused,
+  nsNodeInfoManager* nodeInfoManager =
+    targetContent->GetOwnerDoc() == GetOwnerDoc() ?
+      nsnull : GetOwnerDoc()->NodeInfoManager();
+  nsNodeUtils::Clone(targetContent, PR_TRUE, nodeInfoManager, unused,
                      getter_AddRefs(newnode));
 
   nsCOMPtr<nsIContent> newcontent = do_QueryInterface(newnode);
 
   if (!newcontent)
     return nsnull;
 
   nsCOMPtr<nsIDOMSVGSymbolElement> symbol     = do_QueryInterface(newcontent);
@@ -411,17 +414,18 @@ nsSVGUseElement::SyncWidthHeight(PRUint8
 
 void
 nsSVGUseElement::LookupHref()
 {
   const nsString &href = mStringAttributes[HREF].GetAnimValue();
   if (href.IsEmpty())
     return;
 
-  nsCOMPtr<nsIURI> targetURI, baseURI = GetBaseURI();
+  nsCOMPtr<nsIURI> targetURI;
+  nsCOMPtr<nsIURI> baseURI = mOriginal ? mOriginal->GetBaseURI() : GetBaseURI();
   nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
                                             GetCurrentDoc(), baseURI);
 
   mSource.Reset(this, targetURI);
 }
 
 void
 nsSVGUseElement::TriggerReclone()
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -403,18 +403,32 @@ nsresult nsSVGUtils::GetReferencedFrame(
                                         nsIPresShell *aPresShell)
 {
   *aRefFrame = nsnull;
 
   nsIContent* content = nsContentUtils::GetReferencedElement(aURI, aContent);
   if (!content)
     return NS_ERROR_FAILURE;
 
-  // Get the Primary Frame
-  NS_ASSERTION(aPresShell, "Get referenced SVG frame -- no pres shell provided");
+  nsIDocument* doc = content->GetCurrentDoc();
+  if (!doc)
+    return NS_ERROR_FAILURE;
+
+  if (aPresShell->GetDocument() != doc) {
+    // External reference; switch to the right presshell
+    aPresShell = doc->GetPrimaryShell();
+  }
+#ifdef DEBUG
+  else {
+    // Get the Primary Frame
+    NS_ASSERTION(aPresShell,
+                 "Get referenced SVG frame -- no pres shell provided");
+  }
+#endif
+  
   if (!aPresShell)
     return NS_ERROR_FAILURE;
 
   *aRefFrame = aPresShell->GetPrimaryFrameFor(content);
   if (!(*aRefFrame)) return NS_ERROR_FAILURE;
   return NS_OK;
 }