mirror of https://github.com/gorhill/uBlock.git
Isolate element picker dialog from page content world
Related issues: - https://github.com/gorhill/uBlock/issues/3497 - https://github.com/uBlockOrigin/uBlock-issues/issues/1215 To solve above issues, the element picker's dialog is now isolated from the page content in which it is embedded. The highly interactive, mouse-driven part of the element picker is still visible by the page content.
This commit is contained in:
parent
43dba2bd0e
commit
9eb455ab5e
|
@ -102,8 +102,14 @@ vAPI.messaging = {
|
|||
},
|
||||
disconnectListenerBound: null,
|
||||
|
||||
// 2020-09-01:
|
||||
// In Firefox, `details instanceof Object` resolves to `false` despite
|
||||
// `details` being a valid object. Consequently, falling back to use
|
||||
// `typeof details`.
|
||||
// This is an issue which surfaced when the element picker code was
|
||||
// revisited to isolate the picker dialog DOM from the page DOM.
|
||||
messageListener: function(details) {
|
||||
if ( details instanceof Object === false ) { return; }
|
||||
if ( typeof details !== 'object' || details === null ) { return; }
|
||||
|
||||
// Response to specific message previously sent
|
||||
if ( details.msgId !== undefined ) {
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
html#ublock0-epicker,
|
||||
#ublock0-epicker body {
|
||||
background: transparent;
|
||||
color: black;
|
||||
cursor: not-allowed;
|
||||
font: 12px sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
}
|
||||
#ublock0-epicker :focus {
|
||||
outline: none;
|
||||
}
|
||||
#ublock0-epicker ul,
|
||||
#ublock0-epicker li,
|
||||
#ublock0-epicker div {
|
||||
display: block;
|
||||
}
|
||||
#ublock0-epicker #toolbar {
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#ublock0-epicker aside.moving #toolbar {
|
||||
cursor: grabbing;
|
||||
}
|
||||
#ublock0-epicker ul {
|
||||
margin: 0.25em 0 0 0;
|
||||
}
|
||||
#ublock0-epicker button {
|
||||
background-color: #ccc;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
#ublock0-epicker button:disabled {
|
||||
color: #999;
|
||||
background-color: #ccc;
|
||||
}
|
||||
#ublock0-epicker button:not(:disabled):hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#ublock0-epicker #create:not(:disabled) {
|
||||
background-color: hsl(36, 100%, 83%);
|
||||
border-color: hsl(36, 50%, 60%);
|
||||
}
|
||||
#ublock0-epicker #preview {
|
||||
float: left;
|
||||
}
|
||||
#ublock0-epicker body.preview #preview {
|
||||
background-color: hsl(204, 100%, 83%);
|
||||
border-color: hsl(204, 50%, 60%);
|
||||
}
|
||||
#ublock0-epicker section {
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child {
|
||||
border: 1px solid #aaa;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
#ublock0-epicker section.invalidFilter > div:first-child {
|
||||
border-color: red;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child > textarea {
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
color: #000;
|
||||
font: 11px monospace;
|
||||
height: 8em;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 2px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
#ublock0-epicker #resultsetCount {
|
||||
background-color: #aaa;
|
||||
bottom: 0;
|
||||
color: white;
|
||||
padding: 2px 4px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
#ublock0-epicker section.invalidFilter #resultsetCount {
|
||||
background-color: red;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child + div {
|
||||
direction: ltr;
|
||||
margin: 2px 0;
|
||||
text-align: right;
|
||||
}
|
||||
#ublock0-epicker ul {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters {
|
||||
max-height: 16em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters > li:first-of-type {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
#ublock0-epicker .changeFilter > li > span:nth-of-type(1) {
|
||||
font-weight: bold;
|
||||
}
|
||||
#ublock0-epicker .changeFilter > li > span:nth-of-type(2) {
|
||||
font-size: smaller;
|
||||
color: gray;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter {
|
||||
list-style-type: none;
|
||||
margin: 0 0 0 1em;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
font: 11px monospace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li.active {
|
||||
border: 1px dotted gray;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li:hover {
|
||||
background-color: white;
|
||||
}
|
||||
#ublock0-epicker aside {
|
||||
background-color: #eee;
|
||||
border: 1px solid #aaa;
|
||||
bottom: 4px;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
min-width: 24em;
|
||||
padding: 4px;
|
||||
position: fixed;
|
||||
right: 4px;
|
||||
width: calc(40% - 4px);
|
||||
}
|
||||
/**
|
||||
https://github.com/gorhill/uBlock/issues/3449
|
||||
https://github.com/uBlockOrigin/uBlock-issues/issues/55
|
||||
**/
|
||||
@keyframes startDialog {
|
||||
0% { opacity: 1.0; }
|
||||
60% { opacity: 1.0; }
|
||||
100% { opacity: 0.1; }
|
||||
}
|
||||
#ublock0-epicker body.paused > aside {
|
||||
opacity: 0.1;
|
||||
visibility: visible;
|
||||
z-index: 100;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside:not(:hover):not(.show) {
|
||||
animation-duration: 1.6s;
|
||||
animation-name: startDialog;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside.show {
|
||||
opacity: 1;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside.hide {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
html#ublock0-epicker,
|
||||
#ublock0-epicker body {
|
||||
background: transparent !important;
|
||||
box-sizing: border-box !important;
|
||||
color: black !important;
|
||||
font: 12px sans-serif !important;
|
||||
height: 100vh !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden !important;
|
||||
position: fixed !important;
|
||||
width: 100vw !important;
|
||||
}
|
||||
#ublock0-epicker :focus {
|
||||
outline: none !important;
|
||||
}
|
||||
#ublock0-epicker svg {
|
||||
cursor: crosshair !important;
|
||||
box-sizing: border-box;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
#ublock0-epicker .paused > svg {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
#ublock0-epicker svg > path:first-child {
|
||||
fill: rgba(0,0,0,0.5) !important;
|
||||
fill-rule: evenodd !important;
|
||||
}
|
||||
#ublock0-epicker svg > path + path {
|
||||
stroke: #F00 !important;
|
||||
stroke-width: 0.5px !important;
|
||||
fill: rgba(255,63,63,0.20) !important;
|
||||
}
|
||||
#ublock0-epicker body.zap svg > path + path {
|
||||
stroke: #FF0 !important;
|
||||
stroke-width: 0.5px !important;
|
||||
fill: rgba(255,255,63,0.20) !important;
|
||||
}
|
||||
#ublock0-epicker body.preview svg > path {
|
||||
fill: rgba(0,0,0,0.10) !important;
|
||||
}
|
||||
#ublock0-epicker body.preview svg > path + path {
|
||||
stroke: none !important;
|
||||
}
|
||||
#ublock0-epicker body > iframe {
|
||||
border: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
display: none !important;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
#ublock0-epicker body.paused > iframe {
|
||||
display: initial !important;
|
||||
}
|
250
src/epicker.html
250
src/epicker.html
|
@ -1,250 +0,0 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin Element Picker</title>
|
||||
<style>
|
||||
html#ublock0-epicker,
|
||||
#ublock0-epicker body {
|
||||
background: transparent !important;
|
||||
color: black !important;
|
||||
font: 12px sans-serif !important;
|
||||
height: 100% !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
#ublock0-epicker :focus {
|
||||
outline: none !important;
|
||||
}
|
||||
#ublock0-epicker ul,
|
||||
#ublock0-epicker li,
|
||||
#ublock0-epicker div {
|
||||
display: block !important;
|
||||
}
|
||||
#ublock0-epicker #toolbar {
|
||||
cursor: grab;
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#ublock0-epicker aside.moving #toolbar {
|
||||
cursor: grabbing;
|
||||
}
|
||||
#ublock0-epicker ul {
|
||||
margin: 0.25em 0 0 0 !important;
|
||||
}
|
||||
#ublock0-epicker button {
|
||||
background-color: #ccc !important;
|
||||
border: 1px solid #aaa !important;
|
||||
border-radius: 3px !important;
|
||||
box-sizing: border-box !important;
|
||||
box-shadow: none !important;
|
||||
color: #000 !important;
|
||||
cursor: pointer !important;
|
||||
opacity: 0.7 !important;
|
||||
padding: 4px 6px !important;
|
||||
}
|
||||
#ublock0-epicker button:disabled {
|
||||
color: #999 !important;
|
||||
background-color: #ccc !important;
|
||||
}
|
||||
#ublock0-epicker button:not(:disabled):hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
#ublock0-epicker #create:not(:disabled) {
|
||||
background-color: hsl(36, 100%, 83%) !important;
|
||||
border-color: hsl(36, 50%, 60%) !important;
|
||||
}
|
||||
#ublock0-epicker #preview {
|
||||
float: left !important;
|
||||
}
|
||||
#ublock0-epicker body.preview #preview {
|
||||
background-color: hsl(204, 100%, 83%) !important;
|
||||
border-color: hsl(204, 50%, 60%) !important;
|
||||
}
|
||||
#ublock0-epicker section {
|
||||
border: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
display: inline-block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child {
|
||||
border: 1px solid #aaa !important;
|
||||
margin: 0 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
#ublock0-epicker section.invalidFilter > div:first-child {
|
||||
border-color: red !important;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child > textarea {
|
||||
background-color: #fff !important;
|
||||
border: none !important;
|
||||
box-sizing: border-box !important;
|
||||
color: #000 !important;
|
||||
font: 11px monospace !important;
|
||||
height: 8em !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
padding: 2px !important;
|
||||
resize: none !important;
|
||||
width: 100% !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
#ublock0-epicker #resultsetCount {
|
||||
background-color: #aaa !important;
|
||||
bottom: 0 !important;
|
||||
color: white !important;
|
||||
padding: 2px 4px !important;
|
||||
position: absolute !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
#ublock0-epicker section.invalidFilter #resultsetCount {
|
||||
background-color: red !important;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child + div {
|
||||
direction: ltr !important;
|
||||
margin: 2px 0 !important;
|
||||
text-align: right !important;
|
||||
}
|
||||
#ublock0-epicker ul {
|
||||
padding: 0 !important;
|
||||
list-style-type: none !important;
|
||||
text-align: left !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters {
|
||||
max-height: 16em !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters > li:first-of-type {
|
||||
margin-bottom: 0.5em !important;
|
||||
}
|
||||
#ublock0-epicker .changeFilter > li > span:nth-of-type(1) {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
#ublock0-epicker .changeFilter > li > span:nth-of-type(2) {
|
||||
font-size: smaller !important;
|
||||
color: gray !important;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter {
|
||||
list-style-type: none !important;
|
||||
margin: 0 0 0 1em !important;
|
||||
overflow: hidden !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer !important;
|
||||
direction: ltr !important;
|
||||
font: 11px monospace !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li.active {
|
||||
border: 1px dotted gray;
|
||||
}
|
||||
#ublock0-epicker #candidateFilters .changeFilter li:hover {
|
||||
background-color: white !important;
|
||||
}
|
||||
#ublock0-epicker svg {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
cursor: crosshair !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
#ublock0-epicker .paused > svg {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
#ublock0-epicker svg > path:first-child {
|
||||
fill: rgba(0,0,0,0.5) !important;
|
||||
fill-rule: evenodd !important;
|
||||
}
|
||||
#ublock0-epicker svg > path + path {
|
||||
stroke: #F00 !important;
|
||||
stroke-width: 0.5px !important;
|
||||
fill: rgba(255,63,63,0.20) !important;
|
||||
}
|
||||
#ublock0-epicker body.zap svg > path + path {
|
||||
stroke: #FF0 !important;
|
||||
stroke-width: 0.5px !important;
|
||||
fill: rgba(255,255,63,0.20) !important;
|
||||
}
|
||||
#ublock0-epicker body.preview svg > path {
|
||||
fill: rgba(0,0,0,0.10) !important;
|
||||
}
|
||||
#ublock0-epicker body.preview svg > path + path {
|
||||
stroke: none !important;
|
||||
}
|
||||
#ublock0-epicker aside {
|
||||
background-color: #eee !important;
|
||||
border: 1px solid #aaa !important;
|
||||
bottom: 4px !important;
|
||||
box-sizing: border-box !important;
|
||||
min-width: 24em !important;
|
||||
padding: 4px !important;
|
||||
position: fixed !important;
|
||||
right: 4px !important;
|
||||
visibility: hidden !important;
|
||||
width: calc(40% - 4px) !important;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside {
|
||||
opacity: 0.1;
|
||||
visibility: visible !important;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
/**
|
||||
https://github.com/gorhill/uBlock/issues/3449
|
||||
https://github.com/uBlockOrigin/uBlock-issues/issues/55
|
||||
**/
|
||||
@keyframes startDialog {
|
||||
0% { opacity: 1.0; }
|
||||
60% { opacity: 1.0; }
|
||||
100% { opacity: 0.1; }
|
||||
}
|
||||
#ublock0-epicker body.paused > aside:not(:hover):not(.show) {
|
||||
animation-duration: 1.6s !important;
|
||||
animation-name: startDialog !important;
|
||||
animation-timing-function: linear !important;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside.show {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
#ublock0-epicker body.paused > aside.hide {
|
||||
opacity: 0.1 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body direction="{{bidi_dir}}">
|
||||
<svg><path d></path><path d></path></svg>
|
||||
<aside>
|
||||
<section>
|
||||
<div>
|
||||
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
|
||||
<div id="resultsetCount"></div>
|
||||
</div>
|
||||
<div id="toolbar">
|
||||
<div>
|
||||
<button id="preview" type="button">{{preview}}</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="create" type="button" disabled>{{create}}</button>
|
||||
<button id="pick" type="button">{{pick}}</button>
|
||||
<button id="quit" type="button">{{quit}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<ul id="candidateFilters">
|
||||
<li id="netFilters">
|
||||
<span>{{netFilters}}</span><ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
<li id="cosmeticFilters">
|
||||
<span>{{cosmeticFilters}}</span> <span>{{cosmeticFiltersHint}}</span>
|
||||
<ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</body>
|
|
@ -0,0 +1,499 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2014-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
|
||||
const epickerId = (( ) => {
|
||||
const url = new URL(self.location.href);
|
||||
return url.searchParams.get('epid');
|
||||
})();
|
||||
if ( epickerId === null ) { return; }
|
||||
|
||||
let epickerConnectionId;
|
||||
let filterHostname = '';
|
||||
let filterOrigin = '';
|
||||
let filterResultset = [];
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const $id = id => document.getElementById(id);
|
||||
const $stor = selector => document.querySelector(selector);
|
||||
const $storAll = selector => document.querySelectorAll(selector);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const filterFromTextarea = function() {
|
||||
const s = taCandidate.value.trim();
|
||||
if ( s === '' ) { return ''; }
|
||||
const pos = s.indexOf('\n');
|
||||
const filter = pos === -1 ? s.trim() : s.slice(0, pos).trim();
|
||||
staticFilteringParser.analyze(filter);
|
||||
staticFilteringParser.analyzeExtra();
|
||||
return staticFilteringParser.shouldDiscard() ? '!' : filter;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const userFilterFromCandidate = function(filter) {
|
||||
if ( filter === '' || filter === '!' ) { return; }
|
||||
|
||||
// Cosmetic filter?
|
||||
if ( filter.startsWith('##') ) {
|
||||
return filterHostname + filter;
|
||||
}
|
||||
|
||||
// Assume net filter
|
||||
const opts = [];
|
||||
|
||||
// If no domain included in filter, we need domain option
|
||||
if ( filter.startsWith('||') === false ) {
|
||||
opts.push(`domain=${filterHostname}`);
|
||||
}
|
||||
|
||||
if ( filterResultset.length !== 0 ) {
|
||||
const item = filterResultset[0];
|
||||
if ( item.opts ) {
|
||||
opts.push(item.opts);
|
||||
}
|
||||
}
|
||||
|
||||
if ( opts.length ) {
|
||||
filter += '$' + opts.join(',');
|
||||
}
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const candidateFromFilterChoice = function(filterChoice) {
|
||||
let { slot, filters } = filterChoice;
|
||||
let filter = filters[slot];
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/47
|
||||
for ( const elem of $storAll('#candidateFilters li') ) {
|
||||
elem.classList.remove('active');
|
||||
}
|
||||
|
||||
if ( filter === undefined ) { return ''; }
|
||||
|
||||
// For net filters there no such thing as a path
|
||||
if ( filter.startsWith('##') === false ) {
|
||||
$stor(`#netFilters li:nth-of-type(${slot+1})`)
|
||||
.classList.add('active');
|
||||
return filter;
|
||||
}
|
||||
|
||||
// At this point, we have a cosmetic filter
|
||||
|
||||
$stor(`#cosmeticFilters li:nth-of-type(${slot+1})`)
|
||||
.classList.add('active');
|
||||
|
||||
// Modifier means "target broadly". Hence:
|
||||
// - Do not compute exact path.
|
||||
// - Discard narrowing directives.
|
||||
// - Remove the id if one or more classes exist
|
||||
// TODO: should remove tag name too? ¯\_(ツ)_/¯
|
||||
if ( filterChoice.modifier ) {
|
||||
filter = filter.replace(/:nth-of-type\(\d+\)/, '');
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
||||
// Mind escaped periods: they do not denote a class identifier.
|
||||
if ( filter.charAt(2) === '#' ) {
|
||||
const pos = filter.search(/[^\\]\./);
|
||||
if ( pos !== -1 ) {
|
||||
filter = '##' + filter.slice(pos + 1);
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
// Return path: the target element, then all siblings prepended
|
||||
let selector = '', joiner = '';
|
||||
for ( ; slot < filters.length; slot++ ) {
|
||||
filter = filters[slot];
|
||||
// Remove all classes when an id exists.
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
||||
// Mind escaped periods: they do not denote a class identifier.
|
||||
if ( filter.charAt(2) === '#' ) {
|
||||
filter = filter.replace(/([^\\])\..+$/, '$1');
|
||||
}
|
||||
selector = filter.slice(2) + joiner + selector;
|
||||
// Stop at any element with an id: these are unique in a web page
|
||||
if ( filter.startsWith('###') ) { break; }
|
||||
// Stop if current selector matches only one element on the page
|
||||
if ( document.querySelectorAll(selector).length === 1 ) { break; }
|
||||
joiner = ' > ';
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2519
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
||||
if (
|
||||
slot === filters.length &&
|
||||
selector.startsWith('body > ') === false &&
|
||||
document.querySelectorAll(selector).length > 1
|
||||
) {
|
||||
selector = 'body > ' + selector;
|
||||
}
|
||||
|
||||
return '##' + selector;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onCandidateChanged = function() {
|
||||
const filter = filterFromTextarea();
|
||||
const bad = filter === '!';
|
||||
$stor('section').classList.toggle('invalidFilter', bad);
|
||||
$id('create').disabled = bad;
|
||||
if ( bad ) {
|
||||
$id('resultsetCount').textContent = 'E';
|
||||
$id('create').setAttribute('disabled', '');
|
||||
}
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogSetFilter',
|
||||
filter,
|
||||
compiled: filter.startsWith('##')
|
||||
? staticFilteringParser.result.compiled
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onPreviewClicked = function() {
|
||||
const state = pickerBody.classList.toggle('preview');
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogPreview',
|
||||
state,
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onCreateClicked = function() {
|
||||
const candidate = filterFromTextarea();
|
||||
const filter = userFilterFromCandidate(candidate);
|
||||
if ( filter !== undefined ) {
|
||||
vAPI.messaging.send('elementPicker', {
|
||||
what: 'createUserFilter',
|
||||
autoComment: true,
|
||||
filters: filter,
|
||||
origin: filterOrigin,
|
||||
pageDomain: filterHostname,
|
||||
killCache: /^#[$?]?#/.test(candidate) === false,
|
||||
});
|
||||
}
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogCreate',
|
||||
filter: candidate,
|
||||
compiled: candidate.startsWith('##')
|
||||
? staticFilteringParser.result.compiled
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onPickClicked = function(ev) {
|
||||
if (
|
||||
(ev instanceof MouseEvent) &&
|
||||
(ev.type === 'mousedown') &&
|
||||
(ev.which !== 1 || ev.target !== document.body)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
pickerBody.classList.remove('paused');
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogPick'
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onQuitClicked = function() {
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogQuit'
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onCandidateClicked = function(ev) {
|
||||
let li = ev.target.closest('li');
|
||||
const ul = li.closest('.changeFilter');
|
||||
if ( ul === null ) { return; }
|
||||
const choice = {
|
||||
filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent),
|
||||
slot: 0,
|
||||
modifier: ev.ctrlKey || ev.metaKey
|
||||
};
|
||||
while ( li.previousElementSibling !== null ) {
|
||||
li = li.previousElementSibling;
|
||||
choice.slot += 1;
|
||||
}
|
||||
taCandidate.value = candidateFromFilterChoice(choice);
|
||||
onCandidateChanged();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onKeyPressed = function(ev) {
|
||||
// Esc
|
||||
if ( ev.key === 'Escape' || ev.which === 27 ) {
|
||||
onQuitClicked();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onStartMoving = (( ) => {
|
||||
let mx0 = 0, my0 = 0;
|
||||
let mx1 = 0, my1 = 0;
|
||||
let r0 = 0, b0 = 0;
|
||||
let rMax = 0, bMax = 0;
|
||||
let timer;
|
||||
|
||||
const move = ( ) => {
|
||||
timer = undefined;
|
||||
let r1 = Math.min(Math.max(r0 - mx1 + mx0, 4), rMax);
|
||||
let b1 = Math.min(Math.max(b0 - my1 + my0, 4), bMax);
|
||||
dialog.style.setProperty('right', `${r1}px`, 'important');
|
||||
dialog.style.setProperty('bottom', `${b1}px`, 'important');
|
||||
};
|
||||
|
||||
const moveAsync = ev => {
|
||||
if ( ev.isTrusted === false ) { return; }
|
||||
eatEvent(ev);
|
||||
if ( timer !== undefined ) { return; }
|
||||
mx1 = ev.pageX;
|
||||
my1 = ev.pageY;
|
||||
timer = self.requestAnimationFrame(move);
|
||||
};
|
||||
|
||||
const stop = ev => {
|
||||
if ( ev.isTrusted === false ) { return; }
|
||||
if ( dialog.classList.contains('moving') === false ) { return; }
|
||||
dialog.classList.remove('moving');
|
||||
self.removeEventListener('mousemove', moveAsync, { capture: true });
|
||||
self.removeEventListener('mouseup', stop, { capture: true, once: true });
|
||||
eatEvent(ev);
|
||||
};
|
||||
|
||||
return function(ev) {
|
||||
if ( ev.isTrusted === false ) { return; }
|
||||
const target = dialog.querySelector('#toolbar');
|
||||
if ( ev.target !== target ) { return; }
|
||||
if ( dialog.classList.contains('moving') ) { return; }
|
||||
mx0 = ev.pageX; my0 = ev.pageY;
|
||||
const style = self.getComputedStyle(dialog);
|
||||
r0 = parseInt(style.right, 10);
|
||||
b0 = parseInt(style.bottom, 10);
|
||||
const rect = dialog.getBoundingClientRect();
|
||||
rMax = pickerBody.clientWidth - 4 - rect.width ;
|
||||
bMax = pickerBody.clientHeight - 4 - rect.height;
|
||||
dialog.classList.add('moving');
|
||||
self.addEventListener('mousemove', moveAsync, { capture: true });
|
||||
self.addEventListener('mouseup', stop, { capture: true, once: true });
|
||||
eatEvent(ev);
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const eatEvent = function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const showDialog = function(details) {
|
||||
pickerBody.classList.add('paused');
|
||||
|
||||
const { netFilters, cosmeticFilters, filter, options } = details;
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/738
|
||||
// Trim dots.
|
||||
filterHostname = details.hostname;
|
||||
if ( filterHostname.slice(-1) === '.' ) {
|
||||
filterHostname = filterHostname.slice(0, -1);
|
||||
}
|
||||
filterOrigin = details.origin;
|
||||
|
||||
// Create lists of candidate filters
|
||||
const populate = function(src, des) {
|
||||
const root = dialog.querySelector(des);
|
||||
const ul = root.querySelector('ul');
|
||||
while ( ul.firstChild !== null ) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
for ( let i = 0; i < src.length; i++ ) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = src[i];
|
||||
ul.appendChild(li);
|
||||
}
|
||||
if ( src.length !== 0 ) {
|
||||
root.style.removeProperty('display');
|
||||
} else {
|
||||
root.style.setProperty('display', 'none', 'important');
|
||||
}
|
||||
};
|
||||
|
||||
populate(netFilters, '#netFilters');
|
||||
populate(cosmeticFilters, '#cosmeticFilters');
|
||||
|
||||
dialog.querySelector('ul').style.display =
|
||||
netFilters.length || cosmeticFilters.length ? '' : 'none';
|
||||
dialog.querySelector('#create').disabled = true;
|
||||
|
||||
// Auto-select a candidate filter
|
||||
|
||||
// 2020-09-01:
|
||||
// In Firefox, `details instanceof Object` resolves to `false` despite
|
||||
// `details` being a valid object. Consequently, falling back to use
|
||||
// `typeof details`.
|
||||
// This is an issue which surfaced when the element picker code was
|
||||
// revisited to isolate the picker dialog DOM from the page DOM.
|
||||
if ( typeof filter !== 'object' || filter === null ) {
|
||||
taCandidate.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const filterChoice = {
|
||||
filters: filter.filters,
|
||||
slot: filter.slot,
|
||||
modifier: options.modifier || false
|
||||
};
|
||||
|
||||
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
||||
onCandidateChanged();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Let's have the element picker code flushed from memory when no longer
|
||||
// in use: to ensure this, release all local references.
|
||||
|
||||
const stopPicker = function() {
|
||||
vAPI.shutdown.remove(stopPicker);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const pickerBody = document.body;
|
||||
const dialog = $stor('aside');
|
||||
const taCandidate = $stor('textarea');
|
||||
let staticFilteringParser;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const startDialog = function() {
|
||||
dialog.addEventListener('click', eatEvent);
|
||||
taCandidate.addEventListener('input', onCandidateChanged);
|
||||
$stor('body').addEventListener('mousedown', onPickClicked);
|
||||
$id('preview').addEventListener('click', onPreviewClicked);
|
||||
$id('create').addEventListener('click', onCreateClicked);
|
||||
$id('pick').addEventListener('click', onPickClicked);
|
||||
$id('quit').addEventListener('click', onQuitClicked);
|
||||
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
||||
$id('toolbar').addEventListener('mousedown', onStartMoving);
|
||||
self.addEventListener('keydown', onKeyPressed, true);
|
||||
staticFilteringParser = new vAPI.StaticFilteringParser({ interactive: true });
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onPickerMessage = function(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'showDialog':
|
||||
showDialog(msg);
|
||||
break;
|
||||
case 'filterResultset':
|
||||
filterResultset = msg.resultset;
|
||||
$id('resultsetCount').textContent = filterResultset.length;
|
||||
if ( filterResultset.length !== 0 ) {
|
||||
$id('create').removeAttribute('disabled');
|
||||
} else {
|
||||
$id('create').setAttribute('disabled', '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onConnectionMessage = function(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'connectionBroken':
|
||||
stopPicker();
|
||||
break;
|
||||
case 'connectionMessage':
|
||||
onPickerMessage(msg.payload);
|
||||
break;
|
||||
case 'connectionAccepted':
|
||||
epickerConnectionId = msg.id;
|
||||
startDialog();
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogInit',
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.MessagingConnection.connectTo(
|
||||
`epickerDialog-${epickerId}`,
|
||||
`epicker-${epickerId}`,
|
||||
onConnectionMessage
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
DO NOT:
|
||||
- Remove the following code
|
||||
- Add code beyond the following code
|
||||
Reason:
|
||||
- https://github.com/gorhill/uBlock/pull/3721
|
||||
- uBO never uses the return value from injected content scripts
|
||||
|
||||
**/
|
||||
|
||||
void 0;
|
|
@ -713,34 +713,19 @@ const onMessage = function(request, sender, callback) {
|
|||
switch ( request.what ) {
|
||||
case 'elementPickerArguments':
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'epicker.html', true);
|
||||
xhr.open('GET', 'css/epicker.css', true);
|
||||
xhr.overrideMimeType('text/html;charset=utf-8');
|
||||
xhr.responseType = 'text';
|
||||
xhr.onload = function() {
|
||||
this.onload = null;
|
||||
const i18n = {
|
||||
bidi_dir: document.body.getAttribute('dir'),
|
||||
create: vAPI.i18n('pickerCreate'),
|
||||
pick: vAPI.i18n('pickerPick'),
|
||||
quit: vAPI.i18n('pickerQuit'),
|
||||
preview: vAPI.i18n('pickerPreview'),
|
||||
netFilters: vAPI.i18n('pickerNetFilters'),
|
||||
cosmeticFilters: vAPI.i18n('pickerCosmeticFilters'),
|
||||
cosmeticFiltersHint: vAPI.i18n('pickerCosmeticFiltersHint')
|
||||
};
|
||||
const reStrings = /\{\{(\w+)\}\}/g;
|
||||
const replacer = function(a0, string) {
|
||||
return i18n[string];
|
||||
};
|
||||
|
||||
callback({
|
||||
frameContent: this.responseText.replace(reStrings, replacer),
|
||||
frameCSS: this.responseText,
|
||||
target: µb.epickerArgs.target,
|
||||
mouse: µb.epickerArgs.mouse,
|
||||
zap: µb.epickerArgs.zap,
|
||||
eprom: µb.epickerArgs.eprom,
|
||||
dialogURL: vAPI.getURL(`/web_accessible_resources/epicker-dialog.html${vAPI.warSecret()}`),
|
||||
});
|
||||
|
||||
µb.epickerArgs.target = '';
|
||||
};
|
||||
xhr.send();
|
||||
|
@ -754,21 +739,6 @@ const onMessage = function(request, sender, callback) {
|
|||
let response;
|
||||
|
||||
switch ( request.what ) {
|
||||
case 'compileCosmeticFilterSelector': {
|
||||
const parser = new vAPI.StaticFilteringParser();
|
||||
parser.analyze(request.selector);
|
||||
if ( (parser.flavorBits & parser.BITFlavorExtCosmetic) !== 0 ) {
|
||||
response = parser.result.compiled;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3497
|
||||
// This needs to be removed once issue is fixed.
|
||||
case 'createUserFilter':
|
||||
µb.createUserFilters(request);
|
||||
break;
|
||||
|
||||
case 'elementPickerEprom':
|
||||
µb.epickerArgs.eprom = request;
|
||||
break;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -428,7 +428,7 @@ const matchBucket = function(url, hostname, bucket, start) {
|
|||
});
|
||||
|
||||
await vAPI.tabs.executeScript(tabId, {
|
||||
file: '/js/scriptlets/element-picker.js',
|
||||
file: '/js/scriptlets/epicker.js',
|
||||
runAt: 'document_end',
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html id="ublock0-epicker">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin Element Picker</title>
|
||||
<link rel="stylesheet" href="../css/epicker-dialog.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<aside>
|
||||
<section>
|
||||
<div>
|
||||
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
|
||||
<div id="resultsetCount"></div>
|
||||
</div>
|
||||
<div id="toolbar">
|
||||
<div>
|
||||
<button id="preview" type="button" data-i18n="pickerPreview"></button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="create" type="button" disabled data-i18n="pickerCreate"></button>
|
||||
<button id="pick" type="button" data-i18n="pickerPick"></button>
|
||||
<button id="quit" type="button" data-i18n="pickerQuit"></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<ul id="candidateFilters">
|
||||
<li id="netFilters">
|
||||
<span data-i18n="pickerNetFilters"></span><ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
<li id="cosmeticFilters">
|
||||
<span data-i18n="pickerCosmeticFilters"></span> <span data-i18n="pickerCosmeticFiltersHint"></span>
|
||||
<ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<script src="../js/vapi.js"></script>
|
||||
<script src="../js/vapi-common.js"></script>
|
||||
<script src="../js/vapi-client.js"></script>
|
||||
<script src="../js/vapi-client-extra.js"></script>
|
||||
<script src="../js/i18n.js"></script>
|
||||
<script src="../js/epicker-dialog.js"></script>
|
||||
<script src="../js/static-filtering-parser.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue