Skip to content

Commit

Permalink
rewrite Sandbox and Evaluator as classes extending Port (2.5h)
Browse files Browse the repository at this point in the history
  • Loading branch information
NiklasGollenstede committed May 16, 2018
1 parent 5bb073d commit bd7c397
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 169 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ npm-debug.log
/manifest.json
/WikiPeek.html
/WikiPeek
/view.html
82 changes: 0 additions & 82 deletions background/evaluator.js

This file was deleted.

4 changes: 2 additions & 2 deletions background/fallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
'node_modules/web-ext-utils/loader/views': Views,
'node_modules/web-ext-utils/utils/': { reportError, },
'common/options': options,
'common/sandbox': makeSandbox,
'common/sandbox': Sandbox,
'content/panel.js': js,
'fetch!content/panel.css': css,
'fetch!content/panel.html': html,
Expand Down Expand Up @@ -49,7 +49,7 @@ const methods = {
view.addEventListener('unload', () => { view && tab.port && tab.port.destroy(); tab = tab.port = null; });
view.document.title = `Fallback - Wikipedia Peek`;

const port = tab.port = (await makeSandbox(js, {
const port = tab.port = (await new Sandbox(js, {
html, srcUrl: require.toUrl('content/panel.js'),
host: view.document.body, // needs to reside in the view, otherwise firefox won't give the elements any dimensions
}));
Expand Down
7 changes: 3 additions & 4 deletions background/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function(global) { 'use strict'; define(async ({ // 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/.
'node_modules/web-ext-utils/browser/': { manifest, browserAction, Tabs, runtime, },
'node_modules/web-ext-utils/browser/': { manifest, browserAction, Tabs, },
'node_modules/web-ext-utils/browser/messages': Messages,
'node_modules/web-ext-utils/browser/version': { gecko, fennec, },
'node_modules/web-ext-utils/browser/version': { gecko, },
'node_modules/web-ext-utils/loader/': { ContentScript, detachFormTab, },
'node_modules/web-ext-utils/loader/views': Views,
'node_modules/web-ext-utils/update/': updated,
Expand All @@ -10,14 +10,13 @@
'common/options': options,
Fallback, // loading this on demand is to slow in fennec
Loader,
remote,
remote: _, // the remote plugin handler just needs to be loaded early
require,
}) => {

let debug; options.debug.whenChange(([ value, ]) => { debug = value; require('node_modules/web-ext-utils/loader/').debug = debug >= 2; });
debug && console.info(manifest.name, 'loaded, updated', updated);

void remote; // the remote plugin handler just needs to be loaded early

// Messages
Messages.addHandler(function getPreview() { return Loader.getPreview(this, ...arguments); }); // eslint-disable-line no-invalid-this
Expand Down
8 changes: 3 additions & 5 deletions background/loaders/wikipedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const options = (await register({
priority: 2,
includes: [
'*://*.wikipedia.org/wiki/*', '*://*.mediawiki.org/wiki/*',
String.raw`^https?://.*\.wiki[^\.\/]*?\.org/wiki/.*$`,
String.raw`^https?://([\w-]+\.)*wiki[\w.-]*\.org/wiki/.*$`,
'*://*.gamepedia.com/*',
// String.raw`^https?:\/\/([\w-]+\.)*?(wiki[\w-]+|[\w-]+pedia)(\.[\w-]+)*\/wiki\/.*$`, //
],
options: {
getApiPath: {
Expand Down Expand Up @@ -92,10 +93,7 @@ function getApiPath(url) {
if ((/(?:^|\.)(?:wiki[^\.]*?|mediawiki)\.org$/).test(url.hostname)) {
return 'https://'+ url.host +'/w/api.php'; // always use https
}
if ((/(?:^|\.)gamepedia\.com$/).test(url.hostname)) {
return 'https://'+ url.host +'/api.php'; // always use https
}
return null;
return 'https://'+ url.host +'/api.php'; // always use https
}

function getArticleName(url) {
Expand Down
21 changes: 20 additions & 1 deletion background/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,29 @@ function image({ src, img, title, description, base, dpi, }) { return (


function setFunctionOnChange(loader, options, func, name = func.name) {
async function getEvaluator() {
if (evaluator) { return evaluator; }
return (evaluator = (await new (await require.async('../common/evaluator'))({ init: function() {
const frozen = new Set;
const freeze = object => {
if ((typeof object !== 'object' && typeof object !== 'function') || object === null || frozen.has(object)) { return; }
frozen.add(object);
Object.getOwnPropertyNames(object).forEach(key => { try { freeze(object[key]); } catch (_) { } });
Object.getOwnPropertySymbols(object).forEach(key => { try { freeze(object[key]); } catch (_) { } });
// try { freeze(Object.getPrototypeOf(object)); } catch (_) { }
};
[ 'Object', 'Array', 'Function', 'Math', 'Error', 'TypeError', 'String', 'Number', 'Boolean', 'Symbol', 'RegExp', 'Promise', ]
.forEach(prop => {
Object.defineProperty(window, prop, { writable: false, configurable: false, });
freeze(window[prop]);
});
frozen.forEach(Object.freeze);
}, })));
} let evaluator;
options[name].whenChange(async ([ value, ]) => { try {
loader[name].destroy && loader[name].destroy();
loader[name] = options[name].values.isSet
? (await require.async('./evaluator')).newFunction('url', value) : func;
? (await getEvaluator()).newFunction('url', value) : func;
return loader[name].ready;
} catch (error) { reportError(`Could not compile "${ name }" for "${ loader.name }"`, error); throw error; } });
}
Expand Down
91 changes: 91 additions & 0 deletions common/evaluator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
(function(global) { 'use strict'; define(async ({ // 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/.
'common/sandbox': Sandbox,
}) => {

/**
* Wrapper around a sandboxed iframe that can safely evaluate user input as JavaScript code.
*/
class Evaluator extends Sandbox {

/**
* @param {function?} .init Optional function that does the initial setup inside the sandbox.
* Gets re-compiled and called with one end of the Port connection as the only argument.
* @param {properties} ...options Other options forwarded to `makeSandbox`.
*/
/* async */ constructor({ init, ...options } = { }) { return (async () => { let self; {
// TODO: could append `options.srcUrl` to `eval`ed scripts
self = (await super(setup, options));
(await self.post('init', typeof init === 'function' ? init +'' : null));
} return self; })(); }

/**
* `eval()`s code in the global scope and returns a JSON clone of the (resolved) value.
* @param {string} code Code to execute.
* @return {JSON} Resolved value returned by the evaluated code as a JSON clone.
*/
async eval(code) {
return this.request(':eval', code);
}

/**
* Creates a `new AsyncFunction` in the sandbox and returns a function stub that calls it.
* @param {...[string]} args Arguments forwarded to the function constructor.
* @return {async function} Stub function that calls the function with JSON cloned arguments
* and asynchronously returns a JSON clone of its return value.
* @method destroy Deletes the remote function.
* @property ready Promise that needs to resolve before the `.length` has a useful value.
*/
newFunction(...args) {
const self = this;
const id = Math.random().toString(32).slice(2);
const stub = async function() { return self.request(':F()', id, ...arguments); };
Object.defineProperty(stub, 'ready', { value: self.request(':new F', id, ...args).then(length => {
Object.defineProperty(stub, 'length', { value: length, }); return stub;
}), });
Object.defineProperty(stub, 'destroy', { value() { try { self.post(':~F', id); } catch (_) { } }, });
return stub;
}

}

//// start implementation

function setup(port) {
const AsyncFunction = (async x=>x).constructor;
const globEval = eval; // eval in global scope
const functions = { };
port.addHandlers({
async init(code) {
try { code && (await globEval(code)(port)); }
finally { port.removeHandler('init'); }
},
async ':eval'(code) {
return globEval(code);
},
':new F'(id, ...args) {
const func = functions[id] = new AsyncFunction(...args);
return func.length;
},
':F()'(id, ...args) {
const func = functions[id];
if (!func) { throw new TypeError(`Dead remote function called`); }
return func(...args);
},
':~F'(id) {
delete functions[id];
},
});
}

return Evaluator;

/*
E = await new (await require.async('background/evaluator'))({ init: port => {
window.openTab = url => port.request('browser.tabs.create', { url, });
}, });
E.addHandlers('browser.tabs.', Browser.tabs);
F = E.newFunction('url', 'openTab(url)'); await F("https://example.com"); F.destroy();
*/


}); })(this);
Loading

0 comments on commit bd7c397

Please sign in to comment.