Make the new "fenix" popup panel the default one

The old "classic" popup panel will still be used
when at least one of the following is true:

- advanced setting `uiFlavor` is set to `classic`; or
- the browser is Chromium 65 or older; or
- the browser is Firefox 67 or older

The default configuration of the new popup panel
at installation time is to show the power button,
statistics and the basic tool icons, i.e. access
to dashboard, logger, pickers.

For existing installations, the new popup panel
will be configured by respecting the existing
configuration of the classic one.

The new popup panel is currently already in use
on Firefox for Android, and the visual redesign
was made according to suggestions and feedback
from <https://github.com/brampitoyo> to be
optimal for Firefox for Android.

The new popup panel will allow closing the following
pending issues:

- https://github.com/uBlockOrigin/uBlock-issues/issues/255
- https://github.com/uBlockOrigin/uBlock-issues/issues/178
This commit is contained in:
Raymond Hill 2020-04-30 06:54:51 -04:00
parent 557e06f454
commit b295d4a0d0
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
13 changed files with 196 additions and 104 deletions

View file

@ -9,7 +9,7 @@
"32": "img/icon_32.png"
},
"default_title": "uBlock Origin",
"default_popup": "popup.html"
"default_popup": "popup-fenix.html"
},
"commands": {
"launch-element-zapper": {

View file

@ -10,7 +10,7 @@
"32": "img/icon_32.png"
},
"default_title": "uBlock Origin",
"default_popup": "popup.html"
"default_popup": "popup-fenix.html"
},
"browser_specific_settings": {
"gecko": {

View file

@ -8,7 +8,7 @@
"16": "img/icon_16.png",
"32": "img/icon_32.png"
},
"default_popup": "popup.html",
"default_popup": "popup-fenix.html",
"default_title": "uBlock Origin"
},
"commands": {

View file

@ -201,7 +201,11 @@
},
"popupMoreButton_v2":{
"message":"More",
"description":"Label to be used to toggle overview panel"
"description":"Label to be used to show popup panel sections"
},
"popupLessButton_v2":{
"message":"Less",
"description":"Label to be used to hide popup panel sections"
},
"popupTipGlobalRules":{
"message":"Global rules: this column is for rules which apply to all sites.",
@ -259,6 +263,10 @@
"message":"{{count}} out of {{total}}",
"description":"appears in popup"
},
"popupVersion":{
"message":"Version",
"description":"Example of use: Version 1.26.4"
},
"pickerCreate":{
"message":"Create",
"description":"English: Create"

View file

@ -56,11 +56,11 @@ hr {
align-items: stretch;
display: flex;
justify-content: space-between;
margin: var(--popup-default-gap) 0.5em;
margin: 0.5em 0.5em var(--popup-default-gap) 0.5em;
}
#switch {
display: flex;
flex-grow: 2;
flex-grow: 3;
justify-content: center;
}
#switch .fa-icon {
@ -71,10 +71,6 @@ hr {
margin: 0;
padding: 0;
}
#switch .fa-icon:hover {
color: var(--popup-power-ink-hover);
fill: var(--popup-power-ink-hover);
}
body.off #switch .fa-icon {
color: var(--fg-0-20);
fill: var(--fg-0-20);
@ -85,7 +81,7 @@ body.off #switch .fa-icon {
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-grow: 1;
flex-grow: 2;
justify-content: space-evenly;
}
.rulesetTools [id] {
@ -93,7 +89,7 @@ body.off #switch .fa-icon {
border: 1px solid #ddc;
border-radius: 4px;
cursor: pointer;
fill: #888;
fill: var(--default-ink-a50);
flex-grow: 1;
font-size: 2.2em;
padding: 0;
@ -177,9 +173,23 @@ body.mobile.no-tooltips .toolRibbon .tool {
visibility: visible;
}
body:not(.dfEnabled) #moreButton .fa-icon {
.moreOrLess > span {
cursor: pointer;
}
#moreButton .fa-icon {
transform: rotate(180deg);
}
#lessButton {
text-align: right;
}
body[data-more="a b c d e"] #moreButton {
pointer-events: none;
visibility: hidden;
}
body[data-more=""] #lessButton {
pointer-events: none;
visibility: hidden;
}
#tooltip {
background-color: var(--bg-tooltip);
@ -224,9 +234,6 @@ body[dir="rtl"] #tooltip {
text-align: right;
--rule-cell-width: 5em;
}
body:not(.dfEnabled) #firewall {
display: none;
}
#firewall > div {
border: 0;
direction: ltr;
@ -246,11 +253,11 @@ body:not(.dfEnabled) #firewall {
#firewall.expanded > div.isSubDomain.expandException:not(.isRootContext) {
display: none;
}
#firewall > div > span {
#firewall > div > span,
#actionSelector > #dynaCounts {
background-color: var(--bg-popup-cell-2);
border: none;
box-sizing: border-box;
-moz-box-sizing: border-box;
display: inline-flex;
padding: 0.4em 0;
position: relative;
@ -313,7 +320,8 @@ body:not(.dfEnabled) #firewall {
#firewall.expanded > div:not(.expandException) > span:nth-of-type(3),
#firewall:not(.expanded) > div.expandException > span:nth-of-type(3),
#firewall:not(.expanded) > div.isDomain:not(.expandException) > span:nth-of-type(4),
#firewall.expanded > div.isDomain.expandException > span:nth-of-type(4) {
#firewall.expanded > div.isDomain.expandException > span:nth-of-type(4),
#actionSelector > #dynaCounts {
display: inline-flex;
justify-content: space-between;
}
@ -423,15 +431,15 @@ body.advancedUser #firewall > div > span.ownRule,
color: var(--default-surface);
}
body.advancedUser #firewall > div > span.allowRule.ownRule,
#actionSelector > #dynaAllow:hover {
:root.desktop #actionSelector > #dynaAllow:hover {
background-color: var(--bg-popup-cell-allow-own);
}
body.advancedUser #firewall > div > span.blockRule.ownRule,
#actionSelector > #dynaBlock:hover {
:root.desktop #actionSelector > #dynaBlock:hover {
background-color: var(--bg-popup-cell-block-own);
}
body.advancedUser #firewall > div > span.noopRule.ownRule,
#actionSelector > #dynaNoop:hover {
:root.desktop #actionSelector > #dynaNoop:hover {
background-color: var(--bg-popup-cell-noop-own);
}
@ -458,11 +466,8 @@ body.advancedUser #firewall > div > span.noopRule.ownRule,
width: 33.5%;
}
#actionSelector > #dynaCounts {
align-items: center;
background-color: transparent;
display: inline-flex;
height: 100%;
justify-content: space-between;
left: 0;
pointer-events: none;
position: absolute;
@ -488,6 +493,19 @@ body.advancedUser #firewall > div > span.noopRule.ownRule,
:root body[data-ui~="+no-popups"] #no-popups {
display: flex;
}
body:not([data-more~="a"]) [data-more="a"],
body:not([data-more~="b"]) [data-more="b"],
body:not([data-more~="c"]) [data-more="c"],
body:not([data-more~="e"]) [data-more="e"] {
height: 0;
margin-bottom: 0;
margin-top: 0;
overflow-y: hidden;
visibility: hidden;
}
body:not([data-more~="d"]) [data-more="d"] {
display: none;
}
/* mouse-driven devices */
:root.desktop {
@ -505,6 +523,13 @@ body.advancedUser #firewall > div > span.noopRule.ownRule,
max-width: 300px;
width: max-content;
}
:root.desktop #switch .fa-icon:hover {
color: var(--popup-power-ink-hover);
fill: var(--popup-power-ink-hover);
}
:root.desktop .rulesetTools [id]:hover {
fill: var(--default-ink);
}
:root.desktop #firewall {
direction: rtl;
flex-grow: 1;
@ -513,7 +538,7 @@ body.advancedUser #firewall > div > span.noopRule.ownRule,
max-height: max(100vh, 600px);
min-width: 360px;
overflow-y: auto;
width: max-content;
width: min-content;
}
:root.desktop .tool {
padding: 0.5em;
@ -521,3 +546,6 @@ body.advancedUser #firewall > div > span.noopRule.ownRule,
:root.desktop .tool:hover {
background-color: var(--button-surface);
}
:root.desktop .moreOrLess > span:hover {
background-color: var(--button-surface);
}

View file

@ -18,6 +18,7 @@
--ink-50: #291d4f;
--ink-80: #20123a;
--ink-80-a4: #20123a0a;
--ink-80-a50: #20123a88;
--ink-90: #1d1133;
--light-gray-10: #f9f9fb;
--light-gray-30: #e0e0e6;
@ -53,6 +54,7 @@
--default-ink: var(--ink-80);
--default-ink-a4: var(--ink-80-a4);
--default-ink-a50: var(--ink-80-a50);
--default-surface: var(--light-gray-10);
--bg-1: hsla(240, 20%, 98%, 1);

View file

@ -92,6 +92,8 @@ const µBlock = (( ) => { // jshint ignore:line
ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'),
largeMediaSize: 50,
parseAllABPHideFilters: true,
popupPanelSections: 0b111,
popupPanelDisabledSections: 0,
prefetchingDisabled: true,
requestLogMaxEntries: 1000,
showIconBadge: true,

View file

@ -264,14 +264,14 @@ const getFirewallRules = function(srcHostname, desHostnames) {
const popupDataFromTabId = function(tabId, tabTitle) {
const tabContext = µb.tabContextManager.mustLookup(tabId);
const rootHostname = tabContext.rootHostname;
const µbus = µb.userSettings;
const r = {
advancedUserEnabled: µb.userSettings.advancedUserEnabled,
advancedUserEnabled: µbus.advancedUserEnabled,
appName: vAPI.app.name,
appVersion: vAPI.app.version,
colorBlindFriendly: µb.userSettings.colorBlindFriendly,
colorBlindFriendly: µbus.colorBlindFriendly,
cosmeticFilteringSwitch: false,
dfEnabled: µb.userSettings.dynamicFilteringEnabled,
firewallPaneMinimized: µb.userSettings.firewallPaneMinimized,
firewallPaneMinimized: µbus.firewallPaneMinimized,
globalAllowedRequestCount: µb.localSettings.allowedRequestCount,
globalBlockedRequestCount: µb.localSettings.blockedRequestCount,
fontSize: µb.hiddenSettings.popupFontSize,
@ -283,9 +283,11 @@ const popupDataFromTabId = function(tabId, tabTitle) {
pageAllowedRequestCount: 0,
pageBlockedRequestCount: 0,
popupBlockedCount: 0,
popupPanelSections: µbus.popupPanelSections,
popupPanelDisabledSections: µbus.popupPanelDisabledSections,
tabId: tabId,
tabTitle: tabTitle,
tooltipsDisabled: µb.userSettings.tooltipsDisabled
tooltipsDisabled: µbus.tooltipsDisabled
};
if ( µb.hiddenSettings.uiPopupConfig !== 'undocumented' ) {

View file

@ -30,22 +30,21 @@
/******************************************************************************/
/*
let popupFontSize;
vAPI.localStorage.getItemAsync('popupFontSize').then(value => {
if ( typeof value !== 'string' || value === 'unset' ) { return; }
document.body.style.setProperty('font-size', value);
popupFontSize = value;
});
*/
// https://github.com/chrisaljoudi/uBlock/issues/996
// Experimental: mitigate glitchy popup UI: immediately set the firewall
// pane visibility to its last known state. By default the pane is hidden.
let dfPaneVisibleStored;
vAPI.localStorage.getItemAsync('popupFirewallPane').then(value => {
dfPaneVisibleStored = value === true || value === 'true';
if ( dfPaneVisibleStored ) {
document.body.classList.add('dfEnabled');
}
vAPI.localStorage.getItemAsync('popupPanelSections').then(bits => {
if ( typeof bits !== 'number' ) { return; }
sectionBitsToAttribute(bits);
});
/******************************************************************************/
@ -485,21 +484,6 @@ const renderPopup = function() {
uDom.nodeFromSelector('#no-remote-fonts .fa-icon-badge')
.textContent = total ? Math.min(total, 99).toLocaleString() : '';
// https://github.com/chrisaljoudi/uBlock/issues/470
// This must be done here, to be sure the popup is resized properly
const dfPaneVisible = popupData.dfEnabled;
// https://github.com/chrisaljoudi/uBlock/issues/1068
// Remember the last state of the firewall pane. This allows to
// configure the popup size early next time it is opened, which means a
// less glitchy popup at open time.
if ( dfPaneVisible !== dfPaneVisibleStored ) {
dfPaneVisibleStored = dfPaneVisible;
vAPI.localStorage.setItem('popupFirewallPane', dfPaneVisibleStored);
}
body.classList.toggle('dfEnabled', dfPaneVisible === true);
document.documentElement.classList.toggle(
'colorBlind',
popupData.colorBlindFriendly === true
@ -508,7 +492,7 @@ const renderPopup = function() {
setGlobalExpand(popupData.firewallPaneMinimized === false, true);
// Build dynamic filtering pane only if in use
if ( dfPaneVisible ) {
if ( (popupData.popupPanelSections & ~popupData.popupPanelDisabledSections & 0b1000) !== 0 ) {
buildAllFirewallRows();
}
@ -588,6 +572,7 @@ let renderOnce = function() {
const body = document.body;
/*
if ( popupData.fontSize !== popupFontSize ) {
popupFontSize = popupData.fontSize;
if ( popupFontSize !== 'unset' ) {
@ -598,11 +583,13 @@ let renderOnce = function() {
vAPI.localStorage.removeItem('popupFontSize');
}
}
*/
// https://github.com/uBlockOrigin/uBlock-issues/issues/22
if ( popupData.advancedUserEnabled !== true ) {
uDom('#firewall [title][data-src]').removeAttr('title');
}
uDom.nodeFromId('version').textContent = popupData.appVersion;
sectionBitsToAttribute(
popupData.popupPanelSections & ~popupData.popupPanelDisabledSections
);
if ( popupData.uiPopupConfig !== undefined ) {
document.body.setAttribute('data-ui', popupData.uiPopupConfig);
@ -612,6 +599,11 @@ let renderOnce = function() {
if ( popupData.tooltipsDisabled === true ) {
uDom('[title]').removeAttr('title');
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/22
if ( popupData.advancedUserEnabled !== true ) {
uDom('#firewall [title][data-src]').removeAttr('title');
}
};
/******************************************************************************/
@ -733,29 +725,71 @@ const gotoURL = function(ev) {
/******************************************************************************/
const toggleFirewallPane = function() {
popupData.dfEnabled = !popupData.dfEnabled;
// The popup panel is made of sections. Visiblity of sections can
// be toggle on/off.
const maxNumberOfSections = 5;
const sectionBitsFromAttribute = function() {
const attr = document.body.dataset.more;
if ( attr === '' ) { return 0; }
let bits = 0;
for ( const c of attr.split(' ') ) {
bits |= 1 << (c.charCodeAt(0) - 97);
}
return bits;
};
const sectionBitsToAttribute = function(bits) {
const attr = [];
for ( let i = 0; i < maxNumberOfSections; i++ ) {
const bit = 1 << i;
if ( (bits & bit) === 0 ) { continue; }
attr.push(String.fromCharCode(97 + i));
}
document.body.dataset.more = attr.join(' ');
};
const toggleSections = function(more) {
const mask = ~popupData.popupPanelDisabledSections;
let currentBits = sectionBitsFromAttribute();
let newBits = currentBits;
for ( let i = 0; i < maxNumberOfSections; i++ ) {
const bit = 1 << (more ? i : maxNumberOfSections - i - 1);
if ( (mask & bit) === 0 ) { continue; }
if ( more ) {
newBits |= bit;
} else {
newBits &= ~bit;
}
if ( newBits !== currentBits ) { break; }
}
if ( newBits === currentBits ) { return; }
sectionBitsToAttribute(newBits);
popupData.popupPanelSections = newBits;
messaging.send('popupPanel', {
what: 'userSettings',
name: 'dynamicFilteringEnabled',
value: popupData.dfEnabled,
name: 'popupPanelSections',
value: newBits,
});
// https://github.com/chrisaljoudi/uBlock/issues/996
// Remember the last state of the firewall pane. This allows to
// configure the popup size early next time it is opened, which means a
// less glitchy popup at open time.
dfPaneVisibleStored = popupData.dfEnabled;
vAPI.localStorage.setItem('popupFirewallPane', dfPaneVisibleStored);
// Remember the last state of the firewall pane. This allows to
// configure the popup size early next time it is opened, which means a
// less glitchy popup at open time.
vAPI.localStorage.setItem('popupPanelSections', newBits);
// Dynamic filtering pane may not have been built yet
document.body.classList.toggle('dfEnabled', popupData.dfEnabled);
if ( popupData.dfEnabled && dfPaneBuilt === false ) {
if ( (newBits & 0b1000) !== 0 && dfPaneBuilt === false ) {
buildAllFirewallRows();
}
};
uDom('#moreButton').on('click', ( ) => { toggleSections(true); });
uDom('#lessButton').on('click', ( ) => { toggleSections(false); });
/******************************************************************************/
const mouseenterCellHandler = function() {
@ -1138,7 +1172,6 @@ const getPopupData = async function(tabId) {
uDom('#switch').on('click', toggleNetFilteringSwitch);
uDom('#gotoZap').on('click', gotoZap);
uDom('#gotoPick').on('click', gotoPick);
uDom('#moreButton').on('click', toggleFirewallPane);
uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); });
uDom('#saveRules').on('click', saveFirewallRules);
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); });

View file

@ -506,7 +506,7 @@ const renderPopup = function() {
// https://github.com/chrisaljoudi/uBlock/issues/470
// This must be done here, to be sure the popup is resized properly
const dfPaneVisible = popupData.dfEnabled;
const dfPaneVisible = (popupData.popupPanelSections & 0b1000) !== 0;
// https://github.com/chrisaljoudi/uBlock/issues/1068
// Remember the last state of the firewall pane. This allows to
@ -517,10 +517,7 @@ const renderPopup = function() {
vAPI.localStorage.setItem('popupFirewallPane', dfPaneVisibleStored);
}
uDom.nodeFromId('panes').classList.toggle(
'dfEnabled',
dfPaneVisible === true
);
uDom.nodeFromId('panes').classList.toggle('dfEnabled', dfPaneVisible === true);
document.documentElement.classList.toggle(
'colorBlind',
@ -795,24 +792,24 @@ const gotoURL = function(ev) {
/******************************************************************************/
const toggleFirewallPane = function() {
popupData.dfEnabled = !popupData.dfEnabled;
popupData.popupPanelSections = popupData.popupPanelSections ^ 0b1000;
messaging.send('popupPanel', {
what: 'userSettings',
name: 'dynamicFilteringEnabled',
value: popupData.dfEnabled,
name: 'popupPanelSections',
value: popupData.popupPanelSections | 0b0111,
});
// https://github.com/chrisaljoudi/uBlock/issues/996
// Remember the last state of the firewall pane. This allows to
// configure the popup size early next time it is opened, which means a
// less glitchy popup at open time.
dfPaneVisibleStored = popupData.dfEnabled;
dfPaneVisibleStored = (popupData.popupPanelSections & 0b1000) !== 0;
vAPI.localStorage.setItem('popupFirewallPane', dfPaneVisibleStored);
// Dynamic filtering pane may not have been built yet
uDom.nodeFromId('panes').classList.toggle('dfEnabled', popupData.dfEnabled);
if ( popupData.dfEnabled && dfPaneBuilt === false ) {
uDom.nodeFromId('panes').classList.toggle('dfEnabled', dfPaneVisibleStored);
if ( dfPaneVisibleStored && dfPaneBuilt === false ) {
buildAllFirewallRows();
}
};
@ -1076,7 +1073,7 @@ const toggleHostnameSwitch = async function(ev) {
hostname: popupData.pageHostname,
state: target.classList.contains('on'),
tabId: popupData.tabId,
persist: popupData.dfEnabled === false || ev.ctrlKey || ev.metaKey,
persist: (popupData.popupPanelSections & 0b1000) === 0 || ev.ctrlKey || ev.metaKey,
});
cachePopupData(response);

View file

@ -114,6 +114,15 @@ const onVersionReady = function(lastVersion) {
µb.saveHostnameSwitches();
}
// Configure new popup panel according to classic popup panel
// configuration.
if ( lastVersionInt !== 0 && lastVersionInt <= 1026003014 ) {
µb.userSettings.popupPanelSections =
µb.userSettings.dynamicFilteringEnabled === true ? 0b1111 : 0b0111;
µb.userSettings.dynamicFilteringEnabled = undefined;
µb.saveUserSettings();
}
vAPI.storage.set({ version: vAPI.app.version });
};
@ -356,14 +365,16 @@ if (
browser.browserAction instanceof Object &&
browser.browserAction.setPopup instanceof Function
) {
let uiFlavor = µb.hiddenSettings.uiFlavor;
if ( uiFlavor === 'unset' && vAPI.webextFlavor.soup.has('mobile') ) {
uiFlavor = 'fenix';
}
if ( uiFlavor !== 'unset' && /\w+/.test(uiFlavor) ) {
browser.browserAction.setPopup({
popup: vAPI.getURL(`popup-${uiFlavor}.html`)
});
const env = vAPI.webextFlavor;
if (
µb.hiddenSettings.uiFlavor === 'classic' || (
µb.hiddenSettings.uiFlavor === 'unset' && (
env.soup.has('chromium') && env.major < 66 ||
env.soup.has('firefox') && env.major < 68
)
)
) {
browser.browserAction.setPopup({ popup: vAPI.getURL('popup.html') });
}
}

View file

@ -331,7 +331,7 @@ const matchBucket = function(url, hostname, bucket, start) {
switch ( name ) {
case 'advancedUserEnabled':
if ( value === true ) {
us.dynamicFilteringEnabled = true;
us.popupPanelSections = 0b1111;
}
break;
case 'autoUpdate':

View file

@ -11,7 +11,7 @@
<title data-i18n="extName"></title>
</head>
<body class="loading">
<body class="loading" data-more="a b c">
<div id="panes">
<div id="sticky">
<div id="stickyTools">
@ -27,35 +27,44 @@
</div>
</div>
<div id="hostname"><span>ephemeralnewyork.</span>&shy;<span>wordpress.com</span></div>
<hr>
</div>
<div id="main">
<div id="basicStats" class="itemRibbon">
<span data-i18n="popupBlockedOnThisPage_v2"></span><span></span>
<span data-i18n="popupBlockedSinceInstall_v2"></span><span></span>
<span data-i18n="popupDomainsConnected_v2"></span><span></span>
</div>
<hr>
<div id="extraTools" class="toolRibbon">
<hr data-more="c">
<div id="extraTools" class="toolRibbon" data-more="c">
<span id="no-popups" class="hnSwitch tool enabled" role="button" aria-label tabindex="0" title><span class="fa-icon fa-icon-badged">ph-popups<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span><span class="caption" data-i18n="popupNoPopups_v2"></span></span>
<span id="no-large-media" class="hnSwitch tool enabled" role="button" aria-label tabindex="0" title><span class="fa-icon fa-icon-badged">film<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span><span class="caption" data-i18n="popupNoLargeMedia_v2"></span></span>
<span id="no-cosmetic-filtering" class="hnSwitch tool enabled" role="button" aria-label tabindex="0" title><span class="fa-icon fa-icon-badged">eye-slash<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span><span class="caption" data-i18n="popupNoCosmeticFiltering_v2"></span></span>
<span id="no-remote-fonts" class="hnSwitch tool enabled" role="button" aria-label tabindex="0" title><span class="fa-icon fa-icon-badged">ph-readermode-text-size<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span><span class="caption" data-i18n="popupNoRemoteFonts_v2"></span></span>
<span id="no-scripting" class="hnSwitch tool enabled" role="button" aria-label tabindex="0" title><span class="fa-icon fa-icon-badged">code<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span><span class="caption" data-i18n="popupNoScripting_v2"></span></span>
</div>
<hr>
<div id="basicTools" class="toolRibbon">
<hr data-more="a">
<div id="basicStats" class="itemRibbon" data-more="a">
<span data-i18n="popupBlockedOnThisPage_v2"></span><span></span>
<span data-i18n="popupBlockedSinceInstall_v2"></span><span></span>
<span data-i18n="popupDomainsConnected_v2"></span><span></span>
</div>
<hr data-more="b">
<div id="basicTools" class="toolRibbon" data-more="b">
<span id="gotoZap" class="fa-icon tool" data-i18n-title="popupTipZapper">bolt<span class="caption" data-i18n="popupTipZapper"></span></span>
<span id="gotoPick" class="fa-icon tool" data-i18n-title="popupTipPicker">eye-dropper<span class="caption" data-i18n="popupTipPicker"></span></span>
<a href="logger-ui.html#_" class="fa-icon tool enabled" target="uBOLogger" tabindex="0" data-i18n-title="popupTipLog">list-alt<span class="caption" data-i18n="popupTipLog"></span></a>
<a href="dashboard.html" class="fa-icon tool enabled" target="uBODashboard" tabindex="0" data-i18n-title="popupTipDashboard">sliders<span class="caption" data-i18n="popupTipDashboard"></span></a>
</div>
<hr data-more="e">
<div class="itemRibbon" data-more="e">
<span data-i18n="popupVersion"></span><span id="version"></span>
</div>
<hr>
<div id="moreButton" class="itemRibbon">
<span data-i18n="popupMoreButton_v2"></span><span class="fa-icon">angle-up</span>
<div class="itemRibbon moreOrLess">
<span id="moreButton">
<span data-i18n="popupMoreButton_v2"></span>&emsp;<span class="fa-icon">angle-up</span>
</span>
<span id="lessButton">
<span class="fa-icon">angle-up</span>&emsp;<span data-i18n="popupLessButton_v2"></span>
</span>
</div>
</div>
<div id="firewall">
<div id="firewall" data-more="d">
<div data-des="*" data-type="*"><span data-i18n="popupAnyRulePrompt"></span><span data-src="/" data-i18n-title="popupTipGlobalRules"> </span><span data-src="." data-i18n-title="popupTipLocalRules"> </span></div>
<div data-des="*" data-type="image"><span data-i18n="popupImageRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
<div data-des="*" data-type="3p"><span data-i18n="popup3pAnyRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>