Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"type": "module",
"scripts": {
"build": "deno build.ts --tagName 1.70.0 > ubo.js",
"build": "deno build.ts --tagName 1.70.1b0 > ubo.js",
"test": "node --test"
},
"author": {
Expand Down
237 changes: 236 additions & 1 deletion ubo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,7 @@ function trustedCreateHTML(
// We do not want to recursively create elements
self.trustedCreateHTML = true;
let ancestor = self.frameElement;
while ( ancestor !== null ) {
while ( ancestor ) {
const doc = ancestor.ownerDocument;
if ( doc === null ) { break; }
const win = doc.defaultView;
Expand Down Expand Up @@ -21604,6 +21604,241 @@ preventInnerHTML(...args);
};


scriptlets['prevent-textContent.js'] = {
aliases: [],

requiresTrust: false,
func: function (scriptletGlobals = {}, ...args) {
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),
'Object_toString': Object.prototype.toString,
'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;
}
function preventInnerHTML(
selector = '',
pattern = ''
) {
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('prevent-innerHTML', selector, pattern);
const matcher = safe.initPattern(pattern, { canNegate: true });
const current = safe.Object_getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
if ( current === undefined ) { return; }
const shouldPreventSet = (elem, a) => {
if ( selector !== '' ) {
if ( typeof elem.matches !== 'function' ) { return false; }
if ( elem.matches(selector) === false ) { return false; }
}
return safe.testPattern(matcher, `${a}`);
};
Object.defineProperty(Element.prototype, 'innerHTML', {
get: function() {
return current.get
? current.get.call(this)
: current.value;
},
set: function(a) {
if ( shouldPreventSet(this, a) ) {
safe.uboLog(logPrefix, 'Prevented');
} else if ( current.set ) {
current.set.call(this, a);
}
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Assigned:\n${a}`);
}
current.value = a;
},
});
};
preventInnerHTML(...args);
},
};


scriptlets['prevent-setTimeout.js'] = {
aliases: ["no-setTimeout-if.js","nostif.js","setTimeout-defuser.js"],

Expand Down
Loading