Bug 1405008 - Make WebIDE warn when connecting to old runtimes. r=jdescottes draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 02 Oct 2017 22:40:32 +0200
changeset 700504 7c1f7882b0a47b53d62fbc50bc6692dcb7f32987
parent 700338 dd08f8b19cc32da161811abb2f7093e0f5392e69
child 740906 5c5a42d7aa511a5efef874dac0cf513e8603eddc
push id89869
push userbmo:poirot.alex@gmail.com
push dateMon, 20 Nov 2017 09:28:18 +0000
reviewersjdescottes
bugs1405008
milestone59.0a1
Bug 1405008 - Make WebIDE warn when connecting to old runtimes. r=jdescottes MozReview-Commit-ID: KQc2b1ohksA
devtools/client/locales/en-US/webide.properties
devtools/client/webide/content/webide.js
devtools/client/webide/modules/app-manager.js
devtools/shared/client/debugger-client.js
--- a/devtools/client/locales/en-US/webide.properties
+++ b/devtools/client/locales/en-US/webide.properties
@@ -43,16 +43,19 @@ error_operationFail=Operation failed: %1
 error_cantConnectToApp=Can’t connect to app: %1$S
 
 error_appProjectsLoadFailed=Unable to load project list. This can occur if you’ve used this profile with a newer version of Firefox.
 error_folderCreationFailed=Unable to create project folder in the selected directory.
 
 # Variable: runtime app build ID (looks like this %Y%M%D format) and firefox build ID (same format)
 error_runtimeVersionTooRecent=The connected runtime has a more recent build date (%1$S) than your desktop Firefox (%2$S) does. This is an unsupported setup and may cause DevTools to fail. Please update Firefox.
 
+# Variable: runtime app version (looks like this 52.a3) and firefox version (same format)
+error_runtimeVersionTooOld=The connected runtime has an old version (%1$S). The minimum supported version is (%2$S). This is an unsupported setup and may cause DevTools to fail. Please update the connected runtime.
+
 addons_stable=stable
 addons_unstable=unstable
 addons_install_button=install
 addons_uninstall_button=uninstall
 addons_adb_label=ADB Helper Add-on
 addons_adb_warning=USB devices won’t be detected without this add-on
 addons_status_unknown=?
 addons_status_installed=Installed
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -26,18 +26,16 @@ const {Task} = require("devtools/shared/
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 const HTML = "http://www.w3.org/1999/xhtml";
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting";
 
 const MAX_ZOOM = 1.4;
 const MIN_ZOOM = 0.6;
 
-const MS_PER_DAY = 86400000;
-
 [["AppManager", AppManager],
  ["AppProjects", AppProjects],
  ["Connection", Connection]].forEach(([key, value]) => {
    Object.defineProperty(this, key, {
      value: value,
      enumerable: true,
      writable: false
    });
@@ -747,37 +745,27 @@ var UI = {
   },
 
   resetDeck: function () {
     this.resetFocus();
     let deck = document.querySelector("#deck");
     deck.selectedPanel = null;
   },
 
-  buildIDToDate(buildID) {
-    let fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
-    // Date expects 0 - 11 for months
-    return new Date(fields[1], Number.parseInt(fields[2]) - 1, fields[3]);
-  },
-
   checkRuntimeVersion: Task.async(function* () {
-    if (AppManager.connected && AppManager.deviceFront) {
-      let desc = yield AppManager.deviceFront.getDescription();
-      // Compare device and firefox build IDs
-      // and only compare by day (strip hours/minutes) to prevent
-      // warning against builds of the same day.
-      let deviceID = desc.appbuildid.substr(0, 8);
-      let localID = Services.appinfo.appBuildID.substr(0, 8);
-      let deviceDate = this.buildIDToDate(deviceID);
-      let localDate = this.buildIDToDate(localID);
-      // Allow device to be newer by up to a week.  This accommodates those with
-      // local device builds, since their devices will almost always be newer
-      // than the client.
-      if (deviceDate - localDate > 7 * MS_PER_DAY) {
-        this.reportError("error_runtimeVersionTooRecent", deviceID, localID);
+    if (AppManager.connected) {
+      let { client } = AppManager.connection;
+      let report = yield client.checkRuntimeVersion(AppManager.listTabsForm);
+      if (report.incompatible == "too-recent") {
+        this.reportError("error_runtimeVersionTooRecent", report.runtimeID,
+          report.localID);
+      }
+      if (report.incompatible == "too-old") {
+        this.reportError("error_runtimeVersionTooOld", report.runtimeVersion,
+          report.minVersion);
       }
     }
   }),
 
   /** ******** TOOLBOX **********/
 
   /**
    * There are many ways to close a toolbox:
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -534,16 +534,20 @@ var AppManager = exports.AppManager = {
     // Fx >=39 exposes a dedicated actor via getProcess request
     return this.connection.client &&
            this.connection.client.mainRoot &&
            this.connection.client.mainRoot.traits.allowChromeProcess ||
            (this._listTabsResponse &&
             this._listTabsResponse.consoleActor);
   },
 
+  get listTabsForm() {
+    return this._listTabsResponse;
+  },
+
   get deviceFront() {
     if (!this._listTabsResponse) {
       return null;
     }
     return getDeviceFront(this.connection.client, this._listTabsResponse);
   },
 
   get preferenceFront() {
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -1,39 +1,48 @@
 /* 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 { Cu } = require("chrome");
+const Services = require("Services");
 const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
 const eventSource = require("devtools/shared/client/event-source");
 const {
   ThreadStateTypes,
   UnsolicitedNotifications,
   UnsolicitedPauses,
 } = require("./constants");
 
 loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
 loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
+loader.lazyRequireGetter(this, "getDeviceFront", "devtools/shared/fronts/device", true);
+
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "AddonClient", "devtools/shared/client/addon-client");
 loader.lazyRequireGetter(this, "RootClient", "devtools/shared/client/root-client");
 loader.lazyRequireGetter(this, "TabClient", "devtools/shared/client/tab-client");
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 loader.lazyRequireGetter(this, "TraceClient", "devtools/shared/client/trace-client");
 loader.lazyRequireGetter(this, "WorkerClient", "devtools/shared/client/worker-client");
 
 const noop = () => {};
 
+// Define the minimum officially supported version of Firefox when connecting to a remote
+// runtime. (Use ".0a1" to support the very first nightly version)
+// This is usually the current ESR version.
+const MIN_SUPPORTED_PLATFORM_VERSION = "52.0a1";
+const MS_PER_DAY = 86400000;
+
 /**
  * Creates a client for the remote debugging protocol server. This client
  * provides the means to communicate with the server and exchange the messages
  * required by the protocol in a traditional JavaScript API.
  */
 function DebuggerClient(transport) {
   this._transport = transport;
   this._transport.hooks = this;
@@ -179,16 +188,76 @@ DebuggerClient.prototype = {
       deferred.resolve([applicationType, traits]);
     });
 
     this._transport.ready();
     return deferred.promise;
   },
 
   /**
+   * Tells if the remote device is using a supported version of Firefox.
+   *
+   * @return Object with the following attributes:
+   *   * String incompatible
+   *            null if the runtime is compatible,
+   *            "too-recent" if the runtime uses a too recent version,
+   *            "too-old" if the runtime uses a too old version.
+   *   * String minVersion
+   *            The minimum supported version.
+   *   * String runtimeVersion
+   *            The remote runtime version.
+   *   * String localID
+   *            Build ID of local runtime. A date with like this: YYYYMMDD.
+   *   * String deviceID
+   *            Build ID of remote runtime. A date with like this: YYYYMMDD.
+   */
+  async checkRuntimeVersion(listTabsForm) {
+    let incompatible = null;
+
+    // Instead of requiring to pass `listTabsForm` here,
+    // we can call getRoot() instead, but only once Firefox ESR59 is released
+    let deviceFront = await getDeviceFront(this, listTabsForm);
+    let desc = await deviceFront.getDescription();
+
+    // 1) Check for Firefox too recent on device.
+    // Compare device and firefox build IDs
+    // and only compare by day (strip hours/minutes) to prevent
+    // warning against builds of the same day.
+    let runtimeID = desc.appbuildid.substr(0, 8);
+    let localID = Services.appinfo.appBuildID.substr(0, 8);
+    function buildIDToDate(buildID) {
+      let fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
+      // Date expects 0 - 11 for months
+      return new Date(fields[1], Number.parseInt(fields[2], 10) - 1, fields[3]);
+    }
+    let runtimeDate = buildIDToDate(runtimeID);
+    let localDate = buildIDToDate(localID);
+    // Allow device to be newer by up to a week.  This accommodates those with
+    // local device builds, since their devices will almost always be newer
+    // than the client.
+    if (runtimeDate - localDate > 7 * MS_PER_DAY) {
+      incompatible = "too-recent";
+    }
+
+    // 2) Check for too old Firefox on device
+    let platformversion = desc.platformversion;
+    if (Services.vc.compare(platformversion, MIN_SUPPORTED_PLATFORM_VERSION) < 0) {
+      incompatible = "too-old";
+    }
+
+    return {
+      incompatible,
+      minVersion: MIN_SUPPORTED_PLATFORM_VERSION,
+      runtimeVersion: platformversion,
+      localID,
+      runtimeID,
+    };
+  },
+
+  /**
    * Shut down communication with the debugging server.
    *
    * @param onClosed function
    *        If specified, will be called when the debugging connection
    *        has been closed. This parameter is deprecated - please use
    *        the returned Promise.
    * @return Promise
    *         Resolves after the underlying transport is closed.