mirror of
https://github.com/gorhill/uBlock.git
synced 2024-10-06 09:37:12 +02:00
some refactoring: move to-aux-process-messaging timeout into vAPI
This commit is contained in:
parent
957dea5289
commit
aeba71790f
@ -519,97 +519,159 @@ vAPI.messaging = {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This allows to avoid creating a closure for every single message which
|
|
||||||
// expects an answer. Having a closure created each time a message is processed
|
|
||||||
// has been always bothering me. Another benefit of the implementation here
|
|
||||||
// is to reuse the callback proxy object, so less memory churning.
|
|
||||||
//
|
|
||||||
// https://developers.google.com/speed/articles/optimizing-javascript
|
|
||||||
// "Creating a closure is significantly slower then creating an inner
|
|
||||||
// function without a closure, and much slower than reusing a static
|
|
||||||
// function"
|
|
||||||
//
|
|
||||||
// http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html
|
|
||||||
// "the dreaded 'uniformly slow code' case where every function takes 1%
|
|
||||||
// of CPU and you have to make one hundred separate performance optimizations
|
|
||||||
// to improve performance at all"
|
|
||||||
//
|
|
||||||
// http://jsperf.com/closure-no-closure/2
|
|
||||||
|
|
||||||
var CallbackWrapper = function(port, request) {
|
|
||||||
// No need to bind every single time
|
|
||||||
this.callback = this.proxy.bind(this);
|
|
||||||
this.messaging = vAPI.messaging;
|
|
||||||
this.init(port, request);
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.junkyard = [];
|
|
||||||
|
|
||||||
CallbackWrapper.factory = function(port, request) {
|
|
||||||
var wrapper = CallbackWrapper.junkyard.pop();
|
|
||||||
if ( wrapper ) {
|
|
||||||
wrapper.init(port, request);
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
return new CallbackWrapper(port, request);
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.prototype.init = function(port, request) {
|
|
||||||
this.port = port;
|
|
||||||
this.request = request;
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.prototype.proxy = function(response) {
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/383
|
|
||||||
if ( this.messaging.ports.hasOwnProperty(this.port.name) ) {
|
|
||||||
this.port.postMessage({
|
|
||||||
requestId: this.request.requestId,
|
|
||||||
channelName: this.request.channelName,
|
|
||||||
msg: response !== undefined ? response : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Mark for reuse
|
|
||||||
this.port = this.request = null;
|
|
||||||
CallbackWrapper.junkyard.push(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.messaging.listen = function(listenerName, callback) {
|
vAPI.messaging.listen = function(listenerName, callback) {
|
||||||
this.listeners[listenerName] = callback;
|
this.listeners[listenerName] = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.messaging.onPortMessage = function(request, port) {
|
vAPI.messaging.onPortMessage = (function() {
|
||||||
var callback = vAPI.messaging.NOOPFUNC;
|
var messaging = vAPI.messaging;
|
||||||
if ( request.requestId !== undefined ) {
|
var toAuxPending = {};
|
||||||
callback = CallbackWrapper.factory(port, request).callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specific handler
|
// Use a wrapper to avoid closure and to allow reuse.
|
||||||
var r = vAPI.messaging.UNHANDLED;
|
var CallbackWrapper = function(port, request, timeout) {
|
||||||
var listener = vAPI.messaging.listeners[request.channelName];
|
this.callback = this.proxy.bind(this); // bind once
|
||||||
if ( typeof listener === 'function' ) {
|
this.init(port, request, timeout);
|
||||||
r = listener(request.msg, port.sender, callback);
|
};
|
||||||
}
|
|
||||||
if ( r !== vAPI.messaging.UNHANDLED ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default handler
|
CallbackWrapper.prototype.init = function(port, request, timeout) {
|
||||||
r = vAPI.messaging.defaultHandler(request.msg, port.sender, callback);
|
this.port = port;
|
||||||
if ( r !== vAPI.messaging.UNHANDLED ) {
|
this.request = request;
|
||||||
return;
|
this.timerId = timeout !== undefined ?
|
||||||
}
|
vAPI.setTimeout(this.callback, timeout) :
|
||||||
|
null;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
console.error('uBlock> messaging > unknown request: %o', request);
|
CallbackWrapper.prototype.proxy = function(response) {
|
||||||
|
if ( this.timerId !== null ) {
|
||||||
|
clearTimeout(this.timerId);
|
||||||
|
delete toAuxPending[this.timerId];
|
||||||
|
this.timerId = null;
|
||||||
|
}
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/383
|
||||||
|
if ( messaging.ports.hasOwnProperty(this.port.name) ) {
|
||||||
|
this.port.postMessage({
|
||||||
|
auxProcessId: this.request.auxProcessId,
|
||||||
|
channelName: this.request.channelName,
|
||||||
|
msg: response !== undefined ? response : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Mark for reuse
|
||||||
|
this.port = this.request = null;
|
||||||
|
callbackWrapperJunkyard.push(this);
|
||||||
|
};
|
||||||
|
|
||||||
// Unhandled:
|
var callbackWrapperJunkyard = [];
|
||||||
// Need to callback anyways in case caller expected an answer, or
|
|
||||||
// else there is a memory leak on caller's side
|
var callbackWrapperFactory = function(port, request, timeout) {
|
||||||
callback();
|
var wrapper = callbackWrapperJunkyard.pop();
|
||||||
};
|
if ( wrapper ) {
|
||||||
|
return wrapper.init(port, request, timeout);
|
||||||
|
}
|
||||||
|
return new CallbackWrapper(port, request, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
var toAux = function(details, portFrom) {
|
||||||
|
var portTo;
|
||||||
|
var chromiumTabId = toChromiumTabId(details.toTabId);
|
||||||
|
|
||||||
|
// TODO: This could be an issue with a lot of tabs: easy to address
|
||||||
|
// with a port name to tab id map.
|
||||||
|
for ( var portName in messaging.ports ) {
|
||||||
|
if ( messaging.ports.hasOwnProperty(portName) === false ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( messaging.ports[portName].sender.tab.id === chromiumTabId ) {
|
||||||
|
portTo = messaging.ports[portName];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrapper;
|
||||||
|
if ( details.auxProcessId !== undefined ) {
|
||||||
|
wrapper = callbackWrapperFactory(portFrom, details, 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination not found:
|
||||||
|
if ( portTo === undefined ) {
|
||||||
|
if ( wrapper !== undefined ) {
|
||||||
|
wrapper.callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per HTML5, timer id is always an integer, thus suitable to be
|
||||||
|
// used as a key, and which value is safe to use across process
|
||||||
|
// boundaries.
|
||||||
|
if ( wrapper !== undefined ) {
|
||||||
|
toAuxPending[wrapper.timerId] = wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
portTo.postMessage({
|
||||||
|
mainProcessId: wrapper && wrapper.timerId,
|
||||||
|
channelName: details.toChannel,
|
||||||
|
msg: details.msg
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var toAuxResponse = function(details) {
|
||||||
|
var mainProcessId = details.mainProcessId;
|
||||||
|
if ( mainProcessId === undefined ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( toAuxPending.hasOwnProperty(mainProcessId) === false ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var wrapper = toAuxPending[mainProcessId];
|
||||||
|
delete toAuxPending[mainProcessId];
|
||||||
|
wrapper.callback(details.msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
return function(request, port) {
|
||||||
|
// Auxiliary process to auxiliary process
|
||||||
|
if ( request.toTabId !== undefined ) {
|
||||||
|
toAux(request, port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to auxiliary process: response
|
||||||
|
if ( request.mainProcessId !== undefined ) {
|
||||||
|
toAuxResponse(request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: prepare response
|
||||||
|
var callback = messaging.NOOPFUNC;
|
||||||
|
if ( request.auxProcessId !== undefined ) {
|
||||||
|
callback = callbackWrapperFactory(port, request).callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: specific handler
|
||||||
|
var r = messaging.UNHANDLED;
|
||||||
|
var listener = messaging.listeners[request.channelName];
|
||||||
|
if ( typeof listener === 'function' ) {
|
||||||
|
r = listener(request.msg, port.sender, callback);
|
||||||
|
}
|
||||||
|
if ( r !== messaging.UNHANDLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: default handler
|
||||||
|
r = messaging.defaultHandler(request.msg, port.sender, callback);
|
||||||
|
if ( r !== messaging.UNHANDLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: no handler
|
||||||
|
console.error('uBlock> messaging > unknown request: %o', request);
|
||||||
|
|
||||||
|
// Need to callback anyways in case caller expected an answer, or
|
||||||
|
// else there is a memory leak on caller's side
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -661,46 +723,6 @@ vAPI.messaging.broadcast = function(message) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// "Auxiliary process": any process other than main process.
|
|
||||||
//
|
|
||||||
// Main process to auxiliary processes messaging. The approach is that of
|
|
||||||
// smoke-signal messaging, so emitters have to be ready to deal with no
|
|
||||||
// response at all (i.e. use timeout where needed).
|
|
||||||
//
|
|
||||||
// Mandatory:
|
|
||||||
// - receiverTabId: Which tab to send the message.
|
|
||||||
// No target tab id means sends to all tabs.
|
|
||||||
// - receiverChannel: Which channel to send the message.
|
|
||||||
//
|
|
||||||
// Optional:
|
|
||||||
// - senderTabId: From which tab the message originates.
|
|
||||||
// - senderChannel: From which channel the message originates.
|
|
||||||
// These optional fields are useful for the target, and may be used
|
|
||||||
// to send back a response to the sender.
|
|
||||||
|
|
||||||
vAPI.messaging.post = function(message) {
|
|
||||||
var port;
|
|
||||||
var chromiumTabId = toChromiumTabId(message.receiverTabId);
|
|
||||||
for ( var portName in this.ports ) {
|
|
||||||
if ( this.ports.hasOwnProperty(portName) === false ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
port = this.ports[portName];
|
|
||||||
if ( chromiumTabId !== 0 && port.sender.tab.id !== chromiumTabId ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
port.postMessage({
|
|
||||||
channelName: message.receiverChannel,
|
|
||||||
msg: message
|
|
||||||
});
|
|
||||||
if ( chromiumTabId !== 0 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.net = {};
|
vAPI.net = {};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -73,157 +73,11 @@ vAPI.shutdown = (function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var MessagingListeners = function(callback) {
|
|
||||||
this.listeners = [];
|
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
this.listeners.push(callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.add = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( this.listeners.indexOf(callback) !== -1 ) {
|
|
||||||
throw new Error('Duplicate listener.');
|
|
||||||
}
|
|
||||||
this.listeners.push(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.remove = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( this.listeners.indexOf(callback) === -1 ) {
|
|
||||||
throw new Error('Listener not found.');
|
|
||||||
}
|
|
||||||
this.listeners.splice(this.listeners.indexOf(callback), 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.removeAll = function() {
|
|
||||||
this.listeners = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.process = function(msg) {
|
|
||||||
var listeners = this.listeners;
|
|
||||||
var n = listeners.length;
|
|
||||||
for ( var i = 0; i < n; i++ ) {
|
|
||||||
listeners[i](msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var messagingConnector = function(response) {
|
|
||||||
if ( !response ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
var channels = messaging.channels;
|
|
||||||
var channel;
|
|
||||||
|
|
||||||
// Sent to all channels
|
|
||||||
if ( response.broadcast === true && !response.channelName ) {
|
|
||||||
for ( channel in channels ) {
|
|
||||||
if ( channels[channel] instanceof MessagingChannel === false ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
channels[channel].listeners.process(response.msg);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response to specific message previously sent
|
|
||||||
if ( response.requestId ) {
|
|
||||||
var listener = messaging.pending[response.requestId];
|
|
||||||
delete messaging.pending[response.requestId];
|
|
||||||
delete response.requestId; // TODO: why?
|
|
||||||
if ( listener ) {
|
|
||||||
listener(response.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sent to a specific channel
|
|
||||||
channel = channels[response.channelName];
|
|
||||||
if ( channel instanceof MessagingChannel ) {
|
|
||||||
channel.listeners.process(response.msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var MessagingChannel = function(name, callback) {
|
|
||||||
this.channelName = name;
|
|
||||||
this.listeners = new MessagingListeners(callback);
|
|
||||||
this.refCount = 1;
|
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( messaging.port === null ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.send = function(message, callback) {
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( messaging.port === null ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
var requestId;
|
|
||||||
if ( callback ) {
|
|
||||||
requestId = messaging.requestId++;
|
|
||||||
messaging.pending[requestId] = callback;
|
|
||||||
}
|
|
||||||
messaging.port.postMessage({
|
|
||||||
channelName: this.channelName,
|
|
||||||
requestId: requestId,
|
|
||||||
msg: message
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.close = function() {
|
|
||||||
this.refCount -= 1;
|
|
||||||
if ( this.refCount !== 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
delete messaging.channels[this.channelName];
|
|
||||||
if ( Object.keys(messaging.channels).length === 0 ) {
|
|
||||||
messaging.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.addListener = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners.add(callback);
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( messaging.port === null ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.removeListener = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners.remove(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.removeAllListeners = function() {
|
|
||||||
this.listeners.removeAll();
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.messaging = {
|
vAPI.messaging = {
|
||||||
port: null,
|
port: null,
|
||||||
channels: {},
|
channels: {},
|
||||||
pending: {},
|
pending: {},
|
||||||
requestId: 1,
|
auxProcessId: 1,
|
||||||
|
|
||||||
setup: function() {
|
setup: function() {
|
||||||
this.port = chrome.runtime.connect({name: vAPI.sessionId});
|
this.port = chrome.runtime.connect({name: vAPI.sessionId});
|
||||||
@ -258,6 +112,148 @@ vAPI.messaging = {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var messagingConnector = function(details) {
|
||||||
|
if ( !details ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
var channels = messaging.channels;
|
||||||
|
var channel;
|
||||||
|
|
||||||
|
// Sent to all channels
|
||||||
|
if ( details.broadcast === true && !details.channelName ) {
|
||||||
|
for ( channel in channels ) {
|
||||||
|
if ( channels[channel] instanceof MessagingChannel === false ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
channels[channel].sendToListeners(details.msg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response to specific message previously sent
|
||||||
|
if ( details.auxProcessId ) {
|
||||||
|
var listener = messaging.pending[details.auxProcessId];
|
||||||
|
delete messaging.pending[details.auxProcessId];
|
||||||
|
delete details.auxProcessId; // TODO: why?
|
||||||
|
if ( listener ) {
|
||||||
|
listener(details.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent to a specific channel
|
||||||
|
var response;
|
||||||
|
channel = channels[details.channelName];
|
||||||
|
if ( channel instanceof MessagingChannel ) {
|
||||||
|
response = channel.sendToListeners(details.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond back if required
|
||||||
|
if ( details.mainProcessId !== undefined ) {
|
||||||
|
messaging.port.postMessage({
|
||||||
|
mainProcessId: details.mainProcessId,
|
||||||
|
msg: response
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var MessagingChannel = function(name, callback) {
|
||||||
|
this.channelName = name;
|
||||||
|
this.listeners = typeof callback === 'function' ? [callback] : [];
|
||||||
|
this.refCount = 1;
|
||||||
|
if ( typeof callback === 'function' ) {
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( messaging.port === null ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.send = function(message, callback) {
|
||||||
|
this.sendTo(message, undefined, undefined, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.sendTo = function(message, toTabId, toChannel, callback) {
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( messaging.port === null ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
var auxProcessId;
|
||||||
|
if ( callback ) {
|
||||||
|
auxProcessId = messaging.auxProcessId++;
|
||||||
|
messaging.pending[auxProcessId] = callback;
|
||||||
|
}
|
||||||
|
messaging.port.postMessage({
|
||||||
|
channelName: this.channelName,
|
||||||
|
auxProcessId: auxProcessId,
|
||||||
|
toTabId: toTabId,
|
||||||
|
toChannel: toChannel,
|
||||||
|
msg: message
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.close = function() {
|
||||||
|
this.refCount -= 1;
|
||||||
|
if ( this.refCount !== 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
delete messaging.channels[this.channelName];
|
||||||
|
if ( Object.keys(messaging.channels).length === 0 ) {
|
||||||
|
messaging.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.addListener = function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( this.listeners.indexOf(callback) !== -1 ) {
|
||||||
|
throw new Error('Duplicate listener.');
|
||||||
|
}
|
||||||
|
this.listeners.push(callback);
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( messaging.port === null ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.removeListener = function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pos = this.listeners.indexOf(callback);
|
||||||
|
if ( pos === -1 ) {
|
||||||
|
throw new Error('Listener not found.');
|
||||||
|
}
|
||||||
|
this.listeners.splice(pos, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.removeAllListeners = function() {
|
||||||
|
this.listeners = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.sendToListeners = function(msg) {
|
||||||
|
var response;
|
||||||
|
var listeners = this.listeners;
|
||||||
|
for ( var i = 0, n = listeners.length; i < n; i++ ) {
|
||||||
|
response = listeners[i](msg);
|
||||||
|
if ( response !== undefined ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=rT5zCHn0tsg
|
||||||
|
// https://www.youtube.com/watch?v=E-jS4e3zacI
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// No need to have vAPI client linger around after shutdown if
|
// No need to have vAPI client linger around after shutdown if
|
||||||
// we are not a top window (because element picker can still
|
// we are not a top window (because element picker can still
|
||||||
// be injected in top window).
|
// be injected in top window).
|
||||||
|
@ -1149,60 +1149,202 @@ vAPI.messaging.listen = function(listenerName, callback) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.messaging.onMessage = function({target, data}) {
|
vAPI.messaging.onMessage = (function() {
|
||||||
var messageManager = target.messageManager;
|
var messaging = vAPI.messaging;
|
||||||
|
var toAuxPending = {};
|
||||||
|
|
||||||
|
// Use a wrapper to avoid closure and to allow reuse.
|
||||||
|
var CallbackWrapper = function(messageManager, channelName, listenerId, auxProcessId, timeout) {
|
||||||
|
this.callback = this.proxy.bind(this); // bind once
|
||||||
|
this.init(messageManager, listenerId, channelName, auxProcessId, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
CallbackWrapper.prototype.init = function(messageManager, listenerId, channelName, auxProcessId, timeout) {
|
||||||
|
this.messageManager = messageManager;
|
||||||
|
this.listenerId = listenerId;
|
||||||
|
this.channelName = channelName;
|
||||||
|
this.auxProcessId = auxProcessId;
|
||||||
|
this.timerId = timeout !== undefined ?
|
||||||
|
vAPI.setTimeout(this.callback, timeout) :
|
||||||
|
null;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
CallbackWrapper.prototype.proxy = function(response) {
|
||||||
|
if ( this.timerId !== null ) {
|
||||||
|
clearTimeout(this.timerId);
|
||||||
|
delete toAuxPending[this.timerId];
|
||||||
|
this.timerId = null;
|
||||||
|
}
|
||||||
|
var message = JSON.stringify({
|
||||||
|
auxProcessId: this.auxProcessId,
|
||||||
|
channelName: this.channelName,
|
||||||
|
msg: response !== undefined ? response : null
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( this.messageManager.sendAsyncMessage ) {
|
||||||
|
this.messageManager.sendAsyncMessage(this.listenerId, message);
|
||||||
|
} else {
|
||||||
|
this.messageManager.broadcastAsyncMessage(this.listenerId, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark for reuse
|
||||||
|
this.messageManager =
|
||||||
|
this.listenerId =
|
||||||
|
this.channelName =
|
||||||
|
this.auxProcessId = null;
|
||||||
|
callbackWrapperJunkyard.push(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
var callbackWrapperJunkyard = [];
|
||||||
|
|
||||||
|
var callbackWrapperFactory = function(messageManager, listenerId, channelName, auxProcessId, timeout) {
|
||||||
|
var wrapper = callbackWrapperJunkyard.pop();
|
||||||
|
if ( wrapper ) {
|
||||||
|
return wrapper.init(messageManager, listenerId, channelName, auxProcessId, timeout);
|
||||||
|
}
|
||||||
|
return new CallbackWrapper(messageManager, listenerId, channelName, auxProcessId, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
// "Auxiliary process": any process other than main process.
|
||||||
|
var toAux = function(target, details) {
|
||||||
|
var messageManagerFrom = target.messageManager;
|
||||||
|
|
||||||
if ( !messageManager ) {
|
|
||||||
// Message came from a popup, and its message manager is not usable.
|
// Message came from a popup, and its message manager is not usable.
|
||||||
// So instead we broadcast to the parent window.
|
// So instead we broadcast to the parent window.
|
||||||
messageManager = getOwnerWindow(
|
if ( !messageManagerFrom ) {
|
||||||
target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
|
messageManagerFrom = getOwnerWindow(
|
||||||
).messageManager;
|
target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
|
||||||
}
|
).messageManager;
|
||||||
|
}
|
||||||
|
|
||||||
var channelNameRaw = data.channelName;
|
var wrapper;
|
||||||
var pos = channelNameRaw.indexOf('|');
|
if ( details.auxProcessId !== undefined ) {
|
||||||
var channelName = channelNameRaw.slice(pos + 1);
|
var channelNameRaw = details.channelName;
|
||||||
|
var pos = channelNameRaw.indexOf('|');
|
||||||
|
wrapper = callbackWrapperFactory(
|
||||||
|
messageManagerFrom,
|
||||||
|
channelNameRaw.slice(0, pos),
|
||||||
|
channelNameRaw.slice(pos + 1),
|
||||||
|
details.auxProcessId,
|
||||||
|
1023
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var callback = vAPI.messaging.NOOPFUNC;
|
var messageManagerTo = null;
|
||||||
if ( data.requestId !== undefined ) {
|
var browser = tabWatcher.browserFromTabId(details.toTabId);
|
||||||
callback = CallbackWrapper.factory(
|
if ( browser !== null && browser.messageManager ) {
|
||||||
messageManager,
|
messageManagerTo = browser.messageManager;
|
||||||
channelName,
|
}
|
||||||
channelNameRaw.slice(0, pos),
|
if ( messageManagerTo === null ) {
|
||||||
data.requestId
|
if ( wrapper !== undefined ) {
|
||||||
).callback;
|
wrapper.callback();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var sender = {
|
// As per HTML5, timer id is always an integer, thus suitable to be used
|
||||||
tab: {
|
// as a key, and which value is safe to use across process boundaries.
|
||||||
id: tabWatcher.tabIdFromTarget(target)
|
if ( wrapper !== undefined ) {
|
||||||
|
toAuxPending[wrapper.timerId] = wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetId = location.host + ':broadcast';
|
||||||
|
var payload = JSON.stringify({
|
||||||
|
mainProcessId: wrapper && wrapper.timerId,
|
||||||
|
channelName: details.toChannel,
|
||||||
|
msg: details.msg
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( messageManagerTo.sendAsyncMessage ) {
|
||||||
|
messageManagerTo.sendAsyncMessage(targetId, payload);
|
||||||
|
} else {
|
||||||
|
messageManagerTo.broadcastAsyncMessage(targetId, payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Specific handler
|
var toAuxResponse = function(details) {
|
||||||
var r = vAPI.messaging.UNHANDLED;
|
var mainProcessId = details.mainProcessId;
|
||||||
var listener = vAPI.messaging.listeners[channelName];
|
if ( mainProcessId === undefined ) {
|
||||||
if ( typeof listener === 'function' ) {
|
return;
|
||||||
r = listener(data.msg, sender, callback);
|
}
|
||||||
}
|
if ( toAuxPending.hasOwnProperty(mainProcessId) === false ) {
|
||||||
if ( r !== vAPI.messaging.UNHANDLED ) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
var wrapper = toAuxPending[mainProcessId];
|
||||||
|
delete toAuxPending[mainProcessId];
|
||||||
|
wrapper.callback(details.msg);
|
||||||
|
};
|
||||||
|
|
||||||
// Default handler
|
return function({target, data}) {
|
||||||
r = vAPI.messaging.defaultHandler(data.msg, sender, callback);
|
// Auxiliary process to auxiliary process
|
||||||
if ( r !== vAPI.messaging.UNHANDLED ) {
|
if ( data.toTabId !== undefined ) {
|
||||||
return;
|
toAux(target, data);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.error('uBlock> messaging > unknown request: %o', data);
|
// Auxiliary process to auxiliary process: response
|
||||||
|
if ( data.mainProcessId !== undefined ) {
|
||||||
|
toAuxResponse(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Unhandled:
|
// Auxiliary process to main process
|
||||||
// Need to callback anyways in case caller expected an answer, or
|
var messageManager = target.messageManager;
|
||||||
// else there is a memory leak on caller's side
|
|
||||||
callback();
|
// Message came from a popup, and its message manager is not usable.
|
||||||
};
|
// So instead we broadcast to the parent window.
|
||||||
|
if ( !messageManager ) {
|
||||||
|
messageManager = getOwnerWindow(
|
||||||
|
target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
|
||||||
|
).messageManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelNameRaw = data.channelName;
|
||||||
|
var pos = channelNameRaw.indexOf('|');
|
||||||
|
var channelName = channelNameRaw.slice(pos + 1);
|
||||||
|
|
||||||
|
// Auxiliary process to main process: prepare response
|
||||||
|
var callback = messaging.NOOPFUNC;
|
||||||
|
if ( data.auxProcessId !== undefined ) {
|
||||||
|
callback = callbackWrapperFactory(
|
||||||
|
messageManager,
|
||||||
|
channelNameRaw.slice(0, pos),
|
||||||
|
channelName,
|
||||||
|
data.auxProcessId
|
||||||
|
).callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sender = {
|
||||||
|
tab: {
|
||||||
|
id: tabWatcher.tabIdFromTarget(target)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auxiliary process to main process: specific handler
|
||||||
|
var r = messaging.UNHANDLED;
|
||||||
|
var listener = messaging.listeners[channelName];
|
||||||
|
if ( typeof listener === 'function' ) {
|
||||||
|
r = listener(data.msg, sender, callback);
|
||||||
|
}
|
||||||
|
if ( r !== messaging.UNHANDLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: default handler
|
||||||
|
r = messaging.defaultHandler(data.msg, sender, callback);
|
||||||
|
if ( r !== messaging.UNHANDLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary process to main process: no handler
|
||||||
|
console.error('uBlock> messaging > unknown request: %o', data);
|
||||||
|
|
||||||
|
// Need to callback anyways in case caller expected an answer, or
|
||||||
|
// else there is a memory leak on caller's side
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -1239,55 +1381,6 @@ vAPI.messaging.setup = function(defaultHandler) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// "Auxiliary process": any process other than main process.
|
|
||||||
//
|
|
||||||
// Main process to auxiliary processes messaging. The approach is that of
|
|
||||||
// smoke-signal messaging, so emitters have to be ready to deal with no
|
|
||||||
// response at all (i.e. use timeout where needed).
|
|
||||||
//
|
|
||||||
// Mandatory:
|
|
||||||
// - receiverTabId: Which tab to send the message.
|
|
||||||
// No target tab id means sends to all tabs.
|
|
||||||
// - receiverChannel: Which channel to send the message.
|
|
||||||
//
|
|
||||||
// Optional:
|
|
||||||
// - senderTabId: From which tab the message originates.
|
|
||||||
// - senderChannel: From which channel the message originates.
|
|
||||||
// These optional fields are useful for the target, and may be used
|
|
||||||
// to send back a response to the sender.
|
|
||||||
|
|
||||||
vAPI.messaging.post = function(message) {
|
|
||||||
var ffTabId = message.receiverTabId || '';
|
|
||||||
var targetId = location.host + ':broadcast';
|
|
||||||
var payload = JSON.stringify({
|
|
||||||
channelName: message.receiverChannel,
|
|
||||||
msg: message
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( ffTabId === '' ) {
|
|
||||||
this.globalMessageManager.broadcastAsyncMessage(targetId, payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var browser = tabWatcher.browserFromTabId(ffTabId);
|
|
||||||
if ( browser === null ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageManager = browser.messageManager || null;
|
|
||||||
if ( messageManager === null ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( messageManager.sendAsyncMessage ) {
|
|
||||||
messageManager.sendAsyncMessage(targetId, payload);
|
|
||||||
} else {
|
|
||||||
messageManager.broadcastAsyncMessage(targetId, payload);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.messaging.broadcast = function(message) {
|
vAPI.messaging.broadcast = function(message) {
|
||||||
this.globalMessageManager.broadcastAsyncMessage(
|
this.globalMessageManager.broadcastAsyncMessage(
|
||||||
location.host + ':broadcast',
|
location.host + ':broadcast',
|
||||||
@ -1297,69 +1390,6 @@ vAPI.messaging.broadcast = function(message) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This allows to avoid creating a closure for every single message which
|
|
||||||
// expects an answer. Having a closure created each time a message is processed
|
|
||||||
// has been always bothering me. Another benefit of the implementation here
|
|
||||||
// is to reuse the callback proxy object, so less memory churning.
|
|
||||||
//
|
|
||||||
// https://developers.google.com/speed/articles/optimizing-javascript
|
|
||||||
// "Creating a closure is significantly slower then creating an inner
|
|
||||||
// function without a closure, and much slower than reusing a static
|
|
||||||
// function"
|
|
||||||
//
|
|
||||||
// http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html
|
|
||||||
// "the dreaded 'uniformly slow code' case where every function takes 1%
|
|
||||||
// of CPU and you have to make one hundred separate performance optimizations
|
|
||||||
// to improve performance at all"
|
|
||||||
//
|
|
||||||
// http://jsperf.com/closure-no-closure/2
|
|
||||||
|
|
||||||
var CallbackWrapper = function(messageManager, channelName, listenerId, requestId) {
|
|
||||||
this.callback = this.proxy.bind(this); // bind once
|
|
||||||
this.init(messageManager, channelName, listenerId, requestId);
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.junkyard = [];
|
|
||||||
|
|
||||||
CallbackWrapper.factory = function(messageManager, channelName, listenerId, requestId) {
|
|
||||||
var wrapper = CallbackWrapper.junkyard.pop();
|
|
||||||
if ( wrapper ) {
|
|
||||||
wrapper.init(messageManager, channelName, listenerId, requestId);
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
return new CallbackWrapper(messageManager, channelName, listenerId, requestId);
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.prototype.init = function(messageManager, channelName, listenerId, requestId) {
|
|
||||||
this.messageManager = messageManager;
|
|
||||||
this.channelName = channelName;
|
|
||||||
this.listenerId = listenerId;
|
|
||||||
this.requestId = requestId;
|
|
||||||
};
|
|
||||||
|
|
||||||
CallbackWrapper.prototype.proxy = function(response) {
|
|
||||||
var message = JSON.stringify({
|
|
||||||
requestId: this.requestId,
|
|
||||||
channelName: this.channelName,
|
|
||||||
msg: response !== undefined ? response : null
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( this.messageManager.sendAsyncMessage ) {
|
|
||||||
this.messageManager.sendAsyncMessage(this.listenerId, message);
|
|
||||||
} else {
|
|
||||||
this.messageManager.broadcastAsyncMessage(this.listenerId, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark for reuse
|
|
||||||
this.messageManager =
|
|
||||||
this.channelName =
|
|
||||||
this.requestId =
|
|
||||||
this.listenerId = null;
|
|
||||||
CallbackWrapper.junkyard.push(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var httpObserver = {
|
var httpObserver = {
|
||||||
classDescription: 'net-channel-event-sinks for ' + location.host,
|
classDescription: 'net-channel-event-sinks for ' + location.host,
|
||||||
classID: Components.ID('{dc8d6319-5f6e-4438-999e-53722db99e84}'),
|
classID: Components.ID('{dc8d6319-5f6e-4438-999e-53722db99e84}'),
|
||||||
|
@ -67,152 +67,10 @@ vAPI.shutdown = (function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var MessagingListeners = function(callback) {
|
|
||||||
this.listeners = [];
|
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
this.listeners.push(callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.add = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( this.listeners.indexOf(callback) !== -1 ) {
|
|
||||||
throw new Error('Duplicate listener.');
|
|
||||||
}
|
|
||||||
this.listeners.push(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.remove = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( this.listeners.indexOf(callback) === -1 ) {
|
|
||||||
throw new Error('Listener not found.');
|
|
||||||
}
|
|
||||||
this.listeners.splice(this.listeners.indexOf(callback), 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.removeAll = function() {
|
|
||||||
this.listeners = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingListeners.prototype.process = function(msg) {
|
|
||||||
var listeners = this.listeners;
|
|
||||||
var n = listeners.length;
|
|
||||||
for ( var i = 0; i < n; i++ ) {
|
|
||||||
listeners[i](msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var messagingConnector = function(response) {
|
|
||||||
if ( !response ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
var channels = messaging.channels;
|
|
||||||
var channel;
|
|
||||||
|
|
||||||
// Sent to all channels
|
|
||||||
if ( response.broadcast === true && !response.channelName ) {
|
|
||||||
for ( channel in channels ) {
|
|
||||||
if ( channels[channel] instanceof MessagingChannel === false ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
channels[channel].listeners.process(response.msg);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response to specific message previously sent
|
|
||||||
if ( response.requestId ) {
|
|
||||||
var listener = messaging.pending[response.requestId];
|
|
||||||
delete messaging.pending[response.requestId];
|
|
||||||
delete response.requestId; // TODO: why?
|
|
||||||
if ( listener ) {
|
|
||||||
listener(response.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sent to a specific channel
|
|
||||||
channel = channels[response.channelName];
|
|
||||||
if ( channel instanceof MessagingChannel ) {
|
|
||||||
channel.listeners.process(response.msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var MessagingChannel = function(name, callback) {
|
|
||||||
this.channelName = name;
|
|
||||||
this.listeners = new MessagingListeners(callback);
|
|
||||||
this.refCount = 1;
|
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( !messaging.connected ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.send = function(message, callback) {
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( !messaging.connected ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
var requestId;
|
|
||||||
if ( callback ) {
|
|
||||||
requestId = messaging.requestId++;
|
|
||||||
messaging.pending[requestId] = callback;
|
|
||||||
}
|
|
||||||
sendAsyncMessage('ublock0:background', {
|
|
||||||
channelName: self._sandboxId_ + '|' + this.channelName,
|
|
||||||
requestId: requestId,
|
|
||||||
msg: message
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.close = function() {
|
|
||||||
this.refCount -= 1;
|
|
||||||
if ( this.refCount !== 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delete vAPI.messaging.channels[this.channelName];
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.addListener = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners.add(callback);
|
|
||||||
var messaging = vAPI.messaging;
|
|
||||||
if ( !messaging.connected ) {
|
|
||||||
messaging.setup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.removeListener = function(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners.remove(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
MessagingChannel.prototype.removeAllListeners = function() {
|
|
||||||
this.listeners.removeAll();
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.messaging = {
|
vAPI.messaging = {
|
||||||
channels: {},
|
channels: {},
|
||||||
pending: {},
|
pending: {},
|
||||||
requestId: 1,
|
auxProcessId: 1,
|
||||||
connected: false,
|
connected: false,
|
||||||
connector: function(msg) {
|
connector: function(msg) {
|
||||||
messagingConnector(JSON.parse(msg));
|
messagingConnector(JSON.parse(msg));
|
||||||
@ -283,6 +141,143 @@ window.addEventListener('pageshow', vAPI.messaging.toggleListener, true);
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var messagingConnector = function(details) {
|
||||||
|
if ( !details ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
var channels = messaging.channels;
|
||||||
|
var channel;
|
||||||
|
|
||||||
|
// Sent to all channels
|
||||||
|
if ( details.broadcast === true && !details.channelName ) {
|
||||||
|
for ( channel in channels ) {
|
||||||
|
if ( channels[channel] instanceof MessagingChannel === false ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
channels[channel].sendToListeners(details.msg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response to specific message previously sent
|
||||||
|
if ( details.auxProcessId ) {
|
||||||
|
var listener = messaging.pending[details.auxProcessId];
|
||||||
|
delete messaging.pending[details.auxProcessId];
|
||||||
|
delete details.auxProcessId; // TODO: why?
|
||||||
|
if ( listener ) {
|
||||||
|
listener(details.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent to a specific channel
|
||||||
|
var response;
|
||||||
|
channel = channels[details.channelName];
|
||||||
|
if ( channel instanceof MessagingChannel ) {
|
||||||
|
response = channel.sendToListeners(details.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond back if required
|
||||||
|
if ( details.mainProcessId !== undefined ) {
|
||||||
|
sendAsyncMessage('ublock0:background', {
|
||||||
|
mainProcessId: details.mainProcessId,
|
||||||
|
msg: response
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var MessagingChannel = function(name, callback) {
|
||||||
|
this.channelName = name;
|
||||||
|
this.listeners = typeof callback === 'function' ? [callback] : [];
|
||||||
|
this.refCount = 1;
|
||||||
|
if ( typeof callback === 'function' ) {
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( !messaging.connected ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.send = function(message, callback) {
|
||||||
|
this.sendTo(message, undefined, undefined, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.sendTo = function(message, toTabId, toChannel, callback) {
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( !messaging.connected ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
var auxProcessId;
|
||||||
|
if ( callback ) {
|
||||||
|
auxProcessId = messaging.auxProcessId++;
|
||||||
|
messaging.pending[auxProcessId] = callback;
|
||||||
|
}
|
||||||
|
sendAsyncMessage('ublock0:background', {
|
||||||
|
channelName: self._sandboxId_ + '|' + this.channelName,
|
||||||
|
auxProcessId: auxProcessId,
|
||||||
|
toTabId: toTabId,
|
||||||
|
toChannel: toChannel,
|
||||||
|
msg: message
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.close = function() {
|
||||||
|
this.refCount -= 1;
|
||||||
|
if ( this.refCount !== 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete vAPI.messaging.channels[this.channelName];
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.addListener = function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( this.listeners.indexOf(callback) !== -1 ) {
|
||||||
|
throw new Error('Duplicate listener.');
|
||||||
|
}
|
||||||
|
this.listeners.push(callback);
|
||||||
|
var messaging = vAPI.messaging;
|
||||||
|
if ( !messaging.connected ) {
|
||||||
|
messaging.setup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.removeListener = function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pos = this.listeners.indexOf(callback);
|
||||||
|
if ( pos === -1 ) {
|
||||||
|
throw new Error('Listener not found.');
|
||||||
|
}
|
||||||
|
this.listeners.splice(pos, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.removeAllListeners = function() {
|
||||||
|
this.listeners = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagingChannel.prototype.sendToListeners = function(msg) {
|
||||||
|
var response;
|
||||||
|
var listeners = this.listeners;
|
||||||
|
for ( var i = 0, n = listeners.length; i < n; i++ ) {
|
||||||
|
response = listeners[i](msg);
|
||||||
|
if ( response !== undefined ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=Cg0cmhjdiLs
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// No need to have vAPI client linger around after shutdown if
|
// No need to have vAPI client linger around after shutdown if
|
||||||
// we are not a top window (because element picker can still
|
// we are not a top window (because element picker can still
|
||||||
// be injected in top window).
|
// be injected in top window).
|
||||||
|
@ -142,6 +142,8 @@ var renderDOMFull = function(response) {
|
|||||||
inspector.appendChild(domTree);
|
inspector.appendChild(domTree);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=IDGNA83mxDo
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var patchIncremental = function(from, delta) {
|
var patchIncremental = function(from, delta) {
|
||||||
@ -229,6 +231,8 @@ var renderDOMIncremental = function(response) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=6u2KPtJB9h8
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var countFromNode = function(li) {
|
var countFromNode = function(li) {
|
||||||
@ -358,20 +362,17 @@ var onClick = function(ev) {
|
|||||||
// Toggle selector
|
// Toggle selector
|
||||||
if ( target.localName === 'code' ) {
|
if ( target.localName === 'code' ) {
|
||||||
var original = target.classList.contains('filter') === false;
|
var original = target.classList.contains('filter') === false;
|
||||||
messager.send({
|
messager.sendTo(
|
||||||
what: 'postMessageTo',
|
{
|
||||||
senderTabId: null,
|
|
||||||
senderChannel: 'logger-ui.js',
|
|
||||||
receiverTabId: inspectedTabId,
|
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: {
|
|
||||||
what: 'toggleNodes',
|
what: 'toggleNodes',
|
||||||
original: original,
|
original: original,
|
||||||
target: original !== target.classList.toggle('off'),
|
target: original !== target.classList.toggle('off'),
|
||||||
selector: selectorFromNode(target, original ? 1 : 2),
|
selector: selectorFromNode(target, original ? 1 : 2),
|
||||||
nid: original ? nidFromNode(target) : ''
|
nid: original ? nidFromNode(target) : ''
|
||||||
}
|
},
|
||||||
});
|
inspectedTabId,
|
||||||
|
'dom-inspector.js'
|
||||||
|
);
|
||||||
var cantCreate = inspector.querySelector('#domTree .off') === null;
|
var cantCreate = inspector.querySelector('#domTree .off') === null;
|
||||||
inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate);
|
inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate);
|
||||||
inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate);
|
inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate);
|
||||||
@ -387,19 +388,16 @@ var onMouseOver = (function() {
|
|||||||
|
|
||||||
var timerHandler = function() {
|
var timerHandler = function() {
|
||||||
mouseoverTimer = null;
|
mouseoverTimer = null;
|
||||||
messager.send({
|
messager.sendTo(
|
||||||
what: 'postMessageTo',
|
{
|
||||||
senderTabId: null,
|
|
||||||
senderChannel: 'logger-ui.js',
|
|
||||||
receiverTabId: inspectedTabId,
|
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: {
|
|
||||||
what: 'highlightOne',
|
what: 'highlightOne',
|
||||||
selector: selectorFromNode(mouseoverTarget),
|
selector: selectorFromNode(mouseoverTarget),
|
||||||
nid: nidFromNode(mouseoverTarget),
|
nid: nidFromNode(mouseoverTarget),
|
||||||
scrollTo: true
|
scrollTo: true
|
||||||
}
|
},
|
||||||
});
|
inspectedTabId,
|
||||||
|
'dom-inspector.js'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return function(ev) {
|
return function(ev) {
|
||||||
@ -447,8 +445,8 @@ var cancelPollTimer = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onDOMFetched = function(response) {
|
var onDOMFetched = function(response) {
|
||||||
if ( response === undefined || currentTabId() !== inspectedTabId ) {
|
if ( !response || currentTabId() !== inspectedTabId ) {
|
||||||
shutdownInspector(inspectedTabId);
|
shutdownInspector();
|
||||||
injectInspectorAsync(250);
|
injectInspectorAsync(250);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -478,21 +476,15 @@ var onDOMFetched = function(response) {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var fetchDOM = function() {
|
var fetchDOM = function() {
|
||||||
messager.send({
|
messager.sendTo(
|
||||||
what: 'postMessageTo',
|
{
|
||||||
senderTabId: null,
|
|
||||||
senderChannel: 'logger-ui.js',
|
|
||||||
receiverTabId: inspectedTabId,
|
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: {
|
|
||||||
what: 'domLayout',
|
what: 'domLayout',
|
||||||
fingerprint: fingerprint
|
fingerprint: fingerprint
|
||||||
}
|
},
|
||||||
});
|
inspectedTabId,
|
||||||
pollTimer = vAPI.setTimeout(function() {
|
'dom-inspector.js',
|
||||||
pollTimer = null;
|
onDOMFetched
|
||||||
onDOMFetched();
|
);
|
||||||
}, 1001);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -504,7 +496,7 @@ var fetchDOMAsync = function(delay) {
|
|||||||
pollTimer = vAPI.setTimeout(function() {
|
pollTimer = vAPI.setTimeout(function() {
|
||||||
pollTimer = null;
|
pollTimer = null;
|
||||||
fetchDOM();
|
fetchDOM();
|
||||||
}, delay || 1001);
|
}, delay || 2003);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -543,15 +535,10 @@ var injectInspectorAsync = function(delay) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var shutdownInspector = function(tabId) {
|
var shutdownInspector = function() {
|
||||||
messager.send({
|
if ( inspectedTabId !== '' ) {
|
||||||
what: 'postMessageTo',
|
messager.sendTo({ what: 'shutdown' }, inspectedTabId, 'dom-inspector.js');
|
||||||
senderTabId: null,
|
}
|
||||||
senderChannel: 'logger-ui.js',
|
|
||||||
receiverTabId: tabId,
|
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: { what: 'shutdown', }
|
|
||||||
});
|
|
||||||
logger.removeAllChildren(domTree);
|
logger.removeAllChildren(domTree);
|
||||||
cancelPollTimer();
|
cancelPollTimer();
|
||||||
inspectedTabId = '';
|
inspectedTabId = '';
|
||||||
@ -569,31 +556,25 @@ var onTabIdChanged = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var toggleHighlightMode = function() {
|
var toggleHighlightMode = function() {
|
||||||
messager.send({
|
messager.sendTo(
|
||||||
what: 'postMessageTo',
|
{
|
||||||
senderTabId: null,
|
|
||||||
senderChannel: 'logger-ui.js',
|
|
||||||
receiverTabId: inspectedTabId,
|
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: {
|
|
||||||
what: 'highlightMode',
|
what: 'highlightMode',
|
||||||
invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
|
invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
|
||||||
}
|
},
|
||||||
});
|
inspectedTabId,
|
||||||
|
'dom-inspector.js'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var revert = function() {
|
var revert = function() {
|
||||||
uDom('#domTree .off').removeClass('off');
|
uDom('#domTree .off').removeClass('off');
|
||||||
messager.send({
|
messager.sendTo(
|
||||||
what: 'postMessageTo',
|
{ what: 'resetToggledNodes' },
|
||||||
senderTabId: null,
|
inspectedTabId,
|
||||||
senderChannel: 'logger-ui.js',
|
'dom-inspector.js'
|
||||||
receiverTabId: inspectedTabId,
|
);
|
||||||
receiverChannel: 'dom-inspector.js',
|
|
||||||
msg: { what: 'resetToggledNodes' }
|
|
||||||
});
|
|
||||||
inspector.querySelector('.permatoolbar .revert').classList.add('disabled');
|
inspector.querySelector('.permatoolbar .revert').classList.add('disabled');
|
||||||
inspector.querySelector('.permatoolbar .commit').classList.add('disabled');
|
inspector.querySelector('.permatoolbar .commit').classList.add('disabled');
|
||||||
};
|
};
|
||||||
@ -601,11 +582,10 @@ var revert = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onMessage = function(request) {
|
var onMessage = function(request) {
|
||||||
var msg = request.what === 'postMessageTo' ? request.msg : request;
|
switch ( request.what ) {
|
||||||
switch ( msg.what ) {
|
|
||||||
case 'domLayout':
|
case 'domLayout':
|
||||||
cancelPollTimer();
|
cancelPollTimer();
|
||||||
onDOMFetched(msg);
|
onDOMFetched(request);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -1286,6 +1286,8 @@ var netFilteringManager = (function() {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=XyNYrmmdUd4
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -141,12 +141,6 @@ var onMessage = function(request, sender, callback) {
|
|||||||
vAPI.tabs.open(request.details);
|
vAPI.tabs.open(request.details);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Passthrough for auxiliary process to auxiliary process messaging.
|
|
||||||
case 'postMessageTo':
|
|
||||||
request.senderTabId = tabId;
|
|
||||||
vAPI.messaging.post(request);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'reloadTab':
|
case 'reloadTab':
|
||||||
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
|
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
|
||||||
vAPI.tabs.reload(request.tabId);
|
vAPI.tabs.reload(request.tabId);
|
||||||
|
@ -561,6 +561,8 @@ var domLayout = (function() {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=qo8zKhd4Cf0
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var highlightElements = function(scrollTo) {
|
var highlightElements = function(scrollTo) {
|
||||||
@ -724,6 +726,8 @@ var toggleNodes = function(nodes, originalState, targetState) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://www.youtube.com/watch?v=L5jRewnxSBY
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var resetToggledNodes = function() {
|
var resetToggledNodes = function() {
|
||||||
@ -757,20 +761,19 @@ var shutdown = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onMessage = function(request) {
|
var onMessage = function(request) {
|
||||||
var msg = request.what === 'postMessageTo' ? request.msg : request;
|
|
||||||
var response;
|
var response;
|
||||||
|
|
||||||
switch ( msg.what ) {
|
switch ( request.what ) {
|
||||||
case 'domLayout':
|
case 'domLayout':
|
||||||
response = domLayout.get(msg.fingerprint);
|
response = domLayout.get(request.fingerprint);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'highlightMode':
|
case 'highlightMode':
|
||||||
svgRoot.classList.toggle('invert', msg.invert);
|
svgRoot.classList.toggle('invert', request.invert);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'highlightOne':
|
case 'highlightOne':
|
||||||
hightlightNodes(msg.selector, msg.nid, msg.scrollTo);
|
hightlightNodes(request.selector, request.nid, request.scrollTo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'resetToggledNodes':
|
case 'resetToggledNodes':
|
||||||
@ -778,8 +781,8 @@ var onMessage = function(request) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'toggleNodes':
|
case 'toggleNodes':
|
||||||
highlightedElements = selectNodes(msg.selector, msg.nid);
|
highlightedElements = selectNodes(request.selector, request.nid);
|
||||||
toggleNodes(highlightedElements, msg.original, msg.target);
|
toggleNodes(highlightedElements, request.original, request.target);
|
||||||
highlightElements(true);
|
highlightElements(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -791,16 +794,7 @@ var onMessage = function(request) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( response !== undefined && request.what === 'postMessageTo' ) {
|
return response;
|
||||||
localMessager.send({
|
|
||||||
what: 'postMessageTo',
|
|
||||||
senderTabId: null,
|
|
||||||
senderChannel: 'dom-inspector.js',
|
|
||||||
receiverTabId: request.senderTabId,
|
|
||||||
receiverChannel: request.senderChannel,
|
|
||||||
msg: response
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
Loading…
Reference in New Issue
Block a user