uBlock/platform/chromium/vapi-client-extra.js

313 lines
9.1 KiB
JavaScript
Raw Normal View History

/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2019-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
*/
// For non-background page
'use strict';
/******************************************************************************/
// Direct messaging connection ability
(( ) => {
// >>>>>>>> start of private namespace
if (
typeof vAPI !== 'object' ||
vAPI.messaging instanceof Object === false ||
vAPI.MessagingConnection instanceof Function
) {
return;
}
const listeners = new Set();
const connections = new Map();
vAPI.MessagingConnection = class {
constructor(handler, details) {
this.messaging = vAPI.messaging;
this.handler = handler;
this.id = details.id;
this.to = details.to;
this.toToken = details.toToken;
this.from = details.from;
this.fromToken = details.fromToken;
this.checkTimer = undefined;
// On Firefox it appears ports are not automatically disconnected
// when navigating to another page.
const ctor = vAPI.MessagingConnection;
if ( ctor.pagehide !== undefined ) { return; }
ctor.pagehide = ( ) => {
for ( const connection of connections.values() ) {
connection.disconnect();
connection.handler(
connection.toDetails('connectionBroken')
);
}
};
window.addEventListener('pagehide', ctor.pagehide);
}
toDetails(what, payload) {
return {
what: what,
id: this.id,
from: this.from,
fromToken: this.fromToken,
to: this.to,
toToken: this.toToken,
payload: payload
};
}
disconnect() {
if ( this.checkTimer !== undefined ) {
clearTimeout(this.checkTimer);
this.checkTimer = undefined;
}
connections.delete(this.id);
const port = this.messaging.getPort();
if ( port === null ) { return; }
port.postMessage({
channel: 'vapi',
msg: this.toDetails('connectionBroken'),
});
}
checkAsync() {
if ( this.checkTimer !== undefined ) {
clearTimeout(this.checkTimer);
}
this.checkTimer = vAPI.setTimeout(
( ) => { this.check(); },
499
);
}
check() {
this.checkTimer = undefined;
if ( connections.has(this.id) === false ) { return; }
const port = this.messaging.getPort();
if ( port === null ) { return; }
port.postMessage({
channel: 'vapi',
msg: this.toDetails('connectionCheck'),
});
this.checkAsync();
}
receive(details) {
switch ( details.what ) {
case 'connectionAccepted':
this.toToken = details.toToken;
this.handler(details);
this.checkAsync();
break;
case 'connectionBroken':
connections.delete(this.id);
this.handler(details);
break;
case 'connectionMessage':
this.handler(details);
this.checkAsync();
break;
case 'connectionCheck':
const port = this.messaging.getPort();
if ( port === null ) { return; }
if ( connections.has(this.id) ) {
this.checkAsync();
} else {
details.what = 'connectionBroken';
port.postMessage({ channel: 'vapi', msg: details });
}
break;
case 'connectionRefused':
connections.delete(this.id);
this.handler(details);
break;
}
}
send(payload) {
const port = this.messaging.getPort();
if ( port === null ) { return; }
port.postMessage({
channel: 'vapi',
msg: this.toDetails('connectionMessage', payload),
});
}
static addListener(listener) {
listeners.add(listener);
vAPI.messaging.getPort(); // Ensure a port instance exists
}
static removeListener(listener) {
listeners.delete(listener);
}
static connectTo(from, to, handler) {
const port = vAPI.messaging.getPort();
if ( port === null ) { return; }
const connection = new vAPI.MessagingConnection(handler, {
id: `${from}-${to}-${vAPI.sessionId}`,
to: to,
from: from,
fromToken: port.name
});
connections.set(connection.id, connection);
port.postMessage({
channel: 'vapi',
msg: {
what: 'connectionRequested',
id: connection.id,
from: from,
fromToken: port.name,
to: to,
}
});
return connection.id;
}
static disconnectFrom(connectionId) {
const connection = connections.get(connectionId);
if ( connection === undefined ) { return; }
connection.disconnect();
}
static sendTo(connectionId, payload) {
const connection = connections.get(connectionId);
if ( connection === undefined ) { return; }
connection.send(payload);
}
static canDestroyPort() {
return listeners.length === 0 && connections.size === 0;
}
static mustDestroyPort() {
if ( connections.size === 0 ) { return; }
for ( const connection of connections.values() ) {
connection.receive({ what: 'connectionBroken' });
}
connections.clear();
}
static canProcessMessage(details) {
if ( details.channel !== 'vapi' ) { return; }
switch ( details.msg.what ) {
case 'connectionAccepted':
case 'connectionBroken':
case 'connectionCheck':
case 'connectionMessage':
case 'connectionRefused': {
const connection = connections.get(details.msg.id);
if ( connection === undefined ) { break; }
connection.receive(details.msg);
return true;
}
case 'connectionRequested':
if ( listeners.length === 0 ) { return; }
const port = vAPI.messaging.getPort();
if ( port === null ) { break; }
let listener, result;
for ( listener of listeners ) {
result = listener(details.msg);
if ( result !== undefined ) { break; }
}
if ( result === undefined ) { break; }
if ( result === true ) {
details.msg.what = 'connectionAccepted';
details.msg.toToken = port.name;
const connection = new vAPI.MessagingConnection(
listener,
details.msg
);
connections.set(connection.id, connection);
} else {
details.msg.what = 'connectionRefused';
}
port.postMessage(details);
return true;
default:
break;
}
}
};
vAPI.messaging.extensions.push(vAPI.MessagingConnection);
// <<<<<<<< end of private namespace
})();
/******************************************************************************/
// Broadcast listening ability
(( ) => {
// >>>>>>>> start of private namespace
if (
typeof vAPI !== 'object' ||
vAPI.messaging instanceof Object === false ||
vAPI.broadcastListener instanceof Object
) {
return;
}
const listeners = new Set();
vAPI.broadcastListener = {
add: function(listener) {
listeners.add(listener);
vAPI.messaging.getPort();
},
remove: function(listener) {
listeners.delete(listener);
},
canDestroyPort() {
return listeners.size === 0;
},
mustDestroyPort() {
listeners.clear();
},
canProcessMessage(details) {
if ( details.broadcast === false ) { return; }
for ( const listener of listeners ) {
listener(details.msg);
}
},
};
vAPI.messaging.extensions.push(vAPI.broadcastListener);
// <<<<<<<< end of private namespace
})();
/******************************************************************************/
/*******************************************************************************
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;