1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 12:57:57 +02:00

some refactoring: move to-aux-process-messaging timeout into vAPI

This commit is contained in:
gorhill 2015-06-29 10:46:20 -04:00
parent 957dea5289
commit aeba71790f
8 changed files with 664 additions and 651 deletions

View File

@ -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) {
this.listeners[listenerName] = callback;
};
/******************************************************************************/
vAPI.messaging.onPortMessage = function(request, port) {
var callback = vAPI.messaging.NOOPFUNC;
if ( request.requestId !== undefined ) {
callback = CallbackWrapper.factory(port, request).callback;
}
vAPI.messaging.onPortMessage = (function() {
var messaging = vAPI.messaging;
var toAuxPending = {};
// Specific handler
var r = vAPI.messaging.UNHANDLED;
var listener = vAPI.messaging.listeners[request.channelName];
if ( typeof listener === 'function' ) {
r = listener(request.msg, port.sender, callback);
}
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
// Use a wrapper to avoid closure and to allow reuse.
var CallbackWrapper = function(port, request, timeout) {
this.callback = this.proxy.bind(this); // bind once
this.init(port, request, timeout);
};
// Default handler
r = vAPI.messaging.defaultHandler(request.msg, port.sender, callback);
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
CallbackWrapper.prototype.init = function(port, request, timeout) {
this.port = port;
this.request = request;
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:
// Need to callback anyways in case caller expected an answer, or
// else there is a memory leak on caller's side
callback();
};
var callbackWrapperJunkyard = [];
var callbackWrapperFactory = function(port, request, timeout) {
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 = {};
/******************************************************************************/

View File

@ -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 = {
port: null,
channels: {},
pending: {},
requestId: 1,
auxProcessId: 1,
setup: function() {
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
// we are not a top window (because element picker can still
// be injected in top window).

View File

@ -1149,60 +1149,202 @@ vAPI.messaging.listen = function(listenerName, callback) {
/******************************************************************************/
vAPI.messaging.onMessage = function({target, data}) {
var messageManager = target.messageManager;
vAPI.messaging.onMessage = (function() {
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.
// So instead we broadcast to the parent window.
messageManager = getOwnerWindow(
target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
).messageManager;
}
if ( !messageManagerFrom ) {
messageManagerFrom = getOwnerWindow(
target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
).messageManager;
}
var channelNameRaw = data.channelName;
var pos = channelNameRaw.indexOf('|');
var channelName = channelNameRaw.slice(pos + 1);
var wrapper;
if ( details.auxProcessId !== undefined ) {
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;
if ( data.requestId !== undefined ) {
callback = CallbackWrapper.factory(
messageManager,
channelName,
channelNameRaw.slice(0, pos),
data.requestId
).callback;
}
var messageManagerTo = null;
var browser = tabWatcher.browserFromTabId(details.toTabId);
if ( browser !== null && browser.messageManager ) {
messageManagerTo = browser.messageManager;
}
if ( messageManagerTo === null ) {
if ( wrapper !== undefined ) {
wrapper.callback();
}
return;
}
var sender = {
tab: {
id: tabWatcher.tabIdFromTarget(target)
// 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;
}
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 r = vAPI.messaging.UNHANDLED;
var listener = vAPI.messaging.listeners[channelName];
if ( typeof listener === 'function' ) {
r = listener(data.msg, sender, callback);
}
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
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);
};
// Default handler
r = vAPI.messaging.defaultHandler(data.msg, sender, callback);
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
return function({target, data}) {
// Auxiliary process to auxiliary process
if ( data.toTabId !== undefined ) {
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:
// Need to callback anyways in case caller expected an answer, or
// else there is a memory leak on caller's side
callback();
};
// Auxiliary process to main process
var messageManager = target.messageManager;
// 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) {
this.globalMessageManager.broadcastAsyncMessage(
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 = {
classDescription: 'net-channel-event-sinks for ' + location.host,
classID: Components.ID('{dc8d6319-5f6e-4438-999e-53722db99e84}'),

View File

@ -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 = {
channels: {},
pending: {},
requestId: 1,
auxProcessId: 1,
connected: false,
connector: function(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
// we are not a top window (because element picker can still
// be injected in top window).

View File

@ -142,6 +142,8 @@ var renderDOMFull = function(response) {
inspector.appendChild(domTree);
};
// https://www.youtube.com/watch?v=IDGNA83mxDo
/******************************************************************************/
var patchIncremental = function(from, delta) {
@ -229,6 +231,8 @@ var renderDOMIncremental = function(response) {
}
};
// https://www.youtube.com/watch?v=6u2KPtJB9h8
/******************************************************************************/
var countFromNode = function(li) {
@ -358,20 +362,17 @@ var onClick = function(ev) {
// Toggle selector
if ( target.localName === 'code' ) {
var original = target.classList.contains('filter') === false;
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: inspectedTabId,
receiverChannel: 'dom-inspector.js',
msg: {
messager.sendTo(
{
what: 'toggleNodes',
original: original,
target: original !== target.classList.toggle('off'),
selector: selectorFromNode(target, original ? 1 : 2),
nid: original ? nidFromNode(target) : ''
}
});
},
inspectedTabId,
'dom-inspector.js'
);
var cantCreate = inspector.querySelector('#domTree .off') === null;
inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate);
inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate);
@ -387,19 +388,16 @@ var onMouseOver = (function() {
var timerHandler = function() {
mouseoverTimer = null;
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: inspectedTabId,
receiverChannel: 'dom-inspector.js',
msg: {
messager.sendTo(
{
what: 'highlightOne',
selector: selectorFromNode(mouseoverTarget),
nid: nidFromNode(mouseoverTarget),
scrollTo: true
}
});
},
inspectedTabId,
'dom-inspector.js'
);
};
return function(ev) {
@ -447,8 +445,8 @@ var cancelPollTimer = function() {
/******************************************************************************/
var onDOMFetched = function(response) {
if ( response === undefined || currentTabId() !== inspectedTabId ) {
shutdownInspector(inspectedTabId);
if ( !response || currentTabId() !== inspectedTabId ) {
shutdownInspector();
injectInspectorAsync(250);
return;
}
@ -478,21 +476,15 @@ var onDOMFetched = function(response) {
/******************************************************************************/
var fetchDOM = function() {
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: inspectedTabId,
receiverChannel: 'dom-inspector.js',
msg: {
messager.sendTo(
{
what: 'domLayout',
fingerprint: fingerprint
}
});
pollTimer = vAPI.setTimeout(function() {
pollTimer = null;
onDOMFetched();
}, 1001);
},
inspectedTabId,
'dom-inspector.js',
onDOMFetched
);
};
/******************************************************************************/
@ -504,7 +496,7 @@ var fetchDOMAsync = function(delay) {
pollTimer = vAPI.setTimeout(function() {
pollTimer = null;
fetchDOM();
}, delay || 1001);
}, delay || 2003);
};
/******************************************************************************/
@ -543,15 +535,10 @@ var injectInspectorAsync = function(delay) {
/******************************************************************************/
var shutdownInspector = function(tabId) {
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: tabId,
receiverChannel: 'dom-inspector.js',
msg: { what: 'shutdown', }
});
var shutdownInspector = function() {
if ( inspectedTabId !== '' ) {
messager.sendTo({ what: 'shutdown' }, inspectedTabId, 'dom-inspector.js');
}
logger.removeAllChildren(domTree);
cancelPollTimer();
inspectedTabId = '';
@ -569,31 +556,25 @@ var onTabIdChanged = function() {
/******************************************************************************/
var toggleHighlightMode = function() {
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: inspectedTabId,
receiverChannel: 'dom-inspector.js',
msg: {
messager.sendTo(
{
what: 'highlightMode',
invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
}
});
},
inspectedTabId,
'dom-inspector.js'
);
};
/******************************************************************************/
var revert = function() {
uDom('#domTree .off').removeClass('off');
messager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'logger-ui.js',
receiverTabId: inspectedTabId,
receiverChannel: 'dom-inspector.js',
msg: { what: 'resetToggledNodes' }
});
messager.sendTo(
{ what: 'resetToggledNodes' },
inspectedTabId,
'dom-inspector.js'
);
inspector.querySelector('.permatoolbar .revert').classList.add('disabled');
inspector.querySelector('.permatoolbar .commit').classList.add('disabled');
};
@ -601,11 +582,10 @@ var revert = function() {
/******************************************************************************/
var onMessage = function(request) {
var msg = request.what === 'postMessageTo' ? request.msg : request;
switch ( msg.what ) {
switch ( request.what ) {
case 'domLayout':
cancelPollTimer();
onDOMFetched(msg);
onDOMFetched(request);
break;
default:

View File

@ -1286,6 +1286,8 @@ var netFilteringManager = (function() {
};
})();
// https://www.youtube.com/watch?v=XyNYrmmdUd4
/******************************************************************************/
/******************************************************************************/

View File

@ -141,12 +141,6 @@ var onMessage = function(request, sender, callback) {
vAPI.tabs.open(request.details);
break;
// Passthrough for auxiliary process to auxiliary process messaging.
case 'postMessageTo':
request.senderTabId = tabId;
vAPI.messaging.post(request);
break;
case 'reloadTab':
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
vAPI.tabs.reload(request.tabId);

View File

@ -561,6 +561,8 @@ var domLayout = (function() {
};
})();
// https://www.youtube.com/watch?v=qo8zKhd4Cf0
/******************************************************************************/
var highlightElements = function(scrollTo) {
@ -724,6 +726,8 @@ var toggleNodes = function(nodes, originalState, targetState) {
}
};
// https://www.youtube.com/watch?v=L5jRewnxSBY
/******************************************************************************/
var resetToggledNodes = function() {
@ -757,20 +761,19 @@ var shutdown = function() {
/******************************************************************************/
var onMessage = function(request) {
var msg = request.what === 'postMessageTo' ? request.msg : request;
var response;
switch ( msg.what ) {
switch ( request.what ) {
case 'domLayout':
response = domLayout.get(msg.fingerprint);
response = domLayout.get(request.fingerprint);
break;
case 'highlightMode':
svgRoot.classList.toggle('invert', msg.invert);
svgRoot.classList.toggle('invert', request.invert);
break;
case 'highlightOne':
hightlightNodes(msg.selector, msg.nid, msg.scrollTo);
hightlightNodes(request.selector, request.nid, request.scrollTo);
break;
case 'resetToggledNodes':
@ -778,8 +781,8 @@ var onMessage = function(request) {
break;
case 'toggleNodes':
highlightedElements = selectNodes(msg.selector, msg.nid);
toggleNodes(highlightedElements, msg.original, msg.target);
highlightedElements = selectNodes(request.selector, request.nid);
toggleNodes(highlightedElements, request.original, request.target);
highlightElements(true);
break;
@ -791,16 +794,7 @@ var onMessage = function(request) {
break;
}
if ( response !== undefined && request.what === 'postMessageTo' ) {
localMessager.send({
what: 'postMessageTo',
senderTabId: null,
senderChannel: 'dom-inspector.js',
receiverTabId: request.senderTabId,
receiverChannel: request.senderChannel,
msg: response
});
}
return response;
};
/******************************************************************************/