Skip to content

Commit

Permalink
avoid holding duplicate script source strings in memory
Browse files Browse the repository at this point in the history
old: strings sent via e10s message manager are duplicated and the
sandbox holds onto them as source code, this causes unnecessary memory
overhead.

new: load scripts from file:// URIs and use lazy getters for
scriptSource
  • Loading branch information
the8472 committed Aug 21, 2015
1 parent 32e2122 commit 70a16c4
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 32 deletions.
19 changes: 2 additions & 17 deletions modules/ipcscript.js
@@ -1,6 +1,5 @@
var EXPORTED_SYMBOLS = ['IPCScript'];

Components.utils.import("chrome://greasemonkey-modules/content/extractMeta.js");
Components.utils.import("chrome://greasemonkey-modules/content/util.js");

function IPCScript(aScript, addonVersion) {
Expand All @@ -17,37 +16,25 @@ function IPCScript(aScript, addonVersion) {
this.namespace = aScript.namespace;
this.noframes = aScript.noframes;
this.runAt = aScript.runAt;
this.textContent = aScript.textContent;
this.uuid = aScript.uuid;
this.version = aScript.version;
this.willUpdate = aScript.isRemoteUpdateAllowed();

this.requires = aScript.requires.map(function(req) {
return {
'fileURL': req.fileURL,
'textContent': req.textContent
'fileURL': req.fileURL
};
});

this.resources = aScript.resources.map(function(res) {
return {
'name': res.name,
'mimetype': res.mimetype,
'textContent': res.textContent,
'url': GM_util.getUriFromFile(res.file).spec
};
});
};

IPCScript.prototype.__defineGetter__('metaStr',
function IPCScript_getMetaStr() {
if (!this._metaStr) {
this._metaStr = extractMeta(this.textContent);
}

return this._metaStr;
});

IPCScript.prototype.info = function() {
var resources = {};
for (var i = 0, r = null; r = this.resources[i]; i++) {
Expand All @@ -60,8 +47,6 @@ IPCScript.prototype.info = function() {
return {
'uuid': this.uuid,
'version': this.addonVersion,
'scriptMetaStr': this.metaStr,
'scriptSource': this.textContent,
'scriptWillUpdate': this.willUpdate,
'script': {
'description': this.description,
Expand All @@ -80,4 +65,4 @@ IPCScript.prototype.info = function() {
'version': this.version
}
};
};
};
7 changes: 6 additions & 1 deletion modules/miscapis.js
Expand Up @@ -24,8 +24,13 @@ GM_Resources.prototype.getResourceURL = function(aScript, name) {
return ['greasemonkey-script:', aScript.uuid, '/', name].join('');
};


GM_Resources.prototype.getResourceText = function(name) {
return this._getDep(name).textContent;
var dep = this._getDep(name)
if(dep.textContent !== undefined)
return dep.textContent;
// lazy resources in IPC scripts
return GM_util.fileXHR(dep.url, "text/plain");
};

GM_Resources.prototype._getDep = function(name) {
Expand Down
84 changes: 70 additions & 14 deletions modules/sandbox.js
Expand Up @@ -11,12 +11,15 @@ Cu.import("chrome://greasemonkey-modules/content/miscapis.js");
Cu.import("chrome://greasemonkey-modules/content/storageFront.js");
Cu.import("chrome://greasemonkey-modules/content/util.js");
Cu.import("chrome://greasemonkey-modules/content/xmlhttprequester.js");
Cu.import("chrome://greasemonkey-modules/content/extractMeta.js");

var gStringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://greasemonkey/locale/greasemonkey.properties");
var gInvalidAccesskeyErrorStr = gStringBundle
.GetStringFromName('error.menu-invalid-accesskey');
var subLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);

// Only a particular set of strings are allowed. See: http://goo.gl/ex2LJ
var gMaxJSVersion = "ECMAv5";
Expand All @@ -35,9 +38,8 @@ function createSandbox(aScript, aContentWin, aUrl, aFrameScope) {
'wantXrays': false,
});
// GM_info is always provided.
// TODO: lazy getter? XPCOMUtils.defineLazyGetter
Components.utils.evalInSandbox(
'const GM_info = ' + uneval(aScript.info()), contentSandbox);
injectGMInfo(aScript, contentSandbox);

// Alias unsafeWindow for compatibility.
Components.utils.evalInSandbox(
'const unsafeWindow = window;', contentSandbox);
Expand Down Expand Up @@ -124,19 +126,70 @@ function createSandbox(aScript, aContentWin, aUrl, aFrameScope) {
'contentStartRequest');
}

// TODO: lazy getter?
Components.utils.evalInSandbox(
'const GM_info = ' + uneval(aScript.info()), sandbox);
injectGMInfo(aScript, sandbox);

return sandbox;
}



function injectGMInfo(aScript, sandbox) {
var rawInfo = aScript.info();
var scriptURL = aScript.fileURL;

// TODO: also delay top level clone via lazy getter? XPCOMUtils.defineLazyGetter
sandbox.GM_info = Cu.cloneInto(rawInfo, sandbox);

var waivedInfo = Components.utils.waiveXrays(sandbox.GM_info);

var fileCache = new Map();

// lazy getters for heavyweight strings that aren't sent down through IPC
Object.defineProperty(waivedInfo,
"scriptSource",
{ get: Cu.exportFunction(
function() {
var content = fileCache.get("scriptSource");
if(content === undefined) {
content = GM_util.fileXHR(scriptURL, "application/javascript");
fileCache.set("scriptSource", content);
}
return content;
}, sandbox)
}
);

// meta depends on content, so we need a lazy one here too
Object.defineProperty(waivedInfo,
'scriptMetaStr',
{get: Cu.exportFunction(
function() {
var meta = fileCache.get("meta");
if(meta === undefined) {
meta = extractMeta(this.scriptSource);
fileCache.set("meta", meta);
}
return meta;
}, sandbox)
}
);

}

function uriToString(uri) {
var channel = NetUtil.newChannel({ uri: NetUtil.newURI(uri, "UTF-8"), loadUsingSystemPrincipal: true});
var stream = channel.open();

var count = stream.available();
var data = NetUtil.readInputStreamToString(stream, count, { charset : "utf-8" });
return data;
}

function runScriptInSandbox(script, sandbox) {
// Eval the code, with anonymous wrappers when/if appropriate.
function evalWithWrapper(code, fileName) {
function evalWithWrapper(uri) {
try {
Components.utils.evalInSandbox(code, sandbox, gMaxJSVersion, fileName, 1);
subLoader.loadSubScript(uri, sandbox, "UTF-8");
} catch (e) {
if ("return not in function" == e.message) {
// See #1592; we never anon wrap anymore, unless forced to by a return
Expand All @@ -147,8 +200,11 @@ function runScriptInSandbox(script, sandbox) {
fileName,
e.lineNumber
);

var code = GM_util.fileXHR(uri, "application/javascript");

Components.utils.evalInSandbox(
'(function(){ '+code+'\n})()', sandbox, gMaxJSVersion, fileName, 1);
'(function(){ '+code+'\n})()', sandbox, gMaxJSVersion, uri, 1);
} else {
// Otherwise raise.
throw e;
Expand All @@ -157,22 +213,22 @@ function runScriptInSandbox(script, sandbox) {
}

// Eval the code, with a try/catch to report errors cleanly.
function evalWithCatch(code, fileName) {
function evalWithCatch(uri) {
try {
evalWithWrapper(code, fileName);
evalWithWrapper(uri);
} catch (e) {
// Log it properly.
GM_util.logError(e, false, fileName, e.lineNumber);
GM_util.logError(e, false, e.fileName, e.lineNumber);
// Stop the script, in the case of requires, as if it was one big script.
return false;
}
return true;
}

for (var i = 0, require = null; require = script.requires[i]; i++) {
if (!evalWithCatch(require.textContent, require.fileURL)) {
if (!evalWithCatch(require.fileURL)) {
return;
}
}
evalWithCatch(script.textContent, script.fileURL);
evalWithCatch(script.fileURL);
}
1 change: 1 addition & 0 deletions modules/util.js
Expand Up @@ -27,6 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(GM_util, 'alert', 'chrome://greasemonkey-modul
XPCOMUtils.defineLazyModuleGetter(GM_util, 'compareFirefoxVersion', 'chrome://greasemonkey-modules/content/util/compareFirefoxVersion.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'emptyEl', 'chrome://greasemonkey-modules/content/util/emptyEl.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'enqueueRemoveFile', 'chrome://greasemonkey-modules/content/util/enqueueRemoveFile.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'fileXHR', 'chrome://greasemonkey-modules/content/util/fileXHR.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'findMessageManager', 'chrome://greasemonkey-modules/content/util/findMessageManager.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'getBestLocaleMatch', 'chrome://greasemonkey-modules/content/util/getBestLocaleMatch.js');
XPCOMUtils.defineLazyModuleGetter(GM_util, 'getBinaryContents', 'chrome://greasemonkey-modules/content/util/getBinaryContents.js');
Expand Down
15 changes: 15 additions & 0 deletions modules/util/fileXHR.js
@@ -0,0 +1,15 @@
'use strict';

var EXPORTED_SYMBOLS = ['fileXHR'];

Components.utils.importGlobalProperties(["XMLHttpRequest"]);

// sync XHR. it's just meant to fetch file:// uris that aren't otherwise accessible in content
// don't use it in the parent process or for web URLs
function fileXHR(uri, mimetype) {
var xhr = new XMLHttpRequest();
xhr.open("open", uri, false);
xhr.overrideMimeType(mimetype);
xhr.send(null);
return xhr.responseText;
}

0 comments on commit 70a16c4

Please sign in to comment.