Bug 1497144 - Rewrite DocShellCapabilities.jsm and ScrollPosition.jsm into C++ r=nika
authorAlphan Chen <alchen@mozilla.com>
Tue, 23 Oct 2018 08:15:56 +0000
changeset 490893 5b7afb9ada5e29dabba18a040bdcb509f3a3a315
parent 490892 6615d7dcdec1cde7705d880c932a6ed33b52d806
child 490894 50d03049245af423292d4c69621cfd1c3159401f
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersnika
bugs1497144
milestone65.0a1
Bug 1497144 - Rewrite DocShellCapabilities.jsm and ScrollPosition.jsm into C++ r=nika Differential Revision: https://phabricator.services.mozilla.com/D8083
browser/components/sessionstore/ContentRestore.jsm
browser/components/sessionstore/ContentSessionStore.jsm
browser/components/sessionstore/DocShellCapabilities.jsm
browser/components/sessionstore/moz.build
toolkit/components/sessionstore/nsISessionStoreUtils.idl
toolkit/components/sessionstore/nsSessionStoreUtils.cpp
toolkit/modules/sessionstore/FormData.jsm
toolkit/modules/sessionstore/Utils.jsm
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -3,22 +3,18 @@
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["ContentRestore"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 
-ChromeUtils.defineModuleGetter(this, "DocShellCapabilities",
-  "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 ChromeUtils.defineModuleGetter(this, "FormData",
   "resource://gre/modules/FormData.jsm");
-ChromeUtils.defineModuleGetter(this, "ScrollPosition",
-  "resource://gre/modules/ScrollPosition.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 const ssu = Cc["@mozilla.org/browser/sessionstore/utils;1"]
@@ -163,18 +159,17 @@ ContentRestoreInternal.prototype = {
       this.restoreTabContent(null, false, callbacks.onLoadFinished);
     });
 
     webNavigation.sessionHistory.legacySHistory.addSHistoryListener(listener);
     this._historyListener = listener;
 
     // Make sure to reset the capabilities and attributes in case this tab gets
     // reused.
-    let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
-    DocShellCapabilities.restore(this.docShell, disallow);
+    ssu.restoreDocShellCapabilities(this.docShell, tabData.disallow);
 
     if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
       SessionStorage.restore(this.docShell, tabData.storage);
       delete tabData.storage;
     }
 
     // Add a progress listener to correctly handle browser.loadURI()
     // calls from foreign code.
@@ -322,17 +317,17 @@ ContentRestoreInternal.prototype = {
       // current |frame| and its descendants, if |data.url| is given but
       // doesn't match the loaded document's URL.
       return FormData.restore(frame, data);
     });
 
     // Restore scroll data.
     restoreFrameTreeData(window, scrollPositions, (frame, data) => {
       if (data.scroll) {
-        ScrollPosition.restore(frame, data.scroll);
+        ssu.restoreScrollPosition(frame, data.scroll);
       }
     });
   },
 
   /**
    * Cancel an ongoing restore. This function can be called any time between
    * restoreHistory and restoreDocument.
    *
--- a/browser/components/sessionstore/ContentSessionStore.jsm
+++ b/browser/components/sessionstore/ContentSessionStore.jsm
@@ -17,20 +17,16 @@ function debug(msg) {
   Services.console.logStringMessage("SessionStoreContent: " + msg);
 }
 
 ChromeUtils.defineModuleGetter(this, "FormData",
   "resource://gre/modules/FormData.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ContentRestore",
   "resource:///modules/sessionstore/ContentRestore.jsm");
-ChromeUtils.defineModuleGetter(this, "DocShellCapabilities",
-  "resource:///modules/sessionstore/DocShellCapabilities.jsm");
-ChromeUtils.defineModuleGetter(this, "ScrollPosition",
-  "resource://gre/modules/ScrollPosition.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 const ssu = Cc["@mozilla.org/browser/sessionstore/utils;1"]
@@ -348,17 +344,17 @@ class ScrollPositionListener extends Han
     this.messageQueue.push("scroll", () => this.collect());
   }
 
   onPageLoadStarted() {
     this.messageQueue.push("scroll", () => null);
   }
 
   collect() {
-    return mapFrameTree(this.mm, ScrollPosition.collect);
+    return mapFrameTree(this.mm, ssu.collectScrollPosition.bind(ssu));
   }
 }
 
 /**
  * Listens for changes to input elements. Whenever the value of an input
  * element changes we will re-collect data for the current frame tree and send
  * a message to the parent process.
  *
@@ -413,19 +409,17 @@ class DocShellCapabilitiesListener exten
      * that have just been collected. If nothing changed we won't send a message.
      */
     this._latestCapabilities = "";
 
     this.stateChangeNotifier.addObserver(this);
   }
 
   onPageLoadStarted() {
-    // The order of docShell capabilities cannot change while we're running
-    // so calling join() without sorting before is totally sufficient.
-    let caps = DocShellCapabilities.collect(this.mm.docShell).join(",");
+    let caps = ssu.collectDocShellCapabilities(this.mm.docShell);
 
     // Send new data only when the capability list changes.
     if (caps != this._latestCapabilities) {
       this._latestCapabilities = caps;
       this.messageQueue.push("disallow", () => caps || null);
     }
   }
 }
deleted file mode 100644
--- a/browser/components/sessionstore/DocShellCapabilities.jsm
+++ /dev/null
@@ -1,53 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = ["DocShellCapabilities"];
-
-/**
- * The external API exported by this module.
- */
-var DocShellCapabilities = Object.freeze({
-  collect(docShell) {
-    return DocShellCapabilitiesInternal.collect(docShell);
-  },
-
-  restore(docShell, disallow) {
-    return DocShellCapabilitiesInternal.restore(docShell, disallow);
-  },
-});
-
-const CAPABILITIES_TO_IGNORE = new Set(["Javascript"]);
-
-/**
- * Internal functionality to save and restore the docShell.allow* properties.
- */
-var DocShellCapabilitiesInternal = {
-  // List of docShell capabilities to (re)store. These are automatically
-  // retrieved from a given docShell if not already collected before.
-  // This is made so they're automatically in sync with all nsIDocShell.allow*
-  // properties.
-  caps: null,
-
-  allCapabilities(docShell) {
-    if (!this.caps) {
-      let keys = Object.keys(docShell);
-      this.caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5));
-    }
-    return this.caps;
-  },
-
-  collect(docShell) {
-    let caps = this.allCapabilities(docShell);
-    return caps.filter(cap => !docShell["allow" + cap]
-                              && !CAPABILITIES_TO_IGNORE.has(cap));
-  },
-
-  restore(docShell, disallow) {
-    let caps = this.allCapabilities(docShell);
-    for (let cap of caps)
-      docShell["allow" + cap] = !disallow.has(cap);
-  },
-};
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -7,17 +7,16 @@
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES.sessionstore = [
     'ContentRestore.jsm',
     'ContentSessionStore.jsm',
-    'DocShellCapabilities.jsm',
     'GlobalState.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'RunState.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionMigration.jsm',
     'SessionSaver.jsm',
     'SessionStartup.jsm',
--- a/toolkit/components/sessionstore/nsISessionStoreUtils.idl
+++ b/toolkit/components/sessionstore/nsISessionStoreUtils.idl
@@ -1,16 +1,19 @@
 /* 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 "nsISupports.idl"
 
 interface mozIDOMWindowProxy;
+interface nsIDocShell;
 webidl EventTarget;
+webidl Document;
+interface mozIDOMWindow;
 
 /**
  * A callback passed to nsISessionStoreUtils.forEachNonDynamicChildFrame().
  */
 [function, scriptable, uuid(8199ebf7-76c0-43d6-bcbe-913dd3de3ebf)]
 interface nsISessionStoreUtilsFrameCallback : nsISupports
 {
   /**
@@ -59,9 +62,39 @@ interface nsISessionStoreUtils : nsISupp
    * This is needed, instead of the normal removeEventListener, because the
    * caller doesn't actually have something that WebIDL considers an
    * EventListener.
    */
   void removeDynamicFrameFilteredListener(in EventTarget target,
                                           in AString type,
                                           in nsISupports listener,
                                           in boolean useCapture);
+
+  /*
+   * Save the docShell.allow* properties
+   */
+  ACString collectDocShellCapabilities(in nsIDocShell docShell);
+
+  /*
+   * Restore the docShell.allow* properties
+   */
+  void restoreDocShellCapabilities(in nsIDocShell docShell,
+                                   in ACString disallowCapabilities);
+
+  /**
+   * Collects scroll position data for any given |frame| in the frame hierarchy.
+   *
+   * @param document (DOMDocument)
+   *
+   * @return {scroll: "x,y"} e.g. {scroll: "100,200"}
+   *         Returns null when there is no scroll data we want to store for the
+   *         given |frame|.
+   */
+  ACString collectScrollPosition(in Document document);
+
+  /**
+   * Restores scroll position data for any given |frame| in the frame hierarchy.
+   *
+   * @param frame (DOMWindow)
+   * @param value (ACString)
+   */
+  void restoreScrollPosition(in mozIDOMWindow frame, in ACString data);
 };
--- a/toolkit/components/sessionstore/nsSessionStoreUtils.cpp
+++ b/toolkit/components/sessionstore/nsSessionStoreUtils.cpp
@@ -5,16 +5,21 @@
 #include "nsSessionStoreUtils.h"
 
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/EventListenerBinding.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIScrollableFrame.h"
+#include "nsPresContext.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsPrintfCString.h"
 
 using namespace mozilla::dom;
 
 namespace {
 
 class DynamicFrameEventFilter final : public nsIDOMEventListener
 {
 public:
@@ -153,8 +158,133 @@ nsSessionStoreUtils::RemoveDynamicFrameF
   NS_ENSURE_TRUE(aTarget, NS_ERROR_NO_INTERFACE);
 
   nsCOMPtr<nsIDOMEventListener> listener = do_QueryInterface(aListener);
   NS_ENSURE_TRUE(listener, NS_ERROR_NO_INTERFACE);
 
   aTarget->RemoveEventListener(aType, listener, aUseCapture);
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsSessionStoreUtils::CollectDocShellCapabilities(nsIDocShell* aDocShell,
+                                                 nsACString& aDisallowCapabilities)
+{
+  bool allow;
+
+#define TRY_ALLOWPROP(y)                          \
+  PR_BEGIN_MACRO                                  \
+    aDocShell->GetAllow##y(&allow);               \
+    if (!allow) {                                 \
+      if (!aDisallowCapabilities.IsEmpty()) {     \
+        aDisallowCapabilities.Append(',');        \
+      }                                           \
+      aDisallowCapabilities.Append(#y);           \
+    }                                             \
+  PR_END_MACRO
+
+  TRY_ALLOWPROP(Plugins);
+  // Bug 1328013 : Don't collect "AllowJavascript" property
+  // TRY_ALLOWPROP(Javascript);
+  TRY_ALLOWPROP(MetaRedirects);
+  TRY_ALLOWPROP(Subframes);
+  TRY_ALLOWPROP(Images);
+  TRY_ALLOWPROP(Media);
+  TRY_ALLOWPROP(DNSPrefetch);
+  TRY_ALLOWPROP(WindowControl);
+  TRY_ALLOWPROP(Auth);
+  TRY_ALLOWPROP(ContentRetargeting);
+  TRY_ALLOWPROP(ContentRetargetingOnChildren);
+
+#undef TRY_ALLOWPROP
+
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSessionStoreUtils::RestoreDocShellCapabilities(nsIDocShell* aDocShell,
+                                                 const nsACString& aDisallowCapabilities)
+{
+  aDocShell->SetAllowPlugins(true);
+  aDocShell->SetAllowJavascript(true);
+  aDocShell->SetAllowMetaRedirects(true);
+  aDocShell->SetAllowSubframes(true);
+  aDocShell->SetAllowImages(true);
+  aDocShell->SetAllowMedia(true);
+  aDocShell->SetAllowDNSPrefetch(true);
+  aDocShell->SetAllowWindowControl(true);
+  aDocShell->SetAllowContentRetargeting(true);
+  aDocShell->SetAllowContentRetargetingOnChildren(true);
+
+  nsCCharSeparatedTokenizer tokenizer(aDisallowCapabilities, ',');
+  while (tokenizer.hasMoreTokens()) {
+    const nsACString& token = tokenizer.nextToken();
+    if (token.EqualsLiteral("Plugins")) {
+      aDocShell->SetAllowPlugins(false);
+    } else if (token.EqualsLiteral("Javascript")) {
+      aDocShell->SetAllowJavascript(false);
+    } else if (token.EqualsLiteral("MetaRedirects")) {
+      aDocShell->SetAllowMetaRedirects(false);
+    } else if (token.EqualsLiteral("Subframes")) {
+      aDocShell->SetAllowSubframes(false);
+    } else if (token.EqualsLiteral("Images")) {
+      aDocShell->SetAllowImages(false);
+    } else if (token.EqualsLiteral("Media")) {
+      aDocShell->SetAllowMedia(false);
+    } else if (token.EqualsLiteral("DNSPrefetch")) {
+      aDocShell->SetAllowDNSPrefetch(false);
+    } else if (token.EqualsLiteral("WindowControl")) {
+      aDocShell->SetAllowWindowControl(false);
+    } else if (token.EqualsLiteral("ContentRetargeting")) {
+      bool allow;
+      aDocShell->GetAllowContentRetargetingOnChildren(&allow);
+      aDocShell->SetAllowContentRetargeting(false); //will also set AllowContentRetargetingOnChildren
+      aDocShell->SetAllowContentRetargetingOnChildren(allow); // restore the allowProp to original
+    } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) {
+      aDocShell->SetAllowContentRetargetingOnChildren(false);
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSessionStoreUtils::CollectScrollPosition(nsIDocument* aDocument,
+                                           nsACString& aRet)
+{
+  aRet.Truncate();
+
+  nsIPresShell* presShell = aDocument->GetShell();
+  if (!presShell) {
+    return NS_OK;
+  }
+
+  nsIScrollableFrame* frame = presShell->GetRootScrollFrameAsScrollable();
+  if (!frame) {
+    return NS_OK;
+  }
+
+  nsPoint scrollPos = frame->GetScrollPosition();
+  int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
+  int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
+
+  if ((scrollX != 0) || (scrollY != 0)) {
+    const nsPrintfCString position("%d,%d", scrollX, scrollY);
+    aRet.Assign(position);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSessionStoreUtils::RestoreScrollPosition(mozIDOMWindow* aWindow,
+                                           const nsACString& aPos)
+{
+  nsCCharSeparatedTokenizer tokenizer(aPos, ',');
+  nsAutoCString token(tokenizer.nextToken());
+  int pos_X = atoi(token.get());
+  token = tokenizer.nextToken();
+  int pos_Y = atoi(token.get());
+  nsGlobalWindowInner::Cast(aWindow)->ScrollTo(pos_X, pos_Y);
+
+  return NS_OK;
+}
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -127,17 +127,17 @@ var FormDataInternal = {
    *     }
    *   }
    *
    * @param  doc
    *         DOMDocument instance to obtain form data for.
    * @return object
    *         Form data encoded in an object.
    */
-  collect({document: doc}) {
+  collect(doc) {
     let formNodes = doc.evaluate(
       this.restorableFormNodesXPath,
       doc,
       this.resolveNS.bind(this),
       doc.defaultView.XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
     );
 
     let node;
--- a/toolkit/modules/sessionstore/Utils.jsm
+++ b/toolkit/modules/sessionstore/Utils.jsm
@@ -156,17 +156,28 @@ var Utils = Object.freeze({
    *                                     wish to save for that respective frame.
    * @return {object[]} An array with one entry per dataCollector, containing
    *                    the collected data as a nested data structure according
    *                    to the layout of the frame tree, or null if no data was
    *                    returned by the respective dataCollector.
    */
   mapFrameTree(frame, ...dataCollectors) {
     // Collect data for the current frame.
-    let objs = dataCollectors.map((dataCollector) => dataCollector(frame) || {});
+    let objs = dataCollectors.map(function(dataCollector) {
+      let obj = dataCollector(frame.document);
+        if (!obj || typeof(obj) == "object") {
+          return obj || {};
+        }
+        // Currently, we return string type when collecting scroll position.
+        // Will switched to webidl and return objects in the future.
+        if (typeof(obj) == "string") {
+          return {scroll: obj};
+        }
+        return obj;
+    });
     let children = dataCollectors.map(() => []);
 
     // Recurse into child frames.
     ssu.forEachNonDynamicChildFrame(frame, (subframe, index) => {
       let results = this.mapFrameTree(subframe, ...dataCollectors);
       if (!results) {
         return;
       }