/******************************************************************************* 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 { getExceptionTokenFn } from './utils.js'; import { registerScriptlet } from './base.js'; import { safeSelf } from './safe-self.js'; /******************************************************************************/ export function matchesStackTraceFn( needleDetails, logLevel = '' ) { const safe = safeSelf(); const exceptionToken = getExceptionTokenFn(); const error = new safe.Error(exceptionToken); const docURL = new URL(self.location.href); docURL.hash = ''; // Normalize stack trace const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/; const lines = []; for ( let line of safe.String_split.call(error.stack, /[\n\r]+/) ) { if ( line.includes(exceptionToken) ) { continue; } line = line.trim(); const match = safe.RegExp_exec.call(reLine, line); if ( match === null ) { continue; } let url = match[2]; if ( url.startsWith('(') ) { url = url.slice(1); } if ( url === docURL.href ) { url = 'inlineScript'; } else if ( url.startsWith('') ) { url = 'injectedScript'; } let fn = match[1] !== undefined ? match[1].slice(0, -1) : line.slice(0, match.index).trim(); if ( fn.startsWith('at') ) { fn = fn.slice(2).trim(); } let rowcol = match[3]; lines.push(' ' + `${fn} ${url}${rowcol}:1`.trim()); } lines[0] = `stackDepth:${lines.length-1}`; const stack = lines.join('\t'); const r = needleDetails.matchAll !== true && safe.testPattern(needleDetails, stack); if ( logLevel === 'all' || logLevel === 'match' && r || logLevel === 'nomatch' && !r ) { safe.uboLog(stack.replace(/\t/g, '\n')); } return r; } registerScriptlet(matchesStackTraceFn, { name: 'matches-stack-trace.fn', dependencies: [ getExceptionTokenFn, safeSelf, ], }); /******************************************************************************/ function abortOnStackTrace( chain = '', needle = '' ) { if ( typeof chain !== 'string' ) { return; } const safe = safeSelf(); const needleDetails = safe.initPattern(needle, { canNegate: true }); const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); if ( needle === '' ) { extraArgs.log = 'all'; } const makeProxy = function(owner, chain) { const pos = chain.indexOf('.'); if ( pos === -1 ) { let v = owner[chain]; Object.defineProperty(owner, chain, { get: function() { const log = safe.logLevel > 1 ? 'all' : 'match'; if ( matchesStackTraceFn(needleDetails, log) ) { throw new ReferenceError(getExceptionTokenFn()); } return v; }, set: function(a) { const log = safe.logLevel > 1 ? 'all' : 'match'; if ( matchesStackTraceFn(needleDetails, log) ) { throw new ReferenceError(getExceptionTokenFn()); } v = a; }, }); return; } const prop = chain.slice(0, pos); let v = owner[prop]; chain = chain.slice(pos + 1); if ( v ) { makeProxy(v, chain); return; } const desc = Object.getOwnPropertyDescriptor(owner, prop); if ( desc && desc.set !== undefined ) { return; } Object.defineProperty(owner, prop, { get: function() { return v; }, set: function(a) { v = a; if ( a instanceof Object ) { makeProxy(a, chain); } } }); }; const owner = window; makeProxy(owner, chain); } registerScriptlet(abortOnStackTrace, { name: 'abort-on-stack-trace.js', aliases: [ 'aost.js', ], dependencies: [ getExceptionTokenFn, matchesStackTraceFn, safeSelf, ], }); /******************************************************************************/