mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Script injection, element picker, messaging
- Add script injection to vAPI, plus a raw implementation for Safari (element-picker.js requires it) - Tweak element picker to work with Safari - Revert a change from previous commit: element-picker.js' background message handler (since actually it can have its own messaging channel) - Don't send "undefined" reponses from background to content
This commit is contained in:
parent
88a7910bcb
commit
d38ca13107
7 changed files with 116 additions and 42 deletions
|
@ -19,7 +19,7 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global messager, CSS */
|
/* global CSS */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -120,6 +120,8 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var localMessager = vAPI.messaging.channel('element-picker.js');
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/314#issuecomment-58878112
|
// https://github.com/gorhill/uBlock/issues/314#issuecomment-58878112
|
||||||
// Using an id makes uBlock's CSS rules more specific, thus prevents
|
// Using an id makes uBlock's CSS rules more specific, thus prevents
|
||||||
// surrounding external rules from winning over own rules.
|
// surrounding external rules from winning over own rules.
|
||||||
|
@ -203,11 +205,11 @@ var highlightElements = function(elems, force) {
|
||||||
}
|
}
|
||||||
targetElements = elems;
|
targetElements = elems;
|
||||||
|
|
||||||
var ow = svgRoot.getAttribute('width');
|
var ow = parseInt(svgRoot.style.width, 10);
|
||||||
var ocean = [
|
var ocean = [
|
||||||
'M0 0',
|
'M0 0',
|
||||||
'h', ow,
|
'h', ow,
|
||||||
'v', svgRoot.getAttribute('height'),
|
'v', parseInt(svgRoot.style.height, 10),
|
||||||
'h-', ow,
|
'h-', ow,
|
||||||
'z'
|
'z'
|
||||||
];
|
];
|
||||||
|
@ -231,7 +233,7 @@ var highlightElements = function(elems, force) {
|
||||||
islands.push(poly);
|
islands.push(poly);
|
||||||
}
|
}
|
||||||
svgOcean.setAttribute('d', ocean.join(''));
|
svgOcean.setAttribute('d', ocean.join(''));
|
||||||
svgIslands.setAttribute('d', islands.join(''));
|
svgIslands.setAttribute('d', islands.join('') || 'M 0 0');
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -502,7 +504,7 @@ var onDialogClicked = function(ev) {
|
||||||
else if ( ev.target.id === 'create' ) {
|
else if ( ev.target.id === 'create' ) {
|
||||||
var filter = userFilterFromCandidate();
|
var filter = userFilterFromCandidate();
|
||||||
if ( filter ) {
|
if ( filter ) {
|
||||||
messager.send({ what: 'createUserFilter', filters: filter });
|
localMessager.send({ what: 'createUserFilter', filters: filter });
|
||||||
removeElements(elementsFromFilter(taCandidate.value));
|
removeElements(elementsFromFilter(taCandidate.value));
|
||||||
stopPicker();
|
stopPicker();
|
||||||
}
|
}
|
||||||
|
@ -588,12 +590,12 @@ var showDialog = function(options) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var elementFromPoint = function(x, y) {
|
var elementFromPoint = function(x, y) {
|
||||||
svgRoot.style.pointerEvents = 'none';
|
svgRoot.style.display = 'none';
|
||||||
var elem = document.elementFromPoint(x, y);
|
var elem = document.elementFromPoint(x, y);
|
||||||
if ( elem === document.body || elem === document.documentElement ) {
|
if ( elem === document.body || elem === document.documentElement ) {
|
||||||
elem = null;
|
elem = null;
|
||||||
}
|
}
|
||||||
svgRoot.style.pointerEvents = 'auto';
|
svgRoot.style.display = '';
|
||||||
return elem;
|
return elem;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -641,8 +643,8 @@ var onScrolled = function() {
|
||||||
var newHeight = this.scrollY + this.innerHeight;
|
var newHeight = this.scrollY + this.innerHeight;
|
||||||
if ( newHeight > svgHeight ) {
|
if ( newHeight > svgHeight ) {
|
||||||
svgHeight = newHeight;
|
svgHeight = newHeight;
|
||||||
svgRoot.setAttribute('height', svgHeight);
|
svgRoot.style.height = svgHeight + 'px';
|
||||||
svgRoot.setAttribute("viewBox", '0 0 ' + svgWidth + ' ' + svgHeight);
|
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
|
||||||
}
|
}
|
||||||
highlightElements(targetElements, true);
|
highlightElements(targetElements, true);
|
||||||
};
|
};
|
||||||
|
@ -654,19 +656,19 @@ var onScrolled = function() {
|
||||||
|
|
||||||
var stopPicker = function() {
|
var stopPicker = function() {
|
||||||
if ( pickerRoot !== null ) {
|
if ( pickerRoot !== null ) {
|
||||||
document.removeEventListener('keydown', onKeyPressed);
|
window.removeEventListener('keydown', onKeyPressed, true);
|
||||||
window.removeEventListener('scroll', onScrolled);
|
window.removeEventListener('scroll', onScrolled, true);
|
||||||
taCandidate.removeEventListener('input', onCandidateChanged);
|
taCandidate.removeEventListener('input', onCandidateChanged);
|
||||||
divDialog.removeEventListener('click', onDialogClicked);
|
divDialog.removeEventListener('click', onDialogClicked);
|
||||||
svgRoot.removeEventListener('mousemove', onSvgHovered);
|
svgRoot.removeEventListener('mousemove', onSvgHovered);
|
||||||
svgRoot.removeEventListener('click', onSvgClicked);
|
svgRoot.removeEventListener('click', onSvgClicked);
|
||||||
pickerRoot.parentNode.removeChild(pickerRoot)
|
pickerRoot.parentNode.removeChild(pickerRoot);
|
||||||
pickerRoot =
|
pickerRoot =
|
||||||
divDialog =
|
divDialog =
|
||||||
svgRoot = svgOcean = svgIslands =
|
svgRoot = svgOcean = svgIslands =
|
||||||
taCandidate =
|
taCandidate =
|
||||||
urlNormalizer = null;
|
urlNormalizer = null;
|
||||||
messager.close();
|
localMessager.close();
|
||||||
}
|
}
|
||||||
targetElements = [];
|
targetElements = [];
|
||||||
};
|
};
|
||||||
|
@ -740,7 +742,6 @@ var startPicker = function() {
|
||||||
'position: absolute;',
|
'position: absolute;',
|
||||||
'top: 0;',
|
'top: 0;',
|
||||||
'left: 0;',
|
'left: 0;',
|
||||||
'pointer-events: auto;',
|
|
||||||
'cursor: crosshair;',
|
'cursor: crosshair;',
|
||||||
'z-index: 4999999999;',
|
'z-index: 4999999999;',
|
||||||
'}',
|
'}',
|
||||||
|
@ -846,7 +847,8 @@ var startPicker = function() {
|
||||||
pickerRoot.appendChild(pickerStyle);
|
pickerRoot.appendChild(pickerStyle);
|
||||||
|
|
||||||
svgRoot = document.createElementNS(svgns, 'svg');
|
svgRoot = document.createElementNS(svgns, 'svg');
|
||||||
svgRoot.innerHTML = '<path /><path />';
|
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
|
||||||
|
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
|
||||||
svgWidth = document.documentElement.scrollWidth;
|
svgWidth = document.documentElement.scrollWidth;
|
||||||
svgHeight = Math.max(
|
svgHeight = Math.max(
|
||||||
document.documentElement.scrollHeight,
|
document.documentElement.scrollHeight,
|
||||||
|
@ -854,11 +856,11 @@ var startPicker = function() {
|
||||||
);
|
);
|
||||||
svgRoot.setAttribute('x', 0);
|
svgRoot.setAttribute('x', 0);
|
||||||
svgRoot.setAttribute('y', 0);
|
svgRoot.setAttribute('y', 0);
|
||||||
svgRoot.setAttribute('width', svgWidth);
|
svgRoot.style.width = svgWidth + 'px';
|
||||||
svgRoot.setAttribute('height', svgHeight);
|
svgRoot.style.height = svgHeight + 'px';
|
||||||
svgRoot.setAttribute("viewBox", '0 0 ' + svgWidth + ' ' + svgHeight);
|
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
|
||||||
svgOcean = svgRoot.querySelector('path:first-child');
|
svgOcean = svgRoot.firstChild;
|
||||||
svgIslands = svgRoot.querySelector('path + path');
|
svgIslands = svgRoot.lastChild;
|
||||||
pickerRoot.appendChild(svgRoot);
|
pickerRoot.appendChild(svgRoot);
|
||||||
|
|
||||||
// TODO: do not rely on element ids, they could collide with whatever
|
// TODO: do not rely on element ids, they could collide with whatever
|
||||||
|
@ -892,8 +894,8 @@ var startPicker = function() {
|
||||||
taCandidate = divDialog.querySelector('textarea');
|
taCandidate = divDialog.querySelector('textarea');
|
||||||
taCandidate.addEventListener('input', onCandidateChanged);
|
taCandidate.addEventListener('input', onCandidateChanged);
|
||||||
urlNormalizer = document.createElement('a');
|
urlNormalizer = document.createElement('a');
|
||||||
window.addEventListener('scroll', onScrolled);
|
window.addEventListener('scroll', onScrolled, true);
|
||||||
document.addEventListener('keydown', onKeyPressed);
|
window.addEventListener('keydown', onKeyPressed, true);
|
||||||
|
|
||||||
highlightElements([], true);
|
highlightElements([], true);
|
||||||
|
|
||||||
|
@ -970,13 +972,16 @@ var startPicker = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
messager.send({ what: 'elementPickerArguments' }, initPicker);
|
localMessager.send({ what: 'elementPickerArguments' }, initPicker);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
startPicker();
|
startPicker();
|
||||||
|
|
||||||
|
// This triggers the hiding of the popover in Safari
|
||||||
|
window.focus();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=sociXdKnyr8
|
// https://www.youtube.com/watch?v=sociXdKnyr8
|
||||||
|
|
|
@ -339,9 +339,41 @@ var onMessage = function(details, sender, callback) {
|
||||||
response = filterRequest(pageStore, details);
|
response = filterRequest(pageStore, details);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
};
|
||||||
|
|
||||||
// the following is used by element-picker.js
|
vAPI.messaging.listen('contentscript-end.js', onMessage);
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// element-picker.js
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var µb = µBlock;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var onMessage = function(request, sender, callback) {
|
||||||
|
// Async
|
||||||
|
switch ( request.what ) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
var response;
|
||||||
|
|
||||||
|
switch ( request.what ) {
|
||||||
case 'elementPickerArguments':
|
case 'elementPickerArguments':
|
||||||
response = {
|
response = {
|
||||||
i18n: {
|
i18n: {
|
||||||
|
@ -371,7 +403,7 @@ var onMessage = function(details, sender, callback) {
|
||||||
callback(response);
|
callback(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.listen('contentscript-end.js', onMessage);
|
vAPI.messaging.listen('element-picker.js', onMessage);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -602,14 +602,14 @@
|
||||||
if ( chrome.runtime.lastError ) {
|
if ( chrome.runtime.lastError ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chrome.tabs.executeScript(tabId, {
|
vAPI.tabs.injectScript(tabId, {
|
||||||
file: 'js/contentscript-end.js',
|
file: 'js/contentscript-end.js',
|
||||||
allFrames: true,
|
allFrames: true,
|
||||||
runAt: 'document_idle'
|
runAt: 'document_idle'
|
||||||
}, scriptDone);
|
}, scriptDone);
|
||||||
};
|
};
|
||||||
var scriptStart = function(tabId) {
|
var scriptStart = function(tabId) {
|
||||||
chrome.tabs.executeScript(tabId, {
|
vAPI.tabs.injectScript(tabId, {
|
||||||
file: 'js/contentscript-start.js',
|
file: 'js/contentscript-start.js',
|
||||||
allFrames: true,
|
allFrames: true,
|
||||||
runAt: 'document_idle'
|
runAt: 'document_idle'
|
||||||
|
|
|
@ -252,7 +252,7 @@
|
||||||
|
|
||||||
µBlock.elementPickerExec = function(tabId, targetElement) {
|
µBlock.elementPickerExec = function(tabId, targetElement) {
|
||||||
this.elementPickerTarget = targetElement || '';
|
this.elementPickerTarget = targetElement || '';
|
||||||
this.XAL.injectScript(tabId, { file: 'js/element-picker.js' });
|
vAPI.tabs.injectScript(tabId, { file: 'js/element-picker.js' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -136,7 +136,19 @@ if (window.chrome) {
|
||||||
wrapper();
|
wrapper();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close: chrome.tabs.remove.bind(chrome.tabs)
|
close: chrome.tabs.remove.bind(chrome.tabs),
|
||||||
|
injectScript: function(tabId, details, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
callback = function(){};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabId) {
|
||||||
|
chrome.tabs.executeScript(tabId, details, callback);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
chrome.tabs.executeScript(details, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
||||||
|
@ -176,7 +188,7 @@ if (window.chrome) {
|
||||||
var onMessage = function(request) {
|
var onMessage = function(request) {
|
||||||
var callback = function(response) {
|
var callback = function(response) {
|
||||||
// stfu
|
// stfu
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError || response === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,6 +504,29 @@ if (window.chrome) {
|
||||||
if (tab) {
|
if (tab) {
|
||||||
tab.close();
|
tab.close();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
injectScript: function(tabId, details, callback) {
|
||||||
|
var tab = tabId ? this.stack[tabId] : safari.application.activeBrowserWindow.activeTab;
|
||||||
|
|
||||||
|
if (details.file) {
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
xhr.overrideMimeType('application/x-javascript;charset=utf-8');
|
||||||
|
xhr.open('GET', details.file, false);
|
||||||
|
xhr.send();
|
||||||
|
details.code = xhr.responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.page.dispatchMessage('message', {
|
||||||
|
portName: 'vAPI',
|
||||||
|
msg: {
|
||||||
|
cmd: 'runScript',
|
||||||
|
details: details
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
setTimeout(callback, 13);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -624,7 +659,7 @@ if (window.chrome) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var callback = function(response) {
|
var callback = function(response) {
|
||||||
if (request.message.requestId) {
|
if (request.message.requestId && response !== undefined) {
|
||||||
request.target.page.dispatchMessage(
|
request.target.page.dispatchMessage(
|
||||||
'message',
|
'message',
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,11 @@ var messagingConnector = function(response) {
|
||||||
|
|
||||||
if (response.requestId) {
|
if (response.requestId) {
|
||||||
listener = vAPI.messaging.listeners[response.requestId];
|
listener = vAPI.messaging.listeners[response.requestId];
|
||||||
|
}
|
||||||
|
|
||||||
if (!listener) {
|
if (!listener) {
|
||||||
channel = vAPI.messaging.channels[response.portName];
|
channel = vAPI.messaging.channels[response.portName];
|
||||||
listener = channel && channel.listener;
|
listener = channel && channel.listener;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof listener === 'function') {
|
if (typeof listener === 'function') {
|
||||||
|
@ -230,7 +230,6 @@ if (window.chrome) {
|
||||||
// relevant?
|
// relevant?
|
||||||
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
|
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
|
||||||
vAPI.messaging = {
|
vAPI.messaging = {
|
||||||
port: null,
|
|
||||||
requestId: 0,
|
requestId: 0,
|
||||||
listeners: {},
|
listeners: {},
|
||||||
channels: {},
|
channels: {},
|
||||||
|
@ -240,10 +239,19 @@ if (window.chrome) {
|
||||||
vAPI.messaging.connector(msg.message);
|
vAPI.messaging.connector(msg.message);
|
||||||
};
|
};
|
||||||
safari.self.addEventListener('message', this._connector, false);
|
safari.self.addEventListener('message', this._connector, false);
|
||||||
|
|
||||||
|
this.channels['vAPI'] = {
|
||||||
|
listener: function(msg) {
|
||||||
|
if (msg.cmd === 'runScript' && msg.details.code) {
|
||||||
|
Function(msg.details.code).call(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
close: function() {
|
close: function() {
|
||||||
if (this._connector) {
|
if (this._connector) {
|
||||||
safari.self.removeEventListener('message', this._connector, false);
|
safari.self.removeEventListener('message', this._connector, false);
|
||||||
|
this.channels = this.listeners = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
channel: function(name, callback) {
|
channel: function(name, callback) {
|
||||||
|
|
|
@ -31,12 +31,6 @@
|
||||||
var exports = {};
|
var exports = {};
|
||||||
var noopFunc = function(){};
|
var noopFunc = function(){};
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
exports.injectScript = function(id, details) {
|
|
||||||
chrome.tabs.executeScript(id, details);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue