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:
Deathamns 2014-10-20 13:26:02 +02:00
parent 88a7910bcb
commit d38ca13107
7 changed files with 116 additions and 42 deletions

View File

@ -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

View File

@ -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);
/******************************************************************************/ /******************************************************************************/

View File

@ -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'

View File

@ -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' });
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -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',
{ {

View File

@ -27,12 +27,12 @@ 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') {
delete vAPI.messaging.listeners[response.requestId]; delete vAPI.messaging.listeners[response.requestId];
@ -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) {

View File

@ -31,12 +31,6 @@
var exports = {}; var exports = {};
var noopFunc = function(){}; var noopFunc = function(){};
/******************************************************************************/
exports.injectScript = function(id, details) {
chrome.tabs.executeScript(id, details);
};
/******************************************************************************/ /******************************************************************************/