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>
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/about.js" type="module"></script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
/**
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2018-present Raymond Hill
Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -18,38 +19,17 @@
Home: https://github.com/gorhill/uBlock
*/
.commandEntries {
margin: 2em;
}
/* jshint esversion:11 */
.commandEntries td {
padding: 0.5em 0.25em;
}
'use strict';
.commandEntries td.commandDesc {
text-align: end;
}
import { dom } from './dom.js';
.commandEntries td.commandShortcut {
white-space: nowrap;
}
/******************************************************************************/
.commandEntries td.commandShortcut input {
padding: 0.4em;
}
.commandEntries td.commandShortcut input:focus {
outline: 2px solid blue;
}
.commandEntries td.commandShortcut input ~ .commandReset {
cursor: pointer;
font-size: 150%;
padding: 0 0.2em;
vertical-align: middle;
}
.commandEntries td.commandShortcut input:placeholder-shown ~ .commandReset,
.commandEntries td.commandShortcut input:focus ~ .commandReset {
display: none;
}
const mql = self.matchMedia('(prefers-color-scheme: dark)');
const theme = mql instanceof Object && mql.matches === true
? 'dark'
: 'light';
dom.cl.toggle(dom.html, 'dark', theme === 'dark');
dom.cl.toggle(dom.html, 'light', theme !== 'dark');

View File

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

View File

@ -100,6 +100,7 @@
</div>
</div>
<script src="js/theme.js" type="module"></script>
<script src="js/fa-icons.js"></script>
<script src="js/i18n.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-client.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/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/1p-filters.js" type="module"></script>

View File

@ -77,9 +77,9 @@
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/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/3p-filters.js" type="module"></script>

View File

@ -52,10 +52,10 @@
<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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/about.js"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/about.js" type="module"></script>
</body>
</html>

View File

@ -33,10 +33,10 @@
<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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/advanced-settings.js"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/advanced-settings.js" type="module"></script>
</body>
</html>

View File

@ -42,9 +42,9 @@
<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/theme.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>
</body>

View File

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

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="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="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="about.html" data-i18n="aboutPageName" tabindex="0"></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-common.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/dashboard.js"></script>
<script src="js/dashboard.js" type="module"></script>
</body>
</html>

View File

@ -48,9 +48,9 @@
<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/theme.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>
</body>

View File

@ -57,7 +57,7 @@
<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/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/document-blocked.js" type="module"></script>
</body>

View File

@ -57,9 +57,9 @@
<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/theme.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/dyna-rules.js" type="module"></script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,11 +26,11 @@
/******************************************************************************/
const normalizeTarget = target => {
if ( typeof target === 'string' ) { return Array.from(qsa$(target)); }
if ( target instanceof Element ) { return [ target ]; }
if ( target === null ) { return []; }
if ( Array.isArray(target) ) { return target; }
return target instanceof Element
? [ target ]
: Array.from(target);
return Array.from(target);
};
const makeEventHandler = (selector, callback) => {
@ -70,6 +70,16 @@ class dom {
}
}
static clone(target) {
return normalizeTarget(target)[0].cloneNode(true);
}
static create(a) {
if ( typeof a === 'string' ) {
return document.createElement(a);
}
}
static text(target, text) {
for ( const elem of normalizeTarget(target) ) {
elem.textContent = text;
@ -82,15 +92,43 @@ class dom {
}
}
static on(target, type, selector, callback) {
if ( typeof selector === 'function' ) {
callback = selector;
selector = undefined;
// target, type, callback, [options]
// target, type, subtarget, callback, [options]
static on(target, type, subtarget, callback, options) {
if ( typeof subtarget === 'function' ) {
options = callback;
callback = subtarget;
subtarget = undefined;
if ( typeof options === 'boolean' ) {
options = { capture: true };
}
} else {
callback = makeEventHandler(selector, callback);
callback = makeEventHandler(subtarget, callback);
if ( options === undefined || typeof options === 'boolean' ) {
options = { capture: true };
} else {
options.capture = true;
}
}
for ( const elem of normalizeTarget(target) ) {
elem.addEventListener(type, callback, selector !== undefined);
const targets = target instanceof Window || target instanceof Document
? [ target ]
: normalizeTarget(target);
for ( const elem of targets ) {
elem.addEventListener(type, callback, options);
}
}
static off(target, type, callback, options) {
if ( typeof callback !== 'function' ) { return; }
if ( typeof options === 'boolean' ) {
options = { capture: true };
}
const targets = target instanceof Window || target instanceof Document
? [ target ]
: normalizeTarget(target);
for ( const elem of targets ) {
elem.removeEventListener(type, callback, options);
}
}
}
@ -109,9 +147,11 @@ dom.cl = class {
}
static toggle(target, name, state) {
let r;
for ( const elem of normalizeTarget(target) ) {
elem.classList.toggle(name, state);
r = elem.classList.toggle(name, state);
}
return r;
}
static has(target, name) {
@ -124,31 +164,27 @@ dom.cl = class {
}
};
/******************************************************************************/
function qs$(a, b) {
if ( typeof a === 'string') {
return document.querySelector(a);
}
return a.querySelector(b);
}
function qsa$(a, b) {
if ( typeof a === 'string') {
return document.querySelectorAll(a);
}
return a.querySelectorAll(b);
}
dom.root = qs$(':root');
dom.html = document.documentElement;
dom.head = document.head;
dom.body = document.body;
/******************************************************************************/
function qs$(s, elem = undefined) {
return (elem || document).querySelector(s);
}
function qsa$(s, elem = undefined) {
return (elem || document).querySelectorAll(s);
}
/******************************************************************************/
{
const mql = self.matchMedia('(prefers-color-scheme: dark)');
const theme = mql instanceof Object && mql.matches === true
? 'dark'
: 'light';
dom.cl.toggle(dom.html, 'dark', theme === 'dark');
dom.cl.toggle(dom.html, 'light', theme !== 'dark');
}
/******************************************************************************/
export { dom, qs$, qsa$ };

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

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

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

View File

@ -212,7 +212,7 @@
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.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/logger-ui.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-common.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>
</body>

View File

@ -101,7 +101,7 @@
<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/theme.js" type="module"></script>
<script src="js/i18n.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-common.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/dashboard-common.js"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/settings.js" type="module"></script>
</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-common.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/dashboard-common.js"></script>
<script src="js/support.js"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/support.js" type="module"></script>
</body>
</html>

View File

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

View File

@ -68,7 +68,7 @@
<script src="../js/vapi-common.js"></script>
<script src="../js/vapi-client.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/epicker-ui.js" type="module"></script>

View File

@ -52,9 +52,9 @@
<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/theme.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/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/fa-icons.css $DES/css/
cp src/js/dom.js $DES/js/
cp src/js/fa-icons.js $DES/js/
cp src/js/i18n.js $DES/js/