1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 12:57:57 +02:00

importing uMatrix unified logger

This commit is contained in:
gorhill 2015-05-08 18:28:01 -04:00
parent eba1161f0d
commit f9652b5f57
16 changed files with 1166 additions and 1193 deletions

View File

@ -1908,7 +1908,7 @@ var optionsObserver = {
}
this.setupOptionsButton(doc, 'showDashboardButton', 'dashboard.html');
this.setupOptionsButton(doc, 'showNetworkLogButton', 'devtools.html');
this.setupOptionsButton(doc, 'showNetworkLogButton', 'logger-ui.html');
}
};

View File

@ -1,130 +0,0 @@
body {
border: 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
margin: 0;
overflow-x: hidden;
padding: 0;
white-space: nowrap;
width: 100%;
}
#toolbar {
background-color: white;
border: 0;
box-sizing: border-box;
height: 40px;
left: 0;
margin: 0;
padding: 0 1em;
position: fixed;
top: 0;
width: 100%;
}
#toolbar .button {
background-color: white;
border: none;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-size: 20px;
margin: 0;
padding: 8px;
}
#toolbar .button:hover {
background-color: #eee;
}
body.filterOff #toolbar #filterButton {
opacity: 0.25;
}
#filterExpression.bad {
background-color: #fee;
}
#maxEntries {
margin-left: 3em;
}
input:focus {
background-color: #ffe;
}
#content {
margin-top: 40px;
}
#content table {
border: 0;
border-collapse: collapse;
direction: ltr;
font: 12px monospace;
width: 100%;
}
#content table tr.docBoundary {
background-color: #666;
color: white;
text-align: center;
}
#content table tr.docBoundary > td:first-child {
padding: 1em 0;
white-space: normal;
word-break: break-all;
word-wrap: break-word;
}
#content table tr.blocked {
background-color: rgba(192, 0, 0, 0.1);
}
body.colorBlind #content table tr.blocked {
background-color: rgba(0, 19, 110, 0.1);
}
#content table tr.allowed {
background-color: rgba(0, 160, 0, 0.1);
}
body.colorBlind #content table tr.allowed {
background-color: rgba(255, 194, 57, 0.1)
}
#content table tr.maindoc {
}
#content table tr.cosmetic {
background-color: rgba(255, 255, 0, 0.1);
}
body:not(.filterOff) #content table tr.hidden {
display: none;
}
#content table tr td {
border: 1px solid #ccc;
padding: 3px;
vertical-align: top;
}
#content table tr td:nth-of-type(1) {
padding: 3px 0;
text-align: center;
white-space: pre;
width: 1em;
}
#content table tr td:nth-of-type(2) {
white-space: normal;
width: 25%;
word-break: break-all;
word-wrap: break-word;
}
#content table tr td:nth-of-type(3) {
white-space: nowrap;
}
#content table tr td:nth-of-type(4) {
border-right: none;
white-space: normal;
width: 60%;
word-break: break-all;
word-wrap: break-word;
}
#content table tr td:nth-of-type(4) b {
font-weight: normal;
}
#content table tr.blocked td:nth-of-type(4) b {
background-color: rgba(192, 0, 0, 0.2);
}
body.colorBlind #content table tr.blocked td:nth-of-type(4) b {
background-color: rgba(0, 19, 110, 0.2);
}
#content table tr.allowed td:nth-of-type(4) b {
background-color: rgba(0, 160, 0, 0.2);
}
body.colorBlind #content table tr.allowed td:nth-of-type(4) b {
background-color: rgba(255, 194, 57, 0.2);
}

View File

@ -1,110 +0,0 @@
body {
margin: 0;
overflow-y: hidden;
padding: 0;
}
button {
opacity: 0.25;
}
button:hover {
opacity: 0.75;
}
#toolbar {
background-color: #eee;
border: none;
box-sizing: border-box;
-moz-box-sizing: border-box;
height: 4em;
padding: 1em;
position: fixed;
top: 0;
width: 100%;
}
#toolbar > * {
display: inline-block;
position: relative;
vertical-align: middle;
}
#toolbar button {
background-color: transparent;
border: none;
cursor: pointer;
font-size: 2em;
margin: 0 0 0 1em;
vertical-align: middle;
}
#pageSelector {
max-width: 70%;
}
#toolbar #refresh {
margin-left: 4px;
}
select {
padding: 2px 0;
font-size: 14px;
min-width: 20em;
max-width: 40em;
}
select option {
max-width: 40em;
}
#extras {
background-color: transparent;
border: 0;
margin: 0;
padding: 0;
position: absolute;
right: 0;
}
#extras > span {
border: 0;
margin: 0;
padding: 0;
position: relative;
}
#extras > span > button {
margin-left: 0.2em;
}
#extras > span > div {
background-color: white;
border: 0;
display: none;
position: absolute;
right: 2px;
}
#extras > span > div > * {
border: 1px solid gray;
}
#extras > span > button.enabled {
opacity: 1;
}
#extras > span > button.enabled + div {
display: block;
}
#filterMatcher {
border: 0;
padding: 0.5em;
margin: 0;
}
#filteringResult {
background-color: #eee;
font-family: monospace;
}
#filteringResult.empty {
background-color: transparent;
}
#popup {
border: 0;
padding: 0;
margin: 0;
}
#content {
border: 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
height: calc(100vh - 4em);
margin-top: 4em;
overflow-y: auto;
padding: 0;
width: 100%;
}

251
src/css/logger-ui.css Normal file
View File

@ -0,0 +1,251 @@
body {
background-color: white;
border: 0;
box-sizing: border-box;
color: black;
-moz-box-sizing: border-box;
margin: 0;
overflow-x: hidden;
padding: 0;
white-space: nowrap;
width: 100%;
}
#toolbar {
background-color: white;
border: 0;
box-sizing: border-box;
left: 0;
margin: 0;
padding: 0 1em;
position: fixed;
top: 0;
width: 100%;
z-index: 10;
}
#toolbar .button {
background-color: white;
border: none;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-size: 20px;
margin: 0;
padding: 8px;
}
#toolbar .button.disabled {
opacity: 0.2;
pointer-events: none;
}
#toolbar .button:hover {
background-color: #eee;
}
body #compactViewToggler.button:before {
content: '\f102';
}
body.compactView #compactViewToggler.button:before {
content: '\f103';
}
#filterButton {
opacity: 0.25;
}
body.f #filterButton {
opacity: 1;
}
#filterInput.bad {
background-color: #fee;
}
#maxEntries {
margin-left: 3em;
}
input:focus {
background-color: #ffe;
}
#content {
font: 13px sans-serif;
margin-top: 3.5em;
width: 100%;
}
#content table {
border: 0;
border-collapse: collapse;
direction: ltr;
table-layout: fixed;
width: 100%;
}
#content table > colgroup > col:nth-of-type(1) {
width: 5em;
}
#content table > colgroup > col:nth-of-type(2) {
width: 2.5em;
}
#content table > colgroup > col:nth-of-type(3) {
width: 2.5em;
}
#content table > colgroup > col:nth-of-type(4) {
width: 20%;
}
#content table > colgroup > col:nth-of-type(5) {
width: 6em;
}
#content table > colgroup > col:nth-of-type(6) {
width: calc(100% - 16em - 20%);
}
body.f table tr.f {
display: none;
}
#content table tr.cat_info {
color: #00f;
}
#content table tr.blocked {
background-color: rgba(192, 0, 0, 0.1);
}
body.colorBlind #content table tr.blocked {
background-color: rgba(0, 19, 110, 0.1);
}
#content table tr.allowed {
background-color: rgba(0, 160, 0, 0.1);
}
body.colorBlind #content table tr.allowed {
background-color: rgba(255, 194, 57, 0.1)
}
#content table tr.cosmetic {
background-color: rgba(255, 255, 0, 0.1);
}
#content table tr.maindoc {
background-color: #666;
color: white;
text-align: center;
}
body #content td {
border: 1px solid #ccc;
border-top: none;
min-width: 0.5em;
padding: 3px;
vertical-align: top;
white-space: normal;
word-break: break-all;
word-wrap: break-word;
}
#content table tr td {
border-top: 1px solid #ccc;
}
#content table tr td:first-of-type {
border-left: none;
}
#content table tr td:last-of-type {
border-right: none;
}
body.compactView #content td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#content table tr td:nth-of-type(1) {
text-align: center;
white-space: nowrap;
}
#content table tr td:nth-of-type(2) {
text-align: center;
white-space: nowrap;
}
#content table tr.tab_bts > td:nth-of-type(2):before {
content: '\f070';
font: 1em FontAwesome;
}
#content table tr.tab:not(.canMtx) {
opacity: 0.2;
}
#content table tr.tab:not(.canMtx) > td:nth-of-type(2):before {
content: '\f00d';
font: 1em FontAwesome;
}
body:not(.popupOn) #content table tr.canMtx td:nth-of-type(2) {
cursor: zoom-in;
}
body:not(.popupOn) #content table tr.canMtx td:nth-of-type(2):hover {
background: white;
}
#content table tr.cat_net td:nth-of-type(3),
#content table tr.cat_cosmetic td:nth-of-type(3) {
font: 12px monospace;
text-align: center;
white-space: nowrap;
}
#content table tr.cat_net td:nth-of-type(6) > span > b {
font-weight: bold;
}
#content table tr td:nth-of-type(6) b {
font-weight: normal;
}
#content table tr.blocked td:nth-of-type(6) b {
background-color: rgba(192, 0, 0, 0.2);
}
body.colorBlind #content table tr.blocked td:nth-of-type(6) b {
background-color: rgba(0, 19, 110, 0.2);
}
#content table tr.allowed td:nth-of-type(6) b {
background-color: rgba(0, 160, 0, 0.2);
}
body.colorBlind #content table tr.allowed td:nth-of-type(6) b {
background-color: rgba(255, 194, 57, 0.2);
}
#popupContainer {
background: white;
border: 1px solid gray;
border-radius: 3px;
display: none;
overflow: hidden;
position: fixed;
z-index: 200;
}
body.popupOn #popupContainer {
display: block;
}
#popupContainer > div {
background: #aaa;
border: 0;
cursor: -webkit-grab;
cursor: grab;
height: 1.2em;
}
body[dir="ltr"] #popupContainer > div {
direction: rtl;
}
body[dir="rtl"] #popupContainer > div {
direction: ltr;
}
#popupContainer > div > span {
color: #eee;
cursor: pointer;
display: inline-block;
font: 14px FontAwesome;
padding: 0 3px;
}
#popupContainer > div > span:hover {
color: white;
}
#popupContainer > iframe {
border: 0;
padding: 0;
margin: 0;
width: 100%;
}
#movingOverlay {
bottom: 0;
display: none;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 300;
}
#popupContainer.moving ~ #movingOverlay {
cursor: -webkit-grabbing;
cursor: grabbing;
display: block;
}

View File

@ -1,25 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/devtool-log.css">
<title>uBlock log</title>
</head>
<body>
<div id="toolbar">
<span id="reload" class="button fa">&#xf021;</span>
<span id="clear" class="button fa">&#xf12d;</span>
<span id="filterButton" class="button fa">&#xf0b0;</span><input id="filterExpression" type="text" placeholder="logFilterPrompt">
<input id="maxEntries" type="text" size="5" title="logMaxEntriesTip">
</div>
<div id="content">
<table><tbody></tbody></table>
</div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/devtool-log.js"></script>
</body>
</html>

View File

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<title data-i18n="statsPageName"></title>
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/devtools.css">
</head>
<body>
<div id="toolbar">
<select id="pageSelector"></select>
<button id="refresh" class="fa" type="button">&#xf021;</button>
<span id="extras">
<span>
<button class="fa toolToggler" type="button">&#xf0ad;</button>
<div>
<div id="filterMatcher">
<p><label>URL of context</label><br>
<input type="text" size="40">
<p><label>URL of resource</label><br>
<input type="text" size="40">
<p><label>Type of resource</label><br>
<input type="text" size="40">
<p><label>Matching static filter</label><br>
<span id="filteringResult" class="empty">&nbsp;</span>
</div>
</div>
</span>
<span>
<button id="popupToggler" class="fa toolToggler" type="button">&#xf0c8;</button>
<div>
<iframe id="popup" src=""></iframe>
</div>
</span>
</span>
</div>
<iframe id="content"></iframe>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/devtools.js"></script>
</body>
</html>

View File

@ -34,7 +34,12 @@
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript-end.js > not a HTLMDocument');
return false;
return;
}
// I've seen this happens on Firefox
if ( window.location === null ) {
return;
}
// This can happen

View File

@ -1,448 +0,0 @@
/*******************************************************************************
sessbench - a browser extension to benchmark browser session.
Copyright (C) 2013 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/sessbench
TODO: cleanup/refactor
*/
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('devtool-log.js');
var inspectedTabId = '';
var doc = document;
var body = doc.body;
var tbody = doc.querySelector('#content tbody');
var row1Junkyard = [];
var row4Junkyard = [];
var reFilter = null;
var filterTargetTestResult = true;
var maxEntries = 0;
var prettyRequestTypes = {
'main_frame': 'doc',
'stylesheet': 'css',
'sub_frame': 'frame',
'xmlhttprequest': 'xhr'
};
/******************************************************************************/
var escapeHTML = function(s) {
return s.replace(reEscapeLeftBracket, '&lt;')
.replace(reEscapeRightBracket, '&gt;');
};
var reEscapeLeftBracket = /</g;
var reEscapeRightBracket = />/g;
/******************************************************************************/
var renderURL = function(url, filter) {
if ( filter.charAt(0) !== 's' ) {
return escapeHTML(url);
}
// make a regex out of the filter
var reText = filter.slice(3);
var pos = reText.indexOf('$');
if ( pos > 0 ) {
reText = reText.slice(0, pos);
}
if ( reText === '*' ) {
reText = '\\*';
} else if ( reText.charAt(0) === '/' && reText.slice(-1) === '/' ) {
reText = reText.slice(1, -1);
} else {
reText = reText
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace('||', '')
.replace(/\^/g, '.')
.replace(/^\|/g, '^')
.replace(/\|$/g, '$')
.replace(/\*/g, '.*')
;
}
var re = new RegExp(reText, 'gi');
var matches = re.exec(url);
var renderedURL = url;
if ( matches && matches[0].length ) {
renderedURL = escapeHTML(url.slice(0, matches.index)) +
'<b>' +
escapeHTML(url.slice(matches.index, re.lastIndex)) +
'</b>' +
escapeHTML(url.slice(re.lastIndex));
} else {
renderedURL = escapeHTML(renderedURL);
}
return renderedURL;
};
/******************************************************************************/
var createRow = function() {
var tr = row4Junkyard.pop();
if ( tr ) {
tr.className = '';
return tr;
}
tr = doc.createElement('tr');
tr.appendChild(doc.createElement('td'));
tr.appendChild(doc.createElement('td'));
tr.appendChild(doc.createElement('td'));
tr.appendChild(doc.createElement('td'));
return tr;
};
/******************************************************************************/
var createGap = function(url) {
var tr = row1Junkyard.pop();
if ( !tr ) {
tr = doc.createElement('tr');
tr.classList.add('docBoundary');
tr.appendChild(doc.createElement('td'));
tr.cells[0].setAttribute('colspan', '4');
}
tr.cells[0].textContent = url;
tbody.insertBefore(tr, tbody.firstChild);
};
/******************************************************************************/
var renderLogEntry = function(entry) {
var tr = createRow();
// If the request is that of a root frame, insert a gap in the table
// in order to visually separate entries for different documents.
if ( entry.type === 'main_frame' ) {
createGap(entry.url);
tr.classList.add('maindoc');
}
// Cosmetic filter?
if ( entry.result.charAt(0) === 'c' ) {
tr.classList.add('cosmetic');
}
if ( entry.result.charAt(1) === 'b' ) {
tr.classList.add('blocked');
tr.cells[0].textContent = ' -\u00A0';
} else if ( entry.result.charAt(1) === 'a' ) {
tr.classList.add('allowed');
tr.cells[0].textContent = ' +\u00A0';
} else {
tr.cells[0].textContent = '';
}
var filterText = entry.result.slice(3);
if ( entry.result.lastIndexOf('sa', 0) === 0 ) {
filterText = '@@' + filterText;
}
tr.cells[1].textContent = filterText + '\t';
tr.cells[2].textContent = (prettyRequestTypes[entry.type] || entry.type) + '\t';
vAPI.insertHTML(tr.cells[3], renderURL(entry.url, entry.result));
applyFilterToRow(tr);
tbody.insertBefore(tr, tbody.firstChild);
};
/******************************************************************************/
var renderLogBuffer = function(response) {
body.classList.toggle('colorBlind', response.colorBlind);
var buffer = response.entries;
if ( buffer.length === 0 ) {
return;
}
// Preserve scroll position
var height = tbody.offsetHeight;
var n = buffer.length;
for ( var i = 0; i < n; i++ ) {
renderLogEntry(buffer[i]);
}
// Prevent logger from growing infinitely and eating all memory. For
// instance someone could forget that it is left opened for some
// dynamically refreshed pages.
truncateLog(maxEntries);
var yDelta = tbody.offsetHeight - height;
if ( yDelta === 0 ) {
return;
}
// Chromium:
// body.scrollTop = good value
// body.parentNode.scrollTop = 0
if ( body.scrollTop !== 0 ) {
body.scrollTop += yDelta;
return;
}
// Firefox:
// body.scrollTop = 0
// body.parentNode.scrollTop = good value
var parentNode = body.parentNode;
if ( parentNode && parentNode.scrollTop !== 0 ) {
parentNode.scrollTop += yDelta;
}
};
/******************************************************************************/
var truncateLog = function(size) {
if ( size === 0 ) {
size = 25000;
}
size = Math.min(size, 25000);
var tr;
while ( tbody.childElementCount > size ) {
tr = tbody.lastElementChild;
// https://github.com/gorhill/uBlock/issues/123
// Triage according to row type.
if ( tr.cells.length === 1 ) {
row1Junkyard.push(tr);
} else {
row4Junkyard.push(tr);
}
tbody.removeChild(tr);
}
};
/******************************************************************************/
var onBufferRead = function(response) {
renderLogBuffer(response);
setTimeout(readLogBuffer, 1000);
};
/******************************************************************************/
// This can be called only once, at init time. After that, this will be called
// automatically. If called after init time, this will be messy, and this would
// require a bit more code to ensure no multi time out events.
var readLogBuffer = function() {
messager.send({ what: 'readLogBuffer', tabId: inspectedTabId }, onBufferRead);
};
/******************************************************************************/
var clearBuffer = function() {
var tr;
while ( tbody.firstChild !== null ) {
tr = tbody.lastElementChild;
// https://github.com/gorhill/uBlock/issues/123
// Triage according to row type.
if ( tr.cells.length === 1 ) {
row1Junkyard.push(tr);
} else {
row4Junkyard.push(tr);
}
tbody.removeChild(tr);
}
};
/******************************************************************************/
var reloadTab = function() {
messager.send({ what: 'reloadTab', tabId: inspectedTabId });
};
/******************************************************************************/
var applyFilterToRow = function(row) {
var re = reFilter;
if ( re === null || re.test(row.textContent) === filterTargetTestResult ) {
row.classList.remove('hidden');
} else {
row.classList.add('hidden');
}
};
/******************************************************************************/
var applyFilter = function() {
if ( reFilter === null ) {
unapplyFilter();
return;
}
var row = document.querySelector('#content tr');
if ( row === null ) {
return;
}
var re = reFilter;
var target = filterTargetTestResult;
while ( row !== null ) {
if ( re.test(row.textContent) === target ) {
row.classList.remove('hidden');
} else {
row.classList.add('hidden');
}
row = row.nextSibling;
}
};
/******************************************************************************/
var unapplyFilter = function() {
var row = document.querySelector('#content tr');
if ( row === null ) {
return;
}
while ( row !== null ) {
row.classList.remove('hidden');
row = row.nextSibling;
}
};
/******************************************************************************/
var onFilterButton = function() {
uDom('body').toggleClass('filterOff');
};
/******************************************************************************/
var onFilterChanged = function() {
var filterExpression = uDom('#filterExpression');
var filterRaw = filterExpression.val().trim();
// Assume good filter expression
filterExpression.removeClass('bad');
// Invert resultset?
filterTargetTestResult = filterRaw.charAt(0) !== '!';
if ( filterTargetTestResult === false ) {
filterRaw = filterRaw.slice(1);
}
// No filter
if ( filterRaw === '') {
reFilter = null;
return;
}
// Regex?
if ( filterRaw.length > 1 && filterRaw.charAt(0) === '/' && filterRaw.slice(-1) === '/' ) {
try {
reFilter = new RegExp(filterRaw.slice(1, -1));
} catch (e) {
reFilter = null;
filterExpression.addClass('bad');
}
return;
}
// Plain filtering
var filterParts = filterRaw
.replace(/^\s*-(\s+|$)/, '-\xA0 ')
.replace(/^\s*\\+(\s+|$)/, '+\xA0 ')
.split(/[ \f\n\r\t\v]+/);
var n = filterParts.length;
for ( var i = 0; i < n; i++ ) {
filterParts[i] = filterParts[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
reFilter = new RegExp(filterParts.join('.*\\s+.*'));
};
/******************************************************************************/
var onFilterChangedAsync = (function() {
var timer = null;
var commit = function() {
timer = null;
onFilterChanged();
applyFilter();
};
var changed = function() {
if ( timer !== null ) {
clearTimeout(timer);
}
timer = setTimeout(commit, 750);
};
return changed;
})();
/******************************************************************************/
var onMaxEntriesChanged = function() {
var raw = uDom(this).val();
try {
maxEntries = parseInt(raw, 10);
if ( isNaN(maxEntries) ) {
maxEntries = 0;
}
} catch (e) {
maxEntries = 0;
}
messager.send({
what: 'userSettings',
name: 'requestLogMaxEntries',
value: maxEntries
});
truncateLog(maxEntries);
};
/******************************************************************************/
uDom.onLoad(function() {
// Extract the tab id of the page we need to pull the log
var matches = window.location.search.match(/[\?&]tabId=([^&]+)/);
if ( matches && matches.length === 2 ) {
inspectedTabId = matches[1];
}
var onSettingsReady = function(settings) {
maxEntries = settings.requestLogMaxEntries || 0;
uDom('#maxEntries').val(maxEntries || '');
};
messager.send({ what: 'getUserSettings' }, onSettingsReady);
readLogBuffer();
uDom('#reload').on('click', reloadTab);
uDom('#clear').on('click', clearBuffer);
uDom('#filterButton').on('click', onFilterButton);
uDom('#filterExpression').on('input', onFilterChangedAsync);
uDom('#maxEntries').on('change', onMaxEntriesChanged);
});
/******************************************************************************/
})();

View File

@ -1,231 +0,0 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* jshint bitwise: false */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('devtools.js');
/******************************************************************************/
var renderPageSelector = function(targetTabId) {
var selectedTabId = targetTabId || uDom('#pageSelector').val();
var onDataReceived = function(pageTitles) {
if ( pageTitles.hasOwnProperty(selectedTabId) === false ) {
selectedTabId = pageTitles[0];
}
var select = uDom('#pageSelector').empty();
var option;
for ( var tabId in pageTitles ) {
if ( pageTitles.hasOwnProperty(tabId) === false ) {
continue;
}
option = uDom('<option>').text(pageTitles[tabId])
.prop('value', tabId);
if ( tabId === selectedTabId ) {
option.prop('selected', true);
}
select.append(option);
}
// This must be done after inserting all option tags, or else Firefox
// will refuse values which do not exist yet.
select.prop('value', selectedTabId);
selectPage();
};
messager.send({ what: 'getPageDetails' }, onDataReceived);
};
/******************************************************************************/
var pageSelectorChanged = function() {
selectPage();
};
/******************************************************************************/
var selectPage = function() {
var tabId = uDom('#pageSelector').val() || '';
var inspector = uDom('#content');
var currentSrc = inspector.attr('src');
var targetSrc = 'devtool-log.html?tabId=' + tabId;
if ( targetSrc === currentSrc ) {
return;
}
inspector.attr('src', targetSrc);
uDom('#popup').attr('src', tabId ? 'popup.html?tabId=' + tabId : '');
// This is useful for when the user force-refresh the page: this will
// prevent a reset to the original request log.
// This is also useful for an outside observer to find out which tab is
// being logged, i.e. the popup menu can initialize itself according to
// what tab is currently being logged.
window.history.pushState(
{},
'',
window.location.href.replace(/^(.+[\?&])tabId=([^&]+)(.*)$/, '$1tabId=' + tabId + '$3')
);
};
/******************************************************************************/
var toggleTool = function() {
var button = uDom(this);
button.toggleClass('enabled', !button.hasClass('enabled'));
// Special case: we want the frame of the popup to be filled-in if and
// only if the popup is visible.
if ( this.id === 'popupToggler' ) {
var tabId = uDom('#pageSelector').val() || '';
var body = uDom('body');
body.toggleClass('popupEnabled');
if ( body.hasClass('popupEnabled') === false ) {
tabId = '';
}
uDom('#popup').attr(
'src',
button.hasClass('enabled') && tabId ? 'popup.html?tabId=' + tabId : ''
);
}
};
/******************************************************************************/
var evaluateStaticFiltering = (function() {
var uglyTypeNames = {
'css': 'stylesheet',
'doc': 'main_frame',
'frame': 'sub_frame',
'xhr': 'xmlhttprequest'
};
var onResultReceived = function(response) {
var result = response && response.result.slice(3);
uDom('#filteringResult')
.text(result || '\u00A0')
.toggleClass('empty', result === '');
var input = uDom('#filterMatcher input').at(0);
if ( input.val().trim() === '' ) {
input.val(response && response.contextURL || '');
}
};
var timer = null;
var onTimerElapsed = function() {
timer = null;
var inputs = uDom('#filterMatcher input');
var prettyTypeName = inputs.at(2).val().trim();
messager.send({
what: 'evaluateStaticFiltering',
tabId: uDom('#pageSelector').val() || '',
contextURL: inputs.at(0).val().trim(),
requestURL: inputs.at(1).val().trim(),
requestType: uglyTypeNames[prettyTypeName] || prettyTypeName,
}, onResultReceived);
};
return function() {
if ( timer === null ) {
setTimeout(onTimerElapsed, 750);
}
};
})();
/******************************************************************************/
var resizePopup = function() {
var popup = document.getElementById('popup');
popup.style.width = popup.contentWindow.document.body.clientWidth + 'px';
popup.style.height = popup.contentWindow.document.body.clientHeight + 'px';
};
/******************************************************************************/
var onPopupLoaded = function() {
resizePopup();
if ( popupObserver !== null ) {
popupObserver.disconnect();
}
var popup = document.getElementById('popup');
if ( popup.contentDocument === null ) {
return;
}
var popupBody = popup.contentDocument.body;
if ( popupBody === null ) {
return;
}
var popupPanes = popup.contentDocument.getElementById('panes');
if ( popupPanes === null ) {
return;
}
if ( popupObserver === null ) {
popupObserver = new MutationObserver(resizePopup);
}
var details = {
childList: false,
attributes: true,
attributeFilter: ['class']
};
popupObserver.observe(popupBody, details);
popupObserver.observe(popupPanes, details);
};
var popupObserver = null;
/******************************************************************************/
uDom.onLoad(function() {
var tabId;
// Extract the tab id of the page we need to pull the log
var matches = window.location.search.match(/[\?&]tabId=([^&]+)/);
if ( matches && matches.length === 2 ) {
tabId = matches[1];
}
uDom('.toolToggler').on('click', toggleTool);
uDom('#popup').on('load', onPopupLoaded);
renderPageSelector(tabId);
uDom('#pageSelector').on('change', pageSelectorChanged);
uDom('#refresh').on('click', function() { renderPageSelector(); });
uDom('#filterMatcher input').on('input', evaluateStaticFiltering);
});
/******************************************************************************/
})();

784
src/js/logger-ui.js Normal file
View File

@ -0,0 +1,784 @@
/*******************************************************************************
uMatrix - a browser extension to benchmark browser session.
Copyright (C) 2015 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/sessbench
*/
/* jshint boss: true */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('logger-ui.js');
var tbody = document.querySelector('#content tbody');
var trJunkyard = [];
var tdJunkyard = [];
var firstVarDataCol = 2; // currently, column 2 (0-based index)
var lastVarDataIndex = 4; // currently, d0-d3
var maxEntries = 5000;
var noTabId = '';
var allTabIds = {};
var prettyRequestTypes = {
'main_frame': 'doc',
'stylesheet': 'css',
'sub_frame': 'frame',
'xmlhttprequest': 'xhr'
};
var timeOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
var dateOptions = {
month: 'short',
day: '2-digit'
};
/******************************************************************************/
// Emphasize hostname in URL, as this is what matters in uMatrix's rules.
var nodeFromURL = function(url, filter) {
if ( filter.charAt(0) !== 's' ) {
return document.createTextNode(url);
}
// make a regex out of the filter
var reText = filter.slice(3);
var pos = reText.indexOf('$');
if ( pos > 0 ) {
reText = reText.slice(0, pos);
}
if ( reText === '*' ) {
reText = '\\*';
} else if ( reText.charAt(0) === '/' && reText.slice(-1) === '/' ) {
reText = reText.slice(1, -1);
} else {
reText = reText
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace('||', '')
.replace(/\^/g, '.')
.replace(/^\|/g, '^')
.replace(/\|$/g, '$')
.replace(/\*/g, '.*')
;
}
var re = new RegExp(reText, 'gi');
var matches = re.exec(url);
if ( matches === null || matches[0].length === 0 ) {
return document.createTextNode(url);
}
var node = renderedURLTemplate.cloneNode(true);
node.childNodes[0].textContent = url.slice(0, matches.index);
node.childNodes[1].textContent = url.slice(matches.index, re.lastIndex);
node.childNodes[2].textContent = url.slice(re.lastIndex);
return node;
};
var renderedURLTemplate = document.querySelector('#renderedURLTemplate > span');
/******************************************************************************/
var createCellAt = function(tr, index) {
var td = tr.cells[index];
var mustAppend = !td;
if ( mustAppend ) {
td = tdJunkyard.pop();
}
if ( td ) {
td.removeAttribute('colspan');
td.textContent = '';
} else {
td = document.createElement('td');
}
if ( mustAppend ) {
tr.appendChild(td);
}
return td;
};
/******************************************************************************/
var createRow = function(layout) {
var tr = trJunkyard.pop();
if ( tr ) {
tr.className = '';
} else {
tr = document.createElement('tr');
}
for ( var index = 0; index < firstVarDataCol; index++ ) {
createCellAt(tr, index);
}
var i = 1, span = 1, td;
for (;;) {
td = createCellAt(tr, index);
if ( i === lastVarDataIndex ) {
break;
}
if ( layout.charAt(i) !== '1' ) {
span += 1;
} else {
if ( span !== 1 ) {
td.setAttribute('colspan', span);
}
index += 1;
span = 1;
}
i += 1;
}
if ( span !== 1 ) {
td.setAttribute('colspan', span);
}
index += 1;
while ( td = tr.cells[index] ) {
tdJunkyard.push(tr.removeChild(td));
}
return tr;
};
/******************************************************************************/
var createGap = function(tabId, url) {
var tr = createRow('1');
tr.classList.add('tab');
tr.classList.add('canMtx');
tr.classList.add('tab_' + tabId);
tr.classList.add('maindoc');
tr.cells[firstVarDataCol].textContent = url;
tbody.insertBefore(tr, tbody.firstChild);
};
/******************************************************************************/
var renderNetLogEntry = function(tr, entry) {
var filter = entry.d0;
var type = entry.d1;
var url = entry.d2;
tr.classList.add('canMtx');
// If the request is that of a root frame, insert a gap in the table
// in order to visually separate entries for different documents.
if ( type === 'main_frame' ) {
createGap(entry.tab, url);
}
// Cosmetic filter?
if ( filter.charAt(0) === 'c' ) {
tr.classList.add('cosmetic');
}
if ( filter.charAt(1) === 'b' ) {
tr.classList.add('blocked');
tr.cells[2].textContent = ' --';
} else if ( filter.charAt(1) === 'a' ) {
tr.classList.add('allowed');
tr.cells[2].textContent = ' ++';
} else {
tr.cells[2].textContent = '';
}
var filterText = filter.slice(3);
if ( filter.lastIndexOf('sa', 0) === 0 ) {
filterText = '@@' + filterText;
}
tr.cells[3].textContent = filterText + '\t';
tr.cells[4].textContent = (prettyRequestTypes[type] || type) + '\t';
tr.cells[5].appendChild(nodeFromURL(url, filter));
};
/******************************************************************************/
var renderLogEntry = function(entry) {
var tr;
var fvdc = firstVarDataCol;
switch ( entry.cat ) {
case 'error':
case 'info':
tr = createRow('1');
tr.cells[fvdc].textContent = entry.d0;
break;
case 'cosmetic':
case 'net':
tr = createRow('1111');
renderNetLogEntry(tr, entry);
break;
default:
tr = createRow('1');
tr.cells[fvdc].textContent = entry.d0;
break;
}
// Fields common to all rows.
var time = new Date(entry.tstamp);
tr.cells[0].textContent = time.toLocaleTimeString('fullwide', timeOptions);
tr.cells[0].title = time.toLocaleDateString('fullwide', dateOptions);
if ( entry.tab ) {
tr.classList.add('tab');
if ( entry.tab === noTabId ) {
tr.classList.add('tab_bts');
} else if ( entry.tab !== '' ) {
tr.classList.add('tab_' + entry.tab);
}
}
if ( entry.cat !== '' ) {
tr.classList.add('cat_' + entry.cat);
}
rowFilterer.filterOne(tr, true);
tbody.insertBefore(tr, tbody.firstChild);
};
/******************************************************************************/
var renderLogEntries = function(response) {
document.body.classList.toggle('colorBlind', response.colorBlind);
var entries = response.entries;
if ( entries.length === 0 ) {
return;
}
// Preserve scroll position
var height = tbody.offsetHeight;
var tabIds = response.tabIds;
var n = entries.length;
var entry;
for ( var i = 0; i < n; i++ ) {
entry = entries[i];
// Unlikely, but it may happen
if ( entry.tab && tabIds.hasOwnProperty(entry.tab) === false ) {
continue;
}
renderLogEntry(entries[i]);
}
// Prevent logger from growing infinitely and eating all memory. For
// instance someone could forget that it is left opened for some
// dynamically refreshed pages.
truncateLog(maxEntries);
var yDelta = tbody.offsetHeight - height;
if ( yDelta === 0 ) {
return;
}
// Chromium:
// body.scrollTop = good value
// body.parentNode.scrollTop = 0
if ( document.body.scrollTop !== 0 ) {
document.body.scrollTop += yDelta;
return;
}
// Firefox:
// body.scrollTop = 0
// body.parentNode.scrollTop = good value
var parentNode = document.body.parentNode;
if ( parentNode && parentNode.scrollTop !== 0 ) {
parentNode.scrollTop += yDelta;
}
};
/******************************************************************************/
var truncateLog = function(size) {
if ( size === 0 ) {
size = 5000;
}
var tbody = document.querySelector('#content tbody');
size = Math.min(size, 10000);
var tr;
while ( tbody.childElementCount > size ) {
tr = tbody.lastElementChild;
trJunkyard.push(tbody.removeChild(tr));
}
};
/******************************************************************************/
var onLogBufferRead = function(response) {
// This tells us the behind-the-scene tab id
noTabId = response.noTabId;
// This may have changed meanwhile
if ( response.maxEntries !== maxEntries ) {
maxEntries = response.maxEntries;
uDom('#maxEntries').val(maxEntries || '');
}
// Neuter rows for which a tab does not exist anymore
// TODO: sort to avoid using indexOf
var rowVoided = false;
for ( var tabId in allTabIds ) {
if ( allTabIds.hasOwnProperty(tabId) === false ) {
continue;
}
if ( response.tabIds.hasOwnProperty(tabId) ) {
continue;
}
toJunkyard(uDom('.tab_' + tabId));
if ( tabId === popupManager.tabId ) {
popupManager.toggleOff();
}
rowVoided = true;
}
allTabIds = response.tabIds;
renderLogEntries(response);
// Synchronize toolbar with content of log
uDom('#clear').toggleClass(
'disabled',
tbody.querySelector('tr') === null
);
setTimeout(readLogBuffer, 1200);
};
/******************************************************************************/
// This can be called only once, at init time. After that, this will be called
// automatically. If called after init time, this will be messy, and this would
// require a bit more code to ensure no multi time out events.
var readLogBuffer = function() {
messager.send({ what: 'readAll' }, onLogBufferRead);
};
/******************************************************************************/
var onMaxEntriesChanged = function() {
var raw = uDom(this).val();
try {
maxEntries = parseInt(raw, 10);
if ( isNaN(maxEntries) ) {
maxEntries = 0;
}
} catch (e) {
maxEntries = 0;
}
messager.send({
what: 'userSettings',
name: 'requestLogMaxEntries',
value: maxEntries
});
truncateLog(maxEntries);
};
/******************************************************************************/
var rowFilterer = (function() {
var filters = [];
var parseInput = function() {
filters = [];
var rawPart, not, hardBeg, hardEnd, reStr;
var raw = uDom('#filterInput').val().trim();
var rawParts = raw.split(/\s+/);
var i = rawParts.length;
while ( i-- ) {
rawPart = rawParts[i];
not = rawPart.charAt(0) === '!';
if ( not ) {
rawPart = rawPart.slice(1);
}
hardBeg = rawPart.charAt(0) === '|';
if ( hardBeg ) {
rawPart = rawPart.slice(1);
}
hardEnd = rawPart.slice(-1) === '|';
if ( hardEnd ) {
rawPart = rawPart.slice(0, -1);
}
if ( rawPart === '' ) {
continue;
}
// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
reStr = rawPart.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
if ( hardBeg ) {
reStr = '(?:^|\\s)' + reStr;
}
if ( hardEnd ) {
reStr += '(?:\\s|$)';
}
filters.push({
re: new RegExp(reStr, 'i'),
r: !not
});
}
};
var filterOne = function(tr, clean) {
var ff = filters;
var fcount = ff.length;
if ( fcount === 0 && clean === true ) {
return;
}
// do not filter out doc boundaries, they help separate important
// section of log.
var cl = tr.classList;
if ( cl.contains('maindoc') ) {
return;
}
if ( fcount === 0 ) {
cl.remove('f');
return;
}
var cc = tr.cells;
var ccount = cc.length;
var hit, j, f;
// each filter expression must hit (implicit and-op)
// if...
// positive filter expression = there must one hit on any field
// negative filter expression = there must be no hit on all fields
for ( var i = 0; i < fcount; i++ ) {
f = ff[i];
hit = !f.r;
for ( j = 0; j < ccount; j++ ) {
if ( f.re.test(cc[j].textContent) ) {
hit = f.r;
break;
}
}
if ( !hit ) {
cl.add('f');
return;
}
}
cl.remove('f');
};
var filterAll = function() {
// Special case: no filter
if ( filters.length === 0 ) {
uDom('#content tr').removeClass('f');
return;
}
var tbody = document.querySelector('#content tbody');
var rows = tbody.rows;
var i = rows.length;
while ( i-- ) {
filterOne(rows[i]);
}
};
var onFilterChangedAsync = (function() {
var timer = null;
var commit = function() {
timer = null;
parseInput();
filterAll();
};
return function() {
if ( timer !== null ) {
clearTimeout(timer);
}
timer = setTimeout(commit, 750);
};
})();
var onFilterButton = function() {
var cl = document.body.classList;
cl.toggle('f', cl.contains('f') === false);
};
uDom('#filterButton').on('click', onFilterButton);
uDom('#filterInput').on('input', onFilterChangedAsync);
return {
filterOne: filterOne,
filterAll: filterAll
};
})();
/******************************************************************************/
var toJunkyard = function(trs) {
trs.remove();
var i = trs.length;
while ( i-- ) {
trJunkyard.push(trs.nodeAt(i));
}
};
/******************************************************************************/
var clearBuffer = function() {
var tbody = document.querySelector('#content tbody');
var tr;
while ( tbody.firstChild !== null ) {
tr = tbody.lastElementChild;
trJunkyard.push(tbody.removeChild(tr));
}
uDom('#clear').addClass('disabled');
};
/******************************************************************************/
var toggleCompactView = function() {
document.body.classList.toggle(
'compactView',
document.body.classList.contains('compactView') === false
);
};
/******************************************************************************/
var popupManager = (function() {
var realTabId = null;
var localTabId = null;
var container = null;
var movingOverlay = null;
var popup = null;
var popupObserver = null;
var style = null;
var styleTemplate = [
'tr:not(.tab_{{tabId}}) {',
'cursor: not-allowed;',
'opacity: 0.2;',
'}'
].join('\n');
// Related to moving the popup around
var xnormal, ynormal, crect, dx, dy, vw, vh;
// Viewport data assumed to be properly set up
var positionFromNormal = function(x, y) {
if ( typeof x === 'number' ) {
if ( x < 0.5 ) {
container.style.setProperty('left', (x * vw) + 'px');
container.style.removeProperty('right');
} else {
container.style.removeProperty('left');
container.style.setProperty('right', ((1 - x) * vw) + 'px');
}
}
if ( typeof y === 'number' ) {
if ( y < 0.5 ) {
container.style.setProperty('top', (y * vh) + 'px');
container.style.removeProperty('bottom');
} else {
container.style.removeProperty('top');
container.style.setProperty('bottom', ((1 - y) * vh) + 'px');
}
}
// TODO: adjust size
};
var updateViewportData = function() {
crect = container.getBoundingClientRect();
vw = document.documentElement.clientWidth - crect.width;
vh = document.documentElement.clientHeight - crect.height;
};
var toNormalX = function(x) {
return xnormal = Math.max(Math.min(x / vw, 1), 0);
};
var toNormalY = function(y) {
return ynormal = Math.max(Math.min(y / vh, 1), 0);
};
var onMouseMove = function(ev) {
updateViewportData();
positionFromNormal(
toNormalX(ev.clientX + dx),
toNormalY(ev.clientY + dy)
);
ev.stopPropagation();
ev.preventDefault();
};
var onMouseUp = function(ev) {
updateViewportData();
positionFromNormal(
toNormalX(ev.clientX + dx),
toNormalY(ev.clientY + dy)
);
movingOverlay.removeEventListener('mouseup', onMouseUp);
movingOverlay.removeEventListener('mousemove', onMouseMove);
movingOverlay = null;
container.classList.remove('moving');
vAPI.localStorage.setItem('popupLastPosition', JSON.stringify({
xnormal: xnormal,
ynormal: ynormal
}));
ev.stopPropagation();
ev.preventDefault();
};
var onMouseDown = function(ev) {
if ( ev.target !== ev.currentTarget ) {
return;
}
container.classList.add('moving');
updateViewportData();
dx = crect.left - ev.clientX;
dy = crect.top - ev.clientY;
movingOverlay = document.getElementById('movingOverlay');
movingOverlay.addEventListener('mousemove', onMouseMove, true);
movingOverlay.addEventListener('mouseup', onMouseUp, true);
ev.stopPropagation();
ev.preventDefault();
};
var resizePopup = function() {
var popupBody = popup.contentWindow.document.body;
if ( popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth ) {
container.style.width = popupBody.clientWidth + 'px';
}
if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) {
popup.style.height = popupBody.clientHeight + 'px';
}
};
var onLoad = function() {
resizePopup();
popupObserver.observe(popup.contentDocument.body, {
subtree: true,
attributes: true
});
};
var toggleOn = function(td) {
var tr = td.parentNode;
var matches = tr.className.match(/(?:^| )tab_([^ ]+)/);
if ( matches === null ) {
return;
}
realTabId = localTabId = matches[1];
if ( localTabId === 'bts' ) {
realTabId = noTabId;
}
// Use last normalized position if one is defined.
// Default to top-right.
var x = 1, y = 0;
var json = vAPI.localStorage.getItem('popupLastPosition');
if ( json ) {
try {
var popupLastPosition = JSON.parse(json);
x = popupLastPosition.xnormal;
y = popupLastPosition.ynormal;
}
catch (e) {
}
}
container = document.getElementById('popupContainer');
updateViewportData();
positionFromNormal(x, y);
// Window controls
container.querySelector('div > span:first-child').addEventListener('click', toggleOff);
container.querySelector('div').addEventListener('mousedown', onMouseDown);
popup = document.createElement('iframe');
popup.addEventListener('load', onLoad);
popup.setAttribute('src', 'popup.html?tabId=' + realTabId);
popupObserver = new MutationObserver(resizePopup);
container.appendChild(popup);
style = document.querySelector('#content > style');
style.textContent = styleTemplate.replace('{{tabId}}', localTabId);
document.body.classList.add('popupOn');
};
var toggleOff = function() {
document.body.classList.remove('popupOn');
// Just in case
if ( movingOverlay !== null ) {
movingOverlay.removeEventListener('mousemove', onMouseMove, true);
movingOverlay.removeEventListener('mouseup', onMouseUp, true);
movingOverlay = null;
}
// Window controls
container.querySelector('div > span:first-child').removeEventListener('click', toggleOff);
container.querySelector('div').removeEventListener('mousedown', onMouseDown);
popup.removeEventListener('load', onLoad);
popupObserver.disconnect();
popupObserver = null;
popup.setAttribute('src', '');
container.removeChild(popup);
popup = null;
style.textContent = '';
style = null;
container = null;
realTabId = null;
};
var exports = {
toggleOn: function(ev) {
if ( realTabId === null ) {
toggleOn(ev.target);
}
},
toggleOff: function() {
if ( realTabId !== null ) {
toggleOff();
}
}
};
Object.defineProperty(exports, 'tabId', {
get: function() { return realTabId || 0; }
});
return exports;
})();
/******************************************************************************/
uDom.onLoad(function() {
readLogBuffer();
uDom('#compactViewToggler').on('click', toggleCompactView);
uDom('#clear').on('click', clearBuffer);
uDom('#maxEntries').on('change', onMaxEntriesChanged);
uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn);
});
/******************************************************************************/
})();

View File

@ -1,6 +1,6 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
uBlock - a browser extension to block requests.
Copyright (C) 2015 Raymond Hill
This program is free software: you can redistribute it and/or modify
@ -31,18 +31,18 @@
/******************************************************************************/
/******************************************************************************/
var LogEntry = function(details, result) {
this.init(details, result);
var LogEntry = function(args) {
this.init(args);
};
/******************************************************************************/
var logEntryFactory = function(details, result) {
var logEntryFactory = function(args) {
var entry = logEntryJunkyard.pop();
if ( entry ) {
return entry.init(details, result);
return entry.init(args);
}
return new LogEntry(details, result);
return new LogEntry(args);
};
var logEntryJunkyard = [];
@ -50,19 +50,23 @@ var logEntryJunkyardMax = 100;
/******************************************************************************/
LogEntry.prototype.init = function(details, result) {
LogEntry.prototype.init = function(args) {
this.tstamp = Date.now();
this.url = details.requestURL;
this.hostname = details.requestHostname;
this.type = details.requestType;
this.result = result;
this.tab = args[0] || '';
this.cat = args[1] || '';
this.d0 = args[2];
this.d1 = args[3];
this.d2 = args[4];
this.d3 = args[5];
return this;
};
/******************************************************************************/
LogEntry.prototype.dispose = function() {
this.url = this.hostname = this.type = this.result = '';
this.tstamp = 0;
this.tab = this.cat = '';
this.d0 = this.d1 = this.d2 = this.d3 = undefined;
if ( logEntryJunkyard.length < logEntryJunkyardMax ) {
logEntryJunkyard.push(this);
}
@ -96,13 +100,13 @@ LogBuffer.prototype.dispose = function() {
/******************************************************************************/
LogBuffer.prototype.writeOne = function(details, result) {
LogBuffer.prototype.writeOne = function(args) {
// Reusing log entry = less memory churning
var entry = this.buffer[this.writePtr];
if ( entry instanceof LogEntry === false ) {
this.buffer[this.writePtr] = logEntryFactory(details, result);
this.buffer[this.writePtr] = logEntryFactory(args);
} else {
entry.init(details, result);
entry.init(args);
}
this.writePtr += 1;
if ( this.writePtr === this.size ) {
@ -144,59 +148,49 @@ LogBuffer.prototype.readAll = function() {
/******************************************************************************/
// Tab id to log buffer instances
var logBuffers = {};
var logBuffer = null;
// After 30 seconds without being read, a buffer will be considered unused, and
// After 60 seconds without being read, a buffer will be considered unused, and
// thus removed from memory.
var logBufferObsoleteAfter = 30 * 1000;
var logBufferObsoleteAfter = 60 * 1000;
/******************************************************************************/
var writeOne = function(tabId, details, result) {
if ( logBuffers.hasOwnProperty(tabId) === false ) {
return;
var janitor = function() {
if (
logBuffer !== null &&
logBuffer.lastReadTime < (Date.now() - logBufferObsoleteAfter)
) {
logBuffer = logBuffer.dispose();
}
var logBuffer = logBuffers[tabId];
logBuffer.writeOne(details, result);
};
/******************************************************************************/
var readAll = function(tabId) {
if ( logBuffers.hasOwnProperty(tabId) === false ) {
logBuffers[tabId] = new LogBuffer();
if ( logBuffer !== null ) {
setTimeout(janitor, logBufferObsoleteAfter);
}
return logBuffers[tabId].readAll();
};
/******************************************************************************/
var isObserved = function(tabId) {
return logBuffers.hasOwnProperty(tabId);
};
/******************************************************************************/
var loggerJanitor = function() {
var logBuffer;
var obsolete = Date.now() - logBufferObsoleteAfter;
for ( var tabId in logBuffers ) {
if ( logBuffers.hasOwnProperty(tabId) === false ) {
continue;
}
logBuffer = logBuffers[tabId];
if ( logBuffer.lastReadTime < obsolete ) {
logBuffer.dispose();
delete logBuffers[tabId];
}
var writeOne = function() {
if ( logBuffer !== null ) {
logBuffer.writeOne(arguments);
}
setTimeout(loggerJanitor, loggerJanitorPeriod);
};
// The janitor will look for stale log buffer every 2 minutes.
var loggerJanitorPeriod = 2 * 60 * 1000;
/******************************************************************************/
setTimeout(loggerJanitor, loggerJanitorPeriod);
var readAll = function() {
if ( logBuffer === null ) {
logBuffer = new LogBuffer();
setTimeout(janitor, logBufferObsoleteAfter);
}
return logBuffer.readAll();
};
/******************************************************************************/
var isObserved = function() {
return logBuffer !== null;
};
/******************************************************************************/

View File

@ -66,7 +66,7 @@ var onMessage = function(request, sender, callback) {
/* falls through */
case 'cosmeticFiltersActivated':
// Net-based cosmetic filters are of no interest for logging purpose.
if ( µb.logger.isObserved(tabId) && request.type !== 'net' ) {
if ( µb.logger.isObserved() && request.type !== 'net' ) {
µb.logCosmeticFilters(tabId);
}
break;
@ -275,10 +275,6 @@ var getTargetTabId = function(tab) {
return '';
}
if ( tab.url.lastIndexOf(vAPI.getURL('devtools.html'), 0) !== 0 ) {
return tab.id;
}
// If the URL is that of the network request logger, fill the popup with
// the data from the tab being observed by the logger.
// This allows a user to actually modify filtering profile for
@ -574,22 +570,22 @@ var µb = µBlock;
/******************************************************************************/
var logCosmeticFilters = function(tabId, details) {
if ( µb.logger.isObserved(tabId) === false ) {
if ( µb.logger.isObserved() === false ) {
return;
}
var context = {
requestURL: details.pageURL,
requestHostname: µb.URI.hostnameFromURI(details.pageURL),
requestType: 'dom'
};
var selectors = details.matchedSelectors;
selectors.sort();
for ( var i = 0; i < selectors.length; i++ ) {
µb.logger.writeOne(tabId, context, 'cb:##' + selectors[i]);
µb.logger.writeOne(
tabId,
'cosmetic',
'cb:##' + selectors[i],
'dom',
details.pageURL
);
}
};
@ -1009,126 +1005,6 @@ vAPI.messaging.listen('whitelist.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// devtools.js
(function() {
'use strict';
/******************************************************************************/
var µb = µBlock;
/******************************************************************************/
var getPageDetails = function(callback) {
var out = {};
var tabIds = Object.keys(µb.pageStores);
// Special case: behind-the-scene virtual tab (does not really exist)
var pos = tabIds.indexOf(vAPI.noTabId);
if ( pos !== -1 ) {
tabIds.splice(pos, 1);
out[vAPI.noTabId] = vAPI.i18n('logBehindTheScene');
}
// This can happen
if ( tabIds.length === 0 ) {
callback(out);
return;
}
var countdown = tabIds.length;
var doCountdown = function() {
countdown -= 1;
if ( countdown === 0 ) {
callback(out);
}
};
// Let's not populate the page selector with reference to self
var devtoolsURL = vAPI.getURL('devtools.html');
var onTabDetails = function(tab) {
if ( tab && tab.url.lastIndexOf(devtoolsURL, 0) !== 0 ) {
out[tab.id] = tab.title;
}
doCountdown();
};
var i = countdown;
while ( i-- ) {
vAPI.tabs.get(tabIds[i], onTabDetails);
}
};
/******************************************************************************/
var evaluateStaticFiltering = function(details) {
// URL of context not provided, try to use the one for the given tab id.
var contextURL = details.contextURL;
if ( contextURL === '' ) {
var tabContext = µb.tabContextManager.lookup(details.tabId || 0);
if ( tabContext ) {
contextURL = tabContext.rawURL;
}
}
var pageHostname = µb.URI.hostnameFromURI(contextURL);
var pageDomain = µb.URI.domainFromHostname(pageHostname);
var context = {
rootHostname: pageHostname,
rootDomain: pageDomain,
pageHostname: pageHostname,
pageDomain: pageDomain,
requestURL: details.requestURL,
requestHostname: µb.URI.hostnameFromURI(details.requestURL),
requestType: details.requestType
};
return {
contextURL: contextURL,
result: µb.staticNetFilteringEngine.matchString(context)
};
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getPageDetails':
getPageDetails(callback);
return;
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'evaluateStaticFiltering':
response = evaluateStaticFiltering(request);
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('devtools.js', onMessage);
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
@ -1300,7 +1176,7 @@ vAPI.messaging.listen('settings.js', onMessage);
/******************************************************************************/
/******************************************************************************/
// devtool-log.js
// logger-ui.js
(function() {
@ -1323,10 +1199,19 @@ var onMessage = function(request, sender, callback) {
var response;
switch ( request.what ) {
case 'readLogBuffer':
case 'readAll':
var tabIds = {};
for ( var tabId in µb.pageStores ) {
if ( µb.pageStores.hasOwnProperty(tabId) ) {
tabIds[tabId] = true;
}
}
response = {
colorBlind: µb.userSettings.colorBlindFriendly,
entries: µb.logger.readAll(request.tabId)
entries: µb.logger.readAll(),
maxEntries: µb.userSettings.requestLogMaxEntries,
noTabId: vAPI.noTabId,
tabIds: tabIds
};
break;
@ -1337,7 +1222,7 @@ var onMessage = function(request, sender, callback) {
callback(response);
};
vAPI.messaging.listen('devtool-log.js', onMessage);
vAPI.messaging.listen('logger-ui.js', onMessage);
/******************************************************************************/

View File

@ -393,7 +393,7 @@ var renderPopup = function() {
// If you think the `=== true` is pointless, you are mistaken
uDom('#gotoLog').toggleClass('enabled', popupData.canRequestLog === true)
.attr('href', 'devtools.html?tabId=' + popupData.tabId);
.attr('href', 'logger-ui.html');
uDom('#gotoPick').toggleClass('enabled', popupData.canElementPicker === true);
var text;

View File

@ -477,7 +477,13 @@ vAPI.tabs.onPopup = function(details) {
if ( pageStore ) {
pageStore.logRequest(context, result);
}
µb.logger.writeOne(details.openerTabId, context, result);
µb.logger.writeOne(
details.openerTabId,
'net',
result,
'popup',
targetURL
);
// Not blocked
if ( µb.isAllowResult(result) ) {

View File

@ -92,7 +92,7 @@ var onBeforeRequest = function(details) {
// Possible outcomes: blocked, allowed-passthru, allowed-mirror
pageStore.logRequest(requestContext, result);
µb.logger.writeOne(tabId, requestContext, result);
µb.logger.writeOne(tabId, 'net', result, requestType, requestURL);
// Not blocked
if ( µb.isAllowResult(result) ) {
@ -187,7 +187,7 @@ var onBeforeRootFrameRequest = function(details) {
if ( pageStore ) {
pageStore.logRequest(context, result);
}
µb.logger.writeOne(tabId, context, result);
µb.logger.writeOne(tabId, 'net', result, 'main_frame', requestURL);
// Not blocked
if ( µb.isAllowResult(result) ) {
@ -279,7 +279,7 @@ var onBeforeBehindTheSceneRequest = function(details) {
}
pageStore.logRequest(context, result);
µb.logger.writeOne(vAPI.noTabId, context, result);
µb.logger.writeOne(vAPI.noTabId, 'net', result, details.type, details.url);
// Not blocked
if ( µb.isAllowResult(result) ) {
@ -328,7 +328,7 @@ var onHeadersReceived = function(details) {
var result = pageStore.filterRequestNoCache(context);
pageStore.logRequest(context, result);
µb.logger.writeOne(tabId, context, result);
µb.logger.writeOne(tabId, 'net', result, 'inline-script', details.url);
// Don't block
if ( µb.isAllowResult(result) ) {
@ -378,7 +378,7 @@ var onRootFrameHeadersReceived = function(details) {
var result = pageStore.filterRequestNoCache(context);
pageStore.logRequest(context, result);
µb.logger.writeOne(tabId, context, result);
µb.logger.writeOne(tabId, 'net', result, 'inline-script', details.url);
// Don't block
if ( µb.isAllowResult(result) ) {

42
src/logger-ui.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/logger-ui.css">
<title>uMatrix log</title>
</head>
<body class="compactView f">
<div id="toolbar">
<span id="compactViewToggler" class="button fa"></span>
<span id="clear" class="button fa disabled">&#xf12d;</span>
<span id="filterButton" class="button fa">&#xf0b0;</span><input id="filterInput" type="text" placeholder="logFilterPrompt">
<input id="maxEntries" type="text" size="5" title="logMaxEntriesTip">
</div>
<div id="content">
<style></style>
<table>
<colgroup><col><col><col><col><col></colgroup>
<tbody></tbody>
</table>
</div>
<div id="popupContainer">
<div><span>&#xf00d;</span></div>
</div>
<div id="movingOverlay"></div>
<div style="display: none;">
<div id="renderedURLTemplate"><span><span></span><b></b><span></span></span></div>
</div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/logger-ui.js"></script>
</body>
</html>