Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion media/js/base/consent/utils.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,58 @@ import MozAllowList from './allow-list.es6';
const COOKIE_ID = 'moz-consent-pref'; // Cookie name
const COOKIE_EXPIRY_DAYS = 182; // 6 months expiry

/**
* Sets GTAG ads consent mode
* @param {Boolean} hasConsent - if analytics pref is true or false
* @param {String} type - one of consent mode types (default|update)
* @returns {Boolean}
*/
function setGtagAdsConsentMode(hasConsent, type = 'update') {
// bail out if GTAG has not been created with GTMSnippet.loadSnippet
// this needs to run before GTM snippet loads to set proper defaults
if (typeof window.gtag === 'undefined') {
return false;
}
if (hasConsent) {
window.gtag('consent', type, {
ad_user_data: 'granted',
ad_personalization: 'granted',
ad_storage: 'granted'
});
} else {
window.gtag('consent', type, {
ad_user_data: 'denied',
ad_personalization: 'denied',
ad_storage: 'denied'
});
}
return true;
}

/**
* Sets GTAG analytics consent mode
* @param {Boolean} hasConsent - based on /landing/get default or analytics cookie
* @param {String} type - one of consent mode types (default|update)
* @returns {Boolean}
*/
function setGtagAnalyticsConsentMode(hasConsent, type = 'update') {
// bail out if GTAG has not been created with GTMSnippet.loadSnippet
// this needs to run before GTM snippet loads to set proper defaults
if (typeof window.gtag === 'undefined') {
return false;
}
if (hasConsent) {
window.gtag('consent', type, {
analytics_storage: 'granted'
});
} else {
window.gtag('consent', type, {
analytics_storage: 'denied'
});
}
return true;
}

/**
* Determines if the current page requires consent.
* Looks for a data attribute on the <html> tag.
Expand Down Expand Up @@ -106,6 +158,9 @@ function setConsentCookie(data) {
'lax'
);

setGtagAdsConsentMode(data.analytics);
setGtagAnalyticsConsentMode(data.analytics);

return true;
} catch (e) {
return false;
Expand Down Expand Up @@ -135,6 +190,18 @@ function isFirefoxDownloadThanks(location) {
return location.indexOf('/thanks/') > -1;
}

/**
* Determine if the current page is /landing/get.
* @param {String} location - The current page URL.
* @return {Boolean}.
*/
function isFirefoxLandingGet(location) {
if (typeof location !== 'string') {
return false;
}
return location.indexOf('/landing/get') > -1;
}

/**
* Determines if the current page URL contains a query string
* that allows the consent banner to be displayed.
Expand Down Expand Up @@ -230,7 +297,10 @@ export {
gpcEnabled,
hasConsentCookie,
isFirefoxDownloadThanks,
isFirefoxLandingGet,
isURLExceptionAllowed,
isURLPermitted,
setConsentCookie
setConsentCookie,
setGtagAdsConsentMode,
setGtagAnalyticsConsentMode
};
51 changes: 50 additions & 1 deletion media/js/base/gtm/gtm-snippet.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
dntEnabled,
getConsentCookie,
gpcEnabled,
isFirefoxDownloadThanks
isFirefoxDownloadThanks,
isFirefoxLandingGet,
setGtagAdsConsentMode,
setGtagAnalyticsConsentMode
} from '../consent/utils.es6';

const GTM_CONTAINER_ID = document
Expand All @@ -18,12 +21,46 @@ const GTM_CONTAINER_ID = document

const GTMSnippet = {};

if (typeof window.dataLayer === 'undefined') {
window.dataLayer = [];
}

/**
* Set Gtag consent defaults to false unless there is a
* consent pref cookie allowing analytics OR there's no
* consent required and we're on /landing/get
*/
GTMSnippet.setGtagConsentDefaults = () => {
const cookie = getConsentCookie();
const hasPref = cookie;

if (hasPref) {
setGtagAdsConsentMode(cookie.analytics, 'default');
setGtagAnalyticsConsentMode(cookie.analytics, 'default');
} else {
setGtagAdsConsentMode(false, 'default');
setGtagAnalyticsConsentMode(
GTMSnippet.isFirefoxLandingGet() && !consentRequired()
? true
: false,
'default'
);
}
};

/**
* Load the GTM snippet. Expects `GTM_CONTAINER_ID` to be
* defined in the HTML tag via a data attribute.
*/
GTMSnippet.loadSnippet = () => {
if (GTM_CONTAINER_ID) {
window.gtag = function () {
window.dataLayer.push(arguments);
};
// first: set default consent
GTMSnippet.setGtagConsentDefaults();

// then: load GTM script (the order is important)
// prettier-ignore
(function(w,d,s,l,i,j,f,dl,k,q){
w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});f=d.getElementsByTagName(s)[0];
Expand All @@ -41,13 +78,25 @@ GTMSnippet.isFirefoxDownloadThanks = () => {
return isFirefoxDownloadThanks(window.location.href);
};

/**
* Determine if the current page is /landing/get.
* @returns {Boolean}
*/
GTMSnippet.isFirefoxLandingGet = () => {
return isFirefoxLandingGet(window.location.href);
};

/**
* Event handler for `mozConsentStatus` event.
* @param {Object} e - Event object
*/
GTMSnippet.handleConsent = (e) => {
const hasConsent = e.detail.analytics;

// update gtag consent according to pref
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably a bit redundant given the consent rules under which we display the consent banner (denial default is likely in place & we may be updating for denial too), but it feels more future-proof in case the consent banner logic changes

setGtagAdsConsentMode(hasConsent);
setGtagAnalyticsConsentMode(hasConsent);

if (hasConsent) {
GTMSnippet.loadSnippet();
window.removeEventListener(
Expand Down
109 changes: 108 additions & 1 deletion tests/unit/spec/base/consent/consent-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import {
getHostName,
hasConsentCookie,
isFirefoxDownloadThanks,
isFirefoxLandingGet,
isURLExceptionAllowed,
isURLPermitted,
setConsentCookie
setConsentCookie,
setGtagAdsConsentMode,
setGtagAnalyticsConsentMode
} from '../../../../../media/js/base/consent/utils.es6';

describe('consentRequired()', function () {
Expand Down Expand Up @@ -361,3 +364,107 @@ describe('setConsentCookie()', function () {
expect(result).toBeFalse();
});
});

describe('isFirefoxLandingGet()', function () {
it('should return true if URL contains /landing/get', function () {
expect(
isFirefoxLandingGet('https://www.mozilla.org/en-US/landing/get/')
).toBeTrue();
expect(
isFirefoxLandingGet('https://www.allizom.org/en-US/landing/get/')
).toBeTrue();
expect(
isFirefoxLandingGet('https://localhost:8000/en-US/landing/get/')
).toBeTrue();
});

it('should return false if URL is not /landing/get', function () {
expect(
isFirefoxLandingGet('https://www.mozilla.org/en-US/')
).toBeFalse();
expect(
isFirefoxLandingGet('https://www.allizom.org/en-US/')
).toBeFalse();
expect(
isFirefoxLandingGet('https://localhost:8000/en-US/')
).toBeFalse();
expect(isFirefoxLandingGet('')).toBeFalse();
expect(isFirefoxLandingGet(null)).toBeFalse();
expect(isFirefoxLandingGet(undefined)).toBeFalse();
expect(isFirefoxLandingGet(true)).toBeFalse();
});
});

describe('setGtagAdsConsentMode()', function () {
afterEach(function () {
delete window.gtag;
});

it('should return false if window.gtag is not defined', function () {
expect(setGtagAdsConsentMode(true)).toBeFalse();
});

it('should grant ads consent when called with true', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAdsConsentMode(true);
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
ad_user_data: 'granted',
ad_personalization: 'granted',
ad_storage: 'granted'
});
});

it('should deny ads consent when called with false', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAdsConsentMode(false);
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
ad_user_data: 'denied',
ad_personalization: 'denied',
ad_storage: 'denied'
});
});

it('should use "default" type when specified', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAdsConsentMode(false, 'default');
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
ad_user_data: 'denied',
ad_personalization: 'denied',
ad_storage: 'denied'
});
});
});

describe('setGtagAnalyticsConsentMode()', function () {
afterEach(function () {
delete window.gtag;
});

it('should return false if window.gtag is not defined', function () {
expect(setGtagAnalyticsConsentMode(true)).toBeFalse();
});

it('should grant analytics consent when called with true', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAnalyticsConsentMode(true);
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
analytics_storage: 'granted'
});
});

it('should deny analytics consent when called with false', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAnalyticsConsentMode(false);
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
analytics_storage: 'denied'
});
});

it('should use "default" type when specified', function () {
window.gtag = jasmine.createSpy('gtag');
setGtagAnalyticsConsentMode(true, 'default');
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
analytics_storage: 'granted'
});
});
});
Loading