Bug 1281847 - Make UITour work properly with nested <browser> not necessarily part of gBrowser draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 06 Jul 2016 23:41:31 -0700
changeset 384801 6875ed0e1b88781b468ba12395a0ee9c5114b8e9
parent 384060 dbb31bcad5a1f60a35b5600ea1578d9b9fa55237
child 384802 dfa360700aa68c7a872e8e07c00ad54624ee1225
push id22365
push usermozilla@noorenberghe.ca
push dateThu, 07 Jul 2016 06:47:26 +0000
bugs1281847
milestone50.0a1
Bug 1281847 - Make UITour work properly with nested <browser> not necessarily part of gBrowser MozReview-Commit-ID: 2MBW3nzQoZz
browser/components/uitour/UITour.jsm
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -359,17 +359,23 @@ this.UITour = {
     let originalUrl = ReaderMode.getOriginalUrl(aLocation);
     if (this._readerViewTriggerRegEx.test(originalUrl)) {
       this.startSubTour("readinglist");
     }
   },
 
   onPageEvent: function(aMessage, aEvent) {
     let browser = aMessage.target;
-    let window = browser.ownerDocument.defaultView;
+    let window = browser.docShell
+          .QueryInterface(Ci.nsIDocShellTreeItem)
+          .rootTreeItem
+          .QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIDOMWindow);
+
+    // TODO: test with heartbeat (using windowless browser)
 
     // Does the window have tabs? We need to make sure since windowless browsers do
     // not have tabs.
     if (!window.gBrowser) {
       // When using windowless browsers we don't have a valid |window|. If that's the case,
       // use the most recent window as a target for UITour functions (see Bug 1111022).
       window = Services.wm.getMostRecentWindow("navigator:browser");
     }
@@ -730,58 +736,68 @@ this.UITour = {
     }
 
     this.initForBrowser(browser, window);
 
     return true;
   },
 
   initForBrowser(aBrowser, window) {
-    let gBrowser = window.gBrowser;
-
-    if (gBrowser) {
-        gBrowser.tabContainer.addEventListener("TabSelect", this);
-    }
+    aBrowser.addEventListener("visibilitychange", this.onVisibilityChange);
 
     if (!this.tourBrowsersByWindow.has(window)) {
       this.tourBrowsersByWindow.set(window, new Set());
     }
     this.tourBrowsersByWindow.get(window).add(aBrowser);
 
     Services.obs.addObserver(this, "message-manager-close", false);
 
     window.addEventListener("SSWindowClosing", this);
   },
 
   handleEvent: function(aEvent) {
     log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
     switch (aEvent.type) {
-      case "TabSelect": {
-        let window = aEvent.target.ownerDocument.defaultView;
-
-        // Teardown the browser of the tab we just switched away from.
-        if (aEvent.detail && aEvent.detail.previousTab) {
-          let previousTab = aEvent.detail.previousTab;
-          let openTourWindows = this.tourBrowsersByWindow.get(window);
-          if (openTourWindows.has(previousTab.linkedBrowser)) {
-            this.teardownTourForBrowser(window, previousTab.linkedBrowser, false);
-          }
-        }
-
-        break;
-      }
-
       case "SSWindowClosing": {
         let window = aEvent.target;
         this.teardownTourForWindow(window);
         break;
       }
     }
   },
 
+  /**
+   * Teardown the tour for a browser when it is no longer visible
+   * (e.g. switching tabs, minimizing the window, or removing the browser).
+   *
+   * @param {Event} aEvent - visibilitychange event object
+   */
+  onVisibilityChange(aEvent) {
+    if (aEvent.target.visibilityState == "visible") {
+      return;
+    }
+
+    log.debug("visibilitychange:", aEvent);
+    // TODO: test with heartbeat (using windowless browser) making sure it never gets visibilitychange even on minimize
+
+    let browser = this;
+    let window = browser.docShell
+          .QueryInterface(Ci.nsIDocShellTreeItem)
+          .rootTreeItem
+          .QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIDOMWindow);
+
+    // Teardown the browser that just got hidden
+    let openTourWindows = UITour.tourBrowsersByWindow.get(window);
+    if (openTourWindows.has(browser)) {
+      let closing = aEvent.target.visibilityState == "unloaded";
+      UITour.teardownTourForBrowser(window, browser, closing);
+    }
+  },
+
   observe: function(aSubject, aTopic, aData) {
     log.debug("observe: aTopic =", aTopic);
     switch (aTopic) {
       // The browser message manager is disconnected when the <browser> is
       // destroyed and we want to teardown at that point.
       case "message-manager-close": {
         let winEnum = Services.wm.getEnumerator("navigator:browser");
         while (winEnum.hasMoreElements()) {
@@ -884,16 +900,17 @@ this.UITour = {
     if (this.pageIDSourceBrowsers.has(aBrowser)) {
       let pageID = this.pageIDSourceBrowsers.get(aBrowser);
       this.setExpiringTelemetryBucket(pageID, aTourPageClosing ? "closed" : "inactive");
     }
 
     let openTourBrowsers = this.tourBrowsersByWindow.get(aWindow);
     if (aTourPageClosing && openTourBrowsers) {
       openTourBrowsers.delete(aBrowser);
+      aBrowser.removeEventListener("visibilitychange", this.onVisibilityChange);
     }
 
     this.hideHighlight(aWindow);
     this.hideInfo(aWindow);
     // Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
     this.hideMenu(aWindow, "appMenu");
     this.hideMenu(aWindow, "loop");
     this.hideMenu(aWindow, "controlCenter");
@@ -917,17 +934,16 @@ this.UITour = {
     }
   },
 
   /**
    * Tear down all tours for a ChromeWindow.
    */
   teardownTourForWindow: function(aWindow) {
     log.debug("teardownTourForWindow");
-    aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
     aWindow.removeEventListener("SSWindowClosing", this);
 
     let openTourBrowsers = this.tourBrowsersByWindow.get(aWindow);
     if (openTourBrowsers) {
       for (let browser of openTourBrowsers) {
         if (this.pageIDSourceBrowsers.has(browser)) {
           let pageID = this.pageIDSourceBrowsers.get(browser);
           this.setExpiringTelemetryBucket(pageID, "closed");