morgan pushed to branch tor-browser-128.7.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
8e663084
by Henry Wilkes at 2025-02-20T17:30:12+00:00
-
0cc2f849
by Henry Wilkes at 2025-02-20T17:38:02+00:00
-
21c61532
by Henry Wilkes at 2025-02-20T17:38:03+00:00
5 changed files:
- browser/components/torpreferences/content/torLogDialog.js
- browser/components/torpreferences/content/torLogDialog.xhtml
- browser/components/torpreferences/content/torPreferences.css
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
Changes:
| ... | ... | @@ -4,20 +4,18 @@ const { setTimeout, clearTimeout } = ChromeUtils.importESModule( |
| 4 | 4 | "resource://gre/modules/Timer.sys.mjs"
|
| 5 | 5 | );
|
| 6 | 6 | |
| 7 | -const { TorProviderBuilder } = ChromeUtils.importESModule(
|
|
| 7 | +const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
|
|
| 8 | 8 | "resource://gre/modules/TorProviderBuilder.sys.mjs"
|
| 9 | 9 | );
|
| 10 | 10 | |
| 11 | -window.addEventListener(
|
|
| 12 | - "DOMContentLoaded",
|
|
| 13 | - () => {
|
|
| 11 | +const gTorLogDialog = {
|
|
| 12 | + init() {
|
|
| 14 | 13 | const dialog = document.getElementById("torPreferences-torLog-dialog");
|
| 15 | 14 | const copyLogButton = dialog.getButton("extra1");
|
| 16 | 15 | copyLogButton.setAttribute("data-l10n-id", "tor-log-dialog-copy-button");
|
| 17 | 16 | |
| 18 | - const logText = document.getElementById(
|
|
| 19 | - "torPreferences-torDialog-textarea"
|
|
| 20 | - );
|
|
| 17 | + this._logTable = document.getElementById("tor-log-table");
|
|
| 18 | + this._logBody = document.getElementById("tor-log-body");
|
|
| 21 | 19 | |
| 22 | 20 | let restoreButtonTimeout = null;
|
| 23 | 21 | copyLogButton.addEventListener("command", () => {
|
| ... | ... | @@ -25,7 +23,14 @@ window.addEventListener( |
| 25 | 23 | let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
| 26 | 24 | Ci.nsIClipboardHelper
|
| 27 | 25 | );
|
| 28 | - clipboard.copyString(logText.value);
|
|
| 26 | + // The copied text should match the text content the user would get if
|
|
| 27 | + // they hand-selected the entire table.
|
|
| 28 | + clipboard.copyString(
|
|
| 29 | + Array.from(
|
|
| 30 | + this._logTable.querySelectorAll("td"),
|
|
| 31 | + el => el.textContent
|
|
| 32 | + ).join("\n")
|
|
| 33 | + );
|
|
| 29 | 34 | |
| 30 | 35 | copyLogButton.setAttribute(
|
| 31 | 36 | "data-l10n-id",
|
| ... | ... | @@ -47,13 +52,79 @@ window.addEventListener( |
| 47 | 52 | }, RESTORE_TIME);
|
| 48 | 53 | });
|
| 49 | 54 | |
| 55 | + // Intercept the copy event.
|
|
| 56 | + // NOTE: We attach this to the window rather than the _logTable because if
|
|
| 57 | + // the whole table is selected it will not receive the "copy" event.
|
|
| 58 | + window.addEventListener("copy", event => {
|
|
| 59 | + event.preventDefault();
|
|
| 60 | + event.clipboardData.setData(
|
|
| 61 | + "text",
|
|
| 62 | + // By default the selected text will insert "\n\t" between the <td>
|
|
| 63 | + // elements, which separates the timestamp from the message column.
|
|
| 64 | + // We drop this "\t" character, to just keep the "\n".
|
|
| 65 | + window.getSelection().toString().replace(/^\t/gm, "")
|
|
| 66 | + );
|
|
| 67 | + });
|
|
| 68 | + |
|
| 50 | 69 | // A waiting state should not be needed at this point.
|
| 51 | 70 | // Also, we probably cannot even arrive here if the provider failed to
|
| 52 | 71 | // initialize, otherwise we could use a try/catch, and write the exception
|
| 53 | 72 | // text in the logs, instead.
|
| 54 | - TorProviderBuilder.build().then(
|
|
| 55 | - provider => (logText.value = provider.getLog())
|
|
| 56 | - );
|
|
| 73 | + TorProviderBuilder.build().then(provider => {
|
|
| 74 | + Services.obs.addObserver(this, TorProviderTopics.TorLog);
|
|
| 75 | + window.addEventListener(
|
|
| 76 | + "unload",
|
|
| 77 | + () => {
|
|
| 78 | + Services.obs.removeObserver(this, TorProviderTopics.TorLog);
|
|
| 79 | + },
|
|
| 80 | + { once: true }
|
|
| 81 | + );
|
|
| 82 | + |
|
| 83 | + for (const logEntry of provider.getLog()) {
|
|
| 84 | + this.addLogEntry(logEntry, true);
|
|
| 85 | + }
|
|
| 86 | + // Set the initial scroll to the bottom.
|
|
| 87 | + this._logTable.scrollTo({
|
|
| 88 | + top: this._logTable.scrollTopMax,
|
|
| 89 | + behaviour: "instant",
|
|
| 90 | + });
|
|
| 91 | + });
|
|
| 92 | + },
|
|
| 93 | + |
|
| 94 | + observe(subject, topic) {
|
|
| 95 | + if (topic === TorProviderTopics.TorLog) {
|
|
| 96 | + this.addLogEntry(subject.wrappedJSObject, false);
|
|
| 97 | + }
|
|
| 98 | + },
|
|
| 99 | + |
|
| 100 | + addLogEntry(logEntry, initial) {
|
|
| 101 | + const timeEl = document.createElement("td");
|
|
| 102 | + timeEl.textContent = logEntry.timestamp;
|
|
| 103 | + timeEl.classList.add("time");
|
|
| 104 | + const messageEl = document.createElement("td");
|
|
| 105 | + messageEl.textContent = `[${logEntry.type}] ${logEntry.msg}`;
|
|
| 106 | + messageEl.classList.add("message");
|
|
| 107 | + |
|
| 108 | + const row = document.createElement("tr");
|
|
| 109 | + row.append(timeEl, messageEl);
|
|
| 110 | + |
|
| 111 | + // If this is a new entry, and we are currently scrolled to the bottom (with
|
|
| 112 | + // a 6px allowance) we keep the scroll position at the bottom to "follow"
|
|
| 113 | + // the updates.
|
|
| 114 | + const scrollToBottom =
|
|
| 115 | + !initial && this._logTable.scrollTop >= this._logTable.scrollTopMax - 6;
|
|
| 116 | + |
|
| 117 | + this._logBody.append(row);
|
|
| 118 | + if (scrollToBottom) {
|
|
| 119 | + this._logTable.scrollTo({ top: this._logTable.scrollTopMax });
|
|
| 120 | + }
|
|
| 121 | + },
|
|
| 122 | +};
|
|
| 123 | + |
|
| 124 | +window.addEventListener(
|
|
| 125 | + "DOMContentLoaded",
|
|
| 126 | + () => {
|
|
| 127 | + gTorLogDialog.init();
|
|
| 57 | 128 | },
|
| 58 | 129 | { once: true }
|
| 59 | 130 | ); |
| ... | ... | @@ -23,10 +23,33 @@ |
| 23 | 23 | |
| 24 | 24 | <script src="chrome://browser/content/torpreferences/torLogDialog.js" />
|
| 25 | 25 | |
| 26 | - <html:textarea
|
|
| 27 | - id="torPreferences-torDialog-textarea"
|
|
| 28 | - multiline="true"
|
|
| 29 | - readonly="true"
|
|
| 30 | - />
|
|
| 26 | + <!-- We use a <table> element rather than a <ol>. A table structure allows
|
|
| 27 | + - screen reader users to navigate within one column so they can avoid the
|
|
| 28 | + - readback of the timestamp on every row. See tor-browser#43328.
|
|
| 29 | + - NOTE: We add the explicit role="table". Whilst this should not be
|
|
| 30 | + - neccessary, nor is it recommended, some screen readers (Orca 46) do not
|
|
| 31 | + - read out the default table role if the CSS `display` is not `table`.
|
|
| 32 | + - NOTE: Even though this table is updated with live information, we do
|
|
| 33 | + - not want to make this an aria-live area or use a "log updated"
|
|
| 34 | + - notification because the log messages are potentially busy.
|
|
| 35 | + - Moreover, the live updates is a convience so that the log doesn't need
|
|
| 36 | + - to be manually refreshed, rather than important information.
|
|
| 37 | + - NOTE: We add a tabindex=0 to make this element focusable with or
|
|
| 38 | + - without the overflow. This also makes the table the the initial focus
|
|
| 39 | + - of the dialog.
|
|
| 40 | + - NOTE: We add lang="en" and dir="ltr" to the <tbody> since the content
|
|
| 41 | + - of the log is in English. We do not add this to the <table> element
|
|
| 42 | + - since its aria-label is localised and we want the scrollbar to match
|
|
| 43 | + - the locale direction.
|
|
| 44 | + - NOTE: We avoid any whitespace between the <table> and <tbody> to ensure
|
|
| 45 | + - it does not contribute to the text content when the top of the table is
|
|
| 46 | + - manually copied. -->
|
|
| 47 | + <html:table
|
|
| 48 | + id="tor-log-table"
|
|
| 49 | + role="table"
|
|
| 50 | + data-l10n-id="tor-log-dialog-table"
|
|
| 51 | + tabindex="0"
|
|
| 52 | + ><html:tbody id="tor-log-body" lang="en" dir="ltr"></html:tbody
|
|
| 53 | + ></html:table>
|
|
| 31 | 54 | </dialog>
|
| 32 | 55 | </window> |
| ... | ... | @@ -1058,12 +1058,39 @@ groupbox#torPreferences-bridges-group textarea { |
| 1058 | 1058 | }
|
| 1059 | 1059 | |
| 1060 | 1060 | /* Tor logs dialog */
|
| 1061 | -textarea#torPreferences-torDialog-textarea {
|
|
| 1061 | +#tor-log-table {
|
|
| 1062 | 1062 | flex: 1 0 auto;
|
| 1063 | - font-family: monospace;
|
|
| 1064 | - font-size: 0.8em;
|
|
| 1065 | - white-space: pre;
|
|
| 1066 | 1063 | overflow: auto;
|
| 1067 | - /* 10 lines */
|
|
| 1068 | 1064 | min-height: 20em;
|
| 1065 | + height: 20em;
|
|
| 1066 | + display: flex;
|
|
| 1067 | + flex-direction: column;
|
|
| 1068 | + padding: var(--space-small);
|
|
| 1069 | + margin-block-end: 4px;
|
|
| 1070 | + border: 1px solid var(--in-content-box-border-color);
|
|
| 1071 | + border-radius: var(--border-radius-small);
|
|
| 1072 | + font-size: var(--font-size-small);
|
|
| 1073 | +}
|
|
| 1074 | + |
|
| 1075 | +#tor-log-body,
|
|
| 1076 | +#tor-log-table tr {
|
|
| 1077 | + display: contents;
|
|
| 1078 | +}
|
|
| 1079 | + |
|
| 1080 | +#tor-log-table td {
|
|
| 1081 | + flex: 0 0 auto;
|
|
| 1082 | + padding: 0;
|
|
| 1083 | +}
|
|
| 1084 | + |
|
| 1085 | +#tor-log-table td.time {
|
|
| 1086 | + color: var(--text-color-deemphasized);
|
|
| 1087 | + margin-block-end: var(--space-xsmall);
|
|
| 1088 | +}
|
|
| 1089 | + |
|
| 1090 | +#tor-log-table td.message {
|
|
| 1091 | + overflow-wrap: anywhere;
|
|
| 1092 | +}
|
|
| 1093 | + |
|
| 1094 | +#tor-log-table tr:not(:last-of-type) td.message {
|
|
| 1095 | + margin-block-end: var(--space-medium);
|
|
| 1069 | 1096 | } |
| ... | ... | @@ -512,14 +512,12 @@ export class TorProvider { |
| 512 | 512 | }
|
| 513 | 513 | |
| 514 | 514 | /**
|
| 515 | - * Returns captured log message as a text string (one message per line).
|
|
| 515 | + * Returns captured log messages.
|
|
| 516 | 516 | *
|
| 517 | - * @returns {string} The logs we collected from the tor daemon so far
|
|
| 517 | + * @returns {LogEntry[]} The logs we collected from the tor daemon so far.
|
|
| 518 | 518 | */
|
| 519 | 519 | getLog() {
|
| 520 | - return this.#logs
|
|
| 521 | - .map(logObj => `${logObj.timestamp} [${logObj.type}] ${logObj.msg}`)
|
|
| 522 | - .join(TorLauncherUtil.isWindows ? "\r\n" : "\n");
|
|
| 520 | + return structuredClone(this.#logs);
|
|
| 523 | 521 | }
|
| 524 | 522 | |
| 525 | 523 | /**
|
| ... | ... | @@ -423,6 +423,9 @@ tor-view-log-button = View log… |
| 423 | 423 | # "log" is a noun, referring to the recorded text output of the Tor process.
|
| 424 | 424 | tor-log-dialog-title =
|
| 425 | 425 | .title = Tor log
|
| 426 | +# The screen-reader name for the Tor log table. Should match the dialog title.
|
|
| 427 | +tor-log-dialog-table =
|
|
| 428 | + .aria-label = { tor-log-dialog-title.title }
|
|
| 426 | 429 | # "log" is a noun, referring to the recorded text output of the Tor process.
|
| 427 | 430 | tor-log-dialog-copy-button =
|
| 428 | 431 | .label = Copy Tor log to clipboard
|