mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Isolate element picker's svg layers from page content
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1226
Related commit:
- 9eb455ab5e
In the previous commit, the element picker dialog was
isolated from the page content. This commit is to also
isolate the svg layers from the page content.
With this commit, there is no longer a need for an anonymous
iframe and the isolated world iframe is now directly
embedded in the page.
As a result, pages are now unable to interfere with any
of the element picker user interface. Pages can now only
see an iframe, but are unable to see the content of that
iframe. The styles applied to the iframe are from a user
stylesheet, so as to ensure pages can't override the
iframe's style properties set by uBO.
This commit is contained in:
parent
c02bba3cfa
commit
d23f9c6a8b
7 changed files with 615 additions and 612 deletions
|
@ -153,6 +153,9 @@ vAPI.MessagingConnection = class {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
vAPI.messaging.getPort(); // Ensure a port instance exists
|
vAPI.messaging.getPort(); // Ensure a port instance exists
|
||||||
}
|
}
|
||||||
|
static removeListener(listener) {
|
||||||
|
listeners.delete(listener);
|
||||||
|
}
|
||||||
static connectTo(from, to, handler) {
|
static connectTo(from, to, handler) {
|
||||||
const port = vAPI.messaging.getPort();
|
const port = vAPI.messaging.getPort();
|
||||||
if ( port === null ) { return; }
|
if ( port === null ) { return; }
|
||||||
|
|
|
@ -12,6 +12,22 @@ html#ublock0-epicker,
|
||||||
#ublock0-epicker :focus {
|
#ublock0-epicker :focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
#ublock0-epicker aside {
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
bottom: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: default;
|
||||||
|
display: none;
|
||||||
|
min-width: 24em;
|
||||||
|
padding: 4px;
|
||||||
|
position: fixed;
|
||||||
|
right: 4px;
|
||||||
|
width: calc(40% - 4px);
|
||||||
|
}
|
||||||
|
#ublock0-epicker.paused:not(.zap) aside {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
#ublock0-epicker ul,
|
#ublock0-epicker ul,
|
||||||
#ublock0-epicker li,
|
#ublock0-epicker li,
|
||||||
#ublock0-epicker div {
|
#ublock0-epicker div {
|
||||||
|
@ -53,7 +69,7 @@ html#ublock0-epicker,
|
||||||
#ublock0-epicker #preview {
|
#ublock0-epicker #preview {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.preview #preview {
|
#ublock0-epicker.preview #preview {
|
||||||
background-color: hsl(204, 100%, 83%);
|
background-color: hsl(204, 100%, 83%);
|
||||||
border-color: hsl(204, 50%, 60%);
|
border-color: hsl(204, 50%, 60%);
|
||||||
}
|
}
|
||||||
|
@ -141,18 +157,7 @@ html#ublock0-epicker,
|
||||||
#ublock0-epicker #candidateFilters .changeFilter li:hover {
|
#ublock0-epicker #candidateFilters .changeFilter li:hover {
|
||||||
background-color: white;
|
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/gorhill/uBlock/issues/3449
|
||||||
https://github.com/uBlockOrigin/uBlock-issues/issues/55
|
https://github.com/uBlockOrigin/uBlock-issues/issues/55
|
||||||
|
@ -162,23 +167,55 @@ html#ublock0-epicker,
|
||||||
60% { opacity: 1.0; }
|
60% { opacity: 1.0; }
|
||||||
100% { opacity: 0.1; }
|
100% { opacity: 0.1; }
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.paused > aside {
|
#ublock0-epicker.paused aside {
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.paused > aside:not(:hover):not(.show) {
|
#ublock0-epicker.paused:not(.show):not(.hide) aside:not(:hover) {
|
||||||
animation-duration: 1.6s;
|
animation-duration: 1.6s;
|
||||||
animation-name: startDialog;
|
animation-name: startDialog;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.paused > aside:hover {
|
#ublock0-epicker.paused aside:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.paused > aside.show {
|
#ublock0-epicker.paused.show aside {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#ublock0-epicker body.paused > aside.hide {
|
#ublock0-epicker.paused.hide aside {
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ublock0-epicker svg {
|
||||||
|
cursor: crosshair;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#ublock0-epicker.paused svg {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
#ublock0-epicker svg > path:first-child {
|
||||||
|
fill: rgba(0,0,0,0.5);
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
#ublock0-epicker svg > path + path {
|
||||||
|
stroke: #F00;
|
||||||
|
stroke-width: 0.5px;
|
||||||
|
fill: rgba(255,63,63,0.20);
|
||||||
|
}
|
||||||
|
#ublock0-epicker.zap svg > path + path {
|
||||||
|
stroke: #FF0;
|
||||||
|
stroke-width: 0.5px;
|
||||||
|
fill: rgba(255,255,63,0.20);
|
||||||
|
}
|
||||||
|
#ublock0-epicker.preview svg > path {
|
||||||
|
fill: rgba(0,0,0,0.10);
|
||||||
|
}
|
||||||
|
#ublock0-epicker.preview svg > path + path {
|
||||||
|
stroke: none;
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -30,8 +30,25 @@
|
||||||
|
|
||||||
if ( typeof vAPI !== 'object' ) { return; }
|
if ( typeof vAPI !== 'object' ) { return; }
|
||||||
|
|
||||||
|
const $id = id => document.getElementById(id);
|
||||||
|
const $stor = selector => document.querySelector(selector);
|
||||||
|
const $storAll = selector => document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
const pickerRoot = document.documentElement;
|
||||||
|
const dialog = $stor('aside');
|
||||||
|
const taCandidate = $stor('textarea');
|
||||||
|
let staticFilteringParser;
|
||||||
|
|
||||||
|
const svgRoot = $stor('svg');
|
||||||
|
const svgOcean = svgRoot.children[0];
|
||||||
|
const svgIslands = svgRoot.children[1];
|
||||||
|
const NoPaths = 'M0 0';
|
||||||
|
|
||||||
const epickerId = (( ) => {
|
const epickerId = (( ) => {
|
||||||
const url = new URL(self.location.href);
|
const url = new URL(self.location.href);
|
||||||
|
if ( url.searchParams.has('zap') ) {
|
||||||
|
pickerRoot.classList.add('zap');
|
||||||
|
}
|
||||||
return url.searchParams.get('epid');
|
return url.searchParams.get('epid');
|
||||||
})();
|
})();
|
||||||
if ( epickerId === null ) { return; }
|
if ( epickerId === null ) { return; }
|
||||||
|
@ -43,12 +60,6 @@ let filterResultset = [];
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const $id = id => document.getElementById(id);
|
|
||||||
const $stor = selector => document.querySelector(selector);
|
|
||||||
const $storAll = selector => document.querySelectorAll(selector);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const filterFromTextarea = function() {
|
const filterFromTextarea = function() {
|
||||||
const s = taCandidate.value.trim();
|
const s = taCandidate.value.trim();
|
||||||
if ( s === '' ) { return ''; }
|
if ( s === '' ) { return ''; }
|
||||||
|
@ -121,7 +132,7 @@ const candidateFromFilterChoice = function(filterChoice) {
|
||||||
// - Discard narrowing directives.
|
// - Discard narrowing directives.
|
||||||
// - Remove the id if one or more classes exist
|
// - Remove the id if one or more classes exist
|
||||||
// TODO: should remove tag name too? ¯\_(ツ)_/¯
|
// TODO: should remove tag name too? ¯\_(ツ)_/¯
|
||||||
if ( filterChoice.modifier ) {
|
if ( filterChoice.broad ) {
|
||||||
filter = filter.replace(/:nth-of-type\(\d+\)/, '');
|
filter = filter.replace(/:nth-of-type\(\d+\)/, '');
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
||||||
// Mind escaped periods: they do not denote a class identifier.
|
// Mind escaped periods: they do not denote a class identifier.
|
||||||
|
@ -167,6 +178,127 @@ const candidateFromFilterChoice = function(filterChoice) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const onSvgClicked = function(ev) {
|
||||||
|
// If zap mode, highlight element under mouse, this makes the zapper usable
|
||||||
|
// on touch screens.
|
||||||
|
if ( pickerRoot.classList.contains('zap') ) {
|
||||||
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
|
what: 'zapElementAtPoint',
|
||||||
|
mx: ev.clientX,
|
||||||
|
my: ev.clientY,
|
||||||
|
options: {
|
||||||
|
stay: ev.shiftKey || ev.type === 'touch',
|
||||||
|
highlight: ev.target !== svgIslands,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
|
||||||
|
// Unpause picker if:
|
||||||
|
// - click outside dialog AND
|
||||||
|
// - not in preview mode
|
||||||
|
if ( pickerRoot.classList.contains('paused') ) {
|
||||||
|
if ( pickerRoot.classList.contains('preview') === false ) {
|
||||||
|
unpausePicker();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Force dialog to always be visible when using a touch-driven device.
|
||||||
|
if ( ev.type === 'touch' ) {
|
||||||
|
pickerRoot.classList.add('show');
|
||||||
|
}
|
||||||
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
|
what: 'filterElementAtPoint',
|
||||||
|
mx: ev.clientX,
|
||||||
|
my: ev.clientY,
|
||||||
|
broad: ev.ctrlKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
Swipe right:
|
||||||
|
If picker not paused: quit picker
|
||||||
|
If picker paused and dialog visible: hide dialog
|
||||||
|
If picker paused and dialog not visible: quit picker
|
||||||
|
|
||||||
|
Swipe left:
|
||||||
|
If picker paused and dialog not visible: show dialog
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const onSvgTouch = (( ) => {
|
||||||
|
let startX = 0, startY = 0;
|
||||||
|
let t0 = 0;
|
||||||
|
return ev => {
|
||||||
|
if ( ev.type === 'touchstart' ) {
|
||||||
|
startX = ev.touches[0].screenX;
|
||||||
|
startY = ev.touches[0].screenY;
|
||||||
|
t0 = ev.timeStamp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( startX === undefined ) { return; }
|
||||||
|
if ( ev.cancelable === false ) { return; }
|
||||||
|
const stopX = ev.changedTouches[0].screenX;
|
||||||
|
const stopY = ev.changedTouches[0].screenY;
|
||||||
|
const angle = Math.abs(Math.atan2(stopY - startY, stopX - startX));
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
Math.pow(stopX - startX, 2),
|
||||||
|
Math.pow(stopY - startY, 2)
|
||||||
|
);
|
||||||
|
// Interpret touch events as a tap if:
|
||||||
|
// - Swipe is not valid; and
|
||||||
|
// - The time between start and stop was less than 200ms.
|
||||||
|
const duration = ev.timeStamp - t0;
|
||||||
|
if ( distance < 32 && duration < 200 ) {
|
||||||
|
onSvgClicked({
|
||||||
|
type: 'touch',
|
||||||
|
target: ev.target,
|
||||||
|
clientX: ev.changedTouches[0].pageX,
|
||||||
|
clientY: ev.changedTouches[0].pageY,
|
||||||
|
});
|
||||||
|
ev.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( distance < 64 ) { return; }
|
||||||
|
const angleUpperBound = Math.PI * 0.25 * 0.5;
|
||||||
|
const swipeRight = angle < angleUpperBound;
|
||||||
|
if ( swipeRight === false && angle < Math.PI - angleUpperBound ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
// Swipe left.
|
||||||
|
if ( swipeRight === false ) {
|
||||||
|
if ( pickerRoot.classList.contains('paused') ) {
|
||||||
|
pickerRoot.classList.remove('hide');
|
||||||
|
pickerRoot.classList.add('show');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Swipe right.
|
||||||
|
if (
|
||||||
|
pickerRoot.classList.contains('zap') &&
|
||||||
|
svgIslands.getAttribute('d') !== NoPaths
|
||||||
|
) {
|
||||||
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
|
what: 'unhighlight'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
pickerRoot.classList.contains('paused') &&
|
||||||
|
pickerRoot.classList.contains('show')
|
||||||
|
) {
|
||||||
|
pickerRoot.classList.remove('show');
|
||||||
|
pickerRoot.classList.add('hide');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
quitPicker();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
const onCandidateChanged = function() {
|
const onCandidateChanged = function() {
|
||||||
const filter = filterFromTextarea();
|
const filter = filterFromTextarea();
|
||||||
const bad = filter === '!';
|
const bad = filter === '!';
|
||||||
|
@ -188,9 +320,9 @@ const onCandidateChanged = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onPreviewClicked = function() {
|
const onPreviewClicked = function() {
|
||||||
const state = pickerBody.classList.toggle('preview');
|
const state = pickerRoot.classList.toggle('preview');
|
||||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
what: 'dialogPreview',
|
what: 'togglePreview',
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -221,38 +353,27 @@ const onCreateClicked = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onPickClicked = function(ev) {
|
const onPickClicked = function() {
|
||||||
if (
|
unpausePicker();
|
||||||
(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() {
|
const onQuitClicked = function() {
|
||||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
quitPicker();
|
||||||
what: 'dialogQuit'
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onCandidateClicked = function(ev) {
|
const onCandidateClicked = function(ev) {
|
||||||
let li = ev.target.closest('li');
|
let li = ev.target.closest('li');
|
||||||
|
if ( li === null ) { return; }
|
||||||
const ul = li.closest('.changeFilter');
|
const ul = li.closest('.changeFilter');
|
||||||
if ( ul === null ) { return; }
|
if ( ul === null ) { return; }
|
||||||
const choice = {
|
const choice = {
|
||||||
filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent),
|
filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent),
|
||||||
slot: 0,
|
slot: 0,
|
||||||
modifier: ev.ctrlKey || ev.metaKey
|
broad: ev.ctrlKey || ev.metaKey
|
||||||
};
|
};
|
||||||
while ( li.previousElementSibling !== null ) {
|
while ( li.previousElementSibling !== null ) {
|
||||||
li = li.previousElementSibling;
|
li = li.previousElementSibling;
|
||||||
|
@ -275,6 +396,7 @@ const onKeyPressed = function(ev) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onStartMoving = (( ) => {
|
const onStartMoving = (( ) => {
|
||||||
|
let isTouch = false;
|
||||||
let mx0 = 0, my0 = 0;
|
let mx0 = 0, my0 = 0;
|
||||||
let mx1 = 0, my1 = 0;
|
let mx1 = 0, my1 = 0;
|
||||||
let r0 = 0, b0 = 0;
|
let r0 = 0, b0 = 0;
|
||||||
|
@ -290,44 +412,101 @@ const onStartMoving = (( ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveAsync = ev => {
|
const moveAsync = ev => {
|
||||||
if ( ev.isTrusted === false ) { return; }
|
|
||||||
eatEvent(ev);
|
|
||||||
if ( timer !== undefined ) { return; }
|
if ( timer !== undefined ) { return; }
|
||||||
|
if ( isTouch ) {
|
||||||
|
const touch = ev.touches[0];
|
||||||
|
mx1 = touch.pageX;
|
||||||
|
my1 = touch.pageY;
|
||||||
|
} else {
|
||||||
mx1 = ev.pageX;
|
mx1 = ev.pageX;
|
||||||
my1 = ev.pageY;
|
my1 = ev.pageY;
|
||||||
|
}
|
||||||
timer = self.requestAnimationFrame(move);
|
timer = self.requestAnimationFrame(move);
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = ev => {
|
const stop = ev => {
|
||||||
if ( ev.isTrusted === false ) { return; }
|
|
||||||
if ( dialog.classList.contains('moving') === false ) { return; }
|
if ( dialog.classList.contains('moving') === false ) { return; }
|
||||||
dialog.classList.remove('moving');
|
dialog.classList.remove('moving');
|
||||||
|
if ( isTouch ) {
|
||||||
|
self.removeEventListener('touchmove', moveAsync, { capture: true });
|
||||||
|
} else {
|
||||||
self.removeEventListener('mousemove', moveAsync, { capture: true });
|
self.removeEventListener('mousemove', moveAsync, { capture: true });
|
||||||
self.removeEventListener('mouseup', stop, { capture: true, once: true });
|
}
|
||||||
eatEvent(ev);
|
eatEvent(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
return function(ev) {
|
return function(ev) {
|
||||||
if ( ev.isTrusted === false ) { return; }
|
|
||||||
const target = dialog.querySelector('#toolbar');
|
const target = dialog.querySelector('#toolbar');
|
||||||
if ( ev.target !== target ) { return; }
|
if ( ev.target !== target ) { return; }
|
||||||
if ( dialog.classList.contains('moving') ) { return; }
|
if ( dialog.classList.contains('moving') ) { return; }
|
||||||
mx0 = ev.pageX; my0 = ev.pageY;
|
isTouch = ev.type.startsWith('touch');
|
||||||
|
if ( isTouch ) {
|
||||||
|
const touch = ev.touches[0];
|
||||||
|
mx0 = touch.pageX;
|
||||||
|
my0 = touch.pageY;
|
||||||
|
} else {
|
||||||
|
mx0 = ev.pageX;
|
||||||
|
my0 = ev.pageY;
|
||||||
|
}
|
||||||
const style = self.getComputedStyle(dialog);
|
const style = self.getComputedStyle(dialog);
|
||||||
r0 = parseInt(style.right, 10);
|
r0 = parseInt(style.right, 10);
|
||||||
b0 = parseInt(style.bottom, 10);
|
b0 = parseInt(style.bottom, 10);
|
||||||
const rect = dialog.getBoundingClientRect();
|
const rect = dialog.getBoundingClientRect();
|
||||||
rMax = pickerBody.clientWidth - 4 - rect.width ;
|
rMax = pickerRoot.clientWidth - 4 - rect.width ;
|
||||||
bMax = pickerBody.clientHeight - 4 - rect.height;
|
bMax = pickerRoot.clientHeight - 4 - rect.height;
|
||||||
dialog.classList.add('moving');
|
dialog.classList.add('moving');
|
||||||
|
if ( isTouch ) {
|
||||||
|
self.addEventListener('touchmove', moveAsync, { capture: true });
|
||||||
|
self.addEventListener('touchend', stop, { capture: true, once: true });
|
||||||
|
} else {
|
||||||
self.addEventListener('mousemove', moveAsync, { capture: true });
|
self.addEventListener('mousemove', moveAsync, { capture: true });
|
||||||
self.addEventListener('mouseup', stop, { capture: true, once: true });
|
self.addEventListener('mouseup', stop, { capture: true, once: true });
|
||||||
|
}
|
||||||
eatEvent(ev);
|
eatEvent(ev);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const svgListening = (( ) => {
|
||||||
|
let on = false;
|
||||||
|
let timer;
|
||||||
|
let mx = 0, my = 0;
|
||||||
|
|
||||||
|
const onTimer = ( ) => {
|
||||||
|
timer = undefined;
|
||||||
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
|
what: 'highlightElementAtPoint',
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onHover = ev => {
|
||||||
|
mx = ev.clientX;
|
||||||
|
my = ev.clientY;
|
||||||
|
if ( timer === undefined ) {
|
||||||
|
timer = self.requestAnimationFrame(onTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return state => {
|
||||||
|
if ( state === on ) { return; }
|
||||||
|
on = state;
|
||||||
|
if ( on ) {
|
||||||
|
document.addEventListener('mousemove', onHover, { passive: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.removeEventListener('mousemove', onHover, { passive: true });
|
||||||
|
if ( timer !== undefined ) {
|
||||||
|
self.cancelAnimationFrame(timer);
|
||||||
|
timer = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
const eatEvent = function(ev) {
|
const eatEvent = function(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -336,9 +515,9 @@ const eatEvent = function(ev) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const showDialog = function(details) {
|
const showDialog = function(details) {
|
||||||
pickerBody.classList.add('paused');
|
pausePicker();
|
||||||
|
|
||||||
const { netFilters, cosmeticFilters, filter, options } = details;
|
const { netFilters, cosmeticFilters, filter, options = {} } = details;
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/738
|
// https://github.com/gorhill/uBlock/issues/738
|
||||||
// Trim dots.
|
// Trim dots.
|
||||||
|
@ -390,7 +569,7 @@ const showDialog = function(details) {
|
||||||
const filterChoice = {
|
const filterChoice = {
|
||||||
filters: filter.filters,
|
filters: filter.filters,
|
||||||
slot: filter.slot,
|
slot: filter.slot,
|
||||||
modifier: options.modifier || false
|
broad: options.broad || false
|
||||||
};
|
};
|
||||||
|
|
||||||
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
||||||
|
@ -399,44 +578,61 @@ const showDialog = function(details) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Let's have the element picker code flushed from memory when no longer
|
const pausePicker = function() {
|
||||||
// in use: to ensure this, release all local references.
|
pickerRoot.classList.add('paused');
|
||||||
|
svgListening(false);
|
||||||
const stopPicker = function() {
|
|
||||||
vAPI.shutdown.remove(stopPicker);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const pickerBody = document.body;
|
const unpausePicker = function() {
|
||||||
const dialog = $stor('aside');
|
pickerRoot.classList.remove('paused', 'preview');
|
||||||
const taCandidate = $stor('textarea');
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
let staticFilteringParser;
|
what: 'togglePreview',
|
||||||
|
state: false,
|
||||||
|
});
|
||||||
|
svgListening(true);
|
||||||
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const startDialog = function() {
|
const startPicker = function() {
|
||||||
dialog.addEventListener('click', eatEvent);
|
self.addEventListener('keydown', onKeyPressed, true);
|
||||||
|
const svg = $stor('svg');
|
||||||
|
svg.addEventListener('click', onSvgClicked);
|
||||||
|
svg.addEventListener('touchstart', onSvgTouch);
|
||||||
|
svg.addEventListener('touchend', onSvgTouch);
|
||||||
|
|
||||||
|
unpausePicker();
|
||||||
|
|
||||||
|
if ( pickerRoot.classList.contains('zap') ) { return; }
|
||||||
|
|
||||||
taCandidate.addEventListener('input', onCandidateChanged);
|
taCandidate.addEventListener('input', onCandidateChanged);
|
||||||
$stor('body').addEventListener('mousedown', onPickClicked);
|
|
||||||
$id('preview').addEventListener('click', onPreviewClicked);
|
$id('preview').addEventListener('click', onPreviewClicked);
|
||||||
$id('create').addEventListener('click', onCreateClicked);
|
$id('create').addEventListener('click', onCreateClicked);
|
||||||
$id('pick').addEventListener('click', onPickClicked);
|
$id('pick').addEventListener('click', onPickClicked);
|
||||||
$id('quit').addEventListener('click', onQuitClicked);
|
$id('quit').addEventListener('click', onQuitClicked);
|
||||||
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
||||||
$id('toolbar').addEventListener('mousedown', onStartMoving);
|
$id('toolbar').addEventListener('mousedown', onStartMoving);
|
||||||
self.addEventListener('keydown', onKeyPressed, true);
|
$id('toolbar').addEventListener('touchstart', onStartMoving);
|
||||||
staticFilteringParser = new vAPI.StaticFilteringParser({ interactive: true });
|
staticFilteringParser = new vAPI.StaticFilteringParser({ interactive: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const quitPicker = function() {
|
||||||
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' });
|
||||||
|
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
const onPickerMessage = function(msg) {
|
const onPickerMessage = function(msg) {
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'showDialog':
|
case 'showDialog':
|
||||||
showDialog(msg);
|
showDialog(msg);
|
||||||
break;
|
break;
|
||||||
case 'filterResultset':
|
case 'filterResultset': {
|
||||||
filterResultset = msg.resultset;
|
filterResultset = msg.resultset;
|
||||||
$id('resultsetCount').textContent = filterResultset.length;
|
$id('resultsetCount').textContent = filterResultset.length;
|
||||||
if ( filterResultset.length !== 0 ) {
|
if ( filterResultset.length !== 0 ) {
|
||||||
|
@ -446,6 +642,20 @@ const onPickerMessage = function(msg) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'svgListening': {
|
||||||
|
svgListening(msg.on);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'svgPaths': {
|
||||||
|
let { ocean, islands } = msg;
|
||||||
|
ocean += islands;
|
||||||
|
svgOcean.setAttribute('d', ocean);
|
||||||
|
svgIslands.setAttribute('d', islands || NoPaths);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -453,16 +663,15 @@ const onPickerMessage = function(msg) {
|
||||||
const onConnectionMessage = function(msg) {
|
const onConnectionMessage = function(msg) {
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'connectionBroken':
|
case 'connectionBroken':
|
||||||
stopPicker();
|
|
||||||
break;
|
break;
|
||||||
case 'connectionMessage':
|
case 'connectionMessage':
|
||||||
onPickerMessage(msg.payload);
|
onPickerMessage(msg.payload);
|
||||||
break;
|
break;
|
||||||
case 'connectionAccepted':
|
case 'connectionAccepted':
|
||||||
epickerConnectionId = msg.id;
|
epickerConnectionId = msg.id;
|
||||||
startDialog();
|
startPicker();
|
||||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
what: 'dialogInit',
|
what: 'start',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
|
@ -711,26 +711,6 @@ const onMessage = function(request, sender, callback) {
|
||||||
|
|
||||||
// Async
|
// Async
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
case 'elementPickerArguments':
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', 'css/epicker.css', true);
|
|
||||||
xhr.overrideMimeType('text/html;charset=utf-8');
|
|
||||||
xhr.responseType = 'text';
|
|
||||||
xhr.onload = function() {
|
|
||||||
this.onload = null;
|
|
||||||
callback({
|
|
||||||
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();
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -739,6 +719,16 @@ const onMessage = function(request, sender, callback) {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
|
case 'elementPickerArguments':
|
||||||
|
response = {
|
||||||
|
target: µb.epickerArgs.target,
|
||||||
|
mouse: µb.epickerArgs.mouse,
|
||||||
|
zap: µb.epickerArgs.zap,
|
||||||
|
eprom: µb.epickerArgs.eprom,
|
||||||
|
pickerURL: vAPI.getURL(`/web_accessible_resources/epicker-ui.html${vAPI.warSecret()}`),
|
||||||
|
};
|
||||||
|
µb.epickerArgs.target = '';
|
||||||
|
break;
|
||||||
case 'elementPickerEprom':
|
case 'elementPickerEprom':
|
||||||
µb.epickerArgs.eprom = request;
|
µb.epickerArgs.eprom = request;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -30,24 +30,13 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
if ( window.top !== window || typeof vAPI !== 'object' ) { return; }
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const epickerId = vAPI.randomToken();
|
const epickerId = vAPI.randomToken();
|
||||||
let epickerConnectionId;
|
let epickerConnectionId;
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`);
|
let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`);
|
||||||
if ( pickerRoot !== null ) { return; }
|
if ( pickerRoot !== null ) { return; }
|
||||||
|
|
||||||
let pickerBootArgs;
|
let pickerBootArgs;
|
||||||
let pickerBody = null;
|
|
||||||
let svgOcean = null;
|
|
||||||
let svgIslands = null;
|
|
||||||
let svgRoot = null;
|
|
||||||
let dialog = null;
|
|
||||||
|
|
||||||
const netFilterCandidates = [];
|
const netFilterCandidates = [];
|
||||||
const cosmeticFilterCandidates = [];
|
const cosmeticFilterCandidates = [];
|
||||||
|
@ -103,8 +92,8 @@ const getElementBoundingClientRect = function(elem) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
height: bottom - top,
|
height: bottom - top,
|
||||||
left: left,
|
left,
|
||||||
top: top,
|
top,
|
||||||
width: right - left
|
width: right - left
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -113,45 +102,40 @@ const getElementBoundingClientRect = function(elem) {
|
||||||
|
|
||||||
const highlightElements = function(elems, force) {
|
const highlightElements = function(elems, force) {
|
||||||
// To make mouse move handler more efficient
|
// To make mouse move handler more efficient
|
||||||
if ( !force && elems.length === targetElements.length ) {
|
if (
|
||||||
if ( elems.length === 0 || elems[0] === targetElements[0] ) {
|
(force !== true) &&
|
||||||
|
(elems.length === targetElements.length) &&
|
||||||
|
(elems.length === 0 || elems[0] === targetElements[0])
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
targetElements = [];
|
||||||
targetElements = elems;
|
|
||||||
|
|
||||||
const ow = pickerRoot.contentWindow.innerWidth;
|
const ow = self.innerWidth;
|
||||||
const oh = pickerRoot.contentWindow.innerHeight;
|
const oh = self.innerHeight;
|
||||||
const ocean = [
|
|
||||||
'M0 0',
|
|
||||||
'h', ow,
|
|
||||||
'v', oh,
|
|
||||||
'h-', ow,
|
|
||||||
'z'
|
|
||||||
];
|
|
||||||
const islands = [];
|
const islands = [];
|
||||||
|
|
||||||
for ( let i = 0; i < elems.length; i++ ) {
|
for ( const elem of elems ) {
|
||||||
const elem = elems[i];
|
|
||||||
if ( elem === pickerRoot ) { continue; }
|
if ( elem === pickerRoot ) { continue; }
|
||||||
|
targetElements.push(elem);
|
||||||
const rect = getElementBoundingClientRect(elem);
|
const rect = getElementBoundingClientRect(elem);
|
||||||
|
// Ignore offscreen areas
|
||||||
// Ignore if it's not on the screen
|
if (
|
||||||
if ( rect.left > ow || rect.top > oh ||
|
rect.left > ow || rect.top > oh ||
|
||||||
rect.left + rect.width < 0 || rect.top + rect.height < 0 ) {
|
rect.left + rect.width < 0 || rect.top + rect.height < 0
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
islands.push(
|
||||||
const poly = 'M' + rect.left + ' ' + rect.top +
|
`M${rect.left} ${rect.top}h${rect.width}v${rect.height}h-${rect.width}z`
|
||||||
'h' + rect.width +
|
);
|
||||||
'v' + rect.height +
|
|
||||||
'h-' + rect.width +
|
|
||||||
'z';
|
|
||||||
ocean.push(poly);
|
|
||||||
islands.push(poly);
|
|
||||||
}
|
}
|
||||||
svgOcean.setAttribute('d', ocean.join(''));
|
|
||||||
svgIslands.setAttribute('d', islands.join('') || 'M0 0');
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
|
what: 'svgPaths',
|
||||||
|
ocean: `M0 0h${ow}v${oh}h-${ow}z`,
|
||||||
|
islands: islands.join(''),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -170,8 +154,6 @@ const mergeStrings = function(urls) {
|
||||||
// The differ works at line granularity: we insert a linefeed after
|
// The differ works at line granularity: we insert a linefeed after
|
||||||
// each character to trick the differ to work at character granularity.
|
// each character to trick the differ to work at character granularity.
|
||||||
const diffs = differ.diff_main(
|
const diffs = differ.diff_main(
|
||||||
//urls[i].replace(/.(?=.)/g, '$&\n'),
|
|
||||||
//merged.replace(/.(?=.)/g, '$&\n')
|
|
||||||
urls[i].split('').join('\n'),
|
urls[i].split('').join('\n'),
|
||||||
merged.split('').join('\n')
|
merged.split('').join('\n')
|
||||||
);
|
);
|
||||||
|
@ -552,24 +534,20 @@ const filtersFrom = function(x, y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1545
|
// https://github.com/gorhill/uBlock/issues/1545
|
||||||
// Network filter candidates from all other elements found at point (x, y).
|
// Network filter candidates from all other elements found at
|
||||||
|
// point (x, y).
|
||||||
if ( typeof x === 'number' ) {
|
if ( typeof x === 'number' ) {
|
||||||
let attrName = vAPI.sessionId + '-clickblind';
|
const attrName = vAPI.sessionId + '-clickblind';
|
||||||
let previous;
|
|
||||||
elem = first;
|
elem = first;
|
||||||
while ( elem !== null ) {
|
while ( elem !== null ) {
|
||||||
previous = elem;
|
const previous = elem;
|
||||||
elem.setAttribute(attrName, '');
|
elem.setAttribute(attrName, '');
|
||||||
elem = elementFromPoint(x, y);
|
elem = elementFromPoint(x, y);
|
||||||
if ( elem === null || elem === previous ) {
|
if ( elem === null || elem === previous ) { break; }
|
||||||
break;
|
|
||||||
}
|
|
||||||
netFilterFromElement(elem);
|
netFilterFromElement(elem);
|
||||||
}
|
}
|
||||||
let elems = document.querySelectorAll(`[${attrName}]`);
|
for ( const elem of document.querySelectorAll(`[${attrName}]`) ) {
|
||||||
i = elems.length;
|
elem.removeAttribute(attrName);
|
||||||
while ( i-- ) {
|
|
||||||
elems[i].removeAttribute(attrName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
netFilterFromElement(document.body);
|
netFilterFromElement(document.body);
|
||||||
|
@ -761,7 +739,7 @@ const filterToDOMInterface = (( ) => {
|
||||||
if ( filter === '' || filter === '!' ) {
|
if ( filter === '' || filter === '!' ) {
|
||||||
lastFilter = '';
|
lastFilter = '';
|
||||||
lastResultset = [];
|
lastResultset = [];
|
||||||
return;
|
return lastResultset;
|
||||||
}
|
}
|
||||||
lastFilter = filter;
|
lastFilter = filter;
|
||||||
lastAction = undefined;
|
lastAction = undefined;
|
||||||
|
@ -868,7 +846,6 @@ const filterToDOMInterface = (( ) => {
|
||||||
// immediately rather than wait for the next page load.
|
// immediately rather than wait for the next page load.
|
||||||
const preview = function(state, permanent = false) {
|
const preview = function(state, permanent = false) {
|
||||||
previewing = state !== false;
|
previewing = state !== false;
|
||||||
pickerBody.classList.toggle('preview', previewing);
|
|
||||||
if ( previewing === false ) {
|
if ( previewing === false ) {
|
||||||
return unapply();
|
return unapply();
|
||||||
}
|
}
|
||||||
|
@ -909,15 +886,6 @@ const filterToDOMInterface = (( ) => {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const showDialog = function(options) {
|
const showDialog = function(options) {
|
||||||
pausePicker();
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// Typically the dialog will be forced to be visible when using a
|
|
||||||
// touch-aware device.
|
|
||||||
dialog.classList.toggle('show', options.show === true);
|
|
||||||
dialog.classList.remove('hide');
|
|
||||||
|
|
||||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||||
what: 'showDialog',
|
what: 'showDialog',
|
||||||
hostname: self.location.hostname,
|
hostname: self.location.hostname,
|
||||||
|
@ -931,18 +899,73 @@ const showDialog = function(options) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const elementFromPoint = (( ) => {
|
||||||
|
let lastX, lastY;
|
||||||
|
|
||||||
|
return (x, y) => {
|
||||||
|
if ( x !== undefined ) {
|
||||||
|
lastX = x; lastY = y;
|
||||||
|
} else if ( lastX !== undefined ) {
|
||||||
|
x = lastX; y = lastY;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( !pickerRoot ) { return null; }
|
||||||
|
const magicAttr = `${vAPI.sessionId}-clickblind`;
|
||||||
|
pickerRoot.setAttribute(magicAttr, '');
|
||||||
|
let elem = document.elementFromPoint(x, y);
|
||||||
|
if ( elem === document.body || elem === document.documentElement ) {
|
||||||
|
elem = null;
|
||||||
|
}
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/380
|
||||||
|
pickerRoot.removeAttribute(magicAttr);
|
||||||
|
return elem;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const highlightElementAtPoint = function(mx, my) {
|
||||||
|
const elem = elementFromPoint(mx, my);
|
||||||
|
highlightElements(elem ? [ elem ] : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const filterElementAtPoint = function(mx, my, broad) {
|
||||||
|
if ( filtersFrom(mx, my) === 0 ) { return; }
|
||||||
|
showDialog({ broad });
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://www.reddit.com/r/uBlockOrigin/comments/bktxtb/scrolling_doesnt_work/emn901o
|
// https://www.reddit.com/r/uBlockOrigin/comments/bktxtb/scrolling_doesnt_work/emn901o
|
||||||
// Override 'fixed' position property on body element if present.
|
// Override 'fixed' position property on body element if present.
|
||||||
|
|
||||||
const zap = function() {
|
// With touch-driven devices, first highlight the element and remove only
|
||||||
if ( targetElements.length === 0 ) { return; }
|
// when tapping again the highlighted area.
|
||||||
|
|
||||||
|
const zapElementAtPoint = function(mx, my, options) {
|
||||||
|
if ( options.highlight ) {
|
||||||
|
const elem = elementFromPoint(mx, my);
|
||||||
|
if ( elem ) {
|
||||||
|
highlightElements([ elem ]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let elem = targetElements.length !== 0 && targetElements[0] || null;
|
||||||
|
if ( elem === null && mx !== undefined ) {
|
||||||
|
elem = elementFromPoint(mx, my);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( elem instanceof HTMLElement === false ) { return; }
|
||||||
|
|
||||||
const getStyleValue = function(elem, prop) {
|
const getStyleValue = function(elem, prop) {
|
||||||
const style = window.getComputedStyle(elem);
|
const style = window.getComputedStyle(elem);
|
||||||
return style ? style[prop] : '';
|
return style ? style[prop] : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
let elem = targetElements[0];
|
|
||||||
// Heuristic to detect scroll-locking: remove such lock when detected.
|
// Heuristic to detect scroll-locking: remove such lock when detected.
|
||||||
if (
|
if (
|
||||||
parseInt(getStyleValue(elem, 'zIndex'), 10) >= 1000 ||
|
parseInt(getStyleValue(elem, 'zIndex'), 10) >= 1000 ||
|
||||||
|
@ -960,173 +983,8 @@ const zap = function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.parentNode.removeChild(elem);
|
elem.remove();
|
||||||
elem = elementFromPoint();
|
highlightElementAtPoint(mx, my);
|
||||||
highlightElements(elem ? [ elem ] : []);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const elementFromPoint = (( ) => {
|
|
||||||
let lastX, lastY;
|
|
||||||
|
|
||||||
return (x, y) => {
|
|
||||||
if ( x !== undefined ) {
|
|
||||||
lastX = x; lastY = y;
|
|
||||||
} else if ( lastX !== undefined ) {
|
|
||||||
x = lastX; y = lastY;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if ( !pickerRoot ) { return null; }
|
|
||||||
pickerRoot.style.setProperty('pointer-events', 'none', 'important');
|
|
||||||
let elem = document.elementFromPoint(x, y);
|
|
||||||
if ( elem === document.body || elem === document.documentElement ) {
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/380
|
|
||||||
pickerRoot.style.setProperty('pointer-events', 'auto', 'important');
|
|
||||||
return elem;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const onSvgHovered = (( ) => {
|
|
||||||
let timer;
|
|
||||||
let mx = 0, my = 0;
|
|
||||||
|
|
||||||
const onTimer = function() {
|
|
||||||
timer = undefined;
|
|
||||||
const elem = elementFromPoint(mx, my);
|
|
||||||
highlightElements(elem ? [elem] : []);
|
|
||||||
};
|
|
||||||
|
|
||||||
return function onMove(ev) {
|
|
||||||
mx = ev.clientX;
|
|
||||||
my = ev.clientY;
|
|
||||||
if ( timer === undefined ) {
|
|
||||||
timer = vAPI.setTimeout(onTimer, 40);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
|
|
||||||
Swipe right:
|
|
||||||
If picker not paused: quit picker
|
|
||||||
If picker paused and dialog visible: hide dialog
|
|
||||||
If picker paused and dialog not visible: quit picker
|
|
||||||
|
|
||||||
Swipe left:
|
|
||||||
If picker paused and dialog not visible: show dialog
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const onSvgTouchStartStop = (( ) => {
|
|
||||||
var startX,
|
|
||||||
startY;
|
|
||||||
return function onTouch(ev) {
|
|
||||||
if ( ev.type === 'touchstart' ) {
|
|
||||||
startX = ev.touches[0].screenX;
|
|
||||||
startY = ev.touches[0].screenY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( startX === undefined ) { return; }
|
|
||||||
if ( ev.cancelable === false ) { return; }
|
|
||||||
var stopX = ev.changedTouches[0].screenX,
|
|
||||||
stopY = ev.changedTouches[0].screenY,
|
|
||||||
angle = Math.abs(Math.atan2(stopY - startY, stopX - startX)),
|
|
||||||
distance = Math.sqrt(
|
|
||||||
Math.pow(stopX - startX, 2),
|
|
||||||
Math.pow(stopY - startY, 2)
|
|
||||||
);
|
|
||||||
// Interpret touch events as a click events if swipe is not valid.
|
|
||||||
if ( distance < 32 ) {
|
|
||||||
onSvgClicked({
|
|
||||||
type: 'touch',
|
|
||||||
target: ev.target,
|
|
||||||
clientX: ev.changedTouches[0].pageX,
|
|
||||||
clientY: ev.changedTouches[0].pageY,
|
|
||||||
isTrusted: ev.isTrusted
|
|
||||||
});
|
|
||||||
ev.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( distance < 64 ) { return; }
|
|
||||||
var angleUpperBound = Math.PI * 0.25 * 0.5,
|
|
||||||
swipeRight = angle < angleUpperBound;
|
|
||||||
if ( swipeRight === false && angle < Math.PI - angleUpperBound ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ev.preventDefault();
|
|
||||||
// Swipe left.
|
|
||||||
if ( swipeRight === false ) {
|
|
||||||
if ( pickerBody.classList.contains('paused') ) {
|
|
||||||
dialog.classList.remove('hide');
|
|
||||||
dialog.classList.add('show');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Swipe right.
|
|
||||||
if (
|
|
||||||
pickerBody.classList.contains('paused') &&
|
|
||||||
dialog.classList.contains('show')
|
|
||||||
) {
|
|
||||||
dialog.classList.remove('show');
|
|
||||||
dialog.classList.add('hide');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stopPicker();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const onSvgClicked = function(ev) {
|
|
||||||
if ( ev.isTrusted === false ) { return; }
|
|
||||||
|
|
||||||
// If zap mode, highlight element under mouse, this makes the zapper usable
|
|
||||||
// on touch screens.
|
|
||||||
if ( pickerBootArgs.zap ) {
|
|
||||||
let elem = targetElements.lenght !== 0 && targetElements[0];
|
|
||||||
if ( !elem || ev.target !== svgIslands ) {
|
|
||||||
elem = elementFromPoint(ev.clientX, ev.clientY);
|
|
||||||
if ( elem !== null ) {
|
|
||||||
highlightElements([elem]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zap();
|
|
||||||
if ( !ev.shiftKey ) {
|
|
||||||
stopPicker();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
|
|
||||||
// Unpause picker if:
|
|
||||||
// - click outside dialog AND
|
|
||||||
// - not in preview mode
|
|
||||||
if ( pickerBody.classList.contains('paused') ) {
|
|
||||||
if ( filterToDOMInterface.previewing === false ) {
|
|
||||||
unpausePicker();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showDialog({
|
|
||||||
show: ev.type === 'touch',
|
|
||||||
modifier: ev.ctrlKey
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const svgListening = function(on) {
|
|
||||||
const action = (on ? 'add' : 'remove') + 'EventListener';
|
|
||||||
svgRoot[action]('mousemove', onSvgHovered, { passive: true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -1139,7 +997,7 @@ const onKeyPressed = function(ev) {
|
||||||
) {
|
) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
zap();
|
zapElementAtPoint();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Esc
|
// Esc
|
||||||
|
@ -1147,7 +1005,7 @@ const onKeyPressed = function(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
filterToDOMInterface.preview(false);
|
filterToDOMInterface.preview(false);
|
||||||
stopPicker();
|
quitPicker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1158,67 +1016,18 @@ const onKeyPressed = function(ev) {
|
||||||
// May need to dynamically adjust the height of the overlay + new position
|
// May need to dynamically adjust the height of the overlay + new position
|
||||||
// of highlighted elements.
|
// of highlighted elements.
|
||||||
|
|
||||||
const onScrolled = function() {
|
const onViewportChanged = function() {
|
||||||
highlightElements(targetElements, true);
|
highlightElements(targetElements, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const pausePicker = function() {
|
|
||||||
pickerBody.classList.add('paused');
|
|
||||||
svgListening(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const unpausePicker = function() {
|
|
||||||
filterToDOMInterface.preview(false);
|
|
||||||
pickerBody.classList.remove('paused');
|
|
||||||
svgListening(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
targetElements = [];
|
|
||||||
candidateElements = [];
|
|
||||||
bestCandidateFilter = null;
|
|
||||||
|
|
||||||
if ( pickerRoot === null ) { return; }
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2060
|
|
||||||
if ( vAPI.domFilterer instanceof Object ) {
|
|
||||||
vAPI.userStylesheet.remove(pickerCSS);
|
|
||||||
vAPI.userStylesheet.apply();
|
|
||||||
vAPI.domFilterer.unexcludeNode(pickerRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener('scroll', onScrolled, true);
|
|
||||||
svgListening(false);
|
|
||||||
pickerRoot.remove();
|
|
||||||
pickerRoot = pickerBody = svgRoot = svgOcean = svgIslands = dialog = null;
|
|
||||||
|
|
||||||
window.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Auto-select a specific target, if any, and if possible
|
// Auto-select a specific target, if any, and if possible
|
||||||
|
|
||||||
const startPicker = function() {
|
const startPicker = function() {
|
||||||
svgRoot.addEventListener('click', onSvgClicked);
|
self.addEventListener('scroll', onViewportChanged, { passive: true });
|
||||||
svgRoot.addEventListener('touchstart', onSvgTouchStartStop);
|
self.addEventListener('resize', onViewportChanged, { passive: true });
|
||||||
svgRoot.addEventListener('touchend', onSvgTouchStartStop);
|
self.addEventListener('keydown', onKeyPressed, true);
|
||||||
svgListening(true);
|
|
||||||
|
|
||||||
self.addEventListener('scroll', onScrolled, true);
|
|
||||||
pickerRoot.contentWindow.addEventListener('keydown', onKeyPressed, true);
|
|
||||||
pickerRoot.contentWindow.focus();
|
|
||||||
|
|
||||||
// Try using mouse position
|
// Try using mouse position
|
||||||
if (
|
if (
|
||||||
|
@ -1227,8 +1036,7 @@ const startPicker = function() {
|
||||||
vAPI.mouseClick.x > 0
|
vAPI.mouseClick.x > 0
|
||||||
) {
|
) {
|
||||||
if ( filtersFrom(vAPI.mouseClick.x, vAPI.mouseClick.y) !== 0 ) {
|
if ( filtersFrom(vAPI.mouseClick.x, vAPI.mouseClick.y) !== 0 ) {
|
||||||
showDialog();
|
return showDialog();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1259,35 +1067,55 @@ const startPicker = function() {
|
||||||
}
|
}
|
||||||
elem.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
elem.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
filtersFrom(elem);
|
filtersFrom(elem);
|
||||||
showDialog({ modifier: true });
|
return showDialog({ broad: true });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A target was specified, but it wasn't found: abort.
|
// A target was specified, but it wasn't found: abort.
|
||||||
stopPicker();
|
quitPicker();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Let's have the element picker code flushed from memory when no longer
|
||||||
|
// in use: to ensure this, release all local references.
|
||||||
|
|
||||||
|
const quitPicker = function() {
|
||||||
|
self.removeEventListener('scroll', onViewportChanged, { passive: true });
|
||||||
|
self.removeEventListener('resize', onViewportChanged, { passive: true });
|
||||||
|
self.removeEventListener('keydown', onKeyPressed, true);
|
||||||
|
vAPI.shutdown.remove(quitPicker);
|
||||||
|
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
|
||||||
|
vAPI.MessagingConnection.removeListener(onConnectionMessage);
|
||||||
|
vAPI.userStylesheet.remove(pickerCSS);
|
||||||
|
vAPI.userStylesheet.apply();
|
||||||
|
|
||||||
|
if ( pickerRoot === null ) { return; }
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/2060
|
||||||
|
if ( vAPI.domFilterer instanceof Object ) {
|
||||||
|
vAPI.domFilterer.unexcludeNode(pickerRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
pickerRoot.remove();
|
||||||
|
pickerRoot = null;
|
||||||
|
|
||||||
|
self.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onDialogMessage = function(msg) {
|
const onDialogMessage = function(msg) {
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'dialogInit':
|
case 'start':
|
||||||
startPicker();
|
startPicker();
|
||||||
break;
|
if ( targetElements.length === 0 ) {
|
||||||
case 'dialogPreview':
|
highlightElements([], true);
|
||||||
filterToDOMInterface.preview(msg.state);
|
}
|
||||||
break;
|
break;
|
||||||
case 'dialogCreate':
|
case 'dialogCreate':
|
||||||
filterToDOMInterface.queryAll(msg);
|
filterToDOMInterface.queryAll(msg);
|
||||||
filterToDOMInterface.preview(true, true);
|
filterToDOMInterface.preview(true, true);
|
||||||
stopPicker();
|
quitPicker();
|
||||||
break;
|
|
||||||
case 'dialogPick':
|
|
||||||
unpausePicker();
|
|
||||||
break;
|
|
||||||
case 'dialogQuit':
|
|
||||||
filterToDOMInterface.preview(false);
|
|
||||||
stopPicker();
|
|
||||||
break;
|
break;
|
||||||
case 'dialogSetFilter': {
|
case 'dialogSetFilter': {
|
||||||
const resultset = filterToDOMInterface.queryAll(msg);
|
const resultset = filterToDOMInterface.queryAll(msg);
|
||||||
|
@ -1303,6 +1131,28 @@ const onDialogMessage = function(msg) {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'quitPicker':
|
||||||
|
filterToDOMInterface.preview(false);
|
||||||
|
quitPicker();
|
||||||
|
break;
|
||||||
|
case 'highlightElementAtPoint':
|
||||||
|
highlightElementAtPoint(msg.mx, msg.my);
|
||||||
|
break;
|
||||||
|
case 'unhighlight':
|
||||||
|
highlightElements([]);
|
||||||
|
break;
|
||||||
|
case 'filterElementAtPoint':
|
||||||
|
filterElementAtPoint(msg.mx, msg.my, msg.broad);
|
||||||
|
break;
|
||||||
|
case 'zapElementAtPoint':
|
||||||
|
zapElementAtPoint(msg.mx, msg.my, msg.options);
|
||||||
|
if ( msg.options.highlight !== true && msg.options.stay !== true ) {
|
||||||
|
quitPicker();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'togglePreview':
|
||||||
|
filterToDOMInterface.preview(msg.state);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1311,18 +1161,13 @@ const onDialogMessage = function(msg) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onConnectionMessage = function(msg) {
|
const onConnectionMessage = function(msg) {
|
||||||
if (
|
if ( msg.from !== `epickerDialog-${epickerId}` ) { return; }
|
||||||
msg.from !== `epickerDialog-${epickerId}` ||
|
|
||||||
msg.to !== `epicker-${epickerId}`
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'connectionRequested':
|
case 'connectionRequested':
|
||||||
epickerConnectionId = msg.id;
|
epickerConnectionId = msg.id;
|
||||||
return true;
|
return true;
|
||||||
case 'connectionBroken':
|
case 'connectionBroken':
|
||||||
stopPicker();
|
quitPicker();
|
||||||
break;
|
break;
|
||||||
case 'connectionMessage':
|
case 'connectionMessage':
|
||||||
onDialogMessage(msg.payload);
|
onDialogMessage(msg.payload);
|
||||||
|
@ -1332,16 +1177,60 @@ const onConnectionMessage = function(msg) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
pickerRoot = document.createElement('iframe');
|
// epicker-ui.html will be injected in the page through an iframe, and
|
||||||
pickerRoot.setAttribute(vAPI.sessionId, '');
|
// is a sandboxed so as to prevent the page from interfering with its
|
||||||
|
// content and behavior.
|
||||||
|
//
|
||||||
|
// The purpose of epicker.js is to:
|
||||||
|
// - Install the element picker UI, and wait for the component to establish
|
||||||
|
// a direct communication channel.
|
||||||
|
// - Lookup candidate filters from elements at a specific position.
|
||||||
|
// - Highlight element(s) at a specific position or according to whether
|
||||||
|
// they match candidate filters;
|
||||||
|
// - Preview the result of applying a candidate filter;
|
||||||
|
//
|
||||||
|
// When the element picker is installed on a page, the only change the page
|
||||||
|
// sees is an iframe with a random attribute. The page can't see the content
|
||||||
|
// of the iframe, and cannot interfere with its style properties. However the
|
||||||
|
// page can remove the iframe.
|
||||||
|
|
||||||
|
// We need extra messaging capabilities + fetch/process picker arguments.
|
||||||
|
{
|
||||||
|
const results = await Promise.all([
|
||||||
|
vAPI.messaging.extend(),
|
||||||
|
vAPI.messaging.send('elementPicker', { what: 'elementPickerArguments' }),
|
||||||
|
]);
|
||||||
|
if ( results[0] !== true ) { return; }
|
||||||
|
pickerBootArgs = results[1];
|
||||||
|
if ( typeof pickerBootArgs !== 'object' || pickerBootArgs === null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Restore net filter union data if origin is the same.
|
||||||
|
const eprom = pickerBootArgs.eprom || null;
|
||||||
|
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
|
||||||
|
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
|
||||||
|
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DOM filterer will not be present when cosmetic filtering is disabled.
|
||||||
|
if (
|
||||||
|
pickerBootArgs.zap !== true &&
|
||||||
|
vAPI.domFilterer instanceof Object === false
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1529
|
||||||
|
// In addition to inline styles, harden the element picker styles by using
|
||||||
|
// dedicated CSS rules.
|
||||||
const pickerCSSStyle = [
|
const pickerCSSStyle = [
|
||||||
'background: transparent',
|
'background: transparent',
|
||||||
'border: 0',
|
'border: 0',
|
||||||
'border-radius: 0',
|
'border-radius: 0',
|
||||||
'box-shadow: none',
|
'box-shadow: none',
|
||||||
'display: block',
|
'display: block',
|
||||||
'height: 100%',
|
'height: 100vh',
|
||||||
'left: 0',
|
'left: 0',
|
||||||
'margin: 0',
|
'margin: 0',
|
||||||
'max-height: none',
|
'max-height: none',
|
||||||
|
@ -1351,6 +1240,7 @@ const pickerCSSStyle = [
|
||||||
'opacity: 1',
|
'opacity: 1',
|
||||||
'outline: 0',
|
'outline: 0',
|
||||||
'padding: 0',
|
'padding: 0',
|
||||||
|
'pointer-events: auto',
|
||||||
'position: fixed',
|
'position: fixed',
|
||||||
'top: 0',
|
'top: 0',
|
||||||
'visibility: visible',
|
'visibility: visible',
|
||||||
|
@ -1358,107 +1248,40 @@ const pickerCSSStyle = [
|
||||||
'z-index: 2147483647',
|
'z-index: 2147483647',
|
||||||
''
|
''
|
||||||
].join(' !important;');
|
].join(' !important;');
|
||||||
pickerRoot.style.cssText = pickerCSSStyle;
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/393
|
|
||||||
// This needs to be injected as an inline style, *never* as a user style,
|
|
||||||
// hence why it's not added above as part of the pickerCSSStyle
|
|
||||||
// properties.
|
|
||||||
pickerRoot.style.setProperty('pointer-events', 'auto', 'important');
|
|
||||||
|
|
||||||
const pickerCSS = `
|
const pickerCSS = `
|
||||||
[${vAPI.sessionId}] {
|
:root [${vAPI.sessionId}] {
|
||||||
${pickerCSSStyle}
|
${pickerCSSStyle}
|
||||||
}
|
}
|
||||||
[${vAPI.sessionId}-clickblind] {
|
:root [${vAPI.sessionId}-clickblind] {
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
{
|
|
||||||
const pickerRootLoaded = new Promise(resolve => {
|
|
||||||
pickerRoot.addEventListener('load', ( ) => { resolve(); }, { once: true });
|
|
||||||
});
|
|
||||||
document.documentElement.append(pickerRoot);
|
|
||||||
|
|
||||||
const results = await Promise.all([
|
|
||||||
vAPI.messaging.send('elementPicker', { what: 'elementPickerArguments' }),
|
|
||||||
pickerRootLoaded,
|
|
||||||
]);
|
|
||||||
|
|
||||||
pickerBootArgs = results[0];
|
|
||||||
|
|
||||||
// The DOM filterer will not be present when cosmetic filtering is
|
|
||||||
// disabled.
|
|
||||||
if (
|
|
||||||
pickerBootArgs.zap !== true &&
|
|
||||||
vAPI.domFilterer instanceof Object === false
|
|
||||||
) {
|
|
||||||
pickerRoot.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore net filter union data if origin is the same.
|
|
||||||
const eprom = pickerBootArgs.eprom || null;
|
|
||||||
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
|
|
||||||
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
|
|
||||||
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const frameDoc = pickerRoot.contentDocument;
|
|
||||||
|
|
||||||
// Provide an id users can use as anchor to personalize uBO's element
|
|
||||||
// picker style properties.
|
|
||||||
frameDoc.documentElement.id = 'ublock0-epicker';
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2240
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/170
|
|
||||||
// Remove the already declared inline style tag: we will create a new
|
|
||||||
// one based on the removed one, and replace the old one.
|
|
||||||
const style = frameDoc.createElement('style');
|
|
||||||
style.textContent = pickerBootArgs.frameCSS;
|
|
||||||
frameDoc.head.appendChild(style);
|
|
||||||
|
|
||||||
pickerBody = frameDoc.body;
|
|
||||||
pickerBody.setAttribute('lang', navigator.language);
|
|
||||||
pickerBody.classList.toggle('zap', pickerBootArgs.zap === true);
|
|
||||||
|
|
||||||
svgRoot = frameDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
||||||
svgOcean = frameDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
||||||
svgRoot.append(svgOcean);
|
|
||||||
svgIslands = frameDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
||||||
svgRoot.append(svgIslands);
|
|
||||||
pickerBody.append(svgRoot);
|
|
||||||
|
|
||||||
dialog = frameDoc.createElement('iframe');
|
|
||||||
pickerBody.append(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
highlightElements([], true);
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1529
|
|
||||||
// In addition to inline styles, harden the element picker styles by using
|
|
||||||
// dedicated CSS rules.
|
|
||||||
vAPI.userStylesheet.add(pickerCSS);
|
vAPI.userStylesheet.add(pickerCSS);
|
||||||
vAPI.userStylesheet.apply();
|
vAPI.userStylesheet.apply();
|
||||||
|
|
||||||
vAPI.shutdown.add(stopPicker);
|
pickerRoot = document.createElement('iframe');
|
||||||
|
pickerRoot.setAttribute(vAPI.sessionId, '');
|
||||||
// https://github.com/gorhill/uBlock/issues/3497
|
document.documentElement.append(pickerRoot);
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1215
|
|
||||||
// Instantiate isolated element picker dialog.
|
|
||||||
if ( pickerBootArgs.zap === true ) {
|
|
||||||
startPicker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2060
|
// https://github.com/gorhill/uBlock/issues/2060
|
||||||
|
if ( vAPI.domFilterer instanceof Object ) {
|
||||||
vAPI.domFilterer.excludeNode(pickerRoot);
|
vAPI.domFilterer.excludeNode(pickerRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
vAPI.shutdown.add(quitPicker);
|
||||||
|
|
||||||
if ( await vAPI.messaging.extend() !== true ) { return; }
|
|
||||||
vAPI.MessagingConnection.addListener(onConnectionMessage);
|
vAPI.MessagingConnection.addListener(onConnectionMessage);
|
||||||
|
|
||||||
dialog.contentWindow.location = `${pickerBootArgs.dialogURL}&epid=${epickerId}`;
|
{
|
||||||
|
const url = new URL(pickerBootArgs.pickerURL);
|
||||||
|
url.searchParams.set('epid', epickerId);
|
||||||
|
if ( pickerBootArgs.zap ) {
|
||||||
|
url.searchParams.set('zap', '1');
|
||||||
|
}
|
||||||
|
pickerRoot.contentWindow.location = url.href;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>uBlock Origin Element Picker</title>
|
<title>uBlock Origin Element Picker</title>
|
||||||
<link rel="stylesheet" href="../css/epicker-dialog.css">
|
<link rel="stylesheet" href="../css/epicker-ui.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -35,13 +35,14 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
|
<svg><path d></path><path d></path></svg>
|
||||||
|
|
||||||
<script src="../js/vapi.js"></script>
|
<script src="../js/vapi.js"></script>
|
||||||
<script src="../js/vapi-common.js"></script>
|
<script src="../js/vapi-common.js"></script>
|
||||||
<script src="../js/vapi-client.js"></script>
|
<script src="../js/vapi-client.js"></script>
|
||||||
<script src="../js/vapi-client-extra.js"></script>
|
<script src="../js/vapi-client-extra.js"></script>
|
||||||
<script src="../js/i18n.js"></script>
|
<script src="../js/i18n.js"></script>
|
||||||
<script src="../js/epicker-dialog.js"></script>
|
<script src="../js/epicker-ui.js"></script>
|
||||||
<script src="../js/static-filtering-parser.js"></script>
|
<script src="../js/static-filtering-parser.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
Loading…
Reference in a new issue