Bug 1716748 - Add a nightly only viewer for interaction metadata. r=mak

Differential Revision: https://phabricator.services.mozilla.com/D117999
This commit is contained in:
Mark Banner 2021-06-25 08:18:20 +00:00
parent 9d1ed273fe
commit 9b29670324
5 changed files with 233 additions and 0 deletions

View File

@ -76,6 +76,13 @@ if (AppConstants.NIGHTLY_BUILD) {
gExceptionPaths.push("resource://builtin-addons/translations/");
}
if (AppConstants.NIGHTLY_BUILD) {
// This is nightly-only debug tool.
gExceptionPaths.push(
"chrome://browser/content/places/interactionsViewer.html"
);
}
// Each whitelist entry should have a comment indicating which file is
// referencing the whitelisted file in a way that the test can't detect, or a
// bug number to remove or use the file if it is indeed currently unreferenced.

View File

@ -21,3 +21,8 @@ browser.jar:
* content/browser/places/bookmarksSidebar.xhtml (content/bookmarksSidebar.xhtml)
content/browser/places/bookmarksSidebar.js (content/bookmarksSidebar.js)
content/browser/places/editBookmark.js (content/editBookmark.js)
#ifdef NIGHTLY_BUILD
content/browser/places/interactionsViewer.css (metadataViewer/interactionsViewer.css)
content/browser/places/interactionsViewer.html (metadataViewer/interactionsViewer.html)
content/browser/places/interactionsViewer.js (metadataViewer/interactionsViewer.js)
#endif

View File

@ -0,0 +1,47 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
body {
padding: .5em 2em;
}
.hidden {
display: none;
}
.message-bar-icon {
vertical-align: middle;
}
#metadataLimit {
padding-bottom: 1em;
}
#metadataViewer {
display: grid;
grid-template-columns: max-content fit-content(100%) repeat(4, max-content);
}
#metadataViewer > div {
padding: .3em 1em;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Sets the first row of elements to bold. The number is the number of columns */
#metadataViewer > div:nth-child(-n+6) {
font-weight: bold;
}
/* Highlights every other row to make visual scanning of the table easier.
The numbers need to be adapted if the number of columns changes. */
#metadataViewer > div:nth-child(6n+7):nth-child(12n+7),
#metadataViewer > div:nth-child(6n+8):nth-child(12n+8),
#metadataViewer > div:nth-child(6n+9):nth-child(12n+9),
#metadataViewer > div:nth-child(6n+10):nth-child(12n+10),
#metadataViewer > div:nth-child(6n+11):nth-child(12n+11),
#metadataViewer > div:nth-child(6n+12):nth-child(12n+12) {
background: var(--in-content-box-background-odd);
}

View File

@ -0,0 +1,28 @@
<!--
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<!DOCTYPE html>
<html>
<head>
<title>Interactions Debug Viewer</title>
<script type="module" src="chrome://browser/content/places/interactionsViewer.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" type="text/css" href="chrome://browser/content/places/interactionsViewer.css">
</head>
<body>
<div id="enabledWarning" class="message-bar message-bar-warning" hidden>
<img class="message-bar-icon" src="chrome://browser/skin/warning.svg">
<descripton class="message-bar-description">
You need to have <code>browser.places.interactions.enabled</code>
set to true (and restart) for metadata recording to be enabled.
</descripton>
</div>
<h1>Page Metadata</h1>
<div id="metadataLimit"></div>
<div id="metadataViewer">
</div>
</body>
</html>

View File

@ -0,0 +1,146 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env module */
const { Interactions } = ChromeUtils.import(
"resource:///modules/Interactions.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { PlacesUtils } = ChromeUtils.import(
"resource://gre/modules/PlacesUtils.jsm"
);
const metadataHandler = new (class {
/**
* Maximum number of rows to display by default.
*
* @typedef {number}
*/
#maxRows = 100;
/**
* A reference to the database connection.
*
* @typedef {mozIStorageConnection}
*/
#db = null;
/**
* A map of columns that are displayed by default.
* @note If you change the number of columns, then you also need to change
* the css to account for the new number.
*
* - The key is the column name in the database.
* - The header is the column header on the table.
* - The modifier is a function to modify the returned value from the database
* for display.
* - includeTitle determines if the title attribute should be set on that
* column, for tooltips, e.g. if an element is likely to overflow.
*
* @typedef {Map<string, object>}
*/
#columnMap = new Map([
["id", { header: "ID" }],
["url", { header: "URL", includeTitle: true }],
[
"updated_at",
{
header: "Updated",
modifier: updatedAt => new Date(updatedAt).toLocaleString(),
},
],
[
"total_view_time",
{
header: "View Time (s)",
modifier: totalViewTime => (totalViewTime / 1000).toFixed(2),
},
],
[
"typing_time",
{
header: "Typing Time (s)",
modifier: typingTime => (typingTime / 1000).toFixed(2),
},
],
["key_presses", { header: "Key Presses" }],
]);
async start() {
this.#setupUI();
this.#db = await PlacesUtils.promiseDBConnection();
await this.#updateDisplay();
setInterval(this.#updateDisplay.bind(this), 10000);
}
/**
* Creates the initial table layout to the correct size.
*/
#setupUI() {
let tableBody = document.createDocumentFragment();
let header = document.createDocumentFragment();
for (let details of this.#columnMap.values()) {
let columnDiv = document.createElement("div");
columnDiv.textContent = details.header;
header.appendChild(columnDiv);
}
tableBody.appendChild(header);
for (let i = 0; i < this.#maxRows; i++) {
let row = document.createDocumentFragment();
for (let j = 0; j < this.#columnMap.size; j++) {
row.appendChild(document.createElement("div"));
}
tableBody.appendChild(row);
}
let viewer = document.getElementById("metadataViewer");
viewer.appendChild(tableBody);
let limit = document.getElementById("metadataLimit");
limit.textContent = `Maximum rows displayed: ${this.#maxRows}.`;
}
/**
* Loads the current metadata from the database and updates the display.
*/
async #updateDisplay() {
let rows = await this.#db.executeCached(
`SELECT m.id AS id, h.url AS url, updated_at, total_view_time,
typing_time, key_presses FROM moz_places_metadata m
JOIN moz_places h ON h.id = m.place_id
ORDER BY updated_at DESC
LIMIT ${this.#maxRows}`
);
let viewer = document.getElementById("metadataViewer");
let index = this.#columnMap.size;
for (let row of rows) {
for (let [column, details] of this.#columnMap.entries()) {
let value = row.getResultByName(column);
if (details.includeTitle) {
viewer.children[index].setAttribute("title", value);
}
viewer.children[index].textContent = details.modifier
? details.modifier(value)
: value;
index++;
}
}
}
})();
function checkPrefs() {
if (
!Services.prefs.getBoolPref("browser.places.interactions.enabled", false)
) {
let warning = document.getElementById("enabledWarning");
warning.hidden = false;
}
}
checkPrefs();
metadataHandler.start().catch(console.error);