jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/336670 )

Change subject: Track document visibility in reading depth schema
......................................................................


Track document visibility in reading depth schema

Calculate the number of milliseconds that the page is visible for by
tracking how long the document is hidden for via the Page Visibility API
and subtracting that from the ReadingDepth.totalLength property.

Supporting changes:
* Add the supportsNavigationTiming function to remove the navStart
  module-level variable, which is only used in the logEvent function.
* Add the onLoad and onBeforeUnload functions, so that the order of
  execution can be better documented.

Bug: T155639
Change-Id: I762ec3fc91decf3cffa869dbd783faf62f01329a
---
M modules/ext.wikimediaEvents.readingDepth.js
1 file changed, 122 insertions(+), 21 deletions(-)

Approvals:
  Bmansurov: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/modules/ext.wikimediaEvents.readingDepth.js 
b/modules/ext.wikimediaEvents.readingDepth.js
index 8bd9969..2b5392d 100644
--- a/modules/ext.wikimediaEvents.readingDepth.js
+++ b/modules/ext.wikimediaEvents.readingDepth.js
@@ -34,11 +34,9 @@
                }
        }
 
-       var msPaused = 0,
+       var pausedAt,
+               msPaused = 0,
                perf = window.performance,
-               // This copies logic in mw.now for consistency.
-               // if not defined no events will be logged.
-               navStart = perf && perf.timing && perf.timing.navigationStart,
                EVENT = {
                        pageTitle: config.get( 'wgTitle' ),
                        namespaceId: config.get( 'wgNamespaceNumber' ),
@@ -51,6 +49,45 @@
                };
 
        /**
+        * Pause the user's page session length timer based on information that 
they
+        * have hidden the page, e.g. they opened another tab.
+        *
+        * If the timer is paused, then NOP.
+        *
+        * @param {number} [at=mw.now] The time at which the timer was paused
+        */
+       function pause( at ) {
+               if ( !pausedAt ) {
+                       pausedAt = at || mw.now();
+               }
+       }
+
+       /**
+        * Resume the user's page session length timer.
+        *
+        * If the timer is running, then NOP.
+        */
+       function resume() {
+               if ( pausedAt ) {
+                       // mw.now invokes [window.]performance.now when it's 
available
+                       // 
<https://phabricator.wikimedia.org/rMWe7d3bce00f418b1f2bc7732aa946b7c9d7c563d0#91287c6c>.
+                       //
+                       // performance.now is specified such that it's not 
subject to system clock
+                       // skew or adjustments 
<http://caniuse.com/#feat=high-resolution-time> and
+                       // is supported by the same set of browsers as the 
Navigation Timing API
+                       // <http://caniuse.com/#search=navigation>, which, 
we've already noted is
+                       // supported by more browsers than the Beacon API
+                       // <http://caniuse.com/#feat=beacon>.
+                       //
+                       // Because this code will only be executed by UA's that 
support the Beacon
+                       // API, we can rely on mw.now not to be subject to 
system clock skew or
+                       // adjustments.
+                       msPaused +=  mw.now() - pausedAt;
+                       pausedAt = null;
+               }
+       }
+
+       /**
        * Log an event to the Schema:ReadingDepth
        *
        * @param {string} action a valid value for the action property inside 
the
@@ -58,23 +95,39 @@
        */
        function logEvent( action ) {
                var now,
-                       // will always be defined.
-                       domInteractive = perf.timing.domInteractive,
-                       fp = getFirstPaintTime( perf ),
+                       timing = perf.timing,
+                       domInteractive = timing.domInteractive,
+                       navigationStart = timing.navigationStart,
+                       firstPaint = getFirstPaintTime( perf ),
+
+                       // Used while calculating the totalLength and 
visibleLength properties.
+                       delta, adjustedEpoch, hiddenFor,
+
                        data = $.extend( {}, EVENT, {
                                action: action,
-                               domInteractiveTime: domInteractive - navStart,
-                               firstPaintTime: fp ? fp - navStart : undefined
-                       } ),
-                       // time to start measuring from with preference for 
first paint
-                       from = fp || domInteractive;
+                               domInteractiveTime: domInteractive - 
navigationStart,
+                               firstPaintTime: firstPaint ? firstPaint - 
navigationStart : undefined
+                       } );
 
                if ( action === 'pageUnloaded' ) {
                        now = mw.now();
-                       // times are measured from firstPaint or domInteractive 
depending what's available.
-                       // Since we record these separately it's clear which is 
being used.
-                       data.totalLength = Math.round( now - from );
-                       data.visibleLength =  Math.round( now - from - msPaused 
);
+
+                       // If the first paint time is available, then use it as 
the epoch for
+                       // calculation by adjusting the original - the "DOM 
interactive" time - by
+                       // the difference between the two.
+                       delta = firstPaint ? ( firstPaint - domInteractive ) : 
0;
+                       adjustedEpoch = domInteractive + delta;
+
+                       // In the case where the page is loaded but hidden, 
e.g. the user
+                       // opens the page in a new tab, then the total amount 
of time that the
+                       // page is hidden for is calculated from the original 
epoch. As above,
+                       // an adjustment needs to be made.
+                       //
+                       // If the page hasn't been hidden, however, then NOP.
+                       hiddenFor = Math.max( 0, msPaused - delta );
+
+                       data.totalLength = Math.round( now - adjustedEpoch );
+                       data.visibleLength = Math.round( data.totalLength - 
hiddenFor );
                }
 
                mw.track( 'event.ReadingDepth', data );
@@ -100,10 +153,23 @@
        /**
         * Checks whether the current browser supports sendBeacon feature.
         *
+        * FIXME: Rename this to be consistent with `supportsNavigationTiming`, 
which
+        * itself is consistent with the wording used in `isEnabled`.
+        *
         * @return {boolean}
         */
        function isSendBeaconCapable() {
                return $.isFunction( navigator.sendBeacon );
+       }
+
+       /**
+        * Checks whether the UA supports the Navigation Timing API.
+        *
+        * @return {boolean}
+        */
+       function supportsNavigationTiming() {
+               // This copies the logic in mw.now for consistency.
+               return Boolean( perf && perf.timing && 
perf.timing.navigationStart );
        }
 
        /**
@@ -115,17 +181,52 @@
         * @return {boolean}
         */
        function isEnabled() {
-               return navStart &&
-                       config.get( 'wgWMEReadingDepthEnabled' ) &&
+               return config.get( 'wgWMEReadingDepthEnabled' ) &&
+                       supportsNavigationTiming() &&
                        isSendBeaconCapable() &&
                        isInSample( config.get( 
'wgWMEReadingDepthSamplingRate', 0 ) );
        }
 
-       if ( isEnabled() ) {
-               $( window ).on( 'beforeunload', function () {
-                       logEvent( 'pageUnloaded' );
+       /**
+        * Handles the window being unloaded.
+        *
+        * The "pageUnloaded" ReadingDepth event is logged.
+        */
+       function onBeforeUnload() {
+               logEvent( 'pageUnloaded' );
+       }
+
+       /**
+        * Handles the document and its resources having been loaded enough...
+        *
+        * 1. If the document is loaded and is hidden, then the timer is marked 
as
+        *    having been paused at when [the document readyState became
+        *    "interactive"][0] as that's the default epoch from which the
+        *    `ReadingDepth.totalLength` property is measured from.
+        * 2. The document visibility change handler is set up.
+        * 3. The "pageLoaded" ReadingDepth event is logged.
+        *
+        * [0]: 
https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domInteractive
+        */
+       function onLoad() {
+               if ( document.hidden ) {
+                       pause( perf.timing.domInteractive );
+               }
+
+               $( document ).on( 'visibilitychange', function () {
+                       if ( document.hidden ) {
+                               pause();
+                       } else {
+                               resume();
+                       }
                } );
+
                logEvent( 'pageLoaded' );
        }
 
+       if ( isEnabled() ) {
+               $( window ).on( 'beforeunload', onBeforeUnload );
+               onLoad();
+       }
+
 }( jQuery, mediaWiki, mediaWiki.config, mediaWiki.user, mediaWiki.experiments 
) );

-- 
To view, visit https://gerrit.wikimedia.org/r/336670
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I762ec3fc91decf3cffa869dbd783faf62f01329a
Gerrit-PatchSet: 16
Gerrit-Project: mediawiki/extensions/WikimediaEvents
Gerrit-Branch: master
Gerrit-Owner: Jdlrobson <[email protected]>
Gerrit-Reviewer: Bmansurov <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Jhernandez <[email protected]>
Gerrit-Reviewer: Phuedx <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to