Bug 1450067 - Show last update timestamp for a sw. r=nchevobbe draft
authorBelén Albeza <balbeza@mozilla.com>
Thu, 24 May 2018 12:04:54 +0200
changeset 800585 27a82b144b9e10b4b2ad315558a3f2a103fd3701
parent 799120 47e81ea1ef10189ef210867934bf36e14cf223dc
push id111412
push userbalbeza@mozilla.com
push dateMon, 28 May 2018 15:26:54 +0000
reviewersnchevobbe
bugs1450067
milestone62.0a1
Bug 1450067 - Show last update timestamp for a sw. r=nchevobbe MozReview-Commit-ID: rul88NKcHZ
devtools/client/application/src/components/Worker.css
devtools/client/application/src/components/Worker.js
devtools/client/application/test/browser_application_panel_list-single-worker.js
devtools/server/actors/worker.js
devtools/shared/client/root-client.js
--- a/devtools/client/application/src/components/Worker.css
+++ b/devtools/client/application/src/components/Worker.css
@@ -4,16 +4,17 @@
 
  /*
  * The current layout of a service worker item is
  *
  *  +----------------------------+----------------+
  *  | Service worker scope       | Unregister_btn |
  *  +---+----------+-------------+----------------|
  *  |     "Source" | script_name debug_link       |
+    |              | "Updated" update_time        |
  *  |--------------+-------------+----------------|
  *  |     "Status" | status start_link            |
  *  +---+----------+-------------+----------------|
  */
 
 .worker {
   display: grid;
   grid-template-rows: auto auto auto;
@@ -26,17 +27,17 @@
   font-size: 1.2rem;
 }
 
 .worker:first-child {
   padding-top: 0;
 }
 
 .worker:not(:last-child) {
-  border-bottom: 1px solid var(--grey-30);
+  border-bottom: 1px solid var(--theme-body-color-alt);
 }
 
 .worker__header {
   grid-column: 1/3;
   display: grid;
   grid-template-columns: 1fr auto;
   grid-column-gap: 2rem;
   align-items: center;
@@ -44,27 +45,31 @@
 
 .worker__scope {
   font-weight: bold;
   text-overflow: ellipsis;
   overflow: hidden;
   white-space: nowrap;
 }
 
+.worker__meta-name {
+  color: var(--grey-50);
+  padding-inline-start: 4.5rem;
+}
+
 .worker__data {
   display: grid;
   grid-template-columns: auto 1fr;
   grid-column-gap: 1rem;
 }
 
 .worker__data > * {
   margin: 0;
 }
 
-.worker__meta-name {
-  color: var(--grey-50);
-  padding-inline-start: 4.5rem;
+.worker__data__updated {
+  color: var(--theme-body-color-alt);
 }
 
 .worker__unregister-button {
   /* TODO: remove this once/if we have proper capitalization in the strings file */
   text-transform: capitalize;
 }
--- a/devtools/client/application/src/components/Worker.js
+++ b/devtools/client/application/src/components/Worker.js
@@ -1,17 +1,18 @@
 /* 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/. */
 
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { a, button, dd, dl, dt, header, li, section, span } = require("devtools/client/shared/vendor/react-dom-factories");
+const { a, br, button, dd, dl, dt, header, li, section, span, time } =
+  require("devtools/client/shared/vendor/react-dom-factories");
 const Services = require("Services");
 const { getUnicodeUrl, getUnicodeUrlPath } = require("devtools/client/shared/unicode-url");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser",
   "devtools/client/framework/devtools-browser", true);
 
@@ -131,32 +132,41 @@ class Worker extends Component {
     },
       Strings.GetStringFromName("debug"));
 
     const startLink = !this.isRunning() ?
       a({ onClick: this.start, className: "worker__start-link" },
         Strings.GetStringFromName("start"))
       : null;
 
+    const lastUpdated = worker.lastUpdateTime
+      ? span({ className: "worker__data__updated" },
+          "Updated ",
+          time({ className: "js-sw-updated"},
+            new Date(worker.lastUpdateTime / 1000).toLocaleString()))
+      : null;
+
     return li({ className: "worker js-sw-container" },
       header(
         { className: "worker__header" },
         span({ title: worker.scope, className: "worker__scope js-sw-scope" },
           this.formatScope(worker.scope)),
         section(
           { className: "worker__controls" },
           unregisterButton),
       ),
       dl(
         { className: "worker__data" },
         dt({ className: "worker__meta-name" }, "Source"),
         dd({},
             span({ title: worker.scope, className: "js-source-url" },
               this.formatSource(worker.url)),
-            debugLink),
+            debugLink,
+            lastUpdated ? br({}) : null,
+            lastUpdated ? lastUpdated : null),
         dt({ className: "worker__meta-name" }, "Status"),
         dd({},
           Strings.GetStringFromName(status).toLowerCase(),
           startLink)
       )
     );
   }
 }
--- a/devtools/client/application/test/browser_application_panel_list-single-worker.js
+++ b/devtools/client/application/test/browser_application_panel_list-single-worker.js
@@ -28,16 +28,20 @@ add_task(async function() {
   await waitUntil(() => workerContainer.querySelector(".js-unregister-button"));
 
   let scopeEl = workerContainer.querySelector(".js-sw-scope");
   let expectedScope = "example.com/browser/devtools/client/application/test/" +
                       "service-workers/";
   ok(scopeEl.textContent.startsWith(expectedScope),
     "Service worker has the expected scope");
 
+  let updatedEl = workerContainer.querySelector(".js-sw-updated");
+  ok(updatedEl.textContent.includes(`${new Date().getFullYear()}`),
+    "Service worker has a last updated time");
+
   info("Unregister the service worker");
   await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
     let registration = await content.wrappedJSObject.sw;
     registration.unregister();
   });
 
   info("Wait until the service worker is removed from the application panel");
   await waitUntil(() => getWorkerContainers(doc).length === 0);
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -301,17 +301,18 @@ protocol.ActorClassWithSpec(serviceWorke
       url: registration.scriptSpec,
       installingWorker,
       waitingWorker,
       activeWorker,
       fetch: newestWorker && newestWorker.fetch,
       // - In e10s: only active registrations are available.
       // - In non-e10s: registrations always have at least one worker, if the worker is
       // active, the registration is active.
-      active: isE10s ? true : !!activeWorker
+      active: isE10s ? true : !!activeWorker,
+      lastUpdateTime: registration.lastUpdateTime,
     };
   },
 
   destroy() {
     protocol.Actor.prototype.destroy.call(this);
     Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
     this._registration.removeListener(this);
     this._registration = null;
--- a/devtools/shared/client/root-client.js
+++ b/devtools/shared/client/root-client.js
@@ -143,17 +143,18 @@ RootClient.prototype = {
 
     registrations.forEach(form => {
       result.service.push({
         name: form.url,
         url: form.url,
         scope: form.scope,
         fetch: form.fetch,
         registrationActor: form.actor,
-        active: form.active
+        active: form.active,
+        lastUpdateTime: form.lastUpdateTime
       });
     });
 
     workers.forEach(form => {
       let worker = {
         name: form.url,
         url: form.url,
         workerActor: form.actor