Code maintenance: replace uDom.js with dom.js

`uDom` is old and crusty and `dom` is meant as replacement. The
goal of `dom` is to be simpler and mainly just convenience
methods for handling the DOM with vanilla JS -- this is not a
framework.

Additionally, removed keyboard shortcuts pane which was useful
only on very old versions of Firefox.
This commit is contained in:
Raymond Hill 2022-11-12 09:51:22 -05:00
parent 95f1b2f1bc
commit feaa338678
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
55 changed files with 1254 additions and 2411 deletions

View File

@ -34,6 +34,7 @@
</div> </div>
</div> </div>
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/about.js" type="module"></script> <script src="js/about.js" type="module"></script>

View File

@ -30,6 +30,7 @@
<!-- -------- --> <!-- -------- -->
<iframe id="iframe" src=""></iframe> <iframe id="iframe" src=""></iframe>
<!-- -------- --> <!-- -------- -->
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard.js" type="module"></script> <script src="js/dashboard.js" type="module"></script>

View File

@ -21,15 +21,13 @@
'use strict'; 'use strict';
/******************************************************************************/
import { runtime } from './ext.js'; import { runtime } from './ext.js';
import { qs$ } from './dom.js'; import { dom } from './dom.js';
/******************************************************************************/ /******************************************************************************/
(async ( ) => { (async ( ) => {
const manifest = runtime.getManifest(); const manifest = runtime.getManifest();
qs$('#aboutNameVer').textContent = `${manifest.name} ${manifest.version}`; dom.text('#aboutNameVer', `${manifest.name} ${manifest.version}`);
})(); })();

View File

@ -21,12 +21,10 @@
'use strict'; 'use strict';
/******************************************************************************/ import { dom } from './dom.js';
import { dom, qsa$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
// Open links in the proper window // Open links in the proper window
dom.attr(qsa$('a'), 'target', '_blank'); dom.attr('a', 'target', '_blank');
dom.attr(qsa$('a[href*="dashboard.html"]'), 'target', '_parent'); dom.attr('a[href*="dashboard.html"]', 'target', '_parent');

View File

@ -21,15 +21,13 @@
'use strict'; 'use strict';
/******************************************************************************/
import { simpleStorage } from './storage.js'; import { simpleStorage } from './storage.js';
import { dom, qs$ } from './dom.js'; import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
const discardUnsavedData = function(synchronous = false) { const discardUnsavedData = function(synchronous = false) {
const paneFrame = document.getElementById('iframe'); const paneFrame = qs$('#iframe');
const paneWindow = paneFrame.contentWindow; const paneWindow = paneFrame.contentWindow;
if ( if (
typeof paneWindow.hasUnsavedData !== 'function' || typeof paneWindow.hasUnsavedData !== 'function' ||
@ -44,11 +42,11 @@ const discardUnsavedData = function(synchronous = false) {
return new Promise(resolve => { return new Promise(resolve => {
const modal = document.querySelector('#unsavedWarning'); const modal = document.querySelector('#unsavedWarning');
modal.classList.add('on'); dom.cl.add(modal, 'on');
modal.focus(); modal.focus();
const onDone = status => { const onDone = status => {
modal.classList.remove('on'); dom.cl.remove(modal, 'on');
document.removeEventListener('click', onClick, true); document.removeEventListener('click', onClick, true);
resolve(status); resolve(status);
}; };
@ -73,15 +71,15 @@ const discardUnsavedData = function(synchronous = false) {
const loadDashboardPanel = function(pane, first) { const loadDashboardPanel = function(pane, first) {
const tabButton = document.querySelector(`[data-pane="${pane}"]`); const tabButton = document.querySelector(`[data-pane="${pane}"]`);
if ( tabButton === null || tabButton.classList.contains('selected') ) { if ( tabButton === null || dom.cl.has(tabButton, 'selected') ) {
return; return;
} }
const loadPane = ( ) => { const loadPane = ( ) => {
self.location.replace(`#${pane}`); self.location.replace(`#${pane}`);
for ( const node of document.querySelectorAll('.tabButton.selected') ) { 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(); tabButton.scrollIntoView();
document.querySelector('#iframe').contentWindow.location.replace(pane); document.querySelector('#iframe').contentWindow.location.replace(pane);
if ( pane !== 'no-dashboard.html' ) { if ( pane !== 'no-dashboard.html' ) {
@ -103,11 +101,11 @@ const loadDashboardPanel = function(pane, first) {
}; };
const onTabClickHandler = function(ev) { 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' ) { if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
document.body.classList.add('noDashboard'); dom.cl.add(dom.body, 'noDashboard');
} }
(async ( ) => { (async ( ) => {
@ -117,12 +115,7 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
} }
loadDashboardPanel(pane !== null ? pane : 'settings.html', true); loadDashboardPanel(pane !== null ? pane : 'settings.html', true);
dom.on( dom.on('#dashboard-nav', 'click', '.tabButton', onTabClickHandler);
qs$('#dashboard-nav'),
'click',
'.tabButton',
onTabClickHandler
);
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
window.addEventListener('beforeunload', ( ) => { window.addEventListener('beforeunload', ( ) => {

View File

@ -51,7 +51,7 @@ function setFilteringMode(level, commit = false) {
modeSlider.dataset.level = level; modeSlider.dataset.level = level;
if ( qs$('.filteringModeSlider.moving') === null ) { if ( qs$('.filteringModeSlider.moving') === null ) {
dom.text( dom.text(
qs$('#filteringModeText > span:nth-of-type(1)'), '#filteringModeText > span:nth-of-type(1)',
i18n$(`filteringMode${level}Name`) i18n$(`filteringMode${level}Name`)
); );
} }
@ -79,7 +79,7 @@ async function commitFilteringMode() {
} }
} }
dom.text( dom.text(
qs$('#filteringModeText > span:nth-of-type(1)'), '#filteringModeText > span:nth-of-type(1)',
i18n$(`filteringMode${afterLevel}Name`) i18n$(`filteringMode${afterLevel}Name`)
); );
const actualLevel = await sendMessage({ const actualLevel = await sendMessage({
@ -112,7 +112,7 @@ async function commitFilteringMode() {
const modeSlider = qs$('.filteringModeSlider'); const modeSlider = qs$('.filteringModeSlider');
if ( `${level}` === modeSlider.dataset.level ) { return; } if ( `${level}` === modeSlider.dataset.level ) { return; }
dom.text( dom.text(
qs$('#filteringModeText > span:nth-of-type(2)'), '#filteringModeText > span:nth-of-type(2)',
i18n$(`filteringMode${level}Name`) i18n$(`filteringMode${level}Name`)
); );
setFilteringMode(level); setFilteringMode(level);
@ -131,7 +131,7 @@ async function commitFilteringMode() {
dom.cl.remove(modeSlider, 'moving'); dom.cl.remove(modeSlider, 'moving');
self.removeEventListener('mousemove', moveAsync, { capture: true }); self.removeEventListener('mousemove', moveAsync, { capture: true });
self.removeEventListener('mouseup', stop, { 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(); commitFilteringMode();
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
@ -160,11 +160,11 @@ async function commitFilteringMode() {
ev.preventDefault(); ev.preventDefault();
}; };
dom.on(qs$('.filteringModeButton'), 'mousedown', startSliding); dom.on('.filteringModeButton', 'mousedown', startSliding);
} }
dom.on( dom.on(
qs$('.filteringModeSlider'), '.filteringModeSlider',
'click', 'click',
'.filteringModeSlider span[data-level]', '.filteringModeSlider span[data-level]',
ev => { ev => {
@ -177,25 +177,25 @@ dom.on(
); );
dom.on( dom.on(
qs$('.filteringModeSlider'), '.filteringModeSlider',
'mouseenter', 'mouseenter',
'.filteringModeSlider span[data-level]', '.filteringModeSlider span[data-level]',
ev => { ev => {
const span = ev.target; const span = ev.target;
const level = parseInt(span.dataset.level, 10); const level = parseInt(span.dataset.level, 10);
dom.text( dom.text(
qs$('#filteringModeText > span:nth-of-type(2)'), '#filteringModeText > span:nth-of-type(2)',
i18n$(`filteringMode${level}Name`) i18n$(`filteringMode${level}Name`)
); );
} }
); );
dom.on( dom.on(
qs$('.filteringModeSlider'), '.filteringModeSlider',
'mouseleave', 'mouseleave',
'.filteringModeSlider span[data-level]', '.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); sectionBitsToAttribute(parseInt(s, 10) || 0);
}); });
dom.on(qs$('#moreButton'), 'click', ( ) => { dom.on('#moreButton', 'click', ( ) => {
toggleSections(true); toggleSections(true);
}); });
dom.on(qs$('#lessButton'), 'click', ( ) => { dom.on('#lessButton', 'click', ( ) => {
toggleSections(false); toggleSections(false);
}); });
@ -284,12 +284,12 @@ async function init() {
setFilteringMode(popupPanelData.level); setFilteringMode(popupPanelData.level);
dom.text(qs$('#hostname'), tabHostname); dom.text('#hostname', tabHostname);
const parent = qs$('#rulesetStats'); const parent = qs$('#rulesetStats');
for ( const details of popupPanelData.rulesetDetails || [] ) { for ( const details of popupPanelData.rulesetDetails || [] ) {
const div = qs$('#templates .rulesetDetails').cloneNode(true); const div = dom.clone('#templates .rulesetDetails');
dom.text(qs$('h1', div), details.name); dom.text(qs$(div, 'h1'), details.name);
const { rules, filters, css } = details; const { rules, filters, css } = details;
let ruleCount = rules.plain + rules.regex; let ruleCount = rules.plain + rules.regex;
if ( popupPanelData.hasOmnipotence ) { if ( popupPanelData.hasOmnipotence ) {
@ -301,7 +301,7 @@ async function init() {
specificCount += css.specific.entityBased; specificCount += css.specific.entityBased;
} }
dom.text( dom.text(
qs$('p', div), qs$(div, 'p'),
i18n$('perRulesetStats') i18n$('perRulesetStats')
.replace('{{ruleCount}}', ruleCount.toLocaleString()) .replace('{{ruleCount}}', ruleCount.toLocaleString())
.replace('{{filterCount}}', filters.accepted.toLocaleString()) .replace('{{filterCount}}', filters.accepted.toLocaleString())

View File

@ -21,8 +21,6 @@
'use strict'; 'use strict';
/******************************************************************************/
import { browser, sendMessage } from './ext.js'; import { browser, sendMessage } from './ext.js';
import { i18n$ } from './i18n.js'; import { i18n$ } from './i18n.js';
import { dom, qs$, qsa$ } from './dom.js'; import { dom, qs$, qsa$ } from './dom.js';
@ -66,25 +64,24 @@ function renderFilterLists(soft = false) {
const liFromListEntry = function(ruleset, li, hideUnused) { const liFromListEntry = function(ruleset, li, hideUnused) {
if ( !li ) { if ( !li ) {
li = listEntryTemplate.cloneNode(true); li = dom.clone(listEntryTemplate);
} }
const on = enabledRulesets.includes(ruleset.id); 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 ) { if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
dom.attr(li, 'data-listkey', ruleset.id); dom.attr(li, 'data-listkey', ruleset.id);
qs$('input[type="checkbox"]', li).checked = on; qs$(li, 'input[type="checkbox"]').checked = on;
qs$('.listname', li).textContent = ruleset.name || ruleset.id; dom.text(qs$(li, '.listname'), ruleset.name || ruleset.id);
dom.cl.remove(li, 'toRemove'); dom.cl.remove(li, 'toRemove');
if ( ruleset.homeURL ) { if ( ruleset.homeURL ) {
dom.cl.add(li, 'support'); dom.cl.add(li, 'support');
const elem = qs$('a.support', li); dom.attr(qs$(li, 'a.support'), 'href', ruleset.homeURL);
dom.attr(elem, 'href', ruleset.homeURL);
} else { } else {
dom.cl.remove(li, 'support'); dom.cl.remove(li, 'support');
} }
if ( ruleset.instructionURL ) { if ( ruleset.instructionURL ) {
dom.cl.add(li, 'mustread'); dom.cl.add(li, 'mustread');
dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL); dom.attr(qs$(li, 'a.mustread'), 'href', ruleset.instructionURL);
} else { } else {
dom.cl.remove(li, 'mustread'); dom.cl.remove(li, 'mustread');
} }
@ -93,14 +90,14 @@ function renderFilterLists(soft = false) {
} }
// https://github.com/gorhill/uBlock/issues/1429 // https://github.com/gorhill/uBlock/issues/1429
if ( soft !== true ) { if ( soft !== true ) {
qs$('input[type="checkbox"]', li).checked = on; qs$(li, 'input[type="checkbox"]').checked = on;
} }
const stats = rulesetStats(ruleset.id); const stats = rulesetStats(ruleset.id);
li.title = listStatsTemplate li.title = listStatsTemplate
.replace('{{ruleCount}}', renderNumber(stats.ruleCount)) .replace('{{ruleCount}}', renderNumber(stats.ruleCount))
.replace('{{filterCount}}', renderNumber(stats.filterCount)); .replace('{{filterCount}}', renderNumber(stats.filterCount));
dom.attr( dom.attr(
qs$('.input.checkbox', li), qs$(li, '.input.checkbox'),
'disabled', 'disabled',
stats.ruleCount === 0 ? '' : null stats.ruleCount === 0 ? '' : null
); );
@ -126,22 +123,25 @@ function renderFilterLists(soft = false) {
const liFromListGroup = function(groupKey, groupRulesets) { const liFromListGroup = function(groupKey, groupRulesets) {
let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`); let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`);
if ( liGroup === null ) { if ( liGroup === null ) {
liGroup = listGroupTemplate.cloneNode(true); liGroup = dom.clone(listGroupTemplate);
let groupName = groupNames.get(groupKey); let groupName = groupNames.get(groupKey);
if ( groupName === undefined ) { if ( groupName === undefined ) {
groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
groupNames.set(groupKey, groupName); groupNames.set(groupKey, groupName);
} }
if ( groupName !== '' ) { if ( groupName !== '' ) {
qs$('.geName', liGroup).textContent = groupName; dom.text(qs$(liGroup, '.geName'), groupName);
} }
} }
if ( qs$('.geName:empty', liGroup) === null ) { if ( qs$(liGroup, '.geName:empty') === null ) {
qs$('.geCount', liGroup).textContent = listEntryCountFromGroup(groupRulesets); dom.text(
qs$(liGroup, '.geCount'),
listEntryCountFromGroup(groupRulesets)
);
} }
const hideUnused = mustHideUnusedLists(groupKey); const hideUnused = mustHideUnusedLists(groupKey);
liGroup.classList.toggle('hideUnused', hideUnused); dom.cl.toggle(liGroup, 'hideUnused', hideUnused);
const ulGroup = qs$('.listEntries', liGroup); const ulGroup = qs$(liGroup, '.listEntries');
if ( !groupRulesets ) { return liGroup; } if ( !groupRulesets ) { return liGroup; }
groupRulesets.sort(function(a, b) { groupRulesets.sort(function(a, b) {
return (a.name || '').localeCompare(b.name || ''); return (a.name || '').localeCompare(b.name || '');
@ -161,10 +161,7 @@ function renderFilterLists(soft = false) {
// Incremental rendering: this will allow us to easily discard unused // Incremental rendering: this will allow us to easily discard unused
// DOM list entries. // DOM list entries.
dom.cl.add( dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard');
qsa$('#lists .listEntries .listEntry[data-listkey]'),
'discard'
);
// Visually split the filter lists in three groups // Visually split the filter lists in three groups
const ulLists = qs$('#lists'); const ulLists = qs$('#lists');
@ -192,14 +189,14 @@ function renderFilterLists(soft = false) {
dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*')); dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
for ( const [ groupKey, groupRulesets ] of groups ) { for ( const [ groupKey, groupRulesets ] of groups ) {
let liGroup = liFromListGroup(groupKey, groupRulesets); const liGroup = liFromListGroup(groupKey, groupRulesets);
liGroup.setAttribute('data-groupkey', groupKey); dom.attr(liGroup, 'data-groupkey', groupKey);
if ( liGroup.parentElement === null ) { if ( liGroup.parentElement === null ) {
ulLists.appendChild(liGroup); ulLists.appendChild(liGroup);
} }
} }
dom.remove(qsa$('#lists .listEntries .listEntry.discard')); dom.remove('#lists .listEntries .listEntry.discard');
renderWidgets(); renderWidgets();
} }
@ -220,15 +217,16 @@ const renderWidgets = function() {
let filterCount = 0; let filterCount = 0;
let ruleCount = 0; let ruleCount = 0;
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) { 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); const stats = rulesetStats(liEntry.dataset.listkey);
if ( stats === undefined ) { continue; } if ( stats === undefined ) { continue; }
ruleCount += stats.ruleCount; ruleCount += stats.ruleCount;
filterCount += stats.filterCount; filterCount += stats.filterCount;
} }
qs$('#listsOfBlockedHostsPrompt').textContent = i18n$('perRulesetStats') dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats')
.replace('{{ruleCount}}', ruleCount.toLocaleString()) .replace('{{ruleCount}}', ruleCount.toLocaleString())
.replace('{{filterCount}}', filterCount.toLocaleString()); .replace('{{filterCount}}', filterCount.toLocaleString())
);
}; };
/******************************************************************************/ /******************************************************************************/
@ -267,7 +265,7 @@ async function onFilteringModeChange(ev) {
} }
dom.on( dom.on(
qs$('#defaultFilteringMode'), '#defaultFilteringMode',
'change', 'change',
'.filteringModeCard input[type="radio"]', '.filteringModeCard input[type="radio"]',
ev => { onFilteringModeChange(ev); } 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({ sendMessage({
what: 'setAutoReload', what: 'setAutoReload',
state: ev.target.checked, state: ev.target.checked,
@ -287,7 +285,7 @@ dom.on(qs$('#autoReload input[type="checkbox"'), 'change', ev => {
async function applyEnabledRulesets() { async function applyEnabledRulesets() {
const enabledRulesets = []; const enabledRulesets = [];
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) { 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); enabledRulesets.push(liEntry.dataset.listkey);
} }
@ -299,7 +297,7 @@ async function applyEnabledRulesets() {
renderWidgets(); renderWidgets();
} }
dom.on(qs$('#lists'), 'change', '.listEntry input[type="checkbox"]', ( ) => { dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ( ) => {
applyEnabledRulesets(); applyEnabledRulesets();
}); });
@ -324,8 +322,8 @@ function toggleHideUnusedLists(which) {
if ( mustHide ) { if ( mustHide ) {
hideUnusedSet.add(which); hideUnusedSet.add(which);
} }
document.body.classList.toggle('hideUnused', mustHide); dom.cl.toggle(dom.body, 'hideUnused', mustHide);
dom.cl.toggle(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide); dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide);
} else { } else {
const doesHide = hideUnusedSet.has(which); const doesHide = hideUnusedSet.has(which);
if ( doesHide ) { if ( doesHide ) {
@ -335,7 +333,7 @@ function toggleHideUnusedLists(which) {
} }
mustHide = doesHide === doesHideAll; mustHide = doesHide === doesHideAll;
groupSelector = `.groupEntry[data-groupkey="${which}"]`; 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)`) ) { for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) {
@ -352,16 +350,11 @@ function toggleHideUnusedLists(which) {
); );
} }
dom.on( dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => {
qs$('#lists'),
'click',
'.groupEntry[data-groupkey] > .geDetails',
ev => {
toggleHideUnusedLists( toggleHideUnusedLists(
dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey') dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey')
); );
} });
);
// Initialize from saved state. // Initialize from saved state.
simpleStorage.getItem('hideUnusedFilterLists').then(value => { simpleStorage.getItem('hideUnusedFilterLists').then(value => {

View File

@ -1,6 +1,7 @@
/** /*******************************************************************************
uBlock Origin - a browser extension to block requests. 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 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 it under the terms of the GNU General Public License as published by
@ -18,38 +19,17 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
.commandEntries { /* jshint esversion:11 */
margin: 2em;
}
.commandEntries td { 'use strict';
padding: 0.5em 0.25em;
}
.commandEntries td.commandDesc { import { dom } from './dom.js';
text-align: end;
}
.commandEntries td.commandShortcut { /******************************************************************************/
white-space: nowrap;
}
.commandEntries td.commandShortcut input { const mql = self.matchMedia('(prefers-color-scheme: dark)');
padding: 0.4em; const theme = mql instanceof Object && mql.matches === true
} ? 'dark'
: 'light';
.commandEntries td.commandShortcut input:focus { dom.cl.toggle(dom.html, 'dark', theme === 'dark');
outline: 2px solid blue; dom.cl.toggle(dom.html, 'light', theme !== 'dark');
}
.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;
}

View File

@ -51,6 +51,7 @@
<div class="rulesetDetails"><h1></h1><p data-section="b"></p></div> <div class="rulesetDetails"><h1></h1><p data-section="b"></p></div>
</div> </div>
<script src="js/theme.js" type="module"></script>
<script src="js/fa-icons.js"></script> <script src="js/fa-icons.js"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/popup.js" type="module"></script> <script src="js/popup.js" type="module"></script>

View File

@ -100,6 +100,7 @@
</div> </div>
</div> </div>
<script src="js/theme.js" type="module"></script>
<script src="js/fa-icons.js"></script> <script src="js/fa-icons.js"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script> <script src="js/dashboard-common.js" type="module"></script>

View File

@ -60,9 +60,9 @@
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/vapi-client-extra.js"></script> <script src="js/vapi-client-extra.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script> <script src="js/cloud-ui.js" type="module"></script>
<script src="js/1p-filters.js" type="module"></script> <script src="js/1p-filters.js" type="module"></script>

View File

@ -77,9 +77,9 @@
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/vapi-client-extra.js"></script> <script src="js/vapi-client-extra.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script> <script src="js/cloud-ui.js" type="module"></script>
<script src="js/3p-filters.js" type="module"></script> <script src="js/3p-filters.js" type="module"></script>

View File

@ -52,10 +52,10 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/about.js"></script> <script src="js/about.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -33,10 +33,10 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/advanced-settings.js"></script> <script src="js/advanced-settings.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -42,9 +42,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/asset-viewer.js" type="module"></script> <script src="js/asset-viewer.js" type="module"></script>
</body> </body>

View File

@ -76,9 +76,6 @@ iframe {
width: 100vw; width: 100vw;
} }
body:not(.canUpdateShortcuts) .tabButton[data-pane="shortcuts.html"] {
display: none;
}
body .tabButton[data-pane="no-dashboard.html"] { body .tabButton[data-pane="no-dashboard.html"] {
display: none; display: none;
} }

View File

@ -18,7 +18,6 @@
--><button class="tabButton" type="button" data-pane="1p-filters.html" data-i18n="1pPageName" tabindex="0"></button><!-- --><button class="tabButton" type="button" data-pane="1p-filters.html" data-i18n="1pPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="dyna-rules.html" data-i18n="rulesPageName" tabindex="0"></button><!-- --><button class="tabButton" type="button" data-pane="dyna-rules.html" data-i18n="rulesPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="whitelist.html" data-i18n="whitelistPageName" tabindex="0"></button><!-- --><button class="tabButton" type="button" data-pane="whitelist.html" data-i18n="whitelistPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="shortcuts.html" data-i18n="shortcutsPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="support.html" data-i18n="supportPageName" tabindex="0"></button><!-- --><button class="tabButton" type="button" data-pane="support.html" data-i18n="supportPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="about.html" data-i18n="aboutPageName" tabindex="0"></button><!-- --><button class="tabButton" type="button" data-pane="about.html" data-i18n="aboutPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="no-dashboard.html"></button> --><button class="tabButton" type="button" data-pane="no-dashboard.html"></button>
@ -39,9 +38,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard.js"></script> <script src="js/dashboard.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -48,9 +48,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/devtools.js" type="module"></script> <script src="js/devtools.js" type="module"></script>
</body> </body>

View File

@ -57,7 +57,7 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/document-blocked.js" type="module"></script> <script src="js/document-blocked.js" type="module"></script>
</body> </body>

View File

@ -57,9 +57,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script> <script src="js/cloud-ui.js" type="module"></script>
<script src="js/dyna-rules.js" type="module"></script> <script src="js/dyna-rules.js" type="module"></script>

View File

@ -19,18 +19,17 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global CodeMirror, uDom, uBlockDashboard */ /* global CodeMirror, uBlockDashboard */
'use strict'; 'use strict';
/******************************************************************************/
import { i18n$ } from './i18n.js'; import { i18n$ } from './i18n.js';
import { dom, qs$ } from './dom.js';
import './codemirror/ubo-static-filtering.js'; import './codemirror/ubo-static-filtering.js';
/******************************************************************************/ /******************************************************************************/
const cmEditor = new CodeMirror(document.getElementById('userFilters'), { const cmEditor = new CodeMirror(qs$('#userFilters'), {
autoCloseBrackets: true, autoCloseBrackets: true,
autofocus: true, autofocus: true,
extraKeys: { extraKeys: {
@ -103,8 +102,8 @@ const userFiltersChanged = function(changed) {
if ( typeof changed !== 'boolean' ) { if ( typeof changed !== 'boolean' ) {
changed = self.hasUnsavedData(); changed = self.hasUnsavedData();
} }
uDom.nodeFromId('userFiltersApply').disabled = !changed; qs$('#userFiltersApply').disabled = !changed;
uDom.nodeFromId('userFiltersRevert').disabled = !changed; qs$('#userFiltersRevert').disabled = !changed;
}; };
/******************************************************************************/ /******************************************************************************/
@ -222,7 +221,7 @@ const handleImportFilePicker = function() {
/******************************************************************************/ /******************************************************************************/
const startImportFilePicker = 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 // 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 // triggered if the user pick a file, even if it is the same as the last
// one picked. // one picked.
@ -290,11 +289,11 @@ self.hasUnsavedData = function() {
/******************************************************************************/ /******************************************************************************/
// Handle user interaction // Handle user interaction
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker); dom.on('#importUserFiltersFromFile', 'click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker); dom.on('#importFilePicker', 'change', handleImportFilePicker);
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile); dom.on('#exportUserFiltersToFile', 'click', exportUserFiltersToFile);
uDom('#userFiltersApply').on('click', ( ) => { applyChanges(); }); dom.on('#userFiltersApply', 'click', ( ) => { applyChanges(); });
uDom('#userFiltersRevert').on('click', revertChanges); dom.on('#userFiltersRevert', 'click', revertChanges);
(async ( ) => { (async ( ) => {
await renderUserFilters(); await renderUserFilters();

View File

@ -19,13 +19,10 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
/******************************************************************************/
import { i18n, i18n$ } from './i18n.js'; import { i18n, i18n$ } from './i18n.js';
import { dom, qs$, qsa$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
@ -47,7 +44,7 @@ vAPI.broadcastListener.add(msg => {
updateAssetStatus(msg); updateAssetStatus(msg);
break; break;
case 'assetsUpdated': case 'assetsUpdated':
document.body.classList.remove('updating'); dom.cl.remove(dom.body, 'updating');
renderWidgets(); renderWidgets();
break; break;
case 'staticFilteringDataChanged': case 'staticFilteringDataChanged':
@ -67,8 +64,8 @@ const renderNumber = function(value) {
/******************************************************************************/ /******************************************************************************/
const renderFilterLists = function(soft) { const renderFilterLists = function(soft) {
const listGroupTemplate = uDom('#templates .groupEntry'); const listGroupTemplate = qs$('#templates .groupEntry');
const listEntryTemplate = uDom('#templates .listEntry'); const listEntryTemplate = qs$('#templates .listEntry');
const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats'); const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats');
const renderElapsedTimeToString = i18n.renderElapsedTimeToString; const renderElapsedTimeToString = i18n.renderElapsedTimeToString;
const groupNames = new Map([ [ 'user', '' ] ]); const groupNames = new Map([ [ 'user', '' ] ]);
@ -84,65 +81,62 @@ const renderFilterLists = function(soft) {
const liFromListEntry = function(listKey, li, hideUnused) { const liFromListEntry = function(listKey, li, hideUnused) {
const entry = listDetails.available[listKey]; const entry = listDetails.available[listKey];
if ( !li ) { if ( !li ) {
li = listEntryTemplate.clone().nodeAt(0); li = dom.clone(listEntryTemplate);
} }
const on = entry.off !== true; const on = entry.off !== true;
li.classList.toggle('checked', on); dom.cl.toggle(li, 'checked', on);
let elem; let elem;
if ( li.getAttribute('data-listkey') !== listKey ) { if ( dom.attr(li, 'data-listkey') !== listKey ) {
li.setAttribute('data-listkey', listKey); dom.attr(li, 'data-listkey', listKey);
elem = li.querySelector('input[type="checkbox"]'); elem = qs$(li, 'input[type="checkbox"]');
elem.checked = on; elem.checked = on;
elem = li.querySelector('.listname'); dom.text(qs$(li, '.listname'), listNameFromListKey(listKey));
elem.textContent = listNameFromListKey(listKey); elem = qs$(li, 'a.content');
elem = li.querySelector('a.content'); dom.attr(elem, 'href', 'asset-viewer.html?url=' + encodeURIComponent(listKey));
elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURIComponent(listKey)); dom.attr(elem, 'type', 'text/html');
elem.setAttribute('type', 'text/html'); dom.cl.remove(li, 'toRemove');
li.classList.remove('toRemove');
if ( entry.supportName ) { if ( entry.supportName ) {
li.classList.add('support'); dom.cl.add(li, 'support');
elem = li.querySelector('a.support'); elem = qs$(li, 'a.support');
elem.setAttribute('href', entry.supportURL); dom.attr(elem, 'href', entry.supportURL);
elem.setAttribute('title', entry.supportName); dom.attr(elem, 'title', entry.supportName);
} else { } else {
li.classList.remove('support'); dom.cl.remove(li, 'support');
} }
if ( entry.external ) { if ( entry.external ) {
li.classList.add('external'); dom.cl.add(li, 'external');
} else { } else {
li.classList.remove('external'); dom.cl.remove(li, 'external');
} }
if ( entry.instructionURL ) { if ( entry.instructionURL ) {
li.classList.add('mustread'); dom.cl.add(li, 'mustread');
elem = li.querySelector('a.mustread'); dom.attr(qs$(li, 'a.mustread'), 'href', entry.instructionURL);
elem.setAttribute('href', entry.instructionURL);
} else { } else {
li.classList.remove('mustread'); dom.cl.remove(li, 'mustread');
} }
li.classList.toggle('isDefault', entry.isDefault === true); dom.cl.toggle(li, 'isDefault', entry.isDefault === true);
li.classList.toggle('unused', hideUnused && !on); dom.cl.toggle(li, 'unused', hideUnused && !on);
} }
// https://github.com/gorhill/uBlock/issues/1429 // https://github.com/gorhill/uBlock/issues/1429
if ( !soft ) { 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 = ''; let text = '';
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) { if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
text = listStatsTemplate text = listStatsTemplate
.replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0)) .replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0))
.replace('{{total}}', renderNumber(entry.entryCount)); .replace('{{total}}', renderNumber(entry.entryCount));
} }
elem.textContent = text; dom.text(elem, text);
// https://github.com/chrisaljoudi/uBlock/issues/104 // https://github.com/chrisaljoudi/uBlock/issues/104
const asset = listDetails.cache[listKey] || {}; const asset = listDetails.cache[listKey] || {};
const remoteURL = asset.remoteURL; const remoteURL = asset.remoteURL;
li.classList.toggle( dom.cl.toggle(li, 'unsecure',
'unsecure',
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0 typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
); );
li.classList.toggle('failed', asset.error !== undefined); dom.cl.toggle(li, 'failed', asset.error !== undefined);
li.classList.toggle('obsolete', asset.obsolete === true); dom.cl.toggle(li, 'obsolete', asset.obsolete === true);
const lastUpdateString = lastUpdateTemplateString.replace( const lastUpdateString = lastUpdateTemplateString.replace(
'{{ago}}', '{{ago}}',
renderElapsedTimeToString(asset.writeTime || 0) renderElapsedTimeToString(asset.writeTime || 0)
@ -152,18 +146,15 @@ const renderFilterLists = function(soft) {
if ( asset.cached && asset.writeTime !== 0 ) { if ( asset.cached && asset.writeTime !== 0 ) {
title += '\n' + lastUpdateString; title += '\n' + lastUpdateString;
} }
li.querySelector('.status.obsolete').setAttribute('title', title); dom.attr(qs$(li, '.status.obsolete'), 'title', title);
} }
if ( asset.cached === true ) { if ( asset.cached === true ) {
li.classList.add('cached'); dom.cl.add(li, 'cached');
li.querySelector('.status.cache').setAttribute( dom.attr(qs$(li, '.status.cache'), 'title', lastUpdateString);
'title',
lastUpdateString
);
} else { } else {
li.classList.remove('cached'); dom.cl.remove(li, 'cached');
} }
li.classList.remove('discard'); dom.cl.remove(li, 'discard');
return li; return li;
}; };
@ -183,24 +174,24 @@ const renderFilterLists = function(soft) {
}; };
const liFromListGroup = function(groupKey, listKeys) { 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 ) { if ( liGroup === null ) {
liGroup = listGroupTemplate.clone().nodeAt(0); liGroup = dom.clone(listGroupTemplate);
let groupName = groupNames.get(groupKey); let groupName = groupNames.get(groupKey);
if ( groupName === undefined ) { if ( groupName === undefined ) {
groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
groupNames.set(groupKey, groupName); groupNames.set(groupKey, groupName);
} }
if ( groupName !== '' ) { if ( groupName !== '' ) {
liGroup.querySelector('.geName').textContent = groupName; dom.text(qs$(liGroup, '.geName'), groupName);
} }
} }
if ( liGroup.querySelector('.geName:empty') === null ) { if ( qs$(liGroup, '.geName:empty') === null ) {
liGroup.querySelector('.geCount').textContent = listEntryCountFromGroup(listKeys); dom.text(qs$(liGroup, '.geCount'), listEntryCountFromGroup(listKeys));
} }
let hideUnused = mustHideUnusedLists(groupKey); let hideUnused = mustHideUnusedLists(groupKey);
liGroup.classList.toggle('hideUnused', hideUnused); dom.cl.toggle(liGroup, 'hideUnused', hideUnused);
let ulGroup = liGroup.querySelector('.listEntries'); let ulGroup = qs$(liGroup, '.listEntries');
if ( !listKeys ) { return liGroup; } if ( !listKeys ) { return liGroup; }
listKeys.sort(function(a, b) { listKeys.sort(function(a, b) {
return (listDetails.available[a].title || '').localeCompare(listDetails.available[b].title || ''); 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 // Incremental rendering: this will allow us to easily discard unused
// DOM list entries. // 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. // 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 // Visually split the filter lists in purpose-based groups
const ulLists = document.querySelector('#lists'); const ulLists = qs$('#lists');
const groups = groupsFromLists(details.available); const groups = groupsFromLists(details.available);
const groupKeys = [ const groupKeys = [
'user', 'user',
@ -265,11 +257,11 @@ const renderFilterLists = function(soft) {
'regions', 'regions',
'custom' 'custom'
]; ];
document.body.classList.toggle('hideUnused', mustHideUnusedLists('*')); dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
for ( let i = 0; i < groupKeys.length; i++ ) { for ( let i = 0; i < groupKeys.length; i++ ) {
let groupKey = groupKeys[i]; let groupKey = groupKeys[i];
let liGroup = liFromListGroup(groupKey, groups.get(groupKey)); let liGroup = liFromListGroup(groupKey, groups.get(groupKey));
liGroup.setAttribute('data-groupkey', groupKey); dom.attr(liGroup, 'data-groupkey', groupKey);
if ( liGroup.parentElement === null ) { if ( liGroup.parentElement === null ) {
ulLists.appendChild(liGroup); ulLists.appendChild(liGroup);
} }
@ -280,28 +272,23 @@ const renderFilterLists = function(soft) {
ulLists.appendChild(liFromListGroup(groupKey, groupKey)); ulLists.appendChild(liFromListGroup(groupKey, groupKey));
} }
uDom('#lists .listEntries .listEntry.discard').remove(); dom.remove('#lists .listEntries .listEntry.discard');
// Re-insert import widget. // Re-insert import widget.
uDom('[data-groupkey="custom"] .listEntries').append(importWidget); qs$('[data-groupkey="custom"] .listEntries').append(importWidget);
uDom.nodeFromId('autoUpdate').checked = qs$('#autoUpdate').checked = listDetails.autoUpdate === true;
listDetails.autoUpdate === true; dom.text(
uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent = '#listsOfBlockedHostsPrompt',
i18n$('3pListsOfBlockedHostsPrompt') i18n$('3pListsOfBlockedHostsPrompt')
.replace( .replace('{{netFilterCount}}', renderNumber(details.netFilterCount))
'{{netFilterCount}}', .replace('{{cosmeticFilterCount}}', renderNumber(details.cosmeticFilterCount))
renderNumber(details.netFilterCount)
)
.replace(
'{{cosmeticFilterCount}}',
renderNumber(details.cosmeticFilterCount)
); );
uDom.nodeFromId('parseCosmeticFilters').checked = qs$('#parseCosmeticFilters').checked =
listDetails.parseCosmeticFilters === true; listDetails.parseCosmeticFilters === true;
uDom.nodeFromId('ignoreGenericCosmeticFilters').checked = qs$('#ignoreGenericCosmeticFilters').checked =
listDetails.ignoreGenericCosmeticFilters === true; listDetails.ignoreGenericCosmeticFilters === true;
uDom.nodeFromId('suspendUntilListsAreLoaded').checked = qs$('#suspendUntilListsAreLoaded').checked =
listDetails.suspendUntilListsAreLoaded === true; listDetails.suspendUntilListsAreLoaded === true;
// Compute a hash of the settings so that we can keep track of changes // 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 // https://github.com/gorhill/uBlock/issues/2394
document.body.classList.toggle('updating', listDetails.isUpdating); dom.cl.toggle(dom.body, 'updating', listDetails.isUpdating);
renderWidgets(); renderWidgets();
}; };
@ -326,39 +313,30 @@ const renderFilterLists = function(soft) {
/******************************************************************************/ /******************************************************************************/
const renderWidgets = function() { const renderWidgets = function() {
let cl = uDom.nodeFromId('buttonApply').classList; dom.cl.toggle('#buttonApply', 'disabled',
cl.toggle(
'disabled',
filteringSettingsHash === hashFromCurrentFromSettings() filteringSettingsHash === hashFromCurrentFromSettings()
); );
const updating = document.body.classList.contains('updating'); const updating = dom.cl.has(dom.body, 'updating');
cl = uDom.nodeFromId('buttonUpdate').classList; dom.cl.toggle('#buttonUpdate', 'active', updating);
cl.toggle('active', updating); dom.cl.toggle('#buttonUpdate', 'disabled',
cl.toggle(
'disabled',
updating === false && updating === false &&
document.querySelector('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null qs$('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null
); );
cl = uDom.nodeFromId('buttonPurgeAll').classList; dom.cl.toggle('#buttonPurgeAll', 'disabled',
cl.toggle( updating || qs$('#lists .listEntry.cached:not(.obsolete)') === null
'disabled',
updating || document.querySelector('#lists .listEntry.cached:not(.obsolete)') === null
); );
}; };
/******************************************************************************/ /******************************************************************************/
const updateAssetStatus = function(details) { const updateAssetStatus = function(details) {
const li = document.querySelector( const li = qs$(`#lists .listEntry[data-listkey="${details.key}"]`);
'#lists .listEntry[data-listkey="' + details.key + '"]'
);
if ( li === null ) { return; } if ( li === null ) { return; }
li.classList.toggle('failed', !!details.failed); dom.cl.toggle(li, 'failed', !!details.failed);
li.classList.toggle('obsolete', !details.cached); dom.cl.toggle(li, 'obsolete', !details.cached);
li.classList.toggle('cached', !!details.cached); dom.cl.toggle(li, 'cached', !!details.cached);
if ( details.cached ) { if ( details.cached ) {
li.querySelector('.status.cache').setAttribute( dom.attr(qs$(li, '.status.cache'), 'title',
'title',
lastUpdateTemplateString.replace( lastUpdateTemplateString.replace(
'{{ago}}', '{{ago}}',
i18n.renderElapsedTimeToString(Date.now()) i18n.renderElapsedTimeToString(Date.now())
@ -377,21 +355,21 @@ const updateAssetStatus = function(details) {
const hashFromCurrentFromSettings = function() { const hashFromCurrentFromSettings = function() {
const hash = [ const hash = [
uDom.nodeFromId('parseCosmeticFilters').checked, qs$('#parseCosmeticFilters').checked,
uDom.nodeFromId('ignoreGenericCosmeticFilters').checked qs$('#ignoreGenericCosmeticFilters').checked
]; ];
const listHash = []; 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 ) { for ( const liEntry of listEntries ) {
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) {
listHash.push(liEntry.getAttribute('data-listkey')); listHash.push(dom.attr(liEntry, 'data-listkey'));
} }
} }
hash.push( hash.push(
listHash.sort().join(), listHash.sort().join(),
uDom.nodeFromId('importLists').checked && qs$('#importLists').checked &&
reValidExternalList.test(uDom.nodeFromId('externalLists').value), reValidExternalList.test(qs$('#externalLists').value),
document.querySelector('#lists .listEntry.toRemove') !== null qs$('#lists .listEntry.toRemove') !== null
); );
return hash.join(); return hash.join();
}; };
@ -400,8 +378,7 @@ const hashFromCurrentFromSettings = function() {
const onListsetChanged = function(ev) { const onListsetChanged = function(ev) {
const input = ev.target; const input = ev.target;
const li = input.closest('.listEntry'); dom.cl.toggle(input.closest('.listEntry'), 'checked', input.checked);
li.classList.toggle('checked', input.checked);
onFilteringSettingsChanged(); onFilteringSettingsChanged();
}; };
@ -416,7 +393,7 @@ const onFilteringSettingsChanged = function() {
const onRemoveExternalList = function(ev) { const onRemoveExternalList = function(ev) {
const liEntry = ev.target.closest('[data-listkey]'); const liEntry = ev.target.closest('[data-listkey]');
if ( liEntry === null ) { return; } if ( liEntry === null ) { return; }
liEntry.classList.toggle('toRemove'); dom.cl.toggle(liEntry, 'toRemove');
renderWidgets(); renderWidgets();
}; };
@ -424,7 +401,7 @@ const onRemoveExternalList = function(ev) {
const onPurgeClicked = function(ev) { const onPurgeClicked = function(ev) {
const liEntry = ev.target.closest('[data-listkey]'); const liEntry = ev.target.closest('[data-listkey]');
const listKey = liEntry.getAttribute('data-listkey') || ''; const listKey = dom.attr(liEntry, 'data-listkey') || '';
if ( listKey === '' ) { return; } if ( listKey === '' ) { return; }
messaging.send('dashboard', { messaging.send('dashboard', {
@ -437,10 +414,10 @@ const onPurgeClicked = function(ev) {
// https://github.com/gorhill/uBlock/issues/1733 // https://github.com/gorhill/uBlock/issues/1733
// An external filter list must not be marked as obsolete, they will // An external filter list must not be marked as obsolete, they will
// always be fetched anyways if there is no cached copy. // always be fetched anyways if there is no cached copy.
liEntry.classList.add('obsolete'); dom.cl.add(liEntry, 'obsolete');
liEntry.classList.remove('cached'); dom.cl.remove(liEntry, 'cached');
if ( liEntry.querySelector('input[type="checkbox"]').checked ) { if ( qs$(liEntry, 'input[type="checkbox"]').checked ) {
renderWidgets(); renderWidgets();
} }
}; };
@ -452,41 +429,35 @@ const selectFilterLists = async function() {
messaging.send('dashboard', { messaging.send('dashboard', {
what: 'userSettings', what: 'userSettings',
name: 'parseAllABPHideFilters', name: 'parseAllABPHideFilters',
value: uDom.nodeFromId('parseCosmeticFilters').checked, value: qs$('#parseCosmeticFilters').checked,
}); });
messaging.send('dashboard', { messaging.send('dashboard', {
what: 'userSettings', what: 'userSettings',
name: 'ignoreGenericCosmeticFilters', name: 'ignoreGenericCosmeticFilters',
value: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, value: qs$('#ignoreGenericCosmeticFilters').checked,
}); });
// Filter lists to select // Filter lists to select
const toSelect = []; const toSelect = [];
for ( for ( const liEntry of qsa$('#lists .listEntry[data-listkey]:not(.toRemove)') ) {
const liEntry of if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) {
document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)') toSelect.push(dom.attr(liEntry, 'data-listkey'));
) {
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
toSelect.push(liEntry.getAttribute('data-listkey'));
} }
} }
// External filter lists to remove // External filter lists to remove
const toRemove = []; const toRemove = [];
for ( for ( const liEntry of qsa$('#lists .listEntry.toRemove[data-listkey]') ) {
const liEntry of toRemove.push(dom.attr(liEntry, 'data-listkey'));
document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]')
) {
toRemove.push(liEntry.getAttribute('data-listkey'));
} }
// External filter lists to import // External filter lists to import
const externalListsElem = document.getElementById('externalLists'); const externalListsElem = qs$('#externalLists');
const toImport = externalListsElem.value.trim(); const toImport = externalListsElem.value.trim();
{ {
const liEntry = externalListsElem.closest('.listEntry'); const liEntry = externalListsElem.closest('.listEntry');
liEntry.classList.remove('checked'); dom.cl.remove(liEntry, 'checked');
liEntry.querySelector('input[type="checkbox"]').checked = false; qs$(liEntry, 'input[type="checkbox"]').checked = false;
externalListsElem.value = ''; externalListsElem.value = '';
} }
@ -503,7 +474,7 @@ const selectFilterLists = async function() {
/******************************************************************************/ /******************************************************************************/
const buttonApplyHandler = async function() { const buttonApplyHandler = async function() {
uDom('#buttonApply').removeClass('enabled'); dom.cl.remove('#buttonApply', 'enabled');
await selectFilterLists(); await selectFilterLists();
renderWidgets(); renderWidgets();
messaging.send('dashboard', { what: 'reloadAllFilters' }); messaging.send('dashboard', { what: 'reloadAllFilters' });
@ -513,7 +484,7 @@ const buttonApplyHandler = async function() {
const buttonUpdateHandler = async function() { const buttonUpdateHandler = async function() {
await selectFilterLists(); await selectFilterLists();
document.body.classList.add('updating'); dom.cl.add(dom.body, 'updating');
renderWidgets(); renderWidgets();
messaging.send('dashboard', { what: 'forceUpdateAssets' }); messaging.send('dashboard', { what: 'forceUpdateAssets' });
}; };
@ -521,7 +492,7 @@ const buttonUpdateHandler = async function() {
/******************************************************************************/ /******************************************************************************/
const buttonPurgeAllHandler = async function(hard) { const buttonPurgeAllHandler = async function(hard) {
uDom('#buttonPurgeAll').removeClass('enabled'); dom.cl.remove('#buttonPurgeAll', 'enabled');
await messaging.send('dashboard', { await messaging.send('dashboard', {
what: 'purgeAllCaches', what: 'purgeAllCaches',
hard, hard,
@ -561,8 +532,8 @@ const toggleHideUnusedLists = function(which) {
if ( mustHide ) { if ( mustHide ) {
hideUnusedSet.add(which); hideUnusedSet.add(which);
} }
document.body.classList.toggle('hideUnused', mustHide); dom.cl.toggle(dom.body, 'hideUnused', mustHide);
uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide); dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide);
} else { } else {
const doesHide = hideUnusedSet.has(which); const doesHide = hideUnusedSet.has(which);
if ( doesHide ) { if ( doesHide ) {
@ -571,12 +542,13 @@ const toggleHideUnusedLists = function(which) {
hideUnusedSet.add(which); hideUnusedSet.add(which);
} }
mustHide = doesHide === doesHideAll; mustHide = doesHide === doesHideAll;
groupSelector = '.groupEntry[data-groupkey="' + which + '"] '; groupSelector = `.groupEntry[data-groupkey="${which}"] `;
uDom(groupSelector).toggleClass('hideUnused', mustHide); dom.cl.toggle(groupSelector, 'hideUnused', mustHide);
} }
uDom(groupSelector + '.listEntry input[type="checkbox"]:not(:checked)') qsa$(`${groupSelector}.listEntry input[type="checkbox"]:not(:checked)`)
.ancestors('.listEntry[data-listkey]') .forEach(elem => {
.toggleClass('unused', mustHide); dom.cl.toggle(elem.closest('.listEntry[data-listkey]'), 'unused', mustHide);
});
vAPI.localStorage.setItem( vAPI.localStorage.setItem(
'hideUnusedFilterLists', 'hideUnusedFilterLists',
Array.from(hideUnusedSet) Array.from(hideUnusedSet)
@ -584,20 +556,19 @@ const toggleHideUnusedLists = function(which) {
}; };
const revealHiddenUsedLists = function() { const revealHiddenUsedLists = function() {
uDom('#lists .listEntry.unused input[type="checkbox"]:checked') qsa$('#lists .listEntry.unused input[type="checkbox"]:checked')
.ancestors('.listEntry[data-listkey]') .forEach(elem => {
.removeClass('unused'); dom.cl.remove(elem.closest('.listEntry[data-listkey]'), 'unused');
});
}; };
uDom('#listsOfBlockedHostsPrompt').on('click', function() { dom.on('#listsOfBlockedHostsPrompt', 'click', ( ) => {
toggleHideUnusedLists('*'); toggleHideUnusedLists('*');
}); });
uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(ev) { dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => {
toggleHideUnusedLists( toggleHideUnusedLists(
uDom(ev.target) dom.attr(ev.target.closest('.groupEntry[data-groupkey]'), 'data-groupkey')
.ancestors('.groupEntry[data-groupkey]')
.attr('data-groupkey')
); );
}); });
@ -614,15 +585,15 @@ vAPI.localStorage.getItemAsync('hideUnusedFilterLists').then(value => {
const toCloudData = function() { const toCloudData = function() {
const bin = { const bin = {
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked, parseCosmeticFilters: qs$('#parseCosmeticFilters').checked,
ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, ignoreGenericCosmeticFilters: qs$('#ignoreGenericCosmeticFilters').checked,
selectedLists: [] selectedLists: []
}; };
const liEntries = document.querySelectorAll('#lists .listEntry'); const liEntries = qsa$('#lists .listEntry');
for ( const liEntry of liEntries ) { for ( const liEntry of liEntries ) {
if ( liEntry.querySelector('input').checked ) { if ( qs$(liEntry, 'input').checked ) {
bin.selectedLists.push(liEntry.getAttribute('data-listkey')); bin.selectedLists.push(dom.attr(liEntry, 'data-listkey'));
} }
} }
@ -634,24 +605,22 @@ const fromCloudData = function(data, append) {
let elem, checked; let elem, checked;
elem = uDom.nodeFromId('parseCosmeticFilters'); elem = qs$('#parseCosmeticFilters');
checked = data.parseCosmeticFilters === true || append && elem.checked; checked = data.parseCosmeticFilters === true || append && elem.checked;
elem.checked = listDetails.parseCosmeticFilters = checked; elem.checked = listDetails.parseCosmeticFilters = checked;
elem = uDom.nodeFromId('ignoreGenericCosmeticFilters'); elem = qs$('#ignoreGenericCosmeticFilters');
checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked; checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
elem.checked = listDetails.ignoreGenericCosmeticFilters = checked; elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
const selectedSet = new Set(data.selectedLists); const selectedSet = new Set(data.selectedLists);
const listEntries = uDom('#lists .listEntry'); for ( const listEntry of qsa$('#lists .listEntry') ) {
for ( let i = 0, n = listEntries.length; i < n; i++ ) { const listKey = dom.attr(listEntry, 'data-listkey');
const listEntry = listEntries.at(i);
const listKey = listEntry.attr('data-listkey');
const hasListKey = selectedSet.has(listKey); const hasListKey = selectedSet.has(listKey);
selectedSet.delete(listKey); selectedSet.delete(listKey);
const input = listEntry.descendants('input').first(); const input = qs$(listEntry, 'input');
if ( append && input.prop('checked') ) { continue; } if ( append && input.checked ) { continue; }
input.prop('checked', hasListKey); input.checked = hasListKey;
} }
// If there are URL-like list keys left in the selected set, import them. // 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 ) { if ( selectedSet.size !== 0 ) {
elem = uDom.nodeFromId('externalLists'); elem = qs$('#externalLists');
if ( append ) { if ( append ) {
if ( elem.value.trim() !== '' ) { elem.value += '\n'; } if ( elem.value.trim() !== '' ) { elem.value += '\n'; }
} else { } else {
elem.value = ''; elem.value = '';
} }
elem.value += Array.from(selectedSet).join('\n'); elem.value += Array.from(selectedSet).join('\n');
uDom.nodeFromId('importLists').checked = true; qs$('#importLists').checked = true;
} }
revealHiddenUsedLists(); revealHiddenUsedLists();
@ -686,21 +655,18 @@ self.hasUnsavedData = function() {
/******************************************************************************/ /******************************************************************************/
uDom('#autoUpdate').on('change', userSettingCheckboxChanged); dom.on('#autoUpdate', 'change', userSettingCheckboxChanged);
uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged); dom.on('#parseCosmeticFilters', 'change', onFilteringSettingsChanged);
uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged); dom.on('#ignoreGenericCosmeticFilters', 'change', onFilteringSettingsChanged);
uDom('#suspendUntilListsAreLoaded').on('change', userSettingCheckboxChanged); dom.on('#suspendUntilListsAreLoaded', 'change', userSettingCheckboxChanged);
uDom('#buttonApply').on('click', ( ) => { buttonApplyHandler(); }); dom.on('#buttonApply', 'click', ( ) => { buttonApplyHandler(); });
uDom('#buttonUpdate').on('click', ( ) => { buttonUpdateHandler(); }); dom.on('#buttonUpdate', 'click', ( ) => { buttonUpdateHandler(); });
uDom('#buttonPurgeAll').on('click', ev => { dom.on('#buttonPurgeAll', 'click', ev => { buttonPurgeAllHandler(ev.shiftKey); });
buttonPurgeAllHandler(ev.shiftKey); dom.on('#lists', 'change', '.listEntry input', onListsetChanged);
}); dom.on('#lists', 'click', '.listEntry .remove', onRemoveExternalList);
uDom('#lists').on('change', '.listEntry input', onListsetChanged); dom.on('#lists', 'click', 'span.cache', onPurgeClicked);
uDom('#lists').on('click', '.listEntry .remove', onRemoveExternalList); dom.on('#externalLists', 'input', onFilteringSettingsChanged);
uDom('#lists').on('click', 'span.cache', onPurgeClicked); dom.on('#lists','click', '.listEntry label *', ev => {
uDom('#externalLists').on('input', onFilteringSettingsChanged);
uDom('#lists').on('click', '.listEntry label *', ev => {
if ( ev.target.matches('a,input,.forinput') ) { return; } if ( ev.target.matches('a,input,.forinput') ) { return; }
ev.preventDefault(); ev.preventDefault();
}); });

View File

@ -19,10 +19,10 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
import { dom } from './dom.js';
/******************************************************************************/ /******************************************************************************/
(async ( ) => { (async ( ) => {
@ -30,5 +30,5 @@
what: 'getAppData', what: 'getAppData',
}); });
uDom('#aboutNameVer').text(appData.name + ' ' + appData.version); dom.text('#aboutNameVer', appData.name + ' ' + appData.version);
})(); })();

View File

@ -19,14 +19,11 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global CodeMirror, uDom, uBlockDashboard */ /* global CodeMirror, uBlockDashboard */
'use strict'; 'use strict';
/******************************************************************************/ import { dom, qs$ } from './dom.js';
{
// >>>> Start of private namespace
/******************************************************************************/ /******************************************************************************/
@ -69,15 +66,12 @@ CodeMirror.defineMode('raw-settings', function() {
}; };
}); });
const cmEditor = new CodeMirror( const cmEditor = new CodeMirror(qs$('#advancedSettings'), {
document.getElementById('advancedSettings'),
{
autofocus: true, autofocus: true,
lineNumbers: true, lineNumbers: true,
lineWrapping: false, lineWrapping: false,
styleActiveLine: true styleActiveLine: true
} });
);
uBlockDashboard.patchCodeMirrorEditor(cmEditor); 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 = (( ) => { const advancedSettingsChanged = (( ) => {
let timer; let timer;
@ -132,7 +124,7 @@ const advancedSettingsChanged = (( ) => {
timer = undefined; timer = undefined;
const changed = const changed =
hashFromAdvancedSettings(cmEditor.getValue()) !== beforeHash; hashFromAdvancedSettings(cmEditor.getValue()) !== beforeHash;
uDom.nodeFromId('advancedSettingsApply').disabled = !changed; qs$('#advancedSettingsApply').disabled = !changed;
CodeMirror.commands.save = changed ? applyChanges : function(){}; CodeMirror.commands.save = changed ? applyChanges : function(){};
}; };
@ -195,17 +187,11 @@ const applyChanges = async function() {
/******************************************************************************/ /******************************************************************************/
uDom.nodeFromId('advancedSettings').addEventListener( dom.on('#advancedSettings', 'input', advancedSettingsChanged);
'input', dom.on('#advancedSettingsApply', 'click', ( ) => {
advancedSettingsChanged
);
uDom.nodeFromId('advancedSettingsApply').addEventListener('click', ( ) => {
applyChanges(); applyChanges();
}); });
renderAdvancedSettings(true); renderAdvancedSettings(true);
/******************************************************************************/ /******************************************************************************/
// <<<< End of private namespace
}

View File

@ -25,6 +25,7 @@
/******************************************************************************/ /******************************************************************************/
import { dom, qs$ } from './dom.js';
import './codemirror/ubo-static-filtering.js'; import './codemirror/ubo-static-filtering.js';
/******************************************************************************/ /******************************************************************************/
@ -36,19 +37,19 @@ import './codemirror/ubo-static-filtering.js';
if ( assetKey === null ) { return; } if ( assetKey === null ) { return; }
const subscribeElem = subscribeParams.get('subscribe') !== null const subscribeElem = subscribeParams.get('subscribe') !== null
? document.getElementById('subscribe') ? qs$('#subscribe')
: null; : null;
if ( subscribeElem !== null && subscribeURL.hash !== '#subscribed' ) { if ( subscribeElem !== null && subscribeURL.hash !== '#subscribed' ) {
const title = subscribeParams.get('title'); const title = subscribeParams.get('title');
const promptElem = document.getElementById('subscribePrompt'); const promptElem = qs$('#subscribePrompt');
promptElem.children[0].textContent = title; dom.text(promptElem.children[0], title);
const a = promptElem.children[1]; const a = promptElem.children[1];
a.textContent = assetKey; dom.text(a, assetKey);
a.setAttribute('href', assetKey); dom.attr(a, 'href', assetKey);
subscribeElem.classList.remove('hide'); dom.cl.remove(subscribeElem, 'hide');
} }
const cmEditor = new CodeMirror(document.getElementById('content'), { const cmEditor = new CodeMirror(qs$('#content'), {
autofocus: true, autofocus: true,
foldGutter: true, foldGutter: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
@ -81,10 +82,8 @@ import './codemirror/ubo-static-filtering.js';
cmEditor.setValue(details && details.content || ''); cmEditor.setValue(details && details.content || '');
if ( subscribeElem !== null ) { if ( subscribeElem !== null ) {
document.getElementById('subscribeButton').addEventListener( dom.on('#subscribeButton', 'click', ( ) => {
'click', dom.cl.add(subscribeElem, 'hide');
( ) => {
subscribeElem.classList.add('hide');
vAPI.messaging.send('scriptlets', { vAPI.messaging.send('scriptlets', {
what: 'applyFilterListSelection', what: 'applyFilterListSelection',
toImport: assetKey, toImport: assetKey,
@ -93,16 +92,14 @@ import './codemirror/ubo-static-filtering.js';
what: 'reloadAllFilters' what: 'reloadAllFilters'
}); });
}); });
}, }, { once: true });
{ once: true }
);
} }
if ( details.sourceURL ) { if ( details.sourceURL ) {
const a = document.querySelector('.cm-search-widget .sourceURL'); const a = qs$('.cm-search-widget .sourceURL');
a.setAttribute('href', details.sourceURL); dom.attr(a, 'href', details.sourceURL);
a.setAttribute('title', details.sourceURL); dom.attr(a, 'title', details.sourceURL);
} }
document.body.classList.remove('loading'); dom.cl.remove(dom.body, 'loading');
})(); })();

View File

@ -19,11 +19,12 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom, faIconsInit */ /* global faIconsInit */
'use strict'; 'use strict';
import { i18n, i18n$ } from './i18n.js'; 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; } 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; } if ( self.cloud.datakey === '' ) { return; }
/******************************************************************************/ /******************************************************************************/
const fetchStorageUsed = async function() { const fetchStorageUsed = async function() {
let elem = widget.querySelector('#cloudCapacity'); let elem = qs$(widget, '#cloudCapacity');
if ( elem.classList.contains('hide') ) { return; } if ( dom.cl.has(elem, 'hide') ) { return; }
const result = await vAPI.messaging.send('cloudWidget', { const result = await vAPI.messaging.send('cloudWidget', {
what: 'cloudUsed', what: 'cloudUsed',
datakey: self.cloud.datakey, datakey: self.cloud.datakey,
}); });
if ( result instanceof Object === false ) { if ( result instanceof Object === false ) {
elem.classList.add('hide'); dom.cl.add(elem, 'hide');
return; return;
} }
const units = ' ' + i18n$('genericBytes'); const units = ' ' + i18n$('genericBytes');
@ -75,7 +76,7 @@ const fetchStorageUsed = async function() {
/******************************************************************************/ /******************************************************************************/
const fetchCloudData = async function() { const fetchCloudData = async function() {
const info = widget.querySelector('#cloudInfo'); const info = qs$(widget, '#cloudInfo');
const entry = await vAPI.messaging.send('cloudWidget', { const entry = await vAPI.messaging.send('cloudWidget', {
what: 'cloudPull', what: 'cloudPull',
@ -84,16 +85,16 @@ const fetchCloudData = async function() {
const hasData = entry instanceof Object; const hasData = entry instanceof Object;
if ( hasData === false ) { if ( hasData === false ) {
uDom.nodeFromId('cloudPull').setAttribute('disabled', ''); dom.attr('#cloudPull', 'disabled', '');
uDom.nodeFromId('cloudPullAndMerge').setAttribute('disabled', ''); dom.attr('#cloudPullAndMerge', 'disabled', '');
info.textContent = '...\n...'; info.textContent = '...\n...';
return entry; return entry;
} }
self.cloud.data = entry.data; self.cloud.data = entry.data;
uDom.nodeFromId('cloudPull').removeAttribute('disabled'); dom.attr('#cloudPull', 'disabled', null);
uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled'); dom.attr('#cloudPullAndMerge', 'disabled', null);
const timeOptions = { const timeOptions = {
weekday: 'short', weekday: 'short',
@ -123,11 +124,8 @@ const pushData = async function() {
data: self.cloud.onPush(), data: self.cloud.onPush(),
}); });
const failed = typeof error === 'string'; const failed = typeof error === 'string';
document.getElementById('cloudPush') dom.cl.toggle('#cloudPush', 'error', failed);
.classList dom.text('#cloudError', failed ? error : '');
.toggle('error', failed);
document.querySelector('#cloudError')
.textContent = failed ? error : '';
if ( failed ) { return; } if ( failed ) { return; }
fetchCloudData(); fetchCloudData();
fetchStorageUsed(); fetchStorageUsed();
@ -139,8 +137,8 @@ const pullData = function() {
if ( typeof self.cloud.onPull === 'function' ) { if ( typeof self.cloud.onPull === 'function' ) {
self.cloud.onPull(self.cloud.data, false); self.cloud.onPull(self.cloud.data, false);
} }
document.getElementById('cloudPush').classList.remove('error'); dom.cl.remove('#cloudPush', 'error');
document.querySelector('#cloudError').textContent = ''; dom.text('#cloudError', '');
}; };
/******************************************************************************/ /******************************************************************************/
@ -154,29 +152,29 @@ const pullAndMergeData = function() {
/******************************************************************************/ /******************************************************************************/
const openOptions = function() { const openOptions = function() {
const input = uDom.nodeFromId('cloudDeviceName'); const input = qs$('#cloudDeviceName');
input.value = self.cloud.options.deviceName; input.value = self.cloud.options.deviceName;
input.setAttribute('placeholder', self.cloud.options.defaultDeviceName); dom.attr(input, 'placeholder', self.cloud.options.defaultDeviceName);
uDom.nodeFromId('cloudOptions').classList.add('show'); dom.cl.add('#cloudOptions', 'show');
}; };
/******************************************************************************/ /******************************************************************************/
const closeOptions = function(ev) { const closeOptions = function(ev) {
const root = uDom.nodeFromId('cloudOptions'); const root = qs$('#cloudOptions');
if ( ev.target !== root ) { return; } if ( ev.target !== root ) { return; }
root.classList.remove('show'); dom.cl.remove(root, 'show');
}; };
/******************************************************************************/ /******************************************************************************/
const submitOptions = async function() { const submitOptions = async function() {
uDom.nodeFromId('cloudOptions').classList.remove('show'); dom.cl.remove('#cloudOptions', 'show');
const options = await vAPI.messaging.send('cloudWidget', { const options = await vAPI.messaging.send('cloudWidget', {
what: 'cloudSetOptions', what: 'cloudSetOptions',
options: { options: {
deviceName: uDom.nodeFromId('cloudDeviceName').value deviceName: qs$('#cloudDeviceName').value
}, },
}); });
if ( options instanceof Object ) { if ( options instanceof Object ) {
@ -209,19 +207,19 @@ const onInitialize = function(options) {
faIconsInit(widget); faIconsInit(widget);
i18n.render(widget); i18n.render(widget);
widget.classList.remove('hide'); dom.cl.remove(widget, 'hide');
uDom('#cloudPush').on('click', ( ) => { pushData(); }); dom.on('#cloudPush', 'click', ( ) => { pushData(); });
uDom('#cloudPull').on('click', pullData); dom.on('#cloudPull', 'click', pullData);
uDom('#cloudPullAndMerge').on('click', pullAndMergeData); dom.on('#cloudPullAndMerge', 'click', pullAndMergeData);
uDom('#cloudCog').on('click', openOptions); dom.on('#cloudCog', 'click', openOptions);
uDom('#cloudOptions').on('click', closeOptions); dom.on('#cloudOptions', 'click', closeOptions);
uDom('#cloudOptionsSubmit').on('click', ( ) => { submitOptions(); }); dom.on('#cloudOptionsSubmit', 'click', ( ) => { submitOptions(); });
fetchCloudData().then(result => { fetchCloudData().then(result => {
if ( typeof result !== 'string' ) { return; } if ( typeof result !== 'string' ) { return; }
document.getElementById('cloudPush').classList.add('error'); dom.cl.add('#cloudPush', 'error');
document.querySelector('#cloudError').textContent = result; dom.text('#cloudError', result);
}); });
fetchStorageUsed(); fetchStorageUsed();
}; };

View File

@ -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 // start of local namespace
if ( µb.canUseShortcuts === false ) { return; } if ( vAPI.commands instanceof Object === false ) { return; }
const relaxBlockingMode = (( ) => { const relaxBlockingMode = (( ) => {
const reloadTimers = new Map(); const reloadTimers = new Map();

View File

@ -19,10 +19,10 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
import { dom } from './dom.js';
/******************************************************************************/ /******************************************************************************/
self.uBlockDashboard = self.uBlockDashboard || {}; self.uBlockDashboard = self.uBlockDashboard || {};
@ -196,7 +196,7 @@ self.uBlockDashboard.openOrSelectPage = function(url, options = {}) {
let ev; let ev;
if ( url instanceof MouseEvent ) { if ( url instanceof MouseEvent ) {
ev = url; ev = url;
url = ev.target.getAttribute('href'); url = dom.attr(ev.target, 'href');
} }
const details = Object.assign({ url, select: true, index: -1 }, options); const details = Object.assign({ url, select: true, index: -1 }, options);
vAPI.messaging.send('default', { vAPI.messaging.send('default', {
@ -211,5 +211,5 @@ self.uBlockDashboard.openOrSelectPage = function(url, options = {}) {
/******************************************************************************/ /******************************************************************************/
// Open links in the proper window // Open links in the proper window
uDom('a').attr('target', '_blank'); dom.attr('a', 'target', '_blank');
uDom('a[href*="dashboard.html"]').attr('target', '_parent'); dom.attr('a[href*="dashboard.html"]', 'target', '_parent');

View File

@ -19,14 +19,14 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
const discardUnsavedData = function(synchronous = false) { const discardUnsavedData = function(synchronous = false) {
const paneFrame = document.getElementById('iframe'); const paneFrame = qs$('#iframe');
const paneWindow = paneFrame.contentWindow; const paneWindow = paneFrame.contentWindow;
if ( if (
typeof paneWindow.hasUnsavedData !== 'function' || typeof paneWindow.hasUnsavedData !== 'function' ||
@ -40,13 +40,13 @@ const discardUnsavedData = function(synchronous = false) {
} }
return new Promise(resolve => { return new Promise(resolve => {
const modal = uDom.nodeFromId('unsavedWarning'); const modal = qs$('#unsavedWarning');
modal.classList.add('on'); dom.cl.add(modal, 'on');
modal.focus(); modal.focus();
const onDone = status => { const onDone = status => {
modal.classList.remove('on'); dom.cl.remove(modal, 'on');
document.removeEventListener('click', onClick, true); dom.off(document, 'click', onClick, true);
resolve(status); resolve(status);
}; };
@ -58,27 +58,25 @@ const discardUnsavedData = function(synchronous = false) {
if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) { if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) {
return onDone(true); return onDone(true);
} }
if ( modal.querySelector('[data-i18n="dashboardUnsavedWarning"]').contains(target) ) { if ( qs$(modal, '[data-i18n="dashboardUnsavedWarning"]').contains(target) ) {
return; return;
} }
onDone(false); onDone(false);
}; };
document.addEventListener('click', onClick, true); dom.on(document, 'click', onClick, true);
}); });
}; };
const loadDashboardPanel = function(pane, first) { const loadDashboardPanel = function(pane, first) {
const tabButton = uDom.nodeFromSelector(`[data-pane="${pane}"]`); const tabButton = qs$(`[data-pane="${pane}"]`);
if ( tabButton === null || tabButton.classList.contains('selected') ) { if ( tabButton === null || dom.cl.has(tabButton, 'selected') ) { return; }
return;
}
const loadPane = ( ) => { const loadPane = ( ) => {
self.location.replace(`#${pane}`); self.location.replace(`#${pane}`);
uDom('.tabButton.selected').toggleClass('selected', false); dom.cl.remove('.tabButton.selected', 'selected');
tabButton.classList.add('selected'); dom.cl.add(tabButton, 'selected');
tabButton.scrollIntoView(); tabButton.scrollIntoView();
uDom.nodeFromId('iframe').contentWindow.location.replace(pane); qs$('#iframe').contentWindow.location.replace(pane);
if ( pane !== 'no-dashboard.html' ) { if ( pane !== 'no-dashboard.html' ) {
vAPI.localStorage.setItem('dashboardLastVisitedPane', pane); vAPI.localStorage.setItem('dashboardLastVisitedPane', pane);
} }
@ -88,9 +86,7 @@ const loadDashboardPanel = function(pane, first) {
} }
const r = discardUnsavedData(); const r = discardUnsavedData();
if ( r === false ) { return; } if ( r === false ) { return; }
if ( r === true ) { if ( r === true ) { return loadPane(); }
return loadPane();
}
r.then(status => { r.then(status => {
if ( status === false ) { return; } if ( status === false ) { return; }
loadPane(); loadPane();
@ -98,11 +94,11 @@ const loadDashboardPanel = function(pane, first) {
}; };
const onTabClickHandler = function(ev) { 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' ) { if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
document.body.classList.add('noDashboard'); dom.cl.add(dom.body, 'noDashboard');
} }
(async ( ) => { (async ( ) => {
@ -114,13 +110,9 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
{ {
const details = results[0] || {}; const details = results[0] || {};
document.body.classList.toggle(
'canUpdateShortcuts',
details.canUpdateShortcuts === true
);
if ( details.noDashboard ) { if ( details.noDashboard ) {
self.location.hash = '#no-dashboard.html'; 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' ) { } else if ( self.location.hash === '#no-dashboard.html' ) {
self.location.hash = ''; self.location.hash = '';
} }
@ -133,10 +125,10 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
} }
loadDashboardPanel(pane !== null ? pane : 'settings.html', true); 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 // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
window.addEventListener('beforeunload', ( ) => { dom.on(window, 'beforeunload', ( ) => {
if ( discardUnsavedData(true) ) { return; } if ( discardUnsavedData(true) ) { return; }
event.preventDefault(); event.preventDefault();
event.returnValue = ''; event.returnValue = '';

View File

@ -19,10 +19,12 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global CodeMirror, uDom, uBlockDashboard */ /* global CodeMirror, uBlockDashboard */
'use strict'; 'use strict';
import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
const reFoldable = /^ *(?=\+ \S)/; const reFoldable = /^ *(?=\+ \S)/;
@ -60,9 +62,7 @@ CodeMirror.registerGlobalHelper(
} }
); );
const cmEditor = new CodeMirror( const cmEditor = new CodeMirror(qs$('#console'), {
document.getElementById('console'),
{
autofocus: true, autofocus: true,
foldGutter: true, foldGutter: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
@ -71,8 +71,7 @@ const cmEditor = new CodeMirror(
mode: 'ubo-dump', mode: 'ubo-dump',
styleActiveLine: true, styleActiveLine: true,
undoDepth: 5, undoDepth: 5,
} });
);
uBlockDashboard.patchCodeMirrorEditor(cmEditor); uBlockDashboard.patchCodeMirrorEditor(cmEditor);
@ -84,11 +83,11 @@ function log(text) {
/******************************************************************************/ /******************************************************************************/
uDom.nodeFromId('console-clear').addEventListener('click', ( ) => { dom.on('#console-clear', 'click', ( ) => {
cmEditor.setValue(''); cmEditor.setValue('');
}); });
uDom.nodeFromId('console-fold').addEventListener('click', ( ) => { dom.on('#console-fold', 'click', ( ) => {
const unfolded = []; const unfolded = [];
let maxUnfolded = -1; let maxUnfolded = -1;
cmEditor.eachLine(handle => { cmEditor.eachLine(handle => {
@ -110,7 +109,7 @@ uDom.nodeFromId('console-fold').addEventListener('click', ( ) => {
cmEditor.endOperation(); cmEditor.endOperation();
}); });
uDom.nodeFromId('console-unfold').addEventListener('click', ( ) => { dom.on('#console-unfold', 'click', ( ) => {
const folded = []; const folded = [];
let minFolded = Number.MAX_SAFE_INTEGER; let minFolded = Number.MAX_SAFE_INTEGER;
cmEditor.eachLine(handle => { cmEditor.eachLine(handle => {
@ -132,25 +131,25 @@ uDom.nodeFromId('console-unfold').addEventListener('click', ( ) => {
cmEditor.endOperation(); cmEditor.endOperation();
}); });
uDom.nodeFromId('snfe-dump').addEventListener('click', ev => { dom.on('#snfe-dump', 'click', ev => {
const button = ev.target; const button = ev.target;
button.setAttribute('disabled', ''); dom.attr(button, 'disabled', '');
vAPI.messaging.send('dashboard', { vAPI.messaging.send('dashboard', {
what: 'snfeDump', what: 'snfeDump',
}).then(result => { }).then(result => {
log(result); log(result);
button.removeAttribute('disabled'); dom.attr(button, 'disabled', null);
}); });
}); });
uDom.nodeFromId('snfe-todnr').addEventListener('click', ev => { dom.on('#snfe-todnr', 'click', ev => {
const button = ev.target; const button = ev.target;
button.setAttribute('disabled', ''); dom.attr(button, 'disabled', '');
vAPI.messaging.send('dashboard', { vAPI.messaging.send('dashboard', {
what: 'snfeToDNR', what: 'snfeToDNR',
}).then(result => { }).then(result => {
log(result); log(result);
button.removeAttribute('disabled'); dom.attr(button, 'disabled', null);
}); });
}); });
@ -158,27 +157,27 @@ vAPI.messaging.send('dashboard', {
what: 'getAppData', what: 'getAppData',
}).then(appData => { }).then(appData => {
if ( appData.canBenchmark !== true ) { return; } if ( appData.canBenchmark !== true ) { return; }
uDom.nodeFromId('snfe-benchmark').removeAttribute('disabled'); dom.attr('#snfe-benchmark', 'disabled', null);
uDom.nodeFromId('snfe-benchmark').addEventListener('click', ev => { dom.on('#snfe-benchmark', 'click', ev => {
const button = ev.target; const button = ev.target;
button.setAttribute('disabled', ''); dom.attr(button, 'disabled', '');
vAPI.messaging.send('dashboard', { vAPI.messaging.send('dashboard', {
what: 'snfeBenchmark', what: 'snfeBenchmark',
}).then(result => { }).then(result => {
log(result); log(result);
button.removeAttribute('disabled'); dom.attr(button, 'disabled', null);
}); });
}); });
}); });
uDom.nodeFromId('cfe-dump').addEventListener('click', ev => { dom.on('#cfe-dump', 'click', ev => {
const button = ev.target; const button = ev.target;
button.setAttribute('disabled', ''); dom.attr(button, 'disabled', '');
vAPI.messaging.send('dashboard', { vAPI.messaging.send('dashboard', {
what: 'cfeDump', what: 'cfeDump',
}).then(result => { }).then(result => {
log(result); log(result);
button.removeAttribute('disabled'); dom.attr(button, 'disabled', null);
}); });
}); });

View File

@ -19,13 +19,10 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
/******************************************************************************/
import { i18n$ } from './i18n.js'; 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; } 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 ) { for ( const list of lists ) {
const listElem = document.querySelector('#templates .filterList') const listElem = dom.clone('#templates .filterList');
.cloneNode(true); const sourceElem = qs$(listElem, '.filterListSource');
const sourceElem = listElem.querySelector('.filterListSource');
sourceElem.href += encodeURIComponent(list.assetKey); sourceElem.href += encodeURIComponent(list.assetKey);
sourceElem.textContent = list.title; dom.text(sourceElem, list.title);
if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) { if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) {
const supportElem = listElem.querySelector('.filterListSupport'); const supportElem = qs$(listElem, '.filterListSupport');
supportElem.setAttribute('href', list.supportURL); dom.attr(supportElem, 'href', list.supportURL);
supportElem.classList.remove('hidden'); dom.cl.remove(supportElem, 'hidden');
} }
parent.appendChild(listElem); 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; dom.text('#theURL > p > span:first-of-type', details.url);
uDom.nodeFromId('why').textContent = details.fs; dom.text('#why', details.fs);
/******************************************************************************/ /******************************************************************************/
@ -95,20 +91,21 @@ uDom.nodeFromId('why').textContent = details.fs;
value = name; value = name;
name = ''; name = '';
} }
const li = document.createElement('li'); const li = dom.create('li');
let span = document.createElement('span'); let span = dom.create('span');
span.textContent = name; dom.text(span, name);
li.appendChild(span); li.appendChild(span);
if ( name !== '' && value !== '' ) { if ( name !== '' && value !== '' ) {
li.appendChild(document.createTextNode(' = ')); li.appendChild(document.createTextNode(' = '));
} }
span = document.createElement('span'); span = dom.create('span');
if ( reURL.test(value) ) { if ( reURL.test(value) ) {
const a = document.createElement('a'); const a = dom.create('a');
a.href = a.textContent = value; dom.attr(a, 'href', value);
dom.text(a, value);
span.appendChild(a); span.appendChild(a);
} else { } else {
span.textContent = value; dom.text(span, value);
} }
li.appendChild(span); li.appendChild(span);
return li; return li;
@ -135,7 +132,7 @@ uDom.nodeFromId('why').textContent = details.fs;
for ( const [ name, value ] of params ) { for ( const [ name, value ] of params ) {
const li = liFromParam(name, value); const li = liFromParam(name, value);
if ( depth < 2 && reURL.test(value) ) { if ( depth < 2 && reURL.test(value) ) {
const ul = document.createElement('ul'); const ul = dom.create('ul');
renderParams(ul, value, depth + 1); renderParams(ul, value, depth + 1);
li.appendChild(ul); li.appendChild(ul);
} }
@ -145,27 +142,22 @@ uDom.nodeFromId('why').textContent = details.fs;
return true; return true;
}; };
if ( renderParams(uDom.nodeFromId('parsed'), details.url) === false ) { if ( renderParams(qs$('#parsed'), details.url) === false ) {
return; return;
} }
const toggler = document.querySelector('#toggleParse'); dom.cl.remove('#toggleParse', 'hidden');
toggler.classList.remove('hidden');
toggler.addEventListener('click', ( ) => { dom.on('#toggleParse', 'click', ( ) => {
const cl = uDom.nodeFromId('theURL').classList; dom.cl.toggle('#theURL', 'collapsed');
cl.toggle('collapsed');
vAPI.localStorage.setItem( vAPI.localStorage.setItem(
'document-blocked-expand-url', '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 => { vAPI.localStorage.getItemAsync('document-blocked-expand-url').then(value => {
uDom.nodeFromId('theURL').classList.toggle( dom.cl.toggle('#theURL', 'collapsed', value !== 'true' && value !== true);
'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/ // https://www.reddit.com/r/uBlockOrigin/comments/breeux/close_this_window_doesnt_work_on_firefox/
if ( window.history.length > 1 ) { if ( window.history.length > 1 ) {
uDom('#back').on( dom.on('#back', 'click', ( ) => {
'click',
( ) => {
window.history.back(); window.history.back();
} });
); qs$('#bye').style.display = 'none';
uDom('#bye').css('display', 'none');
} else { } else {
uDom('#bye').on( dom.on('#bye', 'click', ( ) => {
'click',
( ) => {
messaging.send('documentBlocked', { messaging.send('documentBlocked', {
what: 'closeThisTab', what: 'closeThisTab',
}); });
} });
); qs$('#back').style.display = 'none';
uDom('#back').css('display', 'none');
} }
/******************************************************************************/ /******************************************************************************/
@ -223,15 +209,14 @@ const proceedPermanent = async function() {
proceedToURL(); proceedToURL();
}; };
uDom('#disableWarning').on('change', ev => { dom.on('#disableWarning', 'change', ev => {
const checked = ev.target.checked; const checked = ev.target.checked;
document.querySelector('[data-i18n="docblockedBack"]').classList.toggle('disabled', checked); dom.cl.toggle('[data-i18n="docblockedBack"]', 'disabled', checked);
document.querySelector('[data-i18n="docblockedClose"]').classList.toggle('disabled', checked); dom.cl.toggle('[data-i18n="docblockedClose"]', 'disabled', checked);
}); });
uDom('#proceed').on('click', ( ) => { dom.on('#proceed', 'click', ( ) => {
const input = document.querySelector('#disableWarning'); if ( qs$('#disableWarning').checked ) {
if ( input.checked ) {
proceedPermanent(); proceedPermanent();
} else { } else {
proceedTemporary(); proceedTemporary();

View File

@ -26,11 +26,11 @@
/******************************************************************************/ /******************************************************************************/
const normalizeTarget = target => { const normalizeTarget = target => {
if ( typeof target === 'string' ) { return Array.from(qsa$(target)); }
if ( target instanceof Element ) { return [ target ]; }
if ( target === null ) { return []; } if ( target === null ) { return []; }
if ( Array.isArray(target) ) { return target; } if ( Array.isArray(target) ) { return target; }
return target instanceof Element return Array.from(target);
? [ target ]
: Array.from(target);
}; };
const makeEventHandler = (selector, callback) => { 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) { static text(target, text) {
for ( const elem of normalizeTarget(target) ) { for ( const elem of normalizeTarget(target) ) {
elem.textContent = text; elem.textContent = text;
@ -82,15 +92,43 @@ class dom {
} }
} }
static on(target, type, selector, callback) { // target, type, callback, [options]
if ( typeof selector === 'function' ) { // target, type, subtarget, callback, [options]
callback = selector;
selector = undefined; static on(target, type, subtarget, callback, options) {
} else { if ( typeof subtarget === 'function' ) {
callback = makeEventHandler(selector, callback); options = callback;
callback = subtarget;
subtarget = undefined;
if ( typeof options === 'boolean' ) {
options = { capture: true };
} }
for ( const elem of normalizeTarget(target) ) { } else {
elem.addEventListener(type, callback, selector !== undefined); callback = makeEventHandler(subtarget, callback);
if ( options === undefined || typeof options === 'boolean' ) {
options = { capture: true };
} else {
options.capture = true;
}
}
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) { static toggle(target, name, state) {
let r;
for ( const elem of normalizeTarget(target) ) { for ( const elem of normalizeTarget(target) ) {
elem.classList.toggle(name, state); r = elem.classList.toggle(name, state);
} }
return r;
} }
static has(target, name) { 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.html = document.documentElement;
dom.head = document.head; dom.head = document.head;
dom.body = document.body; 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$ }; export { dom, qs$, qsa$ };

View File

@ -19,16 +19,15 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global CodeMirror, diff_match_patch, uDom, uBlockDashboard */ /* global CodeMirror, diff_match_patch, uBlockDashboard */
'use strict'; 'use strict';
/******************************************************************************/
import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js';
import { hostnameFromURI } from './uri-utils.js'; import { hostnameFromURI } from './uri-utils.js';
import { i18n$ } from './i18n.js'; import { i18n$ } from './i18n.js';
import { dom, qs$, qsa$ } from './dom.js';
import './codemirror/ubo-dynamic-filtering.js'; import './codemirror/ubo-dynamic-filtering.js';
@ -37,7 +36,7 @@ import './codemirror/ubo-dynamic-filtering.js';
const hostnameToDomainMap = new Map(); const hostnameToDomainMap = new Map();
const mergeView = new CodeMirror.MergeView( const mergeView = new CodeMirror.MergeView(
document.querySelector('.codeMirrorMergeContainer'), qs$('.codeMirrorMergeContainer'),
{ {
allowEditingOriginals: true, allowEditingOriginals: true,
connect: 'align', connect: 'align',
@ -86,24 +85,23 @@ let isCollapsed = false;
const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])'; 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 + '"])'; const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])';
uDom.nodeFromSelector('.CodeMirror-merge-scrolllock') dom.attr('.CodeMirror-merge-scrolllock', 'title', i18n$('genericMergeViewScrollLock'));
.setAttribute('title', i18n$('genericMergeViewScrollLock'));
const translate = function() { const translate = function() {
let elems = document.querySelectorAll(commitArrowSelector); let elems = qsa$(commitArrowSelector);
for ( const elem of elems ) { 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 ) { for ( const elem of elems ) {
elem.setAttribute('title', i18nRevertStr); dom.attr(elem, 'title', i18nRevertStr);
} }
}; };
const mergeGapObserver = new MutationObserver(translate); const mergeGapObserver = new MutationObserver(translate);
mergeGapObserver.observe( mergeGapObserver.observe(
uDom.nodeFromSelector('.CodeMirror-merge-copybuttons-left'), qs$('.CodeMirror-merge-copybuttons-left'),
{ attributes: true, attributeFilter: [ 'title' ], subtree: true } { attributes: true, attributeFilter: [ 'title' ], subtree: true }
); );
@ -247,7 +245,7 @@ const rulesToDoc = function(clearHistory) {
/******************************************************************************/ /******************************************************************************/
const filterRules = function(key) { const filterRules = function(key) {
const filter = uDom.nodeFromSelector('#ruleFilter input').value; const filter = qs$('#ruleFilter input').value;
const rules = thePanes[key].modified; const rules = thePanes[key].modified;
if ( filter === '' ) { return rules; } if ( filter === '' ) { return rules; }
const out = []; const out = [];
@ -283,7 +281,7 @@ mergeView.options.revertChunk = function(
to, toStart, toEnd to, toStart, toEnd
) { ) {
// https://github.com/gorhill/uBlock/issues/3611 // 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; let tmp = from; from = to; to = tmp;
tmp = fromStart; fromStart = toStart; toStart = tmp; tmp = fromStart; fromStart = toStart; toStart = tmp;
tmp = fromEnd; fromEnd = toEnd; toEnd = tmp; tmp = fromEnd; fromEnd = toEnd; toEnd = tmp;
@ -330,7 +328,7 @@ function handleImportFilePicker() {
/******************************************************************************/ /******************************************************************************/
const startImportFilePicker = 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 // 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 // triggered if the user pick a file, even if it is the same as the last
// one picked. // one picked.
@ -363,7 +361,7 @@ const onFilterChanged = (( ) => {
const process = function() { const process = function() {
timer = undefined; timer = undefined;
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
const filter = uDom.nodeFromSelector('#ruleFilter input').value; const filter = qs$('#ruleFilter input').value;
if ( filter === last ) { return; } if ( filter === last ) { return; }
last = filter; last = filter;
if ( overlay !== null ) { if ( overlay !== null ) {
@ -498,7 +496,7 @@ const onPresentationChanged = (( ) => {
const editPane = thePanes.edit; const editPane = thePanes.edit;
origPane.modified = origPane.original.slice(); origPane.modified = origPane.original.slice();
editPane.modified = editPane.original.slice(); editPane.modified = editPane.original.slice();
const select = document.querySelector('#ruleFilter select'); const select = qs$('#ruleFilter select');
sortType = parseInt(select.value, 10); sortType = parseInt(select.value, 10);
if ( isNaN(sortType) ) { sortType = 1; } if ( isNaN(sortType) ) { sortType = 1; }
{ {
@ -528,7 +526,7 @@ const onTextChanged = (( ) => {
const process = details => { const process = details => {
timer = undefined; timer = undefined;
const diff = document.getElementById('diff'); const diff = qs$('#diff');
let isClean = mergeView.editor().isClean(cleanEditToken); let isClean = mergeView.editor().isClean(cleanEditToken);
if ( if (
details === undefined && details === undefined &&
@ -539,20 +537,17 @@ const onTextChanged = (( ) => {
isClean = true; isClean = true;
} }
const isDirty = mergeView.leftChunks().length !== 0; const isDirty = mergeView.leftChunks().length !== 0;
document.body.classList.toggle('editing', isClean === false); dom.cl.toggle(dom.body, 'editing', isClean === false);
diff.classList.toggle('dirty', isDirty); dom.cl.toggle(diff, 'dirty', isDirty);
uDom('#editSaveButton') dom.cl.toggle('#editSaveButton', 'disabled', isClean);
.toggleClass('disabled', isClean); dom.cl.toggle('#exportButton,#importButton', 'disabled', isClean === false);
uDom('#exportButton,#importButton') dom.cl.toggle('#revertButton,#commitButton', 'disabled', isClean === false || isDirty === false);
.toggleClass('disabled', isClean === false); const input = qs$('#ruleFilter input');
uDom('#revertButton,#commitButton')
.toggleClass('disabled', isClean === false || isDirty === false);
const input = document.querySelector('#ruleFilter input');
if ( isClean ) { if ( isClean ) {
input.removeAttribute('disabled'); dom.attr(input, 'disabled', null);
CodeMirror.commands.save = undefined; CodeMirror.commands.save = undefined;
} else { } else {
input.setAttribute('disabled', ''); dom.attr(input, 'disabled', '');
CodeMirror.commands.save = editSaveHandler; CodeMirror.commands.save = editSaveHandler;
} }
}; };
@ -659,23 +654,25 @@ vAPI.messaging.send('dashboard', {
}); });
// Handle user interaction // Handle user interaction
uDom('#importButton').on('click', startImportFilePicker); dom.on('#importButton', 'click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker); dom.on('#importFilePicker', 'change', handleImportFilePicker);
uDom('#exportButton').on('click', exportUserRulesToFile); dom.on('#exportButton', 'click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertAllHandler); dom.on('#revertButton', 'click', revertAllHandler);
uDom('#commitButton').on('click', commitAllHandler); dom.on('#commitButton', 'click', commitAllHandler);
uDom('#editSaveButton').on('click', editSaveHandler); dom.on('#editSaveButton', 'click', editSaveHandler);
uDom('#ruleFilter input').on('input', onFilterChanged); dom.on('#ruleFilter input', 'input', onFilterChanged);
uDom('#ruleFilter select').on('input', ( ) => { dom.on('#ruleFilter select', 'input', ( ) => {
onPresentationChanged(true); onPresentationChanged(true);
}); });
uDom('#ruleFilter #diffCollapse').on('click', ev => { dom.on('#ruleFilter #diffCollapse', 'click', ev => {
isCollapsed = ev.target.classList.toggle('active'); isCollapsed = dom.cl.toggle(ev.target, 'active');
onPresentationChanged(true); onPresentationChanged(true);
}); });
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs // https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
mergeView.editor().on('updateDiff', ( ) => { onTextChanged(); }); mergeView.editor().on('updateDiff', ( ) => {
onTextChanged();
});
/******************************************************************************/ /******************************************************************************/

View File

@ -23,8 +23,6 @@
'use strict'; 'use strict';
/******************************************************************************/
import './codemirror/ubo-static-filtering.js'; import './codemirror/ubo-static-filtering.js';
import { hostnameFromURI } from './uri-utils.js'; import { hostnameFromURI } from './uri-utils.js';

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. 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 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 it under the terms of the GNU General Public License as published by
@ -19,17 +19,17 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; '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. // Don't bother if the browser is not modern enough.
if ( if (
@ -37,7 +37,7 @@ if (
Map.polyfill || Map.polyfill ||
typeof WeakMap === 'undefined' typeof WeakMap === 'undefined'
) { ) {
showdomButton.classList.add('disabled'); dom.cl.add(showdomButton, 'disabled');
return; return;
} }
@ -48,8 +48,8 @@ var inspectorConnectionId;
var inspectedTabId = 0; var inspectedTabId = 0;
var inspectedURL = ''; var inspectedURL = '';
var inspectedHostname = ''; var inspectedHostname = '';
var inspector = uDom.nodeFromId('domInspector'); var inspector = qs$('#domInspector');
var domTree = uDom.nodeFromId('domTree'); var domTree = qs$('#domTree');
var uidGenerator = 1; var uidGenerator = 1;
var filterToIdMap = new Map(); var filterToIdMap = new Map();
@ -92,8 +92,8 @@ vAPI.MessagingConnection.addListener(function(msg) {
const nodeFromDomEntry = function(entry) { const nodeFromDomEntry = function(entry) {
var node, value; var node, value;
var li = document.createElement('li'); const li = document.createElement('li');
li.setAttribute('id', entry.nid); dom.attr(li, 'id', entry.nid);
// expander/collapser // expander/collapser
li.appendChild(document.createElement('span')); li.appendChild(document.createElement('span'));
// selector // selector
@ -104,24 +104,24 @@ const nodeFromDomEntry = function(entry) {
value = entry.cnt || 0; value = entry.cnt || 0;
node = document.createElement('span'); node = document.createElement('span');
node.textContent = value !== 0 ? value.toLocaleString() : ''; node.textContent = value !== 0 ? value.toLocaleString() : '';
node.setAttribute('data-cnt', value); dom.attr(node, 'data-cnt', value);
li.appendChild(node); li.appendChild(node);
// cosmetic filter // cosmetic filter
if ( entry.filter === undefined ) { if ( entry.filter === undefined ) {
return li; return li;
} }
node = document.createElement('code'); node = document.createElement('code');
node.classList.add('filter'); dom.cl.add(node, 'filter');
value = filterToIdMap.get(entry.filter); value = filterToIdMap.get(entry.filter);
if ( value === undefined ) { if ( value === undefined ) {
value = uidGenerator.toString(); value = uidGenerator.toString();
filterToIdMap.set(entry.filter, value); filterToIdMap.set(entry.filter, value);
uidGenerator += 1; uidGenerator += 1;
} }
node.setAttribute('data-filter-id', value); dom.attr(node, 'data-filter-id', value);
node.textContent = entry.filter; node.textContent = entry.filter;
li.appendChild(node); li.appendChild(node);
li.classList.add('isCosmeticHide'); dom.cl.add(li, 'isCosmeticHide');
return li; return li;
}; };
@ -131,11 +131,11 @@ const appendListItem = function(ul, li) {
ul.appendChild(li); ul.appendChild(li);
// Ancestor nodes of a node which is affected by a cosmetic filter will // Ancestor nodes of a node which is affected by a cosmetic filter will
// be marked as "containing cosmetic filters", for user convenience. // 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 (;;) { for (;;) {
li = li.parentElement.parentElement; li = li.parentElement.parentElement;
if ( li === null ) { break; } 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 ) { if ( entry.lvl > lvl ) {
ul = document.createElement('ul'); ul = document.createElement('ul');
li.appendChild(ul); li.appendChild(ul);
li.classList.add('branch'); dom.cl.add(li, 'branch');
li = nodeFromDomEntry(entry); li = nodeFromDomEntry(entry);
appendListItem(ul, li); appendListItem(ul, li);
lvl = entry.lvl; lvl = entry.lvl;
@ -181,7 +181,7 @@ const renderDOMFull = function(response) {
while ( ul.parentNode !== null ) { while ( ul.parentNode !== null ) {
ul = ul.parentNode; ul = ul.parentNode;
} }
ul.firstElementChild.classList.add('show'); dom.cl.add(ul.firstElementChild, 'show');
domTreeParent.appendChild(domTree); domTreeParent.appendChild(domTree);
}; };
@ -194,8 +194,8 @@ const patchIncremental = function(from, delta) {
var span, cnt; var span, cnt;
var li = from.parentElement.parentElement; var li = from.parentElement.parentElement;
var patchCosmeticHide = delta >= 0 && var patchCosmeticHide = delta >= 0 &&
from.classList.contains('isCosmeticHide') && dom.cl.has(from, 'isCosmeticHide') &&
li.classList.contains('hasCosmeticHide') === false; dom.cl.has(li, 'hasCosmeticHide') === false;
// Include descendants count when removing a node // Include descendants count when removing a node
if ( delta < 0 ) { if ( delta < 0 ) {
delta -= countFromNode(from); delta -= countFromNode(from);
@ -205,10 +205,10 @@ const patchIncremental = function(from, delta) {
if ( delta !== 0 ) { if ( delta !== 0 ) {
cnt = countFromNode(li) + delta; cnt = countFromNode(li) + delta;
span.textContent = cnt !== 0 ? cnt.toLocaleString() : ''; span.textContent = cnt !== 0 ? cnt.toLocaleString() : '';
span.setAttribute('data-cnt', cnt); dom.attr(span, 'data-cnt', cnt);
} }
if ( patchCosmeticHide ) { if ( patchCosmeticHide ) {
li.classList.add('hasCosmeticHide'); dom.cl.add(li, 'hasCosmeticHide');
} }
} }
}; };
@ -226,7 +226,7 @@ const renderDOMIncremental = function(response) {
entry = journal[i]; entry = journal[i];
// Remove node // Remove node
if ( entry.what === -1 ) { if ( entry.what === -1 ) {
li = document.getElementById(entry.nid); li = qs$(`#${entry.nid}`);
if ( li === null ) { continue; } if ( li === null ) { continue; }
patchIncremental(li, -1); patchIncremental(li, -1);
li.parentNode.removeChild(li); li.parentNode.removeChild(li);
@ -239,7 +239,7 @@ const renderDOMIncremental = function(response) {
} }
// Add node as sibling // Add node as sibling
if ( entry.what === 1 && entry.l ) { if ( entry.what === 1 && entry.l ) {
previous = document.getElementById(entry.l); previous = qs$(`#{entry.l}`);
// This should not happen // This should not happen
if ( previous === null ) { if ( previous === null ) {
// throw new Error('No left sibling!?'); // throw new Error('No left sibling!?');
@ -253,17 +253,17 @@ const renderDOMIncremental = function(response) {
} }
// Add node as child // Add node as child
if ( entry.what === 1 && entry.u ) { if ( entry.what === 1 && entry.u ) {
li = document.getElementById(entry.u); li = qs$(`#${entry.u}`);
// This should not happen // This should not happen
if ( li === null ) { if ( li === null ) {
// throw new Error('No parent!?'); // throw new Error('No parent!?');
continue; continue;
} }
ul = li.querySelector('ul'); ul = qs$(li, 'ul');
if ( ul === null ) { if ( ul === null ) {
ul = document.createElement('ul'); ul = document.createElement('ul');
li.appendChild(ul); li.appendChild(ul);
li.classList.add('branch'); dom.cl.add(li, 'branch');
} }
li = nodeFromDomEntry(nodes.get(entry.nid)); li = nodeFromDomEntry(nodes.get(entry.nid));
ul.appendChild(li); ul.appendChild(li);
@ -273,13 +273,11 @@ const renderDOMIncremental = function(response) {
} }
}; };
// https://www.youtube.com/watch?v=6u2KPtJB9h8
/******************************************************************************/ /******************************************************************************/
const countFromNode = function(li) { const countFromNode = function(li) {
var span = li.children[2]; 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; return isNaN(cnt) ? 0 : cnt;
}; };
@ -290,7 +288,7 @@ const selectorFromNode = function(node) {
var code; var code;
while ( node !== null ) { while ( node !== null ) {
if ( node.localName === 'li' ) { if ( node.localName === 'li' ) {
code = node.querySelector('code'); code = qs$(node, 'code');
if ( code !== null ) { if ( code !== null ) {
selector = code.textContent + ' > ' + selector; selector = code.textContent + ' > ' + selector;
if ( selector.indexOf('#') !== -1 ) { if ( selector.indexOf('#') !== -1 ) {
@ -308,7 +306,7 @@ const selectorFromNode = function(node) {
const selectorFromFilter = function(node) { const selectorFromFilter = function(node) {
while ( node !== null ) { while ( node !== null ) {
if ( node.localName === 'li' ) { 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 ) { if ( code !== null ) {
return code.textContent; return code.textContent;
} }
@ -409,10 +407,10 @@ const startDialog = (function() {
const start = function() { const start = function() {
dialog = logger.modalDialog.create('#cosmeticFilteringDialog', stop); dialog = logger.modalDialog.create('#cosmeticFilteringDialog', stop);
textarea = dialog.querySelector('textarea'); textarea = qs$(dialog, 'textarea');
hideSelectors = []; hideSelectors = [];
for ( const node of domTree.querySelectorAll('code.off') ) { for ( const node of qsa$(domTree, 'code.off') ) {
if ( node.classList.contains('filter') ) { continue; } if ( dom.cl.has(node, 'filter') ) { continue; }
hideSelectors.push(selectorFromNode(node)); hideSelectors.push(selectorFromNode(node));
} }
const taValue = []; const taValue = [];
@ -420,8 +418,8 @@ const startDialog = (function() {
taValue.push(inspectedHostname + '##' + selector); taValue.push(inspectedHostname + '##' + selector);
} }
const ids = new Set(); const ids = new Set();
for ( const node of domTree.querySelectorAll('code.filter.off') ) { for ( const node of qsa$(domTree, 'code.filter.off') ) {
const id = node.getAttribute('data-filter-id'); const id = dom.attr(node, 'data-filter-id');
if ( ids.has(id) ) { continue; } if ( ids.has(id) ) { continue; }
ids.add(id); ids.add(id);
unhideSelectors.push(node.textContent); unhideSelectors.push(node.textContent);
@ -465,13 +463,13 @@ const onClicked = function(ev) {
if ( if (
target.localName === 'span' && target.localName === 'span' &&
parent instanceof HTMLLIElement && parent instanceof HTMLLIElement &&
parent.classList.contains('branch') && dom.cl.has(parent, 'branch') &&
target === parent.firstElementChild target === parent.firstElementChild
) { ) {
var state = parent.classList.toggle('show'); const state = dom.cl.toggle(parent, 'show');
if ( !state ) { if ( !state ) {
for ( var node of parent.querySelectorAll('.branch') ) { for ( const node of qsa$(parent, '.branch') ) {
node.classList.remove('show'); dom.cl.remove(node, 'show');
} }
} }
return; return;
@ -481,18 +479,19 @@ const onClicked = function(ev) {
if ( target.localName !== 'code' ) { return; } if ( target.localName !== 'code' ) { return; }
// Toggle cosmetic filter // Toggle cosmetic filter
if ( target.classList.contains('filter') ) { if ( dom.cl.has(target, 'filter') ) {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'toggleFilter', what: 'toggleFilter',
original: false, original: false,
target: target.classList.toggle('off'), target: dom.cl.toggle(target, 'off'),
selector: selectorFromNode(target), selector: selectorFromNode(target),
filter: selectorFromFilter(target), filter: selectorFromFilter(target),
nid: nidFromNode(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', 'off',
target.classList.contains('off') dom.cl.has(target, 'off')
); );
} }
// Toggle node // Toggle node
@ -500,15 +499,15 @@ const onClicked = function(ev) {
vAPI.MessagingConnection.sendTo(inspectorConnectionId, { vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'toggleNodes', what: 'toggleNodes',
original: true, original: true,
target: target.classList.toggle('off') === false, target: dom.cl.toggle(target, 'off') === false,
selector: selectorFromNode(target), selector: selectorFromNode(target),
nid: nidFromNode(target) nid: nidFromNode(target)
}); });
} }
var cantCreate = domTree.querySelector('.off') === null; const cantCreate = qs$(domTree, '.off') === null;
inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate); dom.cl.toggle(qs$(inspector, '.permatoolbar .revert'), 'disabled', cantCreate);
inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate); dom.cl.toggle(qs$(inspector, '.permatoolbar .commit'), 'disabled', cantCreate);
}; };
/******************************************************************************/ /******************************************************************************/
@ -548,7 +547,7 @@ const onMouseOver = (function() {
/******************************************************************************/ /******************************************************************************/
const currentTabId = function() { const currentTabId = function() {
if ( showdomButton.classList.contains('active') === false ) { return 0; } if ( dom.cl.has(showdomButton, 'active') === false ) { return 0; }
return logger.tabIdFromPageSelector(); return logger.tabIdFromPageSelector();
}; };
@ -573,7 +572,7 @@ const shutdownInspector = function() {
inspectorConnectionId = undefined; inspectorConnectionId = undefined;
} }
logger.removeAllChildren(domTree); logger.removeAllChildren(domTree);
inspector.classList.remove('vExpanded'); dom.cl.remove(inspector, 'vExpanded');
inspectedTabId = 0; inspectedTabId = 0;
}; };
@ -593,76 +592,65 @@ const onTabIdChanged = function() {
/******************************************************************************/ /******************************************************************************/
const toggleVCompactView = function() { const toggleVCompactView = function() {
var state = inspector.classList.toggle('vExpanded'); const state = dom.cl.toggle(inspector, 'vExpanded');
var branches = document.querySelectorAll('#domInspector li.branch'); const branches = qsa$('#domInspector li.branch');
for ( var branch of branches ) { for ( const branch of branches ) {
branch.classList.toggle('show', state); dom.cl.toggle(branch, 'show', state);
} }
}; };
const toggleHCompactView = function() { 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() { const revert = function() {
uDom('#domTree .off').removeClass('off'); dom.cl.remove('#domTree .off', 'off');
vAPI.MessagingConnection.sendTo( vAPI.MessagingConnection.sendTo(
inspectorConnectionId, inspectorConnectionId,
{ what: 'resetToggledNodes' } { what: 'resetToggledNodes' }
); );
inspector.querySelector('.permatoolbar .revert').classList.add('disabled'); dom.cl.add(qs$(inspector, '.permatoolbar .revert'), 'disabled');
inspector.querySelector('.permatoolbar .commit').classList.add('disabled'); dom.cl.add(qs$(inspector, '.permatoolbar .commit'), 'disabled');
}; };
/******************************************************************************/ /******************************************************************************/
const toggleOn = function() { const toggleOn = function() {
uDom.nodeFromId('inspectors').classList.add('dom'); dom.cl.add('#inspectors', 'dom');
window.addEventListener('beforeunload', toggleOff); window.addEventListener('beforeunload', toggleOff);
document.addEventListener('tabIdChanged', onTabIdChanged); document.addEventListener('tabIdChanged', onTabIdChanged);
domTree.addEventListener('click', onClicked, true); domTree.addEventListener('click', onClicked, true);
domTree.addEventListener('mouseover', onMouseOver, true); domTree.addEventListener('mouseover', onMouseOver, true);
uDom.nodeFromSelector('#domInspector .vCompactToggler').addEventListener('click', toggleVCompactView); dom.on('#domInspector .vCompactToggler', 'click', toggleVCompactView);
uDom.nodeFromSelector('#domInspector .hCompactToggler').addEventListener('click', toggleHCompactView); dom.on('#domInspector .hCompactToggler', 'click', toggleHCompactView);
//uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode); dom.on('#domInspector .permatoolbar .revert', 'click', revert);
uDom.nodeFromSelector('#domInspector .permatoolbar .revert').addEventListener('click', revert); dom.on('#domInspector .permatoolbar .commit', 'click', startDialog);
uDom.nodeFromSelector('#domInspector .permatoolbar .commit').addEventListener('click', startDialog);
injectInspector(); injectInspector();
}; };
/******************************************************************************/ /******************************************************************************/
const toggleOff = function() { const toggleOff = function() {
showdomButton.classList.remove('active'); dom.cl.remove(showdomButton, 'active');
uDom.nodeFromId('inspectors').classList.remove('dom'); dom.cl.remove('#inspectors', 'dom');
shutdownInspector(); shutdownInspector();
window.removeEventListener('beforeunload', toggleOff); window.removeEventListener('beforeunload', toggleOff);
document.removeEventListener('tabIdChanged', onTabIdChanged); document.removeEventListener('tabIdChanged', onTabIdChanged);
domTree.removeEventListener('click', onClicked, true); domTree.removeEventListener('click', onClicked, true);
domTree.removeEventListener('mouseover', onMouseOver, true); domTree.removeEventListener('mouseover', onMouseOver, true);
uDom.nodeFromSelector('#domInspector .vCompactToggler').removeEventListener('click', toggleVCompactView); dom.off('#domInspector .vCompactToggler', 'click', toggleVCompactView);
uDom.nodeFromSelector('#domInspector .hCompactToggler').removeEventListener('click', toggleHCompactView); dom.off('#domInspector .hCompactToggler', 'click', toggleHCompactView);
//uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode); dom.off('#domInspector .permatoolbar .revert', 'click', revert);
uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert); dom.off('#domInspector .permatoolbar .commit', 'click', startDialog);
uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog);
inspectedTabId = 0; inspectedTabId = 0;
}; };
/******************************************************************************/ /******************************************************************************/
const toggle = function() { const toggle = function() {
if ( showdomButton.classList.toggle('active') ) { if ( dom.cl.toggle(showdomButton, 'active') ) {
toggleOn(); toggleOn();
} else { } else {
toggleOff(); toggleOff();
@ -670,9 +658,7 @@ const toggle = function() {
logger.resize(); logger.resize();
}; };
/******************************************************************************/ dom.on(showdomButton, 'click', toggle);
showdomButton.addEventListener('click', toggle);
/******************************************************************************/ /******************************************************************************/

File diff suppressed because it is too large Load Diff

View File

@ -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 // Support
const getSupportData = async function() { const getSupportData = async function() {
const diffArrays = function(modified, original) { const diffArrays = function(modified, original) {
@ -1506,9 +1472,6 @@ const onMessage = function(request, sender, callback) {
callback(localData); callback(localData);
}); });
case 'getShortcuts':
return getShortcuts(callback);
case 'getSupportData': { case 'getSupportData': {
getSupportData().then(response => { getSupportData().then(response => {
callback(response); callback(response);
@ -1536,7 +1499,6 @@ const onMessage = function(request, sender, callback) {
switch ( request.what ) { switch ( request.what ) {
case 'dashboardConfig': case 'dashboardConfig':
response = { response = {
canUpdateShortcuts: µb.canUpdateShortcuts,
noDashboard: µb.noDashboard, noDashboard: µb.noDashboard,
}; };
break; break;
@ -1597,10 +1559,6 @@ const onMessage = function(request, sender, callback) {
resetUserData(); resetUserData();
break; break;
case 'setShortcut':
setShortcut(request);
break;
case 'writeHiddenSettings': case 'writeHiddenSettings':
µb.changeHiddenSettings(µb.hiddenSettingsFromString(request.content)); µb.changeHiddenSettings(µb.hiddenSettingsFromString(request.content));
break; break;

View File

@ -19,12 +19,11 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
import punycode from '../lib/punycode.js'; import punycode from '../lib/punycode.js';
import { i18n$ } from './i18n.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) { const hashFromPopupData = function(reset = false) {
// It makes no sense to offer to refresh the behind-the-scene scope // It makes no sense to offer to refresh the behind-the-scene scope
if ( popupData.pageHostname === 'behind-the-scene' ) { if ( popupData.pageHostname === 'behind-the-scene' ) {
document.body.classList.remove('needReload'); dom.cl.remove(dom.body, 'needReload');
return; return;
} }
@ -122,17 +121,19 @@ const hashFromPopupData = function(reset = false) {
hasher.push(rule); hasher.push(rule);
} }
hasher.sort(); hasher.sort();
hasher.push(uDom('body').hasClass('off')); hasher.push(
hasher.push(uDom.nodeFromId('no-large-media').classList.contains('on')); dom.cl.has('body', 'off'),
hasher.push(uDom.nodeFromId('no-cosmetic-filtering').classList.contains('on')); dom.cl.has('#no-large-media', 'on'),
hasher.push(uDom.nodeFromId('no-remote-fonts').classList.contains('on')); dom.cl.has('#no-cosmetic-filtering', 'on'),
hasher.push(uDom.nodeFromId('no-scripting').classList.contains('on')); dom.cl.has('#no-remote-fonts', 'on'),
dom.cl.has('#no-scripting', 'on')
);
const hash = hasher.join(''); const hash = hasher.join('');
if ( reset ) { if ( reset ) {
cachedPopupHash = hash; 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) { const updateFirewallCellCount = function(cells, allowed, blocked) {
for ( const cell of cells ) { for ( const cell of cells ) {
if ( gtz(allowed) ) { if ( gtz(allowed) ) {
cell.setAttribute( dom.attr(cell, 'data-acount',
'data-acount',
Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3) Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
); );
} else { } else {
cell.setAttribute('data-acount', '0'); dom.attr(cell, 'data-acount', '0');
} }
if ( gtz(blocked) ) { if ( gtz(blocked) ) {
cell.setAttribute( dom.attr(cell, 'data-bcount',
'data-bcount',
Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3) Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
); );
} else { } 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 ) { for ( const cell of cells ) {
if ( ruleParts === undefined ) { if ( ruleParts === undefined ) {
cell.removeAttribute('class'); dom.attr(cell, 'class', null);
continue; continue;
} }
const action = updateFirewallCellRule.actionNames[ruleParts[3]]; 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. // Use dark shade visual cue if the rule is specific to the cell.
if ( if (
@ -238,7 +237,7 @@ const updateFirewallCellRule = function(cells, scope, des, type, rule) {
(ruleParts[0] === scopeToSrcHostnameMap[scope]) (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 updateAllFirewallCells = function(doRules = true, doCounts = true) {
const { pageDomain } = popupData; const { pageDomain } = popupData;
const rowContainer = document.getElementById('firewall'); const rowContainer = qs$('#firewall');
const rows = rowContainer.querySelectorAll('#firewall > [data-des][data-type]'); const rows = qsa$(rowContainer, '#firewall > [data-des][data-type]');
let a1pScript = 0, b1pScript = 0; let a1pScript = 0, b1pScript = 0;
let a3pScript = 0, b3pScript = 0; let a3pScript = 0, b3pScript = 0;
let a3pFrame = 0, b3pFrame = 0; let a3pFrame = 0, b3pFrame = 0;
for ( const row of rows ) { for ( const row of rows ) {
const des = row.getAttribute('data-des'); const des = dom.attr(row, 'data-des');
const type = row.getAttribute('data-type'); const type = dom.attr(row, 'data-type');
if ( doRules ) { if ( doRules ) {
updateFirewallCellRule( updateFirewallCellRule(
row.querySelectorAll(`:scope > span[data-src="/"]`), qsa$(row, ':scope > span[data-src="/"]'),
'/', '/',
des, des,
type, type,
popupData.firewallRules[`/ ${des} ${type}`] popupData.firewallRules[`/ ${des} ${type}`]
); );
} }
const cells = row.querySelectorAll(`:scope > span[data-src="."]`); const cells = qsa$(row, ':scope > span[data-src="."]');
if ( doRules ) { if ( doRules ) {
updateFirewallCellRule( updateFirewallCellRule(
cells, cells,
@ -301,17 +300,15 @@ const updateAllFirewallCells = function(doRules = true, doCounts = true) {
if ( doCounts ) { if ( doCounts ) {
const fromType = type => const fromType = type =>
document.querySelectorAll( qsa$(`#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]`);
`#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]`
);
updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript); updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript);
updateFirewallCellCount(fromType('3p-script'), a3pScript, b3pScript); 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); 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() { const buildAllFirewallRows = function() {
// Do this before removing the rows // Do this before removing the rows
if ( dfHotspots === null ) { if ( dfHotspots === null ) {
dfHotspots = uDom.nodeFromId('actionSelector'); dfHotspots = qs$('#actionSelector');
dfHotspots.addEventListener('click', setFirewallRuleHandler); dom.on(dfHotspots, 'click', setFirewallRuleHandler);
} }
dfHotspots.remove(); dfHotspots.remove();
@ -355,23 +352,19 @@ const buildAllFirewallRows = function() {
expandHostnameStats(); expandHostnameStats();
// Update incrementally: reuse existing rows if possible. // Update incrementally: reuse existing rows if possible.
const rowContainer = document.getElementById('firewall'); const rowContainer = qs$('#firewall');
const toAppend = document.createDocumentFragment(); const toAppend = document.createDocumentFragment();
const rowTemplate = document.querySelector( const rowTemplate = qs$('#templates > div[data-des=""][data-type="*"]');
'#templates > div[data-des=""][data-type="*"]'
);
const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData; const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData;
let row = rowContainer.querySelector( let row = qs$(rowContainer, 'div[data-des="*"][data-type="3p-frame"] + div');
'div[data-des="*"][data-type="3p-frame"] + div'
);
for ( const des of allHostnameRows ) { for ( const des of allHostnameRows ) {
if ( row === null ) { if ( row === null ) {
row = rowTemplate.cloneNode(true); row = dom.clone(rowTemplate);
toAppend.appendChild(row); toAppend.appendChild(row);
} }
row.setAttribute('data-des', des); dom.attr(row, 'data-des', des);
const hnDetails = hostnameDict[des] || {}; const hnDetails = hostnameDict[des] || {};
const isDomain = des === hnDetails.domain; const isDomain = des === hnDetails.domain;
@ -381,13 +374,13 @@ const buildAllFirewallRows = function() {
const isPunycoded = prettyDomainName !== des; const isPunycoded = prettyDomainName !== des;
if ( isDomain && row.childElementCount < 4 ) { 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 ) { } else if ( isDomain === false && row.childElementCount === 4 ) {
row.children[3].remove(); row.children[3].remove();
} }
const span = row.querySelector('span:first-of-type'); const span = qs$(row, 'span:first-of-type');
span.querySelector(':scope > span > span').textContent = prettyDomainName; dom.text(qs$(span, ':scope > span > span'), prettyDomainName);
const classList = row.classList; const classList = row.classList;
@ -401,7 +394,7 @@ const buildAllFirewallRows = function() {
) { ) {
desExtra = des; desExtra = des;
} }
span.querySelector('sub').textContent = desExtra; dom.text(qs$(span, 'sub'), desExtra);
classList.toggle('isRootContext', des === pageHostname); classList.toggle('isRootContext', des === pageHostname);
classList.toggle('is3p', hnDetails.domain !== pageDomain); classList.toggle('is3p', hnDetails.domain !== pageDomain);
@ -435,10 +428,9 @@ const buildAllFirewallRows = function() {
} }
if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) { if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) {
uDom('#firewall') dom.on('#firewall', 'click', 'span[data-src]', unsetFirewallRuleHandler);
.on('click', 'span[data-src]', unsetFirewallRuleHandler) dom.on('#firewall', 'mouseenter', 'span[data-src]', mouseenterCellHandler);
.on('mouseenter', '[data-src]', mouseenterCellHandler) dom.on('#firewall', 'mouseleave', 'span[data-src]', mouseleaveCellHandler);
.on('mouseleave', '[data-src]', mouseleaveCellHandler);
dfPaneBuilt = true; dfPaneBuilt = true;
} }
@ -507,32 +499,17 @@ const renderPrivacyExposure = function() {
const summary = domainsHitStr const summary = domainsHitStr
.replace('{{count}}', touchedDomainCount.toLocaleString()) .replace('{{count}}', touchedDomainCount.toLocaleString())
.replace('{{total}}', allDomainCount.toLocaleString()); .replace('{{total}}', allDomainCount.toLocaleString());
uDom.nodeFromSelector( dom.text('[data-i18n^="popupDomainsConnected"] + span', summary);
'[data-i18n^="popupDomainsConnected"] + span'
).textContent = summary;
}; };
/******************************************************************************/ /******************************************************************************/
const updateHnSwitches = function() { const updateHnSwitches = function() {
uDom.nodeFromId('no-popups').classList.toggle( dom.cl.toggle('#no-popups', 'on', popupData.noPopups === true);
'on', popupData.noPopups === true dom.cl.toggle('#no-large-media', 'on', popupData.noLargeMedia === true);
); dom.cl.toggle('#no-cosmetic-filtering', 'on',popupData.noCosmeticFiltering === true);
uDom.nodeFromId('no-large-media').classList.toggle( dom.cl.toggle('#no-remote-fonts', 'on', popupData.noRemoteFonts === true);
'on', popupData.noLargeMedia === true dom.cl.toggle('#no-scripting', 'on', popupData.noScripting === 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
);
}; };
/******************************************************************************/ /******************************************************************************/
@ -546,26 +523,28 @@ const renderPopup = function() {
const isFiltering = popupData.netFilteringSwitch; const isFiltering = popupData.netFilteringSwitch;
const body = document.body; dom.cl.toggle(dom.body, 'advancedUser', popupData.advancedUserEnabled === true);
body.classList.toggle('advancedUser', popupData.advancedUserEnabled === true); dom.cl.toggle(dom.body, 'off', popupData.pageURL === '' || isFiltering !== true);
body.classList.toggle('off', popupData.pageURL === '' || isFiltering !== true); dom.cl.toggle(dom.body, 'needSave', popupData.matrixIsDirty === true);
body.classList.toggle('needSave', popupData.matrixIsDirty === true);
// The hostname information below the power switch // The hostname information below the power switch
{ {
const [ elemHn, elemDn ] = uDom.nodeFromId('hostname').children; const [ elemHn, elemDn ] = qs$('#hostname').children;
const { pageDomain, pageHostname } = popupData; const { pageDomain, pageHostname } = popupData;
if ( pageDomain !== '' ) { if ( pageDomain !== '' ) {
elemDn.textContent = safePunycodeToUnicode(pageDomain); dom.text(elemDn, safePunycodeToUnicode(pageDomain));
elemHn.textContent = pageHostname !== pageDomain dom.text(elemHn, pageHostname !== pageDomain
? safePunycodeToUnicode(pageHostname.slice(0, -pageDomain.length - 1)) + '.' ? safePunycodeToUnicode(pageHostname.slice(0, -pageDomain.length - 1)) + '.'
: ''; : ''
);
} else { } else {
elemHn.textContent = elemDn.textContent = ''; dom.text(elemDn, '');
dom.text(elemHn, '');
} }
} }
uDom.nodeFromId('basicTools').classList.toggle( dom.cl.toggle(
'#basicTools',
'canPick', 'canPick',
popupData.canElementPicker === true && isFiltering popupData.canElementPicker === true && isFiltering
); );
@ -586,7 +565,7 @@ const renderPopup = function() {
text = statsStr.replace('{{count}}', formatNumber(blocked)) text = statsStr.replace('{{count}}', formatNumber(blocked))
.replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); .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; blocked = popupData.globalBlockedRequestCount;
total = popupData.globalAllowedRequestCount + blocked; total = popupData.globalAllowedRequestCount + blocked;
@ -596,7 +575,7 @@ const renderPopup = function() {
text = statsStr.replace('{{count}}', formatNumber(blocked)) text = statsStr.replace('{{count}}', formatNumber(blocked))
.replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); .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 // This will collate all domains, touched or not
renderPrivacyExposure(); renderPrivacyExposure();
@ -606,24 +585,27 @@ const renderPopup = function() {
// Report popup count on badge // Report popup count on badge
total = popupData.popupBlockedCount; total = popupData.popupBlockedCount;
uDom.nodeFromSelector('#no-popups .fa-icon-badge') dom.text(
.textContent = total ? Math.min(total, 99).toLocaleString() : ''; '#no-popups .fa-icon-badge',
total ? Math.min(total, 99).toLocaleString() : ''
);
// Report large media count on badge // Report large media count on badge
total = popupData.largeMediaCount; total = popupData.largeMediaCount;
uDom.nodeFromSelector('#no-large-media .fa-icon-badge') dom.text(
.textContent = total ? Math.min(total, 99).toLocaleString() : ''; '#no-large-media .fa-icon-badge',
total ? Math.min(total, 99).toLocaleString() : ''
);
// Report remote font count on badge // Report remote font count on badge
total = popupData.remoteFontCount; total = popupData.remoteFontCount;
uDom.nodeFromSelector('#no-remote-fonts .fa-icon-badge') dom.text(
.textContent = total ? Math.min(total, 99).toLocaleString() : ''; '#no-remote-fonts .fa-icon-badge',
total ? Math.min(total, 99).toLocaleString() : ''
document.documentElement.classList.toggle(
'colorBlind',
popupData.colorBlindFriendly === true
); );
dom.cl.toggle(dom.html, 'colorBlind', popupData.colorBlindFriendly === true);
setGlobalExpand(popupData.firewallPaneMinimized === false, true); setGlobalExpand(popupData.firewallPaneMinimized === false, true);
// Build dynamic filtering pane only if in use // Build dynamic filtering pane only if in use
@ -642,14 +624,14 @@ const renderPopup = function() {
const renderTooltips = function(selector) { const renderTooltips = function(selector) {
for ( const [ key, details ] of tooltipTargetSelectors ) { for ( const [ key, details ] of tooltipTargetSelectors ) {
if ( selector !== undefined && key !== selector ) { continue; } if ( selector !== undefined && key !== selector ) { continue; }
const elem = uDom.nodeFromSelector(key); const elem = qs$(key);
if ( elem.hasAttribute('title') === false ) { continue; } if ( elem.hasAttribute('title') === false ) { continue; }
const text = i18n$( const text = i18n$(
details.i18n + details.i18n +
(uDom.nodeFromSelector(details.state) === null ? '1' : '2') (qs$(details.state) === null ? '1' : '2')
); );
elem.setAttribute('aria-label', text); dom.attr(elem, 'aria-label', text);
elem.setAttribute('title', text); dom.attr(elem, 'title', text);
} }
}; };
@ -705,47 +687,45 @@ const tooltipTargetSelectors = new Map([
let renderOnce = function() { let renderOnce = function() {
renderOnce = function(){}; renderOnce = function(){};
const body = document.body;
if ( popupData.fontSize !== popupFontSize ) { if ( popupData.fontSize !== popupFontSize ) {
popupFontSize = popupData.fontSize; popupFontSize = popupData.fontSize;
if ( popupFontSize !== 'unset' ) { if ( popupFontSize !== 'unset' ) {
body.style.setProperty('--font-size', popupFontSize); dom.body.style.setProperty('--font-size', popupFontSize);
vAPI.localStorage.setItem('popupFontSize', popupFontSize); vAPI.localStorage.setItem('popupFontSize', popupFontSize);
} else { } else {
body.style.removeProperty('--font-size'); dom.body.style.removeProperty('--font-size');
vAPI.localStorage.removeItem('popupFontSize'); vAPI.localStorage.removeItem('popupFontSize');
} }
} }
uDom.nodeFromId('version').textContent = popupData.appVersion; dom.text('#version', popupData.appVersion);
sectionBitsToAttribute(computedSections()); sectionBitsToAttribute(computedSections());
if ( popupData.uiPopupConfig !== undefined ) { 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 ) { if ( popupData.tooltipsDisabled === true ) {
uDom('[title]').removeAttr('title'); dom.attr('[title]', 'title', null);
} }
// https://github.com/uBlockOrigin/uBlock-issues/issues/22 // https://github.com/uBlockOrigin/uBlock-issues/issues/22
if ( popupData.advancedUserEnabled !== true ) { 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 // This must be done the firewall is populated
if ( popupData.popupPanelHeightMode === 1 ) { 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 // Prevent non-advanced user opting into advanced user mode from harming
// themselves by disabling by default features generally suitable to // themselves by disabling by default features generally suitable to
// filter list maintainers and actual advanced users. // filter list maintainers and actual advanced users.
if ( popupData.godMode ) { 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 // Launch potentially expensive hidden elements-counting scriptlet on
// demand only. // demand only.
{ {
const sw = uDom.nodeFromId('no-cosmetic-filtering'); const sw = qs$('#no-cosmetic-filtering');
const badge = sw.querySelector(':scope .fa-icon-badge'); const badge = qs$(sw, ':scope .fa-icon-badge');
badge.textContent = '\u22EF'; dom.text(badge, '\u22EF');
const render = ( ) => { const render = ( ) => {
if ( mustRenderCosmeticFilteringBadge === false ) { return; } if ( mustRenderCosmeticFilteringBadge === false ) { return; }
mustRenderCosmeticFilteringBadge = false; mustRenderCosmeticFilteringBadge = false;
if ( sw.classList.contains('hnSwitchBusy') ) { return; } if ( dom.cl.has(sw, 'hnSwitchBusy') ) { return; }
sw.classList.add('hnSwitchBusy'); dom.cl.add(sw, 'hnSwitchBusy');
messaging.send('popupPanel', { messaging.send('popupPanel', {
what: 'getHiddenElementCount', what: 'getHiddenElementCount',
tabId: popupData.tabId, tabId: popupData.tabId,
@ -779,12 +759,12 @@ const renderPopupLazy = (( ) => {
} else { } else {
text = Math.min(count, 99).toLocaleString(); text = Math.min(count, 99).toLocaleString();
} }
badge.textContent = text; dom.text(badge, text);
sw.classList.remove('hnSwitchBusy'); dom.cl.remove(sw, 'hnSwitchBusy');
}); });
}; };
sw.addEventListener('mouseenter', render, { passive: true }); dom.on(sw, 'mouseenter', render, { passive: true });
} }
return async function() { return async function() {
@ -792,10 +772,10 @@ const renderPopupLazy = (( ) => {
what: 'getScriptCount', what: 'getScriptCount',
tabId: popupData.tabId, tabId: popupData.tabId,
}); });
uDom.nodeFromSelector('#no-scripting .fa-icon-badge') dom.text(
.textContent = (count || 0) !== 0 '#no-scripting .fa-icon-badge',
? Math.min(count, 99).toLocaleString() (count || 0) !== 0 ? Math.min(count, 99).toLocaleString() : ''
: ''; );
mustRenderCosmeticFilteringBadge = true; mustRenderCosmeticFilteringBadge = true;
}; };
})(); })();
@ -808,7 +788,7 @@ const toggleNetFilteringSwitch = function(ev) {
what: 'toggleNetFiltering', what: 'toggleNetFiltering',
url: popupData.pageURL, url: popupData.pageURL,
scope: ev.ctrlKey || ev.metaKey ? 'page' : '', scope: ev.ctrlKey || ev.metaKey ? 'page' : '',
state: !uDom('body').toggleClass('off').hasClass('off'), state: dom.cl.toggle(dom.body, 'off') === false,
tabId: popupData.tabId, tabId: popupData.tabId,
}); });
renderTooltips('#switch'); renderTooltips('#switch');
@ -892,7 +872,7 @@ const gotoURL = function(ev) {
ev.preventDefault(); ev.preventDefault();
let url = this.getAttribute('href'); let url = dom.attr(ev.target, 'href');
if ( if (
url === 'logger-ui.html#_' && url === 'logger-ui.html#_' &&
typeof popupData.tabId === 'number' typeof popupData.tabId === 'number'
@ -984,14 +964,14 @@ const toggleSections = function(more) {
} }
}; };
uDom('#moreButton').on('click', ( ) => { toggleSections(true); }); dom.on('#moreButton', 'click', ( ) => { toggleSections(true); });
uDom('#lessButton').on('click', ( ) => { toggleSections(false); }); dom.on('#lessButton', 'click', ( ) => { toggleSections(false); });
/******************************************************************************/ /******************************************************************************/
const mouseenterCellHandler = function(ev) { const mouseenterCellHandler = function(ev) {
const target = ev.target; const target = ev.target;
if ( target.classList.contains('ownRule') ) { return; } if ( dom.cl.has(target, 'ownRule') ) { return; }
target.appendChild(dfHotspots); target.appendChild(dfHotspots);
}; };
@ -1038,9 +1018,9 @@ const unsetFirewallRuleHandler = function(ev) {
const cell = ev.target; const cell = ev.target;
const row = cell.closest('[data-des]'); const row = cell.closest('[data-des]');
setFirewallRule( setFirewallRule(
cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, dom.attr(cell, 'data-src') === '/' ? '*' : popupData.pageHostname,
row.getAttribute('data-des'), dom.attr(row, 'data-des'),
row.getAttribute('data-type'), dom.attr(row, 'data-type'),
0, 0,
ev.ctrlKey || ev.metaKey ev.ctrlKey || ev.metaKey
); );
@ -1063,9 +1043,9 @@ const setFirewallRuleHandler = function(ev) {
action = 1; action = 1;
} }
setFirewallRule( setFirewallRule(
cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, dom.attr(cell, 'data-src') === '/' ? '*' : popupData.pageHostname,
row.getAttribute('data-des'), dom.attr(row, 'data-des'),
row.getAttribute('data-type'), dom.attr(row, 'data-type'),
action, action,
ev.ctrlKey || ev.metaKey ev.ctrlKey || ev.metaKey
); );
@ -1093,19 +1073,15 @@ const reloadTab = function(ev) {
hashFromPopupData(true); hashFromPopupData(true);
}; };
uDom('#refresh').on('click', reloadTab); dom.on('#refresh', 'click', reloadTab);
// https://github.com/uBlockOrigin/uBlock-issues/issues/672 // https://github.com/uBlockOrigin/uBlock-issues/issues/672
document.addEventListener( dom.on(document, 'keydown', ev => {
'keydown',
ev => {
if ( ev.code !== 'F5' ) { return; } if ( ev.code !== 'F5' ) { return; }
reloadTab(ev); reloadTab(ev);
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
}, }, { capture: true });
{ capture: true }
);
/******************************************************************************/ /******************************************************************************/
@ -1130,11 +1106,11 @@ const saveExpandExceptions = function() {
}; };
const setGlobalExpand = function(state, internal = false) { const setGlobalExpand = function(state, internal = false) {
uDom('.expandException').removeClass('expandException'); dom.cl.remove('.expandException', 'expandException');
if ( state ) { if ( state ) {
uDom('#firewall').addClass('expanded'); dom.cl.add('#firewall', 'expanded');
} else { } else {
uDom('#firewall').removeClass('expanded'); dom.cl.remove('#firewall', 'expanded');
} }
if ( internal ) { return; } if ( internal ) { return; }
popupData.firewallPaneMinimized = !state; popupData.firewallPaneMinimized = !state;
@ -1148,11 +1124,11 @@ const setGlobalExpand = function(state, internal = false) {
}; };
const setSpecificExpand = function(domain, 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 ) { if ( state ) {
unodes.addClass('expandException'); dom.cl.add(elems, 'expandException');
} else { } else {
unodes.removeClass('expandException'); dom.cl.remove(elems, 'expandException');
} }
if ( internal ) { return; } if ( internal ) { return; }
if ( state ) { if ( state ) {
@ -1163,7 +1139,7 @@ const setSpecificExpand = function(domain, state, internal = false) {
saveExpandExceptions(); 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. // Special display mode: in its own tab/window, with no vertical restraint.
// Useful to take snapshots of the whole list of domains -- example: // Useful to take snapshots of the whole list of domains -- example:
// https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944 // https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944
@ -1180,22 +1156,17 @@ uDom('[data-i18n="popupAnyRulePrompt"]').on('click', ev => {
return; return;
} }
setGlobalExpand( setGlobalExpand(dom.cl.has('#firewall', 'expanded') === false);
uDom('#firewall').hasClass('expanded') === false
);
}); });
uDom('#firewall').on( dom.on('#firewall', 'click', '.isDomain[data-type="*"] > span:first-of-type', ev => {
'click', '.isDomain[data-type="*"] > span:first-of-type',
ev => {
const div = ev.target.closest('[data-des]'); const div = ev.target.closest('[data-des]');
if ( div === null ) { return; } if ( div === null ) { return; }
setSpecificExpand( setSpecificExpand(
div.getAttribute('data-des'), dom.attr(div, 'data-des'),
div.classList.contains('expandException') === false dom.cl.has(div, 'expandException') === false
); );
} });
);
/******************************************************************************/ /******************************************************************************/
@ -1205,13 +1176,13 @@ const saveFirewallRules = function() {
srcHostname: popupData.pageHostname, srcHostname: popupData.pageHostname,
desHostnames: popupData.hostnameDict, desHostnames: popupData.hostnameDict,
}); });
document.body.classList.remove('needSave'); dom.cl.remove(dom.body, 'needSave');
}; };
/******************************************************************************/ /******************************************************************************/
const revertFirewallRules = async function() { const revertFirewallRules = async function() {
document.body.classList.remove('needSave'); dom.cl.remove(dom.body, 'needSave');
const response = await messaging.send('popupPanel', { const response = await messaging.send('popupPanel', {
what: 'revertFirewallRules', what: 'revertFirewallRules',
srcHostname: popupData.pageHostname, srcHostname: popupData.pageHostname,
@ -1228,23 +1199,23 @@ const revertFirewallRules = async function() {
const toggleHostnameSwitch = async function(ev) { const toggleHostnameSwitch = async function(ev) {
const target = ev.currentTarget; const target = ev.currentTarget;
const switchName = target.getAttribute('id'); const switchName = dom.attr(target, 'id');
if ( !switchName ) { return; } if ( !switchName ) { return; }
// For touch displays, process click only if the switch is not "busy". // For touch displays, process click only if the switch is not "busy".
if ( if (
vAPI.webextFlavor.soup.has('mobile') && vAPI.webextFlavor.soup.has('mobile') &&
target.classList.contains('hnSwitchBusy') dom.cl.has(target, 'hnSwitchBusy')
) { ) {
return; return;
} }
target.classList.toggle('on'); dom.cl.toggle(target, 'on');
renderTooltips(`#${switchName}`); renderTooltips(`#${switchName}`);
const response = await messaging.send('popupPanel', { const response = await messaging.send('popupPanel', {
what: 'toggleHostnameSwitch', what: 'toggleHostnameSwitch',
name: switchName, name: switchName,
hostname: popupData.pageHostname, hostname: popupData.pageHostname,
state: target.classList.contains('on'), state: dom.cl.has(target, 'on'),
tabId: popupData.tabId, tabId: popupData.tabId,
persist: ev.ctrlKey || ev.metaKey, persist: ev.ctrlKey || ev.metaKey,
}); });
@ -1252,7 +1223,7 @@ const toggleHostnameSwitch = async function(ev) {
cachePopupData(response); cachePopupData(response);
hashFromPopupData(); 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 eventCount = 0;
let eventTime = 0; let eventTime = 0;
document.addEventListener('keydown', ev => { dom.on(document, 'keydown', ev => {
if ( ev.key !== 'Control' ) { if ( ev.key !== 'Control' ) {
eventCount = 0; eventCount = 0;
return; return;
@ -1282,7 +1253,7 @@ const toggleHostnameSwitch = async function(ev) {
eventTime = now; eventTime = now;
if ( eventCount < 2 ) { return; } if ( eventCount < 2 ) { return; }
eventCount = 0; 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 // Use a tolerance proportional to the sum of the width of the panes
// when testing against viewport width. // when testing against viewport width.
const checkViewport = async function() { const checkViewport = async function() {
const root = document.querySelector(':root');
if ( if (
root.classList.contains('mobile') || dom.cl.has(dom.root, 'mobile') ||
selfURL.searchParams.get('portrait') selfURL.searchParams.get('portrait')
) { ) {
root.classList.add('portrait'); dom.cl.add(dom.root, 'portrait');
} else if ( root.classList.contains('desktop') ) { } else if ( dom.cl.has(dom.root, 'desktop') ) {
await nextFrames(4); await nextFrames(4);
const main = document.getElementById('main'); const main = qs$('#main');
const firewall = document.getElementById('firewall'); const firewall = qs$('#firewall');
const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1; const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1;
if ( if (
selfURL.searchParams.get('portrait') || selfURL.searchParams.get('portrait') ||
window.innerWidth < minWidth window.innerWidth < minWidth
) { ) {
root.classList.add('portrait'); dom.cl.add(dom.root, 'portrait');
} }
} }
if ( root.classList.contains('portrait') ) { if ( dom.cl.has(dom.root, 'portrait') ) {
const panes = document.getElementById('panes'); const panes = qs$('#panes');
const sticky = document.getElementById('sticky'); const sticky = qs$('#sticky');
const stickyParent = sticky.parentElement; const stickyParent = sticky.parentElement;
if ( stickyParent !== panes ) { if ( stickyParent !== panes ) {
panes.prepend(sticky); panes.prepend(sticky);
} }
} }
if ( selfURL.searchParams.get('intab') !== null ) { if ( selfURL.searchParams.get('intab') !== null ) {
root.classList.add('intab'); dom.cl.add(dom.root, 'intab');
} }
await nextFrames(1); await nextFrames(1);
document.body.classList.remove('loading'); dom.cl.remove(dom.body, 'loading');
}; };
getPopupData(tabId, true).then(( ) => { getPopupData(tabId, true).then(( ) => {
if ( document.readyState !== 'complete' ) { if ( document.readyState !== 'complete' ) {
self.addEventListener('load', ( ) => { checkViewport(); }, { once: true }); dom.on(self, 'load', ( ) => { checkViewport(); }, { once: true });
} else { } else {
checkViewport(); checkViewport();
} }
@ -1427,27 +1397,25 @@ const getPopupData = async function(tabId, first = false) {
/******************************************************************************/ /******************************************************************************/
uDom('#switch').on('click', toggleNetFilteringSwitch); dom.on('#switch', 'click', toggleNetFilteringSwitch);
uDom('#gotoZap').on('click', gotoZap); dom.on('#gotoZap', 'click', gotoZap);
uDom('#gotoPick').on('click', gotoPick); dom.on('#gotoPick', 'click', gotoPick);
uDom('#gotoReport').on('click', gotoReport); dom.on('#gotoReport', 'click', gotoReport);
uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); }); dom.on('.hnSwitch', 'click', ev => { toggleHostnameSwitch(ev); });
uDom('#saveRules').on('click', saveFirewallRules); dom.on('#saveRules', 'click', saveFirewallRules);
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); }); dom.on('#revertRules', 'click', ( ) => { revertFirewallRules(); });
uDom('a[href]').on('click', gotoURL); dom.on('a[href]', 'click', gotoURL);
/******************************************************************************/ /******************************************************************************/
// Toggle emphasis of rows with[out] 3rd-party scripts/frames // Toggle emphasis of rows with[out] 3rd-party scripts/frames
document.querySelector('#firewall > [data-type="3p-script"] .filter') dom.on('#firewall > [data-type="3p-script"] .filter', 'click', ( ) => {
.addEventListener('click', ( ) => { dom.cl.toggle('#firewall', 'show3pScript');
document.getElementById('firewall').classList.toggle('show3pScript'); });
});
// Toggle visibility of rows with[out] 3rd-party frames // Toggle visibility of rows with[out] 3rd-party frames
document.querySelector('#firewall > [data-type="3p-frame"] .filter') dom.on('#firewall > [data-type="3p-frame"] .filter', 'click', ( ) => {
.addEventListener('click', ( ) => { dom.cl.toggle('#firewall', 'show3pFrame');
document.getElementById('firewall').classList.toggle('show3pFrame'); });
});
/******************************************************************************/ /******************************************************************************/

View File

@ -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) { const highlightElements = function(elems, force) {
// To make mouse move handler more efficient // To make mouse move handler more efficient
if ( if (
@ -554,11 +564,11 @@ const filtersFrom = function(x, y) {
// Network filter candidates from all other elements found at [x,y]. // Network filter candidates from all other elements found at [x,y].
// https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/ // https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/
// Extract network candidates first. // Extract network candidates first.
if ( typeof x === 'number' ) {
const magicAttr = `${vAPI.sessionId}-clickblind`; const magicAttr = `${vAPI.sessionId}-clickblind`;
pickerRoot.setAttribute(magicAttr, ''); pickerRoot.setAttribute(magicAttr, '');
const elems = document.elementsFromPoint(x, y); const elems = elementsFromPoint(document, x, y);
pickerRoot.removeAttribute(magicAttr); pickerRoot.removeAttribute(magicAttr);
if ( typeof x === 'number' ) {
for ( const elem of elems ) { for ( const elem of elems ) {
netFilterFromElement(elem); netFilterFromElement(elem);
} }
@ -570,11 +580,14 @@ const filtersFrom = function(x, y) {
// https://github.com/gorhill/uBlock/issues/2519 // https://github.com/gorhill/uBlock/issues/2519
// https://github.com/uBlockOrigin/uBlock-issues/issues/17 // https://github.com/uBlockOrigin/uBlock-issues/issues/17
// Prepend `body` if full selector is ambiguous. // Prepend `body` if full selector is ambiguous.
let elem = first; for ( const elem of elems ) {
while ( elem && elem !== document.body ) {
cosmeticFilterFromElement(elem); 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 // The body tag is needed as anchor only when the immediate child
// uses `nth-of-type`. // uses `nth-of-type`.
let i = cosmeticFilterCandidates.length; let i = cosmeticFilterCandidates.length;

View File

@ -19,11 +19,11 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global uDom */
'use strict'; 'use strict';
import { i18n$ } from './i18n.js'; 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 startImportFilePicker = function() {
const input = document.getElementById('restoreFilePicker'); const input = qs$('#restoreFilePicker');
// Reset to empty string, this will ensure an change event is properly // 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 // triggered if the user pick a file, even if it is the same as the last
// one picked. // one picked.
@ -134,10 +134,12 @@ const onLocalDataReceived = function(details) {
v = '?'; v = '?';
unit = ''; unit = '';
} }
uDom.nodeFromId('storageUsed').textContent = dom.text(
'#storageUsed',
i18n$('storageUsed') i18n$('storageUsed')
.replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 })) .replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 }))
.replace('{{unit}}', unit && i18n$(unit) || ''); .replace('{{unit}}', unit && i18n$(unit) || '')
);
const timeOptions = { const timeOptions = {
weekday: 'long', weekday: 'long',
@ -153,7 +155,7 @@ const onLocalDataReceived = function(details) {
if ( lastBackupFile !== '' ) { if ( lastBackupFile !== '' ) {
const dt = new Date(details.lastBackupTime); const dt = new Date(details.lastBackupTime);
const text = i18n$('settingsLastBackupPrompt'); const text = i18n$('settingsLastBackupPrompt');
const node = uDom.nodeFromId('settingsLastBackupPrompt'); const node = qs$('#settingsLastBackupPrompt');
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions); node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
node.style.display = ''; node.style.display = '';
} }
@ -162,19 +164,19 @@ const onLocalDataReceived = function(details) {
if ( lastRestoreFile !== '' ) { if ( lastRestoreFile !== '' ) {
const dt = new Date(details.lastRestoreTime); const dt = new Date(details.lastRestoreTime);
const text = i18n$('settingsLastRestorePrompt'); const text = i18n$('settingsLastRestorePrompt');
const node = uDom.nodeFromId('settingsLastRestorePrompt'); const node = qs$('#settingsLastRestorePrompt');
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions); node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
node.style.display = ''; node.style.display = '';
} }
if ( details.cloudStorageSupported === false ) { if ( details.cloudStorageSupported === false ) {
uDom('[data-setting-name="cloudStorageEnabled"]').attr('disabled', ''); dom.attr('[data-setting-name="cloudStorageEnabled"]', 'disabled', '');
} }
if ( details.privacySettingsSupported === false ) { if ( details.privacySettingsSupported === false ) {
uDom('[data-setting-name="prefetchingDisabled"]').attr('disabled', ''); dom.attr('[data-setting-name="prefetchingDisabled"]', 'disabled', '');
uDom('[data-setting-name="hyperlinkAuditingDisabled"]').attr('disabled', ''); dom.attr('[data-setting-name="hyperlinkAuditingDisabled"]', 'disabled', '');
uDom('[data-setting-name="webrtcIPAddressHidden"]').attr('disabled', ''); dom.attr('[data-setting-name="webrtcIPAddressHidden"]', 'disabled', '');
} }
}; };
@ -192,9 +194,10 @@ const resetUserData = function() {
/******************************************************************************/ /******************************************************************************/
const synchronizeDOM = function() { const synchronizeDOM = function() {
document.body.classList.toggle( dom.cl.toggle(
dom.body,
'advancedUser', '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 // Maybe reflect some changes immediately
switch ( name ) { switch ( name ) {
case 'uiTheme': case 'uiTheme':
uDom.setTheme(value, true); setTheme(value, true);
break; break;
case 'uiAccentCustom': case 'uiAccentCustom':
case 'uiAccentCustom0': case 'uiAccentCustom0':
uDom.setAccentColor( setAccentColor(
uDom.nodeFromSelector('[data-setting-name="uiAccentCustom"]').checked, qs$('[data-setting-name="uiAccentCustom"]').checked,
uDom.nodeFromSelector('[data-setting-name="uiAccentCustom0"]').value, qs$('[data-setting-name="uiAccentCustom0"]').value,
true true
); );
break; break;
@ -229,7 +232,7 @@ const changeUserSettings = function(name, value) {
const onValueChanged = function(ev) { const onValueChanged = function(ev) {
const input = ev.target; const input = ev.target;
const name = this.getAttribute('data-setting-name'); const name = dom.attr(input, 'data-setting-name');
let value = input.value; let value = input.value;
// Maybe sanitize value // Maybe sanitize value
switch ( name ) { switch ( name ) {
@ -251,36 +254,36 @@ const onValueChanged = function(ev) {
// TODO: use data-* to declare simple settings // TODO: use data-* to declare simple settings
const onUserSettingsReceived = function(details) { const onUserSettingsReceived = function(details) {
const checkboxes = document.querySelectorAll('[data-setting-type="bool"]'); const checkboxes = qsa$('[data-setting-type="bool"]');
for ( const checkbox of checkboxes ) { for ( const checkbox of checkboxes ) {
const name = checkbox.getAttribute('data-setting-name') || ''; const name = dom.attr(checkbox, 'data-setting-name') || '';
if ( details[name] === undefined ) { if ( details[name] === undefined ) {
checkbox.closest('.checkbox').setAttribute('disabled', ''); dom.attr(checkbox.closest('.checkbox'), 'disabled', '');
checkbox.setAttribute('disabled', ''); dom.attr(checkbox, 'disabled', '');
continue; continue;
} }
checkbox.checked = details[name] === true; checkbox.checked = details[name] === true;
checkbox.addEventListener('change', ( ) => { dom.on(checkbox, 'change', ( ) => {
changeUserSettings(name, checkbox.checked); changeUserSettings(name, checkbox.checked);
synchronizeDOM(); synchronizeDOM();
}); });
} }
if ( details.canLeakLocalIPAddresses === true ) { if ( details.canLeakLocalIPAddresses === true ) {
uDom('[data-setting-name="webrtcIPAddressHidden"]') qs$('[data-setting-name="webrtcIPAddressHidden"]')
.ancestors('div.li') .closest('div.li')
.css('display', ''); .style.display = '';
} }
uDom('[data-setting-type="value"]').forEach(function(uNode) { qsa$('[data-setting-type="value"]').forEach(function(elem) {
uNode.val(details[uNode.attr('data-setting-name')]) elem.value = details[dom.attr(elem, 'data-setting-name')];
.on('change', onValueChanged); dom.on(elem, 'change', onValueChanged);
}); });
uDom('#export').on('click', ( ) => { exportToFile(); }); dom.on('#export', 'click', ( ) => { exportToFile(); });
uDom('#import').on('click', startImportFilePicker); dom.on('#import', 'click', startImportFilePicker);
uDom('#reset').on('click', resetUserData); dom.on('#reset', 'click', resetUserData);
uDom('#restoreFilePicker').on('change', handleImportFilePicker); dom.on('#restoreFilePicker', 'change', handleImportFilePicker);
synchronizeDOM(); synchronizeDOM();
}; };
@ -296,9 +299,8 @@ vAPI.messaging.send('dashboard', { what: 'getLocalData' }).then(result => {
}); });
// https://github.com/uBlockOrigin/uBlock-issues/issues/591 // https://github.com/uBlockOrigin/uBlock-issues/issues/591
document.querySelector( dom.on(
'[data-i18n-title="settingsAdvancedUserSettings"]' '[data-i18n-title="settingsAdvancedUserSettings"]',
).addEventListener(
'click', 'click',
self.uBlockDashboard.openOrSelectPage self.uBlockDashboard.openOrSelectPage
); );

View File

@ -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);
});
})();

View File

@ -19,10 +19,12 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global CodeMirror, uBlockDashboard, uDom */ /* global CodeMirror, uBlockDashboard */
'use strict'; 'use strict';
import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
let supportData; let supportData;
@ -130,10 +132,10 @@ function configToMarkdown(collapse = false) {
} }
function addDetailsToReportURL(id, collapse = false) { function addDetailsToReportURL(id, collapse = false) {
const elem = uDom.nodeFromId(id); const elem = qs$(`#${id}`);
const url = new URL(elem.getAttribute('data-url')); const url = new URL(dom.attr(elem, 'data-url'));
url.searchParams.set('configuration', configToMarkdown(collapse)); url.searchParams.set('configuration', configToMarkdown(collapse));
elem.setAttribute('data-url', url); dom.attr(elem, 'data-url', url);
} }
function showData() { function showData() {
@ -180,21 +182,21 @@ const reportedPage = (( ) => {
parsedURL.username = ''; parsedURL.username = '';
parsedURL.password = ''; parsedURL.password = '';
parsedURL.hash = ''; parsedURL.hash = '';
const select = document.querySelector('select[name="url"]'); const select = qs$('select[name="url"]');
select.options[0].textContent = parsedURL.href; dom.text(select.options[0], parsedURL.href);
if ( parsedURL.search !== '' ) { if ( parsedURL.search !== '' ) {
const option = document.createElement('option'); const option = dom.create('option');
parsedURL.search = ''; parsedURL.search = '';
option.textContent = parsedURL.href; dom.text(option, parsedURL.href);
select.append(option); select.append(option);
} }
if ( parsedURL.pathname !== '/' ) { if ( parsedURL.pathname !== '/' ) {
const option = document.createElement('option'); const option = dom.create('option');
parsedURL.pathname = ''; parsedURL.pathname = '';
option.textContent = parsedURL.href; dom.text(option, parsedURL.href);
select.append(option); select.append(option);
} }
document.body.classList.add('filterIssue'); dom.cl.add(dom.body, 'filterIssue');
return { return {
hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''), hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''),
popupPanel: JSON.parse(url.searchParams.get('popupPanel')), popupPanel: JSON.parse(url.searchParams.get('popupPanel')),
@ -205,21 +207,20 @@ const reportedPage = (( ) => {
})(); })();
function reportSpecificFilterType() { function reportSpecificFilterType() {
return document.querySelector('select[name="type"]').value; return qs$('select[name="type"]').value;
} }
function reportSpecificFilterIssue(ev) { function reportSpecificFilterIssue(ev) {
const githubURL = new URL('https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubo.yml'); const githubURL = new URL('https://github.com/uBlockOrigin/uAssets/issues/new?template=specific_report_from_ubo.yml');
const issueType = reportSpecificFilterType(); const issueType = reportSpecificFilterType();
let title = `${reportedPage.hostname}: ${issueType}`; let title = `${reportedPage.hostname}: ${issueType}`;
if ( document.getElementById('isNSFW').checked ) { if ( qs$('#isNSFW').checked ) {
title = `[nsfw] ${title}`; title = `[nsfw] ${title}`;
} }
githubURL.searchParams.set('title', title); githubURL.searchParams.set('title', title);
githubURL.searchParams.set( githubURL.searchParams.set(
'url_address_of_the_web_page', '`' + 'url_address_of_the_web_page',
document.querySelector('select[name="url"]').value + '`' + qs$('select[name="url"]').value + '`'
'`'
); );
githubURL.searchParams.set('category', issueType); githubURL.searchParams.set('category', issueType);
githubURL.searchParams.set('configuration', configToMarkdown(true)); 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, autofocus: true,
readOnly: true, readOnly: true,
styleActiveLine: true, styleActiveLine: true,
@ -249,9 +250,9 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
showData(); showData();
uDom('[data-url]').on('click', ev => { dom.on('[data-url]', 'click', ev => {
const elem = ev.target.closest('[data-url]'); 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; } if ( typeof url !== 'string' || url === '' ) { return; }
vAPI.messaging.send('default', { vAPI.messaging.send('default', {
what: 'gotoURL', what: 'gotoURL',
@ -261,11 +262,11 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
}); });
if ( reportedPage !== null ) { if ( reportedPage !== null ) {
uDom('[data-i18n="supportReportSpecificButton"]').on('click', ev => { dom.on('[data-i18n="supportReportSpecificButton"]', 'click', ev => {
reportSpecificFilterIssue(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'); const url = new URL('https://github.com/uBlockOrigin/uAssets/issues');
url.searchParams.set('q', `is:issue sort:updated-desc "${reportedPage.hostname}" in:title`); url.searchParams.set('q', `is:issue sort:updated-desc "${reportedPage.hostname}" in:title`);
vAPI.messaging.send('default', { vAPI.messaging.send('default', {
@ -275,15 +276,15 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor);
ev.preventDefault(); ev.preventDefault();
}); });
uDom('#showSupportInfo').on('click', ev => { dom.on('#showSupportInfo', 'click', ev => {
const button = ev.target; const button = ev.target;
button.classList.add('hidden'); dom.cl.add(button, 'hidden');
uDom.nodeFromSelector('.a.b.c.d').classList.add('e'); dom.cl.add('.a.b.c.d', 'e');
cmEditor.refresh(); cmEditor.refresh();
}); });
} }
uDom('#selectAllButton').on('click', ( ) => { dom.on('#selectAllButton', 'click', ( ) => {
cmEditor.focus(); cmEditor.focus();
cmEditor.execCommand('selectAll'); cmEditor.execCommand('selectAll');
}); });

144
src/js/theme.js Normal file
View File

@ -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,
};

View File

@ -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;
})();

View File

@ -19,11 +19,12 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global CodeMirror, uDom, uBlockDashboard */ /* global CodeMirror, uBlockDashboard */
'use strict'; 'use strict';
import { i18n$ } from './i18n.js'; import { i18n$ } from './i18n.js';
import { dom, qs$ } from './dom.js';
/******************************************************************************/ /******************************************************************************/
@ -90,15 +91,12 @@ const noopFunc = function(){};
let cachedWhitelist = ''; let cachedWhitelist = '';
const cmEditor = new CodeMirror( const cmEditor = new CodeMirror(qs$('#whitelist'), {
document.getElementById('whitelist'),
{
autofocus: true, autofocus: true,
lineNumbers: true, lineNumbers: true,
lineWrapping: true, lineWrapping: true,
styleActiveLine: true, styleActiveLine: true,
} });
);
uBlockDashboard.patchCodeMirrorEditor(cmEditor); uBlockDashboard.patchCodeMirrorEditor(cmEditor);
@ -116,12 +114,12 @@ const setEditorText = function(text) {
/******************************************************************************/ /******************************************************************************/
const whitelistChanged = function() { const whitelistChanged = function() {
const whitelistElem = uDom.nodeFromId('whitelist'); const whitelistElem = qs$('#whitelist');
const bad = whitelistElem.querySelector('.cm-error') !== null; const bad = qs$(whitelistElem, '.cm-error') !== null;
const changedWhitelist = getEditorText().trim(); const changedWhitelist = getEditorText().trim();
const changed = changedWhitelist !== cachedWhitelist; const changed = changedWhitelist !== cachedWhitelist;
uDom.nodeFromId('whitelistApply').disabled = !changed || bad; qs$('#whitelistApply').disabled = !changed || bad;
uDom.nodeFromId('whitelistRevert').disabled = !changed; qs$('#whitelistRevert').disabled = !changed;
CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc; CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc;
}; };
@ -188,7 +186,7 @@ const handleImportFilePicker = function() {
/******************************************************************************/ /******************************************************************************/
const startImportFilePicker = 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 // 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 // triggered if the user pick a file, even if it is the same as the last
// one picked. // one picked.
@ -251,11 +249,11 @@ self.hasUnsavedData = function() {
/******************************************************************************/ /******************************************************************************/
uDom('#importWhitelistFromFile').on('click', startImportFilePicker); dom.on('#importWhitelistFromFile', 'click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker); dom.on('#importFilePicker', 'change', handleImportFilePicker);
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile); dom.on('#exportWhitelistToFile', 'click', exportWhitelistToFile);
uDom('#whitelistApply').on('click', ( ) => { applyChanges(); }); dom.on('#whitelistApply', 'click', ( ) => { applyChanges(); });
uDom('#whitelistRevert').on('click', revertChanges); dom.on('#whitelistRevert', 'click', revertChanges);
renderWhitelist(); renderWhitelist();

View File

@ -212,7 +212,7 @@
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/vapi-client-extra.js"></script> <script src="js/vapi-client-extra.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/logger-ui.js" type="module"></script> <script src="js/logger-ui.js" type="module"></script>
<script src="js/logger-ui-inspector.js" type="module"></script> <script src="js/logger-ui-inspector.js" type="module"></script>

View File

@ -20,7 +20,7 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
</body> </body>

View File

@ -101,7 +101,7 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/popup-fenix.js" type="module"></script> <script src="js/popup-fenix.js" type="module"></script>

View File

@ -89,9 +89,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/settings.js" type="module"></script> <script src="js/settings.js" type="module"></script>
</body> </body>

View File

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>uBlock Origin — Keyboard shortcuts</title>
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
<link rel="stylesheet" type="text/css" href="css/shortcuts.css">
</head>
<body>
<div class="body">
<table class="commandEntries"><tbody></tbody></table>
</div>
<div id="templates" style="display: none;">
<table>
<tr class="commandEntry">
<td class="commandDesc">
<td class="commandShortcut">
<input type="text" placeholder="shortcutCapturePlaceholder">
<button class="commandReset vflex" type="button">&times;</button>
</table>
</div>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/shortcuts.js"></script>
</body>
</html>

View File

@ -112,10 +112,10 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/support.js"></script> <script src="js/support.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -20,9 +20,9 @@
<script src="../js/vapi.js"></script> <script src="../js/vapi.js"></script>
<script src="../js/vapi-common.js"></script> <script src="../js/vapi-common.js"></script>
<script src="../js/vapi-client.js"></script> <script src="../js/vapi-client.js"></script>
<script src="../js/i18n.js"></script> <script src="../js/theme.js" type="module"></script>
<script src="../js/udom.js"></script> <script src="../js/i18n.js" type="module"></script>
<script src="../js/click2load.js"></script> <script src="../js/click2load.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -68,7 +68,7 @@
<script src="../js/vapi-common.js"></script> <script src="../js/vapi-common.js"></script>
<script src="../js/vapi-client.js"></script> <script src="../js/vapi-client.js"></script>
<script src="../js/vapi-client-extra.js"></script> <script src="../js/vapi-client-extra.js"></script>
<script src="../js/udom.js"></script> <script src="../js/theme.js" type="module"></script>
<script src="../js/i18n.js" type="module"></script> <script src="../js/i18n.js" type="module"></script>
<script src="../js/epicker-ui.js" type="module"></script> <script src="../js/epicker-ui.js" type="module"></script>

View File

@ -52,9 +52,9 @@
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script> <script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script> <script src="js/cloud-ui.js" type="module"></script>
<script src="js/whitelist.js" type="module"></script> <script src="js/whitelist.js" type="module"></script>

View File

@ -28,6 +28,7 @@ cp src/css/common.css $DES/css/
cp src/css/dashboard-common.css $DES/css/ cp src/css/dashboard-common.css $DES/css/
cp src/css/fa-icons.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/fa-icons.js $DES/js/
cp src/js/i18n.js $DES/js/ cp src/js/i18n.js $DES/js/