diff --git a/lib/plugins/max-scroll-tracker.js b/lib/plugins/max-scroll-tracker.js index b1697113..c346e2b1 100644 --- a/lib/plugins/max-scroll-tracker.js +++ b/lib/plugins/max-scroll-tracker.js @@ -20,8 +20,9 @@ import MethodChain from '../method-chain'; import provide from '../provide'; import Session from '../session'; import Store from '../store'; +import TrackerQueue from '../tracker-queue'; import {plugins, trackUsage} from '../usage'; -import {assign, createFieldsObj, debounce, isObject} from '../utilities'; +import {assign, createFieldsObj, debounce, isObject, now} from '../utilities'; /** @@ -37,9 +38,6 @@ class MaxScrollTracker { constructor(tracker, opts) { trackUsage(tracker, plugins.MAX_SCROLL_TRACKER); - // Feature detects to prevent errors in unsupporting browsers. - if (!window.addEventListener) return; - /** @type {MaxScrollTrackerOpts} */ const defaultOpts = { increaseThreshold: 20, @@ -50,27 +48,33 @@ class MaxScrollTracker { // hitFilter: undefined }; - this.opts = /** @type {MaxScrollTrackerOpts} */ ( - assign(defaultOpts, opts)); - + this.opts = /** @type {MaxScrollTrackerOpts} */ (assign(defaultOpts, opts)); this.tracker = tracker; - this.pagePath = this.getPagePath(); // Binds methods to `this`. + this.init = this.init.bind(this); this.handleScroll = debounce(this.handleScroll.bind(this), 500); this.trackerSetOverride = this.trackerSetOverride.bind(this); - // Creates the store and binds storage change events. + // Override the built-in tracker.set method to watch for changes. + MethodChain.add(tracker, 'set', this.trackerSetOverride); + + this.pagePath = this.getPagePath(); + this.store = Store.getOrCreate( tracker.get('trackingId'), 'plugins/max-scroll-tracker'); - // Creates the session and binds session events. this.session = Session.getOrCreate( tracker, this.opts.sessionTimeout, this.opts.timeZone); - // Override the built-in tracker.set method to watch for changes. - MethodChain.add(tracker, 'set', this.trackerSetOverride); + // Queue the rest of the initialization of the plugin idly. + this.queue = TrackerQueue.getOrCreate(tracker).add(this.init); + } + /** + * Idly initializes the rest of the plugin instance initialization logic. + */ + init() { this.listenForMaxScrollChanges(); } @@ -82,7 +86,7 @@ class MaxScrollTracker { listenForMaxScrollChanges() { const maxScrollPercentage = this.getMaxScrollPercentageForCurrentPage(); if (maxScrollPercentage < 100) { - window.addEventListener('scroll', this.handleScroll); + addEventListener('scroll', this.handleScroll); } } @@ -91,53 +95,55 @@ class MaxScrollTracker { * Removes an added scroll listener. */ stopListeningForMaxScrollChanges() { - window.removeEventListener('scroll', this.handleScroll); + removeEventListener('scroll', this.handleScroll); } /** * Handles the scroll event. If the current scroll percentage is greater - * that the stored scroll event by at least the specified increase threshold, + * than the stored scroll event by at least the specified increase threshold, * send an event with the increase amount. */ handleScroll() { - const pageHeight = getPageHeight(); - const scrollPos = window.pageYOffset; // scrollY isn't supported in IE. - const windowHeight = window.innerHeight; - - // Ensure scrollPercentage is an integer between 0 and 100. - const scrollPercentage = Math.min(100, Math.max(0, - Math.round(100 * (scrollPos / (pageHeight - windowHeight))))); - - // If the max scroll data gets out of the sync with the session data - // (for whatever reason), clear it. - const sessionId = this.session.getId(); - if (sessionId != this.store.get().sessionId) { - this.store.clear(); - this.store.set({sessionId}); - } + this.queue.add(({time}) => { + const pageHeight = getPageHeight(); + const scrollPos = window.pageYOffset; // scrollY isn't supported in IE. + const windowHeight = window.innerHeight; + + // Ensure scrollPercentage is an integer between 0 and 100. + const scrollPercentage = Math.min(100, Math.max(0, + Math.round(100 * (scrollPos / (pageHeight - windowHeight))))); + + // If the max scroll data gets out of the sync with the session data + // (for whatever reason), clear it. + const sessionId = this.session.id; + if (sessionId != this.store.data.sessionId) { + this.store.clear(); + this.store.update({sessionId}); + } - // If the session has expired, clear the stored data and don't send any - // events (since they'd start a new session). Note: this check is needed, - // in addition to the above check, to handle cases where the session IDs - // got out of sync, but the session didn't expire. - if (this.session.isExpired(this.store.get().sessionId)) { - this.store.clear(); - } else { - const maxScrollPercentage = this.getMaxScrollPercentageForCurrentPage(); - - if (scrollPercentage > maxScrollPercentage) { - if (scrollPercentage == 100 || maxScrollPercentage == 100) { - this.stopListeningForMaxScrollChanges(); - } - const increaseAmount = scrollPercentage - maxScrollPercentage; - if (scrollPercentage == 100 || - increaseAmount >= this.opts.increaseThreshold) { - this.setMaxScrollPercentageForCurrentPage(scrollPercentage); - this.sendMaxScrollEvent(increaseAmount, scrollPercentage); + // If the session has expired, clear the stored data and don't send any + // events (since they'd start a new session). Note: this check is needed, + // in addition to the above check, to handle cases where the session IDs + // got out of sync, but the session didn't expire. + if (this.session.isExpired(this.store.data.sessionId)) { + this.store.clear(); + } else { + const maxScrollPercentage = this.getMaxScrollPercentageForCurrentPage(); + + if (scrollPercentage > maxScrollPercentage) { + if (scrollPercentage == 100 || maxScrollPercentage == 100) { + this.stopListeningForMaxScrollChanges(); + } + const increaseAmount = scrollPercentage - maxScrollPercentage; + if (scrollPercentage == 100 || + increaseAmount >= this.opts.increaseThreshold) { + this.setMaxScrollPercentageForCurrentPage(scrollPercentage); + this.sendMaxScrollEvent(increaseAmount, scrollPercentage, time); + } } } - } + }); } /** @@ -171,26 +177,31 @@ class MaxScrollTracker { * Sends an event for the increased max scroll percentage amount. * @param {number} increaseAmount * @param {number} scrollPercentage + * @param {number} scrollTimestamp */ - sendMaxScrollEvent(increaseAmount, scrollPercentage) { - /** @type {FieldsObj} */ - const defaultFields = { - transport: 'beacon', - eventCategory: 'Max Scroll', - eventAction: 'increase', - eventValue: increaseAmount, - eventLabel: String(scrollPercentage), - nonInteraction: true, - }; - - // If a custom metric was specified, set it equal to the event value. - if (this.opts.maxScrollMetricIndex) { - defaultFields['metric' + this.opts.maxScrollMetricIndex] = increaseAmount; - } + sendMaxScrollEvent(increaseAmount, scrollPercentage, scrollTimestamp) { + this.queue.add(() => { + /** @type {FieldsObj} */ + const defaultFields = { + transport: 'beacon', + eventCategory: 'Max Scroll', + eventAction: 'increase', + eventValue: increaseAmount, + eventLabel: String(scrollPercentage), + nonInteraction: true, + queueTime: now() - scrollTimestamp, + }; + + // If a custom metric was specified, set it equal to the event value. + if (this.opts.maxScrollMetricIndex) { + defaultFields['metric' + this.opts.maxScrollMetricIndex] = + increaseAmount; + } - this.tracker.send('event', - createFieldsObj(defaultFields, this.opts.fieldsObj, - this.tracker, this.opts.hitFilter)); + this.tracker.send('event', + createFieldsObj(defaultFields, this.opts.fieldsObj, + this.tracker, this.opts.hitFilter)); + }); } /** @@ -198,9 +209,11 @@ class MaxScrollTracker { * @param {number} maxScrollPercentage */ setMaxScrollPercentageForCurrentPage(maxScrollPercentage) { - this.store.set({ - [this.pagePath]: maxScrollPercentage, - sessionId: this.session.getId(), + this.queue.add(() => { + this.store.update({ + [this.pagePath]: maxScrollPercentage, + sessionId: this.session.id, + }); }); } @@ -209,12 +222,12 @@ class MaxScrollTracker { * @return {number} */ getMaxScrollPercentageForCurrentPage() { - return this.store.get()[this.pagePath] || 0; + return this.store.data[this.pagePath] || 0; } /** * Gets the page path from the tracker object. - * @return {number} + * @return {string} */ getPagePath() { const url = parseUrl( @@ -226,7 +239,10 @@ class MaxScrollTracker { * Removes all event listeners and restores overridden methods. */ remove() { + this.queue.destroy(); + this.store.destroy(); this.session.destroy(); + this.stopListeningForMaxScrollChanges(); MethodChain.remove(this.tracker, 'set', this.trackerSetOverride); }