--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -42,17 +42,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
*/
[
["AboutHome", "resource:///modules/AboutHome.jsm"],
["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
["BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"],
["CastingApps", "resource:///modules/CastingApps.jsm"],
["CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"],
- ["Color", "resource://gre/modules/Color.jsm"],
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
["Deprecated", "resource://gre/modules/Deprecated.jsm"],
["E10SUtils", "resource:///modules/E10SUtils.jsm"],
["ExtensionsUI", "resource:///modules/ExtensionsUI.jsm"],
["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
["FullZoomUI", "resource:///modules/FullZoomUI.jsm"],
["GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm"],
["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
@@ -81,16 +80,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
["Weave", "resource://services-sync/main.js"],
["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
["webrtcUI", "resource:///modules/webrtcUI.jsm"],
].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "Color",
+ "resource://gre/modules/Color.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
if (AppConstants.MOZ_CRASHREPORTER) {
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashHandlers.jsm");
}
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -480,8 +480,114 @@ function makeQI(interfaceNames) {
for (let i = 0; i < interfaceNames.length; i++) {
if (Ci[interfaceNames[i]].equals(iid))
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
};
}
+
+/**
+ * Unload modules that have been idle for some period.
+ */
+function myLog(aMsg)
+{
+ let proc = "[PARENT]";
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ proc = "[CHILDD]";
+ }
+ dump("ZZZ " + proc + " " + aMsg + "\n");
+}
+
+// XXX Is there some way we can detect that a jsm has been imported
+// normally and via the unloadable service? That will may cause weird
+// behavior.
+
+// XXX Make these fields of XPCOMUtils?
+var resourceIds = new Map();
+var resources = [];
+var importedValues = [];
+var actives = [];
+var unloaderTimerId = null;
+const idleUnloadTime = 5000;
+
+function unloaderTimerFunction()
+{
+ myLog(">> unloader timer fired");
+ let anyActive = false;
+
+ for (let currId = 0; currId < importedValues.length; currId++) {
+ if (actives[currId]) {
+ myLog("timer rejected " + resources[currId]);
+ actives[currId] = false;
+ anyActive = true;
+ } else if (importedValues[currId]) {
+ // XXX Ideally, throw an error or something nicer if this
+ // property is not defined.
+ if (importedValues[currId]["Reconstitutable"].is) {
+ myLog("unloading " + resources[currId]);
+ importedValues[currId] = null;
+ Cu.unload(resources[currId]);
+ } else {
+ myLog("can't unload " + resources[currId]);
+ // We can't reconstitute now, but maybe we can later.
+ anyActive = true;
+ }
+ } else {
+ myLog("sleep now " + resources[currId]);
+ }
+ }
+
+ if (!anyActive) {
+ myLog("stopping timer");
+ clearInterval(unloaderTimerId);
+ unloaderTimerId = null;
+ }
+}
+
+// XXX Move this up.
+Cu.import("resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineIdleUnloadLazyModuleGetter = (aObject, aName, aResource, aSymbol) =>
+{
+ let resourceId = resourceIds.get(aResource);
+ if (typeof resourceId === "undefined") {
+ resourceId = importedValues.length;
+ resourceIds.set(aResource, resourceId);
+ resources.push(aResource);
+ importedValues.push(null);
+ actives.push(false);
+ myLog("init with new id " + resourceId + " for " + aResource);
+ } else {
+ myLog("found existing id " + resourceId + " for " + aResource);
+ }
+
+ Object.defineProperty(aObject, aName, {
+ get: function () {
+ if (importedValues[resourceId] == null) {
+ var values = {};
+ try {
+ Cu.import(aResource, values);
+ } catch (ex) {
+ Cu.reportError("Failed to load module " + aResource + ".");
+ throw ex;
+ }
+ importedValues[resourceId] = values;
+ myLog("import " + (aSymbol || aName));
+
+ if (!unloaderTimerId) {
+ myLog("starting timer");
+ unloaderTimerId = setInterval(unloaderTimerFunction,
+ idleUnloadTime);
+ }
+ }
+ if (!actives[resourceId]) {
+ myLog("activated " + aName);
+ }
+ actives[resourceId] = true;
+
+ return importedValues[resourceId][aSymbol || aName];
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
--- a/toolkit/modules/Color.jsm
+++ b/toolkit/modules/Color.jsm
@@ -1,15 +1,15 @@
/* 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";
-this.EXPORTED_SYMBOLS = ["Color"];
+this.EXPORTED_SYMBOLS = ["Color", "Reconstitutable"];
/**
* Color class, which describes a color.
* In the future, this object may be extended to allow for conversions between
* different color formats and notations, support transparency.
*
* @param {Number} r Red color component
* @param {Number} g Green color component
@@ -78,8 +78,18 @@ Color.prototype = {
* @return {Boolean}
*/
isContrastRatioAcceptable(otherColor) {
// Note: this is a high enough value to be considered as 'high contrast',
// but was decided upon empirically.
return this.contrastRatio(otherColor) > 3;
}
};
+
+// XXX What happens if we unload and a color object exists? Do we not
+// really clear it up? Is the Color object something in the Color.jsm
+// compartment? Maybe it would just keep the compartment alive? But
+// what if we then import it again? Maybe unload wouldn't really drop
+// the mapping until the compartment goes away? Wouldn't be too hard
+// to test.
+this.Reconstitutable = {
+ is : true,
+};
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -20,16 +20,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
"nsITextToSubURI");
XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
"@mozilla.org/widget/clipboard;1",
"nsIClipboard");
XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "FinderHighlighter",
+ "resource://gre/modules/FinderHighlighter.jsm");
+
const kSelectionMaxLen = 150;
const kMatchesCountLimitPref = "accessibility.typeaheadfind.matchesCountLimit";
function Finder(docShell) {
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
this._fastFind.init(docShell);
this._currentFoundRange = null;
@@ -45,17 +48,19 @@ function Finder(docShell) {
BrowserUtils.getRootWindow(this._docShell).addEventListener("unload",
this.onLocationChange.bind(this, { isTopLevel: true }));
}
Finder.prototype = {
get iterator() {
if (this._iterator)
return this._iterator;
- this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
+ XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "_iterator",
+ "resource://gre/modules/FinderIterator.jsm",
+ "FinderIterator");
return this._iterator;
},
destroy() {
if (this._iterator)
this._iterator.reset();
let window = this._getWindow();
if (this._highlighter && window) {
@@ -161,18 +166,16 @@ Finder.prototype = {
return;
this._fastFind.entireWord = aEntireWord;
this.iterator.reset();
},
get highlighter() {
if (this._highlighter)
return this._highlighter;
-
- const {FinderHighlighter} = Cu.import("resource://gre/modules/FinderHighlighter.jsm", {});
return this._highlighter = new FinderHighlighter(this);
},
get matchesCountLimit() {
if (typeof this._matchesCountLimit == "number")
return this._matchesCountLimit;
this._matchesCountLimit = Services.prefs.getIntPref(kMatchesCountLimitPref) || 0;
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -1,23 +1,24 @@
/* 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";
-this.EXPORTED_SYMBOLS = ["FinderHighlighter"];
+this.EXPORTED_SYMBOLS = ["FinderHighlighter", "Reconstitutable"];
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "Color",
+ "resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
const kDebugPref = "findbar.modalHighlight.debug";
return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
});
const kContentChangeThresholdPx = 5;
const kBrightTextSampleSize = 5;
@@ -144,17 +145,19 @@ function FinderHighlighter(finder) {
this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
this.finder = finder;
}
FinderHighlighter.prototype = {
get iterator() {
if (this._iterator)
return this._iterator;
- this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
+ XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "_iterator",
+ "resource://gre/modules/FinderIterator.jsm",
+ "FinderIterator");
return this._iterator;
},
/**
* Each window is unique, globally, and the relation between an active
* highlighting session and a window is 1:1.
* For each window we track a number of properties which _at least_ consist of
* - {Boolean} detectedGeometryChange Whether the geometry of the found ranges'
@@ -1618,8 +1621,17 @@ FinderHighlighter.prototype = {
},
// Unimplemented
notifyDocumentCreated() {},
notifyDocumentStateChanged(aDirty) {}
};
}
};
+
+// XXX This doesn't really work, because the map doesn't seem to get
+// cleared, even if we're not currently highlighting anything. Not
+// sure how to deal with that.
+this.Reconstitutable = {
+ get is() {
+ return ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(gWindows).length == 0;
+ }
+};
--- a/toolkit/modules/FinderIterator.jsm
+++ b/toolkit/modules/FinderIterator.jsm
@@ -1,24 +1,24 @@
/* 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";
-this.EXPORTED_SYMBOLS = ["FinderIterator"];
+this.EXPORTED_SYMBOLS = ["FinderIterator", "Reconstitutable"];
const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NLP", "resource://gre/modules/NLP.jsm");
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "NLP", "resource://gre/modules/NLP.jsm");
const kDebug = false;
const kIterationSizeMax = 100;
const kTimeoutPref = "findbar.iteratorTimeout";
/**
* FinderIterator singleton. See the documentation for the `start()` method to
* learn more.
@@ -650,8 +650,14 @@ this.FinderIterator = {
}
node = node.parentNode;
} while (node);
return isInsideLink;
}
};
+
+this.Reconstitutable = {
+ get is() {
+ return !FinderIterator.running;
+ }
+};
--- a/toolkit/modules/NLP.jsm
+++ b/toolkit/modules/NLP.jsm
@@ -1,15 +1,15 @@
/* 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";
-this.EXPORTED_SYMBOLS = ["NLP"];
+this.EXPORTED_SYMBOLS = ["NLP", "Reconstitutable"];
/**
* NLP, which stands for Natural Language Processing, is a module that provides
* an entry point to various methods to interface with human language.
*
* At least, that's the goal. Eventually. Right now, the find toolbar only really
* needs the Levenshtein distance algorithm.
*/
@@ -69,8 +69,12 @@ this.NLP = {
p2 = tmp;
}
c0 = p1[l2];
return c0;
}
};
+
+this.Reconstitutable = {
+ is : true,
+};
--- a/toolkit/modules/tests/xpcshell/test_Color.js
+++ b/toolkit/modules/tests/xpcshell/test_Color.js
@@ -1,11 +1,12 @@
"use strict";
-Components.utils.import("resource://gre/modules/Color.jsm");
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "Color",
+ "resource://gre/modules/Color.jsm");
function run_test() {
testRelativeLuminance();
testIsBright();
testContrastRatio();
testIsContrastRatioAcceptable();
}
--- a/toolkit/modules/tests/xpcshell/test_FinderIterator.js
+++ b/toolkit/modules/tests/xpcshell/test_FinderIterator.js
@@ -1,10 +1,13 @@
const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
-const { FinderIterator } = Cu.import("resource://gre/modules/FinderIterator.jsm", {});
+
+XPCOMUtils.defineIdleUnloadLazyModuleGetter(this, "FinderIterator",
+ "resource://gre/modules/FinderIterator.jsm");
+
Cu.import("resource://gre/modules/Promise.jsm");
var gFindResults = [];
// Stub the method that instantiates nsIFind and does all the interaction with
// the docShell to be searched through.
FinderIterator._iterateDocument = function* (word, window, finder) {
for (let range of gFindResults)
yield range;