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
*/
/* global messager, CSS */
/* global CSS */
'use strict';
/******************************************************************************/
@ -120,6 +120,8 @@
/******************************************************************************/
var localMessager = vAPI.messaging.channel('element-picker.js');
// https://github.com/gorhill/uBlock/issues/314#issuecomment-58878112
// Using an id makes uBlock's CSS rules more specific, thus prevents
// surrounding external rules from winning over own rules.
@ -203,11 +205,11 @@ var highlightElements = function(elems, force) {
}
targetElements = elems;
var ow = svgRoot.getAttribute('width');
var ow = parseInt(svgRoot.style.width, 10);
var ocean = [
'M0 0',
'h', ow,
'v', svgRoot.getAttribute('height'),
'v', parseInt(svgRoot.style.height, 10),
'h-', ow,
'z'
];
@ -231,7 +233,7 @@ var highlightElements = function(elems, force) {
islands.push(poly);
}
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' ) {
var filter = userFilterFromCandidate();
if ( filter ) {
messager.send({ what: 'createUserFilter', filters: filter });
localMessager.send({ what: 'createUserFilter', filters: filter });
removeElements(elementsFromFilter(taCandidate.value));
stopPicker();
}
@ -588,12 +590,12 @@ var showDialog = function(options) {
/******************************************************************************/
var elementFromPoint = function(x, y) {
svgRoot.style.pointerEvents = 'none';
svgRoot.style.display = 'none';
var elem = document.elementFromPoint(x, y);
if ( elem === document.body || elem === document.documentElement ) {
elem = null;
}
svgRoot.style.pointerEvents = 'auto';
svgRoot.style.display = '';
return elem;
};
@ -641,8 +643,8 @@ var onScrolled = function() {
var newHeight = this.scrollY + this.innerHeight;
if ( newHeight > svgHeight ) {
svgHeight = newHeight;
svgRoot.setAttribute('height', svgHeight);
svgRoot.setAttribute("viewBox", '0 0 ' + svgWidth + ' ' + svgHeight);
svgRoot.style.height = svgHeight + 'px';
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
}
highlightElements(targetElements, true);
};
@ -654,19 +656,19 @@ var onScrolled = function() {
var stopPicker = function() {
if ( pickerRoot !== null ) {
document.removeEventListener('keydown', onKeyPressed);
window.removeEventListener('scroll', onScrolled);
window.removeEventListener('keydown', onKeyPressed, true);
window.removeEventListener('scroll', onScrolled, true);
taCandidate.removeEventListener('input', onCandidateChanged);
divDialog.removeEventListener('click', onDialogClicked);
svgRoot.removeEventListener('mousemove', onSvgHovered);
svgRoot.removeEventListener('click', onSvgClicked);
pickerRoot.parentNode.removeChild(pickerRoot)
pickerRoot.parentNode.removeChild(pickerRoot);
pickerRoot =
divDialog =
svgRoot = svgOcean = svgIslands =
taCandidate =
urlNormalizer = null;
messager.close();
localMessager.close();
}
targetElements = [];
};
@ -740,7 +742,6 @@ var startPicker = function() {
'position: absolute;',
'top: 0;',
'left: 0;',
'pointer-events: auto;',
'cursor: crosshair;',
'z-index: 4999999999;',
'}',
@ -846,7 +847,8 @@ var startPicker = function() {
pickerRoot.appendChild(pickerStyle);
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;
svgHeight = Math.max(
document.documentElement.scrollHeight,
@ -854,11 +856,11 @@ var startPicker = function() {
);
svgRoot.setAttribute('x', 0);
svgRoot.setAttribute('y', 0);
svgRoot.setAttribute('width', svgWidth);
svgRoot.setAttribute('height', svgHeight);
svgRoot.setAttribute("viewBox", '0 0 ' + svgWidth + ' ' + svgHeight);
svgOcean = svgRoot.querySelector('path:first-child');
svgIslands = svgRoot.querySelector('path + path');
svgRoot.style.width = svgWidth + 'px';
svgRoot.style.height = svgHeight + 'px';
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
svgOcean = svgRoot.firstChild;
svgIslands = svgRoot.lastChild;
pickerRoot.appendChild(svgRoot);
// TODO: do not rely on element ids, they could collide with whatever
@ -892,8 +894,8 @@ var startPicker = function() {
taCandidate = divDialog.querySelector('textarea');
taCandidate.addEventListener('input', onCandidateChanged);
urlNormalizer = document.createElement('a');
window.addEventListener('scroll', onScrolled);
document.addEventListener('keydown', onKeyPressed);
window.addEventListener('scroll', onScrolled, true);
window.addEventListener('keydown', onKeyPressed, true);
highlightElements([], true);
@ -970,13 +972,16 @@ var startPicker = function() {
}
};
messager.send({ what: 'elementPickerArguments' }, initPicker);
localMessager.send({ what: 'elementPickerArguments' }, initPicker);
};
/******************************************************************************/
startPicker();
// This triggers the hiding of the popover in Safari
window.focus();
/******************************************************************************/
// https://www.youtube.com/watch?v=sociXdKnyr8

View File

@ -339,9 +339,41 @@ var onMessage = function(details, sender, callback) {
response = filterRequest(pageStore, details);
}
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':
response = {
i18n: {
@ -371,7 +403,7 @@ var onMessage = function(details, sender, callback) {
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 ) {
return;
}
chrome.tabs.executeScript(tabId, {
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-end.js',
allFrames: true,
runAt: 'document_idle'
}, scriptDone);
};
var scriptStart = function(tabId) {
chrome.tabs.executeScript(tabId, {
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-start.js',
allFrames: true,
runAt: 'document_idle'

View File

@ -252,7 +252,7 @@
µBlock.elementPickerExec = function(tabId, 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();
}
},
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
@ -176,7 +188,7 @@ if (window.chrome) {
var onMessage = function(request) {
var callback = function(response) {
// stfu
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError || response === undefined) {
return;
}
@ -492,6 +504,29 @@ if (window.chrome) {
if (tab) {
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) {
if (request.message.requestId) {
if (request.message.requestId && response !== undefined) {
request.target.page.dispatchMessage(
'message',
{

View File

@ -27,12 +27,12 @@ var messagingConnector = function(response) {
if (response.requestId) {
listener = vAPI.messaging.listeners[response.requestId];
}
if (!listener) {
channel = vAPI.messaging.channels[response.portName];
listener = channel && channel.listener;
}
}
if (typeof listener === 'function') {
delete vAPI.messaging.listeners[response.requestId];
@ -230,7 +230,6 @@ if (window.chrome) {
// relevant?
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
vAPI.messaging = {
port: null,
requestId: 0,
listeners: {},
channels: {},
@ -240,10 +239,19 @@ if (window.chrome) {
vAPI.messaging.connector(msg.message);
};
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() {
if (this._connector) {
safari.self.removeEventListener('message', this._connector, false);
this.channels = this.listeners = null;
}
},
channel: function(name, callback) {

View File

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