finally a fully working draft: now onto myriad finishing touches

This commit is contained in:
gorhill 2014-12-31 10:47:19 -05:00
parent a953cd5c0c
commit 43e773aab2
6 changed files with 213 additions and 116 deletions

View File

@ -8,13 +8,13 @@ body {
white-space: nowrap; white-space: nowrap;
} }
h1,h2,h3,h4 { h1,h2,h3,h4 {
margin: 0; background-color: #444;
padding: 4px;
border: 0; border: 0;
color: white; color: white;
background-color: #444;
text-align: center;
cursor: pointer; cursor: pointer;
margin: 0;
padding: 4px;
text-align: center;
} }
a { a {
color: inherit; color: inherit;
@ -24,9 +24,9 @@ a {
outline: 0; outline: 0;
} }
#version { #version {
margin-left: 1em;
font-weight: normal;
font-size: 11px; font-size: 11px;
font-weight: normal;
margin-left: 1em;
} }
body > div { body > div {
background-color: transparent; background-color: transparent;
@ -47,14 +47,14 @@ body > div:nth-of-type(2) {
} }
p { p {
margin: 16px 0; margin: 16px 0;
white-space: nowrap;
text-align: center; text-align: center;
white-space: nowrap;
} }
#switch .fa { #switch .fa {
margin: 0;
font-size: 96px;
color: green; color: green;
cursor: pointer; cursor: pointer;
font-size: 96px;
margin: 0;
} }
#switch .fa:hover { #switch .fa:hover {
opacity: 0.9; opacity: 0.9;
@ -63,22 +63,22 @@ p {
color: #ccc; color: #ccc;
} }
#switch-hint { #switch-hint {
font-size: 11px;
color: #888; color: #888;
font-size: 11px;
} }
[data-i18n="popupBlockedRequestPrompt"] { [data-i18n="popupBlockedRequestPrompt"] {
font-size: 16px; font-size: 16px;
} }
#page-blocked { #page-blocked {
margin-top: 4px;
font-size: 20px; font-size: 20px;
margin-top: 4px;
} }
#total-blocked { #total-blocked {
margin-top: 4px;
margin-bottom: 8px;
font-size: 14px; font-size: 14px;
margin-bottom: 8px;
margin-top: 4px;
} }
#stats { .stats {
margin-bottom: 4px; margin-bottom: 4px;
text-align: center; text-align: center;
} }
@ -142,11 +142,11 @@ body.dynamicFilteringEnabled #dynamicFilteringToggler::before {
margin: 0; margin: 0;
padding: 0; padding: 0;
text-align: right; text-align: right;
width: 7px; width: 5px;
} }
body.dynamicFilteringEnabled #dynamicFilteringContainer { body.dynamicFilteringEnabled #dynamicFilteringContainer {
display: block; display: block;
width: 280px; width: auto;
} }
#dynamicFilteringContainer > div { #dynamicFilteringContainer > div {
background-color: transparent; background-color: transparent;
@ -154,7 +154,7 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer {
direction: ltr; direction: ltr;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 280px; width: 320px;
} }
body.dynamicFilteringEnabled #dynamicFilteringContainer > div { body.dynamicFilteringEnabled #dynamicFilteringContainer > div {
background-color: #e6e6e6; background-color: #e6e6e6;
@ -176,8 +176,9 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover {
color: transparent; color: transparent;
display: inline-block; display: inline-block;
height: 24px; height: 24px;
line-height: 28px; line-height: 24px;
pointer-events: none; pointer-events: none;
position: relative;
vertical-align: top; vertical-align: top;
} }
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span { body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
@ -188,33 +189,74 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
#dynamicFilteringContainer > div > span:nth-of-type(1) { #dynamicFilteringContainer > div > span:nth-of-type(1) {
border-right: 1px solid white; border-right: 1px solid white;
padding-right: 4px; padding-right: 4px;
width: 75%; width: 70%;
} }
#dynamicFilteringContainer > div > span:nth-of-type(2) { #dynamicFilteringContainer > div > span:nth-of-type(2) {
cursor: pointer; cursor: pointer;
width: 9%; width: 15%;
} }
#dynamicFilteringContainer > div > span:nth-of-type(3) { #dynamicFilteringContainer > div > span:nth-of-type(3) {
border-left: 1px solid white; border-left: 1px solid white;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
width: 16%; width: 15%;
} }
#dynamicFilteringContainer > div.isDomain > span:nth-of-type(1) { #dynamicFilteringContainer > div.isDomain > span:nth-of-type(1) {
font-weight: bold; font-weight: bold;
} }
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span:nth-of-type(3) { body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span:nth-of-type(3) {
color: #444; color: #666;
pointer-events: auto; pointer-events: auto;
} }
#dynamicFilteringContainer span.blocked[data-src] { #dynamicFilteringContainer span.aRule {
background-color: #fbb; background-color: rgba(0, 160, 0, 0.3);
} }
#dynamicFilteringContainer span.ownFilter[data-src] { #dynamicFilteringContainer span.bRule {
background-color: #bbb; background-color: rgba(192, 0, 0, 0.3);
}
#dynamicFilteringContainer span.nRule {
background-color: rgba(96, 96, 96, 0.3);
}
#dynamicFilteringContainer span.aRule.ownRule {
background-color: rgba(0, 160, 0, 1);
color: white; color: white;
} }
#dynamicFilteringContainer span.blocked.ownFilter[data-src] { #dynamicFilteringContainer span.bRule.ownRule {
background-color: #f66; background-color: rgba(192, 0, 0, 1);
color: white; color: white;
} }
#dynamicFilteringContainer span.nRule.ownRule {
background-color: rgba(108, 108, 108, 1);
color: white;
}
#actionSelector {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
#actionSelector > span {
display: inline-block;
height: 24px;
opacity: 0.2;
width: 33.33%;
}
#actionSelector > span:hover {
opacity: 0.75;
}
#actionSelector > span:nth-of-type(1) {
background-color: rgb(0, 160, 0);
}
#actionSelector > span:nth-of-type(2) {
background-color: rgb(108, 108, 108);
}
#actionSelector > span:nth-of-type(3) {
background-color: rgb(192, 0, 0);
}
#dynamicFilteringContainer span.aRule #actionSelector > span:nth-of-type(1),
#dynamicFilteringContainer span.nRule #actionSelector > span:nth-of-type(2),
#dynamicFilteringContainer span.bRule #actionSelector > span:nth-of-type(3) {
visibility: hidden;
}

View File

@ -130,52 +130,43 @@ Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
/******************************************************************************/ /******************************************************************************/
Matrix.prototype.blockCell = function(srcHostname, desHostname, type) { Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type); this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 1 ) { if ( this.r === 0 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
return true;
};
/******************************************************************************/
Matrix.prototype.setCellZ = function(srcHostname, desHostname, type, action) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === action ) {
return false; return false;
} }
this.setCell(srcHostname, desHostname, type, 0); this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type); this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 1 ) { if ( this.r === action ) {
return true; return true;
} }
this.setCell(srcHostname, desHostname, type, 1); this.setCell(srcHostname, desHostname, type, action);
return true; return true;
}; };
/******************************************************************************/
Matrix.prototype.blockCell = function(srcHostname, desHostname, type) {
return this.setCellZ(srcHostname, desHostname, type, 1);
};
// https://www.youtube.com/watch?v=Csewb_eIStY // https://www.youtube.com/watch?v=Csewb_eIStY
/******************************************************************************/ /******************************************************************************/
Matrix.prototype.allowCell = function(srcHostname, desHostname, type) { Matrix.prototype.allowCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type); return this.setCellZ(srcHostname, desHostname, type, 2);
if ( this.r === 2 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 2 ) {
return true;
}
this.setCell(srcHostname, desHostname, type, 2);
return true;
};
/******************************************************************************/
Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 0 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 0 || this.r === 3 ) {
return true;
}
this.setCell(srcHostname, desHostname, type, 3);
return true;
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -440,6 +440,9 @@ PageStore.prototype.filterRequest = function(context) {
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL); //console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
this.recordResult(context.requestType, requestURL, result); this.recordResult(context.requestType, requestURL, result);
// TODO: send this to a dev-panel tool
//console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result);
var requestHostname = context.requestHostname; var requestHostname = context.requestHostname;
if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) { if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) {
this.hostnameToCountMap[requestHostname] = 0; this.hostnameToCountMap[requestHostname] = 0;

View File

@ -46,8 +46,9 @@ var scopeToSrcHostnameMap = {
'.': '' '.': ''
}; };
var threePlus = '+++'; var threePlus = '+++';
var threeMinus = '\u2012\u2012\u2012'; var threeMinus = '';
var sixSpace = '\u2007\u2007\u2007\u2007\u2007\u2007'; var sixSpace = '\u2007\u2007\u2007\u2007\u2007\u2007';
var dynaHotspots = null;
/******************************************************************************/ /******************************************************************************/
@ -102,6 +103,7 @@ var addDynamicFilterRow = function(des) {
row.toggleClass('isDomain', isDomain); row.toggleClass('isDomain', isDomain);
if ( hnDetails.allowCount !== 0 ) { if ( hnDetails.allowCount !== 0 ) {
touchedDomains[hnDetails.domain] = true; touchedDomains[hnDetails.domain] = true;
row.addClass('wasTouched');
} }
row.appendTo('#dynamicFilteringContainer'); row.appendTo('#dynamicFilteringContainer');
@ -120,7 +122,7 @@ var addDynamicFilterRow = function(des) {
/******************************************************************************/ /******************************************************************************/
var syncDynamicFilter = function(scope, des, type, result) { var syncDynamicFilterCell = function(scope, des, type, result) {
var selector = '#dynamicFilteringContainer span[data-src="' + scope + '"][data-des="' + des + '"][data-type="' + type + '"]'; var selector = '#dynamicFilteringContainer span[data-src="' + scope + '"][data-des="' + des + '"][data-type="' + type + '"]';
var cell = uDom(selector); var cell = uDom(selector);
@ -129,17 +131,20 @@ var syncDynamicFilter = function(scope, des, type, result) {
cell = addDynamicFilterRow(des).descendants(selector); cell = addDynamicFilterRow(des).descendants(selector);
} }
var blocked = result.charAt(1) === 'b'; cell.removeClass();
cell.toggleClass('blocked', blocked); var action = result.charAt(1);
if ( action !== '' ) {
cell.toggleClass(action + 'Rule', true);
}
// Use dark shade visual cue if the filter is specific to the cell. // Use dark shade visual cue if the filter is specific to the cell.
var ownFilter = false; var ownRule = false;
var matches = reSrcHostnameFromResult.exec(result); var matches = reSrcHostnameFromResult.exec(result);
if ( matches !== null ) { if ( matches !== null ) {
ownFilter = matches[2] === des && ownRule = matches[2] === des &&
matches[1] === scopeToSrcHostnameMap[scope]; matches[1] === scopeToSrcHostnameMap[scope];
} }
cell.toggleClass('ownFilter', ownFilter); cell.toggleClass('ownRule', ownRule);
if ( scope !== '.' || type !== '*' ) { if ( scope !== '.' || type !== '*' ) {
return; return;
@ -150,29 +155,30 @@ var syncDynamicFilter = function(scope, des, type, result) {
var hnDetails = stats.hostnameDict[des]; var hnDetails = stats.hostnameDict[des];
var aCount = Math.min(Math.ceil(Math.log10(hnDetails.allowCount + 1)), 3); var aCount = Math.min(Math.ceil(Math.log10(hnDetails.allowCount + 1)), 3);
var bCount = Math.min(Math.ceil(Math.log10(hnDetails.blockCount + 1)), 3); var bCount = Math.min(Math.ceil(Math.log10(hnDetails.blockCount + 1)), 3);
cell.text( // IMPORTANT: It is completely assumed the first node is a TEXT_NODE, so
threePlus.slice(0, aCount) + // ensure this in the HTML file counterpart when you make
sixSpace.slice(aCount + bCount) + // changes
threeMinus.slice(0, bCount) cell.nodeAt(0).firstChild.nodeValue = threePlus.slice(0, aCount) +
); sixSpace.slice(aCount + bCount) +
threeMinus.slice(0, bCount);
}; };
/******************************************************************************/ /******************************************************************************/
var syncAllDynamicFilters = function() { var syncAllDynamicFilters = function() {
var hasBlock = false; var hasRule = false;
var rules = stats.dynamicFilterRules; var rules = stats.dynamicFilterRules;
var type, result; var type, result;
var types = dynaTypes; var types = dynaTypes;
var i = types.length; var i = types.length;
while ( i-- ) { while ( i-- ) {
type = types[i]; type = types[i];
syncDynamicFilter('/', '*', type, rules['/ * ' + type] || ''); syncDynamicFilterCell('/', '*', type, rules['/ * ' + type] || '');
result = rules['. * ' + type] || ''; result = rules['. * ' + type] || '';
if ( result.charAt(1) === 'b' ) { if ( result.charAt(1) !== '' ) {
hasBlock = true; hasRule = true;
} }
syncDynamicFilter('.', '*', type, result); syncDynamicFilterCell('.', '*', type, result);
} }
// Sort hostnames. First-party hostnames must always appear at the top // Sort hostnames. First-party hostnames must always appear at the top
@ -185,10 +191,9 @@ var syncAllDynamicFilters = function() {
if ( key.slice(-1) !== '*' ) { if ( key.slice(-1) !== '*' ) {
continue; continue;
} }
syncDynamicFilter(key.charAt(0), key.slice(2, key.indexOf(' ', 2)), '*', rules[key]); syncDynamicFilterCell(key.charAt(0), key.slice(2, key.indexOf(' ', 2)), '*', rules[key]);
} }
uDom('body').toggleClass('hasDynamicBlock', hasBlock);
uDom('#privacyInfo > b').text(Object.keys(touchedDomains).length); uDom('#privacyInfo > b').text(Object.keys(touchedDomains).length);
}; };
@ -333,30 +338,6 @@ var gotoLink = function(ev) {
/******************************************************************************/ /******************************************************************************/
var onDynamicFilterClicked = function(ev) {
// This can happen on pages where uBlock does not work
if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) {
return;
}
var cell = uDom(ev.target);
var scope = cell.attr('data-src') === '/' ? '*' : stats.pageHostname;
var onDynamicFilterChanged = function(details) {
cachePopupData(details);
syncAllDynamicFilters();
};
messager.send({
what: 'toggleDynamicFilter',
tabId: stats.tabId,
pageHostname: stats.pageHostname,
srcHostname: scope,
desHostname: cell.attr('data-des'),
requestType: cell.attr('data-type'),
block: cell.hasClass('blocked') === false
}, onDynamicFilterChanged);
};
/******************************************************************************/
var toggleDynamicFiltering = function(ev) { var toggleDynamicFiltering = function(ev) {
var el = uDom('body'); var el = uDom('body');
el.toggleClass('dynamicFilteringEnabled'); el.toggleClass('dynamicFilteringEnabled');
@ -369,6 +350,79 @@ var toggleDynamicFiltering = function(ev) {
/******************************************************************************/ /******************************************************************************/
var mouseenterCellHandler = function() {
if ( uDom(this).hasClass('ownRule') === false ) {
dynaHotspots.appendTo(this);
}
};
var mouseleaveCellHandler = function() {
dynaHotspots.detach();
};
/******************************************************************************/
var setDynamicFilter = function(src, des, type, action) {
// This can happen on pages where uBlock does not work
if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) {
return;
}
var onDynamicFilterChanged = function(details) {
cachePopupData(details);
syncAllDynamicFilters();
};
messager.send({
what: 'toggleDynamicFilter',
tabId: stats.tabId,
pageHostname: stats.pageHostname,
srcHostname: src,
desHostname: des,
requestType: type,
action: action
}, onDynamicFilterChanged);
};
/******************************************************************************/
var unsetDynamicFilterHandler = function() {
var cell = uDom(this);
setDynamicFilter(
cell.attr('data-src') === '/' ? '*' : stats.pageHostname,
cell.attr('data-des'),
cell.attr('data-type'),
0
);
dynaHotspots.appendTo(cell);
};
/******************************************************************************/
var setDynamicFilterHandler = function() {
var hotspot = uDom(this);
var cell = hotspot.ancestors('[data-src]');
if ( cell.length === 0 ) {
return;
}
var action = 0;
var hotspotId = hotspot.attr('id');
if ( hotspotId === 'dynaAllow' ) {
action = 2;
} else if ( hotspotId === 'dynaNoop' ) {
action = 3
} else {
action = 1;
}
setDynamicFilter(
cell.attr('data-src') === '/' ? '*' : stats.pageHostname,
cell.attr('data-des'),
cell.attr('data-type'),
action
);
dynaHotspots.detach();
};
/******************************************************************************/
var installEventHandlers = function() { var installEventHandlers = function() {
uDom('h1,h2,h3,h4').on('click', gotoDashboard); uDom('h1,h2,h3,h4').on('click', gotoDashboard);
uDom('#switch .fa').on('click', toggleNetFilteringSwitch); uDom('#switch .fa').on('click', toggleNetFilteringSwitch);
@ -376,7 +430,13 @@ var installEventHandlers = function() {
uDom('#gotoPick').on('click', gotoPick); uDom('#gotoPick').on('click', gotoPick);
uDom('a[href^=http]').on('click', gotoLink); uDom('a[href^=http]').on('click', gotoLink);
uDom('#dynamicFilteringToggler').on('click', toggleDynamicFiltering); uDom('#dynamicFilteringToggler').on('click', toggleDynamicFiltering);
uDom('#dynamicFilteringContainer').on('click', 'span[data-type]', onDynamicFilterClicked);
uDom('#dynamicFilteringContainer').on('click', 'span[data-src]', unsetDynamicFilterHandler);
uDom('#dynamicFilteringContainer')
.on('mouseenter', '[data-src]', mouseenterCellHandler)
.on('mouseleave', '[data-src]', mouseleaveCellHandler);
dynaHotspots = uDom('#actionSelector');
dynaHotspots.on('click', 'span', setDynamicFilterHandler).detach();
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -308,8 +308,8 @@ var matchWhitelistDirective = function(url, hostname, directive) {
µBlock.toggleDynamicFilter = function(details) { µBlock.toggleDynamicFilter = function(details) {
var changed = false; var changed = false;
if ( details.block ) { if ( details.action !== 0 ) {
changed = this.dynamicNetFilteringEngine.blockCell(details.srcHostname, details.desHostname, details.requestType); changed = this.dynamicNetFilteringEngine.setCellZ(details.srcHostname, details.desHostname, details.requestType, details.action);
} else { } else {
changed = this.dynamicNetFilteringEngine.unsetCell(details.srcHostname, details.desHostname, details.requestType); changed = this.dynamicNetFilteringEngine.unsetCell(details.srcHostname, details.desHostname, details.requestType);
} }

View File

@ -12,30 +12,31 @@
<h4 title="popupTipDashboard">v<span id="version"></span></h4> <h4 title="popupTipDashboard">v<span id="version"></span></h4>
<div> <div>
<div id="dynamicFilteringContainer"> <div id="dynamicFilteringContainer">
<div><span>images</span><span data-src="/" data-des="*" data-type="image"></span><span data-src="." data-des="*" data-type="image"></span></div> <div><span>images</span><span data-src="/" data-des="*" data-type="image"> </span><span data-src="." data-des="*" data-type="image"></span></div>
<div><span>inline scripts</span><span data-src="/" data-des="*" data-type="inline-script"></span><span data-src="." data-des="*" data-type="inline-script"></span></div> <div><span>inline scripts</span><span data-src="/" data-des="*" data-type="inline-script"> </span><span data-src="." data-des="*" data-type="inline-script"> </span></div>
<div><span>1st-party scripts</span><span data-src="/" data-des="*" data-type="1p-script"></span><span data-src="." data-des="*" data-type="1p-script"></span></div> <div><span>1st-party scripts</span><span data-src="/" data-des="*" data-type="1p-script"> </span><span data-src="." data-des="*" data-type="1p-script"> </span></div>
<div><span>3rd-party scripts</span><span data-src="/" data-des="*" data-type="3p-script"></span><span data-src="." data-des="*" data-type="3p-script"></span></div> <div><span>3rd-party scripts</span><span data-src="/" data-des="*" data-type="3p-script"> </span><span data-src="." data-des="*" data-type="3p-script"> </span></div>
<div><span>3rd-party frames</span><span data-src="/" data-des="*" data-type="3p-frame"></span><span data-src="." data-des="*" data-type="3p-frame"></span></div> <div><span>3rd-party frames</span><span data-src="/" data-des="*" data-type="3p-frame"> </span><span data-src="." data-des="*" data-type="3p-frame"> </span></div>
<div id="privacyInfo"><b>?</b> distinct domains touched</div> <div id="privacyInfo"><b>?</b> distinct domains touched</div>
</div> </div>
</div><!-- DO NOT REMOVE --><div> </div><!-- DO NOT REMOVE --><div>
<p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa">&#xf011;</span></p> <p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa">&#xf011;</span></p>
<p id="switch-hint"></p> <p id="switch-hint"></p>
<p id="dynamicFilteringToggler" data-i18n="popupBlockedRequestPrompt"></p> <p id="dynamicFilteringToggler" data-i18n="popupBlockedRequestPrompt"></p>
<p id="stats"> <p class="stats">
<span data-i18n="popupBlockedOnThisPagePrompt"></span>&ensp; <span data-i18n="popupBlockedOnThisPagePrompt"></span>&ensp;
<span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top">&#xf1fb;</span>&ensp; <span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top">&#xf1fb;</span>&ensp;
<span id="gotoLog" class="fa tool" data-i18n-tip="popupTipLog" data-tip-anchor="top">&#xf06e;</span> <span id="gotoLog" class="fa tool" data-i18n-tip="popupTipLog" data-tip-anchor="top">&#xf06e;</span>
</p> </p>
<p id="page-blocked">?</p> <p id="page-blocked">?</p>
<p id="stats" data-i18n="popupBlockedSinceInstallPrompt"></p> <p class="stats" data-i18n="popupBlockedSinceInstallPrompt"></p>
<p id="total-blocked">?</p> <p id="total-blocked">?</p>
</div> </div>
<div id="templates" style="display: none"> <div id="templates" style="display: none">
<div><span></span><span data-src="/" data-des="" data-type="*"></span><span data-src="." data-des="" data-type="*"></span></div> <div><span></span><span data-src="/" data-des="" data-type="*"> </span><span data-src="." data-des="" data-type="*"> </span></div>
<div> <div id='actionSelector'><span id="dynaAllow"></span><span id="dynaNoop"></span><span id="dynaBlock"></span></div>
</div>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>