summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js221
1 files changed, 221 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js b/data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js
new file mode 100644
index 0000000..43aec1d
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/resources/safe-self.js
@@ -0,0 +1,221 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+
+*/
+
+import { registerScriptlet } from './base.js';
+
+/******************************************************************************/
+
+// Externally added to the private namespace in which scriptlets execute.
+/* global scriptletGlobals */
+
+export function safeSelf() {
+ if ( scriptletGlobals.safeSelf ) {
+ return scriptletGlobals.safeSelf;
+ }
+ const self = globalThis;
+ const safe = {
+ 'Array_from': Array.from,
+ 'Error': self.Error,
+ 'Function_toStringFn': self.Function.prototype.toString,
+ 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
+ 'Math_floor': Math.floor,
+ 'Math_max': Math.max,
+ 'Math_min': Math.min,
+ 'Math_random': Math.random,
+ 'Object': Object,
+ 'Object_defineProperty': Object.defineProperty.bind(Object),
+ 'Object_defineProperties': Object.defineProperties.bind(Object),
+ 'Object_fromEntries': Object.fromEntries.bind(Object),
+ 'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
+ 'Object_hasOwn': Object.hasOwn.bind(Object),
+ 'RegExp': self.RegExp,
+ 'RegExp_test': self.RegExp.prototype.test,
+ 'RegExp_exec': self.RegExp.prototype.exec,
+ 'Request_clone': self.Request.prototype.clone,
+ 'String': self.String,
+ 'String_fromCharCode': String.fromCharCode,
+ 'String_split': String.prototype.split,
+ 'XMLHttpRequest': self.XMLHttpRequest,
+ 'addEventListener': self.EventTarget.prototype.addEventListener,
+ 'removeEventListener': self.EventTarget.prototype.removeEventListener,
+ 'fetch': self.fetch,
+ 'JSON': self.JSON,
+ 'JSON_parseFn': self.JSON.parse,
+ 'JSON_stringifyFn': self.JSON.stringify,
+ 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args),
+ 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args),
+ 'log': console.log.bind(console),
+ // Properties
+ logLevel: 0,
+ // Methods
+ makeLogPrefix(...args) {
+ return this.sendToLogger && `[${args.join(' \u205D ')}]` || '';
+ },
+ uboLog(...args) {
+ if ( this.sendToLogger === undefined ) { return; }
+ if ( args === undefined || args[0] === '' ) { return; }
+ return this.sendToLogger('info', ...args);
+
+ },
+ uboErr(...args) {
+ if ( this.sendToLogger === undefined ) { return; }
+ if ( args === undefined || args[0] === '' ) { return; }
+ return this.sendToLogger('error', ...args);
+ },
+ escapeRegexChars(s) {
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ },
+ initPattern(pattern, options = {}) {
+ if ( pattern === '' ) {
+ return { matchAll: true, expect: true };
+ }
+ const expect = (options.canNegate !== true || pattern.startsWith('!') === false);
+ if ( expect === false ) {
+ pattern = pattern.slice(1);
+ }
+ const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
+ if ( match !== null ) {
+ return {
+ re: new this.RegExp(
+ match[1],
+ match[2] || options.flags
+ ),
+ expect,
+ };
+ }
+ if ( options.flags !== undefined ) {
+ return {
+ re: new this.RegExp(this.escapeRegexChars(pattern),
+ options.flags
+ ),
+ expect,
+ };
+ }
+ return { pattern, expect };
+ },
+ testPattern(details, haystack) {
+ if ( details.matchAll ) { return true; }
+ if ( details.re ) {
+ return this.RegExp_test.call(details.re, haystack) === details.expect;
+ }
+ return haystack.includes(details.pattern) === details.expect;
+ },
+ patternToRegex(pattern, flags = undefined, verbatim = false) {
+ if ( pattern === '' ) { return /^/; }
+ const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
+ if ( match === null ) {
+ const reStr = this.escapeRegexChars(pattern);
+ return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
+ }
+ try {
+ return new RegExp(match[1], match[2] || undefined);
+ }
+ catch {
+ }
+ return /^/;
+ },
+ getExtraArgs(args, offset = 0) {
+ const entries = args.slice(offset).reduce((out, v, i, a) => {
+ if ( (i & 1) === 0 ) {
+ const rawValue = a[i+1];
+ const value = /^\d+$/.test(rawValue)
+ ? parseInt(rawValue, 10)
+ : rawValue;
+ out.push([ a[i], value ]);
+ }
+ return out;
+ }, []);
+ return this.Object_fromEntries(entries);
+ },
+ onIdle(fn, options) {
+ if ( self.requestIdleCallback ) {
+ return self.requestIdleCallback(fn, options);
+ }
+ return self.requestAnimationFrame(fn);
+ },
+ offIdle(id) {
+ if ( self.requestIdleCallback ) {
+ return self.cancelIdleCallback(id);
+ }
+ return self.cancelAnimationFrame(id);
+ }
+ };
+ scriptletGlobals.safeSelf = safe;
+ if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
+ // This is executed only when the logger is opened
+ safe.logLevel = scriptletGlobals.logLevel || 1;
+ let lastLogType = '';
+ let lastLogText = '';
+ let lastLogTime = 0;
+ safe.toLogText = (type, ...args) => {
+ if ( args.length === 0 ) { return; }
+ const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
+ if ( text === lastLogText && type === lastLogType ) {
+ if ( (Date.now() - lastLogTime) < 5000 ) { return; }
+ }
+ lastLogType = type;
+ lastLogText = text;
+ lastLogTime = Date.now();
+ return text;
+ };
+ try {
+ const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
+ let bcBuffer = [];
+ safe.sendToLogger = (type, ...args) => {
+ const text = safe.toLogText(type, ...args);
+ if ( text === undefined ) { return; }
+ if ( bcBuffer === undefined ) {
+ return bc.postMessage({ what: 'messageToLogger', type, text });
+ }
+ bcBuffer.push({ type, text });
+ };
+ bc.onmessage = ev => {
+ const msg = ev.data;
+ switch ( msg ) {
+ case 'iamready!':
+ if ( bcBuffer === undefined ) { break; }
+ bcBuffer.forEach(({ type, text }) =>
+ bc.postMessage({ what: 'messageToLogger', type, text })
+ );
+ bcBuffer = undefined;
+ break;
+ case 'setScriptletLogLevelToOne':
+ safe.logLevel = 1;
+ break;
+ case 'setScriptletLogLevelToTwo':
+ safe.logLevel = 2;
+ break;
+ }
+ };
+ bc.postMessage('areyouready?');
+ } catch {
+ safe.sendToLogger = (type, ...args) => {
+ const text = safe.toLogText(type, ...args);
+ if ( text === undefined ) { return; }
+ safe.log(`uBO ${text}`);
+ };
+ }
+ return safe;
+}
+registerScriptlet(safeSelf, {
+ name: 'safe-self.fn',
+});