Skip to content

Commit

Permalink
Simplify client messaging code
Browse files Browse the repository at this point in the history
Little-used code from vapi-client.js has been moved
to vapi-client-extra.js. Given that vapi-client.js
is injected in all web pages, this means less dead
code being injected in all pages.

Swathes of code in vapi-client.js was used only in
a few very specific cases, such as when the logger's
DOM inspector is opened or when the "Filter lists"
pane in the dashboard is opened -- and thus to avoid
that little used code to be loaded in every web page
unconditionally, it has been moved to its own
separate file, vapi-client.extra.js.

vapi-client-extra.js is loaded declaratively or
programmatically only where needed.
  • Loading branch information
gorhill committed Sep 19, 2019
1 parent 149b5cf commit 87d0e45
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 419 deletions.
20 changes: 14 additions & 6 deletions platform/chromium/vapi-background.js
Expand Up @@ -939,10 +939,10 @@ vAPI.messaging = {
switch ( msg.what ) {
case 'connectionAccepted':
case 'connectionRefused': {
const { port: toPort } = this.ports.get(msg.fromToken);
const toPort = this.ports.get(msg.fromToken);
if ( toPort !== undefined ) {
msg.tabId = tabId;
toPort.postMessage(request);
toPort.port.postMessage(request);
} else {
msg.what = 'connectionBroken';
port.postMessage(request);
Expand All @@ -952,24 +952,32 @@ vAPI.messaging = {
case 'connectionRequested':
msg.tabId = tabId;
for ( const { port: toPort } of this.ports.values() ) {
if ( toPort === port ) { continue; }
toPort.postMessage(request);
}
break;
case 'connectionBroken':
case 'connectionCheck':
case 'connectionMessage': {
const { port: toPort } = this.ports.get(
const toPort = this.ports.get(
port.name === msg.fromToken ? msg.toToken : msg.fromToken
);
if ( toPort !== undefined ) {
msg.tabId = tabId;
toPort.postMessage(request);
toPort.port.postMessage(request);
} else {
msg.what = 'connectionBroken';
port.postMessage(request);
}
break;
}
case 'extendClient':
vAPI.tabs.executeScript(tabId, {
file: '/js/vapi-client-extra.js',
}).then(( ) => {
callback();
});
break;
case 'userCSS':
if ( tabId === undefined ) { break; }
const details = {
Expand Down Expand Up @@ -1043,7 +1051,7 @@ vAPI.messaging = {
}

// Content process to main process: framework handler.
if ( request.channelName === 'vapi' ) {
if ( request.channel === 'vapi' ) {
this.onFrameworkMessage(request, port, callback);
return;
}
Expand All @@ -1052,7 +1060,7 @@ vAPI.messaging = {
const fromDetails = this.ports.get(port.name);
if ( fromDetails === undefined ) { return; }

const listenerDetails = this.listeners.get(request.channelName);
const listenerDetails = this.listeners.get(request.channel);
let r = this.UNHANDLED;
if (
(listenerDetails !== undefined) &&
Expand Down
308 changes: 308 additions & 0 deletions platform/chromium/vapi-client-extra.js
@@ -0,0 +1,308 @@
/*******************************************************************************
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);
}
static async 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;

0 comments on commit 87d0e45

Please sign in to comment.