{{filter}}
could not be found in any of the currently enabled filter lists",
"description":"Message to show when a filter cannot be found in any filter lists"
},
+ "loggerSettingDiscardPrompt":{
+ "message":"Logger entries which do not fulfill all three conditions below will be automatically discarded:",
+ "description":"Logger setting: A sentence to describe the purpose of the settings below"
+ },
+ "loggerSettingPerEntryMaxAge":{
+ "message":"Preserve entries from the last {{input}} minutes",
+ "description":"A logger setting"
+ },
+ "loggerSettingPerTabMaxLoads":{
+ "message":"Preserve at most {{input}} page loads per tab",
+ "description":"A logger setting"
+ },
+ "loggerSettingPerTabMaxEntries":{
+ "message":"Preserve at most {{input}} entries per tab",
+ "description":"A logger setting"
+ },
+ "loggerSettingPerEntryLineCount":{
+ "message":"Use {{input}} lines per entry in vertically expanded mode",
+ "description":"A logger setting"
+ },
+ "loggerSettingHideColumnsPrompt":{
+ "message":"Hide columns:",
+ "description":"Logger settings: a sentence to describe the purpose of the checkboxes below"
+ },
+ "loggerSettingHideColumnTime":{
+ "message":"{{input}} Time",
+ "description":"A label for the time column"
+ },
+ "loggerSettingHideColumnFilter":{
+ "message":"{{input}} Filter/rule",
+ "description":"A label for the filter or rule column"
+ },
+ "loggerSettingHideColumnContext":{
+ "message":"{{input}} Context",
+ "description":"A label for the context column"
+ },
+ "loggerSettingHideColumnPartyness":{
+ "message":"{{input}} Partyness",
+ "description":"A label for the partyness column"
+ },
"aboutChangelog":{
"message":"Changelog",
"description":""
diff --git a/src/css/fa-icons.css b/src/css/fa-icons.css
index aca86a1f8..907dcadd8 100644
--- a/src/css/fa-icons.css
+++ b/src/css/fa-icons.css
@@ -63,6 +63,7 @@
.fa-icon > .fa-icon_home {
width: calc(1em * 1612 / 1792);
}
+.fa-icon > .fa-icon_cog,
.fa-icon > .fa-icon_floppy-o,
.fa-icon > .fa-icon_info-circle,
.fa-icon > .fa-icon_pause-circle-o,
diff --git a/src/css/logger-ui-inspector.css b/src/css/logger-ui-inspector.css
index f3afd47a6..2055f6bea 100644
--- a/src/css/logger-ui-inspector.css
+++ b/src/css/logger-ui-inspector.css
@@ -85,13 +85,13 @@
display: none;
}
-#domInspector.vCompact li:not(.hasCosmeticHide):not(.isCosmeticHide) {
+#domInspector:not(.vExpanded) li:not(.hasCosmeticHide):not(.isCosmeticHide) {
display: none;
}
#domInspector #domTree > li {
display: block;
}
-#domInspector.vCompact ul {
+#domInspector:not(.vExpanded) ul {
display: block;
}
#domInspector li > ul > li:not(.hasCosmeticHide):not(.isCosmeticHide) {
diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css
index 15adb86b4..b87c8dd87 100644
--- a/src/css/logger-ui.css
+++ b/src/css/logger-ui.css
@@ -38,10 +38,16 @@ textarea {
background-color: #eee;
}
#pageSelector {
- margin-right: 1em;
padding: 0.25em 0;
width: 28em;
}
+body[dir="ltr"] #pageSelector {
+ margin-right: 1em;
+ }
+body[dir="rtl"] #pageSelector {
+ margin-left: 1em;
+ }
+
#showpopup {
display: inline-flex;
align-items: center;
@@ -56,11 +62,16 @@ textarea {
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
- right: 0;
}
#info:hover {
fill: #000;
}
+body[dir="ltr"] #info {
+ right: 0;
+ }
+body[dir="rtl"] #info {
+ left: 0;
+ }
@media (max-width: 600px) {
#info {
display: none;
@@ -81,13 +92,14 @@ textarea {
flex-direction: column;
}
.vscrollable {
+ direction: ltr;
flex-grow: 1;
font-size: small;
overflow-x: hidden;
overflow-y: auto;
}
-.vCompact .vCompactToggler.button {
+.inspector:not(.vExpanded) .vCompactToggler.button {
transform: scaleY(-1)
}
.hCompact .hCompactToggler.button {
@@ -125,14 +137,16 @@ textarea {
#netInspector #filterInput > input {
min-width: 18em;
}
-#netInspector #maxEntries {
- margin: 0 2em;
- }
#netInspector #filterExprButton {
position: absolute;
- right: 0;
transform: scaleY(-1);
}
+body[dir="ltr"] #netInspector #filterExprButton {
+ right: 0;
+ }
+body[dir="rtl"] #netInspector #filterExprButton {
+ left: 0;
+ }
#netInspector #filterExprButton:hover {
background-color: transparent;
}
@@ -146,10 +160,16 @@ textarea {
position: absolute;
flex-direction: column;
font-size: small;
- right: 0;
top: 100%;
z-index: 100;
}
+body[dir="ltr"] #netInspector #filterExprPicker {
+ right: 0;
+ }
+body[dir="rtl"] #netInspector #filterExprPicker {
+ left: 0;
+ }
+
#netInspector #filterExprGroup:hover #filterExprButton.expanded ~ #filterExprPicker {
display: flex;
}
@@ -184,145 +204,169 @@ textarea {
background-color: lightblue;
border: 1px solid lightblue;
}
+#netInspector #settings {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ position: absolute;
+}
+body[dir="ltr"] #netInspector #settings {
+ right: 0;
+ }
+body[dir="rtl"] #netInspector #settings {
+ left: 0;
+ }
-#netInspector table {
- border: 0;
- border-collapse: collapse;
- direction: ltr;
- table-layout: fixed;
+#netInspector .vscrollable {
+ overflow: hidden;
+ }
+#vwRenderer {
+ box-sizing: border-box;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
width: 100%;
}
-#netInspector table > colgroup > col:nth-of-type(1) {
- width: 4.6em;
+#vwRenderer #vwScroller {
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ position: absolute;
+ width: 100%;
}
-#netInspector table > colgroup > col:nth-of-type(2) {
- width: 16%;
+#vwRenderer #vwScroller #vwVirtualContent {
+ overflow: hidden;
}
-#netInspector table > colgroup > col:nth-of-type(3) {
- width: 2.1em;
+#vwRenderer #vwContent {
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ width: 100%;
}
-#netInspector table > colgroup > col:nth-of-type(4) {
- width: 20%;
+#vwRenderer .logEntry {
+ display: block;
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ width: 100%;
}
-#netInspector table > colgroup > col:nth-of-type(5) {
- width: 2.4em;
- }
-#netInspector table > colgroup > col:nth-of-type(6) {
- width: 6em;
- }
-#netInspector table > colgroup > col:nth-of-type(7) {
- width: calc(100% - 4.6em - 16% - 2.1em - 20% - 2.4em - 6em);
- }
-#netInspector.f table tr.f {
+#vwRenderer .logEntry:empty {
display: none;
}
-
-#netInspector tr.cat_info {
- color: #00f;
- }
-#netInspector tr.blocked {
- background-color: rgba(192, 0, 0, 0.1);
- }
-body.colorBlind #netInspector tr.blocked {
- background-color: rgba(0, 19, 110, 0.1);
- }
-#netInspector tr.nooped {
- background-color: rgba(108, 108, 108, 0.1);
- }
-body.colorBlind #netInspector tr.nooped {
- background-color: rgba(96, 96, 96, 0.1);
- }
-#netInspector tr.allowed {
- background-color: rgba(0, 160, 0, 0.1);
- }
-body.colorBlind #netInspector tr.allowed {
- background-color: rgba(255, 194, 57, 0.1)
- }
-#netInspector tr.cosmetic,
-#netInspector tr.redirect {
- background-color: rgba(255, 255, 0, 0.1);
- }
-body.colorBlind #netInspector tr.cosmetic,
-body.colorBlind #netInspector tr.redirect {
- background-color: rgba(0, 19, 110, 0.1);
- }
-#netInspector tr.maindoc {
- background-color: #666;
- color: white;
- text-align: center;
- }
-
-body #netInspector 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;
- }
-#netInspector tr td {
- border-top: 1px solid #ccc;
- }
-#netInspector tr td:first-of-type {
- border-left: none;
- }
-#netInspector tr td:last-of-type {
- border-right: none;
- }
-#netInspector.vCompact tr:not(.vExpanded) td {
- overflow: hidden;
- text-overflow: ellipsis;
+#vwRenderer .logEntry > div {
+ height: 100%;
white-space: nowrap;
}
-#netInspector tr[data-tabid].void {
+#vwRenderer .logEntry > div.blocked {
+ background-color: rgba(192, 0, 0, 0.1);
+ }
+body.colorBlind #vwRenderer .logEntry > div.blocked {
+ background-color: rgba(0, 19, 110, 0.1);
+ }
+#vwRenderer .logEntry > div.nooped {
+ background-color: rgba(108, 108, 108, 0.1);
+ }
+body.colorBlind #vwRenderer .logEntry > div.nooped {
+ background-color: rgba(96, 96, 96, 0.1);
+ }
+#vwRenderer .logEntry > div.allowed {
+ background-color: rgba(0, 160, 0, 0.1);
+ }
+body.colorBlind #vwRenderer .logEntry > div.allowed {
+ background-color: rgba(255, 194, 57, 0.1)
+ }
+#vwRenderer .logEntry > div.cosmetic,
+#vwRenderer .logEntry > div.redirect {
+ background-color: rgba(255, 255, 0, 0.1);
+ }
+body.colorBlind #vwRenderer .logEntry > div.cosmetic,
+body.colorBlind #vwRenderer .logEntry > div.redirect {
+ background-color: rgba(0, 19, 110, 0.1);
+ }
+#vwRenderer .logEntry > div[data-type="tabLoad"] {
+ background-color: #666;
+ color: white;
+ }
+#vwRenderer .logEntry > div[data-type="error"] {
+ color: #800;
+ }
+#vwRenderer .logEntry > div[data-type="info"] {
+ color: #008;
+ }
+#vwRenderer .logEntry > div.voided {
opacity: 0.3;
}
-#netInspector tr[data-tabid].void:hover {
+#vwRenderer .logEntry > div.voided:hover {
opacity: 0.7;
}
-#netInspector tr td:nth-of-type(1) {
- cursor: default;
- text-align: right;
+#vwRenderer .logEntry > div > span {
+ border: 1px solid #ccc;
+ border-top: 0;
+ border-right: 0;
+ box-sizing: border-box;
+ display: inline-block;
+ height: 100%;
+ overflow: hidden;
+ padding: 0.2em;
+ vertical-align: middle;
white-space: nowrap;
+ word-break: break-all;
}
-#netInspector tr td:nth-of-type(2) {
+#vwRenderer .logEntry > div.canDetails:hover > span {
+ background-color: rgba(0,0,0,0.04);
}
-#netInspector tr.canLookup td:nth-of-type(2) {
+body[dir="ltr"] #vwRenderer .logEntry > div > span:first-child {
+ border-left: 0;
+ }
+body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child {
+ border-right: 0;
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(1) {
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(2) {
+ }
+#vwRenderer #vwContent .logEntry > div > span:nth-of-type(2) {
+ text-overflow: ellipsis;
+ }
+.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(2) {
+ overflow-y: auto;
+ white-space: pre-line;
+ }
+#vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) {
+ text-align: center;
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(3) {
+ font: 12px monospace;
+ padding-left: 0.3em;
+ padding-right: 0.3em;
+ text-align: center;
+ }
+#vwRenderer .logEntry > div.canDetails:hover > span:nth-of-type(2),
+#vwRenderer .logEntry > div.canDetails:hover > span:nth-of-type(3),
+#vwRenderer .logEntry > div.canDetails:hover > span:nth-of-type(5) {
+ background: rgba(0, 0, 0, 0.08);
cursor: zoom-in;
}
-#netInspector tr.cat_net td:nth-of-type(3),
-#netInspector tr.cat_cosmetic td:nth-of-type(3),
-#netInspector tr.cat_redirect td:nth-of-type(3) {
- font: 12px monospace;
- text-align: center;
- white-space: nowrap;
- }
-#netInspector tr.cat_net td:nth-of-type(3) {
- cursor: pointer;
- }
-#netInspector tr.cat_net td:nth-of-type(3):hover {
- background: #ccc;
- }
-#netInspector tr td:nth-of-type(4) {
- }
-#netInspector tr[data-dochn] td:nth-of-type(4) {
+#netInspector:not(.vExpanded) #vwRenderer .logEntry > div > span:nth-of-type(4) {
direction: rtl;
}
-#netInspector tr td:nth-of-type(5) {
- cursor: default;
- overflow: visible !important;
- position: relative;
+#vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) {
+ text-overflow: ellipsis;
+ }
+.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) {
+ overflow-y: auto;
+ text-overflow: clip;
+ white-space: pre-line;
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(5) {
text-align: center;
}
-#netInspector tr td[data-parties]:nth-of-type(5) {
- cursor: zoom-in;
- }
/* visual for tabless network requests */
-#netInspector tr.tab_bts td:nth-of-type(5)::before {
- border: 5px solid #bbb;
+#vwRenderer .logEntry > div > span:nth-of-type(5) {
+ position: relative;
+ }
+#vwRenderer .logEntry > div[data-tabid="-1"] > span:nth-of-type(5)::before {
+ border: 5px solid #ccc;
border-bottom: 0;
border-top: 0;
bottom: 0;
@@ -334,49 +378,61 @@ body #netInspector td {
width: calc(100% - 10px);
z-index: -1;
}
-/* visual for quick tooltip */
-#netInspector tr td[data-parties]:nth-of-type(5):active::after {
- background-color: #feb;
- border: 1px outset #feb;
- border-left: 5px solid gray;
- color: black;
- content: attr(data-parties);
- left: 100%;
- padding: 0.4em 0.6em;
- position: absolute;
- text-align: left;
- top: -50%;
- white-space: pre;
-}
-#netInspector tr.cat_net td:nth-of-type(7) > span > b {
+#vwRenderer .logEntry > div > span:nth-of-type(6) {
+ }
+#vwRenderer #vwContent .logEntry > div > span:nth-of-type(6) {
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(7) {
+ }
+#vwRenderer #vwContent .logEntry > div > span:nth-of-type(7) {
+ text-overflow: ellipsis;
+ }
+.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(7) {
+ overflow-y: auto;
+ white-space: pre-line;
+ }
+#vwRenderer .logEntry > div > span:nth-of-type(7) > span > b {
font-weight: bold;
}
-#netInspector tr td:nth-of-type(7) b {
+#vwRenderer .logEntry > div > span:nth-of-type(7) b {
font-weight: normal;
}
-#netInspector tr.blocked td:nth-of-type(7) b {
+#vwRenderer .logEntry > div.blocked > span:nth-of-type(7) b {
background-color: rgba(192, 0, 0, 0.2);
}
-body.colorBlind #netInspector tr.blocked td:nth-of-type(7) b {
+body.colorBlind #vwRenderer .logEntry > div.blocked > span:nth-of-type(7) b {
background-color: rgba(0, 19, 110, 0.2);
}
-#netInspector tr.nooped td:nth-of-type(7) b {
+#vwRenderer .logEntry > div.nooped > span:nth-of-type(7) b {
background-color: rgba(108, 108, 108, 0.2);
}
-body.colorBlind #netInspector tr.nooped td:nth-of-type(7) b {
+body.colorBlind #vwRenderer .logEntry > div.nooped > span:nth-of-type(7) b {
background-color: rgba(96, 96, 96, 0.2);
}
-#netInspector tr.allowed td:nth-of-type(7) b {
+#vwRenderer .logEntry > div.allowed > span:nth-of-type(7) b {
background-color: rgba(0, 160, 0, 0.2);
}
-body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b {
+body.colorBlind #vwRenderer .logEntry > div.allowed > span:nth-of-type(7) b {
background-color: rgba(255, 194, 57, 0.2);
}
+#vwRenderer #vwBottom {
+ background-color: #00F;
+ height: 0;
+ overflow: hidden;
+ width: 100%;
+ }
+#vwRenderer #vwLineSizer {
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ visibility: hidden;
+ width: 100%;
+ }
#popupContainer {
background: white;
border: 1px solid gray;
- border-radius: 3px;
bottom: 0;
box-sizing: border-box;
display: none;
@@ -389,16 +445,13 @@ body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b {
display: block;
}
-.modalDialog {
+#modalOverlay {
align-items: center;
- -webkit-align-items: center;
background-color: rgba(0, 0, 0, 0.5);
border: 0;
bottom: 0;
- display: flex;
- display: -webkit-flex;
+ display: none;
justify-content: center;
- -webkit-justify-content: center;
left: 0;
margin: 0;
position: fixed;
@@ -406,27 +459,56 @@ body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b {
top: 0;
z-index: 400;
}
-
-.modalDialog .dialog {
+#modalOverlay.on {
+ display: flex;
+ }
+#modalOverlay > div {
+ position: relative;
+ }
+#modalOverlay > div > div:nth-of-type(1) {
background-color: white;
- border: 2px solid white;
+ border: 0;
box-sizing: border-box;
- width: 90vw;
+ padding: 1em;
max-height: 90vh;
overflow-y: auto;
+ width: 90vw;
+ }
+#modalOverlay > div > div:nth-of-type(2) {
+ stroke: #000;
+ stroke-width: 3px;
+ position: absolute;
+ width: 1.6em;
+ height: 1.6em;
+ bottom: calc(100% + 2px);
+ background-color: white;
+ }
+body[dir="ltr"] #modalOverlay > div > div:nth-of-type(2) {
+ right: 0;
+ }
+body[dir="rtl"] #modalOverlay > div > div:nth-of-type(2) {
+ left: 0;
+ }
+#modalOverlay > div > div:nth-of-type(2):hover {
+ background-color: #eee;
+ }
+#modalOverlay > div > div:nth-of-type(2) > * {
+ pointer-events: none;
}
-#netFilteringDialog .dialog p {
- line-height: 2em;
+#netFilteringDialog {
+ font-size: 90%;
}
-
-#netFilteringDialog .dialog select {
+#netFilteringDialog a {
+ text-decoration: none;
+ }
+#netFilteringDialog select {
max-width: 75%;
outline: none;
padding: 0.2em;
}
-
-#netFilteringDialog .dialog > div.preview {
+#netFilteringDialog > .preview {
+ align-items: center;
/* http://lea.verou.me/css3patterns/ */
background-color: #aaa;
background-image:
@@ -448,35 +530,28 @@ body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b {
);
background-position:0 0, 9px 9px;
background-size: 18px 18px;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1em;
+ padding: 0.5em;
text-align: center;
}
-#netFilteringDialog .dialog > div.preview > * {
+#netFilteringDialog > .preview > * {
+ background-color: white;
max-width: 100%;
- max-height: 40vh;
+ max-height: 20vh;
+ }
+#netFilteringDialog > .preview > span {
+ cursor: pointer;
+ padding: 0.5em;
}
-#netFilteringDialog .dialog table {
- border: 0;
- border-collapse: collapse;
- table-layout: fixed;
- width: 100%;
- }
-#netFilteringDialog .dialog table > colgroup > col:nth-of-type(1) {
- width: 3.8em;
- }
-#netFilteringDialog .dialog table > colgroup > col:nth-of-type(2) {
- }
-
-#netFilteringDialog .dialog td {
- border: 0;
- padding: 0;
- vertical-align: middle;
- }
-#netFilteringDialog .dialog > div.headers {
+#netFilteringDialog > .headers {
border-bottom: 1px solid #888;
+ line-height: 2;
position: relative;
}
-#netFilteringDialog .dialog > div.headers > span.header {
+#netFilteringDialog > .headers > .header {
background-color: #eee;
border: 1px solid #aaa;
border-bottom: 1px solid #888;
@@ -485,98 +560,164 @@ body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b {
color: #888;
cursor: pointer;
display: inline-block;
- font-size: small;
- line-height: 2em;
- margin-left: 0.5em;
padding: 0 1em;
position: relative;
text-align: center;
top: 1px;
}
-#netFilteringDialog .dialog > div.headers > span.header.selected {
+#netFilteringDialog[data-pane="details"] > .headers > [data-pane="details"],
+#netFilteringDialog[data-pane="dynamic"] > .headers > [data-pane="dynamic"],
+#netFilteringDialog[data-pane="static"] > .headers > [data-pane="static"] {
background-color: white;
border-color: #888;
border-bottom: 1px solid white;
color: black;
}
-#netFilteringDialog .dialog > div.headers > span.tools {
- display: inline-block;
+#netFilteringDialog > .headers > .tools {
+ bottom: 0;
+ display: flex;
position: absolute;
- top: 50%;
- transform: translate(0, -50%);
}
-body[dir="ltr"] #netFilteringDialog .dialog > div.headers > span.tools {
- right: 0.1em;
+body[dir="ltr"] #netFilteringDialog > .headers > .tools {
+ right: 0;
}
-body[dir="rtl"] #netFilteringDialog .dialog > div.headers > span.tools {
- left: 0.1em;
+body[dir="rtl"] #netFilteringDialog > .headers > .tools {
+ left: 0;
}
-#netFilteringDialog .dialog > div.headers > span.tools > span {
+#netFilteringDialog > .headers > .tools > span {
cursor: pointer;
- font-size: 1.2em;
- padding: 0.2em 0.4em;
+ font-size: 1.5em;
+ padding: 0 0.25em;
text-align: center;
}
-#netFilteringDialog .dialog > div.headers > span.tools > span:hover {
+#netFilteringDialog > .headers > .tools > span:hover {
background-color: #eee;
}
-#netFilteringDialog .dialog > div.containers {
- height: 40vh;
- overflow: hidden;
- overflow-y: auto;
- }
-#netFilteringDialog .dialog > div.containers > div {
+#netFilteringDialog.cosmeticRealm > .headers > .dynamic,
+#netFilteringDialog.cosmeticRealm > .panes > .dynamic {
display: none;
}
-#netFilteringDialog .dialog > div.containers > div.selected {
- display: block;
+#netFilteringDialog.cosmeticRealm > .headers > .static,
+#netFilteringDialog.cosmeticRealm > .panes > .static {
+ display: none;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar select {
- font: 14px;
- height: 2.5em;
+#netFilteringDialog > div.panes {
+ min-height: 40vh;
+ overflow: hidden;
+ overflow-y: auto;
+ padding-top: 1em;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar #saveRules {
+#netFilteringDialog > div.panes > div {
+ display: none;
+ }
+#netFilteringDialog[data-pane="details"] > .panes > [data-pane="details"],
+#netFilteringDialog[data-pane="dynamic"] > .panes > [data-pane="dynamic"],
+#netFilteringDialog[data-pane="static"] > .panes > [data-pane="static"] {
+ display: flex;
+ flex-direction: column;
+ }
+#netFilteringDialog > .panes > .details > div {
+ align-items: stretch;
+ background-color: #e6e6e6;
+ border: 0;
+ border-bottom: 1px solid white;
+ display: flex;
+ min-height: 2.2em;
+ }
+#netFilteringDialog > .panes > .details > div > span {
+ align-items: center;
+ display: inline-flex;
+ flex-wrap: wrap;
+ padding: 0.25em 0.5em;
+ }
+#netFilteringDialog > .panes > .details > div > span:nth-of-type(1) {
+ border: 0;
+ flex-grow: 0;
+ flex-shrink: 0;
+ justify-content: flex-end;
+ width: 8em;
+ }
+body[dir="ltr"] #netFilteringDialog > .panes > .details > div > span:nth-of-type(1) {
+ border-right: 1px solid white;
+ }
+body[dir="rtl"] #netFilteringDialog > .panes > .details > div > span:nth-of-type(1) {
+ border-left: 1px solid white;
+ }
+#netFilteringDialog > .panes > .details > div > span:nth-of-type(2) {
+ max-height: 20vh;
+ overflow: hidden auto;
+ white-space: pre-line
+ }
+#netFilteringDialog > .panes > .details > div > span:nth-of-type(2):not(.prose) {
+ word-break: break-all;
+ }
+#netFilteringDialog > .panes > .details > div > span:nth-of-type(2) .fa-icon {
+ font-size: 110%;
+ opacity: 0.5;
+ vertical-align: bottom;
+ }
+#netFilteringDialog > .panes > .details > div > span:nth-of-type(2) .fa-icon:hover {
+ opacity: 1;
+ }
+#netFilteringDialog > div.panes > .dynamic > .toolbar {
+ padding-bottom: 1em;
+ }
+#netFilteringDialog > div.panes > .dynamic .row {
+ display: flex;
+ min-height: 2.2em;
+ }
+#netFilteringDialog > div.panes > .dynamic .row > span:nth-of-type(1) {
+ align-self: stretch;
+ border: 0;
+ display: inline-flex;
+ flex-grow: 0;
+ flex-shrink: 0;
+ text-align: center;
+ width: 4.5em;
+ }
+body[dir="ltr"] #netFilteringDialog > div.panes > .dynamic .row > span:nth-of-type(1) {
+ border-right: 1px solid white;
+ }
+body[dir="rtl"] #netFilteringDialog > div.panes > .dynamic .row > span:nth-of-type(1) {
+ border-left: 1px solid white;
+ }
+#netFilteringDialog > div.panes > .dynamic .row > span:nth-of-type(2) {
+ align-self: center;
+ padding: 0 0.5em;
+ }
+#netFilteringDialog > div.panes > .dynamic > .toolbar #saveRules {
background-color: #ffe;
border: 1px solid #ddc;
border-radius: 4px;
fill: #888;
cursor: pointer;
- font-size: 1.6em;
- margin: 0.1em;
- padding: 0.25em 0.5em;
+ font-size: 2em;
visibility: hidden;
+ width: 100%;
}
-body.dirty #netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar #saveRules {
+body.dirty #netFilteringDialog > div.panes > .dynamic > .toolbar #saveRules {
visibility: visible;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar #saveRules:hover {
+#netFilteringDialog > div.panes > .dynamic > .toolbar #saveRules:hover {
fill: black;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar tr.entry {
+#netFilteringDialog > div.panes > .dynamic > .toolbar .entry {
display: none;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry {
+#netFilteringDialog > div.panes > .dynamic .entry {
background-color: #e6e6e6;
border: 0;
border-bottom: 1px solid white;
- font-size: 13px;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry:hover {
+#netFilteringDialog > div.panes > .dynamic .entry:hover {
background-color: #f0f0f0;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td:first-of-type {
- border: 0;
- border-right: 1px solid white;
- text-align: center;
- }
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action {
+#netFilteringDialog > div.panes > .dynamic .entry > .action {
background-color: transparent;
border: 0;
cursor: pointer;
- height: 2em;
- width: 100%;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span {
+#netFilteringDialog > div.panes > .dynamic .entry > .action > span {
background-color: transparent;
border: 0;
display: inline-block;
@@ -585,90 +726,94 @@ body.dirty #netFilteringDialog .dialog > div.containers > div.dynamic > table.to
visibility: hidden;
width: 33.33%;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.allow {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.allow {
background-color: rgba(0, 160, 0, 0.3);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.allow {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.allow {
background-color: rgba(255, 194, 57, 0.4);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.noop {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.noop {
background-color: rgba(108, 108, 108, 0.3);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.noop {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.noop {
background-color: rgba(96, 96, 96, 0.4);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.block {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.block {
background-color: rgba(192, 0, 0, 0.3);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.block {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.block {
background-color: rgba(0, 19, 110, 0.4);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.allow {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.own.allow {
background-color: rgba(0, 160, 0, 1);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.allow {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.own.allow {
background-color: rgba(255, 194, 57, 1);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.noop,
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.noop {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.own.noop,
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.own.noop {
background-color: rgba(108, 108, 108, 1);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.block {
+#netFilteringDialog > div.panes > .dynamic .entry > .action.own.block {
background-color: rgba(192, 0, 0, 1);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action.own.block {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action.own.block {
background-color: rgba(0, 19, 110, 1);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action:not(.own):hover > span {
+#netFilteringDialog > div.panes > .dynamic .entry > .action:not(.own):hover > span {
opacity: 0.2;
visibility: visible;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action:not(.own):hover > span:hover {
+#netFilteringDialog > div.panes > .dynamic .entry > .action:not(.own):hover > span:hover {
opacity: 0.75;
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.allow {
+#netFilteringDialog > div.panes > .dynamic .entry > .action > .allow {
background-color: rgb(0, 160, 0);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.allow {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action > .allow {
background-color: rgb(255, 194, 57);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.noop {
+#netFilteringDialog > div.panes > .dynamic .entry > .action > .noop {
background-color: rgb(108, 108, 108);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.block {
+#netFilteringDialog > div.panes > .dynamic .entry > .action > .block {
background-color: rgb(192, 0, 0);
}
-body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td > div.action > span.block {
+body.colorBlind #netFilteringDialog > div.panes > .dynamic .entry > .action > .block {
background-color: rgb(0, 19, 110);
}
-#netFilteringDialog .dialog > div.containers > div.dynamic tr.entry > td.url {
+#netFilteringDialog > div.panes > .dynamic .entry > .url {
overflow: hidden;
- padding-left: 4px;
text-overflow: ellipsis;
white-space: nowrap;
}
-#netFilteringDialog .dialog > div.containers > div.static > p {
- margin: 0.75em 0;
+#netFilteringDialog > div.panes > div.static > div {
+ line-height: 2;
}
-#netFilteringDialog .dialog > div.containers > div.static textarea {
+#netFilteringDialog > div.panes > div.static > div {
+ padding-bottom: 1em;
+ }
+#netFilteringDialog > div.panes > div.static textarea {
height: 6em;
+ max-height: 20vh;
+ min-height: 10vh;
+ word-break: break-all;
}
-#netFilteringDialog .dialog > div.containers > div.static > p:nth-of-type(2) {
+#netFilteringDialog > div.panes > div.static > div:nth-of-type(2) {
text-align: center;
}
-#filterFinderDialog .dialog {
- padding: 1em;
+#filterFinderDialog {
word-break: break-all;
}
-#filterFinderDialog .dialog code {
+#filterFinderDialog code {
background: #eee;
font-size: 85%;
padding: 3px;
unicode-bidi: plaintext;
white-space: pre-wrap;
}
-#filterFinderDialog .dialog ul {
+#filterFinderDialog ul {
font-size: larger;
}
#filterFinderDialog .filterFinderListEntry {
@@ -688,13 +833,40 @@ body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.e
#filterFinderDialog .filterFinderListEntry a.fa-icon[href=""] {
display: none;
}
-#filterFinderDialog .dialog > *:first-child {
+#filterFinderDialog > *:first-child {
margin-top: 0;
}
-#filterFinderDialog .dialog > *:last-child {
+#filterFinderDialog > *:last-child {
margin-bottom: 0;
}
-.hide {
- display: none;
+#loggerSettingsDialog {
+ display: flex;
+ flex-direction: column;
+ }
+#loggerSettingsDialog > div {
+ padding-bottom: 1em;
+ }
+#loggerSettingsDialog > div:last-of-type {
+ padding-bottom: 0;
+ }
+#loggerSettingsDialog ul {
+ padding: 0;
+ }
+body[dir="ltr"] #loggerSettingsDialog ul {
+ padding-left: 2em;
+ }
+body[dir="rtl"] #loggerSettingsDialog ul {
+ padding-right: 2em;
+ }
+#loggerSettingsDialog li {
+ list-style-type: none;
+ margin: 0.5em 0 0 0;
+ }
+#loggerSettingsDialog input {
+ max-width: 6em;
+ }
+
+.hide {
+ display: none !important;
}
diff --git a/src/img/fontawesome/fontawesome-defs.svg b/src/img/fontawesome/fontawesome-defs.svg
index ca6d168cd..380545c13 100644
--- a/src/img/fontawesome/fontawesome-defs.svg
+++ b/src/img/fontawesome/fontawesome-defs.svg
@@ -29,6 +29,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68
, , , , and will
+// Anything else than , , , , , and will
// be rendered as plain text.
-// For , only the type attribute is allowed.
// For , only href attribute must be present, and it MUST starts with
// `https://`, and includes no single- or double-quotes.
// No HTML entities are allowed, there is code to handle existing HTML
// entities already present in translation files until they are all gone.
-var reSafeTags = /^([\s\S]*?)<(b|code|em|i|span)>(.+?)<\/\2>([\s\S]*)$/,
- reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/,
- reInput = /^input type=(['"])([a-z]+)\1$/,
- reSafeLink = /^([\s\S]*?)<(a href=['"]https:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/,
- reLink = /^a href=(['"])(https:\/\/[^'"]+)\1$/;
+const reSafeTags = /^([\s\S]*?)<(b|code|em|i|span)>(.+?)<\/\2>([\s\S]*)$/;
+const reSafeLink = /^([\s\S]*?)<(a href=['"]https:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/;
+const reLink = /^a href=(['"])(https:\/\/[^'"]+)\1$/;
-var safeTextToTagNode = function(text) {
- var matches, node;
+const safeTextToTagNode = function(text) {
if ( text.lastIndexOf('a ', 0) === 0 ) {
- matches = reLink.exec(text);
+ const matches = reLink.exec(text);
if ( matches === null ) { return null; }
- node = document.createElement('a');
+ const node = document.createElement('a');
node.setAttribute('href', matches[2]);
return node;
}
- if ( text.lastIndexOf('input ', 0) === 0 ) {
- matches = reInput.exec(text);
- if ( matches === null ) { return null; }
- node = document.createElement('input');
- node.setAttribute('type', matches[2]);
- return node;
- }
// Firefox extension validator warns if using a variable as argument for
// document.createElement().
switch ( text ) {
@@ -79,8 +68,8 @@ var safeTextToTagNode = function(text) {
}
};
-var safeTextToTextNode = (function() {
- let entities = new Map([
+const safeTextToTextNode = (function() {
+ const entities = new Map([
// TODO: Remove quote entities once no longer present in translation
// files. Other entities must stay.
[ '“', '“' ],
@@ -90,7 +79,7 @@ var safeTextToTextNode = (function() {
[ '<', '<' ],
[ '>', '>' ],
]);
- let decodeEntities = match => {
+ const decodeEntities = match => {
return entities.get(match) || match;
};
return function(text) {
@@ -101,15 +90,16 @@ var safeTextToTextNode = (function() {
};
})();
-var safeTextToDOM = function(text, parent) {
+const safeTextToDOM = function(text, parent) {
if ( text === '' ) { return; }
+
// Fast path (most common).
if ( text.indexOf('<') === -1 ) {
parent.appendChild(safeTextToTextNode(text));
return;
}
// Slow path.
- // `` no longer allowed. Code below can be remove once all
's are
+ // `
` no longer allowed. Code below can be removed once all
's are
// gone from translation files.
text = text.replace(/^
|<\/p>/g, '')
.replace(/
/g, '\n\n');
@@ -118,18 +108,17 @@ var safeTextToDOM = function(text, parent) {
if ( matches === null ) {
matches = reSafeLink.exec(text);
if ( matches === null ) {
- matches = reSafeInput.exec(text);
- if ( matches === null ) {
- parent.appendChild(safeTextToTextNode(text));
- return;
- }
+ parent.appendChild(safeTextToTextNode(text));
+ return;
}
}
- safeTextToDOM(matches[1], parent);
- let node = safeTextToTagNode(matches[2]) || parent;
+ const fragment = document.createDocumentFragment();
+ safeTextToDOM(matches[1], fragment);
+ let node = safeTextToTagNode(matches[2]);
safeTextToDOM(matches[3], node);
- parent.appendChild(node);
- safeTextToDOM(matches[4], parent);
+ fragment.appendChild(node);
+ safeTextToDOM(matches[4], fragment);
+ parent.appendChild(fragment);
};
/******************************************************************************/
@@ -146,7 +135,7 @@ vAPI.i18n.safeTemplateToDOM = function(id, dict, parent) {
safeTextToDOM(textin, parent);
return parent;
}
- let re = /\{\{\w+\}\}/g;
+ const re = /\{\{\w+\}\}/g;
let textout = '';
for (;;) {
let match = re.exec(textin);
@@ -172,44 +161,62 @@ vAPI.i18n.safeTemplateToDOM = function(id, dict, parent) {
// Helper to deal with the i18n'ing of HTML files.
vAPI.i18n.render = function(context) {
- var docu = document;
- var root = context || docu;
- var elems, n, i, elem, text;
+ const docu = document;
+ const root = context || docu;
- elems = root.querySelectorAll('[data-i18n]');
- n = elems.length;
- for ( i = 0; i < n; i++ ) {
- elem = elems[i];
- text = vAPI.i18n(elem.getAttribute('data-i18n'));
+ for ( const elem of root.querySelectorAll('[data-i18n]') ) {
+ let text = vAPI.i18n(elem.getAttribute('data-i18n'));
if ( !text ) { continue; }
- // TODO: remove once it's all replaced with
- if ( text.indexOf('{') !== -1 ) {
- text = text.replace(/\{\{input:([^}]+)\}\}/g, '');
+ if ( text.indexOf('{{') === -1 ) {
+ safeTextToDOM(text, elem);
+ continue;
}
- safeTextToDOM(text, elem);
+ // Handle selector-based placeholders: these placeholders tell where
+ // existing child DOM element are to be positioned relative to the
+ // localized text nodes.
+ const parts = text.split(/(\{\{[^}]+\}\})/);
+ const fragment = document.createDocumentFragment();
+ let textBefore = '';
+ for ( let part of parts ) {
+ if ( part === '' ) { continue; }
+ if ( part.startsWith('{{') && part.endsWith('}}') ) {
+ // TODO: remove detection of ':' once it no longer appears
+ // in translation files.
+ const pos = part.indexOf(':');
+ if ( pos !== -1 ) {
+ part = part.slice(0, pos) + part.slice(-2);
+ }
+ const node = elem.querySelector(part.slice(2, -2));
+ if ( node !== null ) {
+ safeTextToDOM(textBefore, fragment);
+ fragment.appendChild(node);
+ textBefore = '';
+ continue;
+ }
+ }
+ textBefore += part;
+ }
+ if ( textBefore !== '' ) {
+ safeTextToDOM(textBefore, fragment);
+ }
+ elem.appendChild(fragment);
}
- elems = root.querySelectorAll('[data-i18n-title]');
- n = elems.length;
- for ( i = 0; i < n; i++ ) {
- elem = elems[i];
- text = vAPI.i18n(elem.getAttribute('data-i18n-title'));
+ for ( const elem of root.querySelectorAll('[data-i18n-title]') ) {
+ const text = vAPI.i18n(elem.getAttribute('data-i18n-title'));
if ( !text ) { continue; }
elem.setAttribute('title', text);
}
- elems = root.querySelectorAll('[placeholder]');
- n = elems.length;
- for ( i = 0; i < n; i++ ) {
- elem = elems[i];
- elem.setAttribute('placeholder', vAPI.i18n(elem.getAttribute('placeholder')));
+ for ( const elem of root.querySelectorAll('[placeholder]') ) {
+ elem.setAttribute(
+ 'placeholder',
+ vAPI.i18n(elem.getAttribute('placeholder'))
+ );
}
- elems = root.querySelectorAll('[data-i18n-tip]');
- n = elems.length;
- for ( i = 0; i < n; i++ ) {
- elem = elems[i];
- text = vAPI.i18n(elem.getAttribute('data-i18n-tip'))
+ for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) {
+ const text = vAPI.i18n(elem.getAttribute('data-i18n-tip'))
.replace(/
/g, '\n')
.replace(/\n{3,}/g, '\n\n');
elem.setAttribute('data-tip', text);
@@ -224,7 +231,7 @@ vAPI.i18n.render();
/******************************************************************************/
vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
- var value = (Date.now() - tstamp) / 60000;
+ let value = (Date.now() - tstamp) / 60000;
if ( value < 2 ) {
return vAPI.i18n('elapsedOneMinuteAgo');
}
diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js
index 28c21559f..e61e07e40 100644
--- a/src/js/logger-ui-inspector.js
+++ b/src/js/logger-ui-inspector.js
@@ -29,7 +29,7 @@
/******************************************************************************/
-var showdomButton = uDom.nodeFromId('showdom');
+const showdomButton = uDom.nodeFromId('showdom');
// Don't bother if the browser is not modern enough.
if (
@@ -340,31 +340,25 @@ var nidFromNode = function(node) {
/******************************************************************************/
-var startDialog = (function() {
- var dialog = uDom.nodeFromId('cosmeticFilteringDialog');
- var textarea = dialog.querySelector('textarea');
- var hideSelectors = [];
- var unhideSelectors = [];
- var inputTimer = null;
+const startDialog = (function() {
+ let dialog;
+ let textarea;
+ let hideSelectors = [];
+ let unhideSelectors = [];
+ let inputTimer;
- var onInputChanged = (function() {
- var parse = function() {
- inputTimer = null;
+ const onInputChanged = (function() {
+ const parse = function() {
+ inputTimer = undefined;
hideSelectors = [];
unhideSelectors = [];
- var line, matches;
- var re = /^([^#]*)(#@?#)(.+)$/;
- var lines = textarea.value.split(/\s*\n\s*/);
- for ( var i = 0; i < lines.length; i++ ) {
- line = lines[i].trim();
- if ( line === '' || line.charAt(0) === '!' ) {
- continue;
- }
- matches = re.exec(line);
- if ( matches === null || matches.length !== 4 ) {
- continue;
- }
+ const re = /^([^#]*)(#@?#)(.+)$/;
+ for ( let line of textarea.value.split(/\s*\n\s*/) ) {
+ line = line.trim();
+ if ( line === '' || line.charAt(0) === '!' ) { continue; }
+ const matches = re.exec(line);
+ if ( matches === null || matches.length !== 4 ) { continue; }
if ( inspectedHostname.lastIndexOf(matches[1]) === -1 ) {
continue;
}
@@ -379,19 +373,15 @@ var startDialog = (function() {
};
return function parseAsync() {
- if ( inputTimer === null ) {
+ if ( inputTimer === undefined ) {
inputTimer = vAPI.setTimeout(parse, 743);
}
};
})();
- var onClicked = function(ev) {
+ const onClicked = function(ev) {
var target = ev.target;
- // click outside the dialog proper
- if ( target.classList.contains('modalDialog') ) {
- return stop();
- }
ev.stopPropagation();
if ( target.id === 'createCosmeticFilters' ) {
@@ -402,7 +392,7 @@ var startDialog = (function() {
}
};
- var showCommitted = function() {
+ const showCommitted = function() {
messaging.sendTo(inspectorConnectionId, {
what: 'showCommitted',
hide: hideSelectors.join(',\n'),
@@ -410,7 +400,7 @@ var startDialog = (function() {
});
};
- var showInteractive = function() {
+ const showInteractive = function() {
messaging.sendTo(inspectorConnectionId, {
what: 'showInteractive',
hide: hideSelectors.join(',\n'),
@@ -418,46 +408,45 @@ var startDialog = (function() {
});
};
- var start = function() {
+ const start = function() {
+ dialog = logger.modalDialog.create('#cosmeticFilteringDialog', stop);
+ textarea = dialog.querySelector('textarea');
hideSelectors = [];
- textarea.addEventListener('input', onInputChanged);
- var node;
- for ( node of domTree.querySelectorAll('code.off') ) {
- if ( node.classList.contains('filter') === false ) {
- hideSelectors.push(selectorFromNode(node));
- }
+ for ( const node of domTree.querySelectorAll('code.off') ) {
+ if ( node.classList.contains('filter') ) { continue; }
+ hideSelectors.push(selectorFromNode(node));
}
- var taValue = [];
- var d = new Date();
- taValue.push('! ' + d.toLocaleString() + ' ' + inspectedURL);
- for ( var selector of hideSelectors ) {
+ const taValue = [];
+ for ( const selector of hideSelectors ) {
taValue.push(inspectedHostname + '##' + selector);
}
- var ids = new Set(), id;
- for ( node of domTree.querySelectorAll('code.filter.off') ) {
- id = node.getAttribute('data-filter-id');
+ const ids = new Set();
+ for ( const node of domTree.querySelectorAll('code.filter.off') ) {
+ const id = node.getAttribute('data-filter-id');
if ( ids.has(id) ) { continue; }
ids.add(id);
unhideSelectors.push(node.textContent);
taValue.push(inspectedHostname + '#@#' + node.textContent);
}
textarea.value = taValue.join('\n');
- document.body.appendChild(dialog);
+ textarea.addEventListener('input', onInputChanged);
dialog.addEventListener('click', onClicked, true);
showCommitted();
+ logger.modalDialog.show();
};
- var stop = function() {
- if ( inputTimer !== null ) {
+ const stop = function() {
+ if ( inputTimer !== undefined ) {
clearTimeout(inputTimer);
- inputTimer = null;
+ inputTimer = undefined;
}
showInteractive();
- hideSelectors = [];
- unhideSelectors = [];
textarea.removeEventListener('input', onInputChanged);
dialog.removeEventListener('click', onClicked, true);
- document.body.removeChild(dialog);
+ dialog = undefined;
+ textarea = undefined;
+ hideSelectors = [];
+ unhideSelectors = [];
};
return start;
@@ -585,7 +574,7 @@ var shutdownInspector = function() {
inspectorConnectionId = undefined;
}
logger.removeAllChildren(domTree);
- inspector.classList.add('vCompact');
+ inspector.classList.remove('vExpanded');
inspectedTabId = 0;
};
@@ -605,7 +594,7 @@ var onTabIdChanged = function() {
/******************************************************************************/
var toggleVCompactView = function() {
- var state = !inspector.classList.toggle('vCompact');
+ var state = inspector.classList.toggle('vExpanded');
var branches = document.querySelectorAll('#domInspector li.branch');
for ( var branch of branches ) {
branch.classList.toggle('show', state);
diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js
index f0db8fca6..102d3ad9d 100644
--- a/src/js/logger-ui.js
+++ b/src/js/logger-ui.js
@@ -31,47 +31,93 @@
const messaging = vAPI.messaging;
const logger = self.logger = { ownerId: Date.now() };
+const logDate = new Date();
+const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000;
+const loggerEntries = [];
+
+let filteredLoggerEntries = [];
+let filteredLoggerEntryVoidedCount = 0;
+
let popupLoggerBox;
let popupLoggerTooltips;
-let activeTabId;
+let activeTabId = 0;
+let selectedTabId = 0;
let netInspectorPaused = false;
/******************************************************************************/
-const removeAllChildren = logger.removeAllChildren = function(node) {
- while ( node.firstChild ) {
- node.removeChild(node.firstChild);
- }
-};
-
-/******************************************************************************/
-
-const tabIdFromClassName = function(className) {
- const matches = className.match(/\btab_([^ ]+)\b/);
- if ( matches === null ) { return 0; }
- if ( matches[1] === 'bts' ) { return -1; }
- return parseInt(matches[1], 10);
-};
+// Various helpers.
const tabIdFromPageSelector = logger.tabIdFromPageSelector = function() {
- const tabClass = uDom.nodeFromId('pageSelector').value;
- if ( tabClass === 'tab_active' && activeTabId !== undefined ) {
- return activeTabId;
- }
- if ( tabClass === 'tab_bts' ) { return -1; }
- return /^tab_\d+$/.test(tabClass) ? parseInt(tabClass.slice(4), 10) : 0;
+ const value = uDom.nodeFromId('pageSelector').value;
+ return value !== '_' ? (parseInt(value, 10) || 0) : activeTabId;
+};
+
+const tabIdFromAttribute = function(elem) {
+ const value = elem.getAttribute('data-tabid') || '';
+ const tabId = parseInt(value, 10);
+ return isNaN(tabId) ? 0 : tabId;
};
/******************************************************************************/
/******************************************************************************/
-const tbody = document.querySelector('#netInspector tbody');
-const trJunkyard = [];
-const tdJunkyard = [];
-const firstVarDataCol = 1;
-const lastVarDataIndex = 6;
-const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
-const netFilteringDialog = uDom.nodeFromId('netFilteringDialog');
+// Current design allows for only one modal DOM-based dialog at any given time.
+//
+const modalDialog = (function() {
+ const overlay = uDom.nodeFromId('modalOverlay');
+ const container = overlay.querySelector(
+ ':scope > div > div:nth-of-type(1)'
+ );
+ const closeButton = overlay.querySelector(
+ ':scope > div > div:nth-of-type(2)'
+ );
+ let onDestroyed;
+
+ const removeChildren = logger.removeAllChildren = function(node) {
+ while ( node.firstChild ) {
+ node.removeChild(node.firstChild);
+ }
+ };
+
+ const create = function(selector, destroyListener) {
+ const template = document.querySelector(selector);
+ const dialog = template.cloneNode(true);
+ removeChildren(container);
+ container.appendChild(dialog);
+ onDestroyed = destroyListener;
+ return dialog;
+ };
+
+ const show = function() {
+ overlay.classList.add('on');
+ };
+
+ const destroy = function() {
+ overlay.classList.remove('on');
+ const dialog = container.firstElementChild;
+ removeChildren(container);
+ if ( typeof onDestroyed === 'function' ) {
+ onDestroyed(dialog);
+ }
+ onDestroyed = undefined;
+ };
+
+ const onClose = function(ev) {
+ if ( ev.target === overlay || ev.target === closeButton ) {
+ destroy();
+ }
+ };
+ overlay.addEventListener('click', onClose);
+ closeButton.addEventListener('click', onClose);
+
+ return { create, show, destroy };
+})();
+
+self.logger.modalDialog = modalDialog;
+
+/******************************************************************************/
+/******************************************************************************/
const prettyRequestTypes = {
'main_frame': 'doc',
@@ -87,39 +133,16 @@ const uglyRequestTypes = {
'xhr': 'xmlhttprequest'
};
-const staticFilterTypes = {
- 'beacon': 'other',
- 'doc': 'document',
- 'css': 'stylesheet',
- 'frame': 'subdocument',
- 'ping': 'other',
- 'object_subrequest': 'object',
- 'xhr': 'xmlhttprequest'
-};
-
-let maxEntries = 5000;
let allTabIds = new Map();
let allTabIdsToken;
-/******************************************************************************/
-
-var classNameFromTabId = function(tabId) {
- if ( tabId < 0 ) {
- return 'tab_bts';
- }
- if ( tabId !== 0 ) {
- return 'tab_' + tabId;
- }
- return '';
-};
-
/******************************************************************************/
/******************************************************************************/
-var regexFromURLFilteringResult = function(result) {
- var beg = result.indexOf(' ');
- var end = result.indexOf(' ', beg + 1);
- var url = result.slice(beg + 1, end);
+const regexFromURLFilteringResult = function(result) {
+ const beg = result.indexOf(' ');
+ const end = result.indexOf(' ', beg + 1);
+ const url = result.slice(beg + 1, end);
if ( url === '*' ) {
return new RegExp('^.*$', 'gi');
}
@@ -130,291 +153,636 @@ var regexFromURLFilteringResult = function(result) {
// Emphasize hostname in URL, as this is what matters in uMatrix's rules.
-var nodeFromURL = function(url, re) {
+const nodeFromURL = function(url, re) {
if ( re instanceof RegExp === false ) {
return document.createTextNode(url);
}
- var matches = re.exec(url);
+ const matches = re.exec(url);
if ( matches === null || matches[0].length === 0 ) {
return document.createTextNode(url);
}
- var node = renderedURLTemplate.cloneNode(true);
+ const 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');
+const renderedURLTemplate = document.querySelector('#renderedURLTemplate > span');
/******************************************************************************/
-const createCellAt = function(tr, index) {
- let td = tr.cells[index];
- const mustAppend = !td;
- if ( mustAppend ) {
- td = tdJunkyard.pop();
- }
- if ( td ) {
- td.removeAttribute('colspan');
- td.removeAttribute('data-parties');
- td.textContent = '';
- } else {
- td = document.createElement('td');
- }
- if ( mustAppend ) {
- tr.appendChild(td);
- }
- return td;
-};
-
-/******************************************************************************/
-
-var createRow = function(layout) {
- let tr = trJunkyard.pop();
- if ( tr ) {
- tr.className = '';
- tr.removeAttribute('data-tabhn');
- tr.removeAttribute('data-dochn');
- tr.removeAttribute('data-filter');
- tr.removeAttribute('data-tabid');
- } else {
- tr = document.createElement('tr');
- }
- let index = 0;
- for ( ; index < firstVarDataCol; index++ ) {
- createCellAt(tr, index);
- }
- let 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 padTo2 = function(v) {
+const padTo2 = function(v) {
return v < 10 ? '0' + v : v;
};
-/******************************************************************************/
-
-const createGap = function(tabId, url) {
- const tr = createRow('1');
- tr.setAttribute('data-tabid', tabId);
- tr.classList.add('tab_' + tabId);
- tr.classList.add('maindoc');
- tr.cells[firstVarDataCol].textContent = url;
- tbody.insertBefore(tr, tbody.firstChild);
+const normalizeToStr = function(s) {
+ return typeof s === 'string' && s !== '' ? s : '';
};
/******************************************************************************/
-var renderNetLogEntry = function(tr, details) {
- const trcl = tr.classList;
- const type = details.type;
- const url = details.url;
- let td;
-
- // 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(details.tabId, url);
- }
-
- tr.classList.add('cat_' + details.realm);
-
- let filter = details.filter || undefined;
- let filteringType;
- if ( filter !== undefined ) {
- if ( typeof filter.source === 'string' ) {
- filteringType = filter.source;
- trcl.add(filteringType);
+const LogEntry = function(details) {
+ if ( details instanceof Object === false ) { return; }
+ const receiver = LogEntry.prototype;
+ for ( const prop in receiver ) {
+ if (
+ details.hasOwnProperty(prop) &&
+ details[prop] !== receiver[prop]
+ ) {
+ this[prop] = details[prop];
}
}
-
- if ( filter !== undefined ) {
- td = tr.cells[1];
- if ( filteringType === 'static' ) {
- td.textContent = filter.raw;
- trcl.add('canLookup');
- tr.setAttribute('data-filter', filter.compiled);
- } else if ( filteringType === 'cosmetic' ) {
- td.textContent = filter.raw;
- trcl.add('canLookup');
- } else {
- td.textContent = filter.raw;
- }
- }
-
- if ( filter !== undefined ) {
- td = tr.cells[2];
- if ( filter.result === 1 ) {
- trcl.add('blocked');
- td.textContent = '--';
- } else if ( filter.result === 2 ) {
- trcl.add('allowed');
- td.textContent = '++';
- } else if ( filter.result === 3 ) {
- trcl.add('nooped');
- td.textContent = '**';
- } else if ( filteringType === 'redirect' ) {
- trcl.add('redirect');
- td.textContent = '<<';
- }
- }
-
- if ( details.tabHostname ) {
- tr.setAttribute('data-tabhn', details.tabHostname);
- }
- if ( details.docHostname ) {
- tr.setAttribute('data-dochn', details.docHostname);
- tr.cells[3].textContent = details.docHostname;
- }
-
- // Partyness
- if ( details.realm === 'net' && details.domain !== undefined ) {
- td = tr.cells[4];
- let text = '';
- if ( details.tabDomain !== undefined ) {
- text += details.domain === details.tabDomain ? '1' : '3';
- } else {
- text += '?';
- }
- if ( details.docDomain !== details.tabDomain ) {
- text += ',';
- if ( details.docDomain !== undefined ) {
- text += details.domain === details.docDomain ? '1' : '3';
- } else {
- text += '?';
- }
- }
- td.textContent = text;
- let indent = '\t';
- text = details.tabDomain;
- if ( details.docDomain !== details.tabDomain ) {
- text += ` \u21d2\n\t${details.docDomain}`;
- indent = '\t\t';
- }
- text += ` \u21d2\n${indent}${details.domain}`;
- td.setAttribute('data-parties', text);
- }
-
- tr.cells[5].textContent = (prettyRequestTypes[type] || type);
-
- let re = null;
- if ( filteringType === 'static' ) {
- re = new RegExp(filter.regex, 'gi');
- } else if ( filteringType === 'dynamicUrl' ) {
- re = regexFromURLFilteringResult(filter.rule.join(' '));
- }
- tr.cells[6].appendChild(nodeFromURL(url, re));
+};
+LogEntry.prototype = {
+ dead: false,
+ docDomain: '',
+ docHostname: '',
+ domain: '',
+ filter: undefined,
+ realm: '',
+ tabDomain: '',
+ tabHostname: '',
+ tabId: undefined,
+ textContent: '',
+ tstamp: 0,
+ type: '',
+ voided: false,
};
/******************************************************************************/
-var renderLogEntry = function(details) {
- const fvdc = firstVarDataCol;
- let tr;
+const createLogSeparator = function(details, text) {
+ const separator = new LogEntry();
+ separator.tstamp = details.tstamp;
+ separator.realm = 'message';
+ separator.tabId = details.tabId;
+ separator.type = 'tabLoad';
+ separator.textContent = '';
- if ( details.error !== undefined ) {
- tr = createRow('1');
- tr.cells[fvdc].textContent = details.error;
- } else if ( details.url !== undefined ) {
- tr = createRow('111111');
- renderNetLogEntry(tr, details);
- } else {
- tr = createRow('1');
- tr.cells[fvdc].textContent = '???';
+ const textContent = [];
+ logDate.setTime(separator.tstamp - logDateTimezoneOffset);
+ textContent.push(
+ // cell 0
+ padTo2(logDate.getUTCHours()) + ':' +
+ padTo2(logDate.getUTCMinutes()) + ':' +
+ padTo2(logDate.getSeconds()),
+ // cell 1
+ text
+ );
+ separator.textContent = textContent.join('\t');
+
+ if ( details.voided ) {
+ separator.voided = true;
}
- // Fields common to all rows.
- const time = logDate;
- time.setTime(details.tstamp - logDateTimezoneOffset);
- tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' +
- padTo2(time.getUTCMinutes()) + ':' +
- padTo2(time.getSeconds());
-
- if ( details.tabId ) {
- tr.setAttribute('data-tabid', details.tabId);
- tr.classList.add(classNameFromTabId(details.tabId));
- }
-
- rowFilterer.filterOne(tr, true);
- tbody.insertBefore(tr, tbody.firstChild);
- return tr;
+ return separator;
};
-// Reuse date objects.
-const logDate = new Date();
-const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000;
-
/******************************************************************************/
-const renderLogEntries = function(response) {
- document.body.classList.toggle('colorBlind', response.colorBlind);
-
+// TODO: once refactoring is mature, consider using push() instead of
+// unshift(). This will require inverting the access logic
+// throughout the code.
+//
+const processLoggerEntries = function(response) {
const entries = response.entries;
if ( entries.length === 0 ) { return; }
- // Preserve scroll position
- const height = tbody.offsetHeight;
+ const autoDeleteVoidedRows = uDom.nodeFromId('pageSelector').value === '_';
+ const previousCount = filteredLoggerEntries.length;
- const tabIds = allTabIds;
for ( const entry of entries ) {
- const details = JSON.parse(entry.details);
- const tr = renderLogEntry(details);
- // https://github.com/gorhill/uBlock/issues/1613#issuecomment-217637122
- // Unlikely, but it may happen: mark as void if associated tab no
- // longer exist.
- if ( details.tabId && tabIds.has(details.tabId) === false ) {
- tr.classList.add('void');
+ const unboxed = JSON.parse(entry);
+ const parsed = parseLogEntry(unboxed);
+ if (
+ parsed.tabId !== undefined &&
+ allTabIds.has(parsed.tabId) === false
+ ) {
+ if ( autoDeleteVoidedRows ) { continue; }
+ parsed.voided = true;
+ }
+ if ( parsed.type === 'main_frame' ) {
+ const separator = createLogSeparator(parsed, unboxed.url);
+ loggerEntries.unshift(separator);
+ if ( rowFilterer.filterOne(separator) ) {
+ filteredLoggerEntries.unshift(separator);
+ if ( separator.voided ) {
+ filteredLoggerEntryVoidedCount += 1;
+ }
+ }
+ }
+ loggerEntries.unshift(parsed);
+ if ( rowFilterer.filterOne(parsed) ) {
+ filteredLoggerEntries.unshift(parsed);
+ if ( parsed.voided ) {
+ filteredLoggerEntryVoidedCount += 1;
+ }
}
}
- // 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);
-
- // Follow waterfall if not observing top of waterfall.
- const yDelta = tbody.offsetHeight - height;
- if ( yDelta === 0 ) { return; }
- const container = uDom.nodeFromSelector('#netInspector .vscrollable');
- if ( container.scrollTop !== 0 ) {
- container.scrollTop += yDelta;
+ const addedCount = filteredLoggerEntries.length - previousCount;
+ if ( addedCount !== 0 ) {
+ viewPort.updateContent(addedCount);
+ rowJanitor.inserted(addedCount);
}
};
/******************************************************************************/
-let updateCurrentTabTitle = (function() {
- let i18nCurrentTab = vAPI.i18n('loggerCurrentTab');
+const parseLogEntry = function(details) {
+ const entry = new LogEntry(details);
+
+ // Assemble the text content, i.e. the pre-built string which will be
+ // used to match logger output filtering expressions.
+ const textContent = [];
+
+ // Cell 0
+ logDate.setTime(details.tstamp - logDateTimezoneOffset);
+ textContent.push(
+ padTo2(logDate.getUTCHours()) + ':' +
+ padTo2(logDate.getUTCMinutes()) + ':' +
+ padTo2(logDate.getSeconds())
+ );
+
+ // Cell 1
+ if ( details.realm === 'message' ) {
+ textContent.push(details.text);
+ entry.textContent = textContent.join('\t');
+ return entry;
+ }
+
+ // Cell 1, 2
+ if ( entry.filter !== undefined ) {
+ textContent.push(entry.filter.raw);
+ if ( entry.filter.result === 1 ) {
+ textContent.push('--');
+ } else if ( entry.filter.result === 2 ) {
+ textContent.push('++');
+ } else if ( entry.filter.result === 3 ) {
+ textContent.push('**');
+ } else if ( entry.filter.source === 'redirect' ) {
+ textContent.push('<<');
+ } else {
+ textContent.push('');
+ }
+ } else {
+ textContent.push('', '');
+ }
+
+ // Cell 3
+ textContent.push(normalizeToStr(entry.docHostname));
+
+ // Cell 4
+ if (
+ entry.realm === 'network' &&
+ typeof entry.domain === 'string' &&
+ entry.domain !== ''
+ ) {
+ let partyness = '';
+ if ( entry.tabDomain !== undefined ) {
+ partyness += entry.domain === entry.tabDomain ? '1' : '3';
+ } else {
+ partyness += '?';
+ }
+ if ( entry.docDomain !== entry.tabDomain ) {
+ partyness += ',';
+ if ( entry.docDomain !== undefined ) {
+ partyness += entry.domain === entry.docDomain ? '1' : '3';
+ } else {
+ partyness += '?';
+ }
+ }
+ textContent.push(partyness);
+ } else {
+ textContent.push('');
+ }
+
+ // Cell 5
+ textContent.push(
+ normalizeToStr(prettyRequestTypes[entry.type] || entry.type)
+ );
+
+ // Cell 6
+ textContent.push(normalizeToStr(details.url));
+
+ entry.textContent = textContent.join('\t');
+ return entry;
+};
+
+/******************************************************************************/
+
+const viewPort = (function() {
+ const vwRenderer = document.getElementById('vwRenderer');
+ const vwScroller = document.getElementById('vwScroller');
+ const vwVirtualContent = document.getElementById('vwVirtualContent');
+ const vwContent = document.getElementById('vwContent');
+ const vwLineSizer = document.getElementById('vwLineSizer');
+ const vwLogEntryTemplate = document.querySelector('#logEntryTemplate > div');
+ const vwEntries = [];
+
+ let vwHeight = 0;
+ let lineHeight = 0;
+ let wholeHeight = 0;
+ let lastTopPix = 0;
+ let lastTopRow = 0;
+ let scrollTimer;
+ let resizeTimer;
+
+ const ViewEntry = function() {
+ this.div = document.createElement('div');
+ this.div.className = 'logEntry';
+ vwContent.appendChild(this.div);
+ this.logEntry = undefined;
+ };
+ ViewEntry.prototype = {
+ dispose: function() {
+ vwContent.removeChild(this.div);
+ },
+ };
+
+ const rowFromScrollTopPix = function(px) {
+ return lineHeight !== 0 ? Math.floor(px / lineHeight) : 0;
+ };
+
+ // This is called when the browser fired scroll events
+ const onScrollChanged = function() {
+ const newScrollTopPix = vwScroller.scrollTop;
+ const delta = newScrollTopPix - lastTopPix;
+ if ( delta === 0 ) { return; }
+ lastTopPix = newScrollTopPix;
+ if ( filteredLoggerEntries.length <= 2 ) { return; }
+ // No entries were rolled = all entries keep their current details
+ if ( rollLines(rowFromScrollTopPix(newScrollTopPix)) ) {
+ fillLines();
+ }
+ positionLines();
+ vwContent.style.top = `${lastTopPix}px`;
+ };
+
+ // Coallesce scroll events
+ const onScroll = function() {
+ if ( scrollTimer !== undefined ) { return; }
+ scrollTimer = setTimeout(
+ ( ) => {
+ scrollTimer = requestAnimationFrame(( ) => {
+ scrollTimer = undefined;
+ onScrollChanged();
+ });
+ },
+ 1000/32
+ );
+ };
+
+ vwScroller.addEventListener('scroll', onScroll, { passive: true });
+
+ const onLayoutChanged = function() {
+ vwHeight = vwRenderer.clientHeight;
+ vwContent.style.height = `${vwScroller.clientHeight}px`;
+
+ const vExpanded =
+ uDom.nodeFromSelector('#netInspector .vCompactToggler')
+ .classList
+ .contains('vExpanded');
+
+ let newLineHeight =
+ vwLineSizer.querySelector('.oneLine').clientHeight;
+
+ if ( vExpanded ) {
+ newLineHeight *= loggerSettings.linesPerEntry;
+ }
+
+ const lineCount = newLineHeight !== 0
+ ? Math.ceil(vwHeight / newLineHeight) + 1
+ : 0;
+ if ( lineCount > vwEntries.length ) {
+ do {
+ vwEntries.push(new ViewEntry());
+ } while ( lineCount > vwEntries.length );
+ } else if ( lineCount < vwEntries.length ) {
+ do {
+ vwEntries.pop().dispose();
+ } while ( lineCount < vwEntries.length );
+ }
+
+ const cellWidths = Array.from(
+ vwLineSizer.querySelectorAll('.oneLine span')
+ ).map((el, i) => {
+ return loggerSettings.columns[i] !== false
+ ? el.clientWidth + 1
+ : 0;
+ });
+ const reservedWidth =
+ cellWidths[0] + cellWidths[2] + cellWidths[4] + cellWidths[5];
+ cellWidths[6] = 0.5;
+ if ( cellWidths[1] === 0 && cellWidths[3] === 0 ) {
+ cellWidths[6] = 1;
+ } else if ( cellWidths[1] === 0 ) {
+ cellWidths[3] = 0.35;
+ cellWidths[6] = 0.65;
+ } else if ( cellWidths[3] === 0 ) {
+ cellWidths[1] = 0.35;
+ cellWidths[6] = 0.65;
+ } else {
+ cellWidths[1] = 0.25;
+ cellWidths[3] = 0.25;
+ cellWidths[6] = 0.5;
+ }
+ const style = document.getElementById('vwRendererRuntimeStyles');
+ const cssRules = [
+ '#vwContent .logEntry {',
+ ` height: ${newLineHeight}px;`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(1) {',
+ ` width: ${cellWidths[0]}px;`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(2) {',
+ ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[1]});`,
+ '}',
+ '#vwContent .logEntry > div.messageRealm > span:nth-of-type(2) {',
+ ` width: calc(100% - ${cellWidths[0]}px);`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(3) {',
+ ` width: ${cellWidths[2]}px;`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(4) {',
+ ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[3]});`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(5) {',
+ ` width: ${cellWidths[4]}px;`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(6) {',
+ ` width: ${cellWidths[5]}px;`,
+ '}',
+ '#vwContent .logEntry > div > span:nth-of-type(7) {',
+ ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[6]});`,
+ '}',
+ '',
+ ];
+ for ( let i = 0; i < cellWidths.length; i++ ) {
+ if ( cellWidths[i] !== 0 ) { continue; }
+ cssRules.push(
+ `#vwContent .logEntry > div > span:nth-of-type(${i + 1}) {`,
+ ' display: none;',
+ '}'
+ );
+ }
+ style.textContent = cssRules.join('\n');
+
+ lineHeight = newLineHeight;
+ positionLines();
+ uDom.nodeFromId('netInspector')
+ .classList
+ .toggle('vExpanded', vExpanded);
+
+ updateContent(0);
+ };
+
+ const updateLayout = function() {
+ if ( resizeTimer !== undefined ) { return; }
+ resizeTimer = setTimeout(
+ ( ) => {
+ resizeTimer = requestAnimationFrame(( ) => {
+ resizeTimer = undefined;
+ onLayoutChanged();
+ });
+ },
+ 1000/8
+ );
+ };
+
+ window.addEventListener('resize', updateLayout, { passive: true });
+
+ updateLayout();
+
+ const renderToDiv = function(vwEntry, i) {
+ if ( i >= filteredLoggerEntries.length ) {
+ vwEntry.logEntry = undefined;
+ return null;
+ }
+
+ const details = filteredLoggerEntries[i];
+ if ( vwEntry.logEntry === details ) {
+ return vwEntry.div.firstElementChild;
+ }
+
+ vwEntry.logEntry = details;
+
+ const cells = details.textContent.split('\t');
+ const div = vwLogEntryTemplate.cloneNode(true);
+ const divcl = div.classList;
+ let span;
+
+
+ // Realm
+ if ( details.realm !== undefined ) {
+ divcl.add(details.realm + 'Realm');
+ }
+
+ // Timestamp
+ span = div.children[0];
+ span.textContent = cells[0];
+
+ // Tab id
+ if ( details.tabId !== undefined ) {
+ div.setAttribute('data-tabid', details.tabId);
+ if ( details.voided ) {
+ divcl.add('voided');
+ }
+ }
+
+ if ( details.realm === 'message' ) {
+ if ( details.type !== undefined ) {
+ div.setAttribute('data-type', details.type);
+ }
+ span = div.children[1];
+ span.textContent = cells[1];
+ return div;
+ }
+
+ if ( details.realm === 'network' || details.realm === 'cosmetic' ) {
+ divcl.add('canDetails');
+ }
+
+ // Filter
+ const filter = details.filter || undefined;
+ let filteringType;
+ if ( filter !== undefined ) {
+ if ( typeof filter.source === 'string' ) {
+ filteringType = filter.source;
+ divcl.add(filteringType);
+ }
+ if ( filteringType === 'static' ) {
+ divcl.add('canLookup');
+ div.setAttribute('data-filter', filter.compiled);
+ } else if ( filteringType === 'cosmetic' ) {
+ divcl.add('canLookup');
+ }
+ }
+ span = div.children[1];
+ span.textContent = cells[1];
+
+ // Event
+ if ( cells[2] === '--' ) {
+ divcl.add('blocked');
+ } else if ( cells[2] === '++' ) {
+ divcl.add('allowed');
+ } else if ( cells[2] === '**' ) {
+ span.add('nooped');
+ } else if ( cells[2] === '<<' ) {
+ divcl.add('redirect');
+ }
+ span = div.children[2];
+ span.textContent = cells[2];
+
+ // Origins
+ if ( details.tabHostname ) {
+ div.setAttribute('data-tabhn', details.tabHostname);
+ }
+ if ( details.docHostname ) {
+ div.setAttribute('data-dochn', details.docHostname);
+ }
+ span = div.children[3];
+ span.textContent = cells[3];
+
+ // Partyness
+ if (
+ cells[4] !== '' &&
+ details.realm === 'network' &&
+ details.domain !== undefined
+ ) {
+ let text = `${details.tabDomain}`;
+ if ( details.docDomain !== details.tabDomain ) {
+ text += ` \u22ef ${details.docDomain}`;
+ }
+ text += ` \u21d2 ${details.domain}`;
+ div.setAttribute('data-parties', text);
+ }
+ span = div.children[4];
+ span.textContent = cells[4];
+
+ // Type
+ span = div.children[5];
+ span.textContent = cells[5];
+
+ // URL
+ let re = null;
+ if ( filteringType === 'static' ) {
+ re = new RegExp(filter.regex, 'gi');
+ } else if ( filteringType === 'dynamicUrl' ) {
+ re = regexFromURLFilteringResult(filter.rule.join(' '));
+ }
+ span = div.children[6];
+ span.appendChild(nodeFromURL(cells[6], re));
+
+ return div;
+ };
+
+ // The idea is that positioning DOM elements is faster than
+ // removing/inserting DOM elements.
+ const positionLines = function() {
+ if ( lineHeight === 0 ) { return; }
+ let y = -(lastTopPix % lineHeight);
+ for ( const vwEntry of vwEntries ) {
+ vwEntry.div.style.top = `${y}px`;
+ y += lineHeight;
+ }
+ };
+
+ const rollLines = function(topRow) {
+ let delta = topRow - lastTopRow;
+ let deltaLength = Math.abs(delta);
+ // No point rolling if no rows can be reused
+ if ( deltaLength > 0 && deltaLength < vwEntries.length ) {
+ if ( delta < 0 ) { // Move bottom rows to the top
+ vwEntries.unshift(...vwEntries.splice(delta));
+ } else { // Move top rows to the bottom
+ vwEntries.push(...vwEntries.splice(0, delta));
+ }
+ }
+ lastTopRow = topRow;
+ return delta;
+ };
+
+ const fillLines = function() {
+ let rowBeg = lastTopRow;
+ for ( const vwEntry of vwEntries ) {
+ const newDiv = renderToDiv(vwEntry, rowBeg);
+ const container = vwEntry.div;
+ const oldDiv = container.firstElementChild;
+ if ( newDiv !== null ) {
+ if ( oldDiv === null ) {
+ container.appendChild(newDiv);
+ } else if ( newDiv !== oldDiv ) {
+ container.removeChild(oldDiv);
+ container.appendChild(newDiv);
+ }
+ } else if ( oldDiv !== null ) {
+ container.removeChild(oldDiv);
+ }
+ rowBeg += 1;
+ }
+ };
+
+ const contentChanged = function(addedCount) {
+ lastTopRow += addedCount;
+ const newWholeHeight = Math.max(
+ filteredLoggerEntries.length * lineHeight,
+ vwRenderer.clientHeight
+ );
+ if ( newWholeHeight !== wholeHeight ) {
+ vwVirtualContent.style.height = `${newWholeHeight}px`;
+ wholeHeight = newWholeHeight;
+ }
+ };
+
+ const updateContent = function(addedCount) {
+ contentChanged(addedCount);
+ // Content changed
+ if ( addedCount === 0 ) {
+ if (
+ lastTopRow !== 0 &&
+ lastTopRow + vwEntries.length > filteredLoggerEntries.length
+ ) {
+ lastTopRow = filteredLoggerEntries.length - vwEntries.length;
+ if ( lastTopRow < 0 ) { lastTopRow = 0; }
+ lastTopPix = lastTopRow * lineHeight;
+ vwContent.style.top = `${lastTopPix}px`;
+ vwScroller.scrollTop = lastTopPix;
+ positionLines();
+ }
+ fillLines();
+ return;
+ }
+
+ // Content added
+ // Preserve scroll position
+ if ( lastTopPix === 0 ) {
+ rollLines(0);
+ positionLines();
+ fillLines();
+ return;
+ }
+
+ // Preserve row position
+ lastTopPix += lineHeight * addedCount;
+ vwContent.style.top = `${lastTopPix}px`;
+ vwScroller.scrollTop = lastTopPix;
+ };
+
+ return { updateContent, updateLayout, };
+})();
+
+/******************************************************************************/
+
+const updateCurrentTabTitle = (function() {
+ const i18nCurrentTab = vAPI.i18n('loggerCurrentTab');
return function() {
- let select = uDom.nodeFromId('pageSelector');
- if ( select.value !== 'tab_active' ) { return; }
- let opt0 = select.querySelector('[value="tab_active"]');
- let opt1 = select.querySelector('[value="tab_' + activeTabId + '"]');
+ const select = uDom.nodeFromId('pageSelector');
+ if ( select.value !== '_' || activeTabId === 0 ) { return; }
+ const opt0 = select.querySelector('[value="_"]');
+ const opt1 = select.querySelector('[value="' + activeTabId + '"]');
let text = i18nCurrentTab;
if ( opt1 !== null ) {
text += ' / ' + opt1.textContent;
@@ -427,42 +795,61 @@ let updateCurrentTabTitle = (function() {
const synchronizeTabIds = function(newTabIds) {
const select = uDom.nodeFromId('pageSelector');
- const selectValue = select.value;
+ const selectedTabValue = select.value;
const oldTabIds = allTabIds;
- const autoDeleteVoidRows = selectValue === 'tab_active';
- let rowVoided = false;
+
+ // Collate removed tab ids.
+ const toVoid = new Set();
for ( const tabId of oldTabIds.keys() ) {
if ( newTabIds.has(tabId) ) { continue; }
- // Mark or remove voided rows
- const trs = uDom('.tab_' + tabId);
- if ( autoDeleteVoidRows ) {
- toJunkyard(trs);
- } else {
- trs.addClass('void');
- rowVoided = true;
+ toVoid.add(tabId);
+ }
+ allTabIds = newTabIds;
+
+ // Mark as "void" all logger entries which are linked to now invalid
+ // tab ids.
+ // When an entry is voided without being removed, we re-create a new entry
+ // in order to ensure the entry has a new identity. A new identify ensures
+ // that identity-based associations elsewhere are automatically
+ // invalidated.
+ if ( toVoid.size !== 0 ) {
+ const autoDeleteVoidedRows = selectedTabValue === '_';
+ let rowVoided = false;
+ for ( let i = 0, n = loggerEntries.length; i < n; i++ ) {
+ const entry = loggerEntries[i];
+ if ( toVoid.has(entry.tabId) === false ) { continue; }
+ if ( entry.voided ) { continue; }
+ rowVoided = entry.voided = true;
+ if ( autoDeleteVoidedRows ) {
+ entry.dead = true;
+ }
+ loggerEntries[i] = new LogEntry(entry);
}
- // Remove popup if it is currently bound to a removed tab.
- if ( tabId === popupManager.tabId ) {
- popupManager.toggleOff();
+ if ( rowVoided ) {
+ rowFilterer.filterAll();
}
}
+ // Remove popup if it is currently bound to a removed tab.
+ if ( toVoid.has(popupManager.tabId) ) {
+ popupManager.toggleOff();
+ }
+
const tabIds = Array.from(newTabIds.keys()).sort(function(a, b) {
return newTabIds.get(a).localeCompare(newTabIds.get(b));
});
let j = 3;
for ( let i = 0; i < tabIds.length; i++ ) {
const tabId = tabIds[i];
- if ( tabId < 0 ) { continue; }
- let option = select.options[j];
- if ( !option ) {
- option = document.createElement('option');
- select.appendChild(option);
+ if ( tabId <= 0 ) { continue; }
+ if ( j === select.options.length ) {
+ select.appendChild(document.createElement('option'));
}
+ const option = select.options[j];
// Truncate too long labels.
option.textContent = newTabIds.get(tabId).slice(0, 80);
- option.value = classNameFromTabId(tabId);
- if ( option.value === selectValue ) {
+ option.setAttribute('value', tabId);
+ if ( option.value === selectedTabValue ) {
select.selectedIndex = j;
option.setAttribute('selected', '');
} else {
@@ -473,33 +860,14 @@ const synchronizeTabIds = function(newTabIds) {
while ( j < select.options.length ) {
select.removeChild(select.options[j]);
}
- if ( select.value !== selectValue ) {
+ if ( select.value !== selectedTabValue ) {
select.selectedIndex = 0;
select.value = '';
select.options[0].setAttribute('selected', '');
pageSelectorChanged();
}
- allTabIds = newTabIds;
-
updateCurrentTabTitle();
-
- return rowVoided;
-};
-
-/******************************************************************************/
-
-var truncateLog = function(size) {
- if ( size === 0 ) {
- size = 5000;
- }
- var tbody = document.querySelector('#netInspector tbody');
- size = Math.min(size, 10000);
- var tr;
- while ( tbody.childElementCount > size ) {
- tr = tbody.lastElementChild;
- trJunkyard.push(tbody.removeChild(tr));
- }
};
/******************************************************************************/
@@ -525,20 +893,13 @@ const onLogBufferRead = function(response) {
activeTabId = response.activeTabId;
}
- // This may have changed meanwhile
- if ( response.maxEntries !== maxEntries ) {
- maxEntries = response.maxEntries;
- uDom('#maxEntries').val(maxEntries || '');
- }
-
if ( Array.isArray(response.tabIds) ) {
response.tabIds = new Map(response.tabIds);
}
- // Neuter rows for which a tab does not exist anymore
- let rowVoided = false;
+ // List of tab ids has changed
if ( response.tabIds !== undefined ) {
- rowVoided = synchronizeTabIds(response.tabIds);
+ synchronizeTabIds(response.tabIds);
allTabIdsToken = response.tabIdsToken;
}
@@ -547,20 +908,21 @@ const onLogBufferRead = function(response) {
}
if ( netInspectorPaused === false ) {
- renderLogEntries(response);
+ processLoggerEntries(response);
}
- if ( rowVoided ) {
- uDom('#clean').toggleClass(
- 'disabled',
- tbody.querySelector('#netInspector tr[data-tabid].void') === null
- );
- }
-
- // Synchronize toolbar with content of log
- uDom('#clear').toggleClass(
+ // Synchronize DOM with sent logger data
+ document.body.classList.toggle(
+ 'colorBlind',
+ response.colorBlind === true
+ );
+ uDom.nodeFromId('clean').classList.toggle(
'disabled',
- tbody.querySelector('tr') === null
+ filteredLoggerEntryVoidedCount === 0
+ );
+ uDom.nodeFromId('clear').classList.toggle(
+ 'disabled',
+ filteredLoggerEntries.length === 0
);
};
@@ -616,81 +978,58 @@ const readLogBuffer = (function() {
/******************************************************************************/
-let pageSelectorChanged = function() {
- let select = uDom.nodeFromId('pageSelector');
+const pageSelectorChanged = function() {
+ const select = uDom.nodeFromId('pageSelector');
window.location.replace('#' + select.value);
pageSelectorFromURLHash();
};
-let pageSelectorFromURLHash = (function() {
- let lastTabClass = '';
- let lastEffectiveTabClass = '';
- let reActiveTabId = /^(tab_[^+]+)\+(.+)$/;
-
- let selectRows = function(tabClass) {
- let effectiveTabClass = tabClass;
- if ( tabClass === 'tab_active' ) {
- if ( activeTabId === undefined ) { return; }
- effectiveTabClass = 'tab_' + activeTabId;
- }
- if ( effectiveTabClass === lastEffectiveTabClass ) { return; }
- lastEffectiveTabClass = effectiveTabClass;
-
- document.dispatchEvent(new Event('tabIdChanged'));
-
- let style = uDom.nodeFromId('tabFilterer');
- let sheet = style.sheet;
- while ( sheet.cssRules.length !== 0 ) {
- sheet.deleteRule(0);
- }
- if ( effectiveTabClass === '' ) { return; }
- sheet.insertRule(
- '#netInspector tr:not(.' + effectiveTabClass + '):not(.tab_bts) ' +
- '{display:none;}',
- 0
- );
-
- updateCurrentTabTitle();
- };
+const pageSelectorFromURLHash = (function() {
+ let lastHash;
+ let lastSelectedTabId;
return function() {
- let tabClass = window.location.hash.slice(1);
- let match = reActiveTabId.exec(tabClass);
+ let hash = window.location.hash.slice(1);
+ let match = /^([^+]+)\+(.+)$/.exec(hash);
if ( match !== null ) {
- tabClass = match[1];
- activeTabId = parseInt(match[2], 10) || undefined;
- window.location.hash = '#' + match[1];
- }
- selectRows(tabClass);
- if ( tabClass === lastTabClass ) { return; }
- lastTabClass = tabClass;
-
- let select = uDom.nodeFromId('pageSelector');
- let option = select.querySelector('option[value="' + tabClass + '"]');
- if ( option === null ) {
- window.location.hash = '';
- tabClass = '';
- option = select.options[0];
+ hash = match[1];
+ activeTabId = parseInt(match[2], 10) || 0;
+ window.location.hash = '#' + hash;
}
- select.selectedIndex = option.index;
- select.value = option.value;
+ if ( hash !== lastHash ) {
+ const select = uDom.nodeFromId('pageSelector');
+ let option = select.querySelector(
+ 'option[value="' + hash + '"]'
+ );
+ if ( option === null ) {
+ hash = '0';
+ option = select.options[0];
+ }
+ select.selectedIndex = option.index;
+ select.value = option.value;
+ lastHash = hash;
+ }
- uDom('.needdom').toggleClass(
- 'disabled',
- tabClass === '' || tabClass === 'tab_bts'
- );
- uDom('.needscope').toggleClass(
- 'disabled',
- tabClass === ''
- );
+ selectedTabId = hash === '_'
+ ? activeTabId
+ : parseInt(hash, 10) || 0;
+
+ if ( lastSelectedTabId === selectedTabId ) { return; }
+
+ rowFilterer.filterAll();
+ document.dispatchEvent(new Event('tabIdChanged'));
+ updateCurrentTabTitle();
+ uDom('.needdom').toggleClass('disabled', selectedTabId <= 0);
+ uDom('.needscope').toggleClass('disabled', selectedTabId <= 0);
+ lastSelectedTabId = selectedTabId;
};
})();
/******************************************************************************/
-var reloadTab = function(ev) {
- var tabId = tabIdFromPageSelector();
+const reloadTab = function(ev) {
+ const tabId = tabIdFromPageSelector();
if ( tabId <= 0 ) { return; }
messaging.send('loggerUI', {
what: 'reloadTab',
@@ -699,86 +1038,60 @@ var reloadTab = function(ev) {
});
};
-/******************************************************************************/
-
-var onMaxEntriesChanged = function() {
- var input = this;
- try {
- maxEntries = parseInt(input.value, 10);
- if ( maxEntries === 0 || isNaN(maxEntries) ) {
- maxEntries = 1000;
- }
- } catch (e) {
- maxEntries = 1000;
- }
-
- maxEntries = Math.min(maxEntries, 5000);
- maxEntries = Math.max(maxEntries, 10);
-
- input.value = maxEntries.toString(10);
-
- messaging.send(
- 'loggerUI',
- {
- what: 'userSettings',
- name: 'requestLogMaxEntries',
- value: maxEntries
- }
- );
-
- truncateLog(maxEntries);
-};
-
/******************************************************************************/
/******************************************************************************/
-var netFilteringManager = (function() {
- var targetRow = null;
- var dialog = null;
- var createdStaticFilters = {};
+const netFilteringManager = (function() {
+ const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
+ const staticFilterTypes = {
+ 'beacon': 'other',
+ 'doc': 'document',
+ 'css': 'stylesheet',
+ 'frame': 'subdocument',
+ 'ping': 'other',
+ 'object_subrequest': 'object',
+ 'xhr': 'xmlhttprequest'
+ };
+ const createdStaticFilters = {};
- var targetType;
- var targetURLs = [];
- var targetFrameHostname;
- var targetPageHostname;
- var targetTabId;
- var targetDomain;
- var targetPageDomain;
- var targetFrameDomain;
+ let dialog = null;
+ let targetRow = null;
+ let targetType;
+ let targetURLs = [];
+ let targetFrameHostname;
+ let targetPageHostname;
+ let targetTabId;
+ let targetDomain;
+ let targetPageDomain;
+ let targetFrameDomain;
- var uglyTypeFromSelector = function(pane) {
- var prettyType = selectValue('select.type.' + pane);
+ const uglyTypeFromSelector = function(pane) {
+ const prettyType = selectValue('select.type.' + pane);
if ( pane === 'static' ) {
return staticFilterTypes[prettyType] || prettyType;
}
return uglyRequestTypes[prettyType] || prettyType;
};
- var selectNode = function(selector) {
+ const selectNode = function(selector) {
return dialog.querySelector(selector);
};
- var selectValue = function(selector) {
+ const selectValue = function(selector) {
return selectNode(selector).value || '';
};
- var staticFilterNode = function() {
- return dialog.querySelector('div.containers > div.static textarea');
+ const staticFilterNode = function() {
+ return dialog.querySelector('div.panes > div.static textarea');
};
- var onColorsReady = function(response) {
+ const onColorsReady = function(response) {
document.body.classList.toggle('dirty', response.dirty);
- var colorEntries = response.colors;
- var colorEntry, node;
- for ( var url in colorEntries ) {
- if ( colorEntries.hasOwnProperty(url) === false ) {
- continue;
- }
- colorEntry = colorEntries[url];
- node = dialog.querySelector('.dynamic .entry .action[data-url="' + url + '"]');
- if ( node === null ) {
- continue;
- }
+ for ( const url in response.colors ) {
+ if ( response.colors.hasOwnProperty(url) === false ) { continue; }
+ const colorEntry = response.colors[url];
+ const node = dialog.querySelector('.dynamic .entry .action[data-url="' + url + '"]');
+ if ( node === null ) { continue; }
node.classList.toggle('allow', colorEntry.r === 2);
node.classList.toggle('noop', colorEntry.r === 3);
node.classList.toggle('block', colorEntry.r === 1);
@@ -786,7 +1099,7 @@ var netFilteringManager = (function() {
}
};
- var colorize = function() {
+ const colorize = function() {
messaging.send(
'loggerUI',
{
@@ -799,14 +1112,14 @@ var netFilteringManager = (function() {
);
};
- var parseStaticInputs = function() {
- var filter = '',
- options = [],
- block = selectValue('select.static.action') === '';
+ const parseStaticInputs = function() {
+ const options = [];
+ const block = selectValue('select.static.action') === '';
+ let filter = '';
if ( !block ) {
filter = '@@';
}
- var value = selectValue('select.static.url');
+ let value = selectValue('select.static.url');
if ( value !== '' ) {
if ( value.slice(-1) === '/' ) {
value += '*';
@@ -838,44 +1151,28 @@ var netFilteringManager = (function() {
updateWidgets();
};
- var updateWidgets = function() {
- var value = staticFilterNode().value;
+ const updateWidgets = function() {
+ const value = staticFilterNode().value;
dialog.querySelector('#createStaticFilter').classList.toggle(
'disabled',
createdStaticFilters.hasOwnProperty(value) || value === ''
);
};
- var onClick = function(ev) {
- var target = ev.target;
-
- // click outside the dialog proper
- if ( target.classList.contains('modalDialog') ) {
- toggleOff();
- return;
- }
-
- ev.stopPropagation();
-
- var tcl = target.classList;
- var value;
+ const onClick = function(ev) {
+ const target = ev.target;
+ const tcl = target.classList;
// Select a mode
if ( tcl.contains('header') ) {
- if ( tcl.contains('selected') ) {
- return;
- }
- uDom('.header').removeClass('selected');
- uDom('.container').removeClass('selected');
- value = target.getAttribute('data-container');
- uDom('.header.' + value).addClass('selected');
- uDom('.container.' + value).addClass('selected');
+ dialog.setAttribute('data-pane', target.getAttribute('data-pane') );
+ ev.stopPropagation();
return;
}
// Create static filter
if ( target.id === 'createStaticFilter' ) {
- value = staticFilterNode().value;
+ const value = staticFilterNode().value;
// Avoid duplicates
if ( createdStaticFilters.hasOwnProperty(value) ) {
return;
@@ -894,6 +1191,7 @@ var netFilteringManager = (function() {
);
}
updateWidgets();
+ ev.stopPropagation();
return;
}
@@ -909,10 +1207,11 @@ var netFilteringManager = (function() {
},
colorize
);
+ ev.stopPropagation();
return;
}
- var persist = !!ev.ctrlKey || !!ev.metaKey;
+ const persist = !!ev.ctrlKey || !!ev.metaKey;
// Remove url filtering rule
if ( tcl.contains('action') ) {
@@ -928,6 +1227,7 @@ var netFilteringManager = (function() {
},
colorize
);
+ ev.stopPropagation();
return;
}
@@ -945,6 +1245,7 @@ var netFilteringManager = (function() {
},
colorize
);
+ ev.stopPropagation();
return;
}
@@ -962,6 +1263,7 @@ var netFilteringManager = (function() {
},
colorize
);
+ ev.stopPropagation();
return;
}
@@ -979,6 +1281,7 @@ var netFilteringManager = (function() {
},
colorize
);
+ ev.stopPropagation();
return;
}
@@ -991,6 +1294,7 @@ var netFilteringManager = (function() {
tabId: targetTabId
}
);
+ ev.stopPropagation();
return;
}
@@ -1005,13 +1309,13 @@ var netFilteringManager = (function() {
select: true
}
);
+ ev.stopPropagation();
return;
}
};
- var onSelectChange = function(ev) {
- var target = ev.target;
- var tcl = target.classList;
+ const onSelectChange = function(ev) {
+ const tcl = ev.target.classList;
if ( tcl.contains('dynamic') ) {
colorize();
@@ -1024,42 +1328,47 @@ var netFilteringManager = (function() {
}
};
- var onInputChange = function() {
+ const onInputChange = function() {
updateWidgets();
};
- var createPreview = function(type, url) {
- // First, whether picker can be used
+ const createPreview = function(type, url) {
+ const cantPreview =
+ type !== 'image' ||
+ targetRow.classList.contains('networkRealm') === false ||
+ targetRow.classList.contains('blocked');
+
+ // Whether picker can be used
dialog.querySelector('.picker').classList.toggle(
'hide',
- targetTabId < 0 ||
- targetType !== 'image' ||
- /(?:^| )[dlsu]b(?: |$)/.test(targetRow.className)
+ targetTabId < 0 || cantPreview
);
- var preview = null;
+ // Whether the resource can be previewed
+ if ( cantPreview ) { return; }
- if ( type === 'image' ) {
- preview = document.createElement('img');
- preview.setAttribute('src', url);
- }
+ const container = dialog.querySelector('.preview');
+ container.querySelector('span').addEventListener(
+ 'click',
+ ( ) => {
+ const preview = document.createElement('img');
+ preview.setAttribute('src', url);
+ container.replaceChild(preview, container.firstElementChild);
+ },
+ { once: true }
+ );
- var container = dialog.querySelector('div.preview');
- container.classList.toggle('hide', preview === null);
- if ( preview === null ) {
- return;
- }
- container.appendChild(preview);
+ container.classList.remove('hide');
};
// https://github.com/gorhill/uBlock/issues/1511
- var shortenLongString = function(url, max) {
- var urlLen = url.length;
+ const shortenLongString = function(url, max) {
+ const urlLen = url.length;
if ( urlLen <= max ) {
return url;
}
- var n = urlLen - max - 1;
- var i = (urlLen - n) / 2 | 0;
+ const n = urlLen - max - 1;
+ const i = (urlLen - n) / 2 | 0;
return url.slice(0, i) + '…' + url.slice(i + n);
};
@@ -1091,14 +1400,153 @@ var netFilteringManager = (function() {
return urls;
};
+ const fillSummaryPaneFilterList = function(row) {
+ const nodeFromFilter = function(filter, lists) {
+ if ( Array.isArray(lists) === false || lists.length === 0 ) {
+ return;
+ }
+ const fragment = document.createDocumentFragment();
+ const template = document.querySelector(
+ '#filterFinderListEntry > span'
+ );
+ for ( const list of lists ) {
+ const span = template.cloneNode(true);
+ let a = span.querySelector('a:nth-of-type(1)');
+ a.href += encodeURIComponent(list.assetKey);
+ a.textContent = list.title;
+ if ( list.supportURL ) {
+ a = span.querySelector('a:nth-of-type(2)');
+ a.setAttribute('href', list.supportURL);
+ }
+ if ( fragment.childElementCount !== 0 ) {
+ fragment.appendChild(document.createTextNode('\n'));
+ }
+ fragment.appendChild(span);
+ }
+ return fragment;
+ };
+
+ const handleResponse = function(response) {
+ if ( response instanceof Object === false ) {
+ response = {};
+ }
+ const fragment = document.createDocumentFragment();
+ for ( const filter in response ) {
+ const spans = nodeFromFilter(filter, response[filter]);
+ if ( spans === undefined ) { continue; }
+ fragment.appendChild(spans);
+ }
+ row.children[1].appendChild(fragment);
+ // https://github.com/gorhill/uBlock/issues/2179
+ if ( row.children[1].childElementCount === 0 ) {
+ vAPI.i18n.safeTemplateToDOM(
+ 'loggerStaticFilteringFinderSentence2',
+ { filter: rawFilter },
+ row.children[1]
+ );
+ }
+ };
+ const rawFilter = targetRow.children[1].textContent;
+ const compiledFilter = targetRow.getAttribute('data-filter');
+
+ if ( targetRow.classList.contains('networkRealm') ) {
+ messaging.send(
+ 'loggerUI',
+ {
+ what: 'listsFromNetFilter',
+ compiledFilter: compiledFilter,
+ rawFilter: rawFilter
+ },
+ handleResponse
+ );
+ } else if ( targetRow.classList.contains('cosmeticRealm') ) {
+ messaging.send(
+ 'loggerUI',
+ {
+ what: 'listsFromCosmeticFilter',
+ url: targetRow.children[6].textContent,
+ rawFilter: rawFilter,
+ },
+ handleResponse
+ );
+ }
+ };
+
+ const fillSummaryPane = function() {
+ const rows = dialog.querySelectorAll('.pane.details > div');
+ const tr = targetRow;
+ const trcl = tr.classList;
+ const trch = tr.children;
+ let text;
+ // Filter and context
+ text = trch[1].textContent;
+ if (
+ (text !== '') &&
+ (trcl.contains('cosmetic') || trcl.contains('static'))
+ ) {
+ rows[0].children[1].textContent = text;
+ } else {
+ rows[0].style.display = 'none';
+ }
+ // Rule
+ if (
+ (text !== '') &&
+ (trcl.contains('dynamicHost') || trcl.contains('dynamicUrl'))
+ ) {
+ rows[2].children[1].textContent = text;
+ } else {
+ rows[2].style.display = 'none';
+ }
+ // Filter list
+ if ( trcl.contains('canLookup') ) {
+ fillSummaryPaneFilterList(rows[1]);
+ } else {
+ rows[1].style.display = 'none';
+ }
+ // Root and immediate contexts
+ const tabhn = tr.getAttribute('data-tabhn') || '';
+ const dochn = tr.getAttribute('data-dochn') || '';
+ if ( tabhn !== '' && tabhn !== dochn ) {
+ rows[3].children[1].textContent = tabhn;
+ } else {
+ rows[3].style.display = 'none';
+ }
+ if ( dochn !== '' ) {
+ rows[4].children[1].textContent = dochn;
+ } else {
+ rows[4].style.display = 'none';
+ }
+ // Partyness
+ text = tr.getAttribute('data-parties') || '';
+ if ( text !== '' ) {
+ rows[5].children[1].textContent = `${trch[4].textContent}\u2002${text}`;
+ } else {
+ rows[5].style.display = 'none';
+ }
+ // Type
+ text = trch[5].textContent;
+ if ( text !== '' ) {
+ rows[6].children[1].textContent = text;
+ } else {
+ rows[6].style.display = 'none';
+ }
+ // URL
+ text = trch[6].textContent;
+ if ( text !== '' ) {
+ rows[7].children[1].appendChild(trch[6].cloneNode(true));
+ } else {
+ rows[7].style.display = 'none';
+ }
+ };
+
// Fill dynamic URL filtering pane
- var fillDynamicPane = function() {
- var select;
+ const fillDynamicPane = function() {
+ if ( targetRow.classList.contains('cosmeticRealm') ) { return; }
+
// Fill context selector
- select = selectNode('select.dynamic.origin');
- removeAllChildren(select);
+ let select = selectNode('select.dynamic.origin');
fillOriginSelect(select, targetPageHostname, targetPageDomain);
- var option = document.createElement('option');
+ const option = document.createElement('option');
option.textContent = '*';
option.setAttribute('value', '*');
select.appendChild(option);
@@ -1110,56 +1558,50 @@ var netFilteringManager = (function() {
select.selectedIndex = 0;
// Fill entries
- var menuEntryTemplate = dialog.querySelector('table.toolbar tr.entry');
- var tbody = dialog.querySelector('div.dynamic table.entries tbody');
- var url, menuEntry;
- for ( var i = 0; i < targetURLs.length; i++ ) {
- url = targetURLs[i];
- menuEntry = menuEntryTemplate.cloneNode(true);
- menuEntry.cells[0].children[0].setAttribute('data-url', url);
- menuEntry.cells[1].textContent = shortenLongString(url, 128);
+ const menuEntryTemplate = dialog.querySelector('.dynamic .toolbar .entry');
+ const tbody = dialog.querySelector('.dynamic .entries');
+ for ( let i = 0; i < targetURLs.length; i++ ) {
+ const url = targetURLs[i];
+ const menuEntry = menuEntryTemplate.cloneNode(true);
+ menuEntry.children[0].setAttribute('data-url', url);
+ menuEntry.children[1].textContent = shortenLongString(url, 128);
tbody.appendChild(menuEntry);
}
colorize();
};
- var fillOriginSelect = function(select, hostname, domain) {
- var option, pos;
- var template = vAPI.i18n('loggerStaticFilteringSentencePartOrigin');
- var value = hostname;
+ const fillOriginSelect = function(select, hostname, domain) {
+ const template = vAPI.i18n('loggerStaticFilteringSentencePartOrigin');
+ let value = hostname;
for (;;) {
- option = document.createElement('option');
+ const option = document.createElement('option');
option.setAttribute('value', value);
option.textContent = template.replace('{{origin}}', value);
select.appendChild(option);
- if ( value === domain ) {
- break;
- }
- pos = value.indexOf('.');
- if ( pos === -1 ) {
- break;
- }
+ if ( value === domain ) { break; }
+ const pos = value.indexOf('.');
+ if ( pos === -1 ) { break; }
value = value.slice(pos + 1);
}
};
// Fill static filtering pane
- var fillStaticPane = function() {
- var template = vAPI.i18n('loggerStaticFilteringSentence');
- var rePlaceholder = /\{\{[^}]+?\}\}/g;
- var nodes = [];
- var match, pos = 0;
- var select, option, n, i, value;
+ const fillStaticPane = function() {
+ if ( targetRow.classList.contains('cosmeticRealm') ) { return; }
+
+ const template = vAPI.i18n('loggerStaticFilteringSentence');
+ const rePlaceholder = /\{\{[^}]+?\}\}/g;
+ const nodes = [];
+ let pos = 0;
for (;;) {
- match = rePlaceholder.exec(template);
- if ( match === null ) {
- break;
- }
+ const match = rePlaceholder.exec(template);
+ if ( match === null ) { break; }
if ( pos !== match.index ) {
nodes.push(document.createTextNode(template.slice(pos, match.index)));
}
pos = rePlaceholder.lastIndex;
+ let select, option;
switch ( match[0] ) {
case '{{br}}':
nodes.push(document.createElement('br'));
@@ -1196,8 +1638,8 @@ var netFilteringManager = (function() {
case '{{url}}':
select = document.createElement('select');
select.className = 'static url';
- for ( i = 0, n = targetURLs.length; i < n; i++ ) {
- value = targetURLs[i].replace(/^[a-z-]+:\/\//, '');
+ for ( let i = 0, n = targetURLs.length; i < n; i++ ) {
+ const value = targetURLs[i].replace(/^[a-z-]+:\/\//, '');
option = document.createElement('option');
option.setAttribute('value', value);
option.textContent = shortenLongString(value, 128);
@@ -1238,34 +1680,46 @@ var netFilteringManager = (function() {
if ( pos < template.length ) {
nodes.push(document.createTextNode(template.slice(pos)));
}
- var parent = dialog.querySelector('div.containers > div.static > p:first-of-type');
- removeAllChildren(parent);
- for ( i = 0; i < nodes.length; i++ ) {
+ const parent = dialog.querySelector('div.panes > .static > div:first-of-type');
+ for ( let i = 0; i < nodes.length; i++ ) {
parent.appendChild(nodes[i]);
}
parseStaticInputs();
};
- var fillDialog = function(domains) {
+ const fillDialog = function(domains) {
+ dialog = modalDialog.create(
+ '#netFilteringDialog',
+ ( ) => {
+ targetURLs = [];
+ targetRow = null;
+ dialog = null;
+ }
+ );
+ dialog.classList.toggle(
+ 'cosmeticRealm',
+ targetRow.classList.contains('cosmeticRealm')
+ );
targetDomain = domains[0];
targetPageDomain = domains[1];
targetFrameDomain = domains[2];
-
createPreview(targetType, targetURLs[0]);
+ fillSummaryPane();
fillDynamicPane();
fillStaticPane();
- document.body.appendChild(netFilteringDialog);
- netFilteringDialog.addEventListener('click', onClick, true);
- netFilteringDialog.addEventListener('change', onSelectChange, true);
- netFilteringDialog.addEventListener('input', onInputChange, true);
+ dialog.addEventListener('click', onClick, true);
+ dialog.addEventListener('change', onSelectChange, true);
+ dialog.addEventListener('input', onInputChange, true);
+ modalDialog.show();
};
- var toggleOn = function(ev) {
- dialog = netFilteringDialog.querySelector('.dialog');
- targetRow = ev.target.parentElement;
- targetTabId = tabIdFromClassName(targetRow.className);
- targetType = targetRow.cells[5].textContent.trim() || '';
- targetURLs = createTargetURLs(targetRow.cells[6].textContent);
+ const toggleOn = function(ev) {
+ targetRow = ev.target.closest('.canDetails');
+ if ( targetRow === null ) { return; }
+ ev.stopPropagation();
+ targetTabId = tabIdFromAttribute(targetRow);
+ targetType = targetRow.children[5].textContent.trim() || '';
+ targetURLs = createTargetURLs(targetRow.children[6].textContent);
targetPageHostname = targetRow.getAttribute('data-tabhn') || '';
targetFrameHostname = targetRow.getAttribute('data-dochn') || '';
@@ -1280,21 +1734,7 @@ var netFilteringManager = (function() {
);
};
- var toggleOff = function() {
- removeAllChildren(dialog.querySelector('div.preview'));
- removeAllChildren(dialog.querySelector('div.dynamic table.entries tbody'));
- dialog = null;
- targetRow = null;
- targetURLs = [];
- netFilteringDialog.removeEventListener('click', onClick, true);
- netFilteringDialog.removeEventListener('change', onSelectChange, true);
- netFilteringDialog.removeEventListener('input', onInputChange, true);
- document.body.removeChild(netFilteringDialog);
- };
-
- return {
- toggleOn: toggleOn
- };
+ return { toggleOn };
})();
// https://www.youtube.com/watch?v=XyNYrmmdUd4
@@ -1302,129 +1742,11 @@ var netFilteringManager = (function() {
/******************************************************************************/
/******************************************************************************/
-var reverseLookupManager = (function() {
- let filterFinderDialog = uDom.nodeFromId('filterFinderDialog');
- let rawFilter = '';
-
- let removeAllChildren = function(node) {
- while ( node.firstChild ) {
- node.removeChild(node.firstChild);
- }
- };
-
- // Clicking outside the dialog will close the dialog
- let onClick = function(ev) {
- if ( ev.target.classList.contains('modalDialog') ) {
- toggleOff();
- return;
- }
-
- ev.stopPropagation();
- };
-
- let nodeFromFilter = function(filter, lists) {
- if ( Array.isArray(lists) === false || lists.length === 0 ) {
- return;
- }
-
- let p = document.createElement('p');
-
- vAPI.i18n.safeTemplateToDOM(
- 'loggerStaticFilteringFinderSentence1',
- { filter: filter },
- p
- );
-
- let ul = document.createElement('ul');
- for ( let list of lists ) {
- let li = document.querySelector('#filterFinderListEntry > li')
- .cloneNode(true);
- let a = li.querySelector('a:nth-of-type(1)');
- a.href += encodeURIComponent(list.assetKey);
- a.textContent = list.title;
- if ( list.supportURL ) {
- a = li.querySelector('a:nth-of-type(2)');
- a.setAttribute('href', list.supportURL);
- }
- ul.appendChild(li);
- }
- p.appendChild(ul);
-
- return p;
- };
-
- let reverseLookupDone = function(response) {
- if ( response instanceof Object === false ) {
- response = {};
- }
-
- let dialog = filterFinderDialog.querySelector('.dialog');
- removeAllChildren(dialog);
-
- for ( let filter in response ) {
- let p = nodeFromFilter(filter, response[filter]);
- if ( p === undefined ) { continue; }
- dialog.appendChild(p);
- }
-
- // https://github.com/gorhill/uBlock/issues/2179
- if ( dialog.childElementCount === 0 ) {
- vAPI.i18n.safeTemplateToDOM(
- 'loggerStaticFilteringFinderSentence2',
- { filter: rawFilter },
- dialog
- );
- }
-
- document.body.appendChild(filterFinderDialog);
- filterFinderDialog.addEventListener('click', onClick, true);
- };
-
- let toggleOn = function(ev) {
- let row = ev.target.parentElement;
- rawFilter = row.cells[1].textContent;
- if ( rawFilter === '' ) { return; }
-
- if ( row.classList.contains('cat_net') ) {
- messaging.send(
- 'loggerUI',
- {
- what: 'listsFromNetFilter',
- compiledFilter: row.getAttribute('data-filter') || '',
- rawFilter: rawFilter
- },
- reverseLookupDone
- );
- } else if ( row.classList.contains('cat_cosmetic') ) {
- messaging.send(
- 'loggerUI',
- {
- what: 'listsFromCosmeticFilter',
- url: row.cells[6].textContent,
- rawFilter: rawFilter,
- },
- reverseLookupDone
- );
- }
- };
-
- let toggleOff = function() {
- filterFinderDialog.removeEventListener('click', onClick, true);
- document.body.removeChild(filterFinderDialog);
- rawFilter = '';
- };
-
- return {
- toggleOn: toggleOn
- };
-})();
-
-/******************************************************************************/
-/******************************************************************************/
-
const rowFilterer = (function() {
const userFilters = [];
const builtinFilters = [];
+
+ let masterFilterSwitch = true;
let filters = [];
const parseInput = function() {
@@ -1493,52 +1815,54 @@ const rowFilterer = (function() {
filters = builtinFilters.concat(userFilters);
};
- const filterOne = function(tr, clean) {
- if ( filters.length === 0 && clean === true ) { return; }
- // do not filter out doc boundaries, they help separate important
- // section of log.
- const cl = tr.classList;
- if ( cl.contains('maindoc') ) { return; }
- if ( filters.length === 0 ) {
- cl.remove('f');
- return;
+ const filterOne = function(logEntry) {
+ if (
+ logEntry.dead ||
+ selectedTabId !== 0 &&
+ logEntry.tabId !== undefined &&
+ logEntry.tabId > 0 &&
+ logEntry.tabId !== selectedTabId
+ ) {
+ return false;
}
- const cc = tr.cells;
- const ccount = cc.length;
- // 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
+
+ if ( masterFilterSwitch === false || filters.length === 0 ) {
+ return true;
+ }
+
+ // Do not filter out tab load event, they help separate key sections
+ // of logger.
+ if ( logEntry.type === 'tabLoad' ) { return true; }
+
for ( const f of filters ) {
- let hit = !f.r;
- for ( let j = 1; j < ccount; j++ ) {
- if ( f.re.test(cc[j].textContent) ) {
- hit = f.r;
- break;
- }
- }
- if ( !hit ) {
- cl.add('f');
- return;
- }
+ if ( f.re.test(logEntry.textContent) !== f.r ) { return false; }
}
- cl.remove('f');
+ return true;
};
const filterAll = function() {
- const filterCount = filters.length;
+ filteredLoggerEntries = [];
+ filteredLoggerEntryVoidedCount = 0;
+ for ( const entry of loggerEntries ) {
+ if ( filterOne(entry) === false ) { continue; }
+ filteredLoggerEntries.push(entry);
+ if ( entry.voided ) {
+ filteredLoggerEntryVoidedCount += 1;
+ }
+ }
+ viewPort.updateContent(0);
uDom.nodeFromId('filterButton').classList.toggle(
'active',
- filterCount !== 0
+ filters.length !== 0
+ );
+ uDom.nodeFromId('clean').classList.toggle(
+ 'disabled',
+ filteredLoggerEntryVoidedCount === 0
+ );
+ uDom.nodeFromId('clear').classList.toggle(
+ 'disabled',
+ filteredLoggerEntries.length === 0
);
- // Special case: no filter
- if ( filterCount === 0 ) {
- uDom('#netInspector tr').removeClass('f');
- return;
- }
- for ( const row of document.querySelector('#netInspector tbody').rows ) {
- filterOne(row);
- }
};
const onFilterChangedAsync = (function() {
@@ -1557,7 +1881,12 @@ const rowFilterer = (function() {
})();
const onFilterButton = function() {
- uDom.nodeFromId('netInspector').classList.toggle('f');
+ masterFilterSwitch = !masterFilterSwitch;
+ uDom.nodeFromId('netInspector').classList.toggle(
+ 'f',
+ masterFilterSwitch
+ );
+ filterAll();
};
const onToggleExtras = function(ev) {
@@ -1611,60 +1940,207 @@ const rowFilterer = (function() {
parseInput();
filterAll();
- return {
- filterOne,
- filterAll,
- };
+ return { filterOne, filterAll };
})();
/******************************************************************************/
-const toJunkyard = function(trs) {
- trs.remove();
- var i = trs.length;
- while ( i-- ) {
- trJunkyard.push(trs.nodeAt(i));
- }
-};
+// Discard logger entries to prevent undue memory usage growth. The criteria
+// to discard are multiple and user configurable:
+//
+// - Max number of page load per distinct tab
+// - Max number of entry per distinct tab
+// - Max entry age
-/******************************************************************************/
+const rowJanitor = (function() {
+ const tabIdToDiscard = new Set();
+ const tabIdToLoadCountMap = new Map();
+ const tabIdToEntryCountMap = new Map();
-var clearBuffer = function() {
- var tabClass = uDom.nodeFromId('pageSelector').value;
- var btsAlso = tabClass === '' || tabClass === 'tab_bts';
- var tbody = document.querySelector('#netInspector tbody');
- var tr = tbody.lastElementChild;
- var trPrevious;
- while ( tr !== null ) {
- trPrevious = tr.previousElementSibling;
- if (
- (tr.clientHeight > 0) &&
- (tr.classList.contains('tab_bts') === false || btsAlso)
- ) {
- trJunkyard.push(tbody.removeChild(tr));
+ let rowIndex = 0;
+
+ const discard = function(timeRemaining) {
+ const opts = loggerSettings.discard;
+ const maxLoadCount = typeof opts.maxLoadCount === 'number'
+ ? opts.maxLoadCount
+ : 0;
+ const maxEntryCount = typeof opts.maxEntryCount === 'number'
+ ? opts.maxEntryCount
+ : 0;
+ const obsolete = typeof opts.maxAge === 'number'
+ ? Date.now() - opts.maxAge * 60000
+ : 0;
+ const deadline = Date.now() + Math.ceil(timeRemaining);
+
+ let i = rowIndex;
+ // TODO: below should not happen -- remove when confirmed.
+ if ( i >= loggerEntries.length ) {
+ i = 0;
}
- tr = trPrevious;
- }
- uDom.nodeFromId('clear').classList.toggle(
- 'disabled',
- tbody.childElementCount === 0
- );
- uDom.nodeFromId('clean').classList.toggle(
- 'disabled',
- tbody.querySelector('#netInspector tr[data-tabid].void') === null
- );
-};
-/******************************************************************************/
+ if ( i === 0 ) {
+ tabIdToDiscard.clear();
+ tabIdToLoadCountMap.clear();
+ tabIdToEntryCountMap.clear();
+ }
-var cleanBuffer = function() {
- var rows = uDom('#netInspector tr[data-tabid].void').remove();
- var i = rows.length;
- while ( i-- ) {
- trJunkyard.push(rows.nodeAt(i));
- }
- uDom('#clean').addClass('disabled');
-};
+ let idel = -1;
+ let bufferedTabId = 0;
+ let bufferedEntryCount = 0;
+ let modified = false;
+
+ while ( i < loggerEntries.length ) {
+
+ if ( i % 64 === 0 && Date.now() >= deadline ) { break; }
+
+ const entry = loggerEntries[i];
+ const tabId = entry.tabId || 0;
+
+ if ( entry.dead || tabIdToDiscard.has(tabId) ) {
+ if ( idel === -1 ) { idel = i; }
+ i += 1;
+ continue;
+ }
+
+ if ( maxLoadCount !== 0 && entry.type === 'tabLoad' ) {
+ let count = (tabIdToLoadCountMap.get(tabId) || 0) + 1;
+ tabIdToLoadCountMap.set(tabId, count);
+ if ( count >= maxLoadCount ) {
+ tabIdToDiscard.add(tabId);
+ }
+ }
+
+ if ( maxEntryCount !== 0 ) {
+ if ( bufferedTabId !== tabId ) {
+ if ( bufferedEntryCount !== 0 ) {
+ tabIdToEntryCountMap.set(bufferedTabId, bufferedEntryCount);
+ }
+ bufferedTabId = tabId;
+ bufferedEntryCount = tabIdToEntryCountMap.get(tabId) || 0;
+ }
+ bufferedEntryCount += 1;
+ if ( bufferedEntryCount >= maxEntryCount ) {
+ tabIdToDiscard.add(bufferedTabId);
+ }
+ }
+
+ // Since entries in the logger are chronologically ordered,
+ // everything below obsolete is to be discarded.
+ if ( obsolete !== 0 && entry.tstamp <= obsolete ) {
+ if ( idel === -1 ) { idel = i; }
+ break;
+ }
+
+ if ( idel !== -1 ) {
+ loggerEntries.copyWithin(idel, i);
+ loggerEntries.length -= i - idel;
+ idel = -1;
+ modified = true;
+ }
+
+ i += 1;
+ }
+
+ if ( idel !== -1 ) {
+ loggerEntries.length = idel;
+ modified = true;
+ }
+
+ if ( i >= loggerEntries.length ) { i = 0; }
+ rowIndex = i;
+
+ if ( rowIndex === 0 ) {
+ tabIdToDiscard.clear();
+ tabIdToLoadCountMap.clear();
+ tabIdToEntryCountMap.clear();
+ }
+
+ if ( modified === false ) { return; }
+
+ rowFilterer.filterAll();
+ };
+
+ const discardAsync = function() {
+ setTimeout(
+ ( ) => {
+ self.requestIdleCallback(deadline => {
+ discard(deadline.timeRemaining());
+ discardAsync();
+ });
+ },
+ 1889
+ );
+ };
+
+ // Clear voided entries from the logger's visible content.
+ //
+ // Voided entries should be visible only from the "All" option of the
+ // tab selector.
+ //
+ const clean = function() {
+ if ( filteredLoggerEntries.length === 0 ) { return; }
+
+ let j = 0;
+ let targetEntry = filteredLoggerEntries[0];
+ for ( const entry of loggerEntries ) {
+ if ( entry !== targetEntry ) { continue; }
+ if ( entry.voided ) {
+ entry.dead = true;
+ }
+ j += 1;
+ if ( j === filteredLoggerEntries.length ) { break; }
+ targetEntry = filteredLoggerEntries[j];
+ }
+ rowFilterer.filterAll();
+ };
+
+ // Clear the logger's visible content.
+ //
+ // "Unrelated" entries -- shown for convenience -- will be also cleared
+ // if and only if the filtered logger content is made entirely of unrelated
+ // entries. In effect, this means clicking a second time on the eraser will
+ // cause unrelated entries to also be cleared.
+ //
+ const clear = function() {
+ if ( filteredLoggerEntries.length === 0 ) { return; }
+
+ let clearUnrelated = true;
+ if ( selectedTabId !== 0 ) {
+ for ( const entry of filteredLoggerEntries ) {
+ if ( entry.tabId === selectedTabId ) {
+ clearUnrelated = false;
+ break;
+ }
+ }
+ }
+
+ let j = 0;
+ let targetEntry = filteredLoggerEntries[0];
+ for ( const entry of loggerEntries ) {
+ if ( entry !== targetEntry ) { continue; }
+ if ( entry.tabId === selectedTabId || clearUnrelated ) {
+ entry.dead = true;
+ }
+ j += 1;
+ if ( j === filteredLoggerEntries.length ) { break; }
+ targetEntry = filteredLoggerEntries[j];
+ }
+ rowFilterer.filterAll();
+ };
+
+ discardAsync();
+
+ uDom.nodeFromId('clean').addEventListener('click', clean);
+ uDom.nodeFromId('clear').addEventListener('click', clear);
+
+ return {
+ inserted: function(count) {
+ if ( rowIndex !== 0 ) {
+ rowIndex += count;
+ }
+ },
+ };
+})();
/******************************************************************************/
@@ -1677,12 +2153,10 @@ const pauseNetInspector = function() {
/******************************************************************************/
const toggleVCompactView = function() {
- uDom.nodeFromId('netInspector').classList.toggle('vCompact');
- uDom('#netInspector .vExpanded').toggleClass('vExpanded');
-};
-
-const toggleVCompactRow = function(ev) {
- ev.target.parentElement.classList.toggle('vExpanded');
+ uDom.nodeFromSelector('#netInspector .vCompactToggler')
+ .classList
+ .toggle('vExpanded');
+ viewPort.updateLayout();
};
/******************************************************************************/
@@ -1735,7 +2209,7 @@ const popupManager = (function() {
const parent = uDom.nodeFromId('inspectors');
const rect = parent.getBoundingClientRect();
- popup.style.setProperty('right', (rect.right - parent.clientWidth) + 'px');
+ popup.style.setProperty('right', `${rect.right - parent.clientWidth}px`);
parent.classList.add('popupOn');
document.addEventListener('tabIdChanged', onTabIdChanged);
@@ -1756,7 +2230,8 @@ const popupManager = (function() {
realTabId = 0;
};
- const exports = {
+ const api = {
+ get tabId() { return realTabId || 0; },
toggleOff: function() {
if ( realTabId !== 0 ) {
toggleOff();
@@ -1764,15 +2239,132 @@ const popupManager = (function() {
}
};
- Object.defineProperty(exports, 'tabId', {
- get: function() { return realTabId || 0; }
- });
+ uDom.nodeFromId('showpopup').addEventListener(
+ 'click',
+ ( ) => {
+ void (realTabId === 0 ? toggleOn() : toggleOff());
+ }
+ );
- uDom('#showpopup').on('click', ( ) => {
- void (realTabId === 0 ? toggleOn() : toggleOff());
- });
+ return api;
+})();
- return exports;
+/******************************************************************************/
+
+// TODO:
+// - Give some thoughts to:
+// - an option to discard immediately filtered out new entries
+// - max entry count _per load_
+//
+const loggerSettings = (function() {
+ const settings = {
+ discard: {
+ maxAge: 240, // global
+ maxEntryCount: 2000, // per-tab
+ maxLoadCount: 20, // per-tab
+ },
+ columns: [ true, true, true, true, true, true, true, true ],
+ linesPerEntry: 4,
+ };
+
+ {
+ try {
+ const stored = JSON.parse(vAPI.localStorage.getItem('loggerSettings'));
+ if ( typeof stored.discard.maxAge === 'number' ) {
+ settings.discard.maxAge = stored.discard.maxAge;
+ }
+ if ( typeof stored.discard.maxEntryCount === 'number' ) {
+ settings.discard.maxEntryCount = stored.discard.maxEntryCount;
+ }
+ if ( typeof stored.discard.maxLoadCount === 'number' ) {
+ settings.discard.maxLoadCount = stored.discard.maxLoadCount;
+ }
+ if ( typeof stored.linesPerEntry === 'number' ) {
+ settings.linesPerEntry = stored.linesPerEntry;
+ }
+ if ( Array.isArray(stored.columns) ) {
+ settings.columns = stored.columns;
+ }
+ } catch(ex) {
+ }
+ }
+
+ const valueFromInput = function(input, def) {
+ let value = parseInt(input.value, 10);
+ if ( isNaN(value) ) { value = def; }
+ const min = parseInt(input.getAttribute('min'), 10);
+ if ( isNaN(min) === false ) {
+ value = Math.max(value, min);
+ }
+ const max = parseInt(input.getAttribute('max'), 10);
+ if ( isNaN(max) === false ) {
+ value = Math.min(value, max);
+ }
+ return value;
+ };
+
+ const toggleOn = function() {
+ const dialog = modalDialog.create(
+ '#loggerSettingsDialog',
+ dialog => {
+ toggleOff(dialog);
+ }
+ );
+
+ // Number inputs
+ let inputs = dialog.querySelectorAll('input[type="number"]');
+ inputs[0].value = settings.discard.maxAge;
+ inputs[1].value = settings.discard.maxLoadCount;
+ inputs[2].value = settings.discard.maxEntryCount;
+ inputs[3].value = settings.linesPerEntry;
+ inputs[3].addEventListener('input', ev => {
+ settings.linesPerEntry = valueFromInput(ev.target, 4);
+ viewPort.updateLayout();
+ });
+
+ // Column checkboxs
+ const onColumnChanged = ev => {
+ const input = ev.target;
+ const i = parseInt(input.getAttribute('data-column'), 10);
+ settings.columns[i] = input.checked !== true;
+ viewPort.updateLayout();
+ };
+ inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]');
+ for ( const input of inputs ) {
+ const i = parseInt(input.getAttribute('data-column'), 10);
+ input.checked = settings.columns[i] === false;
+ input.addEventListener('change', onColumnChanged);
+ }
+
+ modalDialog.show();
+ };
+
+ const toggleOff = function(dialog) {
+ // Number inputs
+ let inputs = dialog.querySelectorAll('input[type="number"]');
+ settings.discard.maxAge = valueFromInput(inputs[0], 240);
+ settings.discard.maxLoadCount = valueFromInput(inputs[1], 25);
+ settings.discard.maxEntryCount = valueFromInput(inputs[2], 2000);
+ settings.linesPerEntry = valueFromInput(inputs[3], 4);
+
+ // Column checkboxs
+ inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]');
+ for ( const input of inputs ) {
+ const i = parseInt(input.getAttribute('data-column'), 10);
+ settings.columns[i] = input.checked !== true;
+ }
+
+ vAPI.localStorage.setItem(
+ 'loggerSettings',
+ JSON.stringify(settings)
+ );
+
+ viewPort.updateLayout();
+ };
+
+ uDom.nodeFromId('settings').addEventListener('click', toggleOn);
+
+ return settings;
})();
/******************************************************************************/
@@ -1835,16 +2427,16 @@ uDom('#pageSelector').on('change', pageSelectorChanged);
uDom('#refresh').on('click', reloadTab);
uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView);
-uDom('#clean').on('click', cleanBuffer);
-uDom('#clear').on('click', clearBuffer);
uDom('#pause').on('click', pauseNetInspector);
-uDom('#maxEntries').on('change', onMaxEntriesChanged);
-uDom('#netInspector table').on('click', 'tr > td:nth-of-type(1)', toggleVCompactRow);
-uDom('#netInspector').on('click', 'tr.canLookup > td:nth-of-type(2)', reverseLookupManager.toggleOn);
-uDom('#netInspector').on('click', 'tr.cat_net > td:nth-of-type(3)', netFilteringManager.toggleOn);
+
+uDom('#netInspector').on(
+ 'click',
+ 'span:nth-of-type(2),span:nth-of-type(3),span:nth-of-type(5)',
+ netFilteringManager.toggleOn
+);
// https://github.com/gorhill/uBlock/issues/507
-// Ensure tab selector is in sync with URL hash
+// Ensure tab selector is in sync with URL hash
pageSelectorFromURLHash();
window.addEventListener('hashchange', pageSelectorFromURLHash);
@@ -1852,16 +2444,21 @@ window.addEventListener('hashchange', pageSelectorFromURLHash);
// is loaded, to be sure no spurious geometry changes will be triggered due
// to the window geometry pontentially not settling fast enough.
if ( self.location.search.includes('popup=1') ) {
- window.addEventListener('load', ( ) => {
- setTimeout(( ) => {
- popupLoggerBox = {
- x: self.screenX,
- y: self.screenY,
- w: self.outerWidth,
- h: self.outerHeight,
- };
- }, 2000);
- }, { once: true });
+ window.addEventListener(
+ 'load',
+ ( ) => {
+ setTimeout(
+ ( ) => {
+ popupLoggerBox = {
+ x: self.screenX,
+ y: self.screenY,
+ w: self.outerWidth,
+ h: self.outerHeight,
+ };
+ }, 2000);
+ },
+ { once: true }
+ );
}
/******************************************************************************/
diff --git a/src/js/logger.js b/src/js/logger.js
index d9a577546..8b5c05c31 100644
--- a/src/js/logger.js
+++ b/src/js/logger.js
@@ -26,17 +26,6 @@
µBlock.logger = (function() {
- const LogEntry = function(details) {
- this.init(details);
- };
-
- LogEntry.prototype.init = function(details) {
- if ( details.tstamp === undefined ) {
- details.tstamp = Date.now();
- }
- this.details = JSON.stringify(details);
- };
-
let buffer = null;
let lastReadTime = 0;
let writePtr = 0;
@@ -61,15 +50,22 @@
}
};
+ const boxEntry = function(details) {
+ if ( details.tstamp === undefined ) {
+ details.tstamp = Date.now();
+ }
+ return JSON.stringify(details);
+ };
+
const api = {
enabled: false,
ownerId: undefined,
writeOne: function(details) {
if ( buffer === null ) { return; }
if ( writePtr === buffer.length ) {
- buffer.push(new LogEntry(details));
+ buffer.push(boxEntry(details));
} else {
- buffer[writePtr].init(details);
+ buffer[writePtr] = boxEntry(details);
}
writePtr += 1;
},
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 0f5d0e9a2..877b3c9a3 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -1398,7 +1398,7 @@ var onMessage = function(request, sender, callback) {
.setURL(request.docURL)
.setDocOriginFromURL(request.docURL);
if ( pageStore.filterRequest(fctxt) === 0 ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
}
break;
diff --git a/src/js/pagestore.js b/src/js/pagestore.js
index 255a6e09d..a3e2e6a46 100644
--- a/src/js/pagestore.js
+++ b/src/js/pagestore.js
@@ -322,7 +322,7 @@ PageStore.prototype.init = function(tabId, context) {
µb.logger.enabled &&
context === 'tabCommitted'
) {
- fctxt.setRealm('net')
+ fctxt.setRealm('network')
.setType('generichide')
.setFilter(µb.staticNetFilteringEngine.toLogData())
.toLogger();
diff --git a/src/js/popup.js b/src/js/popup.js
index 2829f7403..b2c78b01e 100644
--- a/src/js/popup.js
+++ b/src/js/popup.js
@@ -753,7 +753,7 @@ const gotoURL = function(ev) {
let url = this.getAttribute('href');
if (
- url === 'logger-ui.html#tab_active' &&
+ url === 'logger-ui.html#_' &&
typeof popupData.tabId === 'number'
) {
url += '+' + popupData.tabId;
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index dc8382d48..59518f056 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -2192,7 +2192,9 @@ FilterContainer.prototype.compile = function(raw, writer) {
if ( parsed.unsupported ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
- error: `Invalid network filter in ${who}: ${raw}`
+ realm: 'message',
+ type: 'error',
+ text: `Invalid network filter in ${who}: ${raw}`
});
return false;
}
diff --git a/src/js/tab.js b/src/js/tab.js
index ea8ec1e96..64399c5fd 100644
--- a/src/js/tab.js
+++ b/src/js/tab.js
@@ -789,7 +789,7 @@ vAPI.tabs.onPopupUpdated = (function() {
// Log only for when there was a hit against an actual filter (allow or block).
// https://github.com/gorhill/uBlock/issues/2776
if ( µb.logger.enabled ) {
- fctxt.setRealm('net').setType(popupType);
+ fctxt.setRealm('network').setType(popupType);
if ( popupType === 'popup' ) {
fctxt.setURL(targetURL)
.setTabId(openerTabId)
diff --git a/src/js/traffic.js b/src/js/traffic.js
index cc211e1be..000f76e14 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -91,7 +91,7 @@ const onBeforeRequest = function(details) {
pageStore.journalAddRequest(fctxt.getHostname(), result);
if ( µb.logger.enabled ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
// Not blocked
@@ -203,7 +203,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
}
if ( logEnabled ) {
- fctxt.setRealm('net').setFilter(logData).toLogger();
+ fctxt.setRealm('network').setFilter(logData).toLogger();
}
// Not blocked
@@ -311,7 +311,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) {
}
if ( µb.logger.enabled ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
// Blocked?
@@ -396,7 +396,7 @@ const onBeforeMaybeSpuriousCSPReport = (function() {
// At this point, we have a potentially spurious CSP report.
if ( µBlock.logger.enabled ) {
- fctxt.setRealm('net')
+ fctxt.setRealm('network')
.setType('csp_report')
.setFilter({ result: 1, source: 'global', raw: 'no-spurious-csp-report' })
.toLogger();
@@ -775,7 +775,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
if ( pageStore.filterScripting(fctxt, true) === 1 ) {
builtinDirectives.push("script-src http: https:");
if ( loggerEnabled ) {
- fctxt.setRealm('net').setType('scripting').toLogger();
+ fctxt.setRealm('network').setType('scripting').toLogger();
}
} else {
fctxt.type = 'inline-script';
@@ -784,7 +784,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
builtinDirectives.push("script-src 'unsafe-eval' * blob: data:");
}
if ( result !== 0 && loggerEnabled ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
}
@@ -794,7 +794,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
if ( pageStore.filterRequest(fctxt) === 1 ) {
builtinDirectives.push('font-src *');
if ( loggerEnabled ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
}
@@ -825,7 +825,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
) === 2
) {
if ( loggerEnabled ) {
- fctxt.setRealm('net')
+ fctxt.setRealm('network')
.setType('csp')
.setFilter(µb.sessionURLFiltering.toLogData())
.toLogger();
@@ -844,7 +844,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
) === 2
) {
if ( loggerEnabled ) {
- fctxt.setRealm('net')
+ fctxt.setRealm('network')
.setType('csp')
.setFilter(µb.sessionFirewall.toLogData())
.toLogger();
@@ -856,7 +856,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
// Static CSP policies will be applied.
if ( logDataEntries !== undefined ) {
- fctxt.setRealm('net').setType('csp');
+ fctxt.setRealm('network').setType('csp');
for ( const entry of logDataEntries ) {
fctxt.setFilter(entry).toLogger();
}
@@ -908,7 +908,7 @@ const foilLargeMediaElement = function(fctxt, pageStore, responseHeaders) {
if ( result === 0 ) { return; }
if ( µBlock.logger.enabled ) {
- fctxt.setRealm('net').toLogger();
+ fctxt.setRealm('network').toLogger();
}
return { cancel: true };
diff --git a/src/js/udom.js b/src/js/udom.js
index dc85e6b21..af8fd2c04 100644
--- a/src/js/udom.js
+++ b/src/js/udom.js
@@ -132,12 +132,9 @@ var addSelectorToList = function(list, selector, context) {
/******************************************************************************/
-var nodeInNodeList = function(node, nodeList) {
- var i = nodeList.length;
- while ( i-- ) {
- if ( nodeList[i] === node ) {
- return true;
- }
+const nodeInNodeList = function(node, nodeList) {
+ for ( const other of nodeList ) {
+ if ( other === node ) { return true; }
}
return false;
};
@@ -603,9 +600,9 @@ DOMList.prototype.toggleClasses = function(classNames, targetState) {
/******************************************************************************/
-var listenerEntries = [];
+const listenerEntries = [];
-var ListenerEntry = function(target, type, capture, callback) {
+const ListenerEntry = function(target, type, capture, callback) {
this.target = target;
this.type = type;
this.capture = capture;
@@ -621,13 +618,16 @@ ListenerEntry.prototype.dispose = function() {
/******************************************************************************/
-var makeEventHandler = function(selector, callback) {
+const makeEventHandler = function(selector, callback) {
return function(event) {
- var dispatcher = event.currentTarget;
- if ( !dispatcher || typeof dispatcher.querySelectorAll !== 'function' ) {
+ const dispatcher = event.currentTarget;
+ if (
+ dispatcher instanceof HTMLElement === false ||
+ typeof dispatcher.querySelectorAll !== 'function'
+ ) {
return;
}
- var receiver = event.target;
+ const receiver = event.target;
if ( nodeInNodeList(receiver, dispatcher.querySelectorAll(selector)) ) {
callback.call(receiver, event);
}
@@ -642,9 +642,10 @@ DOMList.prototype.on = function(etype, selector, callback) {
callback = makeEventHandler(selector, callback);
}
- var i = this.nodes.length;
- while ( i-- ) {
- listenerEntries.push(new ListenerEntry(this.nodes[i], etype, selector !== undefined, callback));
+ for ( const node of this.nodes ) {
+ listenerEntries.push(
+ new ListenerEntry(node, etype, selector !== undefined, callback)
+ );
}
return this;
};
diff --git a/src/logger-ui.html b/src/logger-ui.html
index 70a7a6fee..bdb4b64df 100644
--- a/src/logger-ui.html
+++ b/src/logger-ui.html
@@ -10,14 +10,15 @@
+
-
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+ 00:00:00 ** 3,3inline-script
+
+
-
+
+
+
+
+
+
+
+
-