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>
<meta charset="utf-8">
<style>
body {
:focus {
outline: none;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: inline-block;
font: 12px sans-serif;
}
ul, li, div {
@ -13,6 +16,7 @@ ul, li, div {
}
button {
border: 1px solid #aaa;
margin-left: 3px;
padding: 6px 8px 4px 8px;
box-sizing: border-box;
box-shadow: none;
@ -61,9 +65,6 @@ div {
div:hover {
opacity: 1;
}
button {
margin-left: 3px;
}
ul {
padding: 0;
list-style-type: none;
@ -94,27 +95,66 @@ ul > li > ul > li {
direction: ltr;
}
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>
</head>
<body direction="{{bidi_dir}}">
<section>
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
<div>
<button id="create" type="button" disabled="disabled">{{create}}</button>
<button id="pick" type="button">{{pick}}</button>
<button id="quit" type="button">{{quit}}</button>
</div>
</section>
<ul>
<li id="netFilters">
<span>{{netFilters}}</span><ul lang="en" class="changeFilter"></ul>
</li>
<li id="cosmeticFilters">
<span>{{cosmeticFilters}}</span> <span>{{cosmeticFiltersHint}}</span>
<ul lang="en" class="changeFilter"></ul>
</li>
</ul>
<svg><path></path><path></path></svg>
<aside>
<section>
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
<div>
<button id="create" type="button" disabled="disabled">{{create}}</button>
<button id="pick" type="button">{{pick}}</button>
<button id="quit" type="button">{{quit}}</button>
</div>
</section>
<ul>
<li id="netFilters">
<span>{{netFilters}}</span><ul lang="en" class="changeFilter"></ul>
</li>
<li id="cosmeticFilters">
<span>{{cosmeticFilters}}</span> <span>{{cosmeticFiltersHint}}</span>
<ul lang="en" class="changeFilter"></ul>
</li>
</ul>
</aside>
</body>

View File

@ -133,23 +133,16 @@ if ( pickerRoot ) {
var localMessager = vAPI.messaging.channel('element-picker.js');
var svgRoot = null;
var svgOcean = null;
var svgIslands = null;
var frameDialog = null;
var dialogBody = null;
var dialog = null;
var taCandidate = null;
var urlNormalizer = null;
var lastDetails = null;
var netFilterCandidates = [];
var cosmeticFilterCandidates = [];
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() {
pickerRoot.classList.add('paused');
dialog.parentNode.classList.add('paused');
svgListening(false);
};
/******************************************************************************/
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;
var ow = parseInt(svgRoot.style.width, 10);
var ow = pickerRoot.contentWindow.innerWidth;
var oh = pickerRoot.contentWindow.innerHeight;
var ocean = [
'M0 0',
'h', ow,
'v', parseInt(svgRoot.style.height, 10),
'v', oh,
'h-', ow,
'z'
];
var offx = window.pageXOffset;
var offy = window.pageYOffset;
var islands = [];
var elem, rect, poly;
@ -212,7 +200,14 @@ var highlightElements = function(elems, force) {
continue;
}
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 +
'v' + rect.height +
'h-' + rect.width +
@ -460,7 +455,7 @@ var userFilterFromCandidate = function() {
var onCandidateChanged = function() {
var elems = elementsFromFilter(taCandidate.value);
dialogBody.querySelector('#create').disabled = elems.length === 0;
dialog.querySelector('#create').disabled = elems.length === 0;
highlightElements(elems);
};
@ -564,7 +559,7 @@ var showDialog = function(options) {
// Create lists of candidate filters
var populate = function(src, des) {
var root = dialogBody.querySelector(des);
var root = dialog.querySelector(des);
var ul = root.querySelector('ul');
removeAllChildren(ul);
var li;
@ -579,8 +574,8 @@ var showDialog = function(options) {
populate(netFilterCandidates, '#netFilters');
populate(cosmeticFilterCandidates, '#cosmeticFilters');
dialogBody.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
dialogBody.querySelector('#create').disabled = true;
dialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
dialog.querySelector('#create').disabled = true;
// Auto-select a candidate filter
var filterChoice = {
@ -602,42 +597,30 @@ var showDialog = function(options) {
taCandidate.value = candidateFromFilterChoice(filterChoice);
onCandidateChanged();
}
frameDialog.style.height = dialogBody.offsetHeight + 'px';
};
/******************************************************************************/
var elementFromPoint = function(x, y) {
svgRoot.style.pointerEvents = 'none';
pickerRoot.style.pointerEvents = 'none';
var elem = document.elementFromPoint(x, y);
if ( elem === document.body || elem === document.documentElement ) {
elem = null;
}
svgRoot.style.pointerEvents = '';
pickerRoot.style.pointerEvents = '';
return elem;
};
/******************************************************************************/
var onSvgHovered = function(ev) {
if ( pickerPaused() || onSvgHoveredTimer) {
return;
}
onSvgHoveredTimer = setTimeout(function() {
var elem = elementFromPoint(ev.clientX, ev.clientY);
highlightElements(elem ? [elem] : []);
onSvgHoveredTimer = null;
}, 50);
var elem = elementFromPoint(ev.clientX, ev.clientY);
highlightElements(elem ? [elem] : []);
};
/******************************************************************************/
var onSvgClicked = function(ev) {
if ( pickerPaused() ) {
return;
}
var elem = elementFromPoint(ev.clientX, ev.clientY);
if ( elem === null ) {
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) {
if ( ev.key === 27 || ev.keyCode === 27 ) {
if ( ev.which === 27 ) {
ev.stopPropagation();
ev.preventDefault();
stopPicker();
@ -663,12 +655,6 @@ var onKeyPressed = function(ev) {
// of highlighted elements.
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);
};
@ -678,37 +664,40 @@ var onScrolled = function() {
// in use: to ensure this, release all local references.
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 = [];
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 elem;
var details = lastDetails;
this.onload = lastDetails = null;
var startPicker = function(details) {
var frameDoc = pickerRoot.contentDocument;
var parsedDom = (new DOMParser()).parseFromString(
details.frameContent,
'text/html'
);
var frameDoc = this.contentDocument;
pickerRoot.onload = stopPicker;
frameDoc.documentElement.replaceChild(
frameDoc.adoptNode(parsedDom.head),
frameDoc.head
@ -718,13 +707,31 @@ var onFrameReady = function() {
frameDoc.body
);
dialogBody = frameDoc.body;
dialogBody.addEventListener('click', onDialogClicked);
taCandidate = dialogBody.querySelector('textarea');
frameDoc.body.setAttribute('lang', navigator.language);
dialog = frameDoc.body.querySelector('aside');
dialog.addEventListener('click', onDialogClicked);
taCandidate = dialog.querySelector('textarea');
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
highlightElements([], true);
var elem;
// Try using mouse position
if ( details.clientX !== -1 ) {
elem = elementFromPoint(details.clientX, details.clientY);
@ -774,129 +781,31 @@ var onFrameReady = function() {
/******************************************************************************/
var startPicker = function(details) {
pickerRoot = document.createElement('div');
var pid = vAPI.sessionId;
pickerRoot.id = pid;
pickerRoot.setAttribute('lang', navigator.language);
pickerRoot = document.createElement('iframe');
pickerRoot.id = vAPI.sessionId;
pickerRoot.style.cssText = [
'position: fixed',
'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');
pickerStyle.setAttribute('scoped', '');
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);
pickerRoot.onload = function() {
localMessager.send({ what: 'elementPickerArguments' }, startPicker);
};
/******************************************************************************/
localMessager.send({ what: 'elementPickerArguments' }, startPicker);
// So the shortcuts will be usable in Firefox
// (also triggers the hiding of the popover in Safari)
window.focus();
document.documentElement.appendChild(pickerRoot);
/******************************************************************************/