Bug 1093387 - Better WebIDE runtime error handling. r=ochameau
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 15 Jan 2015 20:23:15 -0600
changeset 224177 c2498519af6eff3704062a15966cbe062fe740ff
parent 224176 7b98d254877ecacb167871c01286941d14d0fb66
child 224178 896e9c313511044a7ae0c5239f4884e547b72752
push id54150
push usercbook@mozilla.com
push dateFri, 16 Jan 2015 14:14:56 +0000
treeherdermozilla-inbound@ac6623427298 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1093387
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1093387 - Better WebIDE runtime error handling. r=ochameau
browser/devtools/webide/content/webide.js
browser/devtools/webide/modules/app-manager.js
browser/devtools/webide/modules/runtimes.js
browser/devtools/webide/test/test_telemetry.html
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -287,28 +287,30 @@ let UI = {
     this._busyOperationDescription = operationDescription;
     this.setupBusyTimeout();
     this.busy();
     promise.then(() => {
       this.cancelBusyTimeout();
       this.unbusy();
     }, (e) => {
       let message;
-      if (e.error && e.message) {
+      if (e && e.error && e.message) {
         // Some errors come from fronts that are not based on protocol.js.
         // Errors are not translated to strings.
         message = operationDescription + " (" + e.error + "): " + e.message;
       } else {
         message = operationDescription + (e ? (": " + e) : "");
       }
       this.cancelBusyTimeout();
       let operationCanceled = e && e.canceled;
       if (!operationCanceled) {
         UI.reportError("error_operationFail", message);
-        console.error(e);
+        if (e) {
+          console.error(e);
+        }
       }
       this.unbusy();
     });
     return promise;
   },
 
   reportError: function(l10nProperty, ...l10nArgs) {
     let text;
@@ -434,18 +436,23 @@ let UI = {
         }
       }
     }
   },
 
   connectToRuntime: function(runtime) {
     let name = runtime.name;
     let promise = AppManager.connectToRuntime(runtime);
-    promise.then(() => this.initConnectionTelemetry());
-    return this.busyUntil(promise, "connecting to runtime " + name);
+    promise.then(() => this.initConnectionTelemetry())
+           .catch(() => {
+             // Empty rejection handler to silence uncaught rejection warnings
+             // |busyUntil| will listen for rejections.
+             // Bug 1121100 may find a better way to silence these.
+           });
+    return this.busyUntil(promise, "Connecting to " + name);
   },
 
   updateRuntimeButton: function() {
     let labelNode = document.querySelector("#runtime-panel-button > .panel-button-label");
     if (!AppManager.selectedRuntime) {
       labelNode.setAttribute("value", Strings.GetStringFromName("runtimeButton_label"));
     } else {
       let name = AppManager.selectedRuntime.name;
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -88,22 +88,23 @@ let AppManager = exports.AppManager = {
       } else {
         text = Strings.GetStringFromName(l10nProperty);
       }
       console.error(text);
     }
   },
 
   onConnectionChanged: function() {
+    console.log("Connection status changed: " + this.connection.status);
+
     if (this.connection.status == Connection.Status.DISCONNECTED) {
       this.selectedRuntime = null;
     }
 
     if (this.connection.status != Connection.Status.CONNECTED) {
-      console.log("Connection status changed: " + this.connection.status);
       if (this._appsFront) {
         this._appsFront.off("install-progress", this.onInstallProgress);
         this._appsFront.unwatchApps();
         this._appsFront = null;
       }
       this._listTabsResponse = null;
     } else {
       this.connection.client.listTabs((response) => {
@@ -349,28 +350,25 @@ let AppManager = exports.AppManager = {
       let onConnectedOrDisconnected = () => {
         this.connection.off(Connection.Events.CONNECTED, onConnectedOrDisconnected);
         this.connection.off(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
         if (this.connection.status == Connection.Status.CONNECTED) {
           deferred.resolve();
         } else {
           deferred.reject();
         }
-      }
+      };
       this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
       this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
       try {
         // Reset the connection's state to defaults
         this.connection.resetOptions();
-        this.selectedRuntime.connect(this.connection).then(
-          () => {},
-          deferred.reject.bind(deferred));
+        deferred.resolve(this.selectedRuntime.connect(this.connection));
       } catch(e) {
-        console.error(e);
-        deferred.reject();
+        deferred.reject(e);
       }
     }, deferred.reject);
 
     // Record connection result in telemetry
     let logResult = result => {
       this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result);
       if (runtime.type) {
         this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type +
@@ -381,16 +379,20 @@ let AppManager = exports.AppManager = {
 
     // If successful, record connection time in telemetry
     deferred.promise.then(() => {
       const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS";
       this._telemetry.startTimer(timerId);
       this.connection.once(Connection.Events.STATUS_CHANGED, () => {
         this._telemetry.stopTimer(timerId);
       });
+    }).catch(() => {
+      // Empty rejection handler to silence uncaught rejection warnings
+      // |connectToRuntime| caller should listen for rejections.
+      // Bug 1121100 may find a better way to silence these.
     });
 
     return deferred.promise;
   },
 
   isMainProcessDebuggable: function() {
     return this._listTabsResponse &&
            this._listTabsResponse.consoleActor;
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -396,17 +396,17 @@ function DeprecatedUSBRuntime(id) {
 
 DeprecatedUSBRuntime.prototype = {
   type: RuntimeTypes.USB,
   get device() {
     return Devices.getByName(this._id);
   },
   connect: function(connection) {
     if (!this.device) {
-      return promise.reject("Can't find device: " + this.name);
+      return promise.reject(new Error("Can't find device: " + this.name));
     }
     return this.device.connect().then((port) => {
       connection.host = "localhost";
       connection.port = port;
       connection.connect();
     });
   },
   get id() {
@@ -440,17 +440,17 @@ function WiFiRuntime(deviceName) {
   this.deviceName = deviceName;
 }
 
 WiFiRuntime.prototype = {
   type: RuntimeTypes.WIFI,
   connect: function(connection) {
     let service = discovery.getRemoteService("devtools", this.deviceName);
     if (!service) {
-      return promise.reject("Can't find device: " + this.name);
+      return promise.reject(new Error("Can't find device: " + this.name));
     }
     connection.host = service.host;
     connection.port = service.port;
     connection.encryption = service.encryption;
     connection.connect();
     return promise.resolve();
   },
   get id() {
@@ -469,17 +469,17 @@ function SimulatorRuntime(name) {
 }
 
 SimulatorRuntime.prototype = {
   type: RuntimeTypes.SIMULATOR,
   connect: function(connection) {
     let port = ConnectionManager.getFreeTCPPort();
     let simulator = Simulator.getByName(this.name);
     if (!simulator || !simulator.launch) {
-      return promise.reject("Can't find simulator: " + this.name);
+      return promise.reject(new Error("Can't find simulator: " + this.name));
     }
     return simulator.launch({port: port}).then(() => {
       connection.host = "localhost";
       connection.port = port;
       connection.keepConnecting = true;
       connection.once(Connection.Events.DISCONNECTED, simulator.close);
       connection.connect();
     });
@@ -515,28 +515,28 @@ let gLocalRuntime = {
 // For testing use only
 exports._gLocalRuntime = gLocalRuntime;
 
 let gRemoteRuntime = {
   type: RuntimeTypes.REMOTE,
   connect: function(connection) {
     let win = Services.wm.getMostRecentWindow("devtools:webide");
     if (!win) {
-      return promise.reject();
+      return promise.reject(new Error("No WebIDE window found"));
     }
     let ret = {value: connection.host + ":" + connection.port};
     let title = Strings.GetStringFromName("remote_runtime_promptTitle");
     let message = Strings.GetStringFromName("remote_runtime_promptMessage");
     let ok = Services.prompt.prompt(win, title, message, ret, null, {});
     let [host,port] = ret.value.split(":");
     if (!ok) {
       return promise.reject({canceled: true});
     }
     if (!host || !port) {
-      return promise.reject();
+      return promise.reject(new Error("Invalid host or port"));
     }
     connection.host = host;
     connection.port = port;
     connection.connect();
     return promise.resolve();
   },
   get name() {
     return Strings.GetStringFromName("remote_runtime");
--- a/browser/devtools/webide/test/test_telemetry.html
+++ b/browser/devtools/webide/test/test_telemetry.html
@@ -138,17 +138,17 @@
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
           yield win.UI._busyPromise;
           is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
         });
       }
 
       function connectToRuntime(win, type, index) {
         return Task.spawn(function*() {
-          yield startConnection(win, type, index);
+          startConnection(win, type, index);
           yield waitUntilConnected(win);
         });
       }
 
       function checkResults() {
         let result = Telemetry.prototype.telemetryInfo;
         for (let [histId, value] of Iterator(result)) {
           if (histId.endsWith("OPENED_PER_USER_FLAG")) {