diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js index b7a44e7be..f2edd3583 100644 --- a/src/js/document-blocked.js +++ b/src/js/document-blocked.js @@ -127,7 +127,8 @@ var proceedPermanent = function() { name: 'no-strict-blocking', hostname: getTargetHostname(), deep: true, - state: true + state: true, + persist: true }, proceedToURL ); diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js index eb1d704f0..8ffe536b6 100644 --- a/src/js/dyna-rules.js +++ b/src/js/dyna-rules.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 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 @@ -67,12 +67,12 @@ var differ; // ... and modified as needed. var updateOverlay = (function() { - var reFilter; - var mode = { + let reFilter; + let mode = { token: function(stream) { if ( reFilter !== undefined ) { reFilter.lastIndex = stream.pos; - var match = reFilter.exec(stream.string); + let match = reFilter.exec(stream.string); if ( match !== null ) { if ( match.index === stream.pos ) { stream.pos += match[0].length || 1; @@ -100,35 +100,38 @@ var updateOverlay = (function() { // - Minimum amount of text updated var rulesToDoc = function(clearHistory) { - for ( var key in unfilteredRules ) { + for ( let key in unfilteredRules ) { if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; } - var doc = unfilteredRules[key].doc; - var rules = filterRules(key); - if ( doc.lineCount() === 1 && doc.getValue() === '' || rules.length === 0 ) { + let doc = unfilteredRules[key].doc; + let rules = filterRules(key); + if ( + doc.lineCount() === 1 && doc.getValue() === '' || + rules.length === 0 + ) { doc.setValue(rules.length !== 0 ? rules.join('\n') : ''); continue; } if ( differ === undefined ) { differ = new diff_match_patch(); } - var beforeText = doc.getValue(); - var afterText = rules.join('\n'); - var diffs = differ.diff_main(beforeText, afterText); + let beforeText = doc.getValue(); + let afterText = rules.join('\n'); + let diffs = differ.diff_main(beforeText, afterText); doc.startOperation(); - var i = diffs.length, + let i = diffs.length, iedit = beforeText.length; while ( i-- ) { - var diff = diffs[i]; + let diff = diffs[i]; if ( diff[0] === 0 ) { iedit -= diff[1].length; continue; } - var end = doc.posFromIndex(iedit); + let end = doc.posFromIndex(iedit); if ( diff[0] === 1 ) { doc.replaceRange(diff[1], end, end); continue; } /* diff[0] === -1 */ iedit -= diff[1].length; - var beg = doc.posFromIndex(iedit); + let beg = doc.posFromIndex(iedit); doc.replaceRange('', beg, end); } doc.endOperation(); @@ -143,11 +146,11 @@ var rulesToDoc = function(clearHistory) { /******************************************************************************/ var filterRules = function(key) { - var rules = unfilteredRules[key].rules; - var filter = uDom('#ruleFilter input').val(); + let rules = unfilteredRules[key].rules; + let filter = uDom.nodeFromSelector('#ruleFilter input').value; if ( filter !== '' ) { rules = rules.slice(); - var i = rules.length; + let i = rules.length; while ( i-- ) { if ( rules[i].indexOf(filter) === -1 ) { rules.splice(i, 1); @@ -160,16 +163,23 @@ var filterRules = function(key) { /******************************************************************************/ var renderRules = (function() { - var firstVisit = true; + let firstVisit = true; + let reIsSwitchRule = /^[a-z-]+: /; + + // Switches always listed at the top. + let customSort = (a, b) => { + let aIsSwitch = reIsSwitchRule.test(a); + if ( reIsSwitchRule.test(b) === aIsSwitch ) { + return a.localeCompare(b); + } + return aIsSwitch ? -1 : 1; + }; return function(details) { - details.hnSwitches.sort(); - details.permanentRules.sort(); - details.sessionRules.sort(); - unfilteredRules.orig.rules = - details.hnSwitches.concat(details.permanentRules); - unfilteredRules.edit.rules = - details.hnSwitches.concat(details.sessionRules); + details.permanentRules.sort(customSort); + details.sessionRules.sort(customSort); + unfilteredRules.orig.rules = details.permanentRules; + unfilteredRules.edit.rules = details.sessionRules; rulesToDoc(firstVisit); if ( firstVisit ) { firstVisit = false; @@ -206,20 +216,19 @@ mergeView.options.revertChunk = function( ) { // https://github.com/gorhill/uBlock/issues/3611 if ( document.body.getAttribute('dir') === 'rtl' ) { - var tmp; - tmp = from; from = to; to = tmp; + let tmp = from; from = to; to = tmp; tmp = fromStart; fromStart = toStart; toStart = tmp; tmp = fromEnd; fromEnd = toEnd; toEnd = tmp; } if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; } if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; } - var toAdd = from.getRange( + let toAdd = from.getRange( { line: fromStart.line, ch: 0 }, { line: fromEnd.line, ch: 0 } ); if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; } if ( toEnd.ch !== 0 ) { toEnd.line += 1; } - var toRemove = to.getRange( + let toRemove = to.getRange( { line: toStart.line, ch: 0 }, { line: toEnd.line, ch: 0 } ); @@ -229,12 +238,12 @@ mergeView.options.revertChunk = function( /******************************************************************************/ function handleImportFilePicker() { - var fileReaderOnLoadHandler = function() { + let fileReaderOnLoadHandler = function() { if ( typeof this.result !== 'string' || this.result === '' ) { return; } // https://github.com/chrisaljoudi/uBlock/issues/757 // Support RequestPolicy rule syntax - var result = this.result; - var matches = /\[origins-to-destinations\]([^\[]+)/.exec(result); + let result = this.result; + let matches = /\[origins-to-destinations\]([^\[]+)/.exec(result); if ( matches && matches.length === 2 ) { result = matches[1].trim() .replace(/\|/g, ' ') @@ -242,10 +251,10 @@ function handleImportFilePicker() { } applyDiff(false, result, ''); }; - var file = this.files[0]; + let file = this.files[0]; if ( file === undefined || file.name === '' ) { return; } if ( file.type.indexOf('text') !== 0 ) { return; } - var fr = new FileReader(); + let fr = new FileReader(); fr.onload = fileReaderOnLoadHandler; fr.readAsText(file); } @@ -253,7 +262,7 @@ function handleImportFilePicker() { /******************************************************************************/ var startImportFilePicker = function() { - var input = document.getElementById('importFilePicker'); + let input = document.getElementById('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. @@ -264,7 +273,7 @@ var startImportFilePicker = function() { /******************************************************************************/ function exportUserRulesToFile() { - var filename = vAPI.i18n('rulesDefaultFileName') + let filename = vAPI.i18n('rulesDefaultFileName') .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) .replace(/ +/g, '_'); vAPI.download({ @@ -279,14 +288,14 @@ function exportUserRulesToFile() { /******************************************************************************/ var onFilterChanged = (function() { - var timer, + let timer, overlay = null, last = ''; - var process = function() { + let process = function() { timer = undefined; if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } - var filter = uDom('#ruleFilter input').val(); + let filter = uDom.nodeFromSelector('#ruleFilter input').value; if ( filter === last ) { return; } last = filter; if ( overlay !== null ) { @@ -311,12 +320,12 @@ var onFilterChanged = (function() { /******************************************************************************/ var onTextChanged = (function() { - var timer; + let timer; - var process = function(now) { + let process = function(now) { timer = undefined; - var isClean = mergeView.editor().isClean(cleanEditToken); - var diff = document.getElementById('diff'); + let isClean = mergeView.editor().isClean(cleanEditToken); + let diff = document.getElementById('diff'); if ( now && isClean === false && @@ -331,7 +340,7 @@ var onTextChanged = (function() { 'disabled', isClean ); - var input = document.querySelector('#ruleFilter input'); + let input = document.querySelector('#ruleFilter input'); if ( isClean ) { input.removeAttribute('disabled'); CodeMirror.commands.save = undefined; @@ -350,15 +359,15 @@ var onTextChanged = (function() { /******************************************************************************/ var revertAllHandler = function() { - var toAdd = [], toRemove = []; - var left = mergeView.leftOriginal(), + let toAdd = [], toRemove = []; + let left = mergeView.leftOriginal(), edit = mergeView.editor(); - for ( var chunk of mergeView.leftChunks() ) { - var addedLines = left.getRange( + for ( let chunk of mergeView.leftChunks() ) { + let addedLines = left.getRange( { line: chunk.origFrom, ch: 0 }, { line: chunk.origTo, ch: 0 } ); - var removedLines = edit.getRange( + let removedLines = edit.getRange( { line: chunk.editFrom, ch: 0 }, { line: chunk.editTo, ch: 0 } ); @@ -371,15 +380,15 @@ var revertAllHandler = function() { /******************************************************************************/ var commitAllHandler = function() { - var toAdd = [], toRemove = []; - var left = mergeView.leftOriginal(), + let toAdd = [], toRemove = []; + let left = mergeView.leftOriginal(), edit = mergeView.editor(); - for ( var chunk of mergeView.leftChunks() ) { - var addedLines = edit.getRange( + for ( let chunk of mergeView.leftChunks() ) { + let addedLines = edit.getRange( { line: chunk.editFrom, ch: 0 }, { line: chunk.editTo, ch: 0 } ); - var removedLines = left.getRange( + let removedLines = left.getRange( { line: chunk.origFrom, ch: 0 }, { line: chunk.origTo, ch: 0 } ); @@ -392,16 +401,16 @@ var commitAllHandler = function() { /******************************************************************************/ var editSaveHandler = function() { - var editor = mergeView.editor(); - var editText = editor.getValue().trim(); + let editor = mergeView.editor(); + let editText = editor.getValue().trim(); if ( editText === cleanEditText ) { onTextChanged(true); return; } if ( differ === undefined ) { differ = new diff_match_patch(); } - var toAdd = [], toRemove = []; - var diffs = differ.diff_main(cleanEditText, editText); - for ( var diff of diffs ) { + let toAdd = [], toRemove = []; + let diffs = differ.diff_main(cleanEditText, editText); + for ( let diff of diffs ) { if ( diff[0] === 1 ) { toAdd.push(diff[1]); } else if ( diff[0] === -1 ) { diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index bb58ff19c..a2aee6333 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -107,45 +107,59 @@ Matrix.prototype.assign = function(other) { /******************************************************************************/ -Matrix.prototype.copyRules = function(other, srcHostname, desHostnames) { - +Matrix.prototype.copyRules = function(from, srcHostname, desHostnames) { // Specific types - var bits = other.rules.get('* *'); - if ( bits !== undefined ) { - this.rules.set('* *', bits); - } else { - this.rules.delete('* *'); + let thisBits = this.rules.get('* *'); + let fromBits = from.rules.get('* *'); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set('* *', fromBits); + } else { + this.rules.delete('* *'); + } + this.changed = true; } - var key = srcHostname + ' *'; - bits = other.rules.get(key); - if ( bits !== undefined ) { - this.rules.set(key, bits); - } else { - this.rules.delete(key); + + let key = srcHostname + ' *'; + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; } // Specific destinations - for ( var desHostname in desHostnames ) { + for ( let desHostname in desHostnames ) { if ( desHostnames.hasOwnProperty(desHostname) === false ) { continue; } key = '* ' + desHostname; - bits = other.rules.get(key); - if ( bits !== undefined ) { - this.rules.set(key, bits); - } else { - this.rules.delete(key); + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; } key = srcHostname + ' ' + desHostname ; - bits = other.rules.get(key); - if ( bits !== undefined ) { - this.rules.set(key, bits); - } else { - this.rules.delete(key); + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; } } - this.changed = true; - - return true; + return this.changed; }; /******************************************************************************/ diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js index cb04996ac..826943755 100644 --- a/src/js/hnswitches.js +++ b/src/js/hnswitches.js @@ -80,17 +80,54 @@ HnSwitches.prototype.reset = function() { /******************************************************************************/ +HnSwitches.prototype.assign = function(from) { + // Remove rules not in other + for ( let hn of this.switches.keys() ) { + if ( from.switches.has(hn) === false ) { + this.switches.delete(hn); + this.changed = true; + } + } + // Add/change rules in other + for ( let [hn, bits] of from.switches ) { + if ( this.switches.get(hn) !== bits ) { + this.switches.set(hn, bits); + this.changed = true; + } + } +}; + +/******************************************************************************/ + +HnSwitches.prototype.copyRules = function(from, srcHostname) { + let thisBits = this.switches.get(srcHostname); + let fromBits = from.switches.get(srcHostname); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.switches.set(srcHostname, fromBits); + } else { + this.switches.delete(srcHostname); + } + this.changed = true; + } + return this.changed; +}; + +/******************************************************************************/ + +HnSwitches.prototype.hasSameRules = function(other, srcHostname) { + return this.switches.get(srcHostname) === other.switches.get(srcHostname); +}; + +/******************************************************************************/ + // If value is undefined, the switch is removed HnSwitches.prototype.toggle = function(switchName, hostname, newVal) { - var bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { - return false; - } - if ( newVal === this.evaluate(switchName, hostname) ) { - return false; - } - var bits = this.switches.get(hostname) || 0; + let bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + if ( newVal === this.evaluate(switchName, hostname) ) { return false; } + let bits = this.switches.get(hostname) || 0; bits &= ~(3 << bitOffset); bits |= newVal << bitOffset; if ( bits === 0 ) { @@ -105,18 +142,14 @@ HnSwitches.prototype.toggle = function(switchName, hostname, newVal) { /******************************************************************************/ HnSwitches.prototype.toggleOneZ = function(switchName, hostname, newState) { - var bitOffset = switchBitOffsets[switchName]; - if ( bitOffset === undefined ) { - return false; - } - var state = this.evaluateZ(switchName, hostname); - if ( newState === state ) { - return false; - } + let bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + let state = this.evaluateZ(switchName, hostname); + if ( newState === state ) { return false; } if ( newState === undefined ) { newState = !state; } - var bits = this.switches.get(hostname) || 0; + let bits = this.switches.get(hostname) || 0; bits &= ~(3 << bitOffset); if ( bits === 0 ) { this.switches.delete(hostname); @@ -138,17 +171,11 @@ HnSwitches.prototype.toggleBranchZ = function(switchName, targetHostname, newSta // Turn off all descendant switches, they will inherit the state of the // branch's origin. - var targetLen = targetHostname.length; - for ( var hostname of this.switches.keys() ) { - if ( hostname === targetHostname ) { - continue; - } - if ( hostname.length <= targetLen ) { - continue; - } - if ( hostname.endsWith(targetHostname) === false ) { - continue; - } + let targetLen = targetHostname.length; + for ( let hostname of this.switches.keys() ) { + if ( hostname === targetHostname ) { continue; } + if ( hostname.length <= targetLen ) { continue; } + if ( hostname.endsWith(targetHostname) === false ) { continue; } if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) { continue; } @@ -174,11 +201,11 @@ HnSwitches.prototype.toggleZ = function(switchName, hostname, deep, newState) { // 2 = forced default state (to override a broader non-default state) HnSwitches.prototype.evaluate = function(switchName, hostname) { - var bits = this.switches.get(hostname); + let bits = this.switches.get(hostname); if ( bits === undefined ) { return 0; } - var bitOffset = switchBitOffsets[switchName]; + let bitOffset = switchBitOffsets[switchName]; if ( bitOffset === undefined ) { return 0; } @@ -188,7 +215,7 @@ HnSwitches.prototype.evaluate = function(switchName, hostname) { /******************************************************************************/ HnSwitches.prototype.evaluateZ = function(switchName, hostname) { - var bitOffset = switchBitOffsets[switchName]; + let bitOffset = switchBitOffsets[switchName]; if ( bitOffset === undefined ) { this.r = 0; return false; @@ -223,14 +250,14 @@ HnSwitches.prototype.toLogData = function() { /******************************************************************************/ HnSwitches.prototype.toArray = function() { - var out = [], + let out = [], toUnicode = punycode.toUnicode; - for ( var hostname of this.switches.keys() ) { + for ( let hostname of this.switches.keys() ) { for ( var switchName in switchBitOffsets ) { if ( switchBitOffsets.hasOwnProperty(switchName) === false ) { continue; } - var val = this.evaluate(switchName, hostname); + let val = this.evaluate(switchName, hostname); if ( val === 0 ) { continue; } if ( hostname.indexOf('xn--') !== -1 ) { hostname = toUnicode(hostname); @@ -248,7 +275,7 @@ HnSwitches.prototype.toString = function() { /******************************************************************************/ HnSwitches.prototype.fromString = function(text, append) { - var lineIter = new µBlock.LineIterator(text); + let lineIter = new µBlock.LineIterator(text); if ( append !== true ) { this.reset(); } while ( lineIter.eot() === false ) { this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); @@ -270,7 +297,7 @@ HnSwitches.prototype.validateRuleParts = function(parts) { HnSwitches.prototype.addFromRuleParts = function(parts) { if ( this.validateRuleParts(parts) !== undefined ) { - var switchName = parts[0].slice(0, -1); + let switchName = parts[0].slice(0, -1); if ( switchBitOffsets.hasOwnProperty(switchName) ) { this.toggle(switchName, parts[1], nameToSwitchStateMap[parts[2]]); return true; @@ -297,6 +324,7 @@ return HnSwitches; /******************************************************************************/ -µBlock.hnSwitches = new µBlock.HnSwitches(); +µBlock.sessionSwitches = new µBlock.HnSwitches(); +µBlock.permanentSwitches = new µBlock.HnSwitches(); /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 6d0db28f3..ae7c40fa7 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -279,9 +279,10 @@ var getFirewallRules = function(srcHostname, desHostnames) { /******************************************************************************/ var popupDataFromTabId = function(tabId, tabTitle) { - var tabContext = µb.tabContextManager.mustLookup(tabId), + let tabContext = µb.tabContextManager.mustLookup(tabId), rootHostname = tabContext.rootHostname; - var r = { + + let r = { advancedUserEnabled: µb.userSettings.advancedUserEnabled, appName: vAPI.app.name, appVersion: vAPI.app.version, @@ -305,7 +306,7 @@ var popupDataFromTabId = function(tabId, tabTitle) { tooltipsDisabled: µb.userSettings.tooltipsDisabled }; - var pageStore = µb.pageStoreFromTabId(tabId); + let pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore ) { // https://github.com/gorhill/uBlock/issues/2105 // Be sure to always include the current page's hostname -- it might @@ -325,23 +326,30 @@ var popupDataFromTabId = function(tabId, tabTitle) { r.contentLastModified = pageStore.contentLastModified; r.firewallRules = getFirewallRules(rootHostname, r.hostnameDict); r.canElementPicker = µb.URI.isNetworkURI(r.rawURL); - r.noPopups = µb.hnSwitches.evaluateZ('no-popups', rootHostname); + r.noPopups = µb.sessionSwitches.evaluateZ('no-popups', rootHostname); r.popupBlockedCount = pageStore.popupBlockedCount; - r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', rootHostname); - r.noLargeMedia = µb.hnSwitches.evaluateZ('no-large-media', rootHostname); + r.noCosmeticFiltering = µb.sessionSwitches.evaluateZ('no-cosmetic-filtering', rootHostname); + r.noLargeMedia = µb.sessionSwitches.evaluateZ('no-large-media', rootHostname); r.largeMediaCount = pageStore.largeMediaCount; - r.noRemoteFonts = µb.hnSwitches.evaluateZ('no-remote-fonts', rootHostname); + r.noRemoteFonts = µb.sessionSwitches.evaluateZ('no-remote-fonts', rootHostname); r.remoteFontCount = pageStore.remoteFontCount; - r.noScripting = µb.hnSwitches.evaluateZ('no-scripting', rootHostname); + r.noScripting = µb.sessionSwitches.evaluateZ('no-scripting', rootHostname); } else { r.hostnameDict = {}; r.firewallRules = getFirewallRules(); } - r.matrixIsDirty = !µb.sessionFirewall.hasSameRules( + + r.matrixIsDirty = µb.sessionFirewall.hasSameRules( µb.permanentFirewall, rootHostname, r.hostnameDict - ); + ) === false; + if ( r.matrixIsDirty === false ) { + r.matrixIsDirty = µb.sessionSwitches.hasSameRules( + µb.permanentSwitches, + rootHostname + ) === false; + } return r; }; @@ -409,18 +417,41 @@ var onMessage = function(request, sender, callback) { request.srcHostname, request.desHostnames ); + µb.sessionSwitches.copyRules( + µb.permanentSwitches, + request.srcHostname + ); // https://github.com/gorhill/uBlock/issues/188 - µb.cosmeticFilteringEngine.removeFromSelectorCache(request.srcHostname, 'net'); + µb.cosmeticFilteringEngine.removeFromSelectorCache( + request.srcHostname, + 'net' + ); response = popupDataFromTabId(request.tabId); break; case 'saveFirewallRules': - µb.permanentFirewall.copyRules( - µb.sessionFirewall, - request.srcHostname, - request.desHostnames - ); - µb.savePermanentFirewallRules(); + if ( + µb.permanentFirewall.copyRules( + µb.sessionFirewall, + request.srcHostname, + request.desHostnames + ) + ) { + µb.savePermanentFirewallRules(); + } + if ( + µb.permanentSwitches.copyRules( + µb.sessionSwitches, + request.srcHostname + ) + ) { + µb.saveHostnameSwitches(); + } + break; + + case 'toggleHostnameSwitch': + µb.toggleHostnameSwitch(request); + response = popupDataFromTabId(request.tabId); break; case 'toggleFirewallRule': @@ -745,7 +776,7 @@ var backupUserData = function(callback) { netWhitelist: µb.stringFromWhitelist(µb.netWhitelist), dynamicFilteringString: µb.permanentFirewall.toString(), urlFilteringString: µb.permanentURLFiltering.toString(), - hostnameSwitchesString: µb.hnSwitches.toString(), + hostnameSwitchesString: µb.permanentSwitches.toString(), userFilters: '' }; @@ -880,44 +911,55 @@ var getLists = function(callback) { var getRules = function() { return { - permanentRules: µb.permanentFirewall.toArray().concat( - µb.permanentURLFiltering.toArray() - ), - sessionRules: µb.sessionFirewall.toArray().concat( - µb.sessionURLFiltering.toArray() - ), - hnSwitches: µb.hnSwitches.toArray() + permanentRules: + µb.permanentFirewall.toArray().concat( + µb.permanentSwitches.toArray(), + µb.permanentURLFiltering.toArray() + ), + sessionRules: + µb.sessionFirewall.toArray().concat( + µb.sessionSwitches.toArray(), + µb.sessionURLFiltering.toArray() + ) }; }; var modifyRuleset = function(details) { - var swRuleset = µb.hnSwitches, - hnRuleset, urlRuleset; + let swRuleset, hnRuleset, urlRuleset; if ( details.permanent ) { + swRuleset = µb.permanentSwitches; hnRuleset = µb.permanentFirewall; urlRuleset = µb.permanentURLFiltering; } else { + swRuleset = µb.sessionSwitches; hnRuleset = µb.sessionFirewall; urlRuleset = µb.sessionURLFiltering; } - var toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/)); - var rule, parts, _; - for ( rule of toRemove ) { + let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/)); + for ( let rule of toRemove ) { if ( rule === '' ) { continue; } - parts = rule.split(/\s+/); - _ = hnRuleset.removeFromRuleParts(parts) || - swRuleset.removeFromRuleParts(parts) || - urlRuleset.removeFromRuleParts(parts); + let parts = rule.split(/\s+/); + if ( hnRuleset.removeFromRuleParts(parts) === false ) { + if ( swRuleset.removeFromRuleParts(parts) === false ) { + urlRuleset.removeFromRuleParts(parts); + } + } } - var toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/)); - for ( rule of toAdd ) { + let toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/)); + for ( let rule of toAdd ) { if ( rule === '' ) { continue; } - parts = rule.split(/\s+/); - _ = hnRuleset.addFromRuleParts(parts) || - swRuleset.addFromRuleParts(parts) || - urlRuleset.addFromRuleParts(parts); + let parts = rule.split(/\s+/); + if ( hnRuleset.addFromRuleParts(parts) === false ) { + if ( swRuleset.addFromRuleParts(parts) === false ) { + urlRuleset.addFromRuleParts(parts); + } + } } if ( details.permanent ) { + if ( swRuleset.changed ) { + µb.saveHostnameSwitches(); + swRuleset.changed = false; + } if ( hnRuleset.changed ) { µb.savePermanentFirewallRules(); hnRuleset.changed = false; @@ -927,10 +969,6 @@ var modifyRuleset = function(details) { urlRuleset.changed = false; } } - if ( swRuleset.changed ) { - µb.saveHostnameSwitches(); - swRuleset.changed = false; - } }; /******************************************************************************/ diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 4b45607ea..b12075466 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -297,7 +297,7 @@ PageStore.prototype.init = function(tabId, context) { this.netFilteringCache = NetFilteringResultCache.factory(); this.internalRedirectionCount = 0; - this.noCosmeticFiltering = µb.hnSwitches.evaluateZ( + this.noCosmeticFiltering = µb.sessionSwitches.evaluateZ( 'no-cosmetic-filtering', tabContext.rootHostname ) === true; @@ -309,7 +309,7 @@ PageStore.prototype.init = function(tabId, context) { µb.logger.writeOne( tabId, 'cosmetic', - µb.hnSwitches.toLogData(), + µb.sessionSwitches.toLogData(), 'dom', tabContext.rawURL, this.tabHostname, @@ -677,9 +677,9 @@ PageStore.prototype.collapsibleResources = { /******************************************************************************/ PageStore.prototype.filterCSPReport = function(context) { - if ( µb.hnSwitches.evaluateZ('no-csp-reports', context.requestHostname) ) { + if ( µb.sessionSwitches.evaluateZ('no-csp-reports', context.requestHostname) ) { if ( µb.logger.isEnabled() ) { - this.logData = µb.hnSwitches.toLogData(); + this.logData = µb.sessionSwitches.toLogData(); } return 1; } @@ -708,9 +708,9 @@ PageStore.prototype.filterFont = function(context) { if ( context.requestType === 'font' ) { this.remoteFontCount += 1; } - if ( µb.hnSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) { + if ( µb.sessionSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) { if ( µb.logger.isEnabled() ) { - this.logData = µb.hnSwitches.toLogData(); + this.logData = µb.sessionSwitches.toLogData(); } return 1; } @@ -725,12 +725,12 @@ PageStore.prototype.filterScripting = function(rootHostname, netFiltering) { } if ( netFiltering === false || - µb.hnSwitches.evaluateZ('no-scripting', rootHostname) === false + µb.sessionSwitches.evaluateZ('no-scripting', rootHostname) === false ) { return 0; } if ( µb.logger.isEnabled() ) { - this.logData = µb.hnSwitches.toLogData(); + this.logData = µb.sessionSwitches.toLogData(); } return 1; }; @@ -745,7 +745,7 @@ PageStore.prototype.filterLargeMediaElement = function(size) { if ( Date.now() < this.allowLargeMediaElementsUntil ) { return 0; } - if ( µb.hnSwitches.evaluateZ('no-large-media', this.tabHostname) !== true ) { + if ( µb.sessionSwitches.evaluateZ('no-large-media', this.tabHostname) !== true ) { return 0; } if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { @@ -761,7 +761,7 @@ PageStore.prototype.filterLargeMediaElement = function(size) { } if ( µb.logger.isEnabled() ) { - this.logData = µb.hnSwitches.toLogData(); + this.logData = µb.sessionSwitches.toLogData(); } return 1; diff --git a/src/js/popup.js b/src/js/popup.js index ca6515e51..f59e28578 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -391,6 +391,30 @@ var renderPrivacyExposure = function() { /******************************************************************************/ +let 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 + ); +}; + +/******************************************************************************/ + // Assume everything has to be done incrementally. var renderPopup = function() { @@ -435,11 +459,7 @@ var renderPopup = function() { renderPrivacyExposure(); // Extra tools - 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); + updateHnSwitches(); // Report blocked popup count on badge total = popupData.popupBlockedCount; @@ -458,7 +478,7 @@ var renderPopup = function() { // https://github.com/chrisaljoudi/uBlock/issues/470 // This must be done here, to be sure the popup is resized properly - var dfPaneVisible = popupData.dfEnabled; + let dfPaneVisible = popupData.dfEnabled; // https://github.com/chrisaljoudi/uBlock/issues/1068 // Remember the last state of the firewall pane. This allows to @@ -774,14 +794,13 @@ var mouseleaveCellHandler = function() { var setFirewallRule = function(src, des, type, action, persist) { // This can happen on pages where uBlock does not work - if ( typeof popupData.pageHostname !== 'string' || popupData.pageHostname === '' ) { + if ( + typeof popupData.pageHostname !== 'string' || + popupData.pageHostname === '' + ) { return; } - var onFirewallRuleChanged = function(response) { - cachePopupData(response); - updateAllFirewallCells(); - hashFromPopupData(); - }; + messaging.send( 'popupPanel', { @@ -794,14 +813,18 @@ var setFirewallRule = function(src, des, type, action, persist) { action: action, persist: persist }, - onFirewallRuleChanged + response => { + cachePopupData(response); + updateAllFirewallCells(); + hashFromPopupData(); + } ); }; /******************************************************************************/ var unsetFirewallRuleHandler = function(ev) { - var cell = uDom(this); + let cell = uDom(ev.target); setFirewallRule( cell.attr('data-src') === '/' ? '*' : popupData.pageHostname, cell.attr('data-des'), @@ -815,13 +838,11 @@ var unsetFirewallRuleHandler = function(ev) { /******************************************************************************/ var setFirewallRuleHandler = function(ev) { - var hotspot = uDom(this); - var cell = hotspot.ancestors('[data-src]'); - if ( cell.length === 0 ) { - return; - } - var action = 0; - var hotspotId = hotspot.attr('id'); + let hotspot = uDom(ev.target); + let cell = hotspot.ancestors('[data-src]'); + if ( cell.length === 0 ) { return; } + let action = 0; + let hotspotId = hotspot.attr('id'); if ( hotspotId === 'dynaAllow' ) { action = 2; } else if ( hotspotId === 'dynaNoop' ) { @@ -885,9 +906,9 @@ var toggleMinimize = function(ev) { return; } - popupData.firewallPaneMinimized = uDom.nodeFromId('firewallContainer') - .classList - .toggle('minimized'); + popupData.firewallPaneMinimized = + uDom.nodeFromId('firewallContainer').classList.toggle('minimized'); + messaging.send( 'popupPanel', { @@ -916,11 +937,6 @@ var saveFirewallRules = function() { /******************************************************************************/ var revertFirewallRules = function() { - var onFirewallRuleChanged = function(response) { - cachePopupData(response); - updateAllFirewallCells(); - hashFromPopupData(); - }; messaging.send( 'popupPanel', { @@ -929,7 +945,12 @@ var revertFirewallRules = function() { desHostnames: popupData.hostnameDict, tabId: popupData.tabId }, - onFirewallRuleChanged + response => { + cachePopupData(response); + updateAllFirewallCells(); + updateHnSwitches(); + hashFromPopupData(); + } ); uDom.nodeFromId('firewallContainer').classList.remove('dirty'); }; @@ -937,8 +958,8 @@ var revertFirewallRules = function() { /******************************************************************************/ var toggleHostnameSwitch = function(ev) { - var target = ev.currentTarget; - var switchName = target.getAttribute('id'); + let target = ev.currentTarget; + let switchName = target.getAttribute('id'); if ( !switchName ) { return; } target.classList.toggle('on'); messaging.send( @@ -948,11 +969,16 @@ var toggleHostnameSwitch = function(ev) { name: switchName, hostname: popupData.pageHostname, state: target.classList.contains('on'), - tabId: popupData.tabId + tabId: popupData.tabId, + persist: popupData.dfEnabled === false || ev.ctrlKey || ev.metaKey + }, + response => { + cachePopupData(response); + updateAllFirewallCells(); + hashFromPopupData(); } ); renderTooltips('#' + switchName); - hashFromPopupData(); }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index e6099f43d..cd3cbee07 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -42,7 +42,8 @@ vAPI.app.onShutdown = function() { µb.permanentFirewall.reset(); µb.sessionURLFiltering.reset(); µb.permanentURLFiltering.reset(); - µb.hnSwitches.reset(); + µb.sessionSwitches.reset(); + µb.permanentSwitches.reset(); }; /******************************************************************************/ @@ -124,52 +125,11 @@ var onVersionReady = function(lastVersion) { // release. This will be done only for release versions of Firefox. if ( vAPI.webextFlavor.soup.has('firefox') && - /(b|rc)\d+$/.test(vAPI.app.version) === false + vAPI.webextFlavor.soup.has('devbuild') === false ) { µb.redirectEngine.invalidateResourcesSelfie(); } - // From 1.15.19b9 and above, the `behind-the-scene` scope is no longer - // whitelisted by default, and network requests from that scope will be - // subject to filtering by default. - // - // Following code is to remove the `behind-the-scene` scope when updating - // from a version older than 1.15.19b9. - // This will apply only to webext versions of uBO, as the following would - // certainly cause too much breakage in Firefox legacy given that uBO can - // see ALL network requests. - // Remove when everybody is beyond 1.15.19b8. - (function patch1015019008(s) { - if ( vAPI.firefox !== undefined ) { return; } - var match = /^(\d+)\.(\d+)\.(\d+)(?:\D+(\d+))?/.exec(s); - if ( match === null ) { return; } - var v = - parseInt(match[1], 10) * 1000 * 1000 * 1000 + - parseInt(match[2], 10) * 1000 * 1000 + - parseInt(match[3], 10) * 1000 + - (match[4] ? parseInt(match[4], 10) : 0); - if ( /rc\d+$/.test(s) ) { v += 100; } - if ( v > 1015019008 ) { return; } - if ( µb.getNetFilteringSwitch('http://behind-the-scene/') ) { return; } - var fwRules = [ - 'behind-the-scene * * noop', - 'behind-the-scene * image noop', - 'behind-the-scene * 3p noop', - 'behind-the-scene * inline-script noop', - 'behind-the-scene * 1p-script noop', - 'behind-the-scene * 3p-script noop', - 'behind-the-scene * 3p-frame noop' - ].join('\n'); - µb.sessionFirewall.fromString(fwRules, true); - µb.permanentFirewall.fromString(fwRules, true); - µb.savePermanentFirewallRules(); - µb.hnSwitches.fromString([ - 'no-large-media: behind-the-scene false' - ].join('\n'), true); - µb.saveHostnameSwitches(); - µb.toggleNetFilteringSwitch('http://behind-the-scene/', '', true); - })(lastVersion); - vAPI.storage.set({ version: vAPI.app.version }); }; @@ -206,7 +166,8 @@ var onUserSettingsReady = function(fetched) { µb.sessionFirewall.assign(µb.permanentFirewall); µb.permanentURLFiltering.fromString(fetched.urlFilteringString); µb.sessionURLFiltering.assign(µb.permanentURLFiltering); - µb.hnSwitches.fromString(fetched.hostnameSwitchesString); + µb.permanentSwitches.fromString(fetched.hostnameSwitchesString); + µb.sessionSwitches.assign(µb.permanentSwitches); // https://github.com/gorhill/uBlock/issues/1892 // For first installation on a battery-powered device, disable generic @@ -295,7 +256,8 @@ var onSelectedFilterListsLoaded = function() { ].join('\n'), 'urlFilteringString': '', 'hostnameSwitchesString': [ - 'no-large-media: behind-the-scene false' + 'no-large-media: behind-the-scene false', + 'no-scripting: behind-the-scene false' ].join('\n'), 'lastRestoreFile': '', 'lastRestoreTime': 0, diff --git a/src/js/storage.js b/src/js/storage.js index 1ae7357e0..94ba2a248 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -64,14 +64,6 @@ /******************************************************************************/ -µBlock.keyvalSetOne = function(key, val, callback) { - var bin = {}; - bin[key] = val; - vAPI.storage.set(bin, callback || this.noopFunc); -}; - -/******************************************************************************/ - µBlock.saveLocalSettings = (function() { let saveAfter = 4 * 60 * 1000; @@ -206,25 +198,33 @@ /******************************************************************************/ µBlock.savePermanentFirewallRules = function() { - this.keyvalSetOne('dynamicFilteringString', this.permanentFirewall.toString()); + vAPI.storage.set({ + dynamicFilteringString: this.permanentFirewall.toString() + }); }; /******************************************************************************/ µBlock.savePermanentURLFilteringRules = function() { - this.keyvalSetOne('urlFilteringString', this.permanentURLFiltering.toString()); + vAPI.storage.set({ + urlFilteringString: this.permanentURLFiltering.toString() + }); }; /******************************************************************************/ µBlock.saveHostnameSwitches = function() { - this.keyvalSetOne('hostnameSwitchesString', this.hnSwitches.toString()); + vAPI.storage.set({ + hostnameSwitchesString: this.permanentSwitches.toString() + }); }; /******************************************************************************/ µBlock.saveWhitelist = function() { - this.keyvalSetOne('netWhitelist', this.stringFromWhitelist(this.netWhitelist)); + vAPI.storage.set({ + netWhitelist: this.stringFromWhitelist(this.netWhitelist) + }); this.netWhitelistModifyTime = Date.now(); }; diff --git a/src/js/tab.js b/src/js/tab.js index 81368053f..35f90d9f5 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 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 @@ -610,10 +610,10 @@ vAPI.tabs.onPopupUpdated = (function() { // popunders. if ( popupType === 'popup' && - µb.hnSwitches.evaluateZ('no-popups', openerHostname) + µb.sessionSwitches.evaluateZ('no-popups', openerHostname) ) { logData = { - raw: 'no-popups: ' + µb.hnSwitches.z + ' true', + raw: 'no-popups: ' + µb.sessionSwitches.z + ' true', result: 1, source: 'switch' }; diff --git a/src/js/traffic.js b/src/js/traffic.js index 63dd2f7c9..40b6f460f 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -246,10 +246,10 @@ var onBeforeRootFrameRequest = function(details) { } // Permanently unrestricted? - if ( result === 0 && µb.hnSwitches.evaluateZ('no-strict-blocking', requestHostname) ) { + if ( result === 0 && µb.sessionSwitches.evaluateZ('no-strict-blocking', requestHostname) ) { result = 2; - if ( logEnabled === true ) { - logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: ' + µb.hnSwitches.z + ' true' }; + if ( logEnabled ) { + logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: ' + µb.sessionSwitches.z + ' true' }; } } diff --git a/src/js/ublock.js b/src/js/ublock.js index 13ee21818..1df50ed83 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 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 @@ -278,22 +278,20 @@ var matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ µBlock.changeUserSettings = function(name, value) { - var us = this.userSettings; + let us = this.userSettings; // Return all settings if none specified. if ( name === undefined ) { us = JSON.parse(JSON.stringify(us)); - us.noCosmeticFiltering = this.hnSwitches.evaluate('no-cosmetic-filtering', '*') === 1; - us.noLargeMedia = this.hnSwitches.evaluate('no-large-media', '*') === 1; - us.noRemoteFonts = this.hnSwitches.evaluate('no-remote-fonts', '*') === 1; - us.noScripting = this.hnSwitches.evaluate('no-scripting', '*') === 1; - us.noCSPReports = this.hnSwitches.evaluate('no-csp-reports', '*') === 1; + us.noCosmeticFiltering = this.sessionSwitches.evaluate('no-cosmetic-filtering', '*') === 1; + us.noLargeMedia = this.sessionSwitches.evaluate('no-large-media', '*') === 1; + us.noRemoteFonts = this.sessionSwitches.evaluate('no-remote-fonts', '*') === 1; + us.noScripting = this.sessionSwitches.evaluate('no-scripting', '*') === 1; + us.noCSPReports = this.sessionSwitches.evaluate('no-csp-reports', '*') === 1; return us; } - if ( typeof name !== 'string' || name === '' ) { - return; - } + if ( typeof name !== 'string' || name === '' ) { return; } if ( value === undefined ) { return us[name]; @@ -312,7 +310,7 @@ var matchBucket = function(url, hostname, bucket, start) { } // Change -- but only if the user setting actually exists. - var mustSave = us.hasOwnProperty(name) && value !== us[name]; + let mustSave = us.hasOwnProperty(name) && value !== us[name]; if ( mustSave ) { us[name] = value; } @@ -341,27 +339,29 @@ var matchBucket = function(url, hostname, bucket, start) { } break; case 'noCosmeticFiltering': - if ( this.hnSwitches.toggle('no-cosmetic-filtering', '*', value ? 1 : 0) ) { - this.saveHostnameSwitches(); - } - break; case 'noLargeMedia': - if ( this.hnSwitches.toggle('no-large-media', '*', value ? 1 : 0) ) { - this.saveHostnameSwitches(); - } - break; case 'noRemoteFonts': - if ( this.hnSwitches.toggle('no-remote-fonts', '*', value ? 1 : 0) ) { - this.saveHostnameSwitches(); - } - break; case 'noScripting': - if ( this.hnSwitches.toggle('no-scripting', '*', value ? 1 : 0) ) { - this.saveHostnameSwitches(); - } - break; case 'noCSPReports': - if ( this.hnSwitches.toggle('no-csp-reports', '*', value ? 1 : 0) ) { + let switchName; + switch ( name ) { + case 'noCosmeticFiltering': + switchName = 'no-cosmetic-filtering'; break; + case 'noLargeMedia': + switchName = 'no-large-media'; break; + case 'noRemoteFonts': + switchName = 'no-remote-fonts'; break; + case 'noScripting': + switchName = 'no-scripting'; break; + case 'noCSPReports': + switchName = 'no-csp-reports'; break; + default: + break; + } + if ( switchName === undefined ) { break; } + let switchState = value ? 1 : 0; + this.sessionSwitches.toggle(switchName, '*', switchState); + if ( this.permanentSwitches.toggle(switchName, '*', switchState) ) { this.saveHostnameSwitches(); } break; @@ -429,7 +429,7 @@ var matchBucket = function(url, hostname, bucket, start) { // (but not really) redundant rules led to this issue. µBlock.toggleFirewallRule = function(details) { - var requestType = details.requestType; + let requestType = details.requestType; if ( details.action !== 0 ) { this.sessionFirewall.setCell(details.srcHostname, details.desHostname, requestType, details.action); @@ -451,7 +451,7 @@ var matchBucket = function(url, hostname, bucket, start) { // Flush all cached `net` cosmetic filters if we are dealing with a // collapsible type: any of the cached entries could be a resource on the // target page. - var srcHostname = details.srcHostname; + let srcHostname = details.srcHostname; if ( (srcHostname !== '*') && (requestType === '*' || requestType === 'image' || requestType === '3p' || requestType === '3p-frame') @@ -466,22 +466,17 @@ var matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ µBlock.toggleURLFilteringRule = function(details) { - var changed = this.sessionURLFiltering.setRule( + let changed = this.sessionURLFiltering.setRule( details.context, details.url, details.type, details.action ); - - if ( !changed ) { - return; - } + if ( changed === false ) { return; } this.cosmeticFilteringEngine.removeFromSelectorCache(details.context, 'net'); - if ( !details.persist ) { - return; - } + if ( details.persist !== true ) { return; } changed = this.permanentURLFiltering.setRule( details.context, @@ -498,9 +493,13 @@ var matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ µBlock.toggleHostnameSwitch = function(details) { - if ( this.hnSwitches.toggleZ(details.name, details.hostname, !!details.deep, details.state) ) { - this.saveHostnameSwitches(); - } + let changed = this.sessionSwitches.toggleZ( + details.name, + details.hostname, + !!details.deep, + details.state + ); + if ( changed === false ) { return; } // Take action if needed switch ( details.name ) { @@ -517,6 +516,18 @@ var matchBucket = function(url, hostname, bucket, start) { } break; } + + if ( details.persist !== true ) { return; } + + changed = this.permanentSwitches.toggleZ( + details.name, + details.hostname, + !!details.deep, + details.state + ); + if ( changed ) { + this.saveHostnameSwitches(); + } }; /******************************************************************************/