Guest User

Opera uBlock Error

a guest
Sep 9th, 2025
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.31 KB | None | 0 0
  1. ...
  2. 18
  3. 19
  4. 20
  5. 21
  6. 22...
  7.  
  8. 1015
  9. 1016
  10. ...
  11. <17 lines not shown>
  12.  
  13. Home: https://github.com/gorhill/uBlock
  14. */
  15.  
  16. import { dom, qs$, qsa$ } from './dom.js';
  17. import { i18n$ } from './i18n.js';
  18. import punycode from '../lib/punycode.js';
  19.  
  20. /******************************************************************************/
  21.  
  22. let popupFontSize = 'unset';
  23. vAPI.localStorage.getItemAsync('popupFontSize').then(value => {
  24. if ( typeof value !== 'string' || value === 'unset' ) { return; }
  25. document.body.style.setProperty('--font-size', value);
  26. popupFontSize = value;
  27. });
  28.  
  29. // https://github.com/chrisaljoudi/uBlock/issues/996
  30. // Experimental: mitigate glitchy popup UI: immediately set the firewall
  31. // pane visibility to its last known state. By default the pane is hidden.
  32. vAPI.localStorage.getItemAsync('popupPanelSections').then(bits => {
  33. if ( typeof bits !== 'number' ) { return; }
  34. setSections(bits);
  35. });
  36.  
  37. /******************************************************************************/
  38.  
  39. const messaging = vAPI.messaging;
  40. const scopeToSrcHostnameMap = {
  41. '/': '*',
  42. '.': ''
  43. };
  44. const hostnameToSortableTokenMap = new Map();
  45. const statsStr = i18n$('popupBlockedStats');
  46. const domainsHitStr = i18n$('popupHitDomainCount');
  47.  
  48. let popupData = {};
  49. let dfPaneBuilt = false;
  50. let dfHotspots = null;
  51. const allHostnameRows = [];
  52. let cachedPopupHash = '';
  53. let forceReloadFlag = 0;
  54.  
  55. // https://github.com/gorhill/uBlock/issues/2550
  56. // Solution inspired from
  57. // - https://bugs.chromium.org/p/chromium/issues/detail?id=683314
  58. // - https://bugzilla.mozilla.org/show_bug.cgi?id=1332714#c17
  59. // Confusable character set from:
  60. // - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%D0%B0%D1%81%D4%81%D0%B5%D2%BB%D1%96%D1%98%D3%8F%D0%BE%D1%80%D4%9B%D1%95%D4%9D%D1%85%D1%83%D1%8A%D0%AC%D2%BD%D0%BF%D0%B3%D1%B5%D1%A1%5D&g=gc&i=
  61. // Linked from:
  62. // - https://www.chromium.org/developers/design-documents/idn-in-google-chrome
  63. const reCyrillicNonAmbiguous = /[\u0400-\u042b\u042d-\u042f\u0431\u0432\u0434\u0436-\u043d\u0442\u0444\u0446-\u0449\u044b-\u0454\u0457\u0459-\u0460\u0462-\u0474\u0476-\u04ba\u04bc\u04be-\u04ce\u04d0-\u0500\u0502-\u051a\u051c\u051e-\u052f]/;
  64. const reCyrillicAmbiguous = /[\u042c\u0430\u0433\u0435\u043e\u043f\u0440\u0441\u0443\u0445\u044a\u0455\u0456\u0458\u0461\u0475\u04bb\u04bd\u04cf\u0501\u051b\u051d]/;
  65.  
  66. /******************************************************************************/
  67.  
  68. const cachePopupData = function(data) {
  69. popupData = {};
  70. scopeToSrcHostnameMap['.'] = '';
  71. hostnameToSortableTokenMap.clear();
  72.  
  73. if ( typeof data !== 'object' ) {
  74. return popupData;
  75. }
  76. popupData = data;
  77. popupData.cnameMap = new Map(popupData.cnameMap);
  78. scopeToSrcHostnameMap['.'] = popupData.pageHostname || '';
  79. const hostnameDict = popupData.hostnameDict;
  80. if ( typeof hostnameDict !== 'object' ) {
  81. return popupData;
  82. }
  83. for ( const hostname in hostnameDict ) {
  84. if ( Object.hasOwn(hostnameDict, hostname) === false ) { continue; }
  85. let domain = hostnameDict[hostname].domain;
  86. let prefix = hostname.slice(0, 0 - domain.length - 1);
  87. // Prefix with space char for 1st-party hostnames: this ensure these
  88. // will come first in list.
  89. if ( domain === popupData.pageDomain ) {
  90. domain = '\u0020';
  91. }
  92. hostnameToSortableTokenMap.set(
  93. hostname,
  94. domain + ' ' + prefix.split('.').reverse().join('.')
  95. );
  96. }
  97. return popupData;
  98. };
  99.  
  100. /******************************************************************************/
  101.  
  102. const hashFromPopupData = function(reset = false) {
  103. // It makes no sense to offer to refresh the behind-the-scene scope
  104. if ( popupData.pageHostname === 'behind-the-scene' ) {
  105. dom.cl.remove(dom.body, 'needReload');
  106. return;
  107. }
  108.  
  109. const hasher = [];
  110. const rules = popupData.firewallRules;
  111. for ( const key in rules ) {
  112. const rule = rules[key];
  113. if ( rule === undefined ) { continue; }
  114. hasher.push(rule);
  115. }
  116. hasher.sort();
  117. hasher.push(
  118. dom.cl.has('body', 'off'),
  119. dom.cl.has('#no-large-media', 'on'),
  120. dom.cl.has('#no-cosmetic-filtering', 'on'),
  121. dom.cl.has('#no-remote-fonts', 'on'),
  122. dom.cl.has('#no-scripting', 'on')
  123. );
  124.  
  125. const hash = hasher.join('');
  126. if ( reset ) {
  127. cachedPopupHash = hash;
  128. forceReloadFlag = 0;
  129. }
  130. dom.cl.toggle(dom.body, 'needReload',
  131. hash !== cachedPopupHash || popupData.hasUnprocessedRequest === true
  132. );
  133. };
  134.  
  135. /******************************************************************************/
  136.  
  137. // greater-than-zero test
  138.  
  139. const gtz = n => typeof n === 'number' && n > 0;
  140.  
  141. /******************************************************************************/
  142.  
  143. const formatNumber = function(count) {
  144. if ( typeof count !== 'number' ) { return ''; }
  145. if ( count < 1e6 ) { return count.toLocaleString(); }
  146.  
  147. if (
  148. intlNumberFormat === undefined &&
  149. Intl.NumberFormat instanceof Function
  150. ) {
  151. const intl = new Intl.NumberFormat(undefined, {
  152. notation: 'compact',
  153. maximumSignificantDigits: 4
  154. });
  155. if (
  156. intl.resolvedOptions instanceof Function &&
  157. Object.hasOwn(intl.resolvedOptions(), 'notation')
  158. ) {
  159. intlNumberFormat = intl;
  160. }
  161. }
  162.  
  163. if ( intlNumberFormat ) {
  164. return intlNumberFormat.format(count);
  165. }
  166.  
  167. // https://github.com/uBlockOrigin/uBlock-issues/issues/1027#issuecomment-629696676
  168. // For platforms which do not support proper number formatting, use
  169. // a poor's man compact form, which unfortunately is not i18n-friendly.
  170. count /= 1000000;
  171. if ( count >= 100 ) {
  172. count = Math.floor(count * 10) / 10;
  173. } else if ( count > 10 ) {
  174. count = Math.floor(count * 100) / 100;
  175. } else {
  176. count = Math.floor(count * 1000) / 1000;
  177. }
  178. return (count).toLocaleString(undefined) + '\u2009M';
  179. };
  180.  
  181. let intlNumberFormat;
  182.  
  183. /******************************************************************************/
  184.  
  185. const safePunycodeToUnicode = function(hn) {
  186. const pretty = punycode.toUnicode(hn);
  187. return pretty === hn ||
  188. reCyrillicAmbiguous.test(pretty) === false ||
  189. reCyrillicNonAmbiguous.test(pretty)
  190. ? pretty
  191. : hn;
  192. };
  193.  
  194. /******************************************************************************/
  195.  
  196. const updateFirewallCellCount = function(cells, allowed, blocked) {
  197. for ( const cell of cells ) {
  198. if ( gtz(allowed) ) {
  199. dom.attr(cell, 'data-acount',
  200. Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
  201. );
  202. } else {
  203. dom.attr(cell, 'data-acount', '0');
  204. }
  205. if ( gtz(blocked) ) {
  206. dom.attr(cell, 'data-bcount',
  207. Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
  208. );
  209. } else {
  210. dom.attr(cell, 'data-bcount', '0');
  211. }
  212. }
  213. };
  214.  
  215. /******************************************************************************/
  216.  
  217. const updateFirewallCellRule = function(cells, scope, des, type, rule) {
  218. const ruleParts = rule !== undefined ? rule.split(' ') : undefined;
  219.  
  220. for ( const cell of cells ) {
  221. if ( ruleParts === undefined ) {
  222. dom.attr(cell, 'class', null);
  223. continue;
  224. }
  225.  
  226. const action = updateFirewallCellRule.actionNames[ruleParts[3]];
  227. dom.attr(cell, 'class', `${action}Rule`);
  228.  
  229. // Use dark shade visual cue if the rule is specific to the cell.
  230. if (
  231. (ruleParts[1] !== '*' || ruleParts[2] === type) &&
  232. (ruleParts[1] === des) &&
  233. (ruleParts[0] === scopeToSrcHostnameMap[scope])
  234.  
  235. ) {
  236. dom.cl.add(cell, 'ownRule');
  237. }
  238. }
  239. };
  240.  
  241. updateFirewallCellRule.actionNames = { '1': 'block', '2': 'allow', '3': 'noop' };
  242.  
  243. /******************************************************************************/
  244.  
  245. const updateAllFirewallCells = function(doRules = true, doCounts = true) {
  246. const { pageDomain } = popupData;
  247. const rowContainer = qs$('#firewall');
  248. const rows = qsa$(rowContainer, '#firewall > [data-des][data-type]');
  249.  
  250. let a1pScript = 0, b1pScript = 0;
  251. let a3pScript = 0, b3pScript = 0;
  252. let a3pFrame = 0, b3pFrame = 0;
  253.  
  254. for ( const row of rows ) {
  255. const des = dom.attr(row, 'data-des');
  256. const type = dom.attr(row, 'data-type');
  257. if ( doRules ) {
  258. updateFirewallCellRule(
  259. qsa$(row, ':scope > span[data-src="/"]'),
  260. '/',
  261. des,
  262. type,
  263. popupData.firewallRules[`/ ${des} ${type}`]
  264. );
  265. }
  266. const cells = qsa$(row, ':scope > span[data-src="."]');
  267. if ( doRules ) {
  268. updateFirewallCellRule(
  269. cells,
  270. '.',
  271. des,
  272. type,
  273. popupData.firewallRules[`. ${des} ${type}`]
  274. );
  275. }
  276. if ( des === '*' || type !== '*' ) { continue; }
  277. if ( doCounts === false ) { continue; }
  278. const hnDetails = popupData.hostnameDict[des];
  279. if ( hnDetails === undefined ) {
  280. updateFirewallCellCount(cells);
  281. continue;
  282. }
  283. const { allowed, blocked } = hnDetails.counts;
  284. updateFirewallCellCount([ cells[0] ], allowed.any, blocked.any);
  285. const { totals } = hnDetails;
  286. if ( totals !== undefined ) {
  287. updateFirewallCellCount([ cells[1] ], totals.allowed.any, totals.blocked.any);
  288. }
  289. if ( hnDetails.domain === pageDomain ) {
  290. a1pScript += allowed.script; b1pScript += blocked.script;
  291. } else {
  292. a3pScript += allowed.script; b3pScript += blocked.script;
  293. a3pFrame += allowed.frame; b3pFrame += blocked.frame;
  294. }
  295. }
  296.  
  297. if ( doCounts ) {
  298. const fromType = type =>
  299. qsa$(`#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]`);
  300. updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript);
  301. updateFirewallCellCount(fromType('3p-script'), a3pScript, b3pScript);
  302. dom.cl.toggle(rowContainer, 'has3pScript', a3pScript !== 0 || b3pScript !== 0);
  303. updateFirewallCellCount(fromType('3p-frame'), a3pFrame, b3pFrame);
  304. dom.cl.toggle(rowContainer, 'has3pFrame', a3pFrame !== 0 || b3pFrame !== 0);
  305. }
  306.  
  307. dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true);
  308. };
  309.  
  310. /******************************************************************************/
  311.  
  312. // Compute statistics useful only to firewall entries -- we need to call
  313. // this only when overview pane needs to be rendered.
  314.  
  315. const expandHostnameStats = ( ) => {
  316. let dnDetails;
  317. for ( const des of allHostnameRows ) {
  318. const hnDetails = popupData.hostnameDict[des];
  319. const { domain, counts } = hnDetails;
  320. const isDomain = des === domain;
  321. const { allowed: hnAllowed, blocked: hnBlocked } = counts;
  322. if ( isDomain ) {
  323. dnDetails = hnDetails;
  324. dnDetails.totals = JSON.parse(JSON.stringify(dnDetails.counts));
  325. } else {
  326. const { allowed: dnAllowed, blocked: dnBlocked } = dnDetails.totals;
  327. dnAllowed.any += hnAllowed.any;
  328. dnBlocked.any += hnBlocked.any;
  329. }
  330. hnDetails.hasScript = hnAllowed.script !== 0 || hnBlocked.script !== 0;
  331. dnDetails.hasScript = dnDetails.hasScript || hnDetails.hasScript;
  332. hnDetails.hasFrame = hnAllowed.frame !== 0 || hnBlocked.frame !== 0;
  333. dnDetails.hasFrame = dnDetails.hasFrame || hnDetails.hasFrame;
  334. }
  335. };
  336.  
  337. /******************************************************************************/
  338.  
  339. const buildAllFirewallRows = function() {
  340. // Do this before removing the rows
  341. if ( dfHotspots === null ) {
  342. dfHotspots = qs$('#actionSelector');
  343. dom.on(dfHotspots, 'click', setFirewallRuleHandler);
  344. }
  345. dfHotspots.remove();
  346.  
  347. // This must be called before we create the rows.
  348. expandHostnameStats();
  349.  
  350. // Update incrementally: reuse existing rows if possible.
  351. const rowContainer = qs$('#firewall');
  352. const toAppend = document.createDocumentFragment();
  353. const rowTemplate = qs$('#templates > div[data-des=""][data-type="*"]');
  354. const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData;
  355.  
  356. let row = qs$(rowContainer, 'div[data-des="*"][data-type="3p-frame"] + div');
  357.  
  358. for ( const des of allHostnameRows ) {
  359. if ( row === null ) {
  360. row = dom.clone(rowTemplate);
  361. toAppend.appendChild(row);
  362. }
  363. dom.attr(row, 'data-des', des);
  364.  
  365. const hnDetails = hostnameDict[des] || {};
  366. const isDomain = des === hnDetails.domain;
  367. const prettyDomainName = des.includes('xn--')
  368. ? punycode.toUnicode(des)
  369. : des;
  370. const isPunycoded = prettyDomainName !== des;
  371.  
  372. if ( isDomain && row.childElementCount < 4 ) {
  373. row.append(dom.clone(row.children[2]));
  374. } else if ( isDomain === false && row.childElementCount === 4 ) {
  375. row.children[3].remove();
  376. }
  377.  
  378. const span = qs$(row, 'span:first-of-type');
  379. dom.text(qs$(span, ':scope > span > span'), prettyDomainName);
  380.  
  381. const classList = row.classList;
  382.  
  383. let desExtra = '';
  384. if ( classList.toggle('isCname', cnameMap.has(des)) ) {
  385. desExtra = punycode.toUnicode(cnameMap.get(des));
  386. } else if (
  387. isDomain && isPunycoded &&
  388. reCyrillicAmbiguous.test(prettyDomainName) &&
  389. reCyrillicNonAmbiguous.test(prettyDomainName) === false
  390. ) {
  391. desExtra = des;
  392. }
  393. dom.text(qs$(span, 'sub'), desExtra);
  394.  
  395. classList.toggle('isRootContext', des === pageHostname);
  396. classList.toggle('is3p', hnDetails.domain !== pageDomain);
  397. classList.toggle('isDomain', isDomain);
  398. classList.toggle('hasSubdomains', isDomain && hnDetails.hasSubdomains);
  399. classList.toggle('isSubdomain', !isDomain);
  400. const { counts } = hnDetails;
  401. classList.toggle('allowed', gtz(counts.allowed.any));
  402. classList.toggle('blocked', gtz(counts.blocked.any));
  403. const { totals } = hnDetails;
  404. classList.toggle('totalAllowed', gtz(totals && totals.allowed.any));
  405. classList.toggle('totalBlocked', gtz(totals && totals.blocked.any));
  406. classList.toggle('hasScript', hnDetails.hasScript === true);
  407. classList.toggle('hasFrame', hnDetails.hasFrame === true);
  408. classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
  409.  
  410. row = row.nextElementSibling;
  411. }
  412.  
  413. // Remove unused trailing rows
  414. if ( row !== null ) {
  415. while ( row.nextElementSibling !== null ) {
  416. row.nextElementSibling.remove();
  417. }
  418. row.remove();
  419. }
  420.  
  421. // Add new rows all at once
  422. if ( toAppend.childElementCount !== 0 ) {
  423. rowContainer.append(toAppend);
  424. }
  425.  
  426. if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) {
  427. dom.on('#firewall', 'click', 'span[data-src]', unsetFirewallRuleHandler);
  428. dom.on('#firewall', 'mouseenter', 'span[data-src]', mouseenterCellHandler);
  429. dom.on('#firewall', 'mouseleave', 'span[data-src]', mouseleaveCellHandler);
  430. dfPaneBuilt = true;
  431. }
  432.  
  433. updateAllFirewallCells();
  434. };
  435.  
  436. /******************************************************************************/
  437.  
  438. const hostnameCompare = function(a, b) {
  439. let ha = a;
  440. if ( !reIP.test(ha) ) {
  441. ha = hostnameToSortableTokenMap.get(ha) || ' ';
  442. }
  443. let hb = b;
  444. if ( !reIP.test(hb) ) {
  445. hb = hostnameToSortableTokenMap.get(hb) || ' ';
  446. }
  447. const ca = ha.charCodeAt(0);
  448. const cb = hb.charCodeAt(0);
  449. return ca !== cb ? ca - cb : ha.localeCompare(hb);
  450. };
  451.  
  452. const reIP = /(\d|\])$/;
  453.  
  454. /******************************************************************************/
  455.  
  456. function filterFirewallRows() {
  457. const firewallElem = qs$('#firewall');
  458. const elems = qsa$('#firewall .filterExpressions span[data-expr]');
  459. let not = false;
  460. for ( const elem of elems ) {
  461. const on = dom.cl.has(elem, 'on');
  462. switch ( elem.dataset.expr ) {
  463. case 'not':
  464. not = on;
  465. break;
  466. case 'blocked':
  467. dom.cl.toggle(firewallElem, 'showBlocked', !not && on);
  468. dom.cl.toggle(firewallElem, 'hideBlocked', not && on);
  469. break;
  470. case 'allowed':
  471. dom.cl.toggle(firewallElem, 'showAllowed', !not && on);
  472. dom.cl.toggle(firewallElem, 'hideAllowed', not && on);
  473. break;
  474. case 'script':
  475. dom.cl.toggle(firewallElem, 'show3pScript', !not && on);
  476. dom.cl.toggle(firewallElem, 'hide3pScript', not && on);
  477. break;
  478. case 'frame':
  479. dom.cl.toggle(firewallElem, 'show3pFrame', !not && on);
  480. dom.cl.toggle(firewallElem, 'hide3pFrame', not && on);
  481. break;
  482. default:
  483. break;
  484. }
  485. }
  486. }
  487.  
  488. dom.on('#firewall .filterExpressions', 'click', 'span[data-expr]', ev => {
  489. const target = ev.target;
  490. dom.cl.toggle(target, 'on');
  491. switch ( target.dataset.expr ) {
  492. case 'blocked':
  493. if ( dom.cl.has(target, 'on') === false ) { break; }
  494. dom.cl.remove('#firewall .filterExpressions span[data-expr="allowed"]', 'on');
  495. break;
  496. case 'allowed':
  497. if ( dom.cl.has(target, 'on') === false ) { break; }
  498. dom.cl.remove('#firewall .filterExpressions span[data-expr="blocked"]', 'on');
  499. break;
  500. }
  501. filterFirewallRows();
  502. const elems = qsa$('#firewall .filterExpressions span[data-expr]');
  503. const filters = Array.from(elems) .map(el => dom.cl.has(el, 'on') ? '1' : '0');
  504. filters.unshift('00');
  505. vAPI.localStorage.setItem('firewallFilters', filters.join(' '));
  506. });
  507.  
  508. {
  509. vAPI.localStorage.getItemAsync('firewallFilters').then(v => {
  510. if ( v === null ) { return; }
  511. const filters = v.split(' ');
  512. if ( filters.shift() !== '00' ) { return; }
  513. if ( filters.every(v => v === '0') ) { return; }
  514. const elems = qsa$('#firewall .filterExpressions span[data-expr]');
  515. for ( let i = 0; i < elems.length; i++ ) {
  516. if ( filters[i] === '0' ) { continue; }
  517. dom.cl.add(elems[i], 'on');
  518. }
  519. filterFirewallRows();
  520. });
  521. }
  522.  
  523. /******************************************************************************/
  524.  
  525. const renderPrivacyExposure = function() {
  526. const allDomains = {};
  527. let allDomainCount = 0;
  528. let touchedDomainCount = 0;
  529.  
  530. allHostnameRows.length = 0;
  531.  
  532. // Sort hostnames. First-party hostnames must always appear at the top
  533. // of the list.
  534. const { hostnameDict } = popupData;
  535. const desHostnameDone = new Set();
  536. const keys = Object.keys(hostnameDict).sort(hostnameCompare);
  537. for ( const des of keys ) {
  538. // Specific-type rules -- these are built-in
  539. if ( des === '*' || desHostnameDone.has(des) ) { continue; }
  540. const hnDetails = hostnameDict[des];
  541. const { domain, counts } = hnDetails;
  542. if ( Object.hasOwn(allDomains, domain) === false ) {
  543. allDomains[domain] = false;
  544. allDomainCount += 1;
  545. }
  546. if ( gtz(counts.allowed.any) ) {
  547. if ( allDomains[domain] === false ) {
  548. allDomains[domain] = true;
  549. touchedDomainCount += 1;
  550. }
  551. }
  552. const dnDetails = hostnameDict[domain];
  553. if ( dnDetails !== undefined ) {
  554. if ( des !== domain ) {
  555. dnDetails.hasSubdomains = true;
  556. } else if ( dnDetails.hasSubdomains === undefined ) {
  557. dnDetails.hasSubdomains = false;
  558. }
  559. }
  560. allHostnameRows.push(des);
  561. desHostnameDone.add(des);
  562. }
  563.  
  564. const summary = domainsHitStr
  565. .replace('{{count}}', touchedDomainCount.toLocaleString())
  566. .replace('{{total}}', allDomainCount.toLocaleString());
  567. dom.text('[data-i18n^="popupDomainsConnected"] + span', summary);
  568. };
  569.  
  570. /******************************************************************************/
  571.  
  572. const updateHnSwitches = function() {
  573. dom.cl.toggle('#no-popups', 'on', popupData.noPopups === true);
  574. dom.cl.toggle('#no-large-media', 'on', popupData.noLargeMedia === true);
  575. dom.cl.toggle('#no-cosmetic-filtering', 'on',popupData.noCosmeticFiltering === true);
  576. dom.cl.toggle('#no-remote-fonts', 'on', popupData.noRemoteFonts === true);
  577. dom.cl.toggle('#no-scripting', 'on', popupData.noScripting === true);
  578. };
  579.  
  580. /******************************************************************************/
  581.  
  582. // Assume everything has to be done incrementally.
  583.  
  584. const renderPopup = function() {
  585. if ( popupData.tabTitle ) {
  586. document.title = popupData.appName + ' - ' + popupData.tabTitle;
  587. }
  588.  
  589. const isFiltering = popupData.netFilteringSwitch;
  590.  
  591. dom.cl.toggle(dom.body, 'advancedUser', popupData.advancedUserEnabled === true);
  592. dom.cl.toggle(dom.body, 'off', popupData.pageURL === '' || isFiltering !== true);
  593. dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true);
  594.  
  595. // The hostname information below the power switch
  596. {
  597. const [ elemHn, elemDn ] = qs$('#hostname').children;
  598. const { pageDomain, pageHostname } = popupData;
  599. if ( pageDomain !== '' ) {
  600. dom.text(elemDn, safePunycodeToUnicode(pageDomain));
  601. dom.text(elemHn, pageHostname !== pageDomain
  602. ? safePunycodeToUnicode(pageHostname.slice(0, -pageDomain.length - 1)) + '.'
  603. : ''
  604. );
  605. } else {
  606. dom.text(elemDn, '');
  607. dom.text(elemHn, '');
  608. }
  609. }
  610.  
  611. const canPick = popupData.canElementPicker && isFiltering;
  612.  
  613. dom.cl.toggle('#gotoZap', 'canPick', canPick);
  614. dom.cl.toggle('#gotoPick', 'canPick', canPick && popupData.userFiltersAreEnabled);
  615. dom.cl.toggle('#gotoReport', 'canPick', canPick);
  616.  
  617. let blocked, total;
  618. if ( popupData.pageCounts !== undefined ) {
  619. const counts = popupData.pageCounts;
  620. blocked = counts.blocked.any;
  621. total = blocked + counts.allowed.any;
  622. } else {
  623. blocked = 0;
  624. total = 0;
  625. }
  626. let text;
  627. if ( total === 0 ) {
  628. text = formatNumber(0);
  629. } else {
  630. text = statsStr.replace('{{count}}', formatNumber(blocked))
  631. .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total)));
  632. }
  633. dom.text('[data-i18n^="popupBlockedOnThisPage"] + span', text);
  634.  
  635. blocked = popupData.globalBlockedRequestCount;
  636. total = popupData.globalAllowedRequestCount + blocked;
  637. if ( total === 0 ) {
  638. text = formatNumber(0);
  639. } else {
  640. text = statsStr.replace('{{count}}', formatNumber(blocked))
  641. .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total)));
  642. }
  643. dom.text('[data-i18n^="popupBlockedSinceInstall"] + span', text);
  644.  
  645. // This will collate all domains, touched or not
  646. renderPrivacyExposure();
  647.  
  648. // Extra tools
  649. updateHnSwitches();
  650.  
  651. // Report popup count on badge
  652. total = popupData.popupBlockedCount;
  653. dom.text(
  654. '#no-popups .fa-icon-badge',
  655. total ? Math.min(total, 99).toLocaleString() : ''
  656. );
  657.  
  658. // Report large media count on badge
  659. total = popupData.largeMediaCount;
  660. dom.text(
  661. '#no-large-media .fa-icon-badge',
  662. total ? Math.min(total, 99).toLocaleString() : ''
  663. );
  664.  
  665. // Report remote font count on badge
  666. total = popupData.remoteFontCount;
  667. dom.text(
  668. '#no-remote-fonts .fa-icon-badge',
  669. total ? Math.min(total, 99).toLocaleString() : ''
  670. );
  671.  
  672. // Unprocessed request(s) warning
  673. dom.cl.toggle(dom.root, 'warn', popupData.hasUnprocessedRequest === true);
  674.  
  675. dom.cl.toggle(dom.html, 'colorBlind', popupData.colorBlindFriendly === true);
  676.  
  677. setGlobalExpand(popupData.firewallPaneMinimized === false, true);
  678.  
  679. // Build dynamic filtering pane only if in use
  680. if ( (computedSections() & sectionFirewallBit) !== 0 ) {
  681. buildAllFirewallRows();
  682. }
  683.  
  684. renderTooltips();
  685. };
  686.  
  687. /******************************************************************************/
  688.  
  689. dom.on('.dismiss', 'click', ( ) => {
  690. messaging.send('popupPanel', {
  691. what: 'dismissUnprocessedRequest',
  692. tabId: popupData.tabId,
  693. }).then(( ) => {
  694. popupData.hasUnprocessedRequest = false;
  695. dom.cl.remove(dom.root, 'warn');
  696. });
  697. });
  698.  
  699. /******************************************************************************/
  700.  
  701. // https://github.com/gorhill/uBlock/issues/2889
  702. // Use tooltip for ARIA purpose.
  703.  
  704. const renderTooltips = function(selector) {
  705. for ( const [ key, details ] of tooltipTargetSelectors ) {
  706. if ( selector !== undefined && key !== selector ) { continue; }
  707. const elem = qs$(key);
  708. if ( elem.hasAttribute('title') === false ) { continue; }
  709. const text = i18n$(
  710. details.i18n +
  711. (qs$(details.state) === null ? '1' : '2')
  712. );
  713. dom.attr(elem, 'aria-label', text);
  714. dom.attr(elem, 'title', text);
  715. }
  716. };
  717.  
  718. const tooltipTargetSelectors = new Map([
  719. [
  720. '#switch',
  721. {
  722. state: 'body.off',
  723. i18n: 'popupPowerSwitchInfo',
  724. }
  725. ],
  726. [
  727. '#no-popups',
  728. {
  729. state: '#no-popups.on',
  730. i18n: 'popupTipNoPopups'
  731. }
  732. ],
  733. [
  734. '#no-large-media',
  735. {
  736. state: '#no-large-media.on',
  737. i18n: 'popupTipNoLargeMedia'
  738. }
  739. ],
  740. [
  741. '#no-cosmetic-filtering',
  742. {
  743. state: '#no-cosmetic-filtering.on',
  744. i18n: 'popupTipNoCosmeticFiltering'
  745. }
  746. ],
  747. [
  748. '#no-remote-fonts',
  749. {
  750. state: '#no-remote-fonts.on',
  751. i18n: 'popupTipNoRemoteFonts'
  752. }
  753. ],
  754. [
  755. '#no-scripting',
  756. {
  757. state: '#no-scripting.on',
  758. i18n: 'popupTipNoScripting'
  759. }
  760. ],
  761. ]);
  762.  
  763. /******************************************************************************/
  764.  
  765. // All rendering code which need to be executed only once.
  766.  
  767. let renderOnce = function() {
  768. renderOnce = function(){};
  769.  
  770. if ( popupData.fontSize !== popupFontSize ) {
  771. popupFontSize = popupData.fontSize;
  772. if ( popupFontSize !== 'unset' ) {
  773. dom.body.style.setProperty('--font-size', popupFontSize);
  774. vAPI.localStorage.setItem('popupFontSize', popupFontSize);
  775. } else {
  776. dom.body.style.removeProperty('--font-size');
  777. vAPI.localStorage.removeItem('popupFontSize');
  778. }
  779. }
  780.  
  781. dom.text('#version', popupData.appVersion);
  782.  
  783. setSections(computedSections());
  784.  
  785. if ( popupData.uiPopupConfig !== undefined ) {
  786. dom.attr(dom.body, 'data-ui', popupData.uiPopupConfig);
  787. }
  788.  
  789. dom.cl.toggle(dom.body, 'no-tooltips', popupData.tooltipsDisabled === true);
  790. if ( popupData.tooltipsDisabled === true ) {
  791. dom.attr('[title]', 'title', null);
  792. }
  793.  
  794. // https://github.com/uBlockOrigin/uBlock-issues/issues/22
  795. if ( popupData.advancedUserEnabled !== true ) {
  796. dom.attr('#firewall [title][data-src]', 'title', null);
  797. }
  798.  
  799. // This must be done when the firewall is populated
  800. if ( popupData.popupPanelHeightMode === 1 ) {
  801. dom.cl.add(dom.body, 'vMin');
  802. }
  803.  
  804. // Prevent non-advanced user opting into advanced user mode from harming
  805. // themselves by disabling by default features generally suitable to
  806. // filter list maintainers and actual advanced users.
  807. if ( popupData.godMode ) {
  808. dom.cl.add(dom.body, 'godMode');
  809. }
  810. };
  811.  
  812. /******************************************************************************/
  813.  
  814. const renderPopupLazy = (( ) => {
  815. let mustRenderCosmeticFilteringBadge = true;
  816.  
  817. // https://github.com/uBlockOrigin/uBlock-issues/issues/756
  818. // Launch potentially expensive hidden elements-counting scriptlet on
  819. // demand only.
  820. {
  821. const sw = qs$('#no-cosmetic-filtering');
  822. const badge = qs$(sw, ':scope .fa-icon-badge');
  823. dom.text(badge, '\u22EF');
  824.  
  825. const render = ( ) => {
  826. if ( mustRenderCosmeticFilteringBadge === false ) { return; }
  827. mustRenderCosmeticFilteringBadge = false;
  828. if ( dom.cl.has(sw, 'hnSwitchBusy') ) { return; }
  829. dom.cl.add(sw, 'hnSwitchBusy');
  830. messaging.send('popupPanel', {
  831. what: 'getHiddenElementCount',
  832. tabId: popupData.tabId,
  833. }).then(count => {
  834. let text;
  835. if ( (count || 0) === 0 ) {
  836. text = '';
  837. } else if ( count === -1 ) {
  838. text = '?';
  839. } else {
  840. text = Math.min(count, 99).toLocaleString();
  841. }
  842. dom.text(badge, text);
  843. dom.cl.remove(sw, 'hnSwitchBusy');
  844. });
  845. };
  846.  
  847. dom.on(sw, 'mouseenter', render, { passive: true });
  848. }
  849.  
  850. return async function() {
  851. const count = await messaging.send('popupPanel', {
  852. what: 'getScriptCount',
  853. tabId: popupData.tabId,
  854. });
  855. dom.text(
  856. '#no-scripting .fa-icon-badge',
  857. (count || 0) !== 0 ? Math.min(count, 99).toLocaleString() : ''
  858. );
  859. mustRenderCosmeticFilteringBadge = true;
  860. };
  861. })();
  862.  
  863. /******************************************************************************/
  864.  
  865. const toggleNetFilteringSwitch = function(ev) {
  866. if ( !popupData || !popupData.pageURL ) { return; }
  867. messaging.send('popupPanel', {
  868. what: 'toggleNetFiltering',
  869. url: popupData.pageURL,
  870. scope: ev.ctrlKey || ev.metaKey ? 'page' : '',
  871. state: dom.cl.toggle(dom.body, 'off') === false,
  872. tabId: popupData.tabId,
  873. });
  874. renderTooltips('#switch');
  875. hashFromPopupData();
  876. };
  877.  
  878. /******************************************************************************/
  879.  
  880. const gotoZap = function() {
  881. messaging.send('popupPanel', {
  882. what: 'launchElementPicker',
  883. tabId: popupData.tabId,
  884. zap: true,
  885. });
  886.  
  887. vAPI.closePopup();
  888. };
  889.  
  890. /******************************************************************************/
  891.  
  892. const gotoPick = function() {
  893. messaging.send('popupPanel', {
  894. what: 'launchElementPicker',
  895. tabId: popupData.tabId,
  896. });
  897.  
  898. vAPI.closePopup();
  899. };
  900.  
  901. /******************************************************************************/
  902.  
  903. const gotoReport = function() {
  904. const popupPanel = {
  905. blocked: popupData.pageCounts.blocked.any,
  906. };
  907. const reportedStates = [
  908. { name: 'enabled', prop: 'netFilteringSwitch', expected: true },
  909. { name: 'no-cosmetic-filtering', prop: 'noCosmeticFiltering', expected: false },
  910. { name: 'no-large-media', prop: 'noLargeMedia', expected: false },
  911. { name: 'no-popups', prop: 'noPopups', expected: false },
  912. { name: 'no-remote-fonts', prop: 'noRemoteFonts', expected: false },
  913. { name: 'no-scripting', prop: 'noScripting', expected: false },
  914. { name: 'can-element-picker', prop: 'canElementPicker', expected: true },
  915. ];
  916. for ( const { name, prop, expected } of reportedStates ) {
  917. if ( popupData[prop] === expected ) { continue; }
  918. popupPanel[name] = !expected;
  919. }
  920. if ( hostnameToSortableTokenMap.size !== 0 ) {
  921. const network = {};
  922. const hostnames =
  923. Array.from(hostnameToSortableTokenMap.keys()).sort(hostnameCompare);
  924. for ( const hostname of hostnames ) {
  925. const entry = popupData.hostnameDict[hostname];
  926. const count = entry.counts.blocked.any;
  927. if ( count === 0 ) { continue; }
  928. const domain = entry.domain;
  929. if ( network[domain] === undefined ) {
  930. network[domain] = 0;
  931. }
  932. network[domain] += count;
  933. }
  934. if ( Object.keys(network).length !== 0 ) {
  935. popupPanel.network = network;
  936. }
  937. }
  938. messaging.send('popupPanel', {
  939. what: 'launchReporter',
  940. tabId: popupData.tabId,
  941. pageURL: popupData.rawURL,
  942. popupPanel,
  943. });
  944.  
  945. vAPI.closePopup();
  946. };
  947.  
  948. /******************************************************************************/
  949.  
  950. const gotoURL = function(ev) {
  951. if ( this.hasAttribute('href') === false ) { return; }
  952.  
  953. ev.preventDefault();
  954.  
  955. let url = dom.attr(ev.target, 'href');
  956. if (
  957. url === 'logger-ui.html#_' &&
  958. typeof popupData.tabId === 'number'
  959. ) {
  960. url += '+' + popupData.tabId;
  961. }
  962.  
  963. messaging.send('popupPanel', {
  964. what: 'gotoURL',
  965. details: {
  966. url: url,
  967. select: true,
  968. index: -1,
  969. shiftKey: ev.shiftKey
  970. },
  971. });
  972.  
  973. vAPI.closePopup();
  974. };
  975.  
  976. /******************************************************************************/
  977.  
  978. // The popup panel is made of sections. Visibility of sections can
  979. // be toggled on/off.
  980.  
  981. const maxNumberOfSections = 6;
  982. const sectionFirewallBit = 0b10000;
  983.  
  984. const computedSections = ( ) =>
  985. popupData.popupPanelSections &
  986. ~popupData.popupPanelDisabledSections |
  987. popupData.popupPanelLockedSections;
  988.  
  989. const sectionBitsFromAttribute = function() {
  990. const attr = document.body.dataset.more;
  991. if ( attr === '' ) { return 0; }
  992. let bits = 0;
  993. for ( const c of attr ) {
  994. bits |= 1 << (c.charCodeAt(0) - 97);
  995. }
  996. return bits;
  997. };
  998.  
  999. const sectionBitsToAttribute = function(bits) {
  1000. const attr = [];
  1001. for ( let i = 0; i < maxNumberOfSections; i++ ) {
  1002. const bit = 1 << i;
  1003. if ( (bits & bit) === 0 ) { continue; }
  1004. attr.push(String.fromCharCode(97 + i));
  1005. }
  1006. return attr.join('');
  1007. };
  1008.  
  1009. const setSections = function(bits) {
  1010. const value = sectionBitsToAttribute(bits);
  1011. <529 lines not shown>
Tags: uBlock opera
Advertisement
Add Comment
Please, Sign In to add comment