diff --git a/toolkit/components/aboutprocesses/content/aboutProcesses.css b/toolkit/components/aboutprocesses/content/aboutProcesses.css index 7690db974220..fd7ef032a878 100644 --- a/toolkit/components/aboutprocesses/content/aboutProcesses.css +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.css @@ -10,6 +10,7 @@ html { body { overflow-x: hidden; } + #process-table { -moz-user-select: none; font-size: 1em; @@ -48,13 +49,19 @@ td:nth-child(1) { /* At least one column needs to have a flexible width, so no width specified for td:nth-child(2) */ td:nth-child(3) { - width: 10%; + width: 10%; } td:nth-child(4) { - width: 10%; + width: 10%; } td:nth-child(5) { - width: 10%; + width: 10%; +} +td:nth-child(6) { + width: 10%; +} +td:nth-child(7) { + width: 16px; } #process-thead > tr { @@ -73,7 +80,8 @@ td:nth-child(5) { } td { padding: 5px 10px; - min-height: 2em; + min-height: 1.5em; + max-height: 1.5em; color: var(--in-content-text-color); max-width: 70vw; overflow: hidden; @@ -167,3 +175,25 @@ td { #process-tbody > tr.thread { font-size-adjust: 0.5; } + +.killing { + opacity: 0.3; + transition-property: "opacity"; + transition-duration: 1s; +} + +.killed { + opacity: 0.3; +} + +/* icons */ +.close-icon { + background: url("chrome://global/skin/icons/delete.svg") no-repeat center; + opacity: 0; +} +tr:-moz-any([selected], :hover) > .close-icon { + opacity: 1; +} +.close-icon:hover { + background-color: var(--in-content-button-background); +} diff --git a/toolkit/components/aboutprocesses/content/aboutProcesses.ftl b/toolkit/components/aboutprocesses/content/aboutProcesses.ftl index 3c137c33b02f..3f4b45dffb79 100644 --- a/toolkit/components/aboutprocesses/content/aboutProcesses.ftl +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.ftl @@ -5,6 +5,11 @@ # Page title about-processes-title = Process Manager +## Tooltips + +about-processes-shutdown-process = + .title = Kill process + ## Column headers about-processes-column-id = Id diff --git a/toolkit/components/aboutprocesses/content/aboutProcesses.html b/toolkit/components/aboutprocesses/content/aboutProcesses.html index c2089a288823..bccde311d454 100644 --- a/toolkit/components/aboutprocesses/content/aboutProcesses.html +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.html @@ -22,6 +22,7 @@ + diff --git a/toolkit/components/aboutprocesses/content/aboutProcesses.js b/toolkit/components/aboutprocesses/content/aboutProcesses.js index 274c5712dc91..88701bbb12c3 100644 --- a/toolkit/components/aboutprocesses/content/aboutProcesses.js +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.js @@ -229,7 +229,10 @@ var State = { var View = { _fragment: document.createDocumentFragment(), + // Processes that we killed during the previous iteration. + _killedRecently: [], async commit() { + this._killedRecently.length = 0; let tbody = document.getElementById("process-tbody"); // Force translation to happen before we insert the new content in the DOM @@ -329,6 +332,33 @@ var View = { classes: ["numberOfThreads"], }); + // Column: Kill button – but not for all processes. + let killButton = this._addCell(row, { + content: "", + classes: ["action-icon"], + }); + + if (data.type == "socket") { + // We can't kill this process. Let's not pretend that we can. + // Don't display the kill button. + } else if (this._killedRecently.some(pid => pid == data.pid)) { + // We're racing between the "kill" action and the visual refresh. + // In a few cases, we could end up with the visual refresh showing + // a process as un-killed while we actually just killed it. + // + // We still want to display the process in case something actually + // went bad and the user needs the information to realize this. + // But we also want to make it visible that the process is being + // killed. + row.classList.add("killed"); + } else { + // Otherwise, let's display the kill button. + killButton.classList.add("close-icon"); + document.l10n.setAttributes( + killButton, + "about-processes-shutdown-process" + ); + } this._fragment.appendChild(row); return row; }, @@ -391,13 +421,21 @@ var View = { classes: ["numberOfThreads"], }); + // Column: Buttons (empty) + this._addCell(row, { + content: "", + classes: [], + }); + this._fragment.appendChild(row); return row; }, _addCell(row, { content, classes }) { let elt = document.createElement("td"); - this._setTextAndTooltip(elt, content); + if (content) { + this._setTextAndTooltip(elt, content); + } elt.classList.add(...classes); row.appendChild(elt); return elt; @@ -541,19 +579,13 @@ var Control = { tbody.addEventListener("click", event => { this._updateLastMouseEvent(); - // Handle showing or hiding subitems of a row. let target = event.target; if (target.classList.contains("twisty")) { - let row = target.parentNode.parentNode; - let id = row.process.pid; - if (target.classList.toggle("open")) { - this._openItems.add(id); - this._showChildren(row); - View.insertAfterRow(row); - } else { - this._openItems.delete(id); - this._removeSubtree(row); - } + // Handle showing or hiding subitems of a row. + this._handleTwisty(target); + return; + } else if (target.classList.contains("close-icon")) { + this._handleClose(target); return; } @@ -756,6 +788,34 @@ var Control = { return order; }); }, + + _handleTwisty(target) { + let row = target.parentNode.parentNode; + let id = row.process.pid; + if (target.classList.toggle("open")) { + this._openItems.add(id); + this._showChildren(row); + View.insertAfterRow(row); + } else { + this._openItems.delete(id); + this._removeSubtree(row); + } + }, + + _handleClose(target) { + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + let row = target.parentNode; + let pid = row.process.pid; + ProcessTools.kill(pid); + + row.classList.add("killing"); + target.classList.remove("close-icon"); + // Make sure that the user can't click twice on the kill button. + // Otherwise, chaos might ensure. Plus we risk crashing under Windows. + View._killedRecently.push(pid); + }, }; window.onload = async function() {