diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index dc9c6444a..3287f5a65 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -415,6 +415,42 @@ "message":"Dynamic URL filtering", "description":"Small header to identify the dynamic URL filtering section" }, + "loggerStaticFilteringHeader":{ + "message":"Static filtering", + "description":"Small header to identify the static filtering section" + }, + "loggerStaticFilteringSentence":{ + "message":"{{action}} network requests of {{type}} {{br}}which URL address matches {{url}} {{br}}and which originates from {{origin}},{{br}}{{importance}} there is an exception filter.", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartBlock":{ + "message":"Block", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAllow":{ + "message":"Allow", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartType":{ + "message":"type “{{type}}”", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAnyType":{ + "message":"any type", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAnyOrigin":{ + "message":"anywhere", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartNotImportant":{ + "message":"except when", + "description":"Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartImportant":{ + "message":"even if", + "description":"Used in the static filtering wizard" + }, "aboutChangelog":{ "message":"Change log", "description":"English: Change log" diff --git a/src/css/common.css b/src/css/common.css index 31eec4550..7301a441b 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -17,6 +17,28 @@ body { color: black; font: 14px/1.3 sans-serif; } +button.important { + padding: 5px; + border: 1px solid transparent; + border-color: #ffcc7f #ffcc7f hsl(36, 100%, 73%); + border-radius: 3px; + background-color: hsl(36, 100%, 75%); + background-image: linear-gradient(#ffdca8, #ffcc7f); + background-repeat: repeat-x; + color: #222; + opacity: 0.8; + } +button.important[disabled], +button.important.disabled { + border-color: #dddddd #dddddd hsl(36, 0%, 85%); + background-color: hsl(36, 0%, 72%); + background-image: linear-gradient(#f2f2f2, #dddddd); + color: #888; + pointer-events: none; + } +button.important:hover { + opacity: 1.0; + } .hiddenFileInput { height: 0; visibility: hidden; diff --git a/src/css/dashboard-common.css b/src/css/dashboard-common.css index 5edbc0942..b29172063 100644 --- a/src/css/dashboard-common.css +++ b/src/css/dashboard-common.css @@ -25,29 +25,6 @@ div > p:last-child { margin-bottom: 0; } -button.important { - padding: 5px; - border: 1px solid transparent; - border-color: #ffcc7f #ffcc7f hsl(36, 100%, 73%); - border-radius: 3px; - background-color: hsl(36, 100%, 75%); - background-image: linear-gradient(#ffdca8, #ffcc7f); - background-repeat: repeat-x; - color: #222; - opacity: 0.8; - } -button.important[disabled], -button.important.disabled { - border-color: #dddddd #dddddd hsl(36, 0%, 85%); - background-color: hsl(36, 0%, 72%); - background-image: linear-gradient(#f2f2f2, #dddddd); - color: #888; - pointer-events: none; - } -button.important:hover { - opacity: 1.0; - } - .para { width: 40em; } diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 6ae5ff4fe..54f0b7cf1 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -286,6 +286,7 @@ body[dir="rtl"] #popupContainer > div { top: 0; z-index: 400; } + #urlFilteringMenu .dialog { background-color: white; border: 2px solid white; @@ -296,24 +297,23 @@ body[dir="rtl"] #popupContainer > div { transform-style: preserve-3d; width: 80%; } -#urlFilteringMenu .dialog table { - border: 0; - border-collapse: collapse; - table-layout: fixed; - width: 100%; - } -#urlFilteringMenu .dialog table > colgroup > col:nth-of-type(1) { - width: 3.8em; - } -#urlFilteringMenu .dialog table > colgroup > col:nth-of-type(2) { + +#urlFilteringMenu .dialog p { + line-height: 2em; } -#urlFilteringMenu .dialog td { - border: 0; - padding: 0; - vertical-align: middle; - } -#urlFilteringMenu .dialog > table.toolbar td.preview { +#urlFilteringMenu .dialog select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-color: #f4f4f4; + border: 1px solid #ddd; + max-width: 75%; + outline: none; + padding: 0.2em; +} + +#urlFilteringMenu .dialog > div.preview { /* http://lea.verou.me/css3patterns/ */ background-color: #aaa; background-image: @@ -337,20 +337,83 @@ body[dir="rtl"] #popupContainer > div { background-size: 18px 18px; text-align: center; } -#urlFilteringMenu .dialog > table.toolbar td.preview > * { +#urlFilteringMenu .dialog > div.preview > * { max-width: 100%; max-height: 40vh; } -#urlFilteringMenu .dialog > table.toolbar select { - font: 14px; - height: 2em; + +#urlFilteringMenu .dialog table { + border: 0; + border-collapse: collapse; + table-layout: fixed; + width: 100%; } -#urlFilteringMenu .dialog > table.toolbar .fa { +#urlFilteringMenu .dialog table > colgroup > col:nth-of-type(1) { + width: 3.8em; + } +#urlFilteringMenu .dialog table > colgroup > col:nth-of-type(2) { + } + +#urlFilteringMenu .dialog td { + border: 0; + padding: 0; + vertical-align: middle; + } +#urlFilteringMenu .dialog > div.headers { + border-bottom: 1px solid #888; + position: relative; + } +#urlFilteringMenu .dialog > div.headers > span.header { + background-color: #eee; + border: 1px solid #aaa; + border-bottom: 1px solid #888; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + color: #888; + cursor: pointer; + display: inline-block; + font-size: small; + line-height: 2em; + margin-left: 0.5em; + padding: 0 1em; + position: relative; + text-align: center; + top: 1px; + } +#urlFilteringMenu .dialog > div.headers > span.header.selected { + background-color: white; + border-color: #888; + border-bottom: 1px solid white; + color: black; + } +#urlFilteringMenu .dialog > div.headers > span.tools { + display: inline-block; + position: absolute; + right: 0; + top: 50%; + transform: translate(0, -50%); + } +#urlFilteringMenu .dialog > div.headers > span.tools > span { cursor: pointer; font-size: 1.2em; text-align: center; } -#urlFilteringMenu .dialog > table.toolbar .fa.save { +#urlFilteringMenu .dialog > div.containers { + height: 40vh; + overflow: hidden; + overflow-y: auto; + } +#urlFilteringMenu .dialog > div.containers > div { + display: none; + } +#urlFilteringMenu .dialog > div.containers > div.selected { + display: block; + } +#urlFilteringMenu .dialog > div.containers > div.dynamic > table.toolbar select { + font: 14px; + height: 2em; + } +#urlFilteringMenu .dialog > div.containers > div.dynamic > table.toolbar #saveRules { background-color: #ffe; border: 1px solid #ddc; border-radius: 4px; @@ -360,49 +423,37 @@ body[dir="rtl"] #popupContainer > div { padding: 0.25em 0.5em; visibility: hidden; } -body.dirty #urlFilteringMenu .dialog > table.toolbar .fa.save { +body.dirty #urlFilteringMenu .dialog > div.containers > div.dynamic > table.toolbar #saveRules { visibility: visible; } -#urlFilteringMenu .dialog > table.toolbar .fa.save:hover { +#urlFilteringMenu .dialog > div.containers > div.dynamic > table.toolbar #saveRules:hover { color: black; } -#urlFilteringMenu .dialog > table.toolbar tr.entry { +#urlFilteringMenu .dialog > div.containers > div.dynamic > table.toolbar tr.entry { display: none; } -#urlFilteringMenu .dialog > div.entries { - max-height: 30vh; - overflow: hidden; - overflow-y: auto; - } -#urlFilteringMenu .dialog > div.header { - background-color: #666; - color: white; - font-size: smaller; - padding: 2px; - text-align: center; - } -#urlFilteringMenu .dialog > div.entries tr.entry { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry { background-color: #e6e6e6; border: 0; border-bottom: 1px solid white; font-size: 13px; } -#urlFilteringMenu .dialog > div.entries tr.entry:hover { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry:hover { background-color: #f0f0f0; } -#urlFilteringMenu .dialog > div.entries tr.entry > td:first-of-type { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td:first-of-type { border: 0; border-right: 1px solid white; text-align: center; } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action { background-color: transparent; border: 0; cursor: pointer; height: 2em; width: 100%; } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span { background-color: transparent; border: 0; display: inline-block; @@ -411,67 +462,79 @@ body.dirty #urlFilteringMenu .dialog > table.toolbar .fa.save { visibility: hidden; width: 33.33%; } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.allow { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.allow { background-color: rgba(0, 160, 0, 0.3); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.allow { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.allow { background-color: rgba(255, 194, 57, 0.4); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.noop { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.noop { background-color: rgba(108, 108, 108, 0.3); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.noop { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.noop { background-color: rgba(96, 96, 96, 0.4); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.block { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.block { background-color: rgba(192, 0, 0, 0.3); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.block { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.block { background-color: rgba(0, 19, 110, 0.4); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.own.allow { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.allow { background-color: rgba(0, 160, 0, 1); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.own.allow { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.allow { background-color: rgba(255, 194, 57, 1); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.own.noop { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.noop { background-color: rgba(108, 108, 108, 1); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.own.block { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.block { background-color: rgba(192, 0, 0, 1); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action.own.block { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.block { background-color: rgba(0, 19, 110, 1); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action:not(.own):hover > span { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action:not(.own):hover > span { opacity: 0.2; visibility: visible; } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action:not(.own):hover > span:hover { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action:not(.own):hover > span:hover { opacity: 0.75; } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span.allow { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.allow { background-color: rgb(0, 160, 0); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span.allow { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.allow { background-color: rgb(255, 194, 57); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span.noop { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.noop { background-color: rgb(108, 108, 108); } -#urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span.block { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.block { background-color: rgb(192, 0, 0); } -body.colorBlind #urlFilteringMenu .dialog > div.entries tr.entry > td > div.action > span.block { +body.colorBlind #urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.block { background-color: rgb(0, 19, 110); } -#urlFilteringMenu .dialog > div.entries tr.entry > td.url { +#urlFilteringMenu .dialog > div.containers > div.dynamic tr.entry > td.url { overflow: hidden; padding-left: 4px; text-overflow: ellipsis; white-space: nowrap; } +#urlFilteringMenu .dialog > div.containers > div.static > p { + margin: 0.75em 0; + } +#urlFilteringMenu .dialog > div.containers > div.static textarea { + box-sizing: border-box; + height: 6em; + resize: none; + width: 100%; + } +#urlFilteringMenu .dialog > div.containers > div.static > p:nth-of-type(2) { + text-align: center; + } .hide { display: none; } diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 132f74f6b..dcfe894f3 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -197,8 +197,8 @@ var createRow = function(layout) { var tr = trJunkyard.pop(); if ( tr ) { tr.className = ''; - tr.removeAttribute('data-context'); - tr.removeAttribute('data-frame'); + tr.removeAttribute('data-hn-page'); + tr.removeAttribute('data-hn-frame'); } else { tr = document.createElement('tr'); } @@ -270,10 +270,10 @@ var renderNetLogEntry = function(tr, entry) { // Contexts if ( entry.d3 ) { - tr.setAttribute('data-context', entry.d3); + tr.setAttribute('data-hn-page', entry.d3); } if ( entry.d4 ) { - tr.setAttribute('data-frame', entry.d4); + tr.setAttribute('data-hn-frame', entry.d4); } // Cosmetic filter? @@ -590,12 +590,19 @@ var onMaxEntriesChanged = function() { /******************************************************************************/ /******************************************************************************/ -var urlFilteringDialog = (function() { +var filteringDialog = (function() { + var targetRow = null; var dialog = null; - var selectContext = null; - var selectType = null; + var createdStaticFilters = {}; + + var targetType; var targetURLs = []; - var tabId = ''; + var targetFrameHostname; + var targetPageHostname; + var targetTabId; + var targetDomain; + var targetPageDomain; + var targetFrameDomain; var removeAllChildren = function(node) { while ( node.firstChild ) { @@ -603,11 +610,23 @@ var urlFilteringDialog = (function() { } }; - var uglyTypeFromSelector = function() { - var prettyType = selectType.value; + var uglyTypeFromSelector = function(pane) { + var prettyType = selectValue('select.type.' + pane); return uglyRequestTypes[prettyType] || prettyType; }; + var selectNode = function(selector) { + return dialog.querySelector(selector); + }; + + var selectValue = function(selector) { + return selectNode(selector).value || ''; + }; + + var staticFilterNode = function() { + return dialog.querySelector('div.containers > div.static textarea'); + }; + var onColorsReady = function(response) { document.body.classList.toggle('dirty', response.dirty); var colorEntries = response.colors; @@ -617,7 +636,7 @@ var urlFilteringDialog = (function() { continue; } colorEntry = colorEntries[url]; - node = urlFilteringMenu.querySelector('.entry .action[data-url="' + url + '"]'); + node = dialog.querySelector('.dynamic .entry .action[data-url="' + url + '"]'); if ( node === null ) { continue; } @@ -631,12 +650,54 @@ var urlFilteringDialog = (function() { var colorize = function() { messager.send({ what: 'getURLFilteringData', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), urls: targetURLs, - type: uglyTypeFromSelector() + type: uglyTypeFromSelector('dynamic') }, onColorsReady); }; + var parseStaticInputs = function() { + var filter = ''; + var value; + value = selectValue('select.static.action'); + if ( value !== '' ) { + filter = '@@'; + } + value = selectValue('select.static.url'); + if ( value !== '' ) { + filter += '||' + value; + } + var options = []; + if ( selectValue('select.static.importance') !== '' ) { + options.push('important'); + } + value = selectValue('select.static.type'); + if ( value !== '' ) { + options.push(uglyTypeFromSelector('static')); + } + value = selectValue('select.static.origin'); + if ( value !== '' ) { + if ( value === targetDomain ) { + options.push('first-party'); + } else { + options.push('domain=' + value); + } + } + if ( options.length ) { + filter += '$' + options.join(','); + } + staticFilterNode().value = filter; + updateWidgets(); + }; + + var updateWidgets = function() { + var value = staticFilterNode().value; + dialog.querySelector('#createStaticFilter').classList.toggle( + 'disabled', + createdStaticFilters.hasOwnProperty(value) || value === '' + ); + }; + var onClick = function(ev) { var target = ev.target; @@ -649,14 +710,47 @@ var urlFilteringDialog = (function() { ev.stopPropagation(); var tcl = target.classList; + var value; + + // Select a mode + if ( tcl.contains('header') ) { + if ( tcl.contains('selected') ) { + return; + } + uDom('.header').removeClass('selected'); + uDom('.container').removeClass('selected'); + value = target.getAttribute('data-container'); + uDom('.header.' + value).addClass('selected'); + uDom('.container.' + value).addClass('selected'); + return; + } + + // Create static filter + if ( target.id === 'createStaticFilter' ) { + value = staticFilterNode().value; + // Avoid duplicates + if ( createdStaticFilters.hasOwnProperty(value) ) { + return; + } + createdStaticFilters[value] = true; + if ( value !== '' ) { + var d = new Date(); + messager.send({ + what: 'createUserFilter', + filters: '! ' + d.toLocaleString() + ' ' + targetPageDomain + '\n' + value + }); + } + updateWidgets(); + return; + } // Save url filtering rule(s) - if ( tcl.contains('save') ) { + if ( target.id === 'saveRules' ) { messager.send({ what: 'saveURLFilteringRules', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), urls: targetURLs, - type: uglyTypeFromSelector() + type: uglyTypeFromSelector('dynamic') }, colorize); return; } @@ -667,9 +761,9 @@ var urlFilteringDialog = (function() { if ( tcl.contains('action') ) { messager.send({ what: 'setURLFilteringRule', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), url: target.getAttribute('data-url'), - type: uglyTypeFromSelector(), + type: uglyTypeFromSelector('dynamic'), action: 0, persist: persist }, colorize); @@ -680,9 +774,9 @@ var urlFilteringDialog = (function() { if ( tcl.contains('allow') ) { messager.send({ what: 'setURLFilteringRule', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), url: target.parentNode.getAttribute('data-url'), - type: uglyTypeFromSelector(), + type: uglyTypeFromSelector('dynamic'), action: 2, persist: persist }, colorize); @@ -693,9 +787,9 @@ var urlFilteringDialog = (function() { if ( tcl.contains('noop') ) { messager.send({ what: 'setURLFilteringRule', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), url: target.parentNode.getAttribute('data-url'), - type: uglyTypeFromSelector(), + type: uglyTypeFromSelector('dynamic'), action: 3, persist: persist }, colorize); @@ -706,9 +800,9 @@ var urlFilteringDialog = (function() { if ( tcl.contains('block') ) { messager.send({ what: 'setURLFilteringRule', - context: selectContext.value, + context: selectValue('select.dynamic.origin'), url: target.parentNode.getAttribute('data-url'), - type: uglyTypeFromSelector(), + type: uglyTypeFromSelector('dynamic'), action: 1, persist: persist }, colorize); @@ -719,7 +813,7 @@ var urlFilteringDialog = (function() { if ( tcl.contains('reload') ) { messager.send({ what: 'reloadTab', - tabId: tabId + tabId: targetTabId }); return; } @@ -728,7 +822,7 @@ var urlFilteringDialog = (function() { if ( tcl.contains('picker') ) { messager.send({ what: 'launchElementPicker', - tabId: tabId, + tabId: targetTabId, targetURL: 'img\t' + targetURLs[0], select: true }); @@ -736,7 +830,34 @@ var urlFilteringDialog = (function() { } }; + var onSelectChange = function(ev) { + var target = ev.target; + var tcl = target.classList; + + if ( tcl.contains('dynamic') ) { + colorize(); + return; + } + + if ( tcl.contains('static') ) { + parseStaticInputs(); + return; + } + }; + + var onInputChange = function() { + updateWidgets(); + }; + var createPreview = function(type, url) { + // First, whether picker can be used + dialog.querySelector('.picker').classList.toggle( + 'hide', + targetTabId === noTabId || + targetType !== 'image' || + /(?:^| )[dlsu]b(?: |$)/.test(targetRow.className) + ); + var preview = null; if ( type === 'image' ) { @@ -744,9 +865,7 @@ var urlFilteringDialog = (function() { preview.setAttribute('src', url); } - // More... - - var container = dialog.querySelector('table.toolbar td.preview'); + var container = dialog.querySelector('div.preview'); container.classList.toggle('hide', preview === null); if ( preview === null ) { return; @@ -754,94 +873,53 @@ var urlFilteringDialog = (function() { container.appendChild(preview); }; - var toggleOn = function(ev) { - if ( dialog !== null ) { - return toggleOff(); + // Build list of candidate URLs + var createTargetURLs = function(url) { + var urls = []; + var matches = reRFC3986.exec(url); + if ( matches === null || !matches[1] || !matches[2] ) { + return urls; } - dialog = urlFilteringMenu.querySelector('.dialog'); - selectContext = dialog.querySelector('.context'); - selectType = dialog.querySelector('.type'); - - var td = ev.target; - var tr = td.parentElement; - var cells = tr.cells; - - var context = tr.getAttribute('data-context'); - if ( !context ) { - return toggleOff(); - } - - var type = cells[4].textContent.trim(); - if ( !type ) { - return toggleOff(); - } - - tabId = tabIdFromClassName(tr.className); - - var pos, option; - - // Fill context selector - removeAllChildren(selectContext); - for (;;) { - option = document.createElement('option'); - option.textContent = context; - option.setAttribute('value', context); - pos = context.indexOf('.'); - selectContext.appendChild(option); + // Shortest URL for a valid URL filtering rule + var rootURL = matches[1] + matches[2]; + urls.unshift(rootURL); + var path = matches[3] || ''; + var pos = path.charAt(0) === '/' ? 1 : 0; + while ( pos < path.length ) { + pos = path.indexOf('/', pos + 1); if ( pos === -1 ) { - break; + pos = path.length; } - context = context.slice(pos + 1); + urls.unshift(rootURL + path.slice(0, pos)); } - option = document.createElement('option'); + var query = matches[4] || ''; + if ( query !== '') { + urls.unshift(rootURL + path + query); + } + return urls; + }; + + // Fill dynamic URL filtering pane + var fillDynamicPane = function() { + var select; + // Fill context selector + select = selectNode('select.dynamic.origin'); + removeAllChildren(select); + fillOriginSelect(select, targetPageHostname, targetPageDomain); + var option = document.createElement('option'); option.textContent = '*'; option.setAttribute('value', '*'); - selectContext.appendChild(option); + select.appendChild(option); // Fill type selector - selectType.options[0].textContent = type; - selectType.options[0].setAttribute('value', type); - selectType.selectedIndex = 0; + select = selectNode('select.dynamic.type'); + select.options[0].textContent = targetType; + select.options[0].setAttribute('value', targetType); + select.selectedIndex = 0; - // Extract data needed to build URL filtering menu - var candidateURL = cells[5].textContent; - var matches = reRFC3986.exec(candidateURL); - if ( matches === null || !matches[1] || !matches[2] ) { - return toggleOff(); - } - - uDom(dialog).descendants('.picker').toggleClass( - 'hide', - tr.classList.contains('tab_bts') || - type !== 'image' || - /(?:^| )[dlsu]b(?: |$)/.test(tr.className) - ); - - // Shortest URL which for a valid URL filtering rule - var candidateRootURL = matches[1] + matches[2]; - targetURLs.unshift(candidateRootURL); - var candidatePath = matches[3] || ''; - pos = candidatePath.charAt(0) === '/' ? 1 : 0; - while ( pos < candidatePath.length ) { - pos = candidatePath.indexOf('/', pos + 1); - if ( pos === -1 ) { - pos = candidatePath.length; - } - targetURLs.unshift(candidateRootURL + candidatePath.slice(0, pos)); - } - var candidateQuery = matches[4] || ''; - if ( candidateQuery !== '') { - targetURLs.unshift(candidateRootURL + candidatePath + candidateQuery); - } - - // Create preview whenever possible - createPreview(type, targetURLs[0]); - - // Fill menu + // Fill entries var menuEntryTemplate = dialog.querySelector('table.toolbar tr.entry'); - var tbody = dialog.querySelector('div.entries tbody'); - - // Adding URL filtering rules + var tbody = dialog.querySelector('div.dynamic table.entries tbody'); var url, menuEntry; for ( var i = 0; i < targetURLs.length; i++ ) { url = targetURLs[i]; @@ -852,30 +930,169 @@ var urlFilteringDialog = (function() { } colorize(); + }; + var fillOriginSelect = function(select, hostname, domain) { + var option, pos; + var value = hostname; + for (;;) { + option = document.createElement('option'); + option.setAttribute('value', value); + option.textContent = value; + select.appendChild(option); + if ( value === domain ) { + break; + } + pos = value.indexOf('.'); + if ( pos === -1 ) { + break; + } + value = value.slice(pos + 1); + } + }; + + // Fill static filtering pane + var fillStaticPane = function() { + var template = vAPI.i18n('loggerStaticFilteringSentence'); + var rePlaceholder = /\{\{[^}]+?\}\}/g; + var nodes = []; + var match, pos = 0; + var select, option, i, value; + for (;;) { + match = rePlaceholder.exec(template); + if ( match === null ) { + break; + } + if ( pos !== match.index ) { + nodes.push(document.createTextNode(template.slice(pos, match.index))); + } + pos = rePlaceholder.lastIndex; + switch ( match[0] ) { + case '{{br}}': + nodes.push(document.createElement('br')); + break; + + case '{{action}}': + select = document.createElement('select'); + select.className = 'static action'; + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartBlock'); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', '@@'); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAllow'); + select.appendChild(option); + nodes.push(select); + break; + + case '{{type}}': + select = document.createElement('select'); + select.className = 'static type'; + option = document.createElement('option'); + option.setAttribute('value', targetType); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartType').replace('{{type}}', targetType); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyType'); + select.appendChild(option); + nodes.push(select); + break; + + case '{{url}}': + select = document.createElement('select'); + select.className = 'static url'; + for ( i = 0; i < targetURLs.length; i++ ) { + value = targetURLs[i].replace(/^[a-z]+:\/\//, ''); + option = document.createElement('option'); + option.setAttribute('value', value); + option.textContent = value; + select.appendChild(option); + } + nodes.push(select); + break; + + case '{{origin}}': + select = document.createElement('select'); + select.className = 'static origin'; + fillOriginSelect(select, targetFrameHostname, targetFrameDomain); + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyOrigin'); + select.appendChild(option); + nodes.push(select); + break; + + case '{{importance}}': + select = document.createElement('select'); + select.className = 'static importance'; + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartNotImportant'); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', 'important'); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartImportant'); + select.appendChild(option); + nodes.push(select); + break; + + default: + break; + } + } + if ( pos < template.length ) { + nodes.push(document.createTextNode(template.slice(pos))); + } + var parent = dialog.querySelector('div.containers > div.static > p:first-of-type'); + removeAllChildren(parent); + for ( i = 0; i < nodes.length; i++ ) { + parent.appendChild(nodes[i]); + } + parseStaticInputs(); + }; + + var fillDialog = function(domains) { + targetDomain = domains[0]; + targetPageDomain = domains[1]; + targetFrameDomain = domains[2]; + + createPreview(targetType, targetURLs[0]); + fillDynamicPane(); + fillStaticPane(); document.body.appendChild(urlFilteringMenu); urlFilteringMenu.addEventListener('click', onClick, true); - selectContext.addEventListener('change', colorize); - selectType.addEventListener('change', colorize); + urlFilteringMenu.addEventListener('change', onSelectChange, true); + urlFilteringMenu.addEventListener('input', onInputChange, true); + }; + + var toggleOn = function(ev) { + dialog = urlFilteringMenu.querySelector('.dialog'); + targetRow = ev.target.parentElement; + targetTabId = tabIdFromClassName(targetRow.className); + targetType = targetRow.cells[4].textContent.trim() || ''; + targetURLs = createTargetURLs(targetRow.cells[5].textContent); + targetPageHostname = targetRow.getAttribute('data-hn-page') || ''; + targetFrameHostname = targetRow.getAttribute('data-hn-frame') || ''; + + // We need the root domain names for best user experience. + messager.send({ + what: 'getDomainNames', + targets: [targetURLs[0], targetPageHostname, targetFrameHostname] + }, fillDialog); }; var toggleOff = function() { - if ( selectContext !== null ) { - selectContext.removeEventListener('change', colorize); - selectContext = null; - } - if ( selectType !== null ) { - selectType.removeEventListener('change', colorize); - selectType = null; - } - if ( dialog !== null ) { - uDom(dialog).descendants('table.toolbar td.preview > *').remove(); - uDom(dialog).descendants('div.entries tr').remove(); - dialog = null; - } - urlFilteringMenu.removeEventListener('click', onClick, true); - document.body.removeChild(urlFilteringMenu); + removeAllChildren(dialog.querySelector('div.preview')); + removeAllChildren(dialog.querySelector('div.dynamic table.entries tbody')); + dialog = null; + targetRow = null; targetURLs = []; + urlFilteringMenu.removeEventListener('click', onClick, true); + urlFilteringMenu.removeEventListener('change', onSelectChange, true); + urlFilteringMenu.removeEventListener('input', onInputChange, true); + document.body.removeChild(urlFilteringMenu); }; return { @@ -1186,7 +1403,7 @@ uDom.onLoad(function() { uDom('#clear').on('click', clearBuffer); uDom('#maxEntries').on('change', onMaxEntriesChanged); uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); - uDom('#content').on('click', 'tr.cat_net > td:nth-of-type(4)', urlFilteringDialog.toggleOn); + uDom('#content').on('click', 'tr.cat_net > td:nth-of-type(4)', filteringDialog.toggleOn); }); /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index f9c050d5a..da89c22f7 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -32,9 +32,29 @@ /******************************************************************************/ -var onMessage = function(request, sender, callback) { - var µb = µBlock; +var µb = µBlock; +/******************************************************************************/ + +var getDomainNames = function(targets) { + var out = []; + var µburi = µb.URI; + var target, domain; + for ( var i = 0; i < targets.length; i++ ) { + target = targets[i]; + if ( target.indexOf('/') !== -1 ) { + domain = µburi.domainFromURI(target) || ''; + } else { + domain = µburi.domainFromHostname(target) || target; + } + out.push(domain); + } + return out; +}; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'getAssetContent': @@ -71,6 +91,10 @@ var onMessage = function(request, sender, callback) { } break; + case 'createUserFilter': + µb.appendUserFilters(request.filters); + break; + case 'forceUpdateAssets': µb.assetUpdater.force(); break; @@ -79,6 +103,10 @@ var onMessage = function(request, sender, callback) { response = {name: vAPI.app.name, version: vAPI.app.version}; break; + case 'getDomainNames': + response = getDomainNames(request.targets); + break; + case 'getUserSettings': response = µb.userSettings; break; @@ -601,10 +629,6 @@ var onMessage = function(request, sender, callback) { var response; switch ( request.what ) { - case 'createUserFilter': - µb.appendUserFilters(request.filters); - break; - case 'elementPickerEprom': µb.epickerEprom = request; break; diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index edcaf7a3f..fa5b8bca3 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -1294,7 +1294,10 @@ FilterParser.prototype.parseOptType = function(raw, not) { /******************************************************************************/ -FilterParser.prototype.parseOptParty = function(not) { +FilterParser.prototype.parseOptParty = function(firstParty, not) { + if ( firstParty ) { + not = !not; + } if ( not ) { this.firstParty = true; } else { @@ -1330,7 +1333,7 @@ FilterParser.prototype.parseOptions = function(s) { opt = opt.slice(1); } if ( opt === 'third-party' ) { - this.parseOptParty(not); + this.parseOptParty(false, not); continue; } if ( opt === 'elemhide' ) { @@ -1358,6 +1361,10 @@ FilterParser.prototype.parseOptions = function(s) { this.important = Important; continue; } + if ( opt === 'first-party' ) { + this.parseOptParty(true, not); + continue; + } this.unsupported = true; break; } diff --git a/src/logger-ui.html b/src/logger-ui.html index 05c7d4033..0e9246112 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -43,29 +43,40 @@
- | |
- | - - - |
- |
+ | + + + + |
+ |
+ +
+