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 442553 5b7afb9ada5e29dabba18a040bdcb509f3a3a315
parent 442552 6615d7dcdec1cde7705d880c932a6ed33b52d806
child 442554 50d03049245af423292d4c69621cfd1c3159401f
push id34913
push useraciure@mozilla.com
push dateTue, 23 Oct 2018 16:49:58 +0000
treeherdermozilla-central@ff3ed362e82f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1497144
milestone65.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 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;
       }