diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js
index 3f4d4cc79..920eed3f3 100644
--- a/platform/chromium/vapi-background.js
+++ b/platform/chromium/vapi-background.js
@@ -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);
@@ -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 = {
@@ -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;
}
@@ -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) &&
diff --git a/platform/chromium/vapi-client-extra.js b/platform/chromium/vapi-client-extra.js
new file mode 100644
index 000000000..f18280e04
--- /dev/null
+++ b/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;
diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js
index 5acb284c4..3a5bc5d5c 100644
--- a/platform/chromium/vapi-client.js
+++ b/platform/chromium/vapi-client.js
@@ -30,16 +30,18 @@
// Skip if already injected.
// >>>>>>>> start of HUGE-IF-BLOCK
-if ( typeof vAPI === 'object' && !vAPI.clientScript ) {
+if (
+ typeof vAPI === 'object' &&
+ vAPI.randomToken instanceof Function === false
+) {
/******************************************************************************/
/******************************************************************************/
-vAPI.clientScript = true;
-
vAPI.randomToken = function() {
- return String.fromCharCode(Date.now() % 26 + 97) +
- Math.floor(Math.random() * 982451653 + 982451653).toString(36);
+ const now = Date.now();
+ return String.fromCharCode(now % 26 + 97) +
+ Math.floor((1 + Math.random()) * now).toString(36);
};
vAPI.sessionId = vAPI.randomToken();
@@ -77,121 +79,11 @@ vAPI.messaging = {
port: null,
portTimer: null,
portTimerDelay: 10000,
- channels: new Map(),
- connections: new Map(),
- pending: new Map(),
+ extensions: [],
msgIdGenerator: 1,
+ pending: new Map(),
shuttingDown: false,
- Connection: 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 = this.messaging.Connection;
- if ( ctor.pagehide !== undefined ) { return; }
- ctor.pagehide = ( ) => {
- for ( const connection of this.messaging.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;
- }
- this.messaging.connections.delete(this.id);
- const port = this.messaging.getPort();
- if ( port === null ) { return; }
- port.postMessage({
- channelName: '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 ( this.messaging.connections.has(this.id) === false ) { return; }
- const port = this.messaging.getPort();
- if ( port === null ) { return; }
- port.postMessage({
- channelName: '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':
- this.messaging.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 ( this.messaging.connections.has(this.id) ) {
- this.checkAsync();
- } else {
- details.what = 'connectionBroken';
- port.postMessage({ channelName: 'vapi', msg: details });
- }
- break;
- case 'connectionRefused':
- this.messaging.connections.delete(this.id);
- this.handler(details);
- break;
- }
- }
- send(payload) {
- const port = this.messaging.getPort();
- if ( port === null ) { return; }
- port.postMessage({
- channelName: 'vapi',
- msg: this.toDetails('connectionMessage', payload)
- });
- }
- },
-
shutdown: function() {
this.shuttingDown = true;
this.destroyPort();
@@ -212,14 +104,6 @@ vAPI.messaging = {
messageListener: function(details) {
if ( details instanceof Object === false ) { return; }
- // Sent to all channels
- if ( details.broadcast ) {
- for ( const channelName of this.channels.keys() ) {
- this.sendToChannelListeners(channelName, details.msg);
- }
- return;
- }
-
// Response to specific message previously sent
if ( details.msgId !== undefined ) {
const resolver = this.pending.get(details.msgId);
@@ -230,53 +114,28 @@ vAPI.messaging = {
}
}
- if ( details.channelName !== 'vapi' ) { return; }
-
- // Internal handler
- let connection;
-
- switch ( details.msg.what ) {
- case 'connectionAccepted':
- case 'connectionBroken':
- case 'connectionCheck':
- case 'connectionMessage':
- case 'connectionRefused':
- connection = this.connections.get(details.msg.id);
- if ( connection === undefined ) { return; }
- connection.receive(details.msg);
- break;
- case 'connectionRequested':
- const listeners = this.channels.get(details.msg.to);
- if ( listeners === undefined ) { return; }
- const port = this.getPort();
- if ( port === null ) { return; }
- for ( const listener of listeners ) {
- if ( listener(details.msg) !== true ) { continue; }
- details.msg.what = 'connectionAccepted';
- details.msg.toToken = port.name;
- connection = new this.Connection(listener, details.msg);
- this.connections.set(connection.id, connection);
- break;
- }
- if ( details.msg.what !== 'connectionAccepted' ) {
- details.msg.what = 'connectionRefused';
- }
- port.postMessage(details);
- break;
- default:
- break;
- }
+ // Unhandled messages
+ this.extensions.every(ext => ext.canProcessMessage(details) !== true);
},
messageListenerCallback: null,
+ canDestroyPort: function() {
+ return this.pending.size === 0 &&
+ (
+ this.extensions.length === 0 ||
+ this.extensions.every(e => e.canDestroyPort())
+ );
+ },
+
+ mustDestroyPort: function() {
+ if ( this.extensions.length === 0 ) { return; }
+ this.extensions.forEach(e => e.mustDestroyPort());
+ this.extensions.length = 0;
+ },
+
portPoller: function() {
this.portTimer = null;
- if (
- this.port !== null &&
- this.channels.size === 0 &&
- this.connections.size === 0 &&
- this.pending.size === 0
- ) {
+ if ( this.port !== null && this.canDestroyPort() ) {
return this.destroyPort();
}
this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
@@ -296,13 +155,7 @@ vAPI.messaging = {
port.onDisconnect.removeListener(this.disconnectListenerBound);
this.port = null;
}
- this.channels.clear();
- if ( this.connections.size !== 0 ) {
- for ( const connection of this.connections.values() ) {
- connection.receive({ what: 'connectionBroken' });
- }
- this.connections.clear();
- }
+ this.mustDestroyPort();
// service pending callbacks
if ( this.pending.size !== 0 ) {
const pending = this.pending;
@@ -347,7 +200,7 @@ vAPI.messaging = {
return this.port !== null ? this.port : this.createPort();
},
- send: function(channelName, msg) {
+ send: function(channel, msg) {
// Too large a gap between the last request and the last response means
// the main process is no longer reachable: memory leaks and bad
// performance become a risk -- especially for long-lived, dynamic
@@ -363,86 +216,12 @@ vAPI.messaging = {
const promise = new Promise(resolve => {
this.pending.set(msgId, resolve);
});
- port.postMessage({ channelName, msgId, msg });
+ port.postMessage({ channel, msgId, msg });
return promise;
},
-
- connectTo: function(from, to, handler) {
- const port = this.getPort();
- if ( port === null ) { return; }
- const connection = new this.Connection(handler, {
- id: `${from}-${to}-${vAPI.sessionId}`,
- to: to,
- from: from,
- fromToken: port.name
- });
- this.connections.set(connection.id, connection);
- port.postMessage({
- channelName: 'vapi',
- msg: {
- what: 'connectionRequested',
- id: connection.id,
- from: from,
- fromToken: port.name,
- to: to
- }
- });
- return connection.id;
- },
-
- disconnectFrom: function(connectionId) {
- const connection = this.connections.get(connectionId);
- if ( connection === undefined ) { return; }
- connection.disconnect();
- },
-
- sendTo: function(connectionId, payload) {
- const connection = this.connections.get(connectionId);
- if ( connection === undefined ) { return; }
- connection.send(payload);
- },
-
- addChannelListener: function(channelName, listener) {
- const listeners = this.channels.get(channelName);
- if ( listeners === undefined ) {
- this.channels.set(channelName, [ listener ]);
- } else if ( listeners.indexOf(listener) === -1 ) {
- listeners.push(listener);
- }
- this.getPort();
- },
-
- removeChannelListener: function(channelName, listener) {
- const listeners = this.channels.get(channelName);
- if ( listeners === undefined ) { return; }
- const pos = listeners.indexOf(listener);
- if ( pos === -1 ) { return; }
- listeners.splice(pos, 1);
- if ( listeners.length === 0 ) {
- this.channels.delete(channelName);
- }
- },
-
- removeAllChannelListeners: function(channelName) {
- this.channels.delete(channelName);
- },
-
- sendToChannelListeners: function(channelName, msg) {
- let listeners = this.channels.get(channelName);
- if ( listeners === undefined ) { return; }
- listeners = listeners.slice(0);
- let response;
- for ( const listener of listeners ) {
- response = listener(msg);
- if ( response !== undefined ) { break; }
- }
- return response;
- }
};
-/******************************************************************************/
-
-vAPI.shutdown.add(function() {
+vAPI.shutdown.add(( ) => {
vAPI.messaging.shutdown();
window.vAPI = undefined;
});
diff --git a/src/3p-filters.html b/src/3p-filters.html
index b35db0f1d..cda8e41b0 100644
--- a/src/3p-filters.html
+++ b/src/3p-filters.html
@@ -59,6 +59,7 @@
+
diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js
index 10d262b53..31c80273c 100644
--- a/src/js/3p-filters.js
+++ b/src/js/3p-filters.js
@@ -38,7 +38,9 @@ let hideUnusedSet = new Set();
/******************************************************************************/
-const onMessage = function(msg) {
+const messaging = vAPI.messaging;
+
+vAPI.broadcastListener.add(msg => {
switch ( msg.what ) {
case 'assetUpdated':
updateAssetStatus(msg);
@@ -53,10 +55,7 @@ const onMessage = function(msg) {
default:
break;
}
-};
-
-const messaging = vAPI.messaging;
-messaging.addChannelListener('dashboard', onMessage);
+});
/******************************************************************************/
diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js
index 2a42e49c8..20f8f1f3d 100644
--- a/src/js/cosmetic-filtering.js
+++ b/src/js/cosmetic-filtering.js
@@ -817,8 +817,9 @@ FilterContainer.prototype.pruneSelectorCacheAsync = function() {
/******************************************************************************/
FilterContainer.prototype.randomAlphaToken = function() {
- return String.fromCharCode(Date.now() % 26 + 97) +
- Math.floor(Math.random() * 982451653 + 982451653).toString(36);
+ const now = Date.now();
+ return String.fromCharCode(now % 26 + 97) +
+ Math.floor((1 + Math.random()) * now).toString(36);
};
/******************************************************************************/
diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js
index d321496af..17fdb5cad 100644
--- a/src/js/logger-ui-inspector.js
+++ b/src/js/logger-ui-inspector.js
@@ -55,9 +55,10 @@ var filterToIdMap = new Map();
/******************************************************************************/
-var messaging = vAPI.messaging;
+const messaging = vAPI.messaging;
-messaging.addChannelListener('loggerUI', function(msg) {
+vAPI.MessagingConnection.addListener(function(msg) {
+ if ( msg.from !== 'domInspector' || msg.to !== 'loggerUI' ) { return; }
switch ( msg.what ) {
case 'connectionBroken':
if ( inspectorConnectionId === msg.id ) {
@@ -77,12 +78,8 @@ messaging.addChannelListener('loggerUI', function(msg) {
}
break;
case 'connectionRequested':
- if ( msg.from !== 'domInspector' ) { return false; }
- if (
- msg.tabId === undefined ||
- msg.tabId !== inspectedTabId
- ) {
- return false;
+ if ( msg.tabId === undefined || msg.tabId !== inspectedTabId ) {
+ return;
}
filterToIdMap.clear();
logger.removeAllChildren(domTree);
@@ -93,7 +90,7 @@ messaging.addChannelListener('loggerUI', function(msg) {
/******************************************************************************/
-var nodeFromDomEntry = function(entry) {
+const nodeFromDomEntry = function(entry) {
var node, value;
var li = document.createElement('li');
li.setAttribute('id', entry.nid);
@@ -130,25 +127,21 @@ var nodeFromDomEntry = function(entry) {
/******************************************************************************/
-var appendListItem = function(ul, li) {
+const appendListItem = function(ul, li) {
ul.appendChild(li);
// Ancestor nodes of a node which is affected by a cosmetic filter will
// be marked as "containing cosmetic filters", for user convenience.
- if ( li.classList.contains('isCosmeticHide') === false ) {
- return;
- }
+ if ( li.classList.contains('isCosmeticHide') === false ) { return; }
for (;;) {
li = li.parentElement.parentElement;
- if ( li === null ) {
- break;
- }
+ if ( li === null ) { break; }
li.classList.add('hasCosmeticHide');
}
};
/******************************************************************************/
-var renderDOMFull = function(response) {
+const renderDOMFull = function(response) {
var domTreeParent = domTree.parentElement;
var ul = domTreeParent.removeChild(domTree);
logger.removeAllChildren(domTree);
@@ -197,7 +190,7 @@ var renderDOMFull = function(response) {
/******************************************************************************/
-var patchIncremental = function(from, delta) {
+const patchIncremental = function(from, delta) {
var span, cnt;
var li = from.parentElement.parentElement;
var patchCosmeticHide = delta >= 0 &&
@@ -222,7 +215,7 @@ var patchIncremental = function(from, delta) {
/******************************************************************************/
-var renderDOMIncremental = function(response) {
+const renderDOMIncremental = function(response) {
// Process each journal entry:
// 1 = node added
// -1 = node removed
@@ -284,7 +277,7 @@ var renderDOMIncremental = function(response) {
/******************************************************************************/
-var countFromNode = function(li) {
+const countFromNode = function(li) {
var span = li.children[2];
var cnt = parseInt(span.getAttribute('data-cnt'), 10);
return isNaN(cnt) ? 0 : cnt;
@@ -292,7 +285,7 @@ var countFromNode = function(li) {
/******************************************************************************/
-var selectorFromNode = function(node) {
+const selectorFromNode = function(node) {
var selector = '';
var code;
while ( node !== null ) {
@@ -312,7 +305,7 @@ var selectorFromNode = function(node) {
/******************************************************************************/
-var selectorFromFilter = function(node) {
+const selectorFromFilter = function(node) {
while ( node !== null ) {
if ( node.localName === 'li' ) {
var code = node.querySelector('code:nth-of-type(2)');
@@ -327,7 +320,7 @@ var selectorFromFilter = function(node) {
/******************************************************************************/
-var nidFromNode = function(node) {
+const nidFromNode = function(node) {
var li = node;
while ( li !== null ) {
if ( li.localName === 'li' ) {
@@ -399,7 +392,7 @@ const startDialog = (function() {
};
const showCommitted = function() {
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'showCommitted',
hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n')
@@ -407,7 +400,7 @@ const startDialog = (function() {
};
const showInteractive = function() {
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'showInteractive',
hide: hideSelectors.join(',\n'),
unhide: unhideSelectors.join(',\n')
@@ -460,7 +453,7 @@ const startDialog = (function() {
/******************************************************************************/
-var onClicked = function(ev) {
+const onClicked = function(ev) {
ev.stopPropagation();
if ( inspectedTabId === 0 ) { return; }
@@ -489,7 +482,7 @@ var onClicked = function(ev) {
// Toggle cosmetic filter
if ( target.classList.contains('filter') ) {
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'toggleFilter',
original: false,
target: target.classList.toggle('off'),
@@ -504,7 +497,7 @@ var onClicked = function(ev) {
}
// Toggle node
else {
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'toggleNodes',
original: true,
target: target.classList.toggle('off') === false,
@@ -520,13 +513,13 @@ var onClicked = function(ev) {
/******************************************************************************/
-var onMouseOver = (function() {
+const onMouseOver = (function() {
var mouseoverTarget = null;
var mouseoverTimer = null;
var timerHandler = function() {
mouseoverTimer = null;
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'highlightOne',
selector: selectorFromNode(mouseoverTarget),
nid: nidFromNode(mouseoverTarget),
@@ -574,9 +567,9 @@ const injectInspector = function() {
/******************************************************************************/
-var shutdownInspector = function() {
+const shutdownInspector = function() {
if ( inspectorConnectionId !== undefined ) {
- messaging.disconnectFrom(inspectorConnectionId);
+ vAPI.MessagingConnection.disconnectFrom(inspectorConnectionId);
inspectorConnectionId = undefined;
}
logger.removeAllChildren(domTree);
@@ -586,7 +579,7 @@ var shutdownInspector = function() {
/******************************************************************************/
-var onTabIdChanged = function() {
+const onTabIdChanged = function() {
const tabId = currentTabId();
if ( tabId <= 0 ) {
return toggleOff();
@@ -599,7 +592,7 @@ var onTabIdChanged = function() {
/******************************************************************************/
-var toggleVCompactView = function() {
+const toggleVCompactView = function() {
var state = inspector.classList.toggle('vExpanded');
var branches = document.querySelectorAll('#domInspector li.branch');
for ( var branch of branches ) {
@@ -607,14 +600,14 @@ var toggleVCompactView = function() {
}
};
-var toggleHCompactView = function() {
+const toggleHCompactView = function() {
inspector.classList.toggle('hCompact');
};
/******************************************************************************/
/*
var toggleHighlightMode = function() {
- messaging.sendTo(inspectorConnectionId, {
+ vAPI.MessagingConnection.sendTo(inspectorConnectionId, {
what: 'highlightMode',
invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
});
@@ -622,9 +615,12 @@ var toggleHighlightMode = function() {
*/
/******************************************************************************/
-var revert = function() {
+const revert = function() {
uDom('#domTree .off').removeClass('off');
- messaging.sendTo(inspectorConnectionId, { what: 'resetToggledNodes' });
+ vAPI.MessagingConnection.sendTo(
+ inspectorConnectionId,
+ { what: 'resetToggledNodes' }
+ );
inspector.querySelector('.permatoolbar .revert').classList.add('disabled');
inspector.querySelector('.permatoolbar .commit').classList.add('disabled');
};
diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js
index ab3a3f721..9af938969 100644
--- a/src/js/scriptlets/cosmetic-logger.js
+++ b/src/js/scriptlets/cosmetic-logger.js
@@ -24,6 +24,7 @@
/******************************************************************************/
(( ) => {
+// >>>>>>>> start of private namespace
/******************************************************************************/
@@ -295,21 +296,28 @@ const handlers = {
/******************************************************************************/
-const onMessage = function(msg) {
- if ( msg.what === 'loggerDisabled' ) {
- processTimer.clear();
- attributeObserver.disconnect();
- vAPI.domFilterer.removeListener(handlers);
- vAPI.domWatcher.removeListener(handlers);
- vAPI.messaging.removeChannelListener('domLogger', onMessage);
+(async ( ) => {
+ // Dynamically add broadcast listening abilities.
+ if ( vAPI.broadcastListener instanceof Object === false ) {
+ await vAPI.messaging.send('vapi', { what: 'extendClient' });
}
-};
-vAPI.messaging.addChannelListener('domLogger', onMessage);
+ const broadcastListener = msg => {
+ if ( msg.what === 'loggerDisabled' ) {
+ processTimer.clear();
+ attributeObserver.disconnect();
+ vAPI.domFilterer.removeListener(handlers);
+ vAPI.domWatcher.removeListener(handlers);
+ vAPI.broadcastListener.remove(broadcastListener);
+ }
+ };
+ vAPI.broadcastListener.add(broadcastListener);
+})();
vAPI.domWatcher.addListener(handlers);
/******************************************************************************/
+// <<<<<<<< end of private namespace
})();
diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js
index 230757e0c..22805e97e 100644
--- a/src/js/scriptlets/dom-inspector.js
+++ b/src/js/scriptlets/dom-inspector.js
@@ -19,18 +19,16 @@
Home: https://github.com/gorhill/uBlock
*/
-/******************************************************************************/
-/******************************************************************************/
-
-(function() {
-
'use strict';
+/******************************************************************************/
/******************************************************************************/
-if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
- return;
-}
+(( ) => {
+
+/******************************************************************************/
+
+if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; }
/******************************************************************************/
@@ -49,7 +47,7 @@ if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) {
// Added serializeAsString parameter.
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
-var cssEscape = (function(/*root*/) {
+const cssEscape = (function(/*root*/) {
var InvalidCharacterError = function(message) {
this.message = message;
@@ -137,29 +135,29 @@ var cssEscape = (function(/*root*/) {
/******************************************************************************/
/******************************************************************************/
-var loggerConnectionId;
+let loggerConnectionId;
// Highlighter-related
-var svgRoot = null;
-var pickerRoot = null;
+let svgRoot = null;
+let pickerRoot = null;
-var nodeToIdMap = new WeakMap(); // No need to iterate
+let nodeToIdMap = new WeakMap(); // No need to iterate
-var blueNodes = [];
-var roRedNodes = new Map(); // node => current cosmetic filter
-var rwRedNodes = new Set(); // node => new cosmetic filter (toggle node)
+let blueNodes = [];
+const roRedNodes = new Map(); // node => current cosmetic filter
+const rwRedNodes = new Set(); // node => new cosmetic filter (toggle node)
//var roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle)
-var rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter)
+const rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter)
-var reHasCSSCombinators = /[ >+~]/;
+const reHasCSSCombinators = /[ >+~]/;
/******************************************************************************/
-var domLayout = (function() {
- var skipTagNames = new Set([
+const domLayout = (function() {
+ const skipTagNames = new Set([
'br', 'head', 'link', 'meta', 'script', 'style', 'title'
]);
- var resourceAttrNames = new Map([
+ const resourceAttrNames = new Map([
[ 'a', 'href' ],
[ 'iframe', 'src' ],
[ 'img', 'src' ],
@@ -170,13 +168,13 @@ var domLayout = (function() {
// This will be used to uniquely identify nodes across process.
- var newNodeId = function(node) {
+ const newNodeId = function(node) {
var nid = 'n' + (idGenerator++).toString(36);
nodeToIdMap.set(node, nid);
return nid;
};
- var selectorFromNode = function(node) {
+ const selectorFromNode = function(node) {
var str, attr, pos, sw, i;
var tag = node.localName;
var selector = cssEscape(tag);
@@ -217,7 +215,7 @@ var domLayout = (function() {
return selector;
};
- var DomRoot = function() {
+ const DomRoot = function() {
this.nid = newNodeId(document.body);
this.lvl = 0;
this.sel = 'body';
@@ -225,7 +223,7 @@ var domLayout = (function() {
this.filter = roRedNodes.get(document.body);
};
- var DomNode = function(node, level) {
+ const DomNode = function(node, level) {
this.nid = newNodeId(node);
this.lvl = level;
this.sel = selectorFromNode(node);
@@ -233,7 +231,7 @@ var domLayout = (function() {
this.filter = roRedNodes.get(node);
};
- var domNodeFactory = function(level, node) {
+ const domNodeFactory = function(level, node) {
var localName = node.localName;
if ( skipTagNames.has(localName) ) { return null; }
// skip uBlock's own nodes
@@ -246,7 +244,7 @@ var domLayout = (function() {
// Collect layout data.
- var getLayoutData = function() {
+ const getLayoutData = function() {
var layout = [];
var stack = [];
var node = document.documentElement;
@@ -282,7 +280,7 @@ var domLayout = (function() {
// Descendant count for each node.
- var patchLayoutData = function(layout) {
+ const patchLayoutData = function(layout) {
var stack = [], ptr;
var lvl = 0;
var domNode, cnt;
@@ -320,7 +318,7 @@ var domLayout = (function() {
var addedNodelists = [];
var removedNodelist = [];
- var previousElementSiblingId = function(node) {
+ const previousElementSiblingId = function(node) {
var sibling = node;
for (;;) {
sibling = sibling.previousElementSibling;
@@ -330,7 +328,7 @@ var domLayout = (function() {
}
};
- var journalFromBranch = function(root, newNodes, newNodeToIdMap) {
+ const journalFromBranch = function(root, newNodes, newNodeToIdMap) {
var domNode;
var node = root.firstElementChild;
while ( node !== null ) {
@@ -361,7 +359,7 @@ var domLayout = (function() {
}
};
- var journalFromMutations = function() {
+ const journalFromMutations = function() {
var nodelist, node, domNode, nid;
mutationTimer = undefined;
@@ -408,7 +406,7 @@ var domLayout = (function() {
if ( journalEntries.length === 0 ) { return; }
- vAPI.messaging.sendTo(loggerConnectionId, {
+ vAPI.MessagingConnection.sendTo(loggerConnectionId, {
what: 'domLayoutIncremental',
url: window.location.href,
hostname: window.location.hostname,
@@ -417,7 +415,7 @@ var domLayout = (function() {
});
};
- var onMutationObserved = function(mutationRecords) {
+ const onMutationObserved = function(mutationRecords) {
for ( var record of mutationRecords ) {
if ( record.addedNodes.length !== 0 ) {
addedNodelists.push(record.addedNodes);
@@ -433,7 +431,7 @@ var domLayout = (function() {
// API
- var getLayout = function() {
+ const getLayout = function() {
cosmeticFilterMapper.reset();
mutationObserver = new MutationObserver(onMutationObserved);
mutationObserver.observe(document.body, {
@@ -449,11 +447,11 @@ var domLayout = (function() {
};
};
- var reset = function() {
+ const reset = function() {
shutdown();
};
- var shutdown = function() {
+ const shutdown = function() {
if ( mutationTimer !== undefined ) {
clearTimeout(mutationTimer);
mutationTimer = undefined;
@@ -482,7 +480,7 @@ var domLayout = (function() {
// For browsers not supporting `:scope`, it's not the end of the world: the
// suggested CSS selectors may just end up being more verbose.
-var cssScope = ':scope > ';
+let cssScope = ':scope > ';
try {
document.querySelector(':scope *');
} catch (e) {
@@ -491,7 +489,7 @@ try {
/******************************************************************************/
-var cosmeticFilterMapper = (function() {
+const cosmeticFilterMapper = (function() {
// https://github.com/gorhill/uBlock/issues/546
var matchesFnName;
if ( typeof document.body.matches === 'function' ) {
@@ -502,7 +500,7 @@ var cosmeticFilterMapper = (function() {
matchesFnName = 'webkitMatchesSelector';
}
- var nodesFromStyleTag = function(rootNode) {
+ const nodesFromStyleTag = function(rootNode) {
var filterMap = roRedNodes,
entry, selector, canonical, nodes, node;
@@ -544,16 +542,16 @@ var cosmeticFilterMapper = (function() {
}
};
- var incremental = function(rootNode) {
+ const incremental = function(rootNode) {
nodesFromStyleTag(rootNode);
};
- var reset = function() {
- roRedNodes = new Map();
+ const reset = function() {
+ roRedNodes.clear();
incremental(document.documentElement);
};
- var shutdown = function() {
+ const shutdown = function() {
vAPI.domFilterer.toggle(true);
};
@@ -566,26 +564,23 @@ var cosmeticFilterMapper = (function() {
/******************************************************************************/
-var elementsFromSelector = function(selector, context) {
+const elementsFromSelector = function(selector, context) {
if ( !context ) {
context = document;
}
- var out;
if ( selector.indexOf(':') !== -1 ) {
- out = elementsFromSpecialSelector(selector);
- if ( out !== undefined ) {
- return out;
- }
+ const out = elementsFromSpecialSelector(selector);
+ if ( out !== undefined ) { return out; }
}
// plain CSS selector
try {
- out = context.querySelectorAll(selector);
+ return context.querySelectorAll(selector);
} catch (ex) {
}
- return out || [];
+ return [];
};
-var elementsFromSpecialSelector = function(selector) {
+const elementsFromSpecialSelector = function(selector) {
var out = [], i;
var matches = /^(.+?):has\((.+?)\)$/.exec(selector);
if ( matches !== null ) {
@@ -606,36 +601,35 @@ var elementsFromSpecialSelector = function(selector) {
}
matches = /^:xpath\((.+?)\)$/.exec(selector);
- if ( matches !== null ) {
- var xpr = document.evaluate(
- matches[1],
- document,
- null,
- XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
- null
- );
- i = xpr.snapshotLength;
- while ( i-- ) {
- out.push(xpr.snapshotItem(i));
- }
- return out;
+ if ( matches === null ) { return; }
+ const xpr = document.evaluate(
+ matches[1],
+ document,
+ null,
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
+ null
+ );
+ i = xpr.snapshotLength;
+ while ( i-- ) {
+ out.push(xpr.snapshotItem(i));
}
+ return out;
};
/******************************************************************************/
-var getSvgRootChildren = function() {
+const getSvgRootChildren = function() {
if ( svgRoot.children ) {
return svgRoot.children;
} else {
- var childNodes = Array.prototype.slice.apply(svgRoot.childNodes);
+ const childNodes = Array.prototype.slice.apply(svgRoot.childNodes);
return childNodes.filter(function(node) {
return node.nodeType === Node.ELEMENT_NODE;
});
}
};
-var highlightElements = function() {
+const highlightElements = function() {
var islands;
var elem, rect, poly;
var xl, xr, yt, yb, w, h, ws;
@@ -729,9 +723,9 @@ var highlightElements = function() {
/******************************************************************************/
-var onScrolled = (function() {
- var buffered = false;
- var timerHandler = function() {
+const onScrolled = (function() {
+ let buffered = false;
+ const timerHandler = function() {
buffered = false;
highlightElements();
};
@@ -745,10 +739,10 @@ var onScrolled = (function() {
/******************************************************************************/
-var selectNodes = function(selector, nid) {
- var nodes = elementsFromSelector(selector);
+const selectNodes = function(selector, nid) {
+ const nodes = elementsFromSelector(selector);
if ( nid === '' ) { return nodes; }
- for ( var node of nodes ) {
+ for ( const node of nodes ) {
if ( nodeToIdMap.get(node) === nid ) {
return [ node ];
}
@@ -758,9 +752,9 @@ var selectNodes = function(selector, nid) {
/******************************************************************************/
-var nodesFromFilter = function(selector) {
- var out = [];
- for ( var entry of roRedNodes ) {
+const nodesFromFilter = function(selector) {
+ const out = [];
+ for ( const entry of roRedNodes ) {
if ( entry[1] === selector ) {
out.push(entry[0]);
}
@@ -770,8 +764,8 @@ var nodesFromFilter = function(selector) {
/******************************************************************************/
-var toggleExceptions = function(nodes, targetState) {
- for ( var node of nodes ) {
+const toggleExceptions = function(nodes, targetState) {
+ for ( const node of nodes ) {
if ( targetState ) {
rwGreenNodes.add(node);
} else {
@@ -780,8 +774,8 @@ var toggleExceptions = function(nodes, targetState) {
}
};
-var toggleFilter = function(nodes, targetState) {
- for ( var node of nodes ) {
+const toggleFilter = function(nodes, targetState) {
+ for ( const node of nodes ) {
if ( targetState ) {
rwRedNodes.delete(node);
} else {
@@ -790,21 +784,19 @@ var toggleFilter = function(nodes, targetState) {
}
};
-var resetToggledNodes = function() {
+const resetToggledNodes = function() {
rwGreenNodes.clear();
rwRedNodes.clear();
};
-// https://www.youtube.com/watch?v=L5jRewnxSBY
-
/******************************************************************************/
-var start = function() {
- var onReady = function(ev) {
+const start = function() {
+ const onReady = function(ev) {
if ( ev ) {
document.removeEventListener(ev.type, onReady);
}
- vAPI.messaging.sendTo(loggerConnectionId, domLayout.get());
+ vAPI.MessagingConnection.sendTo(loggerConnectionId, domLayout.get());
vAPI.domFilterer.toggle(false, highlightElements);
};
if ( document.readyState === 'loading' ) {
@@ -816,10 +808,10 @@ var start = function() {
/******************************************************************************/
-var shutdown = function() {
+const shutdown = function() {
cosmeticFilterMapper.shutdown();
domLayout.shutdown();
- vAPI.messaging.disconnectFrom(loggerConnectionId);
+ vAPI.MessagingConnection.disconnectFrom(loggerConnectionId);
window.removeEventListener('scroll', onScrolled, true);
document.documentElement.removeChild(pickerRoot);
pickerRoot = svgRoot = null;
@@ -828,7 +820,7 @@ var shutdown = function() {
/******************************************************************************/
/******************************************************************************/
-var onMessage = function(request) {
+const onMessage = function(request) {
var response,
nodes;
@@ -888,32 +880,17 @@ var onMessage = function(request) {
return response;
};
-var messagingHandler = function(msg) {
- switch ( msg.what ) {
- case 'connectionAccepted':
- loggerConnectionId = msg.id;
- start();
- break;
- case 'connectionBroken':
- shutdown();
- break;
- case 'connectionMessage':
- onMessage(msg.payload);
- break;
- }
-};
-
/******************************************************************************/
// Install DOM inspector widget
-var bootstrap = function(ev) {
+const bootstrap = async function(ev) {
if ( ev ) {
pickerRoot.removeEventListener(ev.type, bootstrap);
}
- var pickerDoc = this.contentDocument;
+ const pickerDoc = ev.target.contentDocument;
- var style = pickerDoc.createElement('style');
+ const style = pickerDoc.createElement('style');
style.textContent = [
'body {',
'background-color: transparent;',
@@ -955,7 +932,25 @@ var bootstrap = function(ev) {
window.addEventListener('scroll', onScrolled, true);
- vAPI.messaging.connectTo('domInspector', 'loggerUI', messagingHandler);
+ // Dynamically add direct connection abilities so that we can establish
+ // a direct, fast messaging connection to the logger.
+ if ( vAPI.MessagingConnection instanceof Function === false ) {
+ await vAPI.messaging.send('vapi', { what: 'extendClient' });
+ }
+ vAPI.MessagingConnection.connectTo('domInspector', 'loggerUI', msg => {
+ switch ( msg.what ) {
+ case 'connectionAccepted':
+ loggerConnectionId = msg.id;
+ start();
+ break;
+ case 'connectionBroken':
+ shutdown();
+ break;
+ case 'connectionMessage':
+ onMessage(msg.payload);
+ break;
+ }
+ });
};
pickerRoot = document.createElement('iframe');
@@ -982,7 +977,7 @@ pickerRoot.style.cssText = [
''
].join(' !important;\n');
-pickerRoot.addEventListener('load', bootstrap);
+pickerRoot.addEventListener('load', ev => { bootstrap(ev); });
document.documentElement.appendChild(pickerRoot);
/******************************************************************************/
diff --git a/src/logger-ui.html b/src/logger-ui.html
index f6b153e13..2202579f6 100644
--- a/src/logger-ui.html
+++ b/src/logger-ui.html
@@ -205,6 +205,7 @@
+