Bug 1274919 - part4 : add telemetry probe to measure how long the cursor is hovering before opening the tab. r=bsmedberg,mikedeboer
authorAlastor Wu <alwu@mozilla.com>
Thu, 20 Jul 2017 15:11:35 +0800
changeset 418823 04596e7f840bc610c2c01032c644ca1ef58f1257
parent 418822 ec8748c31ef32b251d193324790239f361030ea2
child 418824 6ed469370daa8ca35c2d71c0792c585e7bb2daa5
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, mikedeboer
bugs1274919
milestone56.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 1274919 - part4 : add telemetry probe to measure how long the cursor is hovering before opening the tab. r=bsmedberg,mikedeboer Measure the time how long the cursor is hovering before opening the unselected tab. If the tab didn't be opened, the data won't be recorded. MozReview-Commit-ID: 4oTj0RzJhG
browser/base/content/tabbrowser.xml
toolkit/components/telemetry/Histograms.json
toolkit/content/widgets/browser.xml
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5590,16 +5590,17 @@
             // Also support adding event listeners (forward to the tab container)
             addEventListener(a, b, c) { this.self.tabContainer.addEventListener(a, b, c); },
             removeEventListener(a, b, c) { this.self.tabContainer.removeEventListener(a, b, c); }
           });
         ]]>
         </getter>
       </property>
       <field name="_soundPlayingAttrRemovalTimer">0</field>
+      <field name="_hoverTabTimer">null</field>
     </implementation>
 
     <handlers>
       <handler event="DOMWindowClose" phase="capturing">
         <![CDATA[
           if (!event.isTrusted)
             return;
 
@@ -7436,16 +7437,21 @@
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
             this.removeAttribute("visuallyselected");
           this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
 
           this._setPositionAttributes(val);
 
+          // Tab becomes visible, it's not unselected anymore.
+          if (val) {
+            this.finishUnselectedTabHoverTimer();
+          }
+
           return val;
           ]]>
         </setter>
       </property>
 
       <property name="_selected">
         <setter>
           <![CDATA[
@@ -7581,16 +7587,17 @@
               tabContainer._afterHoveredTab = candidate;
               candidate.setAttribute("afterhovered", "true");
             }
           }
 
           tabContainer._hoveredTab = this;
           if (this.linkedPanel && !this.selected) {
             this.linkedBrowser.unselectedTabHover(true);
+            this.startUnselectedTabHoverTimer();
           }
         ]]></body>
       </method>
 
       <method name="_mouseleave">
         <body><![CDATA[
           let tabContainer = this.parentNode;
           if (tabContainer._beforeHoveredTab) {
@@ -7600,16 +7607,62 @@
           if (tabContainer._afterHoveredTab) {
             tabContainer._afterHoveredTab.removeAttribute("afterhovered");
             tabContainer._afterHoveredTab = null;
           }
 
           tabContainer._hoveredTab = null;
           if (this.linkedPanel && !this.selected) {
             this.linkedBrowser.unselectedTabHover(false);
+            this.cancelUnselectedTabHoverTimer();
+          }
+        ]]></body>
+      </method>
+
+      <method name="startUnselectedTabHoverTimer">
+        <body><![CDATA[
+          // Only record data when we need to.
+          if (!this.linkedBrowser.shouldHandleUnselectedTabHover) {
+            return;
+          }
+
+          if (!TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
+            TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
+          }
+
+          if (this._hoverTabTimer) {
+            clearTimeout(this._hoverTabTimer);
+            this._hoverTabTimer = null;
+          }
+        ]]></body>
+      </method>
+
+      <method name="cancelUnselectedTabHoverTimer">
+        <body><![CDATA[
+          // Since we're listening "mouseout" event, instead of "mouseleave".
+          // Every time the cursor is moving from the tab to its child node (icon),
+          // it would dispatch "mouseout"(for tab) first and then dispatch
+          // "mouseover" (for icon, eg: close button, speaker icon) soon.
+          // It causes we would cancel present TelemetryStopwatch immediately
+          // when cursor is moving on the icon, and then start a new one.
+          // In order to avoid this situation, we could delay cancellation and
+          // remove it if we get "mouseover" within very short period.
+          this._hoverTabTimer = setTimeout(() => {
+            if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
+              TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
+            }
+          }, 100);
+        ]]></body>
+      </method>
+
+      <method name="finishUnselectedTabHoverTimer">
+        <body><![CDATA[
+          // Stop timer when the tab is opened.
+          if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
+            TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
           }
         ]]></body>
       </method>
 
       <method name="startMediaBlockTimer">
         <body><![CDATA[
           TelemetryStopwatch.start("TAB_MEDIA_BLOCKING_TIME_MS", this);
         ]]></body>
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13640,10 +13640,21 @@
     "bug_numbers": [1368524],
     "expires_in_version": "60",
     "kind": "exponential",
     "low": 3,
     "high": 512,
     "n_buckets": 20,
     "keyed": true,
     "description": "Measures the number of milliseconds we spend synchronously notifying observers, keyed by topic. Note: only NotifyObservers calls which take over 500 microseconds are included in this probe."
+  },
+  "HOVER_UNTIL_UNSELECTED_TAB_OPENED": {
+    "record_in_processes": ["main"],
+    "alert_emails": ["alwu@mozilla.com"],
+    "expires_in_version": "60",
+    "kind": "exponential",
+    "high": 10000,
+    "n_buckets": 100,
+    "bug_numbers": [1274919],
+    "description": "Measure the time how long the cursor is hovering before opening the unselcted tab. Only record the data if someone requests for sending unselected tab hover msg.",
+    "releaseChannelCollection": "opt-out"
   }
 }
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -851,16 +851,19 @@
       </method>
 
       <!--
         Only send the message "Browser:UnselectedTabHover" when someone requests
         for the message, which can reduce non-necessary communication.
       -->
       <field name="_shouldSendUnselectedTabHover">false</field>
       <field name="_unselectedTabHoverMessageListenerCount">0</field>
+      <property name="shouldHandleUnselectedTabHover"
+                onget="return this._shouldSendUnselectedTabHover;"
+                readonly="true"/>
 
       <method name="unselectedTabHover">
         <parameter name="hovered"/>
         <body>
           <![CDATA[
             if (!this._shouldSendUnselectedTabHover) {
               return;
             }