mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
237 lines
8.7 KiB
JavaScript
237 lines
8.7 KiB
JavaScript
/*******************************************************************************
|
|
|
|
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
|
Copyright (C) 2025-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
|
|
*/
|
|
|
|
import { dom, qs$ } from './dom.js';
|
|
import { sendMessage } from './ext.js';
|
|
|
|
/******************************************************************************/
|
|
|
|
export const toolOverlay = {
|
|
url: new URL('about:blank'),
|
|
svgRoot: qs$('svg#overlay'),
|
|
svgOcean: qs$('svg#overlay > path'),
|
|
svgIslands: qs$('svg#overlay > path + path'),
|
|
emptyPath: 'M0 0',
|
|
port: null,
|
|
|
|
start(onmessage) {
|
|
this.onmessage = onmessage;
|
|
globalThis.addEventListener('message', ev => {
|
|
const msg = ev.data || {};
|
|
if ( msg.what !== 'startOverlay' ) { return; }
|
|
if ( Array.isArray(ev.ports) === false ) { return; }
|
|
if ( ev.ports.length === 0 ) { return; }
|
|
toolOverlay.port = ev.ports[0];
|
|
toolOverlay.port.onmessage = ev => {
|
|
this.onMessage(ev.data || {});
|
|
};
|
|
toolOverlay.port.onmessageerror = ( ) => {
|
|
this.onmessage({ what: 'stopTool' });
|
|
};
|
|
this.moveable = qs$('aside:has(#move)');
|
|
if ( this.moveable !== null ) {
|
|
dom.on('aside #move', 'pointerdown', ev => { this.mover(ev); });
|
|
dom.on('aside #move', 'touchstart', this.eatTouchEvent);
|
|
}
|
|
this.onMessage({ what: 'startTool',
|
|
url: msg.url,
|
|
width: msg.width,
|
|
height: msg.height,
|
|
});
|
|
dom.cl.remove(dom.body, 'loading');
|
|
}, { once: true });
|
|
},
|
|
|
|
stop() {
|
|
this.highlightElementUnderMouse(false);
|
|
if ( this.port ) {
|
|
this.port.postMessage({ what: 'quitTool' });
|
|
this.port.onmessage = null;
|
|
this.port.onmessageerror = null;
|
|
this.port = null;
|
|
}
|
|
},
|
|
|
|
onMessage(wrapped) {
|
|
// Response to frame-initiated message?
|
|
if ( typeof wrapped?.fromFrameId === 'number' ) {
|
|
const resolve = this.pendingMessages.get(wrapped.fromFrameId);
|
|
if ( resolve ) {
|
|
this.pendingMessages.delete(wrapped.fromFrameId);
|
|
resolve(wrapped.msg);
|
|
}
|
|
return;
|
|
}
|
|
const msg = wrapped.msg || wrapped;
|
|
switch ( msg.what ) {
|
|
case 'startTool': {
|
|
this.url.href = msg.url;
|
|
const ow = msg.width;
|
|
const oh = msg.height;
|
|
this.svgOcean.setAttribute('d', `M0 0h${ow}v${oh}h-${ow}z`);
|
|
break;
|
|
}
|
|
case 'svgPaths':
|
|
this.svgOcean.setAttribute('d', msg.ocean + msg.islands);
|
|
this.svgIslands.setAttribute('d', msg.islands || this.emptyPath);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
const response = this.onmessage && this.onmessage(msg) || undefined;
|
|
// Send response if this is script-initiated message
|
|
if ( wrapped?.fromScriptId && this.port ) {
|
|
const { fromScriptId } = wrapped;
|
|
if ( response instanceof Promise ) {
|
|
response.then(response => {
|
|
if ( this.port === null ) { return; }
|
|
this.port.postMessage({ fromScriptId, msg: response });
|
|
});
|
|
} else {
|
|
this.port.postMessage({ fromScriptId, msg: response });
|
|
}
|
|
}
|
|
},
|
|
postMessage(msg) {
|
|
if ( this.port === null ) { return; }
|
|
const wrapped = {
|
|
fromFrameId: this.messageId++,
|
|
msg,
|
|
};
|
|
return new Promise(resolve => {
|
|
this.pendingMessages.set(wrapped.fromFrameId, resolve);
|
|
this.port.postMessage(wrapped);
|
|
});
|
|
},
|
|
messageId: 1,
|
|
pendingMessages: new Map(),
|
|
|
|
sendMessage(msg) {
|
|
return sendMessage(msg);
|
|
},
|
|
|
|
highlightElementUnderMouse(state) {
|
|
if ( dom.cl.has(dom.root, 'mobile') ) { return; }
|
|
if ( state === this.mstrackerOn ) { return; }
|
|
this.mstrackerOn = state;
|
|
if ( this.mstrackerOn ) {
|
|
dom.on(document, 'mousemove', this.onHover, { passive: true });
|
|
return;
|
|
}
|
|
dom.off(document, 'mousemove', this.onHover, { passive: true });
|
|
if ( this.mstrackerTimer === undefined ) { return; }
|
|
self.cancelAnimationFrame(this.mstrackerTimer);
|
|
this.mstrackerTimer = undefined;
|
|
},
|
|
onTimer() {
|
|
toolOverlay.mstrackerTimer = undefined;
|
|
if ( toolOverlay.port === null ) { return; }
|
|
toolOverlay.port.postMessage({
|
|
what: 'highlightElementAtPoint',
|
|
mx: toolOverlay.mstrackerX,
|
|
my: toolOverlay.mstrackerY,
|
|
});
|
|
},
|
|
onHover(ev) {
|
|
toolOverlay.mstrackerX = ev.clientX;
|
|
toolOverlay.mstrackerY = ev.clientY;
|
|
if ( toolOverlay.mstrackerTimer !== undefined ) { return; }
|
|
toolOverlay.mstrackerTimer =
|
|
self.requestAnimationFrame(toolOverlay.onTimer);
|
|
},
|
|
mstrackerOn: false,
|
|
mstrackerX: 0, mstrackerY: 0,
|
|
mstrackerTimer: undefined,
|
|
|
|
mover(ev) {
|
|
const target = ev.target;
|
|
if ( target.matches('#move') === false ) { return; }
|
|
if ( dom.cl.has(this.moveable, 'moving') ) { return; }
|
|
target.setPointerCapture(ev.pointerId);
|
|
this.moverX0 = ev.pageX;
|
|
this.moverY0 = ev.pageY;
|
|
const rect = this.moveable.getBoundingClientRect();
|
|
this.moverCX0 = rect.x + rect.width / 2;
|
|
this.moverCY0 = rect.y + rect.height / 2;
|
|
dom.cl.add(this.moveable, 'moving');
|
|
self.addEventListener('pointermove', this.moverMoveAsync, {
|
|
passive: true,
|
|
capture: true,
|
|
});
|
|
self.addEventListener('pointerup', this.moverStop, { capture: true, once: true });
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
},
|
|
moverMove() {
|
|
this.moverTimer = undefined;
|
|
const cx1 = this.moverCX0 + this.moverX1 - this.moverX0;
|
|
const cy1 = this.moverCY0 + this.moverY1 - this.moverY0;
|
|
const rootW = dom.root.clientWidth;
|
|
const rootH = dom.root.clientHeight;
|
|
const moveableW = this.moveable.clientWidth;
|
|
const moveableH = this.moveable.clientHeight;
|
|
if ( cx1 < rootW / 2 ) {
|
|
this.moveable.style.setProperty('left', `${Math.max(cx1-moveableW/2,2)}px`);
|
|
this.moveable.style.removeProperty('right');
|
|
} else {
|
|
this.moveable.style.removeProperty('left');
|
|
this.moveable.style.setProperty('right', `${Math.max(rootW-cx1-moveableW/2,2)}px`);
|
|
}
|
|
if ( cy1 < rootH / 2 ) {
|
|
this.moveable.style.setProperty('top', `${Math.max(cy1-moveableH/2,2)}px`);
|
|
this.moveable.style.removeProperty('bottom');
|
|
} else {
|
|
this.moveable.style.removeProperty('top');
|
|
this.moveable.style.setProperty('bottom', `${Math.max(rootH-cy1-moveableH/2,2)}px`);
|
|
}
|
|
},
|
|
moverMoveAsync(ev) {
|
|
toolOverlay.moverX1 = ev.pageX;
|
|
toolOverlay.moverY1 = ev.pageY;
|
|
if ( toolOverlay.moverTimer !== undefined ) { return; }
|
|
toolOverlay.moverTimer = self.requestAnimationFrame(( ) => {
|
|
toolOverlay.moverMove();
|
|
});
|
|
},
|
|
moverStop(ev) {
|
|
if ( dom.cl.has(toolOverlay.moveable, 'moving') === false ) { return; }
|
|
dom.cl.remove(toolOverlay.moveable, 'moving');
|
|
self.removeEventListener('pointermove', toolOverlay.moverMoveAsync, {
|
|
passive: true,
|
|
capture: true,
|
|
});
|
|
ev.target.releasePointerCapture(ev.pointerId);
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
},
|
|
eatTouchEvent(ev) {
|
|
if ( ev.target !== qs$('aside #move') ) { return; }
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
},
|
|
moveable: null,
|
|
moverX0: 0, moverY0: 0,
|
|
moverX1: 0, moverY1: 0,
|
|
moverCX0: 0, moverCY0: 0,
|
|
moverTimer: undefined,
|
|
};
|
|
|
|
/******************************************************************************/
|