diff --git a/platform/mv3/extension/about.html b/platform/mv3/extension/about.html index b4ea3cee0..a0bb164ea 100644 --- a/platform/mv3/extension/about.html +++ b/platform/mv3/extension/about.html @@ -34,6 +34,7 @@ + diff --git a/platform/mv3/extension/dashboard.html b/platform/mv3/extension/dashboard.html index 4f446dee3..deb6efb15 100644 --- a/platform/mv3/extension/dashboard.html +++ b/platform/mv3/extension/dashboard.html @@ -30,6 +30,7 @@ + diff --git a/platform/mv3/extension/js/about.js b/platform/mv3/extension/js/about.js index 5596b777e..2717501a5 100644 --- a/platform/mv3/extension/js/about.js +++ b/platform/mv3/extension/js/about.js @@ -21,15 +21,13 @@ 'use strict'; -/******************************************************************************/ - import { runtime } from './ext.js'; -import { qs$ } from './dom.js'; +import { dom } from './dom.js'; /******************************************************************************/ (async ( ) => { const manifest = runtime.getManifest(); - qs$('#aboutNameVer').textContent = `${manifest.name} ${manifest.version}`; + dom.text('#aboutNameVer', `${manifest.name} ${manifest.version}`); })(); diff --git a/platform/mv3/extension/js/dashboard-common.js b/platform/mv3/extension/js/dashboard-common.js index 7aa3b3261..80a520653 100644 --- a/platform/mv3/extension/js/dashboard-common.js +++ b/platform/mv3/extension/js/dashboard-common.js @@ -21,12 +21,10 @@ 'use strict'; -/******************************************************************************/ - -import { dom, qsa$ } from './dom.js'; +import { dom } from './dom.js'; /******************************************************************************/ // Open links in the proper window -dom.attr(qsa$('a'), 'target', '_blank'); -dom.attr(qsa$('a[href*="dashboard.html"]'), 'target', '_parent'); +dom.attr('a', 'target', '_blank'); +dom.attr('a[href*="dashboard.html"]', 'target', '_parent'); diff --git a/platform/mv3/extension/js/dashboard.js b/platform/mv3/extension/js/dashboard.js index f21fc9961..b3301160b 100644 --- a/platform/mv3/extension/js/dashboard.js +++ b/platform/mv3/extension/js/dashboard.js @@ -21,15 +21,13 @@ 'use strict'; -/******************************************************************************/ - import { simpleStorage } from './storage.js'; import { dom, qs$ } from './dom.js'; /******************************************************************************/ const discardUnsavedData = function(synchronous = false) { - const paneFrame = document.getElementById('iframe'); + const paneFrame = qs$('#iframe'); const paneWindow = paneFrame.contentWindow; if ( typeof paneWindow.hasUnsavedData !== 'function' || @@ -44,11 +42,11 @@ const discardUnsavedData = function(synchronous = false) { return new Promise(resolve => { const modal = document.querySelector('#unsavedWarning'); - modal.classList.add('on'); + dom.cl.add(modal, 'on'); modal.focus(); const onDone = status => { - modal.classList.remove('on'); + dom.cl.remove(modal, 'on'); document.removeEventListener('click', onClick, true); resolve(status); }; @@ -73,15 +71,15 @@ const discardUnsavedData = function(synchronous = false) { const loadDashboardPanel = function(pane, first) { const tabButton = document.querySelector(`[data-pane="${pane}"]`); - if ( tabButton === null || tabButton.classList.contains('selected') ) { + if ( tabButton === null || dom.cl.has(tabButton, 'selected') ) { return; } const loadPane = ( ) => { self.location.replace(`#${pane}`); for ( const node of document.querySelectorAll('.tabButton.selected') ) { - node.classList.remove('selected'); + dom.cl.remove(node, 'selected'); } - tabButton.classList.add('selected'); + dom.cl.add(tabButton, 'selected'); tabButton.scrollIntoView(); document.querySelector('#iframe').contentWindow.location.replace(pane); if ( pane !== 'no-dashboard.html' ) { @@ -103,11 +101,11 @@ const loadDashboardPanel = function(pane, first) { }; const onTabClickHandler = function(ev) { - loadDashboardPanel(ev.target.getAttribute('data-pane')); + loadDashboardPanel(dom.attr(ev.target, 'data-pane')); }; if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { - document.body.classList.add('noDashboard'); + dom.cl.add(dom.body, 'noDashboard'); } (async ( ) => { @@ -117,12 +115,7 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { } loadDashboardPanel(pane !== null ? pane : 'settings.html', true); - dom.on( - qs$('#dashboard-nav'), - 'click', - '.tabButton', - onTabClickHandler - ); + dom.on('#dashboard-nav', 'click', '.tabButton', onTabClickHandler); // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event window.addEventListener('beforeunload', ( ) => { diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js index ca95b4637..9cf55fed1 100644 --- a/platform/mv3/extension/js/popup.js +++ b/platform/mv3/extension/js/popup.js @@ -51,7 +51,7 @@ function setFilteringMode(level, commit = false) { modeSlider.dataset.level = level; if ( qs$('.filteringModeSlider.moving') === null ) { dom.text( - qs$('#filteringModeText > span:nth-of-type(1)'), + '#filteringModeText > span:nth-of-type(1)', i18n$(`filteringMode${level}Name`) ); } @@ -79,7 +79,7 @@ async function commitFilteringMode() { } } dom.text( - qs$('#filteringModeText > span:nth-of-type(1)'), + '#filteringModeText > span:nth-of-type(1)', i18n$(`filteringMode${afterLevel}Name`) ); const actualLevel = await sendMessage({ @@ -112,7 +112,7 @@ async function commitFilteringMode() { const modeSlider = qs$('.filteringModeSlider'); if ( `${level}` === modeSlider.dataset.level ) { return; } dom.text( - qs$('#filteringModeText > span:nth-of-type(2)'), + '#filteringModeText > span:nth-of-type(2)', i18n$(`filteringMode${level}Name`) ); setFilteringMode(level); @@ -131,7 +131,7 @@ async function commitFilteringMode() { dom.cl.remove(modeSlider, 'moving'); self.removeEventListener('mousemove', moveAsync, { capture: true }); self.removeEventListener('mouseup', stop, { capture: true }); - dom.text(qs$('#filteringModeText > span:nth-of-type(2)'), ''); + dom.text('#filteringModeText > span:nth-of-type(2)', ''); commitFilteringMode(); ev.stopPropagation(); ev.preventDefault(); @@ -160,11 +160,11 @@ async function commitFilteringMode() { ev.preventDefault(); }; - dom.on(qs$('.filteringModeButton'), 'mousedown', startSliding); + dom.on('.filteringModeButton', 'mousedown', startSliding); } dom.on( - qs$('.filteringModeSlider'), + '.filteringModeSlider', 'click', '.filteringModeSlider span[data-level]', ev => { @@ -177,25 +177,25 @@ dom.on( ); dom.on( - qs$('.filteringModeSlider'), + '.filteringModeSlider', 'mouseenter', '.filteringModeSlider span[data-level]', ev => { const span = ev.target; const level = parseInt(span.dataset.level, 10); dom.text( - qs$('#filteringModeText > span:nth-of-type(2)'), + '#filteringModeText > span:nth-of-type(2)', i18n$(`filteringMode${level}Name`) ); } ); dom.on( - qs$('.filteringModeSlider'), + '.filteringModeSlider', 'mouseleave', '.filteringModeSlider span[data-level]', ( ) => { - dom.text(qs$('#filteringModeText > span:nth-of-type(2)'), ''); + dom.text('#filteringModeText > span:nth-of-type(2)', ''); } ); @@ -249,11 +249,11 @@ simpleStorage.getItem('popupPanelSections').then(s => { sectionBitsToAttribute(parseInt(s, 10) || 0); }); -dom.on(qs$('#moreButton'), 'click', ( ) => { +dom.on('#moreButton', 'click', ( ) => { toggleSections(true); }); -dom.on(qs$('#lessButton'), 'click', ( ) => { +dom.on('#lessButton', 'click', ( ) => { toggleSections(false); }); @@ -284,12 +284,12 @@ async function init() { setFilteringMode(popupPanelData.level); - dom.text(qs$('#hostname'), tabHostname); + dom.text('#hostname', tabHostname); const parent = qs$('#rulesetStats'); for ( const details of popupPanelData.rulesetDetails || [] ) { - const div = qs$('#templates .rulesetDetails').cloneNode(true); - dom.text(qs$('h1', div), details.name); + const div = dom.clone('#templates .rulesetDetails'); + dom.text(qs$(div, 'h1'), details.name); const { rules, filters, css } = details; let ruleCount = rules.plain + rules.regex; if ( popupPanelData.hasOmnipotence ) { @@ -301,7 +301,7 @@ async function init() { specificCount += css.specific.entityBased; } dom.text( - qs$('p', div), + qs$(div, 'p'), i18n$('perRulesetStats') .replace('{{ruleCount}}', ruleCount.toLocaleString()) .replace('{{filterCount}}', filters.accepted.toLocaleString()) diff --git a/platform/mv3/extension/js/settings.js b/platform/mv3/extension/js/settings.js index 08acaede3..7772bf972 100644 --- a/platform/mv3/extension/js/settings.js +++ b/platform/mv3/extension/js/settings.js @@ -21,8 +21,6 @@ 'use strict'; -/******************************************************************************/ - import { browser, sendMessage } from './ext.js'; import { i18n$ } from './i18n.js'; import { dom, qs$, qsa$ } from './dom.js'; @@ -66,25 +64,24 @@ function renderFilterLists(soft = false) { const liFromListEntry = function(ruleset, li, hideUnused) { if ( !li ) { - li = listEntryTemplate.cloneNode(true); + li = dom.clone(listEntryTemplate); } const on = enabledRulesets.includes(ruleset.id); - li.classList.toggle('checked', on); + dom.cl.toggle(li, 'checked', on); if ( dom.attr(li, 'data-listkey') !== ruleset.id ) { dom.attr(li, 'data-listkey', ruleset.id); - qs$('input[type="checkbox"]', li).checked = on; - qs$('.listname', li).textContent = ruleset.name || ruleset.id; + qs$(li, 'input[type="checkbox"]').checked = on; + dom.text(qs$(li, '.listname'), ruleset.name || ruleset.id); dom.cl.remove(li, 'toRemove'); if ( ruleset.homeURL ) { dom.cl.add(li, 'support'); - const elem = qs$('a.support', li); - dom.attr(elem, 'href', ruleset.homeURL); + dom.attr(qs$(li, 'a.support'), 'href', ruleset.homeURL); } else { dom.cl.remove(li, 'support'); } if ( ruleset.instructionURL ) { dom.cl.add(li, 'mustread'); - dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL); + dom.attr(qs$(li, 'a.mustread'), 'href', ruleset.instructionURL); } else { dom.cl.remove(li, 'mustread'); } @@ -93,14 +90,14 @@ function renderFilterLists(soft = false) { } // https://github.com/gorhill/uBlock/issues/1429 if ( soft !== true ) { - qs$('input[type="checkbox"]', li).checked = on; + qs$(li, 'input[type="checkbox"]').checked = on; } const stats = rulesetStats(ruleset.id); li.title = listStatsTemplate .replace('{{ruleCount}}', renderNumber(stats.ruleCount)) .replace('{{filterCount}}', renderNumber(stats.filterCount)); dom.attr( - qs$('.input.checkbox', li), + qs$(li, '.input.checkbox'), 'disabled', stats.ruleCount === 0 ? '' : null ); @@ -126,22 +123,25 @@ function renderFilterLists(soft = false) { const liFromListGroup = function(groupKey, groupRulesets) { let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`); if ( liGroup === null ) { - liGroup = listGroupTemplate.cloneNode(true); + liGroup = dom.clone(listGroupTemplate); let groupName = groupNames.get(groupKey); if ( groupName === undefined ) { groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); groupNames.set(groupKey, groupName); } if ( groupName !== '' ) { - qs$('.geName', liGroup).textContent = groupName; + dom.text(qs$(liGroup, '.geName'), groupName); } } - if ( qs$('.geName:empty', liGroup) === null ) { - qs$('.geCount', liGroup).textContent = listEntryCountFromGroup(groupRulesets); + if ( qs$(liGroup, '.geName:empty') === null ) { + dom.text( + qs$(liGroup, '.geCount'), + listEntryCountFromGroup(groupRulesets) + ); } const hideUnused = mustHideUnusedLists(groupKey); - liGroup.classList.toggle('hideUnused', hideUnused); - const ulGroup = qs$('.listEntries', liGroup); + dom.cl.toggle(liGroup, 'hideUnused', hideUnused); + const ulGroup = qs$(liGroup, '.listEntries'); if ( !groupRulesets ) { return liGroup; } groupRulesets.sort(function(a, b) { return (a.name || '').localeCompare(b.name || ''); @@ -161,10 +161,7 @@ function renderFilterLists(soft = false) { // Incremental rendering: this will allow us to easily discard unused // DOM list entries. - dom.cl.add( - qsa$('#lists .listEntries .listEntry[data-listkey]'), - 'discard' - ); + dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard'); // Visually split the filter lists in three groups const ulLists = qs$('#lists'); @@ -192,14 +189,14 @@ function renderFilterLists(soft = false) { dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*')); for ( const [ groupKey, groupRulesets ] of groups ) { - let liGroup = liFromListGroup(groupKey, groupRulesets); - liGroup.setAttribute('data-groupkey', groupKey); + const liGroup = liFromListGroup(groupKey, groupRulesets); + dom.attr(liGroup, 'data-groupkey', groupKey); if ( liGroup.parentElement === null ) { ulLists.appendChild(liGroup); } } - dom.remove(qsa$('#lists .listEntries .listEntry.discard')); + dom.remove('#lists .listEntries .listEntry.discard'); renderWidgets(); } @@ -220,15 +217,16 @@ const renderWidgets = function() { let filterCount = 0; let ruleCount = 0; for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) { - if ( qs$('input[type="checkbox"]:checked', liEntry) === null ) { continue; } + if ( qs$(liEntry, 'input[type="checkbox"]:checked') === null ) { continue; } const stats = rulesetStats(liEntry.dataset.listkey); if ( stats === undefined ) { continue; } ruleCount += stats.ruleCount; filterCount += stats.filterCount; } - qs$('#listsOfBlockedHostsPrompt').textContent = i18n$('perRulesetStats') + dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats') .replace('{{ruleCount}}', ruleCount.toLocaleString()) - .replace('{{filterCount}}', filterCount.toLocaleString()); + .replace('{{filterCount}}', filterCount.toLocaleString()) + ); }; /******************************************************************************/ @@ -267,7 +265,7 @@ async function onFilteringModeChange(ev) { } dom.on( - qs$('#defaultFilteringMode'), + '#defaultFilteringMode', 'change', '.filteringModeCard input[type="radio"]', ev => { onFilteringModeChange(ev); } @@ -275,7 +273,7 @@ dom.on( /******************************************************************************/ -dom.on(qs$('#autoReload input[type="checkbox"'), 'change', ev => { +dom.on('#autoReload input[type="checkbox"', 'change', ev => { sendMessage({ what: 'setAutoReload', state: ev.target.checked, @@ -287,7 +285,7 @@ dom.on(qs$('#autoReload input[type="checkbox"'), 'change', ev => { async function applyEnabledRulesets() { const enabledRulesets = []; for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) { - if ( qs$('input[type="checkbox"]:checked', liEntry) === null ) { continue; } + if ( qs$(liEntry, 'input[type="checkbox"]:checked') === null ) { continue; } enabledRulesets.push(liEntry.dataset.listkey); } @@ -299,7 +297,7 @@ async function applyEnabledRulesets() { renderWidgets(); } -dom.on(qs$('#lists'), 'change', '.listEntry input[type="checkbox"]', ( ) => { +dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ( ) => { applyEnabledRulesets(); }); @@ -324,8 +322,8 @@ function toggleHideUnusedLists(which) { if ( mustHide ) { hideUnusedSet.add(which); } - document.body.classList.toggle('hideUnused', mustHide); - dom.cl.toggle(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide); + dom.cl.toggle(dom.body, 'hideUnused', mustHide); + dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide); } else { const doesHide = hideUnusedSet.has(which); if ( doesHide ) { @@ -335,7 +333,7 @@ function toggleHideUnusedLists(which) { } mustHide = doesHide === doesHideAll; groupSelector = `.groupEntry[data-groupkey="${which}"]`; - dom.cl.toggle(qsa$(groupSelector), 'hideUnused', mustHide); + dom.cl.toggle(groupSelector, 'hideUnused', mustHide); } for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) { @@ -352,16 +350,11 @@ function toggleHideUnusedLists(which) { ); } -dom.on( - qs$('#lists'), - 'click', - '.groupEntry[data-groupkey] > .geDetails', - ev => { - toggleHideUnusedLists( - dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey') - ); - } -); +dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => { + toggleHideUnusedLists( + dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey') + ); +}); // Initialize from saved state. simpleStorage.getItem('hideUnusedFilterLists').then(value => { diff --git a/src/css/shortcuts.css b/platform/mv3/extension/js/theme.js similarity index 50% rename from src/css/shortcuts.css rename to platform/mv3/extension/js/theme.js index 735d87411..48755382e 100644 --- a/src/css/shortcuts.css +++ b/platform/mv3/extension/js/theme.js @@ -1,6 +1,7 @@ -/** +/******************************************************************************* + uBlock Origin - a browser extension to block requests. - Copyright (C) 2018-present Raymond Hill + Copyright (C) 2014-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 @@ -18,38 +19,17 @@ Home: https://github.com/gorhill/uBlock */ -.commandEntries { - margin: 2em; - } +/* jshint esversion:11 */ -.commandEntries td { - padding: 0.5em 0.25em; - } +'use strict'; -.commandEntries td.commandDesc { - text-align: end; - } +import { dom } from './dom.js'; -.commandEntries td.commandShortcut { - white-space: nowrap; - } +/******************************************************************************/ -.commandEntries td.commandShortcut input { - padding: 0.4em; - } - -.commandEntries td.commandShortcut input:focus { - outline: 2px solid blue; - } - -.commandEntries td.commandShortcut input ~ .commandReset { - cursor: pointer; - font-size: 150%; - padding: 0 0.2em; - vertical-align: middle; - } - -.commandEntries td.commandShortcut input:placeholder-shown ~ .commandReset, -.commandEntries td.commandShortcut input:focus ~ .commandReset { - display: none; - } +const mql = self.matchMedia('(prefers-color-scheme: dark)'); +const theme = mql instanceof Object && mql.matches === true + ? 'dark' + : 'light'; +dom.cl.toggle(dom.html, 'dark', theme === 'dark'); +dom.cl.toggle(dom.html, 'light', theme !== 'dark'); diff --git a/platform/mv3/extension/popup.html b/platform/mv3/extension/popup.html index 6af293570..77937a74d 100644 --- a/platform/mv3/extension/popup.html +++ b/platform/mv3/extension/popup.html @@ -51,6 +51,7 @@

+ diff --git a/platform/mv3/extension/settings.html b/platform/mv3/extension/settings.html index 88b5e44fd..f6dc5a4ba 100644 --- a/platform/mv3/extension/settings.html +++ b/platform/mv3/extension/settings.html @@ -100,6 +100,7 @@ + diff --git a/src/1p-filters.html b/src/1p-filters.html index b5cd3b8d3..14cb1964f 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -60,9 +60,9 @@ - + - + diff --git a/src/3p-filters.html b/src/3p-filters.html index 0edf7992f..3fc15297e 100644 --- a/src/3p-filters.html +++ b/src/3p-filters.html @@ -77,9 +77,9 @@ - + - + diff --git a/src/about.html b/src/about.html index 12b130cee..c44366a11 100644 --- a/src/about.html +++ b/src/about.html @@ -52,10 +52,10 @@ - + - - + + diff --git a/src/advanced-settings.html b/src/advanced-settings.html index a681a1ea8..3bffcebe8 100644 --- a/src/advanced-settings.html +++ b/src/advanced-settings.html @@ -33,10 +33,10 @@ - + - - + + diff --git a/src/asset-viewer.html b/src/asset-viewer.html index 2850fc893..a85c667e4 100644 --- a/src/asset-viewer.html +++ b/src/asset-viewer.html @@ -42,9 +42,9 @@ - + - + diff --git a/src/css/dashboard.css b/src/css/dashboard.css index d30494a0a..65eb53cb6 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -76,9 +76,6 @@ iframe { width: 100vw; } -body:not(.canUpdateShortcuts) .tabButton[data-pane="shortcuts.html"] { - display: none; - } body .tabButton[data-pane="no-dashboard.html"] { display: none; } diff --git a/src/dashboard.html b/src/dashboard.html index 118778e92..71bf041b4 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -18,7 +18,6 @@ --> @@ -39,9 +38,9 @@ - + - + diff --git a/src/devtools.html b/src/devtools.html index b935a94fd..6ebf75fdf 100644 --- a/src/devtools.html +++ b/src/devtools.html @@ -48,9 +48,9 @@ - + - + diff --git a/src/document-blocked.html b/src/document-blocked.html index 34485534d..ebc38ddf9 100644 --- a/src/document-blocked.html +++ b/src/document-blocked.html @@ -57,7 +57,7 @@ - + diff --git a/src/dyna-rules.html b/src/dyna-rules.html index a62aca2b2..f94a30ce8 100644 --- a/src/dyna-rules.html +++ b/src/dyna-rules.html @@ -57,9 +57,9 @@ - + - + diff --git a/src/js/1p-filters.js b/src/js/1p-filters.js index 6538fbf84..097ab2d75 100644 --- a/src/js/1p-filters.js +++ b/src/js/1p-filters.js @@ -19,18 +19,17 @@ Home: https://github.com/gorhill/uBlock */ -/* global CodeMirror, uDom, uBlockDashboard */ +/* global CodeMirror, uBlockDashboard */ 'use strict'; -/******************************************************************************/ - import { i18n$ } from './i18n.js'; +import { dom, qs$ } from './dom.js'; import './codemirror/ubo-static-filtering.js'; /******************************************************************************/ -const cmEditor = new CodeMirror(document.getElementById('userFilters'), { +const cmEditor = new CodeMirror(qs$('#userFilters'), { autoCloseBrackets: true, autofocus: true, extraKeys: { @@ -103,8 +102,8 @@ const userFiltersChanged = function(changed) { if ( typeof changed !== 'boolean' ) { changed = self.hasUnsavedData(); } - uDom.nodeFromId('userFiltersApply').disabled = !changed; - uDom.nodeFromId('userFiltersRevert').disabled = !changed; + qs$('#userFiltersApply').disabled = !changed; + qs$('#userFiltersRevert').disabled = !changed; }; /******************************************************************************/ @@ -222,7 +221,7 @@ const handleImportFilePicker = function() { /******************************************************************************/ const startImportFilePicker = function() { - const input = document.getElementById('importFilePicker'); + const input = qs$('#importFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. @@ -290,11 +289,11 @@ self.hasUnsavedData = function() { /******************************************************************************/ // Handle user interaction -uDom('#importUserFiltersFromFile').on('click', startImportFilePicker); -uDom('#importFilePicker').on('change', handleImportFilePicker); -uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile); -uDom('#userFiltersApply').on('click', ( ) => { applyChanges(); }); -uDom('#userFiltersRevert').on('click', revertChanges); +dom.on('#importUserFiltersFromFile', 'click', startImportFilePicker); +dom.on('#importFilePicker', 'change', handleImportFilePicker); +dom.on('#exportUserFiltersToFile', 'click', exportUserFiltersToFile); +dom.on('#userFiltersApply', 'click', ( ) => { applyChanges(); }); +dom.on('#userFiltersRevert', 'click', revertChanges); (async ( ) => { await renderUserFilters(); diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 52d1881cb..74a885c2c 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -19,13 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; -/******************************************************************************/ - import { i18n, i18n$ } from './i18n.js'; +import { dom, qs$, qsa$ } from './dom.js'; /******************************************************************************/ @@ -47,7 +44,7 @@ vAPI.broadcastListener.add(msg => { updateAssetStatus(msg); break; case 'assetsUpdated': - document.body.classList.remove('updating'); + dom.cl.remove(dom.body, 'updating'); renderWidgets(); break; case 'staticFilteringDataChanged': @@ -67,8 +64,8 @@ const renderNumber = function(value) { /******************************************************************************/ const renderFilterLists = function(soft) { - const listGroupTemplate = uDom('#templates .groupEntry'); - const listEntryTemplate = uDom('#templates .listEntry'); + const listGroupTemplate = qs$('#templates .groupEntry'); + const listEntryTemplate = qs$('#templates .listEntry'); const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats'); const renderElapsedTimeToString = i18n.renderElapsedTimeToString; const groupNames = new Map([ [ 'user', '' ] ]); @@ -84,65 +81,62 @@ const renderFilterLists = function(soft) { const liFromListEntry = function(listKey, li, hideUnused) { const entry = listDetails.available[listKey]; if ( !li ) { - li = listEntryTemplate.clone().nodeAt(0); + li = dom.clone(listEntryTemplate); } const on = entry.off !== true; - li.classList.toggle('checked', on); + dom.cl.toggle(li, 'checked', on); let elem; - if ( li.getAttribute('data-listkey') !== listKey ) { - li.setAttribute('data-listkey', listKey); - elem = li.querySelector('input[type="checkbox"]'); + if ( dom.attr(li, 'data-listkey') !== listKey ) { + dom.attr(li, 'data-listkey', listKey); + elem = qs$(li, 'input[type="checkbox"]'); elem.checked = on; - elem = li.querySelector('.listname'); - elem.textContent = listNameFromListKey(listKey); - elem = li.querySelector('a.content'); - elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURIComponent(listKey)); - elem.setAttribute('type', 'text/html'); - li.classList.remove('toRemove'); + dom.text(qs$(li, '.listname'), listNameFromListKey(listKey)); + elem = qs$(li, 'a.content'); + dom.attr(elem, 'href', 'asset-viewer.html?url=' + encodeURIComponent(listKey)); + dom.attr(elem, 'type', 'text/html'); + dom.cl.remove(li, 'toRemove'); if ( entry.supportName ) { - li.classList.add('support'); - elem = li.querySelector('a.support'); - elem.setAttribute('href', entry.supportURL); - elem.setAttribute('title', entry.supportName); + dom.cl.add(li, 'support'); + elem = qs$(li, 'a.support'); + dom.attr(elem, 'href', entry.supportURL); + dom.attr(elem, 'title', entry.supportName); } else { - li.classList.remove('support'); + dom.cl.remove(li, 'support'); } if ( entry.external ) { - li.classList.add('external'); + dom.cl.add(li, 'external'); } else { - li.classList.remove('external'); + dom.cl.remove(li, 'external'); } if ( entry.instructionURL ) { - li.classList.add('mustread'); - elem = li.querySelector('a.mustread'); - elem.setAttribute('href', entry.instructionURL); + dom.cl.add(li, 'mustread'); + dom.attr(qs$(li, 'a.mustread'), 'href', entry.instructionURL); } else { - li.classList.remove('mustread'); + dom.cl.remove(li, 'mustread'); } - li.classList.toggle('isDefault', entry.isDefault === true); - li.classList.toggle('unused', hideUnused && !on); + dom.cl.toggle(li, 'isDefault', entry.isDefault === true); + dom.cl.toggle(li, 'unused', hideUnused && !on); } // https://github.com/gorhill/uBlock/issues/1429 if ( !soft ) { - li.querySelector('input[type="checkbox"]').checked = on; + qs$(li, 'input[type="checkbox"]').checked = on; } - elem = li.querySelector('span.counts'); + elem = qs$(li, 'span.counts'); let text = ''; if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) { text = listStatsTemplate .replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0)) .replace('{{total}}', renderNumber(entry.entryCount)); } - elem.textContent = text; + dom.text(elem, text); // https://github.com/chrisaljoudi/uBlock/issues/104 const asset = listDetails.cache[listKey] || {}; const remoteURL = asset.remoteURL; - li.classList.toggle( - 'unsecure', + dom.cl.toggle(li, 'unsecure', typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0 ); - li.classList.toggle('failed', asset.error !== undefined); - li.classList.toggle('obsolete', asset.obsolete === true); + dom.cl.toggle(li, 'failed', asset.error !== undefined); + dom.cl.toggle(li, 'obsolete', asset.obsolete === true); const lastUpdateString = lastUpdateTemplateString.replace( '{{ago}}', renderElapsedTimeToString(asset.writeTime || 0) @@ -152,18 +146,15 @@ const renderFilterLists = function(soft) { if ( asset.cached && asset.writeTime !== 0 ) { title += '\n' + lastUpdateString; } - li.querySelector('.status.obsolete').setAttribute('title', title); + dom.attr(qs$(li, '.status.obsolete'), 'title', title); } if ( asset.cached === true ) { - li.classList.add('cached'); - li.querySelector('.status.cache').setAttribute( - 'title', - lastUpdateString - ); + dom.cl.add(li, 'cached'); + dom.attr(qs$(li, '.status.cache'), 'title', lastUpdateString); } else { - li.classList.remove('cached'); + dom.cl.remove(li, 'cached'); } - li.classList.remove('discard'); + dom.cl.remove(li, 'discard'); return li; }; @@ -183,24 +174,24 @@ const renderFilterLists = function(soft) { }; const liFromListGroup = function(groupKey, listKeys) { - let liGroup = document.querySelector(`#lists > .groupEntry[data-groupkey="${groupKey}"]`); + let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`); if ( liGroup === null ) { - liGroup = listGroupTemplate.clone().nodeAt(0); + liGroup = dom.clone(listGroupTemplate); let groupName = groupNames.get(groupKey); if ( groupName === undefined ) { groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); groupNames.set(groupKey, groupName); } if ( groupName !== '' ) { - liGroup.querySelector('.geName').textContent = groupName; + dom.text(qs$(liGroup, '.geName'), groupName); } } - if ( liGroup.querySelector('.geName:empty') === null ) { - liGroup.querySelector('.geCount').textContent = listEntryCountFromGroup(listKeys); + if ( qs$(liGroup, '.geName:empty') === null ) { + dom.text(qs$(liGroup, '.geCount'), listEntryCountFromGroup(listKeys)); } let hideUnused = mustHideUnusedLists(groupKey); - liGroup.classList.toggle('hideUnused', hideUnused); - let ulGroup = liGroup.querySelector('.listEntries'); + dom.cl.toggle(liGroup, 'hideUnused', hideUnused); + let ulGroup = qs$(liGroup, '.listEntries'); if ( !listKeys ) { return liGroup; } listKeys.sort(function(a, b) { return (listDetails.available[a].title || '').localeCompare(listDetails.available[b].title || ''); @@ -246,13 +237,14 @@ const renderFilterLists = function(soft) { // Incremental rendering: this will allow us to easily discard unused // DOM list entries. - uDom('#lists .listEntries .listEntry[data-listkey]').addClass('discard'); + dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard'); // Remove import widget while we recreate list of lists. - const importWidget = uDom('.listEntry.toImport').detach(); + const importWidget = qs$('.listEntry.toImport'); + importWidget.remove(); // Visually split the filter lists in purpose-based groups - const ulLists = document.querySelector('#lists'); + const ulLists = qs$('#lists'); const groups = groupsFromLists(details.available); const groupKeys = [ 'user', @@ -265,11 +257,11 @@ const renderFilterLists = function(soft) { 'regions', 'custom' ]; - document.body.classList.toggle('hideUnused', mustHideUnusedLists('*')); + dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*')); for ( let i = 0; i < groupKeys.length; i++ ) { let groupKey = groupKeys[i]; let liGroup = liFromListGroup(groupKey, groups.get(groupKey)); - liGroup.setAttribute('data-groupkey', groupKey); + dom.attr(liGroup, 'data-groupkey', groupKey); if ( liGroup.parentElement === null ) { ulLists.appendChild(liGroup); } @@ -280,28 +272,23 @@ const renderFilterLists = function(soft) { ulLists.appendChild(liFromListGroup(groupKey, groupKey)); } - uDom('#lists .listEntries .listEntry.discard').remove(); + dom.remove('#lists .listEntries .listEntry.discard'); // Re-insert import widget. - uDom('[data-groupkey="custom"] .listEntries').append(importWidget); + qs$('[data-groupkey="custom"] .listEntries').append(importWidget); - uDom.nodeFromId('autoUpdate').checked = - listDetails.autoUpdate === true; - uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent = + qs$('#autoUpdate').checked = listDetails.autoUpdate === true; + dom.text( + '#listsOfBlockedHostsPrompt', i18n$('3pListsOfBlockedHostsPrompt') - .replace( - '{{netFilterCount}}', - renderNumber(details.netFilterCount) - ) - .replace( - '{{cosmeticFilterCount}}', - renderNumber(details.cosmeticFilterCount) - ); - uDom.nodeFromId('parseCosmeticFilters').checked = + .replace('{{netFilterCount}}', renderNumber(details.netFilterCount)) + .replace('{{cosmeticFilterCount}}', renderNumber(details.cosmeticFilterCount)) + ); + qs$('#parseCosmeticFilters').checked = listDetails.parseCosmeticFilters === true; - uDom.nodeFromId('ignoreGenericCosmeticFilters').checked = + qs$('#ignoreGenericCosmeticFilters').checked = listDetails.ignoreGenericCosmeticFilters === true; - uDom.nodeFromId('suspendUntilListsAreLoaded').checked = + qs$('#suspendUntilListsAreLoaded').checked = listDetails.suspendUntilListsAreLoaded === true; // Compute a hash of the settings so that we can keep track of changes @@ -311,7 +298,7 @@ const renderFilterLists = function(soft) { } // https://github.com/gorhill/uBlock/issues/2394 - document.body.classList.toggle('updating', listDetails.isUpdating); + dom.cl.toggle(dom.body, 'updating', listDetails.isUpdating); renderWidgets(); }; @@ -326,39 +313,30 @@ const renderFilterLists = function(soft) { /******************************************************************************/ const renderWidgets = function() { - let cl = uDom.nodeFromId('buttonApply').classList; - cl.toggle( - 'disabled', + dom.cl.toggle('#buttonApply', 'disabled', filteringSettingsHash === hashFromCurrentFromSettings() ); - const updating = document.body.classList.contains('updating'); - cl = uDom.nodeFromId('buttonUpdate').classList; - cl.toggle('active', updating); - cl.toggle( - 'disabled', + const updating = dom.cl.has(dom.body, 'updating'); + dom.cl.toggle('#buttonUpdate', 'active', updating); + dom.cl.toggle('#buttonUpdate', 'disabled', updating === false && - document.querySelector('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null - ); - cl = uDom.nodeFromId('buttonPurgeAll').classList; - cl.toggle( - 'disabled', - updating || document.querySelector('#lists .listEntry.cached:not(.obsolete)') === null + qs$('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null + ); + dom.cl.toggle('#buttonPurgeAll', 'disabled', + updating || qs$('#lists .listEntry.cached:not(.obsolete)') === null ); }; /******************************************************************************/ const updateAssetStatus = function(details) { - const li = document.querySelector( - '#lists .listEntry[data-listkey="' + details.key + '"]' - ); + const li = qs$(`#lists .listEntry[data-listkey="${details.key}"]`); if ( li === null ) { return; } - li.classList.toggle('failed', !!details.failed); - li.classList.toggle('obsolete', !details.cached); - li.classList.toggle('cached', !!details.cached); + dom.cl.toggle(li, 'failed', !!details.failed); + dom.cl.toggle(li, 'obsolete', !details.cached); + dom.cl.toggle(li, 'cached', !!details.cached); if ( details.cached ) { - li.querySelector('.status.cache').setAttribute( - 'title', + dom.attr(qs$(li, '.status.cache'), 'title', lastUpdateTemplateString.replace( '{{ago}}', i18n.renderElapsedTimeToString(Date.now()) @@ -377,21 +355,21 @@ const updateAssetStatus = function(details) { const hashFromCurrentFromSettings = function() { const hash = [ - uDom.nodeFromId('parseCosmeticFilters').checked, - uDom.nodeFromId('ignoreGenericCosmeticFilters').checked + qs$('#parseCosmeticFilters').checked, + qs$('#ignoreGenericCosmeticFilters').checked ]; const listHash = []; - const listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'); + const listEntries = qsa$('#lists .listEntry[data-listkey]:not(.toRemove)'); for ( const liEntry of listEntries ) { - if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { - listHash.push(liEntry.getAttribute('data-listkey')); + if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) { + listHash.push(dom.attr(liEntry, 'data-listkey')); } } hash.push( listHash.sort().join(), - uDom.nodeFromId('importLists').checked && - reValidExternalList.test(uDom.nodeFromId('externalLists').value), - document.querySelector('#lists .listEntry.toRemove') !== null + qs$('#importLists').checked && + reValidExternalList.test(qs$('#externalLists').value), + qs$('#lists .listEntry.toRemove') !== null ); return hash.join(); }; @@ -400,8 +378,7 @@ const hashFromCurrentFromSettings = function() { const onListsetChanged = function(ev) { const input = ev.target; - const li = input.closest('.listEntry'); - li.classList.toggle('checked', input.checked); + dom.cl.toggle(input.closest('.listEntry'), 'checked', input.checked); onFilteringSettingsChanged(); }; @@ -416,7 +393,7 @@ const onFilteringSettingsChanged = function() { const onRemoveExternalList = function(ev) { const liEntry = ev.target.closest('[data-listkey]'); if ( liEntry === null ) { return; } - liEntry.classList.toggle('toRemove'); + dom.cl.toggle(liEntry, 'toRemove'); renderWidgets(); }; @@ -424,7 +401,7 @@ const onRemoveExternalList = function(ev) { const onPurgeClicked = function(ev) { const liEntry = ev.target.closest('[data-listkey]'); - const listKey = liEntry.getAttribute('data-listkey') || ''; + const listKey = dom.attr(liEntry, 'data-listkey') || ''; if ( listKey === '' ) { return; } messaging.send('dashboard', { @@ -437,10 +414,10 @@ const onPurgeClicked = function(ev) { // https://github.com/gorhill/uBlock/issues/1733 // An external filter list must not be marked as obsolete, they will // always be fetched anyways if there is no cached copy. - liEntry.classList.add('obsolete'); - liEntry.classList.remove('cached'); + dom.cl.add(liEntry, 'obsolete'); + dom.cl.remove(liEntry, 'cached'); - if ( liEntry.querySelector('input[type="checkbox"]').checked ) { + if ( qs$(liEntry, 'input[type="checkbox"]').checked ) { renderWidgets(); } }; @@ -452,41 +429,35 @@ const selectFilterLists = async function() { messaging.send('dashboard', { what: 'userSettings', name: 'parseAllABPHideFilters', - value: uDom.nodeFromId('parseCosmeticFilters').checked, + value: qs$('#parseCosmeticFilters').checked, }); messaging.send('dashboard', { what: 'userSettings', name: 'ignoreGenericCosmeticFilters', - value: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, + value: qs$('#ignoreGenericCosmeticFilters').checked, }); // Filter lists to select const toSelect = []; - for ( - const liEntry of - document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)') - ) { - if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { - toSelect.push(liEntry.getAttribute('data-listkey')); + for ( const liEntry of qsa$('#lists .listEntry[data-listkey]:not(.toRemove)') ) { + if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) { + toSelect.push(dom.attr(liEntry, 'data-listkey')); } } // External filter lists to remove const toRemove = []; - for ( - const liEntry of - document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]') - ) { - toRemove.push(liEntry.getAttribute('data-listkey')); + for ( const liEntry of qsa$('#lists .listEntry.toRemove[data-listkey]') ) { + toRemove.push(dom.attr(liEntry, 'data-listkey')); } // External filter lists to import - const externalListsElem = document.getElementById('externalLists'); + const externalListsElem = qs$('#externalLists'); const toImport = externalListsElem.value.trim(); { const liEntry = externalListsElem.closest('.listEntry'); - liEntry.classList.remove('checked'); - liEntry.querySelector('input[type="checkbox"]').checked = false; + dom.cl.remove(liEntry, 'checked'); + qs$(liEntry, 'input[type="checkbox"]').checked = false; externalListsElem.value = ''; } @@ -503,7 +474,7 @@ const selectFilterLists = async function() { /******************************************************************************/ const buttonApplyHandler = async function() { - uDom('#buttonApply').removeClass('enabled'); + dom.cl.remove('#buttonApply', 'enabled'); await selectFilterLists(); renderWidgets(); messaging.send('dashboard', { what: 'reloadAllFilters' }); @@ -513,7 +484,7 @@ const buttonApplyHandler = async function() { const buttonUpdateHandler = async function() { await selectFilterLists(); - document.body.classList.add('updating'); + dom.cl.add(dom.body, 'updating'); renderWidgets(); messaging.send('dashboard', { what: 'forceUpdateAssets' }); }; @@ -521,7 +492,7 @@ const buttonUpdateHandler = async function() { /******************************************************************************/ const buttonPurgeAllHandler = async function(hard) { - uDom('#buttonPurgeAll').removeClass('enabled'); + dom.cl.remove('#buttonPurgeAll', 'enabled'); await messaging.send('dashboard', { what: 'purgeAllCaches', hard, @@ -561,8 +532,8 @@ const toggleHideUnusedLists = function(which) { if ( mustHide ) { hideUnusedSet.add(which); } - document.body.classList.toggle('hideUnused', mustHide); - uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide); + dom.cl.toggle(dom.body, 'hideUnused', mustHide); + dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide); } else { const doesHide = hideUnusedSet.has(which); if ( doesHide ) { @@ -571,12 +542,13 @@ const toggleHideUnusedLists = function(which) { hideUnusedSet.add(which); } mustHide = doesHide === doesHideAll; - groupSelector = '.groupEntry[data-groupkey="' + which + '"] '; - uDom(groupSelector).toggleClass('hideUnused', mustHide); + groupSelector = `.groupEntry[data-groupkey="${which}"] `; + dom.cl.toggle(groupSelector, 'hideUnused', mustHide); } - uDom(groupSelector + '.listEntry input[type="checkbox"]:not(:checked)') - .ancestors('.listEntry[data-listkey]') - .toggleClass('unused', mustHide); + qsa$(`${groupSelector}.listEntry input[type="checkbox"]:not(:checked)`) + .forEach(elem => { + dom.cl.toggle(elem.closest('.listEntry[data-listkey]'), 'unused', mustHide); + }); vAPI.localStorage.setItem( 'hideUnusedFilterLists', Array.from(hideUnusedSet) @@ -584,20 +556,19 @@ const toggleHideUnusedLists = function(which) { }; const revealHiddenUsedLists = function() { - uDom('#lists .listEntry.unused input[type="checkbox"]:checked') - .ancestors('.listEntry[data-listkey]') - .removeClass('unused'); + qsa$('#lists .listEntry.unused input[type="checkbox"]:checked') + .forEach(elem => { + dom.cl.remove(elem.closest('.listEntry[data-listkey]'), 'unused'); + }); }; -uDom('#listsOfBlockedHostsPrompt').on('click', function() { +dom.on('#listsOfBlockedHostsPrompt', 'click', ( ) => { toggleHideUnusedLists('*'); }); -uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(ev) { +dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => { toggleHideUnusedLists( - uDom(ev.target) - .ancestors('.groupEntry[data-groupkey]') - .attr('data-groupkey') + dom.attr(ev.target.closest('.groupEntry[data-groupkey]'), 'data-groupkey') ); }); @@ -614,15 +585,15 @@ vAPI.localStorage.getItemAsync('hideUnusedFilterLists').then(value => { const toCloudData = function() { const bin = { - parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked, - ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, + parseCosmeticFilters: qs$('#parseCosmeticFilters').checked, + ignoreGenericCosmeticFilters: qs$('#ignoreGenericCosmeticFilters').checked, selectedLists: [] }; - const liEntries = document.querySelectorAll('#lists .listEntry'); + const liEntries = qsa$('#lists .listEntry'); for ( const liEntry of liEntries ) { - if ( liEntry.querySelector('input').checked ) { - bin.selectedLists.push(liEntry.getAttribute('data-listkey')); + if ( qs$(liEntry, 'input').checked ) { + bin.selectedLists.push(dom.attr(liEntry, 'data-listkey')); } } @@ -634,24 +605,22 @@ const fromCloudData = function(data, append) { let elem, checked; - elem = uDom.nodeFromId('parseCosmeticFilters'); + elem = qs$('#parseCosmeticFilters'); checked = data.parseCosmeticFilters === true || append && elem.checked; elem.checked = listDetails.parseCosmeticFilters = checked; - elem = uDom.nodeFromId('ignoreGenericCosmeticFilters'); + elem = qs$('#ignoreGenericCosmeticFilters'); checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked; elem.checked = listDetails.ignoreGenericCosmeticFilters = checked; const selectedSet = new Set(data.selectedLists); - const listEntries = uDom('#lists .listEntry'); - for ( let i = 0, n = listEntries.length; i < n; i++ ) { - const listEntry = listEntries.at(i); - const listKey = listEntry.attr('data-listkey'); + for ( const listEntry of qsa$('#lists .listEntry') ) { + const listKey = dom.attr(listEntry, 'data-listkey'); const hasListKey = selectedSet.has(listKey); selectedSet.delete(listKey); - const input = listEntry.descendants('input').first(); - if ( append && input.prop('checked') ) { continue; } - input.prop('checked', hasListKey); + const input = qs$(listEntry, 'input'); + if ( append && input.checked ) { continue; } + input.checked = hasListKey; } // If there are URL-like list keys left in the selected set, import them. @@ -661,14 +630,14 @@ const fromCloudData = function(data, append) { } } if ( selectedSet.size !== 0 ) { - elem = uDom.nodeFromId('externalLists'); + elem = qs$('#externalLists'); if ( append ) { if ( elem.value.trim() !== '' ) { elem.value += '\n'; } } else { elem.value = ''; } elem.value += Array.from(selectedSet).join('\n'); - uDom.nodeFromId('importLists').checked = true; + qs$('#importLists').checked = true; } revealHiddenUsedLists(); @@ -686,21 +655,18 @@ self.hasUnsavedData = function() { /******************************************************************************/ -uDom('#autoUpdate').on('change', userSettingCheckboxChanged); -uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged); -uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged); -uDom('#suspendUntilListsAreLoaded').on('change', userSettingCheckboxChanged); -uDom('#buttonApply').on('click', ( ) => { buttonApplyHandler(); }); -uDom('#buttonUpdate').on('click', ( ) => { buttonUpdateHandler(); }); -uDom('#buttonPurgeAll').on('click', ev => { - buttonPurgeAllHandler(ev.shiftKey); -}); -uDom('#lists').on('change', '.listEntry input', onListsetChanged); -uDom('#lists').on('click', '.listEntry .remove', onRemoveExternalList); -uDom('#lists').on('click', 'span.cache', onPurgeClicked); -uDom('#externalLists').on('input', onFilteringSettingsChanged); - -uDom('#lists').on('click', '.listEntry label *', ev => { +dom.on('#autoUpdate', 'change', userSettingCheckboxChanged); +dom.on('#parseCosmeticFilters', 'change', onFilteringSettingsChanged); +dom.on('#ignoreGenericCosmeticFilters', 'change', onFilteringSettingsChanged); +dom.on('#suspendUntilListsAreLoaded', 'change', userSettingCheckboxChanged); +dom.on('#buttonApply', 'click', ( ) => { buttonApplyHandler(); }); +dom.on('#buttonUpdate', 'click', ( ) => { buttonUpdateHandler(); }); +dom.on('#buttonPurgeAll', 'click', ev => { buttonPurgeAllHandler(ev.shiftKey); }); +dom.on('#lists', 'change', '.listEntry input', onListsetChanged); +dom.on('#lists', 'click', '.listEntry .remove', onRemoveExternalList); +dom.on('#lists', 'click', 'span.cache', onPurgeClicked); +dom.on('#externalLists', 'input', onFilteringSettingsChanged); +dom.on('#lists','click', '.listEntry label *', ev => { if ( ev.target.matches('a,input,.forinput') ) { return; } ev.preventDefault(); }); diff --git a/src/js/about.js b/src/js/about.js index 153794577..4cbf5b19d 100644 --- a/src/js/about.js +++ b/src/js/about.js @@ -19,10 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; +import { dom } from './dom.js'; + /******************************************************************************/ (async ( ) => { @@ -30,5 +30,5 @@ what: 'getAppData', }); - uDom('#aboutNameVer').text(appData.name + ' ' + appData.version); + dom.text('#aboutNameVer', appData.name + ' ' + appData.version); })(); diff --git a/src/js/advanced-settings.js b/src/js/advanced-settings.js index a7cd8b9a1..58b95d1c0 100644 --- a/src/js/advanced-settings.js +++ b/src/js/advanced-settings.js @@ -19,14 +19,11 @@ Home: https://github.com/gorhill/uBlock */ -/* global CodeMirror, uDom, uBlockDashboard */ +/* global CodeMirror, uBlockDashboard */ 'use strict'; -/******************************************************************************/ - -{ -// >>>> Start of private namespace +import { dom, qs$ } from './dom.js'; /******************************************************************************/ @@ -69,15 +66,12 @@ CodeMirror.defineMode('raw-settings', function() { }; }); -const cmEditor = new CodeMirror( - document.getElementById('advancedSettings'), - { - autofocus: true, - lineNumbers: true, - lineWrapping: false, - styleActiveLine: true - } -); +const cmEditor = new CodeMirror(qs$('#advancedSettings'), { + autofocus: true, + lineNumbers: true, + lineWrapping: false, + styleActiveLine: true +}); uBlockDashboard.patchCodeMirrorEditor(cmEditor); @@ -123,8 +117,6 @@ const arrayFromString = function(s) { /******************************************************************************/ -// This is to give a visual hint that the content of user blacklist has changed. - const advancedSettingsChanged = (( ) => { let timer; @@ -132,7 +124,7 @@ const advancedSettingsChanged = (( ) => { timer = undefined; const changed = hashFromAdvancedSettings(cmEditor.getValue()) !== beforeHash; - uDom.nodeFromId('advancedSettingsApply').disabled = !changed; + qs$('#advancedSettingsApply').disabled = !changed; CodeMirror.commands.save = changed ? applyChanges : function(){}; }; @@ -195,17 +187,11 @@ const applyChanges = async function() { /******************************************************************************/ -uDom.nodeFromId('advancedSettings').addEventListener( - 'input', - advancedSettingsChanged -); -uDom.nodeFromId('advancedSettingsApply').addEventListener('click', ( ) => { +dom.on('#advancedSettings', 'input', advancedSettingsChanged); +dom.on('#advancedSettingsApply', 'click', ( ) => { applyChanges(); }); renderAdvancedSettings(true); /******************************************************************************/ - -// <<<< End of private namespace -} diff --git a/src/js/asset-viewer.js b/src/js/asset-viewer.js index ef0824d6d..5331aad74 100644 --- a/src/js/asset-viewer.js +++ b/src/js/asset-viewer.js @@ -25,6 +25,7 @@ /******************************************************************************/ +import { dom, qs$ } from './dom.js'; import './codemirror/ubo-static-filtering.js'; /******************************************************************************/ @@ -36,19 +37,19 @@ import './codemirror/ubo-static-filtering.js'; if ( assetKey === null ) { return; } const subscribeElem = subscribeParams.get('subscribe') !== null - ? document.getElementById('subscribe') + ? qs$('#subscribe') : null; if ( subscribeElem !== null && subscribeURL.hash !== '#subscribed' ) { const title = subscribeParams.get('title'); - const promptElem = document.getElementById('subscribePrompt'); - promptElem.children[0].textContent = title; + const promptElem = qs$('#subscribePrompt'); + dom.text(promptElem.children[0], title); const a = promptElem.children[1]; - a.textContent = assetKey; - a.setAttribute('href', assetKey); - subscribeElem.classList.remove('hide'); + dom.text(a, assetKey); + dom.attr(a, 'href', assetKey); + dom.cl.remove(subscribeElem, 'hide'); } - const cmEditor = new CodeMirror(document.getElementById('content'), { + const cmEditor = new CodeMirror(qs$('#content'), { autofocus: true, foldGutter: true, gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], @@ -81,28 +82,24 @@ import './codemirror/ubo-static-filtering.js'; cmEditor.setValue(details && details.content || ''); if ( subscribeElem !== null ) { - document.getElementById('subscribeButton').addEventListener( - 'click', - ( ) => { - subscribeElem.classList.add('hide'); + dom.on('#subscribeButton', 'click', ( ) => { + dom.cl.add(subscribeElem, 'hide'); + vAPI.messaging.send('scriptlets', { + what: 'applyFilterListSelection', + toImport: assetKey, + }).then(( ) => { vAPI.messaging.send('scriptlets', { - what: 'applyFilterListSelection', - toImport: assetKey, - }).then(( ) => { - vAPI.messaging.send('scriptlets', { - what: 'reloadAllFilters' - }); + what: 'reloadAllFilters' }); - }, - { once: true } - ); + }); + }, { once: true }); } if ( details.sourceURL ) { - const a = document.querySelector('.cm-search-widget .sourceURL'); - a.setAttribute('href', details.sourceURL); - a.setAttribute('title', details.sourceURL); + const a = qs$('.cm-search-widget .sourceURL'); + dom.attr(a, 'href', details.sourceURL); + dom.attr(a, 'title', details.sourceURL); } - document.body.classList.remove('loading'); + dom.cl.remove(dom.body, 'loading'); })(); diff --git a/src/js/cloud-ui.js b/src/js/cloud-ui.js index 36aa1d103..dcd54b058 100644 --- a/src/js/cloud-ui.js +++ b/src/js/cloud-ui.js @@ -19,11 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom, faIconsInit */ +/* global faIconsInit */ 'use strict'; import { i18n, i18n$ } from './i18n.js'; +import { dom, qs$ } from './dom.js'; /******************************************************************************/ @@ -41,23 +42,23 @@ self.cloud = { /******************************************************************************/ -const widget = uDom.nodeFromId('cloudWidget'); +const widget = qs$('#cloudWidget'); if ( widget === null ) { return; } -self.cloud.datakey = widget.getAttribute('data-cloud-entry') || ''; +self.cloud.datakey = dom.attr(widget, 'data-cloud-entry') || ''; if ( self.cloud.datakey === '' ) { return; } /******************************************************************************/ const fetchStorageUsed = async function() { - let elem = widget.querySelector('#cloudCapacity'); - if ( elem.classList.contains('hide') ) { return; } + let elem = qs$(widget, '#cloudCapacity'); + if ( dom.cl.has(elem, 'hide') ) { return; } const result = await vAPI.messaging.send('cloudWidget', { what: 'cloudUsed', datakey: self.cloud.datakey, }); if ( result instanceof Object === false ) { - elem.classList.add('hide'); + dom.cl.add(elem, 'hide'); return; } const units = ' ' + i18n$('genericBytes'); @@ -75,7 +76,7 @@ const fetchStorageUsed = async function() { /******************************************************************************/ const fetchCloudData = async function() { - const info = widget.querySelector('#cloudInfo'); + const info = qs$(widget, '#cloudInfo'); const entry = await vAPI.messaging.send('cloudWidget', { what: 'cloudPull', @@ -84,16 +85,16 @@ const fetchCloudData = async function() { const hasData = entry instanceof Object; if ( hasData === false ) { - uDom.nodeFromId('cloudPull').setAttribute('disabled', ''); - uDom.nodeFromId('cloudPullAndMerge').setAttribute('disabled', ''); + dom.attr('#cloudPull', 'disabled', ''); + dom.attr('#cloudPullAndMerge', 'disabled', ''); info.textContent = '...\n...'; return entry; } self.cloud.data = entry.data; - uDom.nodeFromId('cloudPull').removeAttribute('disabled'); - uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled'); + dom.attr('#cloudPull', 'disabled', null); + dom.attr('#cloudPullAndMerge', 'disabled', null); const timeOptions = { weekday: 'short', @@ -123,11 +124,8 @@ const pushData = async function() { data: self.cloud.onPush(), }); const failed = typeof error === 'string'; - document.getElementById('cloudPush') - .classList - .toggle('error', failed); - document.querySelector('#cloudError') - .textContent = failed ? error : ''; + dom.cl.toggle('#cloudPush', 'error', failed); + dom.text('#cloudError', failed ? error : ''); if ( failed ) { return; } fetchCloudData(); fetchStorageUsed(); @@ -139,8 +137,8 @@ const pullData = function() { if ( typeof self.cloud.onPull === 'function' ) { self.cloud.onPull(self.cloud.data, false); } - document.getElementById('cloudPush').classList.remove('error'); - document.querySelector('#cloudError').textContent = ''; + dom.cl.remove('#cloudPush', 'error'); + dom.text('#cloudError', ''); }; /******************************************************************************/ @@ -154,29 +152,29 @@ const pullAndMergeData = function() { /******************************************************************************/ const openOptions = function() { - const input = uDom.nodeFromId('cloudDeviceName'); + const input = qs$('#cloudDeviceName'); input.value = self.cloud.options.deviceName; - input.setAttribute('placeholder', self.cloud.options.defaultDeviceName); - uDom.nodeFromId('cloudOptions').classList.add('show'); + dom.attr(input, 'placeholder', self.cloud.options.defaultDeviceName); + dom.cl.add('#cloudOptions', 'show'); }; /******************************************************************************/ const closeOptions = function(ev) { - const root = uDom.nodeFromId('cloudOptions'); + const root = qs$('#cloudOptions'); if ( ev.target !== root ) { return; } - root.classList.remove('show'); + dom.cl.remove(root, 'show'); }; /******************************************************************************/ const submitOptions = async function() { - uDom.nodeFromId('cloudOptions').classList.remove('show'); + dom.cl.remove('#cloudOptions', 'show'); const options = await vAPI.messaging.send('cloudWidget', { what: 'cloudSetOptions', options: { - deviceName: uDom.nodeFromId('cloudDeviceName').value + deviceName: qs$('#cloudDeviceName').value }, }); if ( options instanceof Object ) { @@ -209,19 +207,19 @@ const onInitialize = function(options) { faIconsInit(widget); i18n.render(widget); - widget.classList.remove('hide'); + dom.cl.remove(widget, 'hide'); - uDom('#cloudPush').on('click', ( ) => { pushData(); }); - uDom('#cloudPull').on('click', pullData); - uDom('#cloudPullAndMerge').on('click', pullAndMergeData); - uDom('#cloudCog').on('click', openOptions); - uDom('#cloudOptions').on('click', closeOptions); - uDom('#cloudOptionsSubmit').on('click', ( ) => { submitOptions(); }); + dom.on('#cloudPush', 'click', ( ) => { pushData(); }); + dom.on('#cloudPull', 'click', pullData); + dom.on('#cloudPullAndMerge', 'click', pullAndMergeData); + dom.on('#cloudCog', 'click', openOptions); + dom.on('#cloudOptions', 'click', closeOptions); + dom.on('#cloudOptionsSubmit', 'click', ( ) => { submitOptions(); }); fetchCloudData().then(result => { if ( typeof result !== 'string' ) { return; } - document.getElementById('cloudPush').classList.add('error'); - document.querySelector('#cloudError').textContent = result; + dom.cl.add('#cloudPush', 'error'); + dom.text('#cloudError', result); }); fetchStorageUsed(); }; diff --git a/src/js/commands.js b/src/js/commands.js index 1a8986586..de9c6c669 100644 --- a/src/js/commands.js +++ b/src/js/commands.js @@ -28,44 +28,12 @@ import { hostnameFromURI } from './uri-utils.js'; /******************************************************************************/ -µb.canUseShortcuts = vAPI.commands instanceof Object; - -// https://github.com/uBlockOrigin/uBlock-issues/issues/386 -// Firefox 74 and above has complete shortcut assignment user interface. -µb.canUpdateShortcuts = false; - -if ( - µb.canUseShortcuts && - vAPI.webextFlavor.soup.has('firefox') && - typeof vAPI.commands.update === 'function' -) { - self.addEventListener( - 'webextFlavor', - ( ) => { - µb.canUpdateShortcuts = vAPI.webextFlavor.major < 74; - if ( µb.canUpdateShortcuts === false ) { return; } - vAPI.storage.get('commandShortcuts').then(bin => { - if ( bin instanceof Object === false ) { return; } - const shortcuts = bin.commandShortcuts; - if ( Array.isArray(shortcuts) === false ) { return; } - µb.commandShortcuts = new Map(shortcuts); - for ( const [ name, shortcut ] of shortcuts ) { - vAPI.commands.update({ name, shortcut }); - } - }); - }, - { once: true } - ); -} - -/******************************************************************************/ - (( ) => { // ***************************************************************************** // start of local namespace -if ( µb.canUseShortcuts === false ) { return; } +if ( vAPI.commands instanceof Object === false ) { return; } const relaxBlockingMode = (( ) => { const reloadTimers = new Map(); diff --git a/src/js/dashboard-common.js b/src/js/dashboard-common.js index 1df07a912..616eb27a2 100644 --- a/src/js/dashboard-common.js +++ b/src/js/dashboard-common.js @@ -19,10 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; +import { dom } from './dom.js'; + /******************************************************************************/ self.uBlockDashboard = self.uBlockDashboard || {}; @@ -196,7 +196,7 @@ self.uBlockDashboard.openOrSelectPage = function(url, options = {}) { let ev; if ( url instanceof MouseEvent ) { ev = url; - url = ev.target.getAttribute('href'); + url = dom.attr(ev.target, 'href'); } const details = Object.assign({ url, select: true, index: -1 }, options); vAPI.messaging.send('default', { @@ -211,5 +211,5 @@ self.uBlockDashboard.openOrSelectPage = function(url, options = {}) { /******************************************************************************/ // Open links in the proper window -uDom('a').attr('target', '_blank'); -uDom('a[href*="dashboard.html"]').attr('target', '_parent'); +dom.attr('a', 'target', '_blank'); +dom.attr('a[href*="dashboard.html"]', 'target', '_parent'); diff --git a/src/js/dashboard.js b/src/js/dashboard.js index 36c3a3d81..be29cc8c1 100644 --- a/src/js/dashboard.js +++ b/src/js/dashboard.js @@ -19,14 +19,14 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; +import { dom, qs$ } from './dom.js'; + /******************************************************************************/ const discardUnsavedData = function(synchronous = false) { - const paneFrame = document.getElementById('iframe'); + const paneFrame = qs$('#iframe'); const paneWindow = paneFrame.contentWindow; if ( typeof paneWindow.hasUnsavedData !== 'function' || @@ -40,13 +40,13 @@ const discardUnsavedData = function(synchronous = false) { } return new Promise(resolve => { - const modal = uDom.nodeFromId('unsavedWarning'); - modal.classList.add('on'); + const modal = qs$('#unsavedWarning'); + dom.cl.add(modal, 'on'); modal.focus(); const onDone = status => { - modal.classList.remove('on'); - document.removeEventListener('click', onClick, true); + dom.cl.remove(modal, 'on'); + dom.off(document, 'click', onClick, true); resolve(status); }; @@ -58,27 +58,25 @@ const discardUnsavedData = function(synchronous = false) { if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) { return onDone(true); } - if ( modal.querySelector('[data-i18n="dashboardUnsavedWarning"]').contains(target) ) { + if ( qs$(modal, '[data-i18n="dashboardUnsavedWarning"]').contains(target) ) { return; } onDone(false); }; - document.addEventListener('click', onClick, true); + dom.on(document, 'click', onClick, true); }); }; const loadDashboardPanel = function(pane, first) { - const tabButton = uDom.nodeFromSelector(`[data-pane="${pane}"]`); - if ( tabButton === null || tabButton.classList.contains('selected') ) { - return; - } + const tabButton = qs$(`[data-pane="${pane}"]`); + if ( tabButton === null || dom.cl.has(tabButton, 'selected') ) { return; } const loadPane = ( ) => { self.location.replace(`#${pane}`); - uDom('.tabButton.selected').toggleClass('selected', false); - tabButton.classList.add('selected'); + dom.cl.remove('.tabButton.selected', 'selected'); + dom.cl.add(tabButton, 'selected'); tabButton.scrollIntoView(); - uDom.nodeFromId('iframe').contentWindow.location.replace(pane); + qs$('#iframe').contentWindow.location.replace(pane); if ( pane !== 'no-dashboard.html' ) { vAPI.localStorage.setItem('dashboardLastVisitedPane', pane); } @@ -88,9 +86,7 @@ const loadDashboardPanel = function(pane, first) { } const r = discardUnsavedData(); if ( r === false ) { return; } - if ( r === true ) { - return loadPane(); - } + if ( r === true ) { return loadPane(); } r.then(status => { if ( status === false ) { return; } loadPane(); @@ -98,11 +94,11 @@ const loadDashboardPanel = function(pane, first) { }; const onTabClickHandler = function(ev) { - loadDashboardPanel(ev.target.getAttribute('data-pane')); + loadDashboardPanel(dom.attr(ev.target, 'data-pane')); }; if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { - document.body.classList.add('noDashboard'); + dom.cl.add(dom.body, 'noDashboard'); } (async ( ) => { @@ -114,13 +110,9 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { { const details = results[0] || {}; - document.body.classList.toggle( - 'canUpdateShortcuts', - details.canUpdateShortcuts === true - ); if ( details.noDashboard ) { self.location.hash = '#no-dashboard.html'; - document.body.classList.add('noDashboard'); + dom.cl.add(dom.body, 'noDashboard'); } else if ( self.location.hash === '#no-dashboard.html' ) { self.location.hash = ''; } @@ -133,10 +125,10 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { } loadDashboardPanel(pane !== null ? pane : 'settings.html', true); - uDom('.tabButton').on('click', onTabClickHandler); + dom.on('.tabButton', 'click', onTabClickHandler); // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event - window.addEventListener('beforeunload', ( ) => { + dom.on(window, 'beforeunload', ( ) => { if ( discardUnsavedData(true) ) { return; } event.preventDefault(); event.returnValue = ''; diff --git a/src/js/devtools.js b/src/js/devtools.js index fbc526e58..d2fd53f25 100644 --- a/src/js/devtools.js +++ b/src/js/devtools.js @@ -19,10 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -/* global CodeMirror, uDom, uBlockDashboard */ +/* global CodeMirror, uBlockDashboard */ 'use strict'; +import { dom, qs$ } from './dom.js'; + /******************************************************************************/ const reFoldable = /^ *(?=\+ \S)/; @@ -60,19 +62,16 @@ CodeMirror.registerGlobalHelper( } ); -const cmEditor = new CodeMirror( - document.getElementById('console'), - { - autofocus: true, - foldGutter: true, - gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], - lineNumbers: true, - lineWrapping: true, - mode: 'ubo-dump', - styleActiveLine: true, - undoDepth: 5, - } -); +const cmEditor = new CodeMirror(qs$('#console'), { + autofocus: true, + foldGutter: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + lineNumbers: true, + lineWrapping: true, + mode: 'ubo-dump', + styleActiveLine: true, + undoDepth: 5, +}); uBlockDashboard.patchCodeMirrorEditor(cmEditor); @@ -84,11 +83,11 @@ function log(text) { /******************************************************************************/ -uDom.nodeFromId('console-clear').addEventListener('click', ( ) => { - cmEditor.setValue(''); +dom.on('#console-clear', 'click', ( ) => { + cmEditor.setValue(''); }); -uDom.nodeFromId('console-fold').addEventListener('click', ( ) => { +dom.on('#console-fold', 'click', ( ) => { const unfolded = []; let maxUnfolded = -1; cmEditor.eachLine(handle => { @@ -110,7 +109,7 @@ uDom.nodeFromId('console-fold').addEventListener('click', ( ) => { cmEditor.endOperation(); }); -uDom.nodeFromId('console-unfold').addEventListener('click', ( ) => { +dom.on('#console-unfold', 'click', ( ) => { const folded = []; let minFolded = Number.MAX_SAFE_INTEGER; cmEditor.eachLine(handle => { @@ -132,54 +131,54 @@ uDom.nodeFromId('console-unfold').addEventListener('click', ( ) => { cmEditor.endOperation(); }); -uDom.nodeFromId('snfe-dump').addEventListener('click', ev => { - const button = ev.target; - button.setAttribute('disabled', ''); - vAPI.messaging.send('dashboard', { - what: 'snfeDump', - }).then(result => { - log(result); - button.removeAttribute('disabled'); - }); +dom.on('#snfe-dump', 'click', ev => { + const button = ev.target; + dom.attr(button, 'disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'snfeDump', + }).then(result => { + log(result); + dom.attr(button, 'disabled', null); + }); }); -uDom.nodeFromId('snfe-todnr').addEventListener('click', ev => { - const button = ev.target; - button.setAttribute('disabled', ''); - vAPI.messaging.send('dashboard', { - what: 'snfeToDNR', - }).then(result => { - log(result); - button.removeAttribute('disabled'); - }); +dom.on('#snfe-todnr', 'click', ev => { + const button = ev.target; + dom.attr(button, 'disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'snfeToDNR', + }).then(result => { + log(result); + dom.attr(button, 'disabled', null); + }); }); vAPI.messaging.send('dashboard', { what: 'getAppData', }).then(appData => { if ( appData.canBenchmark !== true ) { return; } - uDom.nodeFromId('snfe-benchmark').removeAttribute('disabled'); - uDom.nodeFromId('snfe-benchmark').addEventListener('click', ev => { + dom.attr('#snfe-benchmark', 'disabled', null); + dom.on('#snfe-benchmark', 'click', ev => { const button = ev.target; - button.setAttribute('disabled', ''); + dom.attr(button, 'disabled', ''); vAPI.messaging.send('dashboard', { what: 'snfeBenchmark', }).then(result => { log(result); - button.removeAttribute('disabled'); + dom.attr(button, 'disabled', null); }); }); }); -uDom.nodeFromId('cfe-dump').addEventListener('click', ev => { - const button = ev.target; - button.setAttribute('disabled', ''); - vAPI.messaging.send('dashboard', { - what: 'cfeDump', - }).then(result => { - log(result); - button.removeAttribute('disabled'); - }); +dom.on('#cfe-dump', 'click', ev => { + const button = ev.target; + dom.attr(button, 'disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'cfeDump', + }).then(result => { + log(result); + dom.attr(button, 'disabled', null); + }); }); /******************************************************************************/ diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js index 84af40e33..7e91bdfa8 100644 --- a/src/js/document-blocked.js +++ b/src/js/document-blocked.js @@ -19,13 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; -/******************************************************************************/ - import { i18n$ } from './i18n.js'; +import { dom, qs$ } from './dom.js'; /******************************************************************************/ @@ -58,27 +55,26 @@ let details = {}; if ( Array.isArray(lists) === false || lists.length === 0 ) { return; } - const parent = uDom.nodeFromSelector('#whyex > ul'); + const parent = qs$('#whyex > ul'); for ( const list of lists ) { - const listElem = document.querySelector('#templates .filterList') - .cloneNode(true); - const sourceElem = listElem.querySelector('.filterListSource'); + const listElem = dom.clone('#templates .filterList'); + const sourceElem = qs$(listElem, '.filterListSource'); sourceElem.href += encodeURIComponent(list.assetKey); - sourceElem.textContent = list.title; + dom.text(sourceElem, list.title); if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) { - const supportElem = listElem.querySelector('.filterListSupport'); - supportElem.setAttribute('href', list.supportURL); - supportElem.classList.remove('hidden'); + const supportElem = qs$(listElem, '.filterListSupport'); + dom.attr(supportElem, 'href', list.supportURL); + dom.cl.remove(supportElem, 'hidden'); } parent.appendChild(listElem); } - uDom.nodeFromId('whyex').style.removeProperty('display'); + qs$('#whyex').style.removeProperty('display'); })(); /******************************************************************************/ -uDom.nodeFromSelector('#theURL > p > span:first-of-type').textContent = details.url; -uDom.nodeFromId('why').textContent = details.fs; +dom.text('#theURL > p > span:first-of-type', details.url); +dom.text('#why', details.fs); /******************************************************************************/ @@ -95,20 +91,21 @@ uDom.nodeFromId('why').textContent = details.fs; value = name; name = ''; } - const li = document.createElement('li'); - let span = document.createElement('span'); - span.textContent = name; + const li = dom.create('li'); + let span = dom.create('span'); + dom.text(span, name); li.appendChild(span); if ( name !== '' && value !== '' ) { li.appendChild(document.createTextNode(' = ')); } - span = document.createElement('span'); + span = dom.create('span'); if ( reURL.test(value) ) { - const a = document.createElement('a'); - a.href = a.textContent = value; + const a = dom.create('a'); + dom.attr(a, 'href', value); + dom.text(a, value); span.appendChild(a); } else { - span.textContent = value; + dom.text(span, value); } li.appendChild(span); return li; @@ -135,7 +132,7 @@ uDom.nodeFromId('why').textContent = details.fs; for ( const [ name, value ] of params ) { const li = liFromParam(name, value); if ( depth < 2 && reURL.test(value) ) { - const ul = document.createElement('ul'); + const ul = dom.create('ul'); renderParams(ul, value, depth + 1); li.appendChild(ul); } @@ -145,27 +142,22 @@ uDom.nodeFromId('why').textContent = details.fs; return true; }; - if ( renderParams(uDom.nodeFromId('parsed'), details.url) === false ) { + if ( renderParams(qs$('#parsed'), details.url) === false ) { return; } - const toggler = document.querySelector('#toggleParse'); - toggler.classList.remove('hidden'); + dom.cl.remove('#toggleParse', 'hidden'); - toggler.addEventListener('click', ( ) => { - const cl = uDom.nodeFromId('theURL').classList; - cl.toggle('collapsed'); + dom.on('#toggleParse', 'click', ( ) => { + dom.cl.toggle('#theURL', 'collapsed'); vAPI.localStorage.setItem( 'document-blocked-expand-url', - (cl.contains('collapsed') === false).toString() + (dom.cl.has('#theURL', 'collapsed') === false).toString() ); }); vAPI.localStorage.getItemAsync('document-blocked-expand-url').then(value => { - uDom.nodeFromId('theURL').classList.toggle( - 'collapsed', - value !== 'true' && value !== true - ); + dom.cl.toggle('#theURL', 'collapsed', value !== 'true' && value !== true); }); })(); @@ -174,23 +166,17 @@ uDom.nodeFromId('why').textContent = details.fs; // https://www.reddit.com/r/uBlockOrigin/comments/breeux/close_this_window_doesnt_work_on_firefox/ if ( window.history.length > 1 ) { - uDom('#back').on( - 'click', - ( ) => { - window.history.back(); - } - ); - uDom('#bye').css('display', 'none'); + dom.on('#back', 'click', ( ) => { + window.history.back(); + }); + qs$('#bye').style.display = 'none'; } else { - uDom('#bye').on( - 'click', - ( ) => { - messaging.send('documentBlocked', { - what: 'closeThisTab', - }); - } - ); - uDom('#back').css('display', 'none'); + dom.on('#bye', 'click', ( ) => { + messaging.send('documentBlocked', { + what: 'closeThisTab', + }); + }); + qs$('#back').style.display = 'none'; } /******************************************************************************/ @@ -223,15 +209,14 @@ const proceedPermanent = async function() { proceedToURL(); }; -uDom('#disableWarning').on('change', ev => { +dom.on('#disableWarning', 'change', ev => { const checked = ev.target.checked; - document.querySelector('[data-i18n="docblockedBack"]').classList.toggle('disabled', checked); - document.querySelector('[data-i18n="docblockedClose"]').classList.toggle('disabled', checked); + dom.cl.toggle('[data-i18n="docblockedBack"]', 'disabled', checked); + dom.cl.toggle('[data-i18n="docblockedClose"]', 'disabled', checked); }); -uDom('#proceed').on('click', ( ) => { - const input = document.querySelector('#disableWarning'); - if ( input.checked ) { +dom.on('#proceed', 'click', ( ) => { + if ( qs$('#disableWarning').checked ) { proceedPermanent(); } else { proceedTemporary(); diff --git a/platform/mv3/extension/js/dom.js b/src/js/dom.js similarity index 60% rename from platform/mv3/extension/js/dom.js rename to src/js/dom.js index 4e2a4657c..9d2e819da 100644 --- a/platform/mv3/extension/js/dom.js +++ b/src/js/dom.js @@ -26,11 +26,11 @@ /******************************************************************************/ const normalizeTarget = target => { + if ( typeof target === 'string' ) { return Array.from(qsa$(target)); } + if ( target instanceof Element ) { return [ target ]; } if ( target === null ) { return []; } - if ( Array.isArray(target) ) { return target; } - return target instanceof Element - ? [ target ] - : Array.from(target); + if ( Array.isArray(target) ) { return target; } + return Array.from(target); }; const makeEventHandler = (selector, callback) => { @@ -70,6 +70,16 @@ class dom { } } + static clone(target) { + return normalizeTarget(target)[0].cloneNode(true); + } + + static create(a) { + if ( typeof a === 'string' ) { + return document.createElement(a); + } + } + static text(target, text) { for ( const elem of normalizeTarget(target) ) { elem.textContent = text; @@ -82,15 +92,43 @@ class dom { } } - static on(target, type, selector, callback) { - if ( typeof selector === 'function' ) { - callback = selector; - selector = undefined; + // target, type, callback, [options] + // target, type, subtarget, callback, [options] + + static on(target, type, subtarget, callback, options) { + if ( typeof subtarget === 'function' ) { + options = callback; + callback = subtarget; + subtarget = undefined; + if ( typeof options === 'boolean' ) { + options = { capture: true }; + } } else { - callback = makeEventHandler(selector, callback); + callback = makeEventHandler(subtarget, callback); + if ( options === undefined || typeof options === 'boolean' ) { + options = { capture: true }; + } else { + options.capture = true; + } } - for ( const elem of normalizeTarget(target) ) { - elem.addEventListener(type, callback, selector !== undefined); + const targets = target instanceof Window || target instanceof Document + ? [ target ] + : normalizeTarget(target); + for ( const elem of targets ) { + elem.addEventListener(type, callback, options); + } + } + + static off(target, type, callback, options) { + if ( typeof callback !== 'function' ) { return; } + if ( typeof options === 'boolean' ) { + options = { capture: true }; + } + const targets = target instanceof Window || target instanceof Document + ? [ target ] + : normalizeTarget(target); + for ( const elem of targets ) { + elem.removeEventListener(type, callback, options); } } } @@ -109,9 +147,11 @@ dom.cl = class { } static toggle(target, name, state) { + let r; for ( const elem of normalizeTarget(target) ) { - elem.classList.toggle(name, state); + r = elem.classList.toggle(name, state); } + return r; } static has(target, name) { @@ -124,31 +164,27 @@ dom.cl = class { } }; +/******************************************************************************/ + +function qs$(a, b) { + if ( typeof a === 'string') { + return document.querySelector(a); + } + return a.querySelector(b); +} + +function qsa$(a, b) { + if ( typeof a === 'string') { + return document.querySelectorAll(a); + } + return a.querySelectorAll(b); +} + +dom.root = qs$(':root'); dom.html = document.documentElement; dom.head = document.head; dom.body = document.body; /******************************************************************************/ -function qs$(s, elem = undefined) { - return (elem || document).querySelector(s); -} - -function qsa$(s, elem = undefined) { - return (elem || document).querySelectorAll(s); -} - -/******************************************************************************/ - -{ - const mql = self.matchMedia('(prefers-color-scheme: dark)'); - const theme = mql instanceof Object && mql.matches === true - ? 'dark' - : 'light'; - dom.cl.toggle(dom.html, 'dark', theme === 'dark'); - dom.cl.toggle(dom.html, 'light', theme !== 'dark'); -} - -/******************************************************************************/ - export { dom, qs$, qsa$ }; diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js index d8423e72c..34f714cb6 100644 --- a/src/js/dyna-rules.js +++ b/src/js/dyna-rules.js @@ -19,16 +19,15 @@ Home: https://github.com/gorhill/uMatrix */ -/* global CodeMirror, diff_match_patch, uDom, uBlockDashboard */ +/* global CodeMirror, diff_match_patch, uBlockDashboard */ 'use strict'; -/******************************************************************************/ - import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; import { hostnameFromURI } from './uri-utils.js'; import { i18n$ } from './i18n.js'; +import { dom, qs$, qsa$ } from './dom.js'; import './codemirror/ubo-dynamic-filtering.js'; @@ -37,7 +36,7 @@ import './codemirror/ubo-dynamic-filtering.js'; const hostnameToDomainMap = new Map(); const mergeView = new CodeMirror.MergeView( - document.querySelector('.codeMirrorMergeContainer'), + qs$('.codeMirrorMergeContainer'), { allowEditingOriginals: true, connect: 'align', @@ -86,24 +85,23 @@ let isCollapsed = false; const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])'; const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])'; - uDom.nodeFromSelector('.CodeMirror-merge-scrolllock') - .setAttribute('title', i18n$('genericMergeViewScrollLock')); + dom.attr('.CodeMirror-merge-scrolllock', 'title', i18n$('genericMergeViewScrollLock')); const translate = function() { - let elems = document.querySelectorAll(commitArrowSelector); + let elems = qsa$(commitArrowSelector); for ( const elem of elems ) { - elem.setAttribute('title', i18nCommitStr); + dom.attr(elem, 'title', i18nCommitStr); } - elems = document.querySelectorAll(revertArrowSelector); + elems = qsa$(revertArrowSelector); for ( const elem of elems ) { - elem.setAttribute('title', i18nRevertStr); + dom.attr(elem, 'title', i18nRevertStr); } }; const mergeGapObserver = new MutationObserver(translate); mergeGapObserver.observe( - uDom.nodeFromSelector('.CodeMirror-merge-copybuttons-left'), + qs$('.CodeMirror-merge-copybuttons-left'), { attributes: true, attributeFilter: [ 'title' ], subtree: true } ); @@ -247,7 +245,7 @@ const rulesToDoc = function(clearHistory) { /******************************************************************************/ const filterRules = function(key) { - const filter = uDom.nodeFromSelector('#ruleFilter input').value; + const filter = qs$('#ruleFilter input').value; const rules = thePanes[key].modified; if ( filter === '' ) { return rules; } const out = []; @@ -283,7 +281,7 @@ mergeView.options.revertChunk = function( to, toStart, toEnd ) { // https://github.com/gorhill/uBlock/issues/3611 - if ( document.body.getAttribute('dir') === 'rtl' ) { + if ( dom.attr(dom.body, 'dir') === 'rtl' ) { let tmp = from; from = to; to = tmp; tmp = fromStart; fromStart = toStart; toStart = tmp; tmp = fromEnd; fromEnd = toEnd; toEnd = tmp; @@ -330,7 +328,7 @@ function handleImportFilePicker() { /******************************************************************************/ const startImportFilePicker = function() { - const input = document.getElementById('importFilePicker'); + const input = qs$('#importFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. @@ -363,7 +361,7 @@ const onFilterChanged = (( ) => { const process = function() { timer = undefined; if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } - const filter = uDom.nodeFromSelector('#ruleFilter input').value; + const filter = qs$('#ruleFilter input').value; if ( filter === last ) { return; } last = filter; if ( overlay !== null ) { @@ -498,7 +496,7 @@ const onPresentationChanged = (( ) => { const editPane = thePanes.edit; origPane.modified = origPane.original.slice(); editPane.modified = editPane.original.slice(); - const select = document.querySelector('#ruleFilter select'); + const select = qs$('#ruleFilter select'); sortType = parseInt(select.value, 10); if ( isNaN(sortType) ) { sortType = 1; } { @@ -528,7 +526,7 @@ const onTextChanged = (( ) => { const process = details => { timer = undefined; - const diff = document.getElementById('diff'); + const diff = qs$('#diff'); let isClean = mergeView.editor().isClean(cleanEditToken); if ( details === undefined && @@ -539,20 +537,17 @@ const onTextChanged = (( ) => { isClean = true; } const isDirty = mergeView.leftChunks().length !== 0; - document.body.classList.toggle('editing', isClean === false); - diff.classList.toggle('dirty', isDirty); - uDom('#editSaveButton') - .toggleClass('disabled', isClean); - uDom('#exportButton,#importButton') - .toggleClass('disabled', isClean === false); - uDom('#revertButton,#commitButton') - .toggleClass('disabled', isClean === false || isDirty === false); - const input = document.querySelector('#ruleFilter input'); + dom.cl.toggle(dom.body, 'editing', isClean === false); + dom.cl.toggle(diff, 'dirty', isDirty); + dom.cl.toggle('#editSaveButton', 'disabled', isClean); + dom.cl.toggle('#exportButton,#importButton', 'disabled', isClean === false); + dom.cl.toggle('#revertButton,#commitButton', 'disabled', isClean === false || isDirty === false); + const input = qs$('#ruleFilter input'); if ( isClean ) { - input.removeAttribute('disabled'); + dom.attr(input, 'disabled', null); CodeMirror.commands.save = undefined; } else { - input.setAttribute('disabled', ''); + dom.attr(input, 'disabled', ''); CodeMirror.commands.save = editSaveHandler; } }; @@ -659,23 +654,25 @@ vAPI.messaging.send('dashboard', { }); // Handle user interaction -uDom('#importButton').on('click', startImportFilePicker); -uDom('#importFilePicker').on('change', handleImportFilePicker); -uDom('#exportButton').on('click', exportUserRulesToFile); -uDom('#revertButton').on('click', revertAllHandler); -uDom('#commitButton').on('click', commitAllHandler); -uDom('#editSaveButton').on('click', editSaveHandler); -uDom('#ruleFilter input').on('input', onFilterChanged); -uDom('#ruleFilter select').on('input', ( ) => { +dom.on('#importButton', 'click', startImportFilePicker); +dom.on('#importFilePicker', 'change', handleImportFilePicker); +dom.on('#exportButton', 'click', exportUserRulesToFile); +dom.on('#revertButton', 'click', revertAllHandler); +dom.on('#commitButton', 'click', commitAllHandler); +dom.on('#editSaveButton', 'click', editSaveHandler); +dom.on('#ruleFilter input', 'input', onFilterChanged); +dom.on('#ruleFilter select', 'input', ( ) => { onPresentationChanged(true); }); -uDom('#ruleFilter #diffCollapse').on('click', ev => { - isCollapsed = ev.target.classList.toggle('active'); +dom.on('#ruleFilter #diffCollapse', 'click', ev => { + isCollapsed = dom.cl.toggle(ev.target, 'active'); onPresentationChanged(true); }); // https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs -mergeView.editor().on('updateDiff', ( ) => { onTextChanged(); }); +mergeView.editor().on('updateDiff', ( ) => { + onTextChanged(); +}); /******************************************************************************/ diff --git a/src/js/epicker-ui.js b/src/js/epicker-ui.js index fda45838b..9169efe56 100644 --- a/src/js/epicker-ui.js +++ b/src/js/epicker-ui.js @@ -23,8 +23,6 @@ 'use strict'; -/******************************************************************************/ - import './codemirror/ubo-static-filtering.js'; import { hostnameFromURI } from './uri-utils.js'; diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index f474e4926..b1963bb80 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-2018 Raymond Hill + Copyright (C) 2015-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 @@ -19,17 +19,17 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; +import { dom, qs$, qsa$ } from './dom.js'; + /******************************************************************************/ (( ) => { /******************************************************************************/ -const showdomButton = uDom.nodeFromId('showdom'); +const showdomButton = qs$('#showdom'); // Don't bother if the browser is not modern enough. if ( @@ -37,7 +37,7 @@ if ( Map.polyfill || typeof WeakMap === 'undefined' ) { - showdomButton.classList.add('disabled'); + dom.cl.add(showdomButton, 'disabled'); return; } @@ -48,8 +48,8 @@ var inspectorConnectionId; var inspectedTabId = 0; var inspectedURL = ''; var inspectedHostname = ''; -var inspector = uDom.nodeFromId('domInspector'); -var domTree = uDom.nodeFromId('domTree'); +var inspector = qs$('#domInspector'); +var domTree = qs$('#domTree'); var uidGenerator = 1; var filterToIdMap = new Map(); @@ -92,8 +92,8 @@ vAPI.MessagingConnection.addListener(function(msg) { const nodeFromDomEntry = function(entry) { var node, value; - var li = document.createElement('li'); - li.setAttribute('id', entry.nid); + const li = document.createElement('li'); + dom.attr(li, 'id', entry.nid); // expander/collapser li.appendChild(document.createElement('span')); // selector @@ -104,24 +104,24 @@ const nodeFromDomEntry = function(entry) { value = entry.cnt || 0; node = document.createElement('span'); node.textContent = value !== 0 ? value.toLocaleString() : ''; - node.setAttribute('data-cnt', value); + dom.attr(node, 'data-cnt', value); li.appendChild(node); // cosmetic filter if ( entry.filter === undefined ) { return li; } node = document.createElement('code'); - node.classList.add('filter'); + dom.cl.add(node, 'filter'); value = filterToIdMap.get(entry.filter); if ( value === undefined ) { value = uidGenerator.toString(); filterToIdMap.set(entry.filter, value); uidGenerator += 1; } - node.setAttribute('data-filter-id', value); + dom.attr(node, 'data-filter-id', value); node.textContent = entry.filter; li.appendChild(node); - li.classList.add('isCosmeticHide'); + dom.cl.add(li, 'isCosmeticHide'); return li; }; @@ -131,11 +131,11 @@ const appendListItem = function(ul, li) { ul.appendChild(li); // Ancestor nodes of a node which is affected by a cosmetic filter will // be marked as "containing cosmetic filters", for user convenience. - if ( li.classList.contains('isCosmeticHide') === false ) { return; } + if ( dom.cl.has(li, 'isCosmeticHide') === false ) { return; } for (;;) { li = li.parentElement.parentElement; if ( li === null ) { break; } - li.classList.add('hasCosmeticHide'); + dom.cl.add(li, 'hasCosmeticHide'); } }; @@ -162,7 +162,7 @@ const renderDOMFull = function(response) { if ( entry.lvl > lvl ) { ul = document.createElement('ul'); li.appendChild(ul); - li.classList.add('branch'); + dom.cl.add(li, 'branch'); li = nodeFromDomEntry(entry); appendListItem(ul, li); lvl = entry.lvl; @@ -181,7 +181,7 @@ const renderDOMFull = function(response) { while ( ul.parentNode !== null ) { ul = ul.parentNode; } - ul.firstElementChild.classList.add('show'); + dom.cl.add(ul.firstElementChild, 'show'); domTreeParent.appendChild(domTree); }; @@ -194,8 +194,8 @@ const patchIncremental = function(from, delta) { var span, cnt; var li = from.parentElement.parentElement; var patchCosmeticHide = delta >= 0 && - from.classList.contains('isCosmeticHide') && - li.classList.contains('hasCosmeticHide') === false; + dom.cl.has(from, 'isCosmeticHide') && + dom.cl.has(li, 'hasCosmeticHide') === false; // Include descendants count when removing a node if ( delta < 0 ) { delta -= countFromNode(from); @@ -205,10 +205,10 @@ const patchIncremental = function(from, delta) { if ( delta !== 0 ) { cnt = countFromNode(li) + delta; span.textContent = cnt !== 0 ? cnt.toLocaleString() : ''; - span.setAttribute('data-cnt', cnt); + dom.attr(span, 'data-cnt', cnt); } if ( patchCosmeticHide ) { - li.classList.add('hasCosmeticHide'); + dom.cl.add(li, 'hasCosmeticHide'); } } }; @@ -226,7 +226,7 @@ const renderDOMIncremental = function(response) { entry = journal[i]; // Remove node if ( entry.what === -1 ) { - li = document.getElementById(entry.nid); + li = qs$(`#${entry.nid}`); if ( li === null ) { continue; } patchIncremental(li, -1); li.parentNode.removeChild(li); @@ -239,7 +239,7 @@ const renderDOMIncremental = function(response) { } // Add node as sibling if ( entry.what === 1 && entry.l ) { - previous = document.getElementById(entry.l); + previous = qs$(`#{entry.l}`); // This should not happen if ( previous === null ) { // throw new Error('No left sibling!?'); @@ -253,17 +253,17 @@ const renderDOMIncremental = function(response) { } // Add node as child if ( entry.what === 1 && entry.u ) { - li = document.getElementById(entry.u); + li = qs$(`#${entry.u}`); // This should not happen if ( li === null ) { // throw new Error('No parent!?'); continue; } - ul = li.querySelector('ul'); + ul = qs$(li, 'ul'); if ( ul === null ) { ul = document.createElement('ul'); li.appendChild(ul); - li.classList.add('branch'); + dom.cl.add(li, 'branch'); } li = nodeFromDomEntry(nodes.get(entry.nid)); ul.appendChild(li); @@ -273,13 +273,11 @@ const renderDOMIncremental = function(response) { } }; -// https://www.youtube.com/watch?v=6u2KPtJB9h8 - /******************************************************************************/ const countFromNode = function(li) { var span = li.children[2]; - var cnt = parseInt(span.getAttribute('data-cnt'), 10); + var cnt = parseInt(dom.attr(span, 'data-cnt'), 10); return isNaN(cnt) ? 0 : cnt; }; @@ -290,7 +288,7 @@ const selectorFromNode = function(node) { var code; while ( node !== null ) { if ( node.localName === 'li' ) { - code = node.querySelector('code'); + code = qs$(node, 'code'); if ( code !== null ) { selector = code.textContent + ' > ' + selector; if ( selector.indexOf('#') !== -1 ) { @@ -308,7 +306,7 @@ const selectorFromNode = function(node) { const selectorFromFilter = function(node) { while ( node !== null ) { if ( node.localName === 'li' ) { - var code = node.querySelector('code:nth-of-type(2)'); + var code = qs$(node, 'code:nth-of-type(2)'); if ( code !== null ) { return code.textContent; } @@ -409,10 +407,10 @@ const startDialog = (function() { const start = function() { dialog = logger.modalDialog.create('#cosmeticFilteringDialog', stop); - textarea = dialog.querySelector('textarea'); + textarea = qs$(dialog, 'textarea'); hideSelectors = []; - for ( const node of domTree.querySelectorAll('code.off') ) { - if ( node.classList.contains('filter') ) { continue; } + for ( const node of qsa$(domTree, 'code.off') ) { + if ( dom.cl.has(node, 'filter') ) { continue; } hideSelectors.push(selectorFromNode(node)); } const taValue = []; @@ -420,8 +418,8 @@ const startDialog = (function() { taValue.push(inspectedHostname + '##' + selector); } const ids = new Set(); - for ( const node of domTree.querySelectorAll('code.filter.off') ) { - const id = node.getAttribute('data-filter-id'); + for ( const node of qsa$(domTree, 'code.filter.off') ) { + const id = dom.attr(node, 'data-filter-id'); if ( ids.has(id) ) { continue; } ids.add(id); unhideSelectors.push(node.textContent); @@ -465,13 +463,13 @@ const onClicked = function(ev) { if ( target.localName === 'span' && parent instanceof HTMLLIElement && - parent.classList.contains('branch') && + dom.cl.has(parent, 'branch') && target === parent.firstElementChild ) { - var state = parent.classList.toggle('show'); + const state = dom.cl.toggle(parent, 'show'); if ( !state ) { - for ( var node of parent.querySelectorAll('.branch') ) { - node.classList.remove('show'); + for ( const node of qsa$(parent, '.branch') ) { + dom.cl.remove(node, 'show'); } } return; @@ -481,18 +479,19 @@ const onClicked = function(ev) { if ( target.localName !== 'code' ) { return; } // Toggle cosmetic filter - if ( target.classList.contains('filter') ) { + if ( dom.cl.has(target, 'filter') ) { vAPI.MessagingConnection.sendTo(inspectorConnectionId, { what: 'toggleFilter', original: false, - target: target.classList.toggle('off'), + target: dom.cl.toggle(target, 'off'), selector: selectorFromNode(target), filter: selectorFromFilter(target), nid: nidFromNode(target) }); - uDom('[data-filter-id="' + target.getAttribute('data-filter-id') + '"]', inspector).toggleClass( + dom.cl.toggle( + qsa$(inspector, `[data-filter-id="${dom.attr(target, 'data-filter-id')}"]`), 'off', - target.classList.contains('off') + dom.cl.has(target, 'off') ); } // Toggle node @@ -500,15 +499,15 @@ const onClicked = function(ev) { vAPI.MessagingConnection.sendTo(inspectorConnectionId, { what: 'toggleNodes', original: true, - target: target.classList.toggle('off') === false, + target: dom.cl.toggle(target, 'off') === false, selector: selectorFromNode(target), nid: nidFromNode(target) }); } - var cantCreate = domTree.querySelector('.off') === null; - inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate); - inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate); + const cantCreate = qs$(domTree, '.off') === null; + dom.cl.toggle(qs$(inspector, '.permatoolbar .revert'), 'disabled', cantCreate); + dom.cl.toggle(qs$(inspector, '.permatoolbar .commit'), 'disabled', cantCreate); }; /******************************************************************************/ @@ -548,7 +547,7 @@ const onMouseOver = (function() { /******************************************************************************/ const currentTabId = function() { - if ( showdomButton.classList.contains('active') === false ) { return 0; } + if ( dom.cl.has(showdomButton, 'active') === false ) { return 0; } return logger.tabIdFromPageSelector(); }; @@ -573,7 +572,7 @@ const shutdownInspector = function() { inspectorConnectionId = undefined; } logger.removeAllChildren(domTree); - inspector.classList.remove('vExpanded'); + dom.cl.remove(inspector, 'vExpanded'); inspectedTabId = 0; }; @@ -593,76 +592,65 @@ const onTabIdChanged = function() { /******************************************************************************/ const toggleVCompactView = function() { - var state = inspector.classList.toggle('vExpanded'); - var branches = document.querySelectorAll('#domInspector li.branch'); - for ( var branch of branches ) { - branch.classList.toggle('show', state); + const state = dom.cl.toggle(inspector, 'vExpanded'); + const branches = qsa$('#domInspector li.branch'); + for ( const branch of branches ) { + dom.cl.toggle(branch, 'show', state); } }; const toggleHCompactView = function() { - inspector.classList.toggle('hCompact'); + dom.cl.toggle(inspector, 'hCompact'); }; -/******************************************************************************/ -/* -var toggleHighlightMode = function() { - vAPI.MessagingConnection.sendTo(inspectorConnectionId, { - what: 'highlightMode', - invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert') - }); -}; -*/ /******************************************************************************/ const revert = function() { - uDom('#domTree .off').removeClass('off'); + dom.cl.remove('#domTree .off', 'off'); vAPI.MessagingConnection.sendTo( inspectorConnectionId, { what: 'resetToggledNodes' } ); - inspector.querySelector('.permatoolbar .revert').classList.add('disabled'); - inspector.querySelector('.permatoolbar .commit').classList.add('disabled'); + dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled'); + dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled'); }; /******************************************************************************/ const toggleOn = function() { - uDom.nodeFromId('inspectors').classList.add('dom'); + dom.cl.add('#inspectors', 'dom'); window.addEventListener('beforeunload', toggleOff); document.addEventListener('tabIdChanged', onTabIdChanged); domTree.addEventListener('click', onClicked, true); domTree.addEventListener('mouseover', onMouseOver, true); - uDom.nodeFromSelector('#domInspector .vCompactToggler').addEventListener('click', toggleVCompactView); - uDom.nodeFromSelector('#domInspector .hCompactToggler').addEventListener('click', toggleHCompactView); - //uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode); - uDom.nodeFromSelector('#domInspector .permatoolbar .revert').addEventListener('click', revert); - uDom.nodeFromSelector('#domInspector .permatoolbar .commit').addEventListener('click', startDialog); + dom.on('#domInspector .vCompactToggler', 'click', toggleVCompactView); + dom.on('#domInspector .hCompactToggler', 'click', toggleHCompactView); + dom.on('#domInspector .permatoolbar .revert', 'click', revert); + dom.on('#domInspector .permatoolbar .commit', 'click', startDialog); injectInspector(); }; /******************************************************************************/ const toggleOff = function() { - showdomButton.classList.remove('active'); - uDom.nodeFromId('inspectors').classList.remove('dom'); + dom.cl.remove(showdomButton, 'active'); + dom.cl.remove('#inspectors', 'dom'); shutdownInspector(); window.removeEventListener('beforeunload', toggleOff); document.removeEventListener('tabIdChanged', onTabIdChanged); domTree.removeEventListener('click', onClicked, true); domTree.removeEventListener('mouseover', onMouseOver, true); - uDom.nodeFromSelector('#domInspector .vCompactToggler').removeEventListener('click', toggleVCompactView); - uDom.nodeFromSelector('#domInspector .hCompactToggler').removeEventListener('click', toggleHCompactView); - //uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode); - uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert); - uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog); + dom.off('#domInspector .vCompactToggler', 'click', toggleVCompactView); + dom.off('#domInspector .hCompactToggler', 'click', toggleHCompactView); + dom.off('#domInspector .permatoolbar .revert', 'click', revert); + dom.off('#domInspector .permatoolbar .commit', 'click', startDialog); inspectedTabId = 0; }; /******************************************************************************/ const toggle = function() { - if ( showdomButton.classList.toggle('active') ) { + if ( dom.cl.toggle(showdomButton, 'active') ) { toggleOn(); } else { toggleOff(); @@ -670,9 +658,7 @@ const toggle = function() { logger.resize(); }; -/******************************************************************************/ - -showdomButton.addEventListener('click', toggle); +dom.on(showdomButton, 'click', toggle); /******************************************************************************/ diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 6e6377923..aa0feb127 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -19,14 +19,11 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; -/******************************************************************************/ - import { hostnameFromURI } from './uri-utils.js'; import { i18n, i18n$ } from './i18n.js'; +import { dom, qs$, qsa$ } from './dom.js'; /******************************************************************************/ @@ -55,12 +52,12 @@ let cnameOfEnabled = false; // Various helpers. const tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { - const value = uDom.nodeFromId('pageSelector').value; + const value = qs$('#pageSelector').value; return value !== '_' ? (parseInt(value, 10) || 0) : activeTabId; }; const tabIdFromAttribute = function(elem) { - const value = elem.getAttribute('data-tabid') || ''; + const value = dom.attr(elem, 'data-tabid') || ''; const tabId = parseInt(value, 10); return isNaN(tabId) ? 0 : tabId; }; @@ -71,13 +68,9 @@ const tabIdFromAttribute = function(elem) { // Current design allows for only one modal DOM-based dialog at any given time. // const modalDialog = (( ) => { - const overlay = uDom.nodeFromId('modalOverlay'); - const container = overlay.querySelector( - ':scope > div > div:nth-of-type(1)' - ); - const closeButton = overlay.querySelector( - ':scope > div > div:nth-of-type(2)' - ); + const overlay = qs$('#modalOverlay'); + const container = qs$(overlay, ':scope > div > div:nth-of-type(1)'); + const closeButton = qs$(overlay, ':scope > div > div:nth-of-type(2)'); let onDestroyed; const removeChildren = logger.removeAllChildren = function(node) { @@ -87,8 +80,8 @@ const modalDialog = (( ) => { }; const create = function(selector, destroyListener) { - const template = document.querySelector(selector); - const dialog = template.cloneNode(true); + const template = qs$(selector); + const dialog = dom.clone(template); removeChildren(container); container.appendChild(dialog); onDestroyed = destroyListener; @@ -96,11 +89,11 @@ const modalDialog = (( ) => { }; const show = function() { - overlay.classList.add('on'); + dom.cl.add(overlay, 'on'); }; const destroy = function() { - overlay.classList.remove('on'); + dom.cl.remove(overlay, 'on'); const dialog = container.firstElementChild; removeChildren(container); if ( typeof onDestroyed === 'function' ) { @@ -114,8 +107,8 @@ const modalDialog = (( ) => { destroy(); } }; - overlay.addEventListener('click', onClose); - closeButton.addEventListener('click', onClose); + dom.on(overlay, 'click', onClose); + dom.on(closeButton, 'click', onClose); return { create, show, destroy }; })(); @@ -188,8 +181,8 @@ const nodeFromURL = function(parent, url, re) { } if ( /^https?:\/\//.test(url) ) { const a = document.createElement('a'); - a.setAttribute('href', url); - a.setAttribute('target', '_blank'); + dom.attr(a, 'href', url); + dom.attr(a, 'target', '_blank'); fragment.appendChild(a); } parent.appendChild(fragment); @@ -289,7 +282,7 @@ const processLoggerEntries = function(response) { const entries = response.entries; if ( entries.length === 0 ) { return; } - const autoDeleteVoidedRows = uDom.nodeFromId('pageSelector').value === '_'; + const autoDeleteVoidedRows = qs$('#pageSelector').value === '_'; const previousCount = filteredLoggerEntries.length; for ( const entry of entries ) { @@ -323,7 +316,7 @@ const processLoggerEntries = function(response) { } } if ( cnameOfEnabled === false && parsed.aliased ) { - uDom.nodeFromId('filterExprCnameOf').style.display = ''; + qs$('#filterExprCnameOf').style.display = ''; cnameOfEnabled = true; } loggerEntries.unshift(parsed); @@ -442,12 +435,12 @@ const parseLogEntry = function(details) { /******************************************************************************/ const viewPort = (( ) => { - const vwRenderer = document.getElementById('vwRenderer'); - const vwScroller = document.getElementById('vwScroller'); - const vwVirtualContent = document.getElementById('vwVirtualContent'); - const vwContent = document.getElementById('vwContent'); - const vwLineSizer = document.getElementById('vwLineSizer'); - const vwLogEntryTemplate = document.querySelector('#logEntryTemplate > div'); + const vwRenderer = qs$('#vwRenderer'); + const vwScroller = qs$('#vwScroller'); + const vwVirtualContent = qs$('#vwVirtualContent'); + const vwContent = qs$('#vwContent'); + const vwLineSizer = qs$('#vwLineSizer'); + const vwLogEntryTemplate = qs$('#logEntryTemplate > div'); const vwEntries = []; const detailableRealms = new Set([ 'network', 'extended' ]); @@ -505,19 +498,16 @@ const viewPort = (( ) => { ); }; - vwScroller.addEventListener('scroll', onScroll, { passive: true }); + dom.on(vwScroller, 'scroll', onScroll, { passive: true }); const onLayoutChanged = function() { vwHeight = vwRenderer.clientHeight; vwContent.style.height = `${vwScroller.clientHeight}px`; const vExpanded = - uDom.nodeFromSelector('#netInspector .vCompactToggler') - .classList - .contains('vExpanded'); + dom.cl.has('#netInspector .vCompactToggler', 'vExpanded'); - let newLineHeight = - vwLineSizer.querySelector('.oneLine').clientHeight; + let newLineHeight = qs$(vwLineSizer, '.oneLine').clientHeight; if ( vExpanded ) { newLineHeight *= loggerSettings.linesPerEntry; @@ -537,7 +527,7 @@ const viewPort = (( ) => { } const cellWidths = Array.from( - vwLineSizer.querySelectorAll('.oneLine span') + qsa$(vwLineSizer, '.oneLine span') ).map((el, i) => { return loggerSettings.columns[i] !== false ? el.clientWidth + 1 @@ -559,7 +549,7 @@ const viewPort = (( ) => { cellWidths[3] = 0.25; cellWidths[6] = 0.5; } - const style = document.getElementById('vwRendererRuntimeStyles'); + const style = qs$('#vwRendererRuntimeStyles'); const cssRules = [ '#vwContent .logEntry {', ` height: ${newLineHeight}px;`, @@ -602,9 +592,7 @@ const viewPort = (( ) => { lineHeight = newLineHeight; positionLines(); - uDom.nodeFromId('netInspector') - .classList - .toggle('vExpanded', vExpanded); + dom.cl.toggle('#netInspector', 'vExpanded', vExpanded); updateContent(0); }; @@ -622,7 +610,7 @@ const viewPort = (( ) => { ); }; - window.addEventListener('resize', updateLayout, { passive: true }); + dom.on(window, 'resize', updateLayout, { passive: true }); updateLayout(); @@ -653,7 +641,7 @@ const viewPort = (( ) => { vwEntry.logEntry = details; const cells = details.textContent.split('\t'); - const div = vwLogEntryTemplate.cloneNode(true); + const div = dom.clone(vwLogEntryTemplate); const divcl = div.classList; let span; @@ -668,7 +656,7 @@ const viewPort = (( ) => { // Tab id if ( details.tabId !== undefined ) { - div.setAttribute('data-tabid', details.tabId); + dom.attr(div, 'data-tabid', details.tabId); if ( details.voided ) { divcl.add('voided'); } @@ -676,7 +664,7 @@ const viewPort = (( ) => { if ( details.realm === 'message' ) { if ( details.type !== undefined ) { - div.setAttribute('data-type', details.type); + dom.attr(div, 'data-type', details.type); } span = div.children[1]; span.textContent = cells[1]; @@ -701,7 +689,7 @@ const viewPort = (( ) => { divcl.toggle('isException', filter.raw.startsWith('#@#')); } if ( filter.modifier === true ) { - div.setAttribute('data-modifier', ''); + dom.attr(div, 'data-modifier', ''); } } span = div.children[1]; @@ -711,11 +699,11 @@ const viewPort = (( ) => { // Event if ( cells[2] === '--' ) { - div.setAttribute('data-status', '1'); + dom.attr(div, 'data-status', '1'); } else if ( cells[2] === '++' ) { - div.setAttribute('data-status', '2'); + dom.attr(div, 'data-status', '2'); } else if ( cells[2] === '**' ) { - div.setAttribute('data-status', '3'); + dom.attr(div, 'data-status', '3'); } else if ( cells[2] === '<<' ) { divcl.add('redirect'); } @@ -724,10 +712,10 @@ const viewPort = (( ) => { // Origins if ( details.tabHostname ) { - div.setAttribute('data-tabhn', details.tabHostname); + dom.attr(div, 'data-tabhn', details.tabHostname); } if ( details.docHostname ) { - div.setAttribute('data-dochn', details.docHostname); + dom.attr(div, 'data-dochn', details.docHostname); } span = div.children[3]; span.textContent = cells[3]; @@ -743,7 +731,7 @@ const viewPort = (( ) => { text += ` \u22ef ${details.docDomain}`; } text += ` \u21d2 ${details.domain}`; - div.setAttribute('data-parties', text); + dom.attr(div, 'data-parties', text); } span = div.children[4]; span.textContent = cells[4]; @@ -765,7 +753,7 @@ const viewPort = (( ) => { if ( cells.length > 7 ) { const pos = details.textContent.lastIndexOf('\taliasURL='); if ( pos !== -1 ) { - div.setAttribute('data-aliasid', details.id); + dom.attr(div, 'data-aliasid', details.id); } } @@ -873,10 +861,10 @@ const updateCurrentTabTitle = (( ) => { const i18nCurrentTab = i18n$('loggerCurrentTab'); return function() { - const select = uDom.nodeFromId('pageSelector'); + const select = qs$('#pageSelector'); if ( select.value !== '_' || activeTabId === 0 ) { return; } - const opt0 = select.querySelector('[value="_"]'); - const opt1 = select.querySelector(`[value="${activeTabId}"]`); + const opt0 = qs$(select, '[value="_"]'); + const opt1 = qs$(select, `[value="${activeTabId}"]`); let text = i18nCurrentTab; if ( opt1 !== null ) { text += ' / ' + opt1.textContent; @@ -888,7 +876,7 @@ const updateCurrentTabTitle = (( ) => { /******************************************************************************/ const synchronizeTabIds = function(newTabIds) { - const select = uDom.nodeFromId('pageSelector'); + const select = qs$('#pageSelector'); const selectedTabValue = select.value; const oldTabIds = allTabIds; @@ -942,12 +930,12 @@ const synchronizeTabIds = function(newTabIds) { const option = select.options[j]; // Truncate too long labels. option.textContent = newTabIds.get(tabId).slice(0, 80); - option.setAttribute('value', tabId); + dom.attr(option, 'value', tabId); if ( option.value === selectedTabValue ) { select.selectedIndex = j; - option.setAttribute('selected', ''); + dom.attr(option, 'selected', ''); } else { - option.removeAttribute('selected'); + dom.attr(option, 'selected', null); } j += 1; } @@ -957,7 +945,7 @@ const synchronizeTabIds = function(newTabIds) { if ( select.value !== selectedTabValue ) { select.selectedIndex = 0; select.value = ''; - select.options[0].setAttribute('selected', ''); + dom.attr(select.options[0], 'selected', ''); pageSelectorChanged(); } @@ -976,7 +964,7 @@ const onLogBufferRead = function(response) { ) { popupLoggerTooltips = response.tooltips; if ( popupLoggerTooltips === false ) { - uDom('[data-i18n-title]').attr('title', ''); + dom.attr('[data-i18n-title]', 'title', ''); } } @@ -1006,18 +994,9 @@ const onLogBufferRead = function(response) { processLoggerEntries(response); // Synchronize DOM with sent logger data - document.documentElement.classList.toggle( - 'colorBlind', - response.colorBlind === true - ); - uDom.nodeFromId('clean').classList.toggle( - 'disabled', - filteredLoggerEntryVoidedCount === 0 - ); - uDom.nodeFromId('clear').classList.toggle( - 'disabled', - filteredLoggerEntries.length === 0 - ); + dom.cl.toggle(dom.html, 'colorBlind', response.colorBlind === true); + dom.cl.toggle('#clean', 'disabled', filteredLoggerEntryVoidedCount === 0); + dom.cl.toggle('#clear', 'disabled', filteredLoggerEntries.length === 0); }; /******************************************************************************/ @@ -1073,7 +1052,7 @@ const readLogBuffer = (( ) => { /******************************************************************************/ const pageSelectorChanged = function() { - const select = uDom.nodeFromId('pageSelector'); + const select = qs$('#pageSelector'); window.location.replace('#' + select.value); pageSelectorFromURLHash(); }; @@ -1092,10 +1071,8 @@ const pageSelectorFromURLHash = (( ) => { } if ( hash !== lastHash ) { - const select = uDom.nodeFromId('pageSelector'); - let option = select.querySelector( - 'option[value="' + hash + '"]' - ); + const select = qs$('#pageSelector'); + let option = qs$(select, `option[value="${hash}"]`); if ( option === null ) { hash = '0'; option = select.options[0]; @@ -1114,8 +1091,8 @@ const pageSelectorFromURLHash = (( ) => { rowFilterer.filterAll(); document.dispatchEvent(new Event('tabIdChanged')); updateCurrentTabTitle(); - uDom('.needdom').toggleClass('disabled', selectedTabId <= 0); - uDom('.needscope').toggleClass('disabled', selectedTabId <= 0); + dom.cl.toggle('.needdom', 'disabled', selectedTabId <= 0); + dom.cl.toggle('.needscope', 'disabled', selectedTabId <= 0); lastSelectedTabId = selectedTabId; }; })(); @@ -1168,7 +1145,7 @@ const reloadTab = function(ev) { }; const selectNode = function(selector) { - return dialog.querySelector(selector); + return qs$(dialog, selector); }; const selectValue = function(selector) { @@ -1176,20 +1153,20 @@ const reloadTab = function(ev) { }; const staticFilterNode = function() { - return dialog.querySelector('div.panes > div.static textarea'); + return qs$(dialog, 'div.panes > div.static textarea'); }; const onColorsReady = function(response) { - document.body.classList.toggle('dirty', response.dirty); + dom.cl.toggle(dom.body, 'dirty', response.dirty); for ( const url in response.colors ) { if ( response.colors.hasOwnProperty(url) === false ) { continue; } const colorEntry = response.colors[url]; - const node = dialog.querySelector('.dynamic .entry .action[data-url="' + url + '"]'); + const node = qs$(dialog, `.dynamic .entry .action[data-url="${url}"]`); if ( node === null ) { continue; } - node.classList.toggle('allow', colorEntry.r === 2); - node.classList.toggle('noop', colorEntry.r === 3); - node.classList.toggle('block', colorEntry.r === 1); - node.classList.toggle('own', colorEntry.own); + dom.cl.toggle(node, 'allow', colorEntry.r === 2); + dom.cl.toggle(node, 'noop', colorEntry.r === 3); + dom.cl.toggle(node, 'block', colorEntry.r === 1); + dom.cl.toggle(node, 'own', colorEntry.own); } }; @@ -1248,7 +1225,8 @@ const reloadTab = function(ev) { const updateWidgets = function() { const value = staticFilterNode().value; - dialog.querySelector('#createStaticFilter').classList.toggle( + dom.cl.toggle( + qs$(dialog, '#createStaticFilter'), 'disabled', createdStaticFilters.hasOwnProperty(value) || value === '' ); @@ -1261,7 +1239,7 @@ const reloadTab = function(ev) { // Select a mode if ( tcl.contains('header') ) { ev.stopPropagation(); - dialog.setAttribute('data-pane', target.getAttribute('data-pane') ); + dom.attr(dialog, 'data-pane', dom.attr(target, 'data-pane')); return; } @@ -1273,7 +1251,7 @@ const reloadTab = function(ev) { filter: filterFromTargetRow(), }); const row = target.closest('div'); - row.classList.toggle('exceptored', status); + dom.cl.toggle(row, 'exceptored', status); return; } @@ -1321,7 +1299,7 @@ const reloadTab = function(ev) { await messaging.send('loggerUI', { what: 'setURLFilteringRule', context: selectValue('select.dynamic.origin'), - url: target.getAttribute('data-url'), + url: dom.attr(target, 'data-url'), type: uglyTypeFromSelector('dynamic'), action: 0, persist: persist, @@ -1336,7 +1314,7 @@ const reloadTab = function(ev) { await messaging.send('loggerUI', { what: 'setURLFilteringRule', context: selectValue('select.dynamic.origin'), - url: target.parentNode.getAttribute('data-url'), + url: dom.attr(target.parentNode, 'data-url'), type: uglyTypeFromSelector('dynamic'), action: 2, persist: persist, @@ -1351,7 +1329,7 @@ const reloadTab = function(ev) { await messaging.send('loggerUI', { what: 'setURLFilteringRule', context: selectValue('select.dynamic.origin'), - url: target.parentNode.getAttribute('data-url'), + url: dom.attr(target.parentNode, 'data-url'), type: uglyTypeFromSelector('dynamic'), action: 3, persist: persist, @@ -1366,7 +1344,7 @@ const reloadTab = function(ev) { await messaging.send('loggerUI', { what: 'setURLFilteringRule', context: selectValue('select.dynamic.origin'), - url: target.parentNode.getAttribute('data-url'), + url: dom.attr(target.parentNode, 'data-url'), type: uglyTypeFromSelector('dynamic'), action: 1, persist: persist, @@ -1419,11 +1397,12 @@ const reloadTab = function(ev) { const createPreview = function(type, url) { const cantPreview = type !== 'image' || - targetRow.classList.contains('networkRealm') === false || - targetRow.getAttribute('data-status') === '1'; + dom.cl.has(targetRow, 'networkRealm') === false || + dom.attr(targetRow, 'data-status') === '1'; // Whether picker can be used - dialog.querySelector('.picker').classList.toggle( + dom.cl.toggle( + qs$(dialog, '.picker'), 'hide', targetTabId < 0 || cantPreview ); @@ -1431,18 +1410,14 @@ const reloadTab = function(ev) { // Whether the resource can be previewed if ( cantPreview ) { return; } - const container = dialog.querySelector('.preview'); - container.querySelector('span').addEventListener( - 'click', - ( ) => { - const preview = document.createElement('img'); - preview.setAttribute('src', url); - container.replaceChild(preview, container.firstElementChild); - }, - { once: true } - ); + const container = qs$(dialog, '.preview'); + dom.on(qs$(container, 'span'), 'click', ( ) => { + const preview = dom.create('img'); + dom.attr(preview, 'src', url); + container.replaceChild(preview, container.firstElementChild); + }, { once: true }); - container.classList.remove('hide'); + dom.cl.remove(container, 'hide'); }; // https://github.com/gorhill/uBlock/issues/1511 @@ -1486,7 +1461,7 @@ const reloadTab = function(ev) { }; const filterFromTargetRow = function() { - return targetRow.children[1].textContent; + return dom.text(targetRow.children[1]); }; const aliasURLFromID = function(id) { @@ -1516,7 +1491,7 @@ const reloadTab = function(ev) { what: 'hasTemporaryException', filter, }); - receiver.classList.toggle('exceptored', isTemporaryException); + dom.cl.toggle(receiver, 'exceptored', isTemporaryException); if ( match[0] === '##' || isTemporaryException ) { receiver.children[2].style.visibility = ''; } @@ -1529,17 +1504,15 @@ const reloadTab = function(ev) { const nodeFromFilter = function(filter, lists) { const fragment = document.createDocumentFragment(); - const template = document.querySelector( - '#filterFinderListEntry > span' - ); + const template = qs$('#filterFinderListEntry > span'); for ( const list of lists ) { - const span = template.cloneNode(true); - let a = span.querySelector('a:nth-of-type(1)'); + const span = dom.clone(template); + let a = qs$(span, 'a:nth-of-type(1)'); a.href += encodeURIComponent(list.assetKey); a.textContent = list.title; - a = span.querySelector('a:nth-of-type(2)'); + a = qs$(span, 'a:nth-of-type(2)'); if ( list.supportURL ) { - a.setAttribute('href', list.supportURL); + dom.attr(a, 'href', list.supportURL); } else { a.style.display = 'none'; } @@ -1581,13 +1554,13 @@ const reloadTab = function(ev) { } }; - if ( targetRow.classList.contains('networkRealm') ) { + if ( dom.cl.has(targetRow, 'networkRealm') ) { const response = await messaging.send('loggerUI', { what: 'listsFromNetFilter', rawFilter: rawFilter, }); handleResponse(response); - } else if ( targetRow.classList.contains('extendedRealm') ) { + } else if ( dom.cl.has(targetRow, 'extendedRealm') ) { const response = await messaging.send('loggerUI', { what: 'listsFromCosmeticFilter', url: targetRow.children[6].textContent, @@ -1598,7 +1571,7 @@ const reloadTab = function(ev) { }; const fillSummaryPane = function() { - const rows = dialog.querySelectorAll('.pane.details > div'); + const rows = qsa$(dialog, '.pane.details > div'); const tr = targetRow; const trcl = tr.classList; const trch = tr.children; @@ -1633,8 +1606,8 @@ const reloadTab = function(ev) { rows[1].style.display = 'none'; } // Root and immediate contexts - const tabhn = tr.getAttribute('data-tabhn') || ''; - const dochn = tr.getAttribute('data-dochn') || ''; + const tabhn = dom.attr(tr, 'data-tabhn') || ''; + const dochn = dom.attr(tr, 'data-dochn') || ''; if ( tabhn !== '' && tabhn !== dochn ) { rows[3].children[1].textContent = tabhn; } else { @@ -1646,7 +1619,7 @@ const reloadTab = function(ev) { rows[4].style.display = 'none'; } // Partyness - text = tr.getAttribute('data-parties') || ''; + text = dom.attr(tr, 'data-parties') || ''; if ( text !== '' ) { rows[5].children[1].textContent = `(${trch[4].textContent})\u2002${text}`; } else { @@ -1662,19 +1635,19 @@ const reloadTab = function(ev) { // URL const canonicalURL = trch[6].textContent; if ( canonicalURL !== '' ) { - const attr = tr.getAttribute('data-status') || ''; + const attr = dom.attr(tr, 'data-status') || ''; if ( attr !== '' ) { - rows[7].setAttribute('data-status', attr); + dom.attr(rows[7], 'data-status', attr); if ( tr.hasAttribute('data-modifier') ) { - rows[7].setAttribute('data-modifier', ''); + dom.attr(rows[7], 'data-modifier', ''); } } - rows[7].children[1].appendChild(trch[6].cloneNode(true)); + rows[7].children[1].appendChild(dom.clone(trch[6])); } else { rows[7].style.display = 'none'; } // Alias URL - text = tr.getAttribute('data-aliasid'); + text = dom.attr(tr, 'data-aliasid'); const aliasURL = text ? aliasURLFromID(text) : ''; if ( aliasURL !== '' ) { rows[8].children[1].textContent = @@ -1689,9 +1662,7 @@ const reloadTab = function(ev) { // Fill dynamic URL filtering pane const fillDynamicPane = function() { - if ( targetRow.classList.contains('extendedRealm') ) { - return; - } + if ( dom.cl.has(targetRow, 'extendedRealm') ) { return; } // https://github.com/uBlockOrigin/uBlock-issues/issues/662#issuecomment-509220702 if ( targetType === 'doc' ) { return; } @@ -1706,21 +1677,21 @@ const reloadTab = function(ev) { fillOriginSelect(select, targetPageHostname, targetPageDomain); const option = document.createElement('option'); option.textContent = '*'; - option.setAttribute('value', '*'); + dom.attr(option, 'value', '*'); select.appendChild(option); // Fill type selector select = selectNode('select.dynamic.type'); select.options[0].textContent = targetType; - select.options[0].setAttribute('value', targetType); + dom.attr(select.options[0], 'value', targetType); select.selectedIndex = 0; // Fill entries - const menuEntryTemplate = dialog.querySelector('.dynamic .toolbar .entry'); - const tbody = dialog.querySelector('.dynamic .entries'); + const menuEntryTemplate = qs$(dialog, '.dynamic .toolbar .entry'); + const tbody = qs$(dialog, '.dynamic .entries'); for ( const targetURL of targetURLs ) { - const menuEntry = menuEntryTemplate.cloneNode(true); - menuEntry.children[0].setAttribute('data-url', targetURL); + const menuEntry = dom.clone(menuEntryTemplate); + dom.attr(menuEntry.children[0], 'data-url', targetURL); menuEntry.children[1].textContent = shortenLongString(targetURL, 128); tbody.appendChild(menuEntry); } @@ -1733,7 +1704,7 @@ const reloadTab = function(ev) { let value = hostname; for (;;) { const option = document.createElement('option'); - option.setAttribute('value', value); + dom.attr(option, 'value', value); option.textContent = template.replace('{{origin}}', value); select.appendChild(option); if ( value === domain ) { break; } @@ -1745,9 +1716,7 @@ const reloadTab = function(ev) { // Fill static filtering pane const fillStaticPane = function() { - if ( targetRow.classList.contains('extendedRealm') ) { - return; - } + if ( dom.cl.has(targetRow, 'extendedRealm') ) { return; } const template = i18n$('loggerStaticFilteringSentence'); const rePlaceholder = /\{\{[^}]+?\}\}/g; @@ -1770,11 +1739,11 @@ const reloadTab = function(ev) { select = document.createElement('select'); select.className = 'static action'; option = document.createElement('option'); - option.setAttribute('value', ''); + dom.attr(option, 'value', ''); option.textContent = i18n$('loggerStaticFilteringSentencePartBlock'); select.appendChild(option); option = document.createElement('option'); - option.setAttribute('value', '@@'); + dom.attr(option, 'value', '@@'); option.textContent = i18n$('loggerStaticFilteringSentencePartAllow'); select.appendChild(option); nodes.push(select); @@ -1785,11 +1754,11 @@ const reloadTab = function(ev) { select = document.createElement('select'); select.className = 'static type'; option = document.createElement('option'); - option.setAttribute('value', filterType); + dom.attr(option, 'value', filterType); option.textContent = i18n$('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType); select.appendChild(option); option = document.createElement('option'); - option.setAttribute('value', ''); + dom.attr(option, 'value', ''); option.textContent = i18n$('loggerStaticFilteringSentencePartAnyType'); select.appendChild(option); nodes.push(select); @@ -1801,7 +1770,7 @@ const reloadTab = function(ev) { for ( const targetURL of targetURLs ) { const value = targetURL.replace(/^[a-z-]+:\/\//, ''); option = document.createElement('option'); - option.setAttribute('value', value); + dom.attr(option, 'value', value); option.textContent = shortenLongString(value, 128); select.appendChild(option); } @@ -1813,7 +1782,7 @@ const reloadTab = function(ev) { select.className = 'static origin'; fillOriginSelect(select, targetFrameHostname, targetFrameDomain); option = document.createElement('option'); - option.setAttribute('value', ''); + dom.attr(option, 'value', ''); option.textContent = i18n$('loggerStaticFilteringSentencePartAnyOrigin'); select.appendChild(option); nodes.push(select); @@ -1823,11 +1792,11 @@ const reloadTab = function(ev) { select = document.createElement('select'); select.className = 'static importance'; option = document.createElement('option'); - option.setAttribute('value', ''); + dom.attr(option, 'value', ''); option.textContent = i18n$('loggerStaticFilteringSentencePartNotImportant'); select.appendChild(option); option = document.createElement('option'); - option.setAttribute('value', 'important'); + dom.attr(option, 'value', 'important'); option.textContent = i18n$('loggerStaticFilteringSentencePartImportant'); select.appendChild(option); nodes.push(select); @@ -1840,7 +1809,7 @@ const reloadTab = function(ev) { if ( pos < template.length ) { nodes.push(document.createTextNode(template.slice(pos))); } - const parent = dialog.querySelector('div.panes > .static > div:first-of-type'); + const parent = qs$(dialog, 'div.panes > .static > div:first-of-type'); for ( let i = 0; i < nodes.length; i++ ) { parent.appendChild(nodes[i]); } @@ -1856,9 +1825,10 @@ const reloadTab = function(ev) { dialog = null; } ); - dialog.classList.toggle( + dom.cl.toggle( + dialog, 'extendedRealm', - targetRow.classList.contains('extendedRealm') + dom.cl.has(targetRow, 'extendedRealm') ); targetDomain = domains[0]; targetPageDomain = domains[1]; @@ -1867,9 +1837,9 @@ const reloadTab = function(ev) { fillSummaryPane(); fillDynamicPane(); fillStaticPane(); - dialog.addEventListener('click', ev => { onClick(ev); }, true); - dialog.addEventListener('change', onSelectChange, true); - dialog.addEventListener('input', onInputChange, true); + dom.on(dialog, 'click', ev => { onClick(ev); }, true); + dom.on(dialog, 'change', onSelectChange, true); + dom.on(dialog, 'input', onInputChange, true); modalDialog.show(); }; @@ -1880,8 +1850,8 @@ const reloadTab = function(ev) { targetTabId = tabIdFromAttribute(targetRow); targetType = targetRow.children[5].textContent.trim() || ''; targetURLs = createTargetURLs(targetRow.children[6].textContent); - targetPageHostname = targetRow.getAttribute('data-tabhn') || ''; - targetFrameHostname = targetRow.getAttribute('data-dochn') || ''; + targetPageHostname = dom.attr(targetRow, 'data-tabhn') || ''; + targetFrameHostname = dom.attr(targetRow, 'data-dochn') || ''; // We need the root domain names for best user experience. const domains = await messaging.send('loggerUI', { @@ -1895,7 +1865,8 @@ const reloadTab = function(ev) { fillDialog(domains); }; - uDom('#netInspector').on( + dom.on( + '#netInspector', 'click', '.canDetails > span:nth-of-type(2),.canDetails > span:nth-of-type(3),.canDetails > span:nth-of-type(5)', ev => { toggleOn(ev); } @@ -1915,11 +1886,7 @@ const rowFilterer = (( ) => { const parseInput = function() { userFilters.length = 0; - const rawParts = - uDom.nodeFromSelector('#filterInput > input') - .value - .trim() - .split(/\s+/); + const rawParts = qs$('#filterInput > input').value.trim().split(/\s+/); const n = rawParts.length; const reStrs = []; let not = false; @@ -2015,18 +1982,9 @@ const rowFilterer = (( ) => { } } viewPort.updateContent(0); - uDom.nodeFromId('filterButton').classList.toggle( - 'active', - filters.length !== 0 - ); - uDom.nodeFromId('clean').classList.toggle( - 'disabled', - filteredLoggerEntryVoidedCount === 0 - ); - uDom.nodeFromId('clear').classList.toggle( - 'disabled', - filteredLoggerEntries.length === 0 - ); + dom.cl.toggle('#filterButton', 'active', filters.length !== 0); + dom.cl.toggle('#clean', 'disabled', filteredLoggerEntryVoidedCount === 0); + dom.cl.toggle('#clear', 'disabled', filteredLoggerEntries.length === 0); }; const onFilterChangedAsync = (( ) => { @@ -2046,27 +2004,24 @@ const rowFilterer = (( ) => { const onFilterButton = function() { masterFilterSwitch = !masterFilterSwitch; - uDom.nodeFromId('netInspector').classList.toggle( - 'f', - masterFilterSwitch - ); + dom.cl.toggle('#netInspector', 'f', masterFilterSwitch); filterAll(); }; const onToggleExtras = function(ev) { - ev.target.classList.toggle('expanded'); + dom.cl.toggle(ev.target, 'expanded'); }; const onToggleBuiltinExpression = function(ev) { builtinFilters.length = 0; - ev.target.classList.toggle('on'); - const filtexElems = ev.currentTarget.querySelectorAll('[data-filtex]'); + dom.cl.toggle(ev.target, 'on'); + const filtexElems = qsa$(ev.currentTarget, '[data-filtex]'); const orExprs = []; let not = false; for ( const filtexElem of filtexElems ) { - let filtex = filtexElem.getAttribute('data-filtex'); - let active = filtexElem.classList.contains('on'); + let filtex = dom.attr(filtexElem, 'data-filtex'); + let active = dom.cl.has(filtexElem, 'on'); if ( filtex === '!' ) { if ( orExprs.length !== 0 ) { builtinFilters.push({ @@ -2087,17 +2042,14 @@ const rowFilterer = (( ) => { }); } filters = builtinFilters.concat(userFilters); - uDom.nodeFromId('filterExprButton').classList.toggle( - 'active', - builtinFilters.length !== 0 - ); + dom.cl.toggle('#filterExprButton', 'active', builtinFilters.length !== 0); filterAll(); }; - uDom('#filterButton').on('click', onFilterButton); - uDom('#filterInput > input').on('input', onFilterChangedAsync); - uDom('#filterExprButton').on('click', onToggleExtras); - uDom('#filterExprPicker').on('click', '[data-filtex]', onToggleBuiltinExpression); + dom.on('#filterButton', 'click', onFilterButton); + dom.on('#filterInput > input', 'input', onFilterChangedAsync); + dom.on('#filterExprButton', 'click', onToggleExtras); + dom.on('#filterExprPicker', 'click', '[data-filtex]', onToggleBuiltinExpression); // https://github.com/gorhill/uBlock/issues/404 // Ensure page state is in sync with the state of its various widgets. @@ -2294,8 +2246,8 @@ const rowJanitor = (( ) => { discardAsync(); - uDom.nodeFromId('clean').addEventListener('click', clean); - uDom.nodeFromId('clear').addEventListener('click', clear); + dom.on('#clean', 'click', clean); + dom.on('#clear', 'click', clear); return { inserted: function(count) { @@ -2309,17 +2261,13 @@ const rowJanitor = (( ) => { /******************************************************************************/ const pauseNetInspector = function() { - netInspectorPaused = uDom.nodeFromId('netInspector') - .classList - .toggle('paused'); + netInspectorPaused = dom.cl.toggle('#netInspector', 'paused'); }; /******************************************************************************/ const toggleVCompactView = function() { - uDom.nodeFromSelector('#netInspector .vCompactToggler') - .classList - .toggle('vExpanded'); + dom.cl.toggle('#netInspector .vCompactToggler', 'vExpanded'); viewPort.updateLayout(); }; @@ -2351,7 +2299,7 @@ const popupManager = (( ) => { const setTabId = function(tabId) { if ( popup === null ) { return; } - popup.setAttribute('src', 'popup-fenix.html?portrait=1&tabId=' + tabId); + dom.attr(popup, 'src', `popup-fenix.html?portrait=1&tabId=${tabId}`); }; const onTabIdChanged = function() { @@ -2366,30 +2314,30 @@ const popupManager = (( ) => { if ( tabId === 0 ) { return; } realTabId = tabId; - popup = uDom.nodeFromId('popupContainer'); + popup = qs$('#popupContainer'); - popup.addEventListener('load', onLoad); + dom.on(popup, 'load', onLoad); popupObserver = new MutationObserver(resizePopup); - const parent = uDom.nodeFromId('inspectors'); + const parent = qs$('#inspectors'); const rect = parent.getBoundingClientRect(); popup.style.setProperty('right', `${rect.right - parent.clientWidth}px`); - parent.classList.add('popupOn'); + dom.cl.add(parent, 'popupOn'); - document.addEventListener('tabIdChanged', onTabIdChanged); + dom.on(document, 'tabIdChanged', onTabIdChanged); setTabId(realTabId); - uDom.nodeFromId('showpopup').classList.add('active'); + dom.cl.add('#showpopup', 'active'); }; const toggleOff = function() { - uDom.nodeFromId('showpopup').classList.remove('active'); - document.removeEventListener('tabIdChanged', onTabIdChanged); - uDom.nodeFromId('inspectors').classList.remove('popupOn'); - popup.removeEventListener('load', onLoad); + dom.cl.remove('#showpopup', 'active'); + dom.off(document, 'tabIdChanged', onTabIdChanged); + dom.cl.remove('#inspectors', 'popupOn'); + dom.off(popup, 'load', onLoad); popupObserver.disconnect(); popupObserver = null; - popup.setAttribute('src', ''); + dom.attr(popup, 'src', ''); realTabId = 0; }; @@ -2403,12 +2351,9 @@ const popupManager = (( ) => { } }; - uDom.nodeFromId('showpopup').addEventListener( - 'click', - ( ) => { - void (realTabId === 0 ? toggleOn() : toggleOff()); - } - ); + dom.on('#showpopup', 'click', ( ) => { + void (realTabId === 0 ? toggleOn() : toggleOff()); + }); return api; })(); @@ -2440,7 +2385,7 @@ const loggerStats = (( ) => { }); const doc = document; - const parent = dialog.querySelector('.sortedEntries'); + const parent = qs$(dialog, '.sortedEntries'); let i = 0; // Reuse existing rows @@ -2481,7 +2426,7 @@ const loggerStats = (( ) => { modalDialog.show(); }; - uDom.nodeFromId('loggerStats').addEventListener('click', toggleOn); + dom.on('#loggerStats', 'click', toggleOn); return { processFilter: function(filter) { @@ -2608,7 +2553,7 @@ const loggerStats = (( ) => { }; const format = function() { - const output = dialog.querySelector('.output'); + const output = qs$(dialog, '.output'); if ( options.format === 'list' ) { output.textContent = formatAsList(); } else { @@ -2618,12 +2563,13 @@ const loggerStats = (( ) => { const setRadioButton = function(group, value) { if ( options.hasOwnProperty(group) === false ) { return; } - const groupEl = dialog.querySelector(`[data-radio="${group}"]`); - const buttonEls = groupEl.querySelectorAll('[data-radio-item]'); + const groupEl = qs$(dialog, `[data-radio="${group}"]`); + const buttonEls = qsa$(groupEl, '[data-radio-item]'); for ( const buttonEl of buttonEls ) { - buttonEl.classList.toggle( + dom.cl.toggle( + buttonEl, 'on', - buttonEl.getAttribute('data-radio-item') === value + dom.attr(buttonEl, 'data-radio-item') === value ); } options[group] = value; @@ -2635,7 +2581,7 @@ const loggerStats = (( ) => { // Copy to clipboard if ( target.matches('.pushbutton') ) { - const textarea = dialog.querySelector('textarea'); + const textarea = qs$(dialog, 'textarea'); textarea.focus(); if ( textarea.selectionEnd === textarea.selectionStart ) { textarea.select(); @@ -2652,8 +2598,8 @@ const loggerStats = (( ) => { const item = target.closest('[data-radio-item]'); if ( item === null ) { return; } setRadioButton( - group.getAttribute('data-radio'), - item.getAttribute('data-radio-item') + dom.attr(group, 'data-radio'), + dom.attr(item, 'data-radio-item') ); format(); ev.stopPropagation(); @@ -2674,16 +2620,12 @@ const loggerStats = (( ) => { collectLines(); format(); - dialog.querySelector('.options').addEventListener( - 'click', - onOption, - { capture: true } - ); + dom.on(qs$(dialog, '.options'), 'click', onOption, { capture: true }); modalDialog.show(); }; - uDom.nodeFromId('loggerExport').addEventListener('click', toggleOn); + dom.on('#loggerExport', 'click', toggleOn); })(); /******************************************************************************/ @@ -2729,11 +2671,11 @@ const loggerSettings = (( ) => { const valueFromInput = function(input, def) { let value = parseInt(input.value, 10); if ( isNaN(value) ) { value = def; } - const min = parseInt(input.getAttribute('min'), 10); + const min = parseInt(dom.attr(input, 'min'), 10); if ( isNaN(min) === false ) { value = Math.max(value, min); } - const max = parseInt(input.getAttribute('max'), 10); + const max = parseInt(dom.attr(input, 'max'), 10); if ( isNaN(max) === false ) { value = Math.min(value, max); } @@ -2749,12 +2691,12 @@ const loggerSettings = (( ) => { ); // Number inputs - let inputs = dialog.querySelectorAll('input[type="number"]'); + let inputs = qsa$(dialog, 'input[type="number"]'); inputs[0].value = settings.discard.maxAge; inputs[1].value = settings.discard.maxLoadCount; inputs[2].value = settings.discard.maxEntryCount; inputs[3].value = settings.linesPerEntry; - inputs[3].addEventListener('input', ev => { + dom.on(inputs[3], 'input', ev => { settings.linesPerEntry = valueFromInput(ev.target, 4); viewPort.updateLayout(); }); @@ -2762,15 +2704,15 @@ const loggerSettings = (( ) => { // Column checkboxs const onColumnChanged = ev => { const input = ev.target; - const i = parseInt(input.getAttribute('data-column'), 10); + const i = parseInt(dom.attr(input, 'data-column'), 10); settings.columns[i] = input.checked !== true; viewPort.updateLayout(); }; - inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]'); + inputs = qsa$(dialog, 'input[type="checkbox"][data-column]'); for ( const input of inputs ) { - const i = parseInt(input.getAttribute('data-column'), 10); + const i = parseInt(dom.attr(input, 'data-column'), 10); input.checked = settings.columns[i] === false; - input.addEventListener('change', onColumnChanged); + dom.on(input, 'change', onColumnChanged); } modalDialog.show(); @@ -2778,16 +2720,16 @@ const loggerSettings = (( ) => { const toggleOff = function(dialog) { // Number inputs - let inputs = dialog.querySelectorAll('input[type="number"]'); + let inputs = qsa$(dialog, 'input[type="number"]'); settings.discard.maxAge = valueFromInput(inputs[0], 240); settings.discard.maxLoadCount = valueFromInput(inputs[1], 25); settings.discard.maxEntryCount = valueFromInput(inputs[2], 2000); settings.linesPerEntry = valueFromInput(inputs[3], 4); // Column checkboxs - inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]'); + inputs = qsa$(dialog, 'input[type="checkbox"][data-column]'); for ( const input of inputs ) { - const i = parseInt(input.getAttribute('data-column'), 10); + const i = parseInt(dom.attr(input, 'data-column'), 10); settings.columns[i] = input.checked !== true; } @@ -2799,7 +2741,7 @@ const loggerSettings = (( ) => { viewPort.updateLayout(); }; - uDom.nodeFromId('loggerSettings').addEventListener('click', toggleOn); + dom.on('#loggerSettings', 'click', toggleOn); return settings; })(); @@ -2810,9 +2752,8 @@ logger.resize = (function() { let timer; const resize = function() { - const vrect = document.body.getBoundingClientRect(); - const elems = document.querySelectorAll('.vscrollable'); - for ( const elem of elems ) { + const vrect = dom.body.getBoundingClientRect(); + for ( const elem of qsa$('.vscrollable') ) { const crect = elem.getBoundingClientRect(); const dh = crect.bottom - vrect.bottom; if ( dh === 0 ) { continue; } @@ -2830,7 +2771,7 @@ logger.resize = (function() { resizeAsync(); - window.addEventListener('resize', resizeAsync, { passive: true }); + dom.on(window, 'resize', resizeAsync, { passive: true }); return resizeAsync; })(); @@ -2853,42 +2794,38 @@ const releaseView = function() { logger.ownerId = undefined; }; -window.addEventListener('pagehide', releaseView); -window.addEventListener('pageshow', grabView); +dom.on(window, 'pagehide', releaseView); +dom.on(window, 'pageshow', grabView); // https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 -window.addEventListener('beforeunload', releaseView); +dom.on(window, 'beforeunload', releaseView); /******************************************************************************/ -uDom('#pageSelector').on('change', pageSelectorChanged); -uDom('#refresh').on('click', reloadTab); -uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView); -uDom('#pause').on('click', pauseNetInspector); +dom.on('#pageSelector', 'change', pageSelectorChanged); +dom.on('#refresh', 'click', reloadTab); +dom.on('#netInspector .vCompactToggler', 'click', toggleVCompactView); +dom.on('#pause', 'click', pauseNetInspector); // https://github.com/gorhill/uBlock/issues/507 // Ensure tab selector is in sync with URL hash pageSelectorFromURLHash(); -window.addEventListener('hashchange', pageSelectorFromURLHash); +dom.on(window, 'hashchange', pageSelectorFromURLHash); // Start to watch the current window geometry 2 seconds after the document // is loaded, to be sure no spurious geometry changes will be triggered due // to the window geometry pontentially not settling fast enough. if ( self.location.search.includes('popup=1') ) { - window.addEventListener( - 'load', - ( ) => { - setTimeout( - ( ) => { - popupLoggerBox = { - x: self.screenX, - y: self.screenY, - w: self.outerWidth, - h: self.outerHeight, - }; - }, 2000); - }, - { once: true } - ); + dom.on(window, 'load', ( ) => { + setTimeout( + ( ) => { + popupLoggerBox = { + x: self.screenX, + y: self.screenY, + w: self.outerWidth, + h: self.outerHeight, + }; + }, 2000); + }, { once: true }); } /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 820ac6a42..668f7f61a 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1311,40 +1311,6 @@ const modifyRuleset = function(details) { } }; -// Shortcuts -const getShortcuts = function(callback) { - if ( µb.canUseShortcuts === false ) { - return callback([]); - } - - vAPI.commands.getAll(commands => { - let response = []; - for ( let command of commands ) { - let desc = command.description; - let match = /^__MSG_(.+?)__$/.exec(desc); - if ( match !== null ) { - desc = i18n$(match[1]); - } - if ( desc === '' ) { continue; } - command.description = desc; - response.push(command); - } - callback(response); - }); -}; - -const setShortcut = function(details) { - if ( µb.canUpdateShortcuts === false ) { return; } - if ( details.shortcut === undefined ) { - vAPI.commands.reset(details.name); - µb.commandShortcuts.delete(details.name); - } else { - vAPI.commands.update({ name: details.name, shortcut: details.shortcut }); - µb.commandShortcuts.set(details.name, details.shortcut); - } - vAPI.storage.set({ commandShortcuts: Array.from(µb.commandShortcuts) }); -}; - // Support const getSupportData = async function() { const diffArrays = function(modified, original) { @@ -1506,9 +1472,6 @@ const onMessage = function(request, sender, callback) { callback(localData); }); - case 'getShortcuts': - return getShortcuts(callback); - case 'getSupportData': { getSupportData().then(response => { callback(response); @@ -1536,7 +1499,6 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'dashboardConfig': response = { - canUpdateShortcuts: µb.canUpdateShortcuts, noDashboard: µb.noDashboard, }; break; @@ -1597,10 +1559,6 @@ const onMessage = function(request, sender, callback) { resetUserData(); break; - case 'setShortcut': - setShortcut(request); - break; - case 'writeHiddenSettings': µb.changeHiddenSettings(µb.hiddenSettingsFromString(request.content)); break; diff --git a/src/js/popup-fenix.js b/src/js/popup-fenix.js index 4c478341d..c58b84730 100644 --- a/src/js/popup-fenix.js +++ b/src/js/popup-fenix.js @@ -19,12 +19,11 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; import punycode from '../lib/punycode.js'; import { i18n$ } from './i18n.js'; +import { dom, qs$, qsa$ } from './dom.js'; /******************************************************************************/ @@ -110,7 +109,7 @@ const cachePopupData = function(data) { const hashFromPopupData = function(reset = false) { // It makes no sense to offer to refresh the behind-the-scene scope if ( popupData.pageHostname === 'behind-the-scene' ) { - document.body.classList.remove('needReload'); + dom.cl.remove(dom.body, 'needReload'); return; } @@ -122,17 +121,19 @@ const hashFromPopupData = function(reset = false) { hasher.push(rule); } hasher.sort(); - hasher.push(uDom('body').hasClass('off')); - hasher.push(uDom.nodeFromId('no-large-media').classList.contains('on')); - hasher.push(uDom.nodeFromId('no-cosmetic-filtering').classList.contains('on')); - hasher.push(uDom.nodeFromId('no-remote-fonts').classList.contains('on')); - hasher.push(uDom.nodeFromId('no-scripting').classList.contains('on')); + hasher.push( + dom.cl.has('body', 'off'), + dom.cl.has('#no-large-media', 'on'), + dom.cl.has('#no-cosmetic-filtering', 'on'), + dom.cl.has('#no-remote-fonts', 'on'), + dom.cl.has('#no-scripting', 'on') + ); const hash = hasher.join(''); if ( reset ) { cachedPopupHash = hash; } - document.body.classList.toggle('needReload', hash !== cachedPopupHash); + dom.cl.toggle(dom.body, 'needReload', hash !== cachedPopupHash); }; /******************************************************************************/ @@ -199,20 +200,18 @@ const safePunycodeToUnicode = function(hn) { const updateFirewallCellCount = function(cells, allowed, blocked) { for ( const cell of cells ) { if ( gtz(allowed) ) { - cell.setAttribute( - 'data-acount', + dom.attr(cell, 'data-acount', Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3) ); } else { - cell.setAttribute('data-acount', '0'); + dom.attr(cell, 'data-acount', '0'); } if ( gtz(blocked) ) { - cell.setAttribute( - 'data-bcount', + dom.attr(cell, 'data-bcount', Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3) ); } else { - cell.setAttribute('data-bcount', '0'); + dom.attr(cell, 'data-bcount', '0'); } } }; @@ -224,12 +223,12 @@ const updateFirewallCellRule = function(cells, scope, des, type, rule) { for ( const cell of cells ) { if ( ruleParts === undefined ) { - cell.removeAttribute('class'); + dom.attr(cell, 'class', null); continue; } const action = updateFirewallCellRule.actionNames[ruleParts[3]]; - cell.setAttribute('class', `${action}Rule`); + dom.attr(cell, 'class', `${action}Rule`); // Use dark shade visual cue if the rule is specific to the cell. if ( @@ -238,7 +237,7 @@ const updateFirewallCellRule = function(cells, scope, des, type, rule) { (ruleParts[0] === scopeToSrcHostnameMap[scope]) ) { - cell.classList.add('ownRule'); + dom.cl.add(cell, 'ownRule'); } } }; @@ -249,26 +248,26 @@ updateFirewallCellRule.actionNames = { '1': 'block', '2': 'allow', '3': 'noop' } const updateAllFirewallCells = function(doRules = true, doCounts = true) { const { pageDomain } = popupData; - const rowContainer = document.getElementById('firewall'); - const rows = rowContainer.querySelectorAll('#firewall > [data-des][data-type]'); + const rowContainer = qs$('#firewall'); + const rows = qsa$(rowContainer, '#firewall > [data-des][data-type]'); let a1pScript = 0, b1pScript = 0; let a3pScript = 0, b3pScript = 0; let a3pFrame = 0, b3pFrame = 0; for ( const row of rows ) { - const des = row.getAttribute('data-des'); - const type = row.getAttribute('data-type'); + const des = dom.attr(row, 'data-des'); + const type = dom.attr(row, 'data-type'); if ( doRules ) { updateFirewallCellRule( - row.querySelectorAll(`:scope > span[data-src="/"]`), + qsa$(row, ':scope > span[data-src="/"]'), '/', des, type, popupData.firewallRules[`/ ${des} ${type}`] ); } - const cells = row.querySelectorAll(`:scope > span[data-src="."]`); + const cells = qsa$(row, ':scope > span[data-src="."]'); if ( doRules ) { updateFirewallCellRule( cells, @@ -301,17 +300,15 @@ const updateAllFirewallCells = function(doRules = true, doCounts = true) { if ( doCounts ) { const fromType = type => - document.querySelectorAll( - `#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]` - ); + qsa$(`#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]`); updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript); updateFirewallCellCount(fromType('3p-script'), a3pScript, b3pScript); - rowContainer.classList.toggle('has3pScript', a3pScript !== 0 || b3pScript !== 0); + dom.cl.toggle(rowContainer, 'has3pScript', a3pScript !== 0 || b3pScript !== 0); updateFirewallCellCount(fromType('3p-frame'), a3pFrame, b3pFrame); - rowContainer.classList.toggle('has3pFrame', a3pFrame !== 0 || b3pFrame !== 0); + dom.cl.toggle(rowContainer, 'has3pFrame', a3pFrame !== 0 || b3pFrame !== 0); } - document.body.classList.toggle('needSave', popupData.matrixIsDirty === true); + dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true); }; /******************************************************************************/ @@ -346,8 +343,8 @@ const expandHostnameStats = ( ) => { const buildAllFirewallRows = function() { // Do this before removing the rows if ( dfHotspots === null ) { - dfHotspots = uDom.nodeFromId('actionSelector'); - dfHotspots.addEventListener('click', setFirewallRuleHandler); + dfHotspots = qs$('#actionSelector'); + dom.on(dfHotspots, 'click', setFirewallRuleHandler); } dfHotspots.remove(); @@ -355,23 +352,19 @@ const buildAllFirewallRows = function() { expandHostnameStats(); // Update incrementally: reuse existing rows if possible. - const rowContainer = document.getElementById('firewall'); + const rowContainer = qs$('#firewall'); const toAppend = document.createDocumentFragment(); - const rowTemplate = document.querySelector( - '#templates > div[data-des=""][data-type="*"]' - ); + const rowTemplate = qs$('#templates > div[data-des=""][data-type="*"]'); const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData; - let row = rowContainer.querySelector( - 'div[data-des="*"][data-type="3p-frame"] + div' - ); + let row = qs$(rowContainer, 'div[data-des="*"][data-type="3p-frame"] + div'); for ( const des of allHostnameRows ) { if ( row === null ) { - row = rowTemplate.cloneNode(true); + row = dom.clone(rowTemplate); toAppend.appendChild(row); } - row.setAttribute('data-des', des); + dom.attr(row, 'data-des', des); const hnDetails = hostnameDict[des] || {}; const isDomain = des === hnDetails.domain; @@ -381,13 +374,13 @@ const buildAllFirewallRows = function() { const isPunycoded = prettyDomainName !== des; if ( isDomain && row.childElementCount < 4 ) { - row.append(row.children[2].cloneNode(true)); + row.append(dom.clone(row.children[2])); } else if ( isDomain === false && row.childElementCount === 4 ) { row.children[3].remove(); } - const span = row.querySelector('span:first-of-type'); - span.querySelector(':scope > span > span').textContent = prettyDomainName; + const span = qs$(row, 'span:first-of-type'); + dom.text(qs$(span, ':scope > span > span'), prettyDomainName); const classList = row.classList; @@ -401,7 +394,7 @@ const buildAllFirewallRows = function() { ) { desExtra = des; } - span.querySelector('sub').textContent = desExtra; + dom.text(qs$(span, 'sub'), desExtra); classList.toggle('isRootContext', des === pageHostname); classList.toggle('is3p', hnDetails.domain !== pageDomain); @@ -435,10 +428,9 @@ const buildAllFirewallRows = function() { } if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) { - uDom('#firewall') - .on('click', 'span[data-src]', unsetFirewallRuleHandler) - .on('mouseenter', '[data-src]', mouseenterCellHandler) - .on('mouseleave', '[data-src]', mouseleaveCellHandler); + dom.on('#firewall', 'click', 'span[data-src]', unsetFirewallRuleHandler); + dom.on('#firewall', 'mouseenter', 'span[data-src]', mouseenterCellHandler); + dom.on('#firewall', 'mouseleave', 'span[data-src]', mouseleaveCellHandler); dfPaneBuilt = true; } @@ -507,32 +499,17 @@ const renderPrivacyExposure = function() { const summary = domainsHitStr .replace('{{count}}', touchedDomainCount.toLocaleString()) .replace('{{total}}', allDomainCount.toLocaleString()); - uDom.nodeFromSelector( - '[data-i18n^="popupDomainsConnected"] + span' - ).textContent = summary; + dom.text('[data-i18n^="popupDomainsConnected"] + span', summary); }; /******************************************************************************/ const updateHnSwitches = function() { - uDom.nodeFromId('no-popups').classList.toggle( - 'on', popupData.noPopups === true - ); - uDom.nodeFromId('no-large-media').classList.toggle( - 'on', popupData.noLargeMedia === true - ); - uDom.nodeFromId('no-cosmetic-filtering').classList.toggle( - 'on', - popupData.noCosmeticFiltering === true - ); - uDom.nodeFromId('no-remote-fonts').classList.toggle( - 'on', - popupData.noRemoteFonts === true - ); - uDom.nodeFromId('no-scripting').classList.toggle( - 'on', - popupData.noScripting === true - ); + dom.cl.toggle('#no-popups', 'on', popupData.noPopups === true); + dom.cl.toggle('#no-large-media', 'on', popupData.noLargeMedia === true); + dom.cl.toggle('#no-cosmetic-filtering', 'on',popupData.noCosmeticFiltering === true); + dom.cl.toggle('#no-remote-fonts', 'on', popupData.noRemoteFonts === true); + dom.cl.toggle('#no-scripting', 'on', popupData.noScripting === true); }; /******************************************************************************/ @@ -546,26 +523,28 @@ const renderPopup = function() { const isFiltering = popupData.netFilteringSwitch; - const body = document.body; - body.classList.toggle('advancedUser', popupData.advancedUserEnabled === true); - body.classList.toggle('off', popupData.pageURL === '' || isFiltering !== true); - body.classList.toggle('needSave', popupData.matrixIsDirty === true); + dom.cl.toggle(dom.body, 'advancedUser', popupData.advancedUserEnabled === true); + dom.cl.toggle(dom.body, 'off', popupData.pageURL === '' || isFiltering !== true); + dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true); // The hostname information below the power switch { - const [ elemHn, elemDn ] = uDom.nodeFromId('hostname').children; + const [ elemHn, elemDn ] = qs$('#hostname').children; const { pageDomain, pageHostname } = popupData; if ( pageDomain !== '' ) { - elemDn.textContent = safePunycodeToUnicode(pageDomain); - elemHn.textContent = pageHostname !== pageDomain + dom.text(elemDn, safePunycodeToUnicode(pageDomain)); + dom.text(elemHn, pageHostname !== pageDomain ? safePunycodeToUnicode(pageHostname.slice(0, -pageDomain.length - 1)) + '.' - : ''; + : '' + ); } else { - elemHn.textContent = elemDn.textContent = ''; + dom.text(elemDn, ''); + dom.text(elemHn, ''); } } - uDom.nodeFromId('basicTools').classList.toggle( + dom.cl.toggle( + '#basicTools', 'canPick', popupData.canElementPicker === true && isFiltering ); @@ -586,7 +565,7 @@ const renderPopup = function() { text = statsStr.replace('{{count}}', formatNumber(blocked)) .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); } - uDom.nodeFromSelector('[data-i18n^="popupBlockedOnThisPage"] + span').textContent = text; + dom.text('[data-i18n^="popupBlockedOnThisPage"] + span', text); blocked = popupData.globalBlockedRequestCount; total = popupData.globalAllowedRequestCount + blocked; @@ -596,7 +575,7 @@ const renderPopup = function() { text = statsStr.replace('{{count}}', formatNumber(blocked)) .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); } - uDom.nodeFromSelector('[data-i18n^="popupBlockedSinceInstall"] + span').textContent = text; + dom.text('[data-i18n^="popupBlockedSinceInstall"] + span', text); // This will collate all domains, touched or not renderPrivacyExposure(); @@ -606,24 +585,27 @@ const renderPopup = function() { // Report popup count on badge total = popupData.popupBlockedCount; - uDom.nodeFromSelector('#no-popups .fa-icon-badge') - .textContent = total ? Math.min(total, 99).toLocaleString() : ''; + dom.text( + '#no-popups .fa-icon-badge', + total ? Math.min(total, 99).toLocaleString() : '' + ); // Report large media count on badge total = popupData.largeMediaCount; - uDom.nodeFromSelector('#no-large-media .fa-icon-badge') - .textContent = total ? Math.min(total, 99).toLocaleString() : ''; + dom.text( + '#no-large-media .fa-icon-badge', + total ? Math.min(total, 99).toLocaleString() : '' + ); // Report remote font count on badge total = popupData.remoteFontCount; - uDom.nodeFromSelector('#no-remote-fonts .fa-icon-badge') - .textContent = total ? Math.min(total, 99).toLocaleString() : ''; - - document.documentElement.classList.toggle( - 'colorBlind', - popupData.colorBlindFriendly === true + dom.text( + '#no-remote-fonts .fa-icon-badge', + total ? Math.min(total, 99).toLocaleString() : '' ); + dom.cl.toggle(dom.html, 'colorBlind', popupData.colorBlindFriendly === true); + setGlobalExpand(popupData.firewallPaneMinimized === false, true); // Build dynamic filtering pane only if in use @@ -642,14 +624,14 @@ const renderPopup = function() { const renderTooltips = function(selector) { for ( const [ key, details ] of tooltipTargetSelectors ) { if ( selector !== undefined && key !== selector ) { continue; } - const elem = uDom.nodeFromSelector(key); + const elem = qs$(key); if ( elem.hasAttribute('title') === false ) { continue; } const text = i18n$( details.i18n + - (uDom.nodeFromSelector(details.state) === null ? '1' : '2') + (qs$(details.state) === null ? '1' : '2') ); - elem.setAttribute('aria-label', text); - elem.setAttribute('title', text); + dom.attr(elem, 'aria-label', text); + dom.attr(elem, 'title', text); } }; @@ -705,47 +687,45 @@ const tooltipTargetSelectors = new Map([ let renderOnce = function() { renderOnce = function(){}; - const body = document.body; - if ( popupData.fontSize !== popupFontSize ) { popupFontSize = popupData.fontSize; if ( popupFontSize !== 'unset' ) { - body.style.setProperty('--font-size', popupFontSize); + dom.body.style.setProperty('--font-size', popupFontSize); vAPI.localStorage.setItem('popupFontSize', popupFontSize); } else { - body.style.removeProperty('--font-size'); + dom.body.style.removeProperty('--font-size'); vAPI.localStorage.removeItem('popupFontSize'); } } - uDom.nodeFromId('version').textContent = popupData.appVersion; + dom.text('#version', popupData.appVersion); sectionBitsToAttribute(computedSections()); if ( popupData.uiPopupConfig !== undefined ) { - document.body.setAttribute('data-ui', popupData.uiPopupConfig); + dom.attr(dom.body, 'data-ui', popupData.uiPopupConfig); } - body.classList.toggle('no-tooltips', popupData.tooltipsDisabled === true); + dom.cl.toggle(dom.body, 'no-tooltips', popupData.tooltipsDisabled === true); if ( popupData.tooltipsDisabled === true ) { - uDom('[title]').removeAttr('title'); + dom.attr('[title]', 'title', null); } // https://github.com/uBlockOrigin/uBlock-issues/issues/22 if ( popupData.advancedUserEnabled !== true ) { - uDom('#firewall [title][data-src]').removeAttr('title'); + dom.attr('#firewall [title][data-src]', 'title', null); } // This must be done the firewall is populated if ( popupData.popupPanelHeightMode === 1 ) { - body.classList.add('vMin'); + dom.cl.add(dom.body, 'vMin'); } // Prevent non-advanced user opting into advanced user mode from harming // themselves by disabling by default features generally suitable to // filter list maintainers and actual advanced users. if ( popupData.godMode ) { - body.classList.add('godMode'); + dom.cl.add(dom.body, 'godMode'); } }; @@ -758,15 +738,15 @@ const renderPopupLazy = (( ) => { // Launch potentially expensive hidden elements-counting scriptlet on // demand only. { - const sw = uDom.nodeFromId('no-cosmetic-filtering'); - const badge = sw.querySelector(':scope .fa-icon-badge'); - badge.textContent = '\u22EF'; + const sw = qs$('#no-cosmetic-filtering'); + const badge = qs$(sw, ':scope .fa-icon-badge'); + dom.text(badge, '\u22EF'); const render = ( ) => { if ( mustRenderCosmeticFilteringBadge === false ) { return; } mustRenderCosmeticFilteringBadge = false; - if ( sw.classList.contains('hnSwitchBusy') ) { return; } - sw.classList.add('hnSwitchBusy'); + if ( dom.cl.has(sw, 'hnSwitchBusy') ) { return; } + dom.cl.add(sw, 'hnSwitchBusy'); messaging.send('popupPanel', { what: 'getHiddenElementCount', tabId: popupData.tabId, @@ -779,12 +759,12 @@ const renderPopupLazy = (( ) => { } else { text = Math.min(count, 99).toLocaleString(); } - badge.textContent = text; - sw.classList.remove('hnSwitchBusy'); + dom.text(badge, text); + dom.cl.remove(sw, 'hnSwitchBusy'); }); }; - sw.addEventListener('mouseenter', render, { passive: true }); + dom.on(sw, 'mouseenter', render, { passive: true }); } return async function() { @@ -792,10 +772,10 @@ const renderPopupLazy = (( ) => { what: 'getScriptCount', tabId: popupData.tabId, }); - uDom.nodeFromSelector('#no-scripting .fa-icon-badge') - .textContent = (count || 0) !== 0 - ? Math.min(count, 99).toLocaleString() - : ''; + dom.text( + '#no-scripting .fa-icon-badge', + (count || 0) !== 0 ? Math.min(count, 99).toLocaleString() : '' + ); mustRenderCosmeticFilteringBadge = true; }; })(); @@ -808,7 +788,7 @@ const toggleNetFilteringSwitch = function(ev) { what: 'toggleNetFiltering', url: popupData.pageURL, scope: ev.ctrlKey || ev.metaKey ? 'page' : '', - state: !uDom('body').toggleClass('off').hasClass('off'), + state: dom.cl.toggle(dom.body, 'off') === false, tabId: popupData.tabId, }); renderTooltips('#switch'); @@ -892,7 +872,7 @@ const gotoURL = function(ev) { ev.preventDefault(); - let url = this.getAttribute('href'); + let url = dom.attr(ev.target, 'href'); if ( url === 'logger-ui.html#_' && typeof popupData.tabId === 'number' @@ -984,14 +964,14 @@ const toggleSections = function(more) { } }; -uDom('#moreButton').on('click', ( ) => { toggleSections(true); }); -uDom('#lessButton').on('click', ( ) => { toggleSections(false); }); +dom.on('#moreButton', 'click', ( ) => { toggleSections(true); }); +dom.on('#lessButton', 'click', ( ) => { toggleSections(false); }); /******************************************************************************/ const mouseenterCellHandler = function(ev) { const target = ev.target; - if ( target.classList.contains('ownRule') ) { return; } + if ( dom.cl.has(target, 'ownRule') ) { return; } target.appendChild(dfHotspots); }; @@ -1038,9 +1018,9 @@ const unsetFirewallRuleHandler = function(ev) { const cell = ev.target; const row = cell.closest('[data-des]'); setFirewallRule( - cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, - row.getAttribute('data-des'), - row.getAttribute('data-type'), + dom.attr(cell, 'data-src') === '/' ? '*' : popupData.pageHostname, + dom.attr(row, 'data-des'), + dom.attr(row, 'data-type'), 0, ev.ctrlKey || ev.metaKey ); @@ -1063,9 +1043,9 @@ const setFirewallRuleHandler = function(ev) { action = 1; } setFirewallRule( - cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, - row.getAttribute('data-des'), - row.getAttribute('data-type'), + dom.attr(cell, 'data-src') === '/' ? '*' : popupData.pageHostname, + dom.attr(row, 'data-des'), + dom.attr(row, 'data-type'), action, ev.ctrlKey || ev.metaKey ); @@ -1093,19 +1073,15 @@ const reloadTab = function(ev) { hashFromPopupData(true); }; -uDom('#refresh').on('click', reloadTab); +dom.on('#refresh', 'click', reloadTab); // https://github.com/uBlockOrigin/uBlock-issues/issues/672 -document.addEventListener( - 'keydown', - ev => { - if ( ev.code !== 'F5' ) { return; } - reloadTab(ev); - ev.preventDefault(); - ev.stopPropagation(); - }, - { capture: true } -); +dom.on(document, 'keydown', ev => { + if ( ev.code !== 'F5' ) { return; } + reloadTab(ev); + ev.preventDefault(); + ev.stopPropagation(); +}, { capture: true }); /******************************************************************************/ @@ -1130,11 +1106,11 @@ const saveExpandExceptions = function() { }; const setGlobalExpand = function(state, internal = false) { - uDom('.expandException').removeClass('expandException'); + dom.cl.remove('.expandException', 'expandException'); if ( state ) { - uDom('#firewall').addClass('expanded'); + dom.cl.add('#firewall', 'expanded'); } else { - uDom('#firewall').removeClass('expanded'); + dom.cl.remove('#firewall', 'expanded'); } if ( internal ) { return; } popupData.firewallPaneMinimized = !state; @@ -1148,11 +1124,11 @@ const setGlobalExpand = function(state, internal = false) { }; const setSpecificExpand = function(domain, state, internal = false) { - const unodes = uDom(`[data-des="${domain}"],[data-des$=".${domain}"]`); + const elems = qsa$(`[data-des="${domain}"],[data-des$=".${domain}"]`); if ( state ) { - unodes.addClass('expandException'); + dom.cl.add(elems, 'expandException'); } else { - unodes.removeClass('expandException'); + dom.cl.remove(elems, 'expandException'); } if ( internal ) { return; } if ( state ) { @@ -1163,7 +1139,7 @@ const setSpecificExpand = function(domain, state, internal = false) { saveExpandExceptions(); }; -uDom('[data-i18n="popupAnyRulePrompt"]').on('click', ev => { +dom.on('[data-i18n="popupAnyRulePrompt"]', 'click', ev => { // Special display mode: in its own tab/window, with no vertical restraint. // Useful to take snapshots of the whole list of domains -- example: // https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944 @@ -1180,22 +1156,17 @@ uDom('[data-i18n="popupAnyRulePrompt"]').on('click', ev => { return; } - setGlobalExpand( - uDom('#firewall').hasClass('expanded') === false - ); + setGlobalExpand(dom.cl.has('#firewall', 'expanded') === false); }); -uDom('#firewall').on( - 'click', '.isDomain[data-type="*"] > span:first-of-type', - ev => { - const div = ev.target.closest('[data-des]'); - if ( div === null ) { return; } - setSpecificExpand( - div.getAttribute('data-des'), - div.classList.contains('expandException') === false - ); - } -); +dom.on('#firewall', 'click', '.isDomain[data-type="*"] > span:first-of-type', ev => { + const div = ev.target.closest('[data-des]'); + if ( div === null ) { return; } + setSpecificExpand( + dom.attr(div, 'data-des'), + dom.cl.has(div, 'expandException') === false + ); +}); /******************************************************************************/ @@ -1205,13 +1176,13 @@ const saveFirewallRules = function() { srcHostname: popupData.pageHostname, desHostnames: popupData.hostnameDict, }); - document.body.classList.remove('needSave'); + dom.cl.remove(dom.body, 'needSave'); }; /******************************************************************************/ const revertFirewallRules = async function() { - document.body.classList.remove('needSave'); + dom.cl.remove(dom.body, 'needSave'); const response = await messaging.send('popupPanel', { what: 'revertFirewallRules', srcHostname: popupData.pageHostname, @@ -1228,23 +1199,23 @@ const revertFirewallRules = async function() { const toggleHostnameSwitch = async function(ev) { const target = ev.currentTarget; - const switchName = target.getAttribute('id'); + const switchName = dom.attr(target, 'id'); if ( !switchName ) { return; } // For touch displays, process click only if the switch is not "busy". if ( vAPI.webextFlavor.soup.has('mobile') && - target.classList.contains('hnSwitchBusy') + dom.cl.has(target, 'hnSwitchBusy') ) { return; } - target.classList.toggle('on'); + dom.cl.toggle(target, 'on'); renderTooltips(`#${switchName}`); const response = await messaging.send('popupPanel', { what: 'toggleHostnameSwitch', name: switchName, hostname: popupData.pageHostname, - state: target.classList.contains('on'), + state: dom.cl.has(target, 'on'), tabId: popupData.tabId, persist: ev.ctrlKey || ev.metaKey, }); @@ -1252,7 +1223,7 @@ const toggleHostnameSwitch = async function(ev) { cachePopupData(response); hashFromPopupData(); - document.body.classList.toggle('needSave', popupData.matrixIsDirty === true); + dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true); }; /******************************************************************************* @@ -1268,7 +1239,7 @@ const toggleHostnameSwitch = async function(ev) { let eventCount = 0; let eventTime = 0; - document.addEventListener('keydown', ev => { + dom.on(document, 'keydown', ev => { if ( ev.key !== 'Control' ) { eventCount = 0; return; @@ -1282,7 +1253,7 @@ const toggleHostnameSwitch = async function(ev) { eventTime = now; if ( eventCount < 2 ) { return; } eventCount = 0; - document.body.classList.toggle('godMode'); + dom.cl.toggle(dom.body, 'godMode'); }); } @@ -1383,42 +1354,41 @@ const getPopupData = async function(tabId, first = false) { // Use a tolerance proportional to the sum of the width of the panes // when testing against viewport width. const checkViewport = async function() { - const root = document.querySelector(':root'); if ( - root.classList.contains('mobile') || + dom.cl.has(dom.root, 'mobile') || selfURL.searchParams.get('portrait') ) { - root.classList.add('portrait'); - } else if ( root.classList.contains('desktop') ) { + dom.cl.add(dom.root, 'portrait'); + } else if ( dom.cl.has(dom.root, 'desktop') ) { await nextFrames(4); - const main = document.getElementById('main'); - const firewall = document.getElementById('firewall'); + const main = qs$('#main'); + const firewall = qs$('#firewall'); const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1; if ( selfURL.searchParams.get('portrait') || window.innerWidth < minWidth ) { - root.classList.add('portrait'); + dom.cl.add(dom.root, 'portrait'); } } - if ( root.classList.contains('portrait') ) { - const panes = document.getElementById('panes'); - const sticky = document.getElementById('sticky'); + if ( dom.cl.has(dom.root, 'portrait') ) { + const panes = qs$('#panes'); + const sticky = qs$('#sticky'); const stickyParent = sticky.parentElement; if ( stickyParent !== panes ) { panes.prepend(sticky); } } if ( selfURL.searchParams.get('intab') !== null ) { - root.classList.add('intab'); + dom.cl.add(dom.root, 'intab'); } await nextFrames(1); - document.body.classList.remove('loading'); + dom.cl.remove(dom.body, 'loading'); }; getPopupData(tabId, true).then(( ) => { if ( document.readyState !== 'complete' ) { - self.addEventListener('load', ( ) => { checkViewport(); }, { once: true }); + dom.on(self, 'load', ( ) => { checkViewport(); }, { once: true }); } else { checkViewport(); } @@ -1427,27 +1397,25 @@ const getPopupData = async function(tabId, first = false) { /******************************************************************************/ -uDom('#switch').on('click', toggleNetFilteringSwitch); -uDom('#gotoZap').on('click', gotoZap); -uDom('#gotoPick').on('click', gotoPick); -uDom('#gotoReport').on('click', gotoReport); -uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); }); -uDom('#saveRules').on('click', saveFirewallRules); -uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); }); -uDom('a[href]').on('click', gotoURL); +dom.on('#switch', 'click', toggleNetFilteringSwitch); +dom.on('#gotoZap', 'click', gotoZap); +dom.on('#gotoPick', 'click', gotoPick); +dom.on('#gotoReport', 'click', gotoReport); +dom.on('.hnSwitch', 'click', ev => { toggleHostnameSwitch(ev); }); +dom.on('#saveRules', 'click', saveFirewallRules); +dom.on('#revertRules', 'click', ( ) => { revertFirewallRules(); }); +dom.on('a[href]', 'click', gotoURL); /******************************************************************************/ // Toggle emphasis of rows with[out] 3rd-party scripts/frames -document.querySelector('#firewall > [data-type="3p-script"] .filter') - .addEventListener('click', ( ) => { - document.getElementById('firewall').classList.toggle('show3pScript'); - }); +dom.on('#firewall > [data-type="3p-script"] .filter', 'click', ( ) => { + dom.cl.toggle('#firewall', 'show3pScript'); +}); // Toggle visibility of rows with[out] 3rd-party frames -document.querySelector('#firewall > [data-type="3p-frame"] .filter') - .addEventListener('click', ( ) => { - document.getElementById('firewall').classList.toggle('show3pFrame'); - }); +dom.on('#firewall > [data-type="3p-frame"] .filter', 'click', ( ) => { + dom.cl.toggle('#firewall', 'show3pFrame'); +}); /******************************************************************************/ diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 875839b81..6fed5ac64 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -113,6 +113,16 @@ const getElementBoundingClientRect = function(elem) { /******************************************************************************/ +const elementsFromPoint = function(parent, x, y) { + const elems = parent.elementsFromPoint(x, y); + if ( elems.length !== 0 && elems[0].shadowRoot !== null ) { + return elementsFromPoint(elems[0].shadowRoot, x, y); + } + return elems; +}; + +/******************************************************************************/ + const highlightElements = function(elems, force) { // To make mouse move handler more efficient if ( @@ -554,11 +564,11 @@ const filtersFrom = function(x, y) { // Network filter candidates from all other elements found at [x,y]. // https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/ // Extract network candidates first. + const magicAttr = `${vAPI.sessionId}-clickblind`; + pickerRoot.setAttribute(magicAttr, ''); + const elems = elementsFromPoint(document, x, y); + pickerRoot.removeAttribute(magicAttr); if ( typeof x === 'number' ) { - const magicAttr = `${vAPI.sessionId}-clickblind`; - pickerRoot.setAttribute(magicAttr, ''); - const elems = document.elementsFromPoint(x, y); - pickerRoot.removeAttribute(magicAttr); for ( const elem of elems ) { netFilterFromElement(elem); } @@ -570,11 +580,14 @@ const filtersFrom = function(x, y) { // https://github.com/gorhill/uBlock/issues/2519 // https://github.com/uBlockOrigin/uBlock-issues/issues/17 // Prepend `body` if full selector is ambiguous. - let elem = first; - while ( elem && elem !== document.body ) { + for ( const elem of elems ) { cosmeticFilterFromElement(elem); - elem = elem.parentNode; } + //let elem = first; + //while ( elem && elem !== document.body ) { + // cosmeticFilterFromElement(elem); + // elem = elem.parentNode; + //} // The body tag is needed as anchor only when the immediate child // uses `nth-of-type`. let i = cosmeticFilterCandidates.length; diff --git a/src/js/settings.js b/src/js/settings.js index 4dde87547..8653528a4 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -19,11 +19,11 @@ Home: https://github.com/gorhill/uBlock */ -/* global uDom */ - 'use strict'; import { i18n$ } from './i18n.js'; +import { dom, qs$, qsa$ } from './dom.js'; +import { setAccentColor, setTheme } from './theme.js'; /******************************************************************************/ @@ -84,7 +84,7 @@ const handleImportFilePicker = function() { /******************************************************************************/ const startImportFilePicker = function() { - const input = document.getElementById('restoreFilePicker'); + const input = qs$('#restoreFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. @@ -134,10 +134,12 @@ const onLocalDataReceived = function(details) { v = '?'; unit = ''; } - uDom.nodeFromId('storageUsed').textContent = + dom.text( + '#storageUsed', i18n$('storageUsed') .replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 })) - .replace('{{unit}}', unit && i18n$(unit) || ''); + .replace('{{unit}}', unit && i18n$(unit) || '') + ); const timeOptions = { weekday: 'long', @@ -153,7 +155,7 @@ const onLocalDataReceived = function(details) { if ( lastBackupFile !== '' ) { const dt = new Date(details.lastBackupTime); const text = i18n$('settingsLastBackupPrompt'); - const node = uDom.nodeFromId('settingsLastBackupPrompt'); + const node = qs$('#settingsLastBackupPrompt'); node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions); node.style.display = ''; } @@ -162,19 +164,19 @@ const onLocalDataReceived = function(details) { if ( lastRestoreFile !== '' ) { const dt = new Date(details.lastRestoreTime); const text = i18n$('settingsLastRestorePrompt'); - const node = uDom.nodeFromId('settingsLastRestorePrompt'); + const node = qs$('#settingsLastRestorePrompt'); node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions); node.style.display = ''; } if ( details.cloudStorageSupported === false ) { - uDom('[data-setting-name="cloudStorageEnabled"]').attr('disabled', ''); + dom.attr('[data-setting-name="cloudStorageEnabled"]', 'disabled', ''); } if ( details.privacySettingsSupported === false ) { - uDom('[data-setting-name="prefetchingDisabled"]').attr('disabled', ''); - uDom('[data-setting-name="hyperlinkAuditingDisabled"]').attr('disabled', ''); - uDom('[data-setting-name="webrtcIPAddressHidden"]').attr('disabled', ''); + dom.attr('[data-setting-name="prefetchingDisabled"]', 'disabled', ''); + dom.attr('[data-setting-name="hyperlinkAuditingDisabled"]', 'disabled', ''); + dom.attr('[data-setting-name="webrtcIPAddressHidden"]', 'disabled', ''); } }; @@ -192,9 +194,10 @@ const resetUserData = function() { /******************************************************************************/ const synchronizeDOM = function() { - document.body.classList.toggle( + dom.cl.toggle( + dom.body, 'advancedUser', - uDom.nodeFromSelector('[data-setting-name="advancedUserEnabled"]').checked === true + qs$('[data-setting-name="advancedUserEnabled"]').checked === true ); }; @@ -210,13 +213,13 @@ const changeUserSettings = function(name, value) { // Maybe reflect some changes immediately switch ( name ) { case 'uiTheme': - uDom.setTheme(value, true); + setTheme(value, true); break; case 'uiAccentCustom': case 'uiAccentCustom0': - uDom.setAccentColor( - uDom.nodeFromSelector('[data-setting-name="uiAccentCustom"]').checked, - uDom.nodeFromSelector('[data-setting-name="uiAccentCustom0"]').value, + setAccentColor( + qs$('[data-setting-name="uiAccentCustom"]').checked, + qs$('[data-setting-name="uiAccentCustom0"]').value, true ); break; @@ -229,7 +232,7 @@ const changeUserSettings = function(name, value) { const onValueChanged = function(ev) { const input = ev.target; - const name = this.getAttribute('data-setting-name'); + const name = dom.attr(input, 'data-setting-name'); let value = input.value; // Maybe sanitize value switch ( name ) { @@ -251,36 +254,36 @@ const onValueChanged = function(ev) { // TODO: use data-* to declare simple settings const onUserSettingsReceived = function(details) { - const checkboxes = document.querySelectorAll('[data-setting-type="bool"]'); + const checkboxes = qsa$('[data-setting-type="bool"]'); for ( const checkbox of checkboxes ) { - const name = checkbox.getAttribute('data-setting-name') || ''; + const name = dom.attr(checkbox, 'data-setting-name') || ''; if ( details[name] === undefined ) { - checkbox.closest('.checkbox').setAttribute('disabled', ''); - checkbox.setAttribute('disabled', ''); + dom.attr(checkbox.closest('.checkbox'), 'disabled', ''); + dom.attr(checkbox, 'disabled', ''); continue; } checkbox.checked = details[name] === true; - checkbox.addEventListener('change', ( ) => { + dom.on(checkbox, 'change', ( ) => { changeUserSettings(name, checkbox.checked); synchronizeDOM(); }); } if ( details.canLeakLocalIPAddresses === true ) { - uDom('[data-setting-name="webrtcIPAddressHidden"]') - .ancestors('div.li') - .css('display', ''); + qs$('[data-setting-name="webrtcIPAddressHidden"]') + .closest('div.li') + .style.display = ''; } - uDom('[data-setting-type="value"]').forEach(function(uNode) { - uNode.val(details[uNode.attr('data-setting-name')]) - .on('change', onValueChanged); + qsa$('[data-setting-type="value"]').forEach(function(elem) { + elem.value = details[dom.attr(elem, 'data-setting-name')]; + dom.on(elem, 'change', onValueChanged); }); - uDom('#export').on('click', ( ) => { exportToFile(); }); - uDom('#import').on('click', startImportFilePicker); - uDom('#reset').on('click', resetUserData); - uDom('#restoreFilePicker').on('change', handleImportFilePicker); + dom.on('#export', 'click', ( ) => { exportToFile(); }); + dom.on('#import', 'click', startImportFilePicker); + dom.on('#reset', 'click', resetUserData); + dom.on('#restoreFilePicker', 'change', handleImportFilePicker); synchronizeDOM(); }; @@ -296,9 +299,8 @@ vAPI.messaging.send('dashboard', { what: 'getLocalData' }).then(result => { }); // https://github.com/uBlockOrigin/uBlock-issues/issues/591 -document.querySelector( - '[data-i18n-title="settingsAdvancedUserSettings"]' -).addEventListener( +dom.on( + '[data-i18n-title="settingsAdvancedUserSettings"]', 'click', self.uBlockDashboard.openOrSelectPage ); diff --git a/src/js/shortcuts.js b/src/js/shortcuts.js deleted file mode 100644 index 4c1ad766d..000000000 --- a/src/js/shortcuts.js +++ /dev/null @@ -1,230 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2018-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 -*/ - -'use strict'; - -(( ) => { - - // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/commands#Shortcut_values - const validStatus0Keys = new Map([ - [ 'alt', 'Alt' ], - [ 'control', 'Ctrl' ], - ]); - const validStatus1Keys = new Map([ - [ 'a', 'A' ], - [ 'b', 'B' ], - [ 'c', 'C' ], - [ 'd', 'D' ], - [ 'e', 'E' ], - [ 'f', 'F' ], - [ 'g', 'G' ], - [ 'h', 'H' ], - [ 'i', 'I' ], - [ 'j', 'J' ], - [ 'k', 'K' ], - [ 'l', 'L' ], - [ 'm', 'M' ], - [ 'n', 'N' ], - [ 'o', 'O' ], - [ 'p', 'P' ], - [ 'q', 'Q' ], - [ 'r', 'R' ], - [ 's', 'S' ], - [ 't', 'T' ], - [ 'u', 'U' ], - [ 'v', 'V' ], - [ 'w', 'W' ], - [ 'x', 'X' ], - [ 'y', 'Y' ], - [ 'z', 'Z' ], - [ '0', '0' ], - [ '1', '1' ], - [ '2', '2' ], - [ '3', '3' ], - [ '4', '4' ], - [ '5', '5' ], - [ '6', '6' ], - [ '7', '7' ], - [ '8', '8' ], - [ '9', '9' ], - [ 'f1', 'F1' ], - [ 'f2', 'F2' ], - [ 'f3', 'F3' ], - [ 'f4', 'F4' ], - [ 'f5', 'F5' ], - [ 'f6', 'F6' ], - [ 'f7', 'F7' ], - [ 'f8', 'F8' ], - [ 'f9', 'F9' ], - [ 'f10', 'F10' ], - [ 'f11', 'F11' ], - [ 'f12', 'F12' ], - [ ' ', 'Space' ], - [ ',', 'Comma' ], - [ '.', 'Period' ], - [ 'home', 'Home' ], - [ 'end', 'End' ], - [ 'pageup', 'PageUp' ], - [ 'pagedown', 'PageDown' ], - [ 'insert', 'Insert' ], - [ 'delete', 'Delete' ], - [ 'arrowup', 'Up' ], - [ 'arrowdown', 'Down' ], - [ 'arrowleft', 'Left' ], - [ 'arrowright', 'Right' ], - [ 'shift', 'Shift' ], - ]); - - const commandNameFromElement = function(elem) { - while ( elem !== null ) { - const name = elem.getAttribute('data-name'); - if ( typeof name === 'string' && name !== '' ) { return name; } - elem = elem.parentElement; - } - }; - - const captureShortcut = function(ev) { - const input = ev.target; - const name = commandNameFromElement(input); - if ( name === undefined ) { return; } - - const before = input.value; - const after = new Set(); - let status = 0; - - const updateCapturedShortcut = function() { - return (input.value = Array.from(after).join('+')); - }; - - const blurHandler = function() { - input.removeEventListener('blur', blurHandler, true); - input.removeEventListener('keydown', keydownHandler, true); - input.removeEventListener('keyup', keyupHandler, true); - if ( status === 2 ) { - vAPI.messaging.send('dashboard', { - what: 'setShortcut', - name, - shortcut: updateCapturedShortcut(), - }); - } else { - input.value = before; - } - }; - - const keydownHandler = function(ev) { - ev.preventDefault(); - ev.stopImmediatePropagation(); - if ( ev.code === 'Escape' ) { - input.blur(); - return; - } - if ( status === 0 ) { - const keyName = validStatus0Keys.get(ev.key.toLowerCase()); - if ( keyName !== undefined ) { - after.add(keyName); - updateCapturedShortcut(); - status = 1; - } - return; - } - if ( status === 1 ) { - if ( ev.key === 'Shift' ) { - after.add('Shift'); - updateCapturedShortcut(); - return; - } - let keyName = validStatus1Keys.get(ev.key.toLowerCase()); - if ( keyName !== undefined ) { - after.add(keyName); - updateCapturedShortcut(); - status = 2; - input.blur(); - return; - } - } - }; - - const keyupHandler = function(ev) { - ev.preventDefault(); - ev.stopImmediatePropagation(); - if ( status !== 1 ) { return; } - const keyName = validStatus0Keys.get(ev.key.toLowerCase()); - if ( keyName !== undefined && after.has(keyName) ) { - after.clear(); - updateCapturedShortcut(); - status = 0; - return; - } - if ( ev.key === 'Shift' ) { - after.delete('Shift'); - updateCapturedShortcut(); - return; - } - }; - - input.value = ''; - input.addEventListener('blur', blurHandler, true); - input.addEventListener('keydown', keydownHandler, true); - input.addEventListener('keyup', keyupHandler, true); - }; - - const resetShortcut = function(ev) { - const name = commandNameFromElement(ev.target); - if ( name === undefined ) { return; } - - const input = document.querySelector('[data-name="' + name + '"] input'); - if ( input === null ) { return; } - input.value = ''; - vAPI.messaging.send('dashboard', { - what: 'setShortcut', - name, - }); - }; - - const onShortcutsReady = function(commands) { - if ( Array.isArray(commands) === false ) { return; } - const template = document.querySelector('#templates .commandEntry'); - const tbody = document.querySelector('.commandEntries tbody'); - for ( const command of commands ) { - if ( - typeof command.description !== 'string' || - command.description === '' ) - { - continue; - } - const tr = template.cloneNode(true); - tr.setAttribute('data-name', command.name); - tr.querySelector('.commandDesc').textContent = command.description; - const input = tr.querySelector('.commandShortcut input'); - input.setAttribute('data-name', command.name); - input.value = command.shortcut; - input.addEventListener('focus', captureShortcut); - tr.querySelector('.commandReset').addEventListener('click', resetShortcut); - tbody.appendChild(tr); - } - }; - - vAPI.messaging.send('dashboard', { - what: 'getShortcuts', - }).then(commands => { - onShortcutsReady(commands); - }); -})(); diff --git a/src/js/support.js b/src/js/support.js index 5bf25ae75..3b878b035 100644 --- a/src/js/support.js +++ b/src/js/support.js @@ -19,10 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -/* global CodeMirror, uBlockDashboard, uDom */ +/* global CodeMirror, uBlockDashboard */ 'use strict'; +import { dom, qs$ } from './dom.js'; + /******************************************************************************/ let supportData; @@ -130,10 +132,10 @@ function configToMarkdown(collapse = false) { } function addDetailsToReportURL(id, collapse = false) { - const elem = uDom.nodeFromId(id); - const url = new URL(elem.getAttribute('data-url')); + const elem = qs$(`#${id}`); + const url = new URL(dom.attr(elem, 'data-url')); url.searchParams.set('configuration', configToMarkdown(collapse)); - elem.setAttribute('data-url', url); + dom.attr(elem, 'data-url', url); } function showData() { @@ -180,21 +182,21 @@ const reportedPage = (( ) => { parsedURL.username = ''; parsedURL.password = ''; parsedURL.hash = ''; - const select = document.querySelector('select[name="url"]'); - select.options[0].textContent = parsedURL.href; + const select = qs$('select[name="url"]'); + dom.text(select.options[0], parsedURL.href); if ( parsedURL.search !== '' ) { - const option = document.createElement('option'); + const option = dom.create('option'); parsedURL.search = ''; - option.textContent = parsedURL.href; + dom.text(option, parsedURL.href); select.append(option); } if ( parsedURL.pathname !== '/' ) { - const option = document.createElement('option'); + const option = dom.create('option'); parsedURL.pathname = ''; - option.textContent = parsedURL.href; + dom.text(option, parsedURL.href); select.append(option); } - document.body.classList.add('filterIssue'); + dom.cl.add(dom.body, 'filterIssue'); return { hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''), popupPanel: JSON.parse(url.searchParams.get('popupPanel')), @@ -205,21 +207,20 @@ const reportedPage = (( ) => { })(); function reportSpecificFilterType() { - return document.querySelector('select[name="type"]').value; + return qs$('select[name="type"]').value; } function reportSpecificFilterIssue(ev) { const githubURL = new URL('https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubo.yml'); const issueType = reportSpecificFilterType(); let title = `${reportedPage.hostname}: ${issueType}`; - if ( document.getElementById('isNSFW').checked ) { + if ( qs$('#isNSFW').checked ) { title = `[nsfw] ${title}`; } githubURL.searchParams.set('title', title); githubURL.searchParams.set( - 'url_address_of_the_web_page', '`' + - document.querySelector('select[name="url"]').value + - '`' + 'url_address_of_the_web_page', + '`' + qs$('select[name="url"]').value + '`' ); githubURL.searchParams.set('category', issueType); githubURL.searchParams.set('configuration', configToMarkdown(true)); @@ -232,7 +233,7 @@ function reportSpecificFilterIssue(ev) { /******************************************************************************/ -const cmEditor = new CodeMirror(document.getElementById('supportData'), { +const cmEditor = new CodeMirror(qs$('#supportData'), { autofocus: true, readOnly: true, styleActiveLine: true, @@ -249,9 +250,9 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor); showData(); - uDom('[data-url]').on('click', ev => { + dom.on('[data-url]', 'click', ev => { const elem = ev.target.closest('[data-url]'); - const url = elem.getAttribute('data-url'); + const url = dom.attr(elem, 'data-url'); if ( typeof url !== 'string' || url === '' ) { return; } vAPI.messaging.send('default', { what: 'gotoURL', @@ -261,11 +262,11 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor); }); if ( reportedPage !== null ) { - uDom('[data-i18n="supportReportSpecificButton"]').on('click', ev => { + dom.on('[data-i18n="supportReportSpecificButton"]', 'click', ev => { reportSpecificFilterIssue(ev); }); - uDom('[data-i18n="supportFindSpecificButton"]').on('click', ev => { + dom.on('[data-i18n="supportFindSpecificButton"]', 'click', ev => { const url = new URL('https://github.com/uBlockOrigin/uAssets/issues'); url.searchParams.set('q', `is:issue sort:updated-desc "${reportedPage.hostname}" in:title`); vAPI.messaging.send('default', { @@ -275,15 +276,15 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor); ev.preventDefault(); }); - uDom('#showSupportInfo').on('click', ev => { + dom.on('#showSupportInfo', 'click', ev => { const button = ev.target; - button.classList.add('hidden'); - uDom.nodeFromSelector('.a.b.c.d').classList.add('e'); + dom.cl.add(button, 'hidden'); + dom.cl.add('.a.b.c.d', 'e'); cmEditor.refresh(); }); } - uDom('#selectAllButton').on('click', ( ) => { + dom.on('#selectAllButton', 'click', ( ) => { cmEditor.focus(); cmEditor.execCommand('selectAll'); }); diff --git a/src/js/theme.js b/src/js/theme.js new file mode 100644 index 000000000..0d40b8a7d --- /dev/null +++ b/src/js/theme.js @@ -0,0 +1,144 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-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 +*/ + +'use strict'; + +function setTheme(theme, propagate = false) { + if ( theme === 'auto' ) { + if ( typeof self.matchMedia === 'function' ) { + const mql = self.matchMedia('(prefers-color-scheme: dark)'); + theme = mql instanceof Object && mql.matches === true + ? 'dark' + : 'light'; + } else { + theme = 'light'; + } + } + let w = self; + for (;;) { + const rootcl = w.document.documentElement.classList; + if ( theme === 'dark' ) { + rootcl.add('dark'); + rootcl.remove('light'); + } else /* if ( theme === 'light' ) */ { + rootcl.add('light'); + rootcl.remove('dark'); + } + if ( propagate === false ) { break; } + if ( w === w.parent ) { break; } + w = w.parent; + try { void w.document; } catch(ex) { return; } + } +} + +function setAccentColor( + accentEnabled, + accentColor, + propagate, + stylesheet = '' +) { + if ( accentEnabled && stylesheet === '' && self.hsluv !== undefined ) { + const toRGB = hsl => self.hsluv.hsluvToRgb(hsl).map(a => Math.round(a * 255)).join(' '); + // Normalize first + const hsl = self.hsluv.hexToHsluv(accentColor); + hsl[0] = Math.round(hsl[0] * 10) / 10; + hsl[1] = Math.round(Math.min(100, Math.max(0, hsl[1]))); + // Use normalized result to derive all shades + const shades = [ 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95 ]; + const text = []; + text.push(':root.accented {'); + for ( const shade of shades ) { + hsl[2] = shade; + text.push(` --primary-${shade}: ${toRGB(hsl)};`); + } + text.push('}'); + hsl[1] = Math.min(25, hsl[1]); + hsl[2] = 80; + text.push( + ':root.light.accented {', + ` --button-surface-rgb: ${toRGB(hsl)};`, + '}', + ); + hsl[2] = 30; + text.push( + ':root.dark.accented {', + ` --button-surface-rgb: ${toRGB(hsl)};`, + '}', + ); + text.push(''); + stylesheet = text.join('\n'); + vAPI.messaging.send('dom', { what: 'uiAccentStylesheet', stylesheet }); + } + let w = self; + for (;;) { + const wdoc = w.document; + let style = wdoc.querySelector('style#accentColors'); + if ( style !== null ) { style.remove(); } + if ( accentEnabled ) { + style = wdoc.createElement('style'); + style.id = 'accentColors'; + style.textContent = stylesheet; + wdoc.head.append(style); + wdoc.documentElement.classList.add('accented'); + } else { + wdoc.documentElement.classList.remove('accented'); + } + if ( propagate === false ) { break; } + if ( w === w.parent ) { break; } + w = w.parent; + try { void w.document; } catch(ex) { break; } + } +} + +{ + // https://github.com/uBlockOrigin/uBlock-issues/issues/1044 + // Offer the possibility to bypass uBO's default styling + vAPI.messaging.send('dom', { what: 'uiStyles' }).then(response => { + if ( typeof response !== 'object' || response === null ) { return; } + setTheme(response.uiTheme); + if ( response.uiAccentCustom ) { + setAccentColor( + true, + response.uiAccentCustom0, + false, + response.uiAccentStylesheet + ); + } + if ( response.uiStyles !== 'unset' ) { + document.body.style.cssText = response.uiStyles; + } + }); + + const rootcl = document.documentElement.classList; + if ( vAPI.webextFlavor.soup.has('mobile') ) { + rootcl.add('mobile'); + } else { + rootcl.add('desktop'); + } + if ( window.matchMedia('(min-resolution: 150dpi)').matches ) { + rootcl.add('hidpi'); + } +} + +export { + setTheme, + setAccentColor, +}; diff --git a/src/js/udom.js b/src/js/udom.js deleted file mode 100644 index 4f6e8aafe..000000000 --- a/src/js/udom.js +++ /dev/null @@ -1,778 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-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 -*/ - -/* global DOMTokenList */ -/* exported uDom */ - -'use strict'; - -/******************************************************************************/ - -// It's just a silly, minimalist DOM framework: this allows me to not rely -// on jQuery. jQuery contains way too much stuff than I need, and as per -// Opera rules, I am not allowed to use a cut-down version of jQuery. So -// the code here does *only* what I need, and nothing more, and with a lot -// of assumption on passed parameters, etc. I grow it on a per-need-basis only. - -const uDom = (( ) => { - -/******************************************************************************/ - -const DOMList = class { - constructor() { - this.nodes = []; - } - get length() { - return this.nodes.length; - } -}; - -/******************************************************************************/ - -const DOMListFactory = function(selector, context) { - const r = new DOMList(); - if ( typeof selector === 'string' ) { - selector = selector.trim(); - if ( selector !== '' ) { - return addSelectorToList(r, selector, context); - } - } - if ( selector instanceof Node ) { - return addNodeToList(r, selector); - } - if ( selector instanceof NodeList ) { - return addNodeListToList(r, selector); - } - if ( selector instanceof DOMList ) { - return addListToList(r, selector); - } - return r; -}; - -DOMListFactory.root = document.querySelector(':root'); - -/******************************************************************************/ - -DOMListFactory.setTheme = function(theme, propagate = false) { - if ( theme === 'auto' ) { - if ( typeof self.matchMedia === 'function' ) { - const mql = self.matchMedia('(prefers-color-scheme: dark)'); - theme = mql instanceof Object && mql.matches === true - ? 'dark' - : 'light'; - } else { - theme = 'light'; - } - } - let w = self; - for (;;) { - const rootcl = w.document.documentElement.classList; - if ( theme === 'dark' ) { - rootcl.add('dark'); - rootcl.remove('light'); - } else /* if ( theme === 'light' ) */ { - rootcl.add('light'); - rootcl.remove('dark'); - } - if ( propagate === false ) { break; } - if ( w === w.parent ) { break; } - w = w.parent; - try { void w.document; } catch(ex) { return; } - } -}; - -DOMListFactory.setAccentColor = function( - accentEnabled, - accentColor, - propagate, - stylesheet = '' -) { - if ( accentEnabled && stylesheet === '' && self.hsluv !== undefined ) { - const toRGB = hsl => self.hsluv.hsluvToRgb(hsl).map(a => Math.round(a * 255)).join(' '); - // Normalize first - const hsl = self.hsluv.hexToHsluv(accentColor); - hsl[0] = Math.round(hsl[0] * 10) / 10; - hsl[1] = Math.round(Math.min(100, Math.max(0, hsl[1]))); - // Use normalized result to derive all shades - const shades = [ 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95 ]; - const text = []; - text.push(':root.accented {'); - for ( const shade of shades ) { - hsl[2] = shade; - text.push(` --primary-${shade}: ${toRGB(hsl)};`); - } - text.push('}'); - hsl[1] = Math.min(25, hsl[1]); - hsl[2] = 80; - text.push( - ':root.light.accented {', - ` --button-surface-rgb: ${toRGB(hsl)};`, - '}', - ); - hsl[2] = 30; - text.push( - ':root.dark.accented {', - ` --button-surface-rgb: ${toRGB(hsl)};`, - '}', - ); - text.push(''); - stylesheet = text.join('\n'); - vAPI.messaging.send('uDom', { what: 'uiAccentStylesheet', stylesheet }); - } - let w = self; - for (;;) { - const wdoc = w.document; - let style = wdoc.querySelector('style#accentColors'); - if ( style !== null ) { style.remove(); } - if ( accentEnabled ) { - style = wdoc.createElement('style'); - style.id = 'accentColors'; - style.textContent = stylesheet; - wdoc.head.append(style); - wdoc.documentElement.classList.add('accented'); - } else { - wdoc.documentElement.classList.remove('accented'); - } - if ( propagate === false ) { break; } - if ( w === w.parent ) { break; } - w = w.parent; - try { void w.document; } catch(ex) { break; } - } -}; - -{ - // https://github.com/uBlockOrigin/uBlock-issues/issues/1044 - // Offer the possibility to bypass uBO's default styling - vAPI.messaging.send('uDom', { what: 'uiStyles' }).then(response => { - if ( typeof response !== 'object' || response === null ) { return; } - uDom.setTheme(response.uiTheme); - if ( response.uiAccentCustom ) { - uDom.setAccentColor( - true, - response.uiAccentCustom0, - false, - response.uiAccentStylesheet - ); - } - if ( response.uiStyles !== 'unset' ) { - document.body.style.cssText = response.uiStyles; - } - }); - - const rootcl = DOMListFactory.root.classList; - if ( vAPI.webextFlavor.soup.has('mobile') ) { - rootcl.add('mobile'); - } else { - rootcl.add('desktop'); - } - if ( window.matchMedia('(min-resolution: 150dpi)').matches ) { - rootcl.add('hidpi'); - } -} - -/******************************************************************************/ - -DOMListFactory.onLoad = function(callback) { - window.addEventListener('load', callback); -}; - -/******************************************************************************/ - -DOMListFactory.nodeFromId = function(id) { - return document.getElementById(id); -}; - -DOMListFactory.nodeFromSelector = function(selector) { - return document.querySelector(selector); -}; - -/******************************************************************************/ - -const addNodeToList = function(list, node) { - if ( node ) { - list.nodes.push(node); - } - return list; -}; - -/******************************************************************************/ - -const addNodeListToList = function(list, nodelist) { - if ( nodelist ) { - var n = nodelist.length; - for ( var i = 0; i < n; i++ ) { - list.nodes.push(nodelist[i]); - } - } - return list; -}; - -/******************************************************************************/ - -const addListToList = function(list, other) { - list.nodes = list.nodes.concat(other.nodes); - return list; -}; - -/******************************************************************************/ - -const addSelectorToList = function(list, selector, context) { - var p = context || document; - var r = p.querySelectorAll(selector); - var n = r.length; - for ( var i = 0; i < n; i++ ) { - list.nodes.push(r[i]); - } - return list; -}; - -/******************************************************************************/ - -DOMList.prototype.nodeAt = function(i) { - return this.nodes[i] || null; -}; - -DOMList.prototype.at = function(i) { - return addNodeToList(new DOMList(), this.nodes[i]); -}; - -/******************************************************************************/ - -DOMList.prototype.toArray = function() { - return this.nodes.slice(); -}; - -/******************************************************************************/ - -DOMList.prototype.pop = function() { - return addNodeToList(new DOMList(), this.nodes.pop()); -}; - -/******************************************************************************/ - -DOMList.prototype.forEach = function(fn) { - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - fn(this.at(i), i); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.subset = function(i, l) { - var r = new DOMList(); - var n = l !== undefined ? l : this.nodes.length; - var j = Math.min(i + n, this.nodes.length); - if ( i < j ) { - r.nodes = this.nodes.slice(i, j); - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.first = function() { - return this.subset(0, 1); -}; - -/******************************************************************************/ - -DOMList.prototype.next = function(selector) { - var r = new DOMList(); - var n = this.nodes.length; - var node; - for ( var i = 0; i < n; i++ ) { - node = this.nodes[i]; - while ( node.nextSibling !== null ) { - node = node.nextSibling; - if ( node.nodeType !== 1 ) { continue; } - if ( node.matches(selector) === false ) { continue; } - addNodeToList(r, node); - break; - } - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.parent = function() { - var r = new DOMList(); - if ( this.nodes.length ) { - addNodeToList(r, this.nodes[0].parentNode); - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.filter = function(filter) { - var r = new DOMList(); - var filterFunc; - if ( typeof filter === 'string' ) { - filterFunc = function() { - return this.matches(filter); - }; - } else if ( typeof filter === 'function' ) { - filterFunc = filter; - } else { - filterFunc = function(){ - return true; - }; - } - var n = this.nodes.length; - var node; - for ( var i = 0; i < n; i++ ) { - node = this.nodes[i]; - if ( filterFunc.apply(node) ) { - addNodeToList(r, node); - } - } - return r; -}; - -/******************************************************************************/ - -// TODO: Avoid possible duplicates - -DOMList.prototype.ancestors = function(selector) { - var r = new DOMList(); - for ( var i = 0, n = this.nodes.length; i < n; i++ ) { - var node = this.nodes[i].parentNode; - while ( node ) { - if ( - node instanceof Element && - node.matches(selector) - ) { - addNodeToList(r, node); - } - node = node.parentNode; - } - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.descendants = function(selector) { - var r = new DOMList(); - var n = this.nodes.length; - var nl; - for ( var i = 0; i < n; i++ ) { - nl = this.nodes[i].querySelectorAll(selector); - addNodeListToList(r, nl); - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.contents = function() { - var r = new DOMList(); - var cnodes, cn, ci; - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - cnodes = this.nodes[i].childNodes; - cn = cnodes.length; - for ( ci = 0; ci < cn; ci++ ) { - addNodeToList(r, cnodes.item(ci)); - } - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.remove = function() { - var cn, p; - var i = this.nodes.length; - while ( i-- ) { - cn = this.nodes[i]; - if ( (p = cn.parentNode) ) { - p.removeChild(cn); - } - } - return this; -}; - -DOMList.prototype.detach = DOMList.prototype.remove; - -/******************************************************************************/ - -DOMList.prototype.empty = function() { - var node; - var i = this.nodes.length; - while ( i-- ) { - node = this.nodes[i]; - while ( node.firstChild ) { - node.removeChild(node.firstChild); - } - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.append = function(selector, context) { - var p = this.nodes[0]; - if ( p ) { - var c = DOMListFactory(selector, context); - var n = c.nodes.length; - for ( var i = 0; i < n; i++ ) { - p.appendChild(c.nodes[i]); - } - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.prepend = function(selector, context) { - var p = this.nodes[0]; - if ( p ) { - var c = DOMListFactory(selector, context); - var i = c.nodes.length; - while ( i-- ) { - p.insertBefore(c.nodes[i], p.firstChild); - } - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.appendTo = function(selector, context) { - var p = selector instanceof DOMListFactory ? selector : DOMListFactory(selector, context); - var n = p.length; - for ( var i = 0; i < n; i++ ) { - p.nodes[0].appendChild(this.nodes[i]); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.insertAfter = function(selector, context) { - if ( this.nodes.length === 0 ) { - return this; - } - var p = this.nodes[0].parentNode; - if ( !p ) { - return this; - } - var c = DOMListFactory(selector, context); - var n = c.nodes.length; - for ( var i = 0; i < n; i++ ) { - p.appendChild(c.nodes[i]); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.insertBefore = function(selector, context) { - if ( this.nodes.length === 0 ) { - return this; - } - var referenceNodes = DOMListFactory(selector, context); - if ( referenceNodes.nodes.length === 0 ) { - return this; - } - var referenceNode = referenceNodes.nodes[0]; - var parentNode = referenceNode.parentNode; - if ( !parentNode ) { - return this; - } - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - parentNode.insertBefore(this.nodes[i], referenceNode); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.clone = function(notDeep) { - var r = new DOMList(); - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - addNodeToList(r, this.nodes[i].cloneNode(!notDeep)); - } - return r; -}; - -/******************************************************************************/ - -DOMList.prototype.nthOfType = function() { - if ( this.nodes.length === 0 ) { - return 0; - } - var node = this.nodes[0]; - var tagName = node.tagName; - var i = 1; - while ( node.previousElementSibling !== null ) { - node = node.previousElementSibling; - if ( typeof node.tagName !== 'string' ) { - continue; - } - if ( node.tagName !== tagName ) { - continue; - } - i++; - } - return i; -}; - -/******************************************************************************/ - -DOMList.prototype.attr = function(attr, value) { - var i = this.nodes.length; - if ( value === undefined && typeof attr !== 'object' ) { - return i ? this.nodes[0].getAttribute(attr) : undefined; - } - if ( typeof attr === 'object' ) { - var attrNames = Object.keys(attr); - var node, j, attrName; - while ( i-- ) { - node = this.nodes[i]; - j = attrNames.length; - while ( j-- ) { - attrName = attrNames[j]; - node.setAttribute(attrName, attr[attrName]); - } - } - } else { - while ( i-- ) { - this.nodes[i].setAttribute(attr, value); - } - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.removeAttr = function(attr) { - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].removeAttribute(attr); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.prop = function(prop, value) { - var i = this.nodes.length; - if ( value === undefined ) { - return i !== 0 ? this.nodes[0][prop] : undefined; - } - while ( i-- ) { - this.nodes[i][prop] = value; - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.css = function(prop, value) { - var i = this.nodes.length; - if ( value === undefined ) { - return i ? this.nodes[0].style[prop] : undefined; - } - if ( value !== '' ) { - while ( i-- ) { - this.nodes[i].style.setProperty(prop, value); - } - return this; - } - while ( i-- ) { - this.nodes[i].style.removeProperty(prop); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.val = function(value) { - return this.prop('value', value); -}; - -/******************************************************************************/ - -DOMList.prototype.text = function(text) { - var i = this.nodes.length; - if ( text === undefined ) { - return i ? this.nodes[0].textContent : ''; - } - while ( i-- ) { - this.nodes[i].textContent = text; - } - return this; -}; - -/******************************************************************************/ - -const toggleClass = function(node, className, targetState) { - const tokenList = node.classList; - if ( tokenList instanceof DOMTokenList === false ) { return; } - const currentState = tokenList.contains(className); - const newState = targetState !== undefined ? targetState : !currentState; - if ( newState === currentState ) { return; } - tokenList.toggle(className, newState); -}; - -/******************************************************************************/ - -DOMList.prototype.hasClass = function(className) { - if ( !this.nodes.length ) { - return false; - } - const tokenList = this.nodes[0].classList; - return tokenList instanceof DOMTokenList && - tokenList.contains(className); -}; -DOMList.prototype.hasClassName = DOMList.prototype.hasClass; - -DOMList.prototype.addClass = function(className) { - return this.toggleClass(className, true); -}; - -DOMList.prototype.removeClass = function(className) { - if ( className !== undefined ) { - return this.toggleClass(className, false); - } - for ( const node of this.nodes ) { - node.className = ''; - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.toggleClass = function(className, targetState) { - if ( className.indexOf(' ') !== -1 ) { - return this.toggleClasses(className, targetState); - } - for ( const node of this.nodes ) { - toggleClass(node, className, targetState); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.toggleClasses = function(classNames, targetState) { - const tokens = classNames.split(/\s+/); - for ( const node of this.nodes ) { - for ( const token of tokens ) { - toggleClass(node, token, targetState); - } - } - return this; -}; - -/******************************************************************************/ - -const listenerEntries = []; - -const ListenerEntry = function(target, type, capture, callback) { - this.target = target; - this.type = type; - this.capture = capture; - this.callback = callback; - target.addEventListener(type, callback, capture); -}; - -ListenerEntry.prototype.dispose = function() { - this.target.removeEventListener(this.type, this.callback, this.capture); - this.target = null; - this.callback = null; -}; - -/******************************************************************************/ - -const makeEventHandler = function(selector, callback) { - return function(event) { - const dispatcher = event.currentTarget; - if ( - dispatcher instanceof HTMLElement === false || - typeof dispatcher.querySelectorAll !== 'function' - ) { - return; - } - const receiver = event.target; - const ancestor = receiver.closest(selector); - if ( - ancestor === receiver && - ancestor !== dispatcher && - dispatcher.contains(ancestor) - ) { - callback.call(receiver, event); - } - }; -}; - -DOMList.prototype.on = function(etype, selector, callback) { - if ( typeof selector === 'function' ) { - callback = selector; - selector = undefined; - } else { - callback = makeEventHandler(selector, callback); - } - - for ( const node of this.nodes ) { - listenerEntries.push( - new ListenerEntry(node, etype, selector !== undefined, callback) - ); - } - return this; -}; - -/******************************************************************************/ - -// TODO: Won't work for delegated handlers. Need to figure -// what needs to be done. - -DOMList.prototype.off = function(evtype, callback) { - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].removeEventListener(evtype, callback); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.trigger = function(etype) { - var ev = new CustomEvent(etype); - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].dispatchEvent(ev); - } - return this; -}; - -/******************************************************************************/ - -return DOMListFactory; - -})(); diff --git a/src/js/whitelist.js b/src/js/whitelist.js index 2ad4460b8..a8a424b69 100644 --- a/src/js/whitelist.js +++ b/src/js/whitelist.js @@ -19,11 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -/* global CodeMirror, uDom, uBlockDashboard */ +/* global CodeMirror, uBlockDashboard */ 'use strict'; import { i18n$ } from './i18n.js'; +import { dom, qs$ } from './dom.js'; /******************************************************************************/ @@ -90,15 +91,12 @@ const noopFunc = function(){}; let cachedWhitelist = ''; -const cmEditor = new CodeMirror( - document.getElementById('whitelist'), - { - autofocus: true, - lineNumbers: true, - lineWrapping: true, - styleActiveLine: true, - } -); +const cmEditor = new CodeMirror(qs$('#whitelist'), { + autofocus: true, + lineNumbers: true, + lineWrapping: true, + styleActiveLine: true, +}); uBlockDashboard.patchCodeMirrorEditor(cmEditor); @@ -116,12 +114,12 @@ const setEditorText = function(text) { /******************************************************************************/ const whitelistChanged = function() { - const whitelistElem = uDom.nodeFromId('whitelist'); - const bad = whitelistElem.querySelector('.cm-error') !== null; + const whitelistElem = qs$('#whitelist'); + const bad = qs$(whitelistElem, '.cm-error') !== null; const changedWhitelist = getEditorText().trim(); const changed = changedWhitelist !== cachedWhitelist; - uDom.nodeFromId('whitelistApply').disabled = !changed || bad; - uDom.nodeFromId('whitelistRevert').disabled = !changed; + qs$('#whitelistApply').disabled = !changed || bad; + qs$('#whitelistRevert').disabled = !changed; CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc; }; @@ -188,7 +186,7 @@ const handleImportFilePicker = function() { /******************************************************************************/ const startImportFilePicker = function() { - const input = document.getElementById('importFilePicker'); + const input = qs$('#importFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. @@ -251,11 +249,11 @@ self.hasUnsavedData = function() { /******************************************************************************/ -uDom('#importWhitelistFromFile').on('click', startImportFilePicker); -uDom('#importFilePicker').on('change', handleImportFilePicker); -uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile); -uDom('#whitelistApply').on('click', ( ) => { applyChanges(); }); -uDom('#whitelistRevert').on('click', revertChanges); +dom.on('#importWhitelistFromFile', 'click', startImportFilePicker); +dom.on('#importFilePicker', 'change', handleImportFilePicker); +dom.on('#exportWhitelistToFile', 'click', exportWhitelistToFile); +dom.on('#whitelistApply', 'click', ( ) => { applyChanges(); }); +dom.on('#whitelistRevert', 'click', revertChanges); renderWhitelist(); diff --git a/src/logger-ui.html b/src/logger-ui.html index fd7c5b240..7286d6fbc 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -212,7 +212,7 @@ - + diff --git a/src/no-dashboard.html b/src/no-dashboard.html index 4aa163cfb..35b27c9d5 100644 --- a/src/no-dashboard.html +++ b/src/no-dashboard.html @@ -20,7 +20,7 @@ - + diff --git a/src/popup-fenix.html b/src/popup-fenix.html index 7bba0b141..13f850451 100644 --- a/src/popup-fenix.html +++ b/src/popup-fenix.html @@ -101,7 +101,7 @@ - + diff --git a/src/settings.html b/src/settings.html index 7936c07ac..28a428ba3 100644 --- a/src/settings.html +++ b/src/settings.html @@ -89,9 +89,9 @@ - + - + diff --git a/src/shortcuts.html b/src/shortcuts.html deleted file mode 100644 index 39ee585d2..000000000 --- a/src/shortcuts.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - -uBlock Origin — Keyboard shortcuts - - - - - - - - -
-
-
- - - - - - - - - - - - - - - diff --git a/src/support.html b/src/support.html index d0304d9a8..77faaca7b 100644 --- a/src/support.html +++ b/src/support.html @@ -112,10 +112,10 @@ - + - - + + diff --git a/src/web_accessible_resources/click2load.html b/src/web_accessible_resources/click2load.html index 3452f1f0e..f9a09c045 100644 --- a/src/web_accessible_resources/click2load.html +++ b/src/web_accessible_resources/click2load.html @@ -20,9 +20,9 @@ - - - + + + diff --git a/src/web_accessible_resources/epicker-ui.html b/src/web_accessible_resources/epicker-ui.html index 055427395..bad51c3d2 100644 --- a/src/web_accessible_resources/epicker-ui.html +++ b/src/web_accessible_resources/epicker-ui.html @@ -68,7 +68,7 @@ - + diff --git a/src/whitelist.html b/src/whitelist.html index 9a8cc3824..12a6e8233 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -52,9 +52,9 @@ - + - + diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index f9f34ccd7..20f631583 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -28,6 +28,7 @@ cp src/css/common.css $DES/css/ cp src/css/dashboard-common.css $DES/css/ cp src/css/fa-icons.css $DES/css/ +cp src/js/dom.js $DES/js/ cp src/js/fa-icons.js $DES/js/ cp src/js/i18n.js $DES/js/