vAPI.sessionId, element-picker dialog as iframe

vAPI.sessionId - random ID generated every time when a page loads.

Having the dialog in an iframe lowers the chance of interference with the
styling of the page, also avoids using innerHTML (AMO complaint).
This commit is contained in:
Deathamns 2015-02-08 19:34:28 +01:00
parent ff82b32853
commit 8693ab738d
6 changed files with 327 additions and 338 deletions

View File

@ -25,14 +25,13 @@
/******************************************************************************/ /******************************************************************************/
(function() { (function(self) {
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
var vAPI = self.vAPI = self.vAPI || {}; var vAPI = self.vAPI = self.vAPI || {};
var chrome = self.chrome; var chrome = self.chrome;
// https://github.com/gorhill/uBlock/issues/456 // https://github.com/gorhill/uBlock/issues/456
@ -40,8 +39,10 @@ var chrome = self.chrome;
if ( vAPI.vapiClientInjected ) { if ( vAPI.vapiClientInjected ) {
return; return;
} }
vAPI.vapiClientInjected = true;
vAPI.vapiClientInjected = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
Math.random().toString(36).slice(2);
vAPI.chrome = true; vAPI.chrome = true;
/******************************************************************************/ /******************************************************************************/
@ -85,21 +86,14 @@ var messagingConnector = function(response) {
/******************************************************************************/ /******************************************************************************/
var uniqueId = function() {
return Math.random().toString(36).slice(2);
};
/******************************************************************************/
vAPI.messaging = { vAPI.messaging = {
port: null, port: null,
channels: {}, channels: {},
listeners: {}, listeners: {},
requestId: 1, requestId: 1,
connectorId: uniqueId(),
setup: function() { setup: function() {
this.port = chrome.runtime.connect({name: this.connectorId}); this.port = chrome.runtime.connect({name: vAPI.sessionId});
this.port.onMessage.addListener(messagingConnector); this.port.onMessage.addListener(messagingConnector);
}, },
@ -150,6 +144,6 @@ vAPI.messaging = {
/******************************************************************************/ /******************************************************************************/
})(); })(this);
/******************************************************************************/ /******************************************************************************/

View File

@ -31,8 +31,10 @@
/******************************************************************************/ /******************************************************************************/
self.vAPI = self.vAPI || {}; var vAPI = self.vAPI = self.vAPI || {};
vAPI.firefox = true; vAPI.firefox = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
Math.random().toString(36).slice(2);
/******************************************************************************/ /******************************************************************************/

View File

@ -21,7 +21,7 @@
/******************************************************************************/ /******************************************************************************/
// For non background pages // For non background pages
(function() { (function(self) {
'use strict'; 'use strict';
var vAPI = self.vAPI = self.vAPI || {}; var vAPI = self.vAPI = self.vAPI || {};
if(vAPI.vapiClientInjected) { if(vAPI.vapiClientInjected) {
@ -29,7 +29,8 @@
} }
vAPI.vapiClientInjected = true; vAPI.vapiClientInjected = true;
vAPI.safari = true; vAPI.safari = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
Math.random().toString(36).slice(2);
/******************************************************************************/ /******************************************************************************/
var messagingConnector = function(response) { var messagingConnector = function(response) {
if(!response) { if(!response) {
@ -63,26 +64,21 @@
} }
}; };
/******************************************************************************/ /******************************************************************************/
var uniqueId = function() {
return Math.random().toString(36).slice(2);
};
/******************************************************************************/
// 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 = {
channels: {}, channels: {},
listeners: {}, listeners: {},
requestId: 1, requestId: 1,
connectorId: uniqueId(),
setup: function() { setup: function() {
if(typeof safari === "undefined") { if(typeof safari === "undefined") {
return; return;
} }
this.connector = function(msg) { this.connector = function(msg) {
// messages from the background script are sent to every frame, // messages from the background script are sent to every frame,
// so we need to check the connectorId to accept only // so we need to check the vAPI.sessionId to accept only
// what is meant for the current context // what is meant for the current context
if(msg.name === vAPI.messaging.connectorId || msg.name === 'broadcast') { if(msg.name === vAPI.sessionId || msg.name === 'broadcast') {
messagingConnector(msg.message); messagingConnector(msg.message);
} }
}; };
@ -131,7 +127,7 @@
return; return;
} }
safari.extension.globalPage.contentWindow.vAPI.messaging.onMessage({ safari.extension.globalPage.contentWindow.vAPI.messaging.onMessage({
name: vAPI.messaging.connectorId, name: vAPI.sessionId,
message: message, message: message,
target: { target: {
page: { page: {
@ -142,7 +138,7 @@
} }
}); });
} else { } else {
safari.self.tab.dispatchMessage(vAPI.messaging.connectorId, message); safari.self.tab.dispatchMessage(vAPI.sessionId, message);
} }
}, },
close: function() { close: function() {
@ -215,8 +211,7 @@
var firstMutation = function() { var firstMutation = function() {
document.removeEventListener("DOMContentLoaded", firstMutation, true); document.removeEventListener("DOMContentLoaded", firstMutation, true);
firstMutation = false; firstMutation = false;
var randEventName = uniqueId(); document.addEventListener(vAPI.sessionId, function(e) {
document.addEventListener(randEventName, function(e) {
if(shouldBlockDetailedRequest(e.detail)) { if(shouldBlockDetailedRequest(e.detail)) {
e.detail.url = false; e.detail.url = false;
} }
@ -225,7 +220,7 @@
var tmpScript = "\ var tmpScript = "\
(function() {\ (function() {\
var block = function(u, t) {\ var block = function(u, t) {\
var e = new CustomEvent('" + randEventName + "', {\ var e = new CustomEvent('" + vAPI.sessionId + "', {\
detail: {\ detail: {\
url: u,\ url: u,\
type: t\ type: t\
@ -298,5 +293,5 @@ return r;\
safari.self.tab.setContextMenuEventUserInfo(e, details); safari.self.tab.setContextMenuEventUserInfo(e, details);
}; };
self.addEventListener("contextmenu", onContextMenu, true); self.addEventListener("contextmenu", onContextMenu, true);
})(); })(this);
/******************************************************************************/ /******************************************************************************/

120
src/epicker.html Normal file
View File

@ -0,0 +1,120 @@
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
width: 100%;
overflow: hidden;
display: inline-block;
font: 12px sans-serif;
}
ul, li, div {
display: block;
}
button {
border: 1px solid #aaa;
padding: 6px 8px 4px 8px;
box-sizing: border-box;
box-shadow: none;
border-radius: 3px;
display: inline;
line-height: 1;
color: #444;
background-color: #ccc;
cursor: pointer;
}
button:hover {
background-color: #eee;
}
button:disabled {
color: #999;
background-color: #ccc;
}
#create:not(:disabled) {
background-color: #ffdca8;
}
section {
box-sizing: border-box;
display: inline-block;
height: 8em;
position: relative;
width: 100%;
}
textarea {
background-color: #fff;
border: 1px solid #ccc;
box-sizing: border-box;
font: 11px monospace;
height: 100%;
overflow: hidden;
padding: 2px;
resize: none;
width: 100%;
}
div {
bottom: 2px;
direction: ltr;
opacity: 0.2;
position: absolute;
right: 2px;
}
div:hover {
opacity: 1;
}
button {
margin-left: 3px;
}
ul {
padding: 0;
list-style-type: none;
text-align: left;
overflow: hidden;
}
body > ul > li {
padding-top: 3px;
}
ul > li > span:nth-of-type(1) {
font-weight: bold;
}
ul > li > span:nth-of-type(2) {
font-size: smaller;
color: gray;
}
ul > li > ul {
background-color: #eee;
list-style-type: none;
margin: 0 0 0 1em;
overflow: hidden;
text-align: left;
}
ul > li > ul > li {
font: 11px monospace;
white-space: nowrap;
cursor: pointer;
direction: ltr;
}
ul > li > ul > li:hover {
background-color: rgba(255,255,255,1.0);
}
</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>
</body>

View File

@ -125,12 +125,7 @@ if ( window.top !== window ) {
return; return;
} }
// https://github.com/gorhill/uBlock/issues/314#issuecomment-58878112 var pickerRoot = document.getElementById(vAPI.sessionId);
// Using an id makes uBlock's CSS rules more specific, thus prevents
// surrounding external rules from winning over own rules.
var µBlockId = CSS.escape('µBlock');
var pickerRoot = document.getElementById(µBlockId);
if ( pickerRoot ) { if ( pickerRoot ) {
return; return;
@ -138,22 +133,22 @@ if ( pickerRoot ) {
var localMessager = vAPI.messaging.channel('element-picker.js'); var localMessager = vAPI.messaging.channel('element-picker.js');
var svgns = 'http://www.w3.org/2000/svg';
var svgRoot = null; var svgRoot = null;
var svgOcean = null; var svgOcean = null;
var svgIslands = null; var svgIslands = null;
var divDialog = null; var frameDialog = 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 svgWidth = 0;
var svgHeight = 0; var svgHeight = 0;
var elementFromPointCSSProperty = 'pointerEvents';
var onSvgHoveredTimer = null; var onSvgHoveredTimer = null;
/******************************************************************************/ /******************************************************************************/
@ -189,20 +184,6 @@ var unpausePicker = function() {
/******************************************************************************/ /******************************************************************************/
var pickerRootDistance = function(elem) {
var distance = 0;
while ( elem ) {
if ( elem === pickerRoot ) {
return distance;
}
elem = elem.parentNode;
distance += 1;
}
return -1;
};
/******************************************************************************/
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 ) {
@ -479,7 +460,7 @@ var userFilterFromCandidate = function() {
var onCandidateChanged = function() { var onCandidateChanged = function() {
var elems = elementsFromFilter(taCandidate.value); var elems = elementsFromFilter(taCandidate.value);
divDialog.querySelector('#create').disabled = elems.length === 0; dialogBody.querySelector('#create').disabled = elems.length === 0;
highlightElements(elems); highlightElements(elems);
}; };
@ -554,7 +535,7 @@ var onDialogClicked = function(ev) {
stopPicker(); stopPicker();
} }
else if ( ev.target.tagName.toLowerCase() === 'li' && pickerRootDistance(ev.target) === 5 ) { else if ( ev.target.parentNode.classList.contains('changeFilter') ) {
taCandidate.value = candidateFromFilterChoice(filterChoiceFromEvent(ev)); taCandidate.value = candidateFromFilterChoice(filterChoiceFromEvent(ev));
onCandidateChanged(); onCandidateChanged();
} }
@ -583,7 +564,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 = divDialog.querySelector(des); var root = dialogBody.querySelector(des);
var ul = root.querySelector('ul'); var ul = root.querySelector('ul');
removeAllChildren(ul); removeAllChildren(ul);
var li; var li;
@ -598,8 +579,8 @@ var showDialog = function(options) {
populate(netFilterCandidates, '#netFilters'); populate(netFilterCandidates, '#netFilters');
populate(cosmeticFilterCandidates, '#cosmeticFilters'); populate(cosmeticFilterCandidates, '#cosmeticFilters');
divDialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none'; dialogBody.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
divDialog.querySelector('#create').disabled = true; dialogBody.querySelector('#create').disabled = true;
// Auto-select a candidate filter // Auto-select a candidate filter
var filterChoice = { var filterChoice = {
@ -621,17 +602,19 @@ 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[elementFromPointCSSProperty] = 'none'; svgRoot.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[elementFromPointCSSProperty] = ''; svgRoot.style.pointerEvents = '';
return elem; return elem;
}; };
@ -699,12 +682,13 @@ var stopPicker = function() {
window.removeEventListener('keydown', onKeyPressed, true); window.removeEventListener('keydown', onKeyPressed, true);
window.removeEventListener('scroll', onScrolled, true); window.removeEventListener('scroll', onScrolled, true);
taCandidate.removeEventListener('input', onCandidateChanged); taCandidate.removeEventListener('input', onCandidateChanged);
divDialog.removeEventListener('click', onDialogClicked); dialogBody.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 = frameDialog =
dialogBody =
svgRoot = svgOcean = svgIslands = svgRoot = svgOcean = svgIslands =
taCandidate = taCandidate =
urlNormalizer = null; urlNormalizer = null;
@ -715,267 +699,29 @@ var stopPicker = function() {
/******************************************************************************/ /******************************************************************************/
var startPicker = function(details) { var onFrameReady = function() {
pickerRoot = document.createElement('div'); var elem;
pickerRoot.id = µBlockId; var details = lastDetails;
pickerRoot.setAttribute('lang', navigator.language); this.onload = lastDetails = null;
var pickerStyle = document.createElement('style'); var parsedDom = (new DOMParser()).parseFromString(
pickerStyle.setAttribute('scoped', ''); details.frameContent,
pickerStyle.textContent = [ 'text/html'
'#µBlock, #µBlock * {', );
'background: transparent;', var frameDoc = this.contentDocument;
'background-image: none;', frameDoc.documentElement.replaceChild(
'border: 0;', frameDoc.adoptNode(parsedDom.head),
'border-radius: 0;', frameDoc.head
'box-shadow: none;', );
'color: #000;', frameDoc.documentElement.replaceChild(
'display: inline;', frameDoc.adoptNode(parsedDom.body),
'float: none;', frameDoc.body
'font: 12px sans-serif;',
'height: auto;',
'letter-spacing: normal;',
'margin: 0;',
'max-width: none;',
'min-height: 0;',
'min-width: 0;',
'outline: 0;',
'overflow: visible;',
'padding: 0;',
'text-transform: none;',
'vertical-align: baseline;',
'width: auto;',
'z-index: auto;',
'}',
'#µBlock {',
'position: absolute;',
'top: 0;',
'left: 0;',
'}',
'#µBlock style, #µBlock script {',
'display: none;',
'}',
'#µBlock ul, #µBlock li, #µBlock div {',
'display: block;',
'}',
'#µBlock *::selection {',
'background-color: Highlight;',
'color: HighlightText;',
'}',
'#µBlock button {',
'border: 1px solid #aaa !important;',
'padding: 6px 8px 4px 8px;',
'box-sizing: border-box;',
'box-shadow: none;',
'border-radius: 3px;',
'display: inline;',
'line-height: 1;',
'color: #444;',
'background-color: #ccc;',
'cursor: pointer;',
'}',
'#µBlock button:hover {',
'background: none;',
'background-color: #eee;',
'background-image: none;',
'}',
'#µBlock button:disabled {',
'color: #999;',
'background-color: #ccc;',
'}',
'#µBlock button#create:not(:disabled) {',
'background-color: #ffdca8;',
'}',
'#µBlock > svg {',
'position: absolute;',
'top: 0;',
'left: 0;',
'pointer-events: auto;',
'cursor: crosshair;',
'z-index: 4999999999;',
'}',
'#µBlock.paused > svg {',
'cursor: wait;',
'}',
'#µBlock > svg > path:first-child {',
'fill: rgba(0,0,0,0.75);',
'fill-rule: evenodd;',
'}',
'#µBlock > svg > path + path {',
'stroke: #F00;',
'stroke-width: 0.5px;',
'fill: rgba(255,0,0,0.25);',
'}',
'#µBlock > div {',
'background-color: rgba(255,255,255,0.9);',
'bottom: 4px;',
'display: none;',
'font: 12px sans-serif;',
'padding: 4px;',
'position: fixed;',
'right: 4px;',
'width: 30em;',
'z-index: 5999999999;',
'}',
'#µBlock.paused > div {',
'opacity: 0.2;',
'display: block;',
'}',
'#µBlock.paused > div:hover {',
'opacity: 1;',
'}',
'#µBlock > div > div {',
'box-sizing: border-box;',
'display: inline-block;',
'height: 8em;',
'padding: 0;',
'position: relative;',
'width: 100%;',
'}',
'#µBlock > div > div > textarea {',
'background-color: white;',
'border: 1px solid #ccc;',
'box-sizing: border-box;',
'font: 11px monospace;',
'height: 100% !important;',
'max-height: 100% !important;',
'min-height: 100% !important;',
'overflow: hidden !important;',
'padding: 2px;',
'resize: none;',
'width: 100% !important;',
'}',
'#µBlock > div > div > div {',
'bottom: 2px;',
'direction: ltr;',
'opacity: 0.2;',
'position: absolute;',
'right: 2px;',
'}',
'#µBlock > div > div > div:hover {',
'opacity: 1;',
'}',
'#µBlock > div > div > div > button {',
'margin-left: 3px !important;',
'}',
'#µBlock > div > ul {',
'margin: 0;',
'list-style-type: none;',
'text-align: left;',
'overflow: hidden;',
'}',
'#µBlock > div > ul > li {',
'padding-top: 3px;',
'}',
'#µBlock > div > ul > li > span:nth-of-type(1) {',
'font-weight: bold;',
'}',
'#µBlock > div > ul > li > span:nth-of-type(2) {',
'font-size: smaller;',
'color: gray;',
'}',
'#µBlock > div > ul > li > ul {',
'background-color: #eee;',
'list-style-type: none;',
'margin: 0 0 0 1em;',
'overflow: hidden;',
'text-align: left;',
'}',
'#µBlock > div > ul > li > ul > li {',
'font: 11px monospace;',
'white-space: nowrap;',
'cursor: pointer;',
'direction: ltr;',
'}',
'#µBlock > div > ul > li > ul > li:hover {',
'background-color: rgba(255,255,255,1.0);',
'}',
''
].join('\n');
pickerRoot.appendChild(pickerStyle);
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);
// TODO: do not rely on element ids, they could collide with whatever dialogBody = frameDoc.body;
// is used in the page. Just use built-in hierarchy of elements as dialogBody.addEventListener('click', onDialogClicked);
// selectors. taCandidate = dialogBody.querySelector('textarea');
divDialog = document.createElement('div');
divDialog.innerHTML = [
'<div>',
'<textarea lang="en" dir="ltr" spellcheck="false"></textarea>',
'<div>',
'<button id="create" type="button" disabled="disabled">.</button>',
'<button id="pick" type="button">.</button>',
'<button id="quit" type="button">.</button>',
'</div>',
'</div>',
'<ul>',
'<li id="netFilters"><span>.</span><ul lang="en"></ul></li>',
'<li id="cosmeticFilters"><span>.</span> <span>.</span><ul lang="en"></ul></li>',
'</ul>'
].join('');
pickerRoot.appendChild(divDialog);
// 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);
divDialog.addEventListener('click', onDialogClicked);
taCandidate = divDialog.querySelector('textarea');
taCandidate.addEventListener('input', onCandidateChanged); taCandidate.addEventListener('input', onCandidateChanged);
urlNormalizer = document.createElement('a');
window.addEventListener('scroll', onScrolled, true);
window.addEventListener('keydown', onKeyPressed, true);
highlightElements([], true);
var i18nMap = {
'#µBlock > div': '@@bidi_dir',
'#create': 'create',
'#pick': 'pick',
'#quit': 'quit',
'ul > li#netFilters > span:nth-of-type(1)': 'netFilters',
'ul > li#cosmeticFilters > span:nth-of-type(1)': 'cosmeticFilters',
'ul > li#cosmeticFilters > span:nth-of-type(2)': 'cosmeticFiltersHint'
};
if ( details.i18n['@@bidi_dir'] ) {
divDialog.style.direction = details.i18n['@@bidi_dir'];
delete i18nMap['#µBlock > div'];
}
for ( var k in i18nMap ) {
if ( i18nMap.hasOwnProperty(k) === false ) {
continue;
}
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
@ -1028,6 +774,124 @@ var startPicker = function(details) {
/******************************************************************************/ /******************************************************************************/
var startPicker = function(details) {
pickerRoot = document.createElement('div');
var pid = vAPI.sessionId;
pickerRoot.id = pid;
pickerRoot.setAttribute('lang', navigator.language);
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);
};
/******************************************************************************/
localMessager.send({ what: 'elementPickerArguments' }, startPicker); localMessager.send({ what: 'elementPickerArguments' }, startPicker);
// So the shortcuts will be usable in Firefox // So the shortcuts will be usable in Firefox

View File

@ -527,6 +527,40 @@ var µb = µBlock;
var onMessage = function(request, sender, callback) { var onMessage = function(request, sender, callback) {
// Async // Async
switch ( request.what ) { switch ( request.what ) {
case 'elementPickerArguments':
var xhr = new XMLHttpRequest();
xhr.open('GET', 'epicker.html', true);
xhr.overrideMimeType('text/html;charset=utf-8');
xhr.responseType = 'text';
xhr.onload = function() {
this.onload = null;
var i18n = {
bidi_dir: document.body.getAttribute('dir'),
create: vAPI.i18n('pickerCreate'),
pick: vAPI.i18n('pickerPick'),
quit: vAPI.i18n('pickerQuit'),
netFilters: vAPI.i18n('pickerNetFilters'),
cosmeticFilters: vAPI.i18n('pickerCosmeticFilters'),
cosmeticFiltersHint: vAPI.i18n('pickerCosmeticFiltersHint')
};
var reStrings = /\{\{(\w+)\}\}/g;
var replacer = function(a0, string) {
return i18n[string];
};
callback({
frameContent: this.responseText.replace(reStrings, replacer),
target: µb.contextMenuTarget,
clientX: µb.contextMenuClientX,
clientY: µb.contextMenuClientY
});
µb.contextMenuTarget = '';
µb.contextMenuClientX = -1;
µb.contextMenuClientY = -1;
};
xhr.send();
return;
default: default:
break; break;
} }
@ -535,26 +569,6 @@ var onMessage = function(request, sender, callback) {
var response; var response;
switch ( request.what ) { switch ( request.what ) {
case 'elementPickerArguments':
response = {
i18n: {
'@@bidi_dir': document.body.getAttribute('dir'),
create: vAPI.i18n('pickerCreate'),
pick: vAPI.i18n('pickerPick'),
quit: vAPI.i18n('pickerQuit'),
netFilters: vAPI.i18n('pickerNetFilters'),
cosmeticFilters: vAPI.i18n('pickerCosmeticFilters'),
cosmeticFiltersHint: vAPI.i18n('pickerCosmeticFiltersHint')
},
target: µb.contextMenuTarget,
clientX: µb.contextMenuClientX,
clientY: µb.contextMenuClientY
};
µb.contextMenuTarget = '';
µb.contextMenuClientX = -1;
µb.contextMenuClientY = -1;
break;
case 'createUserFilter': case 'createUserFilter':
µb.appendUserFilters(request.filters); µb.appendUserFilters(request.filters);
break; break;