|
|
@@ -1,300 +1,273 @@
|
|
|
-// Avoid running repeatedly due to new `document.documentElement`
|
|
|
-if (window.VM) return;
|
|
|
-window.VM = 1;
|
|
|
+(function injectContent() {
|
|
|
+ // Avoid running repeatedly due to new `document.documentElement`
|
|
|
+ if (window.VM) return;
|
|
|
+ window.VM = 1;
|
|
|
|
|
|
-function getUniqId() {
|
|
|
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
|
-}
|
|
|
-function noop() {}
|
|
|
-function includes(arr, item) {
|
|
|
- for (var i = arr.length; i --;) {
|
|
|
- if (arr[i] === item) return true;
|
|
|
+ function getUniqId() {
|
|
|
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
|
}
|
|
|
- return false;
|
|
|
-}
|
|
|
-function forEach(arr, func) {
|
|
|
- var length = arr && arr.length || 0;
|
|
|
- for (var i = 0; i < length; i ++) func(arr[i], i, arr);
|
|
|
-}
|
|
|
-function map(arr, func) {
|
|
|
- var comm = this;
|
|
|
- var res = [];
|
|
|
- comm.forEach(arr, function (item, i) {
|
|
|
- res.push(func(item, i, arr));
|
|
|
- });
|
|
|
- return res;
|
|
|
-}
|
|
|
|
|
|
-/**
|
|
|
- * http://www.webtoolkit.info/javascript-utf8.html
|
|
|
- */
|
|
|
-function utf8decode(utftext) {
|
|
|
- var string = '';
|
|
|
- var i = 0;
|
|
|
- var c = 0, c2 = 0, c3 = 0;
|
|
|
- while ( i < utftext.length ) {
|
|
|
- c = utftext.charCodeAt(i);
|
|
|
- if (c < 128) {
|
|
|
- string += String.fromCharCode(c);
|
|
|
- i ++;
|
|
|
- } else if (c > 191 && c < 224) {
|
|
|
- c2 = utftext.charCodeAt(i + 1);
|
|
|
- string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
|
|
- i += 2;
|
|
|
- } else {
|
|
|
- c2 = utftext.charCodeAt(i + 1);
|
|
|
- c3 = utftext.charCodeAt(i + 2);
|
|
|
- string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
|
|
- i += 3;
|
|
|
+ /**
|
|
|
+ * http://www.webtoolkit.info/javascript-utf8.html
|
|
|
+ */
|
|
|
+ function utf8decode(utftext) {
|
|
|
+ /* eslint-disable no-bitwise */
|
|
|
+ let string = '';
|
|
|
+ let i = 0;
|
|
|
+ let c1 = 0;
|
|
|
+ let c2 = 0;
|
|
|
+ let c3 = 0;
|
|
|
+ while (i < utftext.length) {
|
|
|
+ c1 = utftext.charCodeAt(i);
|
|
|
+ if (c1 < 128) {
|
|
|
+ string += String.fromCharCode(c1);
|
|
|
+ i += 1;
|
|
|
+ } else if (c1 > 191 && c1 < 224) {
|
|
|
+ c2 = utftext.charCodeAt(i + 1);
|
|
|
+ string += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
|
|
|
+ i += 2;
|
|
|
+ } else {
|
|
|
+ c2 = utftext.charCodeAt(i + 1);
|
|
|
+ c3 = utftext.charCodeAt(i + 2);
|
|
|
+ string += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
|
|
+ i += 3;
|
|
|
+ }
|
|
|
}
|
|
|
+ return string;
|
|
|
+ /* eslint-enable no-bitwise */
|
|
|
}
|
|
|
- return string;
|
|
|
-}
|
|
|
-
|
|
|
-function sendMessage(data) {
|
|
|
- return browser.runtime.sendMessage(data);
|
|
|
-}
|
|
|
-function getPopup(){
|
|
|
- // XXX: only scripts run in top level window are counted
|
|
|
- top === window && sendMessage({
|
|
|
- cmd: 'SetPopup',
|
|
|
- data: {
|
|
|
- ids: ids,
|
|
|
- menus: menus,
|
|
|
- },
|
|
|
- })
|
|
|
- .catch(noop);
|
|
|
-}
|
|
|
|
|
|
-var badge = {
|
|
|
- number: 0,
|
|
|
- ready: false,
|
|
|
- willSet: false,
|
|
|
-};
|
|
|
-function getBadge() {
|
|
|
- badge.willSet = true;
|
|
|
- setBadge();
|
|
|
-}
|
|
|
-function setBadge() {
|
|
|
- if (badge.ready && badge.willSet) {
|
|
|
- // XXX: only scripts run in top level window are counted
|
|
|
- top === window && sendMessage({cmd: 'SetBadge', data: badge.number});
|
|
|
+ function sendMessage(data) {
|
|
|
+ return browser.runtime.sendMessage(data);
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @desc Wrap methods to prevent unexpected modifications.
|
|
|
- */
|
|
|
-function getWrapper() {
|
|
|
- // http://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
|
|
|
- // http://developer.mozilla.org/docs/Web/API/Window
|
|
|
- var comm = this;
|
|
|
- var wrapper = {};
|
|
|
- // `eval` should be called directly so that it is run in current scope
|
|
|
- wrapper.eval = eval;
|
|
|
- // Wrap methods
|
|
|
- comm.forEach([
|
|
|
- // 'uneval',
|
|
|
- 'isFinite',
|
|
|
- 'isNaN',
|
|
|
- 'parseFloat',
|
|
|
- 'parseInt',
|
|
|
- 'decodeURI',
|
|
|
- 'decodeURIComponent',
|
|
|
- 'encodeURI',
|
|
|
- 'encodeURIComponent',
|
|
|
|
|
|
- 'addEventListener',
|
|
|
- 'alert',
|
|
|
- 'atob',
|
|
|
- 'blur',
|
|
|
- 'btoa',
|
|
|
- 'clearInterval',
|
|
|
- 'clearTimeout',
|
|
|
- 'close',
|
|
|
- 'confirm',
|
|
|
- 'dispatchEvent',
|
|
|
- 'fetch',
|
|
|
- 'find',
|
|
|
- 'focus',
|
|
|
- 'getComputedStyle',
|
|
|
- 'getSelection',
|
|
|
- 'matchMedia',
|
|
|
- 'moveBy',
|
|
|
- 'moveTo',
|
|
|
- 'open',
|
|
|
- 'openDialog',
|
|
|
- 'postMessage',
|
|
|
- 'print',
|
|
|
- 'prompt',
|
|
|
- 'removeEventListener',
|
|
|
- 'requestAnimationFrame',
|
|
|
- 'resizeBy',
|
|
|
- 'resizeTo',
|
|
|
- 'scroll',
|
|
|
- 'scrollBy',
|
|
|
- 'scrollByLines',
|
|
|
- 'scrollByPages',
|
|
|
- 'scrollTo',
|
|
|
- 'setInterval',
|
|
|
- 'setTimeout',
|
|
|
- 'stop',
|
|
|
- ], function (name) {
|
|
|
- var method = window[name];
|
|
|
- if (method) wrapper[name] = function () {
|
|
|
- return method.apply(window, arguments);
|
|
|
- };
|
|
|
- });
|
|
|
- function defineProtectedProperty(name) {
|
|
|
- var modified = false;
|
|
|
- var value;
|
|
|
- Object.defineProperty(wrapper, name, {
|
|
|
- get: function () {
|
|
|
- if (!modified) value = window[name];
|
|
|
- return value === window ? wrapper : value;
|
|
|
- },
|
|
|
- set: function (val) {
|
|
|
- modified = true;
|
|
|
- value = val;
|
|
|
- },
|
|
|
- });
|
|
|
- }
|
|
|
- function defineReactedProperty(name) {
|
|
|
- Object.defineProperty(wrapper, name, {
|
|
|
- get: function () {
|
|
|
- var value = window[name];
|
|
|
- return value === window ? wrapper : value;
|
|
|
- },
|
|
|
- set: function (val) {
|
|
|
- window[name] = val;
|
|
|
- },
|
|
|
- });
|
|
|
+ const badge = {
|
|
|
+ number: 0,
|
|
|
+ ready: false,
|
|
|
+ willSet: false,
|
|
|
+ };
|
|
|
+ function getBadge() {
|
|
|
+ badge.willSet = true;
|
|
|
+ setBadge();
|
|
|
}
|
|
|
- // Wrap properties
|
|
|
- comm.forEach(comm.props, function (name) {
|
|
|
- if (wrapper[name]) return;
|
|
|
- if (name.slice(0, 2) === 'on') defineReactedProperty(name);
|
|
|
- else defineProtectedProperty(name);
|
|
|
- });
|
|
|
- return wrapper;
|
|
|
-}
|
|
|
-// Communicator
|
|
|
-var comm = {
|
|
|
- vmid: 'VM_' + getUniqId(),
|
|
|
- state: 0,
|
|
|
- utf8decode: utf8decode,
|
|
|
- getUniqId: getUniqId,
|
|
|
- props: Object.getOwnPropertyNames(window),
|
|
|
-
|
|
|
- // Array functions
|
|
|
- // Notice: avoid using prototype functions since they may be changed by page scripts
|
|
|
- includes: includes,
|
|
|
- forEach: forEach,
|
|
|
- map: map,
|
|
|
-
|
|
|
- init: function (srcId, destId) {
|
|
|
- var comm = this;
|
|
|
- comm.sid = comm.vmid + srcId;
|
|
|
- comm.did = comm.vmid + destId;
|
|
|
- var handle = comm['handle' + srcId];
|
|
|
- document.addEventListener(comm.sid, function (e) {
|
|
|
- var data = JSON.parse(e.detail);
|
|
|
- handle.call(comm, data);
|
|
|
- }, false);
|
|
|
- comm.load = comm.checkLoad = function () {};
|
|
|
- // check whether the page is injectable via <script>, whether limited by CSP
|
|
|
- try {
|
|
|
- comm.injectable = (0, eval)('true');
|
|
|
- } catch (e) {
|
|
|
- comm.injectable = false;
|
|
|
+ function setBadge() {
|
|
|
+ if (badge.ready && badge.willSet) {
|
|
|
+ // XXX: only scripts run in top level window are counted
|
|
|
+ if (top === window) sendMessage({ cmd: 'SetBadge', data: badge.number });
|
|
|
}
|
|
|
- },
|
|
|
- post: function (data) {
|
|
|
- // Firefox issue: data must be stringified to avoid cross-origin problem
|
|
|
- var e = new CustomEvent(this.did, {detail: JSON.stringify(data)});
|
|
|
- document.dispatchEvent(e);
|
|
|
- },
|
|
|
- handleR: function (obj) {
|
|
|
- var comm = this;
|
|
|
- var maps = {
|
|
|
- LoadScript: comm.loadScript.bind(comm),
|
|
|
- Command: function (data) {
|
|
|
- var func = comm.command[data];
|
|
|
- func && func();
|
|
|
+ }
|
|
|
+ function handleWeb(obj) {
|
|
|
+ const comm = this;
|
|
|
+ const handlers = {
|
|
|
+ LoadScripts(data) {
|
|
|
+ comm.onLoadScripts(data);
|
|
|
+ },
|
|
|
+ Command(data) {
|
|
|
+ const func = comm.command[data];
|
|
|
+ if (func) func();
|
|
|
},
|
|
|
- GotRequestId: function (id) {
|
|
|
- comm.qrequests.shift().start(id);
|
|
|
+ GotRequestId(id) {
|
|
|
+ const req = comm.qrequests.shift();
|
|
|
+ req.start(req, id);
|
|
|
},
|
|
|
- HttpRequested: function (r) {
|
|
|
- var req = comm.requests[r.id];
|
|
|
- if (req) req.callback(r);
|
|
|
+ HttpRequested(res) {
|
|
|
+ const req = comm.requests[res.id];
|
|
|
+ if (req) req.callback(req, res);
|
|
|
},
|
|
|
- UpdateValues: function (data) {
|
|
|
- var values = comm.values;
|
|
|
+ UpdateValues(data) {
|
|
|
+ const { values } = comm.values;
|
|
|
if (values && values[data.uri]) values[data.uri] = data.values;
|
|
|
},
|
|
|
- NotificationClicked: function (id) {
|
|
|
- var options = comm.notif[id];
|
|
|
+ NotificationClicked(id) {
|
|
|
+ const options = comm.notif[id];
|
|
|
if (options) {
|
|
|
- var onclick = options.onclick;
|
|
|
- onclick && onclick();
|
|
|
+ const { onclick } = options;
|
|
|
+ if (onclick) onclick();
|
|
|
}
|
|
|
},
|
|
|
- NotificationClosed: function (id) {
|
|
|
- var options = comm.notif[id];
|
|
|
+ NotificationClosed(id) {
|
|
|
+ const options = comm.notif[id];
|
|
|
if (options) {
|
|
|
delete comm.notif[id];
|
|
|
- var ondone = options.ondone;
|
|
|
- ondone && ondone();
|
|
|
+ const { ondone } = options;
|
|
|
+ if (ondone) ondone();
|
|
|
}
|
|
|
},
|
|
|
// advanced inject
|
|
|
- Injected: function (id) {
|
|
|
- var obj = comm.ainject[id];
|
|
|
- var func = window['VM_' + id];
|
|
|
- delete window['VM_' + id];
|
|
|
+ Injected(id) {
|
|
|
+ const item = comm.ainject[id];
|
|
|
+ const func = window[`VM_${id}`];
|
|
|
+ delete window[`VM_${id}`];
|
|
|
delete comm.ainject[id];
|
|
|
- if (obj && func) comm.runCode(obj[0], func, obj[1], obj[2]);
|
|
|
+ if (item && func) comm.runCode(item[0], func, item[1], item[2]);
|
|
|
},
|
|
|
};
|
|
|
- var func = maps[obj.cmd];
|
|
|
- if (func) func(obj.data);
|
|
|
- },
|
|
|
- runCode: function (name, func, args, thisObj) {
|
|
|
- try {
|
|
|
- func.apply(thisObj, args);
|
|
|
- } catch (e) {
|
|
|
- var msg = 'Error running script: ' + name + '\n' + e;
|
|
|
- if (e.message) msg += '\n' + e.message;
|
|
|
- console.error(msg);
|
|
|
+ const handle = handlers[obj.cmd];
|
|
|
+ if (handle) handle(obj.data);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @desc Wrap methods to prevent unexpected modifications.
|
|
|
+ */
|
|
|
+ function getWrapper() {
|
|
|
+ // http://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
|
|
|
+ // http://developer.mozilla.org/docs/Web/API/Window
|
|
|
+ const comm = this;
|
|
|
+ const wrapper = {};
|
|
|
+ comm.forEach([
|
|
|
+ // `eval` should be called directly so that it is run in current scope
|
|
|
+ 'eval',
|
|
|
+ ], name => {
|
|
|
+ wrapper[name] = window[name];
|
|
|
+ });
|
|
|
+ comm.forEach([
|
|
|
+ // 'uneval',
|
|
|
+ 'isFinite',
|
|
|
+ 'isNaN',
|
|
|
+ 'parseFloat',
|
|
|
+ 'parseInt',
|
|
|
+ 'decodeURI',
|
|
|
+ 'decodeURIComponent',
|
|
|
+ 'encodeURI',
|
|
|
+ 'encodeURIComponent',
|
|
|
+
|
|
|
+ 'addEventListener',
|
|
|
+ 'alert',
|
|
|
+ 'atob',
|
|
|
+ 'blur',
|
|
|
+ 'btoa',
|
|
|
+ 'clearInterval',
|
|
|
+ 'clearTimeout',
|
|
|
+ 'close',
|
|
|
+ 'confirm',
|
|
|
+ 'dispatchEvent',
|
|
|
+ 'fetch',
|
|
|
+ 'find',
|
|
|
+ 'focus',
|
|
|
+ 'getComputedStyle',
|
|
|
+ 'getSelection',
|
|
|
+ 'matchMedia',
|
|
|
+ 'moveBy',
|
|
|
+ 'moveTo',
|
|
|
+ 'open',
|
|
|
+ 'openDialog',
|
|
|
+ 'postMessage',
|
|
|
+ 'print',
|
|
|
+ 'prompt',
|
|
|
+ 'removeEventListener',
|
|
|
+ 'requestAnimationFrame',
|
|
|
+ 'resizeBy',
|
|
|
+ 'resizeTo',
|
|
|
+ 'scroll',
|
|
|
+ 'scrollBy',
|
|
|
+ 'scrollByLines',
|
|
|
+ 'scrollByPages',
|
|
|
+ 'scrollTo',
|
|
|
+ 'setInterval',
|
|
|
+ 'setTimeout',
|
|
|
+ 'stop',
|
|
|
+ ], name => {
|
|
|
+ const method = window[name];
|
|
|
+ if (method) {
|
|
|
+ wrapper[name] = (...args) => method.apply(window, args);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ function defineProtectedProperty(name) {
|
|
|
+ let modified = false;
|
|
|
+ let value;
|
|
|
+ Object.defineProperty(wrapper, name, {
|
|
|
+ get() {
|
|
|
+ if (!modified) value = window[name];
|
|
|
+ return value === window ? wrapper : value;
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ modified = true;
|
|
|
+ value = val;
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ function defineReactedProperty(name) {
|
|
|
+ Object.defineProperty(wrapper, name, {
|
|
|
+ get() {
|
|
|
+ const value = window[name];
|
|
|
+ return value === window ? wrapper : value;
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ window[name] = val;
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // Wrap properties
|
|
|
+ comm.forEach(comm.props, name => {
|
|
|
+ if (name in wrapper) return;
|
|
|
+ if (name.slice(0, 2) === 'on') defineReactedProperty(name);
|
|
|
+ else defineProtectedProperty(name);
|
|
|
+ });
|
|
|
+ return wrapper;
|
|
|
+ }
|
|
|
+ function initHandler(src, dest) {
|
|
|
+ const comm = this;
|
|
|
+ comm.sid = comm.vmid + src;
|
|
|
+ comm.did = comm.vmid + dest;
|
|
|
+ const handle = comm[`handle_${src}`];
|
|
|
+ document.addEventListener(comm.sid, e => {
|
|
|
+ const data = JSON.parse(e.detail);
|
|
|
+ handle.call(comm, data);
|
|
|
+ }, false);
|
|
|
+ comm.load = comm.noop;
|
|
|
+ comm.checkLoad = comm.noop;
|
|
|
+ }
|
|
|
+ function postData(data) {
|
|
|
+ const comm = this;
|
|
|
+ // Firefox issue: data must be stringified to avoid cross-origin problem
|
|
|
+ const e = new CustomEvent(comm.did, { detail: JSON.stringify(data) });
|
|
|
+ document.dispatchEvent(e);
|
|
|
+ }
|
|
|
+ function getRequest(arg) {
|
|
|
+ const comm = this;
|
|
|
+ init();
|
|
|
+ return comm.getRequest(arg);
|
|
|
+ function init() {
|
|
|
+ comm.requests = {};
|
|
|
+ comm.qrequests = [];
|
|
|
+ comm.getRequest = details => {
|
|
|
+ const req = {
|
|
|
+ details,
|
|
|
+ callback,
|
|
|
+ start,
|
|
|
+ req: {
|
|
|
+ abort: reqAbort,
|
|
|
+ },
|
|
|
+ };
|
|
|
+ details.url = getFullUrl(details.url);
|
|
|
+ comm.qrequests.push(req);
|
|
|
+ comm.post({ cmd: 'GetRequestId' });
|
|
|
+ return req.req;
|
|
|
+ };
|
|
|
}
|
|
|
- },
|
|
|
- initRequest: function () {
|
|
|
- // request functions
|
|
|
- function reqAbort(){
|
|
|
- comm.post({cmd: 'AbortRequest', data: this.id});
|
|
|
+ function reqAbort() {
|
|
|
+ comm.post({ cmd: 'AbortRequest', data: this.id });
|
|
|
}
|
|
|
function parseData(req, details) {
|
|
|
if (req.resType) {
|
|
|
// blob or arraybuffer
|
|
|
- var data = req.data.response.split(',');
|
|
|
- var mimetype = data[0].match(/^data:(.*?);base64$/);
|
|
|
+ let data = req.data.response.split(',');
|
|
|
+ const mimetype = data[0].match(/^data:(.*?);base64$/);
|
|
|
if (!mimetype) {
|
|
|
// invalid
|
|
|
req.data.response = null;
|
|
|
} else {
|
|
|
data = window.atob(data[1]);
|
|
|
- var arr = new window.Uint8Array(data.length);
|
|
|
- for (var i = 0; i < data.length; i ++) arr[i] = data.charCodeAt(i);
|
|
|
- if (details.responseType == 'blob') {
|
|
|
+ const arr = new window.Uint8Array(data.length);
|
|
|
+ for (let i = 0; i < data.length; i += 1) arr[i] = data.charCodeAt(i);
|
|
|
+ if (details.responseType === 'blob') {
|
|
|
// blob
|
|
|
- return new Blob([arr], {type: mimetype});
|
|
|
- } else {
|
|
|
- // arraybuffer
|
|
|
- return arr.buffer;
|
|
|
+ return new Blob([arr], { type: mimetype });
|
|
|
}
|
|
|
+ // arraybuffer
|
|
|
+ return arr.buffer;
|
|
|
}
|
|
|
- } else if (details.responseType == 'json') {
|
|
|
+ } else if (details.responseType === 'json') {
|
|
|
// json
|
|
|
return JSON.parse(req.data.response);
|
|
|
} else {
|
|
|
@@ -303,106 +276,74 @@ var comm = {
|
|
|
}
|
|
|
}
|
|
|
// request object functions
|
|
|
- function callback(req) {
|
|
|
- var t = this;
|
|
|
- var cb = t.details['on' + req.type];
|
|
|
+ function callback(req, res) {
|
|
|
+ const cb = req.details[`on${res.type}`];
|
|
|
if (cb) {
|
|
|
- if (req.data.response) {
|
|
|
- if (!t.data) t.data = [parseData(req, t.details)];
|
|
|
- req.data.response = t.data[0];
|
|
|
+ if (res.data.response) {
|
|
|
+ if (!req.data) req.data = [parseData(res, req.details)];
|
|
|
+ res.data.response = req.data[0];
|
|
|
}
|
|
|
- cb(req.data);
|
|
|
+ cb(res.data);
|
|
|
}
|
|
|
- if (req.type == 'loadend') delete comm.requests[t.id];
|
|
|
+ if (res.type === 'loadend') delete comm.requests[req.id];
|
|
|
}
|
|
|
- function start(id) {
|
|
|
- var t = this;
|
|
|
- var data = {
|
|
|
- id: id,
|
|
|
- method: t.details.method,
|
|
|
- url: t.details.url,
|
|
|
- data: t.details.data,
|
|
|
- //async: !t.details.synchronous,
|
|
|
- user: t.details.user,
|
|
|
- password: t.details.password,
|
|
|
- headers: t.details.headers,
|
|
|
- overrideMimeType: t.details.overrideMimeType,
|
|
|
+ function start(req, id) {
|
|
|
+ const data = {
|
|
|
+ id,
|
|
|
+ method: req.details.method,
|
|
|
+ url: req.details.url,
|
|
|
+ data: req.details.data,
|
|
|
+ // async: !req.details.synchronous,
|
|
|
+ user: req.details.user,
|
|
|
+ password: req.details.password,
|
|
|
+ headers: req.details.headers,
|
|
|
+ overrideMimeType: req.details.overrideMimeType,
|
|
|
};
|
|
|
- t.id = id;
|
|
|
- comm.requests[id] = t;
|
|
|
- if (comm.includes(['arraybuffer', 'blob'], t.details.responseType)) {
|
|
|
+ req.id = id;
|
|
|
+ comm.requests[id] = req;
|
|
|
+ if (comm.includes(['arraybuffer', 'blob'], req.details.responseType)) {
|
|
|
data.responseType = 'blob';
|
|
|
}
|
|
|
- comm.post({cmd: 'HttpRequest', data: data});
|
|
|
+ comm.post({ cmd: 'HttpRequest', data });
|
|
|
}
|
|
|
function getFullUrl(url) {
|
|
|
- var a = document.createElement('a');
|
|
|
+ const a = document.createElement('a');
|
|
|
a.setAttribute('href', url);
|
|
|
return a.href;
|
|
|
}
|
|
|
-
|
|
|
- var comm = this;
|
|
|
- comm.requests = {};
|
|
|
- comm.qrequests = [];
|
|
|
- comm.Request = function (details) {
|
|
|
- var t = {
|
|
|
- details: details,
|
|
|
- callback: callback,
|
|
|
- start: start,
|
|
|
- req: {
|
|
|
- abort: reqAbort,
|
|
|
- },
|
|
|
- };
|
|
|
- details.url = getFullUrl(details.url);
|
|
|
- comm.qrequests.push(t);
|
|
|
- comm.post({cmd: 'GetRequestId'});
|
|
|
- return t.req;
|
|
|
- };
|
|
|
- },
|
|
|
- getWrapper: getWrapper,
|
|
|
- wrapGM: function (script, cache) {
|
|
|
- function getValues() {
|
|
|
- return comm.values[script.uri];
|
|
|
- }
|
|
|
- function propertyToString() {
|
|
|
- return '[Violentmonkey property]';
|
|
|
- }
|
|
|
- function addProperty(name, prop, obj) {
|
|
|
- if ('value' in prop) prop.writable = false;
|
|
|
- prop.configurable = false;
|
|
|
- Object.defineProperty(obj, name, prop);
|
|
|
- if (typeof obj[name] == 'function') obj[name].toString = propertyToString;
|
|
|
- }
|
|
|
- function saveValues() {
|
|
|
- comm.post({
|
|
|
- cmd: 'SetValue',
|
|
|
- data: {
|
|
|
- uri: script.uri,
|
|
|
- values: getValues(),
|
|
|
- },
|
|
|
- });
|
|
|
- }
|
|
|
+ }
|
|
|
+ function wrapGM(script, cache) {
|
|
|
// Add GM functions
|
|
|
// Reference: http://wiki.greasespot.net/Greasemonkey_Manual:API
|
|
|
- var comm = this;
|
|
|
- var gm = {};
|
|
|
- var grant = script.meta.grant || [];
|
|
|
- var urls = {};
|
|
|
- if (!grant.length || grant.length == 1 && grant[0] == 'none') {
|
|
|
+ const comm = this;
|
|
|
+ const gm = {};
|
|
|
+ const grant = script.meta.grant || [];
|
|
|
+ const urls = {};
|
|
|
+ if (!grant.length || (grant.length === 1 && grant[0] === 'none')) {
|
|
|
// @grant none
|
|
|
grant.pop();
|
|
|
} else {
|
|
|
- gm['window'] = comm.getWrapper();
|
|
|
+ gm.window = comm.getWrapper();
|
|
|
}
|
|
|
if (!comm.includes(grant, 'unsafeWindow')) grant.push('unsafeWindow');
|
|
|
if (!comm.includes(grant, 'GM_info')) grant.push('GM_info');
|
|
|
- var resources = script.meta.resources || {};
|
|
|
- var gm_funcs = {
|
|
|
- unsafeWindow: {value: window},
|
|
|
+ const resources = script.meta.resources || {};
|
|
|
+ const dataEncoders = {
|
|
|
+ o: val => JSON.stringify(val),
|
|
|
+ '': val => val.toString(),
|
|
|
+ };
|
|
|
+ const dataDecoders = {
|
|
|
+ n: val => Number(val),
|
|
|
+ b: val => val === 'true',
|
|
|
+ o: val => JSON.parse(val),
|
|
|
+ '': val => val,
|
|
|
+ };
|
|
|
+ const gmFunctions = {
|
|
|
+ unsafeWindow: { value: window },
|
|
|
GM_info: {
|
|
|
- get: function () {
|
|
|
- var m = script.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/);
|
|
|
- var data = {
|
|
|
+ get() {
|
|
|
+ const matches = script.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/);
|
|
|
+ const data = {
|
|
|
description: script.meta.description || '',
|
|
|
excludes: script.meta.exclude.concat(),
|
|
|
includes: script.meta.include.concat(),
|
|
|
@@ -414,159 +355,146 @@ var comm = {
|
|
|
unwrap: false,
|
|
|
version: script.meta.version || '',
|
|
|
};
|
|
|
- var obj = {};
|
|
|
- addProperty('scriptMetaStr', {value: m ? m[1] : ''}, obj);
|
|
|
+ const obj = {};
|
|
|
+ addProperty('scriptMetaStr', { value: matches ? matches[1] : '' }, obj);
|
|
|
|
|
|
// whether update is allowed
|
|
|
- addProperty('scriptWillUpdate', {value: !!script.update}, obj);
|
|
|
+ addProperty('scriptWillUpdate', { value: !!script.update }, obj);
|
|
|
|
|
|
// Violentmonkey specific data
|
|
|
- addProperty('version', {value: comm.version}, obj);
|
|
|
- addProperty('scriptHandler', {value: 'Violentmonkey'}, obj);
|
|
|
+ addProperty('version', { value: comm.version }, obj);
|
|
|
+ addProperty('scriptHandler', { value: 'Violentmonkey' }, obj);
|
|
|
|
|
|
// script object
|
|
|
- addProperty('script', {value:{}}, obj);
|
|
|
- var i;
|
|
|
- for (i in data) addProperty(i, {value: data[i]}, obj.script);
|
|
|
- for (i in script.meta.resources) {
|
|
|
- addProperty(i, {value: script.meta.resources[i]}, obj.script.resources);
|
|
|
- }
|
|
|
+ addProperty('script', { value: {} }, obj);
|
|
|
+ comm.forEach(Object.keys(resources), name => {
|
|
|
+ addProperty(name, { value: resources[name] }, data.resources);
|
|
|
+ });
|
|
|
+ comm.forEach(Object.keys(data), name => {
|
|
|
+ addProperty(name, { value: data[name] }, obj.script);
|
|
|
+ });
|
|
|
return obj;
|
|
|
},
|
|
|
},
|
|
|
GM_deleteValue: {
|
|
|
- value: function (key) {
|
|
|
- var values = getValues();
|
|
|
+ value(key) {
|
|
|
+ const values = getValues();
|
|
|
delete values[key];
|
|
|
saveValues();
|
|
|
},
|
|
|
},
|
|
|
GM_getValue: {
|
|
|
- value: function (key, val) {
|
|
|
- var values = getValues();
|
|
|
- var v = values[key];
|
|
|
- if (v) {
|
|
|
- var type = v[0];
|
|
|
- v = v.slice(1);
|
|
|
- switch (type) {
|
|
|
- case 'n':
|
|
|
- val = Number(v);
|
|
|
- break;
|
|
|
- case 'b':
|
|
|
- val = v == 'true';
|
|
|
- break;
|
|
|
- case 'o':
|
|
|
- try {
|
|
|
- val = JSON.parse(v);
|
|
|
- } catch (e) {
|
|
|
- console.warn(e);
|
|
|
- }
|
|
|
- break;
|
|
|
- default:
|
|
|
- val = v;
|
|
|
+ value(key, def) {
|
|
|
+ const values = getValues();
|
|
|
+ const raw = values[key];
|
|
|
+ if (raw) {
|
|
|
+ const type = raw[0];
|
|
|
+ const handle = dataDecoders[type] || dataDecoders[''];
|
|
|
+ let value = raw.slice(1);
|
|
|
+ try {
|
|
|
+ value = handle(value);
|
|
|
+ } catch (e) {
|
|
|
+ console.warn(e);
|
|
|
}
|
|
|
+ return value;
|
|
|
}
|
|
|
- return val;
|
|
|
+ return def;
|
|
|
},
|
|
|
},
|
|
|
GM_listValues: {
|
|
|
- value: function () {
|
|
|
- return Object.getOwnPropertyNames(getValues());
|
|
|
+ value() {
|
|
|
+ return Object.keys(getValues());
|
|
|
},
|
|
|
},
|
|
|
GM_setValue: {
|
|
|
- value: function (key, val) {
|
|
|
- var type = (typeof val)[0];
|
|
|
- switch (type) {
|
|
|
- case 'o':
|
|
|
- val = type + JSON.stringify(val);
|
|
|
- break;
|
|
|
- default:
|
|
|
- val = type + val;
|
|
|
- }
|
|
|
- var values = getValues();
|
|
|
- values[key] = val;
|
|
|
+ value(key, val) {
|
|
|
+ const type = (typeof val)[0];
|
|
|
+ const handle = dataEncoders[type] || dataEncoders[''];
|
|
|
+ let value = val;
|
|
|
+ value = type + handle(value);
|
|
|
+ const values = getValues();
|
|
|
+ values[key] = value;
|
|
|
saveValues();
|
|
|
},
|
|
|
},
|
|
|
GM_getResourceText: {
|
|
|
- value: function (name) {
|
|
|
- for (var i in resources) if (name == i) {
|
|
|
- var text = cache[resources[i]];
|
|
|
- if (text) text = comm.utf8decode(window.atob(text));
|
|
|
+ value(name) {
|
|
|
+ if (name in resources) {
|
|
|
+ const uri = resources[name];
|
|
|
+ const raw = cache[uri];
|
|
|
+ const text = raw && comm.utf8decode(window.atob(raw));
|
|
|
return text;
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
GM_getResourceURL: {
|
|
|
- value: function (name) {
|
|
|
- for (var k in resources) if (name == k) {
|
|
|
- var key = resources[k];
|
|
|
- var url = urls[key];
|
|
|
- if (!url) {
|
|
|
- var cc = cache[key];
|
|
|
- if (cc) {
|
|
|
+ value(name) {
|
|
|
+ if (name in resources) {
|
|
|
+ const key = resources[name];
|
|
|
+ let blobUrl = urls[key];
|
|
|
+ if (!blobUrl) {
|
|
|
+ const raw = cache[key];
|
|
|
+ if (raw) {
|
|
|
// Binary string is not supported by blob constructor,
|
|
|
// so we have to transform it into array buffer.
|
|
|
- var bin = window.atob(cc);
|
|
|
- var arr = new window.Uint8Array(bin.length);
|
|
|
- for (var i = 0; i < bin.length; i ++) {
|
|
|
- arr[i] = bin.charCodeAt(i);
|
|
|
- }
|
|
|
- var b = new Blob([arr]);
|
|
|
- urls[key] = url = URL.createObjectURL(b);
|
|
|
+ const bin = window.atob(raw);
|
|
|
+ const arr = new window.Uint8Array(bin.length);
|
|
|
+ for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
|
|
|
+ const blob = new Blob([arr]);
|
|
|
+ blobUrl = URL.createObjectURL(blob);
|
|
|
+ urls[key] = blobUrl;
|
|
|
} else {
|
|
|
- url = key;
|
|
|
+ blobUrl = key;
|
|
|
}
|
|
|
}
|
|
|
- return url;
|
|
|
+ return blobUrl;
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
},
|
|
|
GM_addStyle: {
|
|
|
- value: function (css) {
|
|
|
- comm.post({cmd: 'AddStyle', data: css});
|
|
|
+ value(data) {
|
|
|
+ comm.post({ cmd: 'AddStyle', data });
|
|
|
},
|
|
|
},
|
|
|
GM_log: {
|
|
|
- value: function (data) {
|
|
|
- console.log(data); // eslint-disable-line no-console
|
|
|
+ value(data) {
|
|
|
+ console.log(`[Violentmonkey][${script.meta.name || 'No name'}]`, data); // eslint-disable-line no-console
|
|
|
},
|
|
|
},
|
|
|
GM_openInTab: {
|
|
|
- value: function (url, background) {
|
|
|
- comm.post({cmd: 'OpenTab', data: {url: url, active: !background}});
|
|
|
+ value(url, background) {
|
|
|
+ comm.post({ cmd: 'OpenTab', data: { url, active: !background } });
|
|
|
},
|
|
|
},
|
|
|
GM_registerMenuCommand: {
|
|
|
- value: function (cap, func, acc) {
|
|
|
+ value(cap, func, acc) {
|
|
|
comm.command[cap] = func;
|
|
|
- comm.post({cmd: 'RegisterMenu', data: [cap, acc]});
|
|
|
+ comm.post({ cmd: 'RegisterMenu', data: [cap, acc] });
|
|
|
},
|
|
|
},
|
|
|
GM_xmlhttpRequest: {
|
|
|
- value: function (details) {
|
|
|
- if (!comm.Request) comm.initRequest();
|
|
|
- return comm.Request(details);
|
|
|
+ value(details) {
|
|
|
+ return comm.getRequest(details);
|
|
|
},
|
|
|
},
|
|
|
GM_notification: {
|
|
|
- value: function (text, title, image, onclick) {
|
|
|
- if (!text) {
|
|
|
- throw 'Invalid parameters.';
|
|
|
- }
|
|
|
- var options = typeof text === 'object' ? text : {
|
|
|
- text: text,
|
|
|
- title: title,
|
|
|
- image: image,
|
|
|
- onclick: onclick,
|
|
|
+ value(text, title, image, onclick) {
|
|
|
+ const options = typeof text === 'object' ? text : {
|
|
|
+ text,
|
|
|
+ title,
|
|
|
+ image,
|
|
|
+ onclick,
|
|
|
};
|
|
|
- var id = comm.notif[''] = (comm.notif[''] || 0) + 1;
|
|
|
+ if (!options.text) {
|
|
|
+ throw new Error('GM_notification: `text` is required!');
|
|
|
+ }
|
|
|
+ const id = (comm.notif[''] || 0) + 1;
|
|
|
+ comm.notif[''] = id;
|
|
|
comm.notif[id] = options;
|
|
|
comm.post({
|
|
|
cmd: 'Notification',
|
|
|
data: {
|
|
|
- id: id,
|
|
|
+ id,
|
|
|
text: options.text,
|
|
|
title: options.title,
|
|
|
image: options.image,
|
|
|
@@ -575,255 +503,315 @@ var comm = {
|
|
|
},
|
|
|
},
|
|
|
GM_setClipboard: {
|
|
|
- value: function (text, type) {
|
|
|
+ value(data, type) {
|
|
|
comm.post({
|
|
|
cmd: 'SetClipboard',
|
|
|
- data: {
|
|
|
- type: type,
|
|
|
- data: text,
|
|
|
- },
|
|
|
+ data: { type, data },
|
|
|
});
|
|
|
},
|
|
|
},
|
|
|
};
|
|
|
- comm.forEach(grant, function (name) {
|
|
|
- var prop = gm_funcs[name];
|
|
|
- prop && addProperty(name, prop, gm);
|
|
|
+ comm.forEach(grant, name => {
|
|
|
+ const prop = gmFunctions[name];
|
|
|
+ if (prop) addProperty(name, prop, gm);
|
|
|
});
|
|
|
return gm;
|
|
|
- },
|
|
|
- loadScript: function (data) {
|
|
|
- function buildCode(script) {
|
|
|
- var require = script.meta.require || [];
|
|
|
- var wrapper = comm.wrapGM(script, data.cache);
|
|
|
- // Must use Object.getOwnPropertyNames to list unenumerable properties
|
|
|
- var wrapperKeys = Object.getOwnPropertyNames(wrapper);
|
|
|
- var code = [
|
|
|
- comm.map(wrapperKeys, function (name) {
|
|
|
- return 'this["' + name + '"]=' + name;
|
|
|
- }).join(';') + ';with(this)!function(){',
|
|
|
- ];
|
|
|
- comm.forEach(require, function (key) {
|
|
|
- var script = data.require[key];
|
|
|
- if (script) {
|
|
|
- code.push(script);
|
|
|
- // Add `;` to a new line in case script ends with comment lines
|
|
|
- code.push(';');
|
|
|
- }
|
|
|
- });
|
|
|
- // wrap code to make 'use strict' work
|
|
|
- code.push('!function(){' + script.code + '\n}.call(this)');
|
|
|
- code.push('}.call(this);');
|
|
|
- code = code.join('\n');
|
|
|
- var name = script.custom.name || script.meta.name || script.id;
|
|
|
- var args = comm.map(wrapperKeys, function (key) {return wrapper[key];});
|
|
|
- var thisObj = wrapper.window || wrapper;
|
|
|
- if (comm.injectable) {
|
|
|
- // normal injection
|
|
|
- try {
|
|
|
- var func = Function.apply(null, wrapperKeys.concat([code]));
|
|
|
- } catch (e) {
|
|
|
- console.error('Syntax error in script: ' + name + '\n' + e.message);
|
|
|
- return;
|
|
|
- }
|
|
|
- comm.runCode(name, func, args, thisObj);
|
|
|
- } else {
|
|
|
- // advanced injection
|
|
|
- var id = comm.getUniqId();
|
|
|
- comm.ainject[id] = [name, args, thisObj];
|
|
|
- comm.post({cmd: 'Inject', data: [id, wrapperKeys, code]});
|
|
|
- }
|
|
|
+ function getValues() {
|
|
|
+ return comm.values[script.uri];
|
|
|
}
|
|
|
- function run(list) {
|
|
|
- while (list.length) buildCode(list.shift());
|
|
|
+ function propertyToString() {
|
|
|
+ return '[Violentmonkey property]';
|
|
|
+ }
|
|
|
+ function addProperty(name, prop, obj) {
|
|
|
+ if ('value' in prop) prop.writable = false;
|
|
|
+ prop.configurable = false;
|
|
|
+ Object.defineProperty(obj, name, prop);
|
|
|
+ if (typeof obj[name] === 'function') obj[name].toString = propertyToString;
|
|
|
+ }
|
|
|
+ function saveValues() {
|
|
|
+ comm.post({
|
|
|
+ cmd: 'SetValue',
|
|
|
+ data: {
|
|
|
+ uri: script.uri,
|
|
|
+ values: getValues(),
|
|
|
+ },
|
|
|
+ });
|
|
|
}
|
|
|
- var comm = this;
|
|
|
- var start = [];
|
|
|
- var idle = [];
|
|
|
- var end = [];
|
|
|
+ }
|
|
|
+ function onLoadScripts(data) {
|
|
|
+ const comm = this;
|
|
|
+ const start = [];
|
|
|
+ const idle = [];
|
|
|
+ const end = [];
|
|
|
comm.command = {};
|
|
|
comm.notif = {};
|
|
|
comm.ainject = {};
|
|
|
comm.version = data.version;
|
|
|
comm.values = {};
|
|
|
// reset load and checkLoad
|
|
|
- comm.load = function () {
|
|
|
+ comm.load = () => {
|
|
|
run(end);
|
|
|
setTimeout(run, 0, idle);
|
|
|
};
|
|
|
- comm.checkLoad = function () {
|
|
|
- if (!comm.state && comm.includes(['interactive', 'complete'], document.readyState))
|
|
|
- comm.state = 1;
|
|
|
+ comm.checkLoad = () => {
|
|
|
+ if (!comm.state && comm.includes(['interactive', 'complete'], document.readyState)) comm.state = 1;
|
|
|
if (comm.state) comm.load();
|
|
|
};
|
|
|
- var listMap = {
|
|
|
+ const listMap = {
|
|
|
'document-start': start,
|
|
|
'document-idle': idle,
|
|
|
'document-end': end,
|
|
|
};
|
|
|
- comm.forEach(data.scripts, function (script) {
|
|
|
+ comm.forEach(data.scripts, script => {
|
|
|
comm.values[script.uri] = data.values[script.uri] || {};
|
|
|
if (script && script.enabled) {
|
|
|
- var list = listMap[script.custom['run-at'] || script.meta['run-at']] || end;
|
|
|
+ const list = listMap[script.custom['run-at'] || script.meta['run-at']] || end;
|
|
|
list.push(script);
|
|
|
}
|
|
|
});
|
|
|
run(start);
|
|
|
comm.checkLoad();
|
|
|
- },
|
|
|
-};
|
|
|
-
|
|
|
-var menus = [];
|
|
|
-var ids = [];
|
|
|
-function injectScript(data) {
|
|
|
- // data: [id, wrapperKeys, code]
|
|
|
- var func = function (id, did, cb) {
|
|
|
- Object.defineProperty(window, 'VM_' + id, {
|
|
|
- value: cb,
|
|
|
- configurable: true,
|
|
|
- });
|
|
|
- var e = new CustomEvent(did, {detail: {cmd: 'Injected', data: id}});
|
|
|
- document.dispatchEvent(e);
|
|
|
- };
|
|
|
- inject('!' + func.toString() + '(' + JSON.stringify(data[0]) + ',' + JSON.stringify(comm.did) + ',function(' + data[1].join(',') + '){' + data[2] + '})');
|
|
|
-}
|
|
|
-function handleC(req) {
|
|
|
- if (!req) {
|
|
|
- console.error('[Violentmonkey] Invalid data! There might be unsupported data format.');
|
|
|
- return;
|
|
|
+ function buildCode(script) {
|
|
|
+ const requireKeys = script.meta.require || [];
|
|
|
+ const wrapper = comm.wrapGM(script, data.cache);
|
|
|
+ // Must use Object.getOwnPropertyNames to list unenumerable properties
|
|
|
+ const wrapperKeys = Object.getOwnPropertyNames(wrapper);
|
|
|
+ const wrapperInit = comm.map(wrapperKeys, name => `this["${name}"]=${name}`).join(';');
|
|
|
+ const codeSlices = [`${wrapperInit};with(this)!function(){`];
|
|
|
+ comm.forEach(requireKeys, key => {
|
|
|
+ const requireCode = data.require[key];
|
|
|
+ if (requireCode) {
|
|
|
+ codeSlices.push(requireCode);
|
|
|
+ // Add `;` to a new line in case script ends with comment lines
|
|
|
+ codeSlices.push(';');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // wrap code to make 'use strict' work
|
|
|
+ codeSlices.push(`!function(){${script.code$}\n}.call(this)`);
|
|
|
+ codeSlices.push('}.call(this);');
|
|
|
+ const code = codeSlices.join('\n');
|
|
|
+ const name = script.custom.name || script.meta.name || script.id;
|
|
|
+ const args = comm.map(wrapperKeys, key => wrapper[key]);
|
|
|
+ const thisObj = wrapper.window || wrapper;
|
|
|
+ const id = comm.getUniqId();
|
|
|
+ comm.ainject[id] = [name, args, thisObj];
|
|
|
+ comm.post({ cmd: 'Inject', data: [id, wrapperKeys, code] });
|
|
|
+ }
|
|
|
+ function run(list) {
|
|
|
+ while (list.length) buildCode(list.shift());
|
|
|
+ }
|
|
|
}
|
|
|
- var maps = {
|
|
|
- GetRequestId: getRequestId,
|
|
|
- HttpRequest: httpRequest,
|
|
|
- AbortRequest: abortRequest,
|
|
|
- Inject: injectScript,
|
|
|
- OpenTab: function (data) {
|
|
|
- sendMessage({cmd: 'OpenTab', data: data});
|
|
|
+ // Communicator
|
|
|
+ const comm = {
|
|
|
+ vmid: `VM_${getUniqId()}`,
|
|
|
+ state: 0,
|
|
|
+ utf8decode,
|
|
|
+ getUniqId,
|
|
|
+ props: Object.getOwnPropertyNames(window),
|
|
|
+ noop() {},
|
|
|
+
|
|
|
+ // Array functions
|
|
|
+ // Notice: avoid using prototype functions since they may be changed by page scripts
|
|
|
+ forEach(arr, func) {
|
|
|
+ const length = arr && arr.length;
|
|
|
+ for (let i = 0; i < length; i += 1) func(arr[i], i, arr);
|
|
|
},
|
|
|
- SetValue: function (data) {
|
|
|
- sendMessage({cmd: 'SetValue', data: data});
|
|
|
+ includes(arr, item) {
|
|
|
+ const length = arr && arr.length;
|
|
|
+ for (let i = 0; i < length; i += 1) {
|
|
|
+ if (arr[i] === item) return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
},
|
|
|
- RegisterMenu: function (data) {
|
|
|
- if (window.top === window) menus.push(data);
|
|
|
- getPopup();
|
|
|
+ map(arr, func) {
|
|
|
+ const res = [];
|
|
|
+ this.forEach(arr, (item, i) => {
|
|
|
+ res.push(func(item, i, arr));
|
|
|
+ });
|
|
|
+ return res;
|
|
|
},
|
|
|
- AddStyle: function (css) {
|
|
|
- if (document.head) {
|
|
|
- var style = document.createElement('style');
|
|
|
- style.innerHTML = css;
|
|
|
- document.head.appendChild(style);
|
|
|
+
|
|
|
+ init: initHandler,
|
|
|
+ post: postData,
|
|
|
+ runCode(name, func, args, thisObj) {
|
|
|
+ try {
|
|
|
+ func.apply(thisObj, args);
|
|
|
+ } catch (e) {
|
|
|
+ let msg = `Error running script: ${name}\n${e}`;
|
|
|
+ if (e.message) msg = `${msg}\n${e.message}`;
|
|
|
+ console.error(msg);
|
|
|
}
|
|
|
},
|
|
|
- Notification: onNotificationCreate,
|
|
|
- SetClipboard: function (data) {
|
|
|
- sendMessage({cmd: 'SetClipboard', data: data});
|
|
|
- },
|
|
|
+ getRequest,
|
|
|
+ getWrapper,
|
|
|
+ wrapGM,
|
|
|
+ onLoadScripts,
|
|
|
};
|
|
|
- var func = maps[req.cmd];
|
|
|
- if (func) func(req.data);
|
|
|
-}
|
|
|
|
|
|
-var notifications = {};
|
|
|
-function onNotificationCreate(options) {
|
|
|
- sendMessage({cmd: 'Notification', data: options})
|
|
|
- .then(function (nid) {
|
|
|
- notifications[nid] = options.id;
|
|
|
- });
|
|
|
-}
|
|
|
-function onNotificationClick(nid) {
|
|
|
- var id = notifications[nid];
|
|
|
- id && comm.post({cmd: 'NotificationClicked', data: id});
|
|
|
-}
|
|
|
-function onNotificationClose(nid) {
|
|
|
- var id = notifications[nid];
|
|
|
- if (id) {
|
|
|
- comm.post({cmd: 'NotificationClosed', data: id});
|
|
|
- delete notifications[nid];
|
|
|
+ const ids = [];
|
|
|
+ const menus = [];
|
|
|
+ function injectScript(data) {
|
|
|
+ // data: [id, wrapperKeys, code]
|
|
|
+ const func = (scriptId, destId, cb) => {
|
|
|
+ Object.defineProperty(window, `VM_${scriptId}`, {
|
|
|
+ value: cb,
|
|
|
+ configurable: true,
|
|
|
+ });
|
|
|
+ const e = new CustomEvent(destId, { detail: { cmd: 'Injected', data: scriptId } });
|
|
|
+ document.dispatchEvent(e);
|
|
|
+ };
|
|
|
+ inject(`!${func.toString()}(${JSON.stringify(data[0])},${JSON.stringify(comm.did)},function(${data[1].join(',')}){${data[2]}})`);
|
|
|
+ }
|
|
|
+ function handleContent(req) {
|
|
|
+ if (!req) {
|
|
|
+ console.error('[Violentmonkey] Invalid data! There might be unsupported data format.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const handlers = {
|
|
|
+ GetRequestId: getRequestId,
|
|
|
+ HttpRequest: httpRequest,
|
|
|
+ AbortRequest: abortRequest,
|
|
|
+ Inject: injectScript,
|
|
|
+ OpenTab(data) {
|
|
|
+ sendMessage({ cmd: 'OpenTab', data });
|
|
|
+ },
|
|
|
+ SetValue(data) {
|
|
|
+ sendMessage({ cmd: 'SetValue', data });
|
|
|
+ },
|
|
|
+ RegisterMenu(data) {
|
|
|
+ if (window.top === window) menus.push(data);
|
|
|
+ getPopup();
|
|
|
+ },
|
|
|
+ AddStyle(css) {
|
|
|
+ if (document.head) {
|
|
|
+ const style = document.createElement('style');
|
|
|
+ style.innerHTML = css;
|
|
|
+ document.head.appendChild(style);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Notification: onNotificationCreate,
|
|
|
+ SetClipboard(data) {
|
|
|
+ sendMessage({ cmd: 'SetClipboard', data });
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const handle = handlers[req.cmd];
|
|
|
+ if (handle) handle(req.data);
|
|
|
+ }
|
|
|
+ function getPopup() {
|
|
|
+ // XXX: only scripts run in top level window are counted
|
|
|
+ if (top === window) {
|
|
|
+ sendMessage({
|
|
|
+ cmd: 'SetPopup',
|
|
|
+ data: { ids, menus },
|
|
|
+ })
|
|
|
+ .catch(comm.noop);
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// Messages
|
|
|
-browser.runtime.onMessage.addListener(function (req, src) {
|
|
|
- var handlers = {
|
|
|
- Command: function (data) {
|
|
|
- comm.post({cmd: 'Command', data: data});
|
|
|
- },
|
|
|
- GetPopup: getPopup,
|
|
|
- GetBadge: getBadge,
|
|
|
- HttpRequested: httpRequested,
|
|
|
- UpdateValues: function (data) {
|
|
|
- comm.post({cmd: 'UpdateValues', data: data});
|
|
|
- },
|
|
|
- NotificationClick: onNotificationClick,
|
|
|
- NotificationClose: onNotificationClose,
|
|
|
- };
|
|
|
- var func = handlers[req.cmd];
|
|
|
- if (func) func(req.data, src);
|
|
|
-});
|
|
|
+ const notifications = {};
|
|
|
+ function onNotificationCreate(options) {
|
|
|
+ sendMessage({ cmd: 'Notification', data: options })
|
|
|
+ .then(nid => { notifications[nid] = options.id; });
|
|
|
+ }
|
|
|
+ function onNotificationClick(nid) {
|
|
|
+ const id = notifications[nid];
|
|
|
+ if (id) comm.post({ cmd: 'NotificationClicked', data: id });
|
|
|
+ }
|
|
|
+ function onNotificationClose(nid) {
|
|
|
+ const id = notifications[nid];
|
|
|
+ if (id) {
|
|
|
+ comm.post({ cmd: 'NotificationClosed', data: id });
|
|
|
+ delete notifications[nid];
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-// Requests
|
|
|
-var requests = {};
|
|
|
-function getRequestId() {
|
|
|
- sendMessage({cmd: 'GetRequestId'}).then(function (id) {
|
|
|
- requests[id] = 1;
|
|
|
- comm.post({cmd: 'GotRequestId', data: id});
|
|
|
+ // Messages
|
|
|
+ browser.runtime.onMessage.addListener((req, src) => {
|
|
|
+ const handlers = {
|
|
|
+ Command(data) {
|
|
|
+ comm.post({ cmd: 'Command', data });
|
|
|
+ },
|
|
|
+ GetPopup: getPopup,
|
|
|
+ GetBadge: getBadge,
|
|
|
+ HttpRequested: httpRequested,
|
|
|
+ UpdateValues(data) {
|
|
|
+ comm.post({ cmd: 'UpdateValues', data });
|
|
|
+ },
|
|
|
+ NotificationClick: onNotificationClick,
|
|
|
+ NotificationClose: onNotificationClose,
|
|
|
+ };
|
|
|
+ const handle = handlers[req.cmd];
|
|
|
+ if (handle) handle(req.data, src);
|
|
|
});
|
|
|
-}
|
|
|
-function httpRequest(details) {
|
|
|
- sendMessage({cmd: 'HttpRequest', data: details});
|
|
|
-}
|
|
|
-function httpRequested(data) {
|
|
|
- if (requests[data.id]) {
|
|
|
- if (data.type == 'loadend') delete requests[data.id];
|
|
|
- comm.post({cmd: 'HttpRequested', data: data});
|
|
|
+
|
|
|
+ // Requests
|
|
|
+ const requests = {};
|
|
|
+ function getRequestId() {
|
|
|
+ sendMessage({ cmd: 'GetRequestId' })
|
|
|
+ .then(id => {
|
|
|
+ requests[id] = 1;
|
|
|
+ comm.post({ cmd: 'GotRequestId', data: id });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ function httpRequest(details) {
|
|
|
+ sendMessage({ cmd: 'HttpRequest', data: details });
|
|
|
+ }
|
|
|
+ function httpRequested(data) {
|
|
|
+ if (requests[data.id]) {
|
|
|
+ if (data.type === 'loadend') delete requests[data.id];
|
|
|
+ comm.post({ cmd: 'HttpRequested', data });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function abortRequest(id) {
|
|
|
+ sendMessage({ cmd: 'AbortRequest', data: id });
|
|
|
}
|
|
|
-}
|
|
|
-function abortRequest(id) {
|
|
|
- sendMessage({cmd: 'AbortRequest', data: id});
|
|
|
-}
|
|
|
|
|
|
-function objEncode(obj) {
|
|
|
- var list = [];
|
|
|
- for (var i in obj) {
|
|
|
- if (!obj.hasOwnProperty(i)) continue;
|
|
|
- if (typeof obj[i] == 'function') {
|
|
|
- list.push(i + ':' + obj[i].toString());
|
|
|
- } else {
|
|
|
- list.push(i + ':' + JSON.stringify(obj[i]));
|
|
|
+ function objEncode(obj) {
|
|
|
+ const list = Object.keys(obj).map(name => {
|
|
|
+ const value = obj[name];
|
|
|
+ const jsonKey = JSON.stringify(name);
|
|
|
+ if (typeof value === 'function') return `${jsonKey}:${value.toString()}`;
|
|
|
+ return `${jsonKey}:${JSON.stringify(value)}`;
|
|
|
+ });
|
|
|
+ return `{${list.join(',')}}`;
|
|
|
+ }
|
|
|
+ function inject(code) {
|
|
|
+ const script = document.createElement('script');
|
|
|
+ const doc = document.body || document.documentElement;
|
|
|
+ script.innerHTML = code;
|
|
|
+ doc.appendChild(script);
|
|
|
+ try {
|
|
|
+ doc.removeChild(script);
|
|
|
+ } catch (e) {
|
|
|
+ // ignore if body is changed and script is detached
|
|
|
}
|
|
|
}
|
|
|
- return '{' + list.join(',') + '}';
|
|
|
-}
|
|
|
-function inject(code) {
|
|
|
- var script = document.createElement('script');
|
|
|
- var doc = document.body || document.documentElement;
|
|
|
- script.innerHTML = code;
|
|
|
- doc.appendChild(script);
|
|
|
- try {
|
|
|
- doc.removeChild(script);
|
|
|
- } catch (e) {
|
|
|
- // ignore if body is changed and script is detached
|
|
|
+ function loadScripts(data) {
|
|
|
+ comm.forEach(data.scripts, script => {
|
|
|
+ ids.push(script.id);
|
|
|
+ if (script.enabled) badge.number += 1;
|
|
|
+ });
|
|
|
+ comm.post({ cmd: 'LoadScripts', data });
|
|
|
+ badge.ready = true;
|
|
|
+ getPopup();
|
|
|
+ setBadge();
|
|
|
}
|
|
|
-}
|
|
|
-function loadScript(data) {
|
|
|
- forEach(data.scripts, function (script) {
|
|
|
- ids.push(script.id);
|
|
|
- if (script.enabled) badge.number ++;
|
|
|
- });
|
|
|
- comm.post({cmd: 'LoadScript', data: data});
|
|
|
- badge.ready = true;
|
|
|
- getPopup();
|
|
|
- setBadge();
|
|
|
-}
|
|
|
-function initCommunicator() {
|
|
|
- var C = 'C';
|
|
|
- var R = 'R';
|
|
|
- inject(
|
|
|
- '!function(c,R,C){c.init(R,C);document.addEventListener("DOMContentLoaded",function(e){c.state=1;c.load();},false);c.checkLoad();}(' +
|
|
|
- objEncode(comm) + ',"' + R + '","' + C + '")'
|
|
|
- );
|
|
|
- comm.handleC = handleC;
|
|
|
- comm.init(C, R);
|
|
|
- sendMessage({cmd: 'GetInjected', data: location.href}).then(loadScript);
|
|
|
-}
|
|
|
-initCommunicator();
|
|
|
+ function initCommunicator() {
|
|
|
+ const contentId = getUniqId();
|
|
|
+ const webId = getUniqId();
|
|
|
+ comm[`handle_${webId}`] = handleWeb;
|
|
|
+ const initWeb = (comm, webId, contentId) => { // eslint-disable-line no-shadow
|
|
|
+ comm.init(webId, contentId);
|
|
|
+ document.addEventListener('DOMContentLoaded', () => {
|
|
|
+ comm.state = 1;
|
|
|
+ comm.load();
|
|
|
+ }, false);
|
|
|
+ comm.checkLoad();
|
|
|
+ };
|
|
|
+ inject(`(${initWeb.toString()})(${objEncode(comm)},${JSON.stringify(webId)},${JSON.stringify(contentId)})`);
|
|
|
+ comm[`handle_${contentId}`] = handleContent;
|
|
|
+ comm.init(contentId, webId);
|
|
|
+ sendMessage({ cmd: 'GetInjected', data: location.href }).then(loadScripts);
|
|
|
+ }
|
|
|
+ initCommunicator();
|
|
|
+}());
|