Move every part of the picker to the iframe

This commit is contained in:
Deathamns 2015-02-09 14:03:29 +01:00
parent 8693ab738d
commit 187355b2b2
2 changed files with 165 additions and 216 deletions

View File

@ -1,11 +1,14 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>
body { :focus {
outline: none;
}
html, body {
margin: 0; margin: 0;
width: 100%; width: 100%;
height: 100%;
overflow: hidden; overflow: hidden;
display: inline-block;
font: 12px sans-serif; font: 12px sans-serif;
} }
ul, li, div { ul, li, div {
@ -13,6 +16,7 @@ ul, li, div {
} }
button { button {
border: 1px solid #aaa; border: 1px solid #aaa;
margin-left: 3px;
padding: 6px 8px 4px 8px; padding: 6px 8px 4px 8px;
box-sizing: border-box; box-sizing: border-box;
box-shadow: none; box-shadow: none;
@ -61,9 +65,6 @@ div {
div:hover { div:hover {
opacity: 1; opacity: 1;
} }
button {
margin-left: 3px;
}
ul { ul {
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
@ -94,27 +95,66 @@ ul > li > ul > li {
direction: ltr; direction: ltr;
} }
ul > li > ul > li:hover { ul > li > ul > li:hover {
background-color: rgba(255,255,255,1.0); background-color: rgba(255,255,255,1);
}
svg {
position: fixed;
top: 0;
left: 0;
cursor: crosshair;
width: 100%;
height: 100%;
}
.paused > svg {
cursor: wait;
}
svg > path:first-child {
fill: rgba(0,0,0,0.75);
fill-rule: evenodd;
}
svg > path + path {
stroke: #F00;
stroke-width: 0.5px;
fill: rgba(255,0,0,0.25);
}
aside {
background-color: rgba(255,255,255,0.9);
bottom: 4px;
padding: 4px;
display: none;
position: fixed;
right: 4px;
width: 30em;
}
body.paused > aside {
opacity: 0.2;
display: block;
}
body.paused > aside:hover {
opacity: 1;
} }
</style> </style>
</head> </head>
<body direction="{{bidi_dir}}"> <body direction="{{bidi_dir}}">
<section> <svg><path></path><path></path></svg>
<textarea lang="en" dir="ltr" spellcheck="false"></textarea> <aside>
<div> <section>
<button id="create" type="button" disabled="disabled">{{create}}</button> <textarea lang="en" dir="ltr" spellcheck="false"></textarea>
<button id="pick" type="button">{{pick}}</button> <div>
<button id="quit" type="button">{{quit}}</button> <button id="create" type="button" disabled="disabled">{{create}}</button>
</div> <button id="pick" type="button">{{pick}}</button>
</section> <button id="quit" type="button">{{quit}}</button>
<ul> </div>
<li id="netFilters"> </section>
<span>{{netFilters}}</span><ul lang="en" class="changeFilter"></ul> <ul>
</li> <li id="netFilters">
<li id="cosmeticFilters"> <span>{{netFilters}}</span><ul lang="en" class="changeFilter"></ul>
<span>{{cosmeticFilters}}</span> <span>{{cosmeticFiltersHint}}</span> </li>
<ul lang="en" class="changeFilter"></ul> <li id="cosmeticFilters">
</li> <span>{{cosmeticFilters}}</span> <span>{{cosmeticFiltersHint}}</span>
</ul> <ul lang="en" class="changeFilter"></ul>
</li>
</ul>
</aside>
</body> </body>

View File

@ -133,23 +133,16 @@ if ( pickerRoot ) {
var localMessager = vAPI.messaging.channel('element-picker.js'); var localMessager = vAPI.messaging.channel('element-picker.js');
var svgRoot = null;
var svgOcean = null; var svgOcean = null;
var svgIslands = null; var svgIslands = null;
var frameDialog = null; var dialog = null;
var dialogBody = null;
var taCandidate = null; var taCandidate = null;
var urlNormalizer = null; var urlNormalizer = null;
var lastDetails = null;
var netFilterCandidates = []; var netFilterCandidates = [];
var cosmeticFilterCandidates = []; var cosmeticFilterCandidates = [];
var targetElements = []; var targetElements = [];
var svgWidth = 0;
var svgHeight = 0;
var onSvgHoveredTimer = null;
/******************************************************************************/ /******************************************************************************/
@ -166,20 +159,16 @@ try {
/******************************************************************************/ /******************************************************************************/
var pickerPaused = function() {
return pickerRoot.classList.contains('paused');
};
/******************************************************************************/
var pausePicker = function() { var pausePicker = function() {
pickerRoot.classList.add('paused'); dialog.parentNode.classList.add('paused');
svgListening(false);
}; };
/******************************************************************************/ /******************************************************************************/
var unpausePicker = function() { var unpausePicker = function() {
pickerRoot.classList.remove('paused'); dialog.parentNode.classList.remove('paused');
svgListening(true);
}; };
/******************************************************************************/ /******************************************************************************/
@ -193,16 +182,15 @@ var highlightElements = function(elems, force) {
} }
targetElements = elems; targetElements = elems;
var ow = parseInt(svgRoot.style.width, 10); var ow = pickerRoot.contentWindow.innerWidth;
var oh = pickerRoot.contentWindow.innerHeight;
var ocean = [ var ocean = [
'M0 0', 'M0 0',
'h', ow, 'h', ow,
'v', parseInt(svgRoot.style.height, 10), 'v', oh,
'h-', ow, 'h-', ow,
'z' 'z'
]; ];
var offx = window.pageXOffset;
var offy = window.pageYOffset;
var islands = []; var islands = [];
var elem, rect, poly; var elem, rect, poly;
@ -212,7 +200,14 @@ var highlightElements = function(elems, force) {
continue; continue;
} }
rect = elem.getBoundingClientRect(); rect = elem.getBoundingClientRect();
poly = 'M' + (rect.left + offx) + ' ' + (rect.top + offy) +
// Ignore if it's not on the screen
if ( rect.left > ow || rect.top > oh ||
rect.left + rect.width < 0 || rect.top + rect.height < 0 ) {
continue;
}
poly = 'M' + rect.left + ' ' + rect.top +
'h' + rect.width + 'h' + rect.width +
'v' + rect.height + 'v' + rect.height +
'h-' + rect.width + 'h-' + rect.width +
@ -460,7 +455,7 @@ var userFilterFromCandidate = function() {
var onCandidateChanged = function() { var onCandidateChanged = function() {
var elems = elementsFromFilter(taCandidate.value); var elems = elementsFromFilter(taCandidate.value);
dialogBody.querySelector('#create').disabled = elems.length === 0; dialog.querySelector('#create').disabled = elems.length === 0;
highlightElements(elems); highlightElements(elems);
}; };
@ -564,7 +559,7 @@ var showDialog = function(options) {
// Create lists of candidate filters // Create lists of candidate filters
var populate = function(src, des) { var populate = function(src, des) {
var root = dialogBody.querySelector(des); var root = dialog.querySelector(des);
var ul = root.querySelector('ul'); var ul = root.querySelector('ul');
removeAllChildren(ul); removeAllChildren(ul);
var li; var li;
@ -579,8 +574,8 @@ var showDialog = function(options) {
populate(netFilterCandidates, '#netFilters'); populate(netFilterCandidates, '#netFilters');
populate(cosmeticFilterCandidates, '#cosmeticFilters'); populate(cosmeticFilterCandidates, '#cosmeticFilters');
dialogBody.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none'; dialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
dialogBody.querySelector('#create').disabled = true; dialog.querySelector('#create').disabled = true;
// Auto-select a candidate filter // Auto-select a candidate filter
var filterChoice = { var filterChoice = {
@ -602,42 +597,30 @@ var showDialog = function(options) {
taCandidate.value = candidateFromFilterChoice(filterChoice); taCandidate.value = candidateFromFilterChoice(filterChoice);
onCandidateChanged(); onCandidateChanged();
} }
frameDialog.style.height = dialogBody.offsetHeight + 'px';
}; };
/******************************************************************************/ /******************************************************************************/
var elementFromPoint = function(x, y) { var elementFromPoint = function(x, y) {
svgRoot.style.pointerEvents = 'none'; pickerRoot.style.pointerEvents = '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 = ''; pickerRoot.style.pointerEvents = '';
return elem; return elem;
}; };
/******************************************************************************/ /******************************************************************************/
var onSvgHovered = function(ev) { var onSvgHovered = function(ev) {
if ( pickerPaused() || onSvgHoveredTimer) { var elem = elementFromPoint(ev.clientX, ev.clientY);
return; highlightElements(elem ? [elem] : []);
}
onSvgHoveredTimer = setTimeout(function() {
var elem = elementFromPoint(ev.clientX, ev.clientY);
highlightElements(elem ? [elem] : []);
onSvgHoveredTimer = null;
}, 50);
}; };
/******************************************************************************/ /******************************************************************************/
var onSvgClicked = function(ev) { var onSvgClicked = function(ev) {
if ( pickerPaused() ) {
return;
}
var elem = elementFromPoint(ev.clientX, ev.clientY); var elem = elementFromPoint(ev.clientX, ev.clientY);
if ( elem === null ) { if ( elem === null ) {
return; return;
@ -648,8 +631,17 @@ var onSvgClicked = function(ev) {
/******************************************************************************/ /******************************************************************************/
var svgListening = function(on) {
var svg = dialog.ownerDocument.body.querySelector('svg');
var action = (on ? 'add' : 'remove') + 'EventListener';
svg[action]('mousemove', onSvgHovered);
svg[action]('click', onSvgClicked);
};
/******************************************************************************/
var onKeyPressed = function(ev) { var onKeyPressed = function(ev) {
if ( ev.key === 27 || ev.keyCode === 27 ) { if ( ev.which === 27 ) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
stopPicker(); stopPicker();
@ -663,12 +655,6 @@ var onKeyPressed = function(ev) {
// of highlighted elements. // of highlighted elements.
var onScrolled = function() { var onScrolled = function() {
var newHeight = this.scrollY + this.innerHeight;
if ( newHeight > svgHeight ) {
svgHeight = newHeight;
svgRoot.style.height = svgHeight + 'px';
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
}
highlightElements(targetElements, true); highlightElements(targetElements, true);
}; };
@ -678,37 +664,40 @@ var onScrolled = function() {
// in use: to ensure this, release all local references. // in use: to ensure this, release all local references.
var stopPicker = function() { var stopPicker = function() {
if ( pickerRoot !== null ) {
window.removeEventListener('keydown', onKeyPressed, true);
window.removeEventListener('scroll', onScrolled, true);
taCandidate.removeEventListener('input', onCandidateChanged);
dialogBody.removeEventListener('click', onDialogClicked);
svgRoot.removeEventListener('mousemove', onSvgHovered);
svgRoot.removeEventListener('click', onSvgClicked);
pickerRoot.parentNode.removeChild(pickerRoot);
pickerRoot =
frameDialog =
dialogBody =
svgRoot = svgOcean = svgIslands =
taCandidate =
urlNormalizer = null;
localMessager.close();
}
targetElements = []; targetElements = [];
if ( pickerRoot === null ) {
return;
}
window.removeEventListener('scroll', onScrolled, true);
pickerRoot.contentWindow.removeEventListener('keydown', onKeyPressed, true);
taCandidate.removeEventListener('input', onCandidateChanged);
dialog.removeEventListener('click', onDialogClicked);
svgListening(false);
pickerRoot.parentNode.removeChild(pickerRoot);
pickerRoot.onload = null;
pickerRoot =
dialog =
svgOcean = svgIslands =
taCandidate =
urlNormalizer = null;
localMessager.close();
window.focus();
}; };
/******************************************************************************/ /******************************************************************************/
var onFrameReady = function() { var startPicker = function(details) {
var elem; var frameDoc = pickerRoot.contentDocument;
var details = lastDetails;
this.onload = lastDetails = null;
var parsedDom = (new DOMParser()).parseFromString( var parsedDom = (new DOMParser()).parseFromString(
details.frameContent, details.frameContent,
'text/html' 'text/html'
); );
var frameDoc = this.contentDocument;
pickerRoot.onload = stopPicker;
frameDoc.documentElement.replaceChild( frameDoc.documentElement.replaceChild(
frameDoc.adoptNode(parsedDom.head), frameDoc.adoptNode(parsedDom.head),
frameDoc.head frameDoc.head
@ -718,13 +707,31 @@ var onFrameReady = function() {
frameDoc.body frameDoc.body
); );
dialogBody = frameDoc.body; frameDoc.body.setAttribute('lang', navigator.language);
dialogBody.addEventListener('click', onDialogClicked);
taCandidate = dialogBody.querySelector('textarea'); dialog = frameDoc.body.querySelector('aside');
dialog.addEventListener('click', onDialogClicked);
taCandidate = dialog.querySelector('textarea');
taCandidate.addEventListener('input', onCandidateChanged); taCandidate.addEventListener('input', onCandidateChanged);
var svgRoot = frameDoc.body.querySelector('svg');
svgOcean = svgRoot.firstChild;
svgIslands = svgRoot.lastChild;
svgListening(true);
urlNormalizer = document.createElement('a');
window.addEventListener('scroll', onScrolled, true);
pickerRoot.contentWindow.addEventListener('keydown', onKeyPressed, true);
pickerRoot.contentWindow.focus();
// Auto-select a specific target, if any, and if possible // Auto-select a specific target, if any, and if possible
highlightElements([], true);
var elem;
// Try using mouse position // Try using mouse position
if ( details.clientX !== -1 ) { if ( details.clientX !== -1 ) {
elem = elementFromPoint(details.clientX, details.clientY); elem = elementFromPoint(details.clientX, details.clientY);
@ -774,129 +781,31 @@ var onFrameReady = function() {
/******************************************************************************/ /******************************************************************************/
var startPicker = function(details) { pickerRoot = document.createElement('iframe');
pickerRoot = document.createElement('div'); pickerRoot.id = vAPI.sessionId;
var pid = vAPI.sessionId; pickerRoot.style.cssText = [
pickerRoot.id = pid; 'position: fixed',
pickerRoot.setAttribute('lang', navigator.language); 'top: 0',
'left: 0',
'width: 100%',
'height: 100%',
'background: transparent',
'border: 0',
'border-radius: 0',
'box-shadow: none',
'float: none',
'margin: 0',
'outline: 0',
'padding: 0',
'z-index: 2147483647',
''
].join('!important; ');
var pickerStyle = document.createElement('style'); pickerRoot.onload = function() {
pickerStyle.setAttribute('scoped', ''); localMessager.send({ what: 'elementPickerArguments' }, startPicker);
pid = '#' + pid;
pickerStyle.textContent = [
pid + ' {',
'position: absolute;',
'top: 0;',
'left: 0;',
'}',
pid + ', ' + pid + ' > iframe, ' + pid + ' > svg {',
'background: transparent;',
'border: 0;',
'border-radius: 0;',
'box-shadow: none;',
'display: inline;',
'float: none;',
'height: auto;',
'margin: 0;',
'max-width: none;',
'min-height: 0;',
'min-width: 0;',
'outline: 0;',
'overflow: visible;',
'padding: 0;',
'width: auto;',
'z-index: auto;',
'}',
pid + ' > svg {',
'position: absolute;',
'top: 0;',
'left: 0;',
'pointer-events: auto;',
'cursor: crosshair;',
'z-index: 4999999999;',
'}',
pid + '.paused > svg {',
'cursor: wait;',
'}',
pid + ' > svg > path:first-child {',
'fill: rgba(0,0,0,0.75);',
'fill-rule: evenodd;',
'}',
pid + ' > svg > path + path {',
'stroke: #F00;',
'stroke-width: 0.5px;',
'fill: rgba(255,0,0,0.25);',
'}',
pid + ' > iframe {',
'background-color: rgba(255,255,255,0.9);',
'bottom: 4px;',
'display: none;',
'padding: 4px;',
'position: fixed;',
'right: 4px;',
'width: 30em;',
'z-index: 5999999999;',
'}',
pid + '.paused > iframe {',
'opacity: 0.2;',
'display: block;',
'}',
pid + '.paused > iframe:hover {',
'opacity: 1;',
'}',
''
].join('\n');
pickerRoot.appendChild(pickerStyle);
var svgns = 'http://www.w3.org/2000/svg';
svgRoot = document.createElementNS(svgns, 'svg');
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
svgWidth = document.documentElement.scrollWidth;
svgHeight = Math.max(
document.documentElement.scrollHeight,
window.scrollY + window.innerHeight
);
svgRoot.setAttribute('x', 0);
svgRoot.setAttribute('y', 0);
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);
// https://github.com/gorhill/uBlock/issues/344#issuecomment-60775958
// Insert in `html` tag, not `body` tag.
document.documentElement.appendChild(pickerRoot);
svgRoot.addEventListener('click', onSvgClicked);
svgRoot.addEventListener('mousemove', onSvgHovered);
urlNormalizer = document.createElement('a');
window.addEventListener('scroll', onScrolled, true);
window.addEventListener('keydown', onKeyPressed, true);
highlightElements([], true);
lastDetails = {
frameContent: details.frameContent,
clientX: details.clientX,
clientY: details.clientY,
target: details.target
};
frameDialog = document.createElement('iframe');
frameDialog.setAttribute('seamless', '');
frameDialog.onload = onFrameReady;
pickerRoot.appendChild(frameDialog);
}; };
/******************************************************************************/ document.documentElement.appendChild(pickerRoot);
localMessager.send({ what: 'elementPickerArguments' }, startPicker);
// So the shortcuts will be usable in Firefox
// (also triggers the hiding of the popover in Safari)
window.focus();
/******************************************************************************/ /******************************************************************************/