Element picker tweaks

- Indentation whitespace fixes.
- Use built-in getBoundingClientRect() function instead of self-made.
- Use built-in DOM API for manipulating the class attributes instead of
  altering the className property.
- Add pointer-events: none to the svgRoot when using
  document.elementFromPoint(), but if the browser (older Safari for
  example) doesn't take the pointer-events into account, then fall back to
  display: none.
- Initiate every part of the picker at the same time; when the message is
  received from the background. This way the selected element will have
  the red overlay immediately, instead of showing first the black overlay,
  then a few milliseconds later the red.
This commit is contained in:
Deathamns 2014-10-23 14:12:37 +02:00
parent 6d49ef0dac
commit cc27193147
2 changed files with 174 additions and 181 deletions

View File

@ -120,16 +120,21 @@
/******************************************************************************/ /******************************************************************************/
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.
var µBlockId = CSS.escape('µBlock'); var µBlockId = CSS.escape('µBlock');
var pickerRoot = document.getElementById(µBlockId);
if ( pickerRoot ) {
return;
}
var localMessager = vAPI.messaging.channel('element-picker.js');
var svgns = 'http://www.w3.org/2000/svg'; var svgns = 'http://www.w3.org/2000/svg';
var pickerRoot = null;
var svgRoot = null; var svgRoot = null;
var svgOcean = null; var svgOcean = null;
var svgIslands = null; var svgIslands = null;
@ -143,23 +148,25 @@ var cosmeticFilterCandidates = [];
var targetElements = []; var targetElements = [];
var svgWidth = 0; var svgWidth = 0;
var svgHeight = 0; var svgHeight = 0;
var elementFromPointCSSProperty = 'pointerEvents';
var onSvgHoveredTimer = null;
/******************************************************************************/ /******************************************************************************/
var pickerPaused = function() { var pickerPaused = function() {
return /(^| )paused( |$)/.test(pickerRoot.className); return pickerRoot.classList.contains('paused');
}; };
/******************************************************************************/ /******************************************************************************/
var pausePicker = function() { var pausePicker = function() {
pickerRoot.className += ' paused'; pickerRoot.classList.add('paused');
}; };
/******************************************************************************/ /******************************************************************************/
var unpausePicker = function() { var unpausePicker = function() {
pickerRoot.className = pickerRoot.className.replace(/(^| )paused( |$)/g, '').trim(); pickerRoot.classList.remove('paused');
}; };
/******************************************************************************/ /******************************************************************************/
@ -178,24 +185,6 @@ var pickerRootDistance = function(elem) {
/******************************************************************************/ /******************************************************************************/
// https://github.com/gorhill/uBlock/issues/210
// HTMLElement.getBoundingClientRect() is unreliable (or I misunderstand what
// it does). I will just compute it myself.
var getBoundingClientRect = function(elem, r) {
r.w = elem.offsetWidth;
r.h = elem.offsetHeight;
r.x = elem.offsetLeft - elem.scrollLeft;
r.y = elem.offsetTop - elem.scrollTop;
while ( elem = elem.offsetParent ) {
r.x += elem.offsetLeft - elem.scrollLeft;
r.y += elem.offsetTop - elem.scrollTop;
}
};
/******************************************************************************/
var highlightElements = function(elems, force) { var highlightElements = function(elems, force) {
// To make mouse move handler more efficient // To make mouse move handler more efficient
if ( !force && elems.length === targetElements.length ) { if ( !force && elems.length === targetElements.length ) {
@ -216,7 +205,6 @@ var highlightElements = function(elems, force) {
var offx = window.pageXOffset; var offx = window.pageXOffset;
var offy = window.pageYOffset; var offy = window.pageYOffset;
var islands = []; var islands = [];
// To avoid memory churning, reuse object
var elem, rect, poly; var elem, rect, poly;
for ( var i = 0; i < elems.length; i++ ) { for ( var i = 0; i < elems.length; i++ ) {
elem = elems[i]; elem = elems[i];
@ -590,23 +578,27 @@ var showDialog = function(options) {
/******************************************************************************/ /******************************************************************************/
var elementFromPoint = function(x, y) { var elementFromPoint = function(x, y) {
svgRoot.style.display = 'none'; svgRoot.style[elementFromPointCSSProperty] = '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.display = ''; svgRoot.style[elementFromPointCSSProperty] = '';
return elem; return elem;
}; };
/******************************************************************************/ /******************************************************************************/
var onSvgHovered = function(ev) { var onSvgHovered = function(ev) {
if ( pickerPaused() ) { if ( pickerPaused() || onSvgHoveredTimer) {
return; return;
} }
onSvgHoveredTimer = setTimeout(function() {
var elem = elementFromPoint(ev.clientX, ev.clientY); var elem = elementFromPoint(ev.clientX, ev.clientY);
highlightElements(elem ? [elem] : []); highlightElements(elem ? [elem] : []);
onSvgHoveredTimer = null;
}, 50);
}; };
/******************************************************************************/ /******************************************************************************/
@ -675,11 +667,7 @@ var stopPicker = function() {
/******************************************************************************/ /******************************************************************************/
var startPicker = function() { var startPicker = function(details) {
pickerRoot = document.getElementById(µBlockId);
if ( pickerRoot !== null ) {
return;
}
pickerRoot = document.createElement('div'); pickerRoot = document.createElement('div');
pickerRoot.id = µBlockId; pickerRoot.id = µBlockId;
@ -742,6 +730,7 @@ 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;',
'}', '}',
@ -899,7 +888,6 @@ var startPicker = function() {
highlightElements([], true); highlightElements([], true);
var initPicker = function(details) {
var i18nMap = { var i18nMap = {
'#µBlock > div': '@@bidi_dir', '#µBlock > div': '@@bidi_dir',
'#create': 'create', '#create': 'create',
@ -922,8 +910,16 @@ var startPicker = function() {
divDialog.querySelector(k).firstChild.nodeValue = details.i18n[i18nMap[k]]; divDialog.querySelector(k).firstChild.nodeValue = details.i18n[i18nMap[k]];
} }
// First we test if pointer-events are hadnled in Node.elementFromPoint().
// If the browser ignores pointer-events in Node.elementFromPoint(),
// then use the display property instead (e.g., for older Safari).
var elem = elementFromPoint(0, 0);
if ( elem === svgRoot ) {
elementFromPointCSSProperty = 'display';
}
// Auto-select a specific target, if any, and if possible // Auto-select a specific target, if any, and if possible
var elem;
// Try using mouse position // Try using mouse position
if ( details.clientX !== -1 ) { if ( details.clientX !== -1 ) {
@ -972,12 +968,9 @@ var startPicker = function() {
} }
}; };
localMessager.send({ what: 'elementPickerArguments' }, initPicker);
};
/******************************************************************************/ /******************************************************************************/
startPicker(); localMessager.send({ what: 'elementPickerArguments' }, startPicker);
// This triggers the hiding of the popover in Safari // This triggers the hiding of the popover in Safari
window.focus(); window.focus();

View File

@ -38,7 +38,7 @@ var messagingConnector = function(response) {
// Safari bug // Safari bug
// Deleting the response.requestId below (only in some cases, probably // Deleting the response.requestId below (only in some cases, probably
// when frames are present on the page) will remove it from all the // when frames are present on the page) will remove it from all the
// following messages too, however with the following line it won't. // future messages too, however with the following line it won't.
vAPI.safari && console.log; vAPI.safari && console.log;
delete vAPI.messaging.listeners[response.requestId]; delete vAPI.messaging.listeners[response.requestId];