Bug 1141337 - Update nsIWebBrowserPersistable to allow subframe targeting for Save Frame As. r=jld
authorMike Conley <mconley@mozilla.com>
Thu, 06 Aug 2015 10:44:16 -0400
changeset 257755 a4fb24ca03ed2d439b2059d1151d090077f2b508
parent 257754 8efc942a5dd97f3ee54e5b8ec0e79a448d53b717
child 257756 9a6a4af927320dc7ce8645701a6ca606ddc22171
push id29226
push userryanvm@gmail.com
push dateFri, 14 Aug 2015 13:01:14 +0000
treeherdermozilla-central@1b2402247429 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjld
bugs1141337
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1141337 - Update nsIWebBrowserPersistable to allow subframe targeting for Save Frame As. r=jld This alters nsIWebBrowserPersistable so that startPersistence takes an outerWindowID. This allows us to target a particular subframe from beneath an nsFrameLoader, which is useful when attempting to Save Frame As a remote browser.
browser/base/content/nsContextMenu.js
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsFrameLoader.cpp
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
toolkit/content/contentAreaUtils.js
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1181,17 +1181,17 @@ nsContextMenu.prototype = {
 #endif
     };
 
     mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
   },
 
   // Save URL of clicked-on frame.
   saveFrame: function () {
-    saveDocument(this.target.ownerDocument);
+    saveBrowser(this.browser, false, this.frameOuterWindowID);
   },
 
   // Helper function to wait for appropriate MIME-type headers and
   // then prompt the user with a file picker
   saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
                        windowID, linkDownload) {
     // canonical def in nsURILoader.h
     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3844,16 +3844,40 @@ nsContentUtils::MatchElementId(nsIConten
   if (!id) {
     // OOM, so just bail
     return nullptr;
   }
 
   return MatchElementId(aContent, id);
 }
 
+/* static */
+nsIDocument*
+nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+                                                uint64_t aOuterWindowId)
+{
+  if (!aDocument || !aOuterWindowId) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId);
+  if (!window) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocument> foundDoc = window->GetDoc();
+  if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) {
+    // Note that ContentIsCrossDocDescendantOf will return true if
+    // foundDoc == aDocument.
+    return foundDoc;
+  }
+
+  return nullptr;
+}
+
 // Convert the string from the given encoding to Unicode.
 /* static */
 nsresult
 nsContentUtils::ConvertStringFromEncoding(const nsACString& aEncoding,
                                           const nsACString& aInput,
                                           nsAString& aOutput)
 {
   nsAutoCString encoding;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -320,16 +320,33 @@ public:
    * @param   aDocumentPosition   The document position flags to be reversed.
    *
    * @return  The reversed document position flags.
    *
    * @see nsIDOMNode
    */
   static uint16_t ReverseDocumentPosition(uint16_t aDocumentPosition);
 
+  /**
+   * Returns a subdocument for aDocument with a particular outer window ID.
+   *
+   * @param aDocument
+   *        The document whose subdocuments will be searched.
+   * @param aOuterWindowID
+   *        The outer window ID for the subdocument to be found. This must
+   *        be a value greater than 0.
+   * @return nsIDocument*
+   *        A pointer to the found nsIDocument. nullptr if the subdocument
+   *        cannot be found, or if either aDocument or aOuterWindowId were
+   *        invalid. If the outer window ID belongs to aDocument itself, this
+   *        will return a pointer to aDocument.
+   */
+  static nsIDocument* GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+                                                      uint64_t aOuterWindowId);
+
   static uint32_t CopyNewlineNormalizedUnicodeTo(const nsAString& aSource,
                                                  uint32_t aSrcOffset,
                                                  char16_t* aDest,
                                                  uint32_t aLength,
                                                  bool& aLastCharCR);
 
   static uint32_t CopyNewlineNormalizedUnicodeTo(nsReadingIterator<char16_t>& aSrcStart, const nsReadingIterator<char16_t>& aSrcEnd, nsAString& aDest);
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2880,23 +2880,36 @@ nsFrameLoader::InitializeBrowserAPI()
     nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
     if (browserFrame) {
       browserFrame->InitializeBrowserAPI();
     }
   }
 }
 
 NS_IMETHODIMP
-nsFrameLoader::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+nsFrameLoader::StartPersistence(uint64_t aOuterWindowID,
+                                nsIWebBrowserPersistDocumentReceiver* aRecv)
 {
+  if (!aRecv) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
   if (mRemoteBrowser) {
-    return mRemoteBrowser->StartPersistence(aRecv);
+    return mRemoteBrowser->StartPersistence(aOuterWindowID, aRecv);
   }
-  if (mDocShell) {
-    nsCOMPtr<nsIDocument> doc = do_GetInterface(mDocShell);
-    NS_ENSURE_STATE(doc);
+
+  nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(mDocShell);
+  nsCOMPtr<nsIDocument> foundDoc;
+  if (aOuterWindowID) {
+    foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID);
+  } else {
+    foundDoc = rootDoc;
+  }
+
+  if (!foundDoc) {
+    aRecv->OnError(NS_ERROR_NO_CONTENT);
+  } else {
     nsCOMPtr<nsIWebBrowserPersistDocument> pdoc =
-      new mozilla::WebBrowserPersistLocalDocument(doc);
+      new mozilla::WebBrowserPersistLocalDocument(foundDoc);
     aRecv->OnDocumentReady(pdoc);
-    return NS_OK;
   }
-  return NS_ERROR_NO_CONTENT;
+  return NS_OK;
 }
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -116,17 +116,17 @@ both:
                  Principal aPrincipal);
 
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
     PRenderFrame();
 
-    PWebBrowserPersistDocument();
+    PWebBrowserPersistDocument(uint64_t aOuterWindowID);
 
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
      * aParentDoc is the accessible document it was created in if any, and
      * aParentAcc is the id of the accessible in that document the new document
      * is a child of.
      */
@@ -519,16 +519,17 @@ parent:
                                uint64_t aObserverId);
     SynthesizeNativeTouchTap(IntPoint aPointerScreenPoint,
                              bool aLongTap,
                              uint64_t aObserverId);
     ClearNativeTouchSequence(uint64_t aObserverId);
 child:
     NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
 
+
 parent:
 
     /**
      * Child informs the parent that the graphics objects are ready for
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3113,26 +3113,38 @@ TabChildGlobal::GetGlobalJSObject()
 {
   NS_ENSURE_TRUE(mTabChild, nullptr);
   nsCOMPtr<nsIXPConnectJSObjectHolder> ref = mTabChild->GetGlobal();
   NS_ENSURE_TRUE(ref, nullptr);
   return ref->GetJSObject();
 }
 
 PWebBrowserPersistDocumentChild*
-TabChild::AllocPWebBrowserPersistDocumentChild()
+TabChild::AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID)
 {
   return new WebBrowserPersistDocumentChild();
 }
 
 bool
-TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor)
+TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor,
+                                                    const uint64_t& aOuterWindowID)
 {
-  nsCOMPtr<nsIDocument> doc = GetDocument();
-  static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(doc);
+  nsCOMPtr<nsIDocument> rootDoc = GetDocument();
+  nsCOMPtr<nsIDocument> foundDoc;
+  if (aOuterWindowID) {
+    foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID);
+  } else {
+    foundDoc = rootDoc;
+  }
+
+  if (!foundDoc) {
+    aActor->SendInitFailure(NS_ERROR_NO_CONTENT);
+  } else {
+    static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(foundDoc);
+  }
   return true;
 }
 
 bool
 TabChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor)
 {
   delete aActor;
   return true;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -490,18 +490,19 @@ public:
     bool ParentIsActive()
     {
       return mParentIsActive;
     }
     bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
 
     virtual ScreenIntSize GetInnerSize() override;
 
-    virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild() override;
-    virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor) override;
+    virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID) override;
+    virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor,
+                                                           const uint64_t& aOuterWindowID) override;
     virtual bool DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override;
 
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3363,34 +3363,35 @@ TabParent::TakeDragVisualization(RefPtr<
 bool
 TabParent::AsyncPanZoomEnabled() const
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   return widget && widget->AsyncPanZoomEnabled();
 }
 
 PWebBrowserPersistDocumentParent*
-TabParent::AllocPWebBrowserPersistDocumentParent()
+TabParent::AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID)
 {
   return new WebBrowserPersistDocumentParent();
 }
 
 bool
 TabParent::DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor)
 {
   delete aActor;
   return true;
 }
 
 NS_IMETHODIMP
-TabParent::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+TabParent::StartPersistence(uint64_t aOuterWindowID,
+                            nsIWebBrowserPersistDocumentReceiver* aRecv)
 {
   auto* actor = new WebBrowserPersistDocumentParent();
   actor->SetOnReady(aRecv);
-  return SendPWebBrowserPersistDocumentConstructor(actor)
+  return SendPWebBrowserPersistDocumentConstructor(actor, aOuterWindowID)
     ? NS_OK : NS_ERROR_FAILURE;
   // (The actor will be destroyed on constructor failure.)
 }
 
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -430,17 +430,17 @@ public:
                           const int32_t& aDragAreaX, const int32_t& aDragAreaY) override;
 
     void AddInitialDnDDataTo(DataTransfer* aDataTransfer);
 
     void TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                int32_t& aDragAreaX, int32_t& aDragAreaY);
     layout::RenderFrameParent* GetRenderFrame();
 
-    virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent() override;
+    virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID) override;
     virtual bool DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
 
 protected:
     bool ReceiveMessage(const nsString& aMessage,
                         bool aSync,
                         const StructuredCloneData* aCloneData,
                         mozilla::jsipc::CpowHolder* aCpows,
                         nsIPrincipal* aPrincipal,
--- a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -341,17 +341,20 @@ nsresult
 ResourceReader::OnWalkSubframe(nsIDOMNode* aNode)
 {
     nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode);
     NS_ENSURE_STATE(loaderOwner);
     nsRefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
     NS_ENSURE_STATE(loader);
 
     ++mOutstandingDocuments;
-    nsresult rv = loader->StartPersistence(this);
+    // Pass in 0 as the outer window ID so that we start
+    // persisting the root of this subframe, and not some other
+    // subframe child of this subframe.
+    nsresult rv = loader->StartPersistence(0, this);
     if (NS_FAILED(rv)) {
         if (rv == NS_ERROR_NO_CONTENT) {
             // Just ignore frames with no content document.
             rv = NS_OK;
         }
         // StartPersistence won't eventually call this if it failed,
         // so this does so (to keep mOutstandingDocuments correct).
         DocumentDone(rv);
--- a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
@@ -32,17 +32,21 @@ WebBrowserPersistResourcesChild::VisitRe
 }
 
 NS_IMETHODIMP
 WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument,
                                                nsIWebBrowserPersistDocument* aSubDocument)
 {
     auto* subActor = new WebBrowserPersistDocumentChild();
     dom::PBrowserChild* grandManager = Manager()->Manager();
-    if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor)) {
+    // As a consequence of how PWebBrowserPersistDocumentConstructor can be
+    // sent by both the parent and the child, we must pass the outerWindowID
+    // argument here. We pass 0, though note that this argument is actually
+    // just ignored when passed up to the parent from the child.
+    if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor, 0)) {
         // NOTE: subActor is freed at this point.
         return NS_ERROR_FAILURE;
     }
     // ...but here, IPC won't free subActor until after this returns
     // to the event loop.
 
     // The order of these two messages will be preserved, because
     // they're the same toplevel protocol and priority.
--- a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
+++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
@@ -17,14 +17,25 @@ interface nsIWebBrowserPersistDocumentRe
  *
  * Warning: this is currently implemented only by nsFrameLoader, and
  * may change in the future to become more frame-loader-specific or be
  * merged into nsIFrameLoader.  See bug 1101100 comment #34.
  *
  * @see nsIWebBrowserPersistDocumentReceiver
  * @see nsIWebBrowserPersistDocument
  * @see nsIWebBrowserPersist
+ *
+ * @param aOuterWindowID
+ *        The outer window ID of the subframe we'd like to persist.
+ *        If set at 0, nsIWebBrowserPersistable will attempt to persist
+ *        the top-level document. If the outer window ID is for a subframe
+ *        that does not exist, or is not held beneath the nsIWebBrowserPersistable,
+ *        aRecv's onError method will be called with NS_ERROR_NO_CONTENT.
+ * @param aRecv
+ *        The nsIWebBrowserPersistDocumentReceiver is a callback that
+ *        will be fired once the document is ready for persisting.
  */
-[scriptable, function, uuid(24d0dc9e-b970-4cca-898f-cbba03abaa73)]
+[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)]
 interface nsIWebBrowserPersistable : nsISupports
 {
-  void startPersistence(in nsIWebBrowserPersistDocumentReceiver aRecv);
+  void startPersistence(in unsigned long long aOuterWindowID,
+                        in nsIWebBrowserPersistDocumentReceiver aRecv);
 };
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -124,26 +124,26 @@ function saveImageURL(aURL, aFileName, a
   internalSave(aURL, null, aFileName, aContentDisp, aContentType,
                aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
                aDoc, aSkipPrompt, null);
 }
 
 // This is like saveDocument, but takes any browser/frame-like element
 // (nsIFrameLoaderOwner) and saves the current document inside it,
 // whether in-process or out-of-process.
-function saveBrowser(aBrowser, aSkipPrompt)
+function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0)
 {
   if (!aBrowser) {
     throw "Must have a browser when calling saveBrowser";
   }
   let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
                     .frameLoader
                     .QueryInterface(Ci.nsIWebBrowserPersistable);
   let stack = Components.stack.caller;
-  persistable.startPersistence({
+  persistable.startPersistence(aOuterWindowID, {
     onDocumentReady: function (document) {
       saveDocument(document, aSkipPrompt);
     },
     onError: function (status) {
       throw new Components.Exception("saveBrowser failed asynchronously in startPersistence",
                                      status, stack);
     }
   });