Bug 1471795 - Part 12: Update extension list ui when the extensions were added or removed. r?jdescottes draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 19 Jul 2018 17:37:54 +0900
changeset 820261 b6f08ecfa40857033d353d140c4f1e19a9a06373
parent 820260 6f2906372b33ac92277c219b143bb434dad1747a
child 820262 b67a99c62c8abacddf6dce8a150f38f5a18b5742
push id116772
push userbmo:dakatsuka@mozilla.com
push dateThu, 19 Jul 2018 09:51:56 +0000
reviewersjdescottes
bugs1471795
milestone63.0a1
Bug 1471795 - Part 12: Update extension list ui when the extensions were added or removed. r?jdescottes MozReview-Commit-ID: 7AmOebWj0BD
devtools/client/aboutdebugging-new/aboutdebugging.js
devtools/client/aboutdebugging-new/src/actions/runtimes.js
devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
devtools/client/aboutdebugging-new/src/runtimes/runtime.js
devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -38,28 +38,32 @@ const AboutDebugging = {
 
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
     const thisFirefox = new ThisFirefox();
     await this.updateSelectedRuntime(thisFirefox);
 
     render(Provider({ store: this.store }, App({ thisFirefox })), this.mount);
+
+    this.thisFirefox = thisFirefox;
   },
 
   destroy() {
     unmountComponentAtNode(this.mount);
+    this.thisFirefox.destroy();
   },
 
   get mount() {
     return document.getElementById("mount");
   },
 
   async updateSelectedRuntime(runtime) {
-    await this.actions.updateSelectedRuntime(runtime);
+    const previousSelectedRuntime = this.store.getState().runtimes.selectedRuntime;
+    await this.actions.updateSelectedRuntime(runtime, previousSelectedRuntime);
   },
 };
 
 window.addEventListener("DOMContentLoaded", () => {
   AboutDebugging.init();
 }, { once: true });
 
 window.addEventListener("unload", () => {
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -3,18 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   UPDATE_SELECTED_RUNTIME,
 } = require("../constants");
 
-function updateSelectedRuntime(runtime) {
+function updateSelectedRuntime(runtime, previousRuntime) {
   return async dispatch => {
+    if (previousRuntime) {
+      await previousRuntime.disconnect();
+    }
+
     await runtime.connect();
     dispatch({
       type: UPDATE_SELECTED_RUNTIME,
       runtime,
     });
   };
 }
 
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
@@ -20,43 +20,49 @@ class DebugTargetsPane extends PureCompo
     return {
       runtime: PropTypes.instanceOf(Runtime).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
+    this.onExtensionsUpdated = this.onExtensionsUpdated.bind(this);
     this.onTabsUpdated = this.onTabsUpdated.bind(this);
 
     this.state = {
       info: {},
       installedExtensions: [],
       tabs: [],
       temporaryExtensions: [],
     };
     this.update(props.runtime);
   }
 
   componentDidUpdate(prevProps) {
     if (prevProps.runtime !== this.props.runtime) {
-      prevProps.runtime.removeTabsUpdateListener(this.onTabsUpdated);
+      const { runtime } = prevProps;
+      runtime.removeExtensionsUpdateListener(this.onExtensionsUpdated);
+      runtime.removeTabsUpdateListener(this.onTabsUpdated);
       this.update(this.props.runtime);
     }
   }
 
   componentWillUnmount() {
-    this.props.runtime.removeTabsUpdateListener(this.onTabsUpdated);
+    const { runtime } = this.props;
+    runtime.removeExtensionsUpdateListener(this.onExtensionsUpdated);
+    runtime.removeTabsUpdateListener(this.onTabsUpdated);
   }
 
   update(runtime) {
     this.updateRuntimeInfo(runtime);
     this.updateTabs(runtime);
     this.updateExtensions(runtime);
 
+    runtime.addExtensionsUpdateListener(this.onExtensionsUpdated);
     runtime.addTabsUpdateListener(this.onTabsUpdated);
   }
 
   async updateExtensions(runtime) {
     const extensions = (await runtime.getExtensions()).filter(t => t.debuggable);
     const installedExtensions = extensions.filter(t => !t.temporarilyInstalled);
     const temporaryExtensions = extensions.filter(t => t.temporarilyInstalled);
     this.setState({ installedExtensions, temporaryExtensions });
@@ -68,16 +74,20 @@ class DebugTargetsPane extends PureCompo
   }
 
   async updateTabs(runtime) {
     // Filter out closed tabs (represented as `null`).
     const tabs = (await runtime.getTabs()).filter(t => !!t);
     this.setState({ tabs });
   }
 
+  onExtensionsUpdated() {
+    this.updateExtensions(this.props.runtime);
+  }
+
   onTabsUpdated() {
     this.updateTabs(this.props.runtime);
   }
 
   render() {
     const { runtime } = this.props;
     const {
       info,
--- a/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
@@ -4,16 +4,25 @@
 
 "use strict";
 
 /**
  * This class represents a runtime, such as a remote Firefox.
  */
 class Runtime {
   /**
+   * Add a extensions update listener to detect extensions change.
+   * Subclass should override this method.
+   * @param {function}
+   */
+  addExtensionsUpdateListener(listener) {
+    throw new Error("Subclass of Runtime should override addExtensionsUpdateListener()");
+  }
+
+  /**
    * Add a tab update listener to detect tabs change.
    * Subclass should override this method.
    * @param {function}
    */
   addTabsUpdateListener(listener) {
     throw new Error("Subclass of Runtime should override addTabsUpdateListener()");
   }
 
@@ -21,16 +30,32 @@ class Runtime {
    * Connect to this runtime.
    * Subclass should override this method.
    */
   async connect() {
     throw new Error("Subclass of Runtime should override connect()");
   }
 
   /**
+   * This method will be called when about:debugging page was unloaded.
+   * Subclass should override this method.
+   */
+  destroy() {
+    throw new Error("Subclass of Runtime should override destroy()");
+  }
+
+  /**
+   * This method will be called when this runtime was unselected.
+   * Subclass should override this method.
+   */
+  async disconnect() {
+    throw new Error("Subclass of Runtime should override disconnect()");
+  }
+
+  /**
    * Return extensions on this runtime.
    * Subclass should override this method.
    * @return {Array}
    */
   async getExtensions() {
     throw new Error("Subclass of Runtime should override getExtensions()");
   }
 
@@ -78,14 +103,24 @@ class Runtime {
     throw new Error("Subclass of Runtime should override inspectTab()");
   }
 
   /**
    * Remove a tab update listener.
    * Subclass should override this method.
    * @param {function}
    */
+  removeExtensionsUpdateListener(listener) {
+    throw new Error("Subclass of Runtime should override " +
+                    "removeExtensionsUpdateListener()");
+  }
+
+  /**
+   * Remove a tab update listener.
+   * Subclass should override this method.
+   * @param {function}
+   */
   removeTabsUpdateListener(listener) {
     throw new Error("Subclass of Runtime should override removeTabsUpdateListener()");
   }
 }
 
 module.exports = Runtime;
--- a/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
@@ -1,40 +1,63 @@
 /* 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 { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { BrowserToolboxProcess } =
   require("resource://devtools/client/framework/ToolboxProcess.jsm");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
 const Services = require("Services");
 
 const Runtime = require("./runtime");
 
 /**
  * This class represents the Firefox instance which runs in the same environment that
  * opened about:debugging.
  */
 class ThisFirefox extends Runtime {
+  constructor() {
+    super();
+    this.extensionsListeners = [];
+    AddonManager.addAddonListener(this);
+  }
+
+  addExtensionsUpdateListener(listener) {
+    this.extensionsListeners.push(listener);
+  }
+
   addTabsUpdateListener(listener) {
     this.client.addListener("tabListChanged", listener);
   }
 
   async connect() {
     // Setup a server if we don't have one already running
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     const transport = DebuggerServer.connectPipe();
     this.client = new DebuggerClient(transport);
     return this.client.connect();
   }
 
+  async destroy() {
+    await this.disconnect();
+    this.extensionsListeners = null;
+  }
+
+  async disconnect() {
+    AddonManager.removeAddonListener(this);
+    await this.client.close();
+    DebuggerServer.destroy();
+    this.client = null;
+  }
+
   async getExtensions() {
     const { addons } = await this.client.listAddons();
     return addons;
   }
 
   async getRuntimeInfo() {
     return {
       icon: "chrome://branding/content/icon64.png",
@@ -61,14 +84,56 @@ class ThisFirefox extends Runtime {
       }
     });
   }
 
   async inspectTab(debugTarget) {
     window.open(`about:devtools-toolbox?type=tab&id=${ debugTarget.outerWindowID }`);
   }
 
+  removeExtensionsUpdateListener(listener) {
+    const index = this.extensionsListeners.indexOf(listener);
+
+    if (index > -1) {
+      this.extensionsListeners.splice(index, 1);
+    }
+  }
+
   removeTabsUpdateListener(listener) {
     this.client.removeListener("tabListChanged", listener);
   }
+
+  onExtensionsUpdated() {
+    for (const listener of this.extensionsListeners) {
+      listener();
+    }
+  }
+
+  /**
+   * Mandatory callback as AddonManager listener.
+   */
+  onInstalled() {
+    this.onExtensionsUpdated();
+  }
+
+  /**
+   * Mandatory callback as AddonManager listener.
+   */
+  onUninstalled() {
+    this.onExtensionsUpdated();
+  }
+
+  /**
+   * Mandatory callback as AddonManager listener.
+   */
+  onEnabled() {
+    this.onExtensionsUpdated();
+  }
+
+  /**
+   * Mandatory callback as AddonManager listener.
+   */
+  onDisabled() {
+    this.onExtensionsUpdated();
+  }
 }
 
 module.exports = ThisFirefox;