Skip to content

Commit b843b13

Browse files
Port consent updates from springfield
Ports the basic consent mode configuration from springfield but does not port the download attribution or promoted pages concept. Original JIRA ticket: https://mozilla-hub.atlassian.net/browse/WT-602 Completed in: mozmeao/springfield#955
1 parent 2597353 commit b843b13

4 files changed

Lines changed: 288 additions & 3 deletions

File tree

media/js/base/consent/utils.es6.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,58 @@ import MozAllowList from './allow-list.es6';
99
const COOKIE_ID = 'moz-consent-pref'; // Cookie name
1010
const COOKIE_EXPIRY_DAYS = 182; // 6 months expiry
1111

12+
/**
13+
* Sets GTAG ads consent mode
14+
* @param {Boolean} hasConsent - if analytics pref is true or false
15+
* @param {String} type - one of consent mode types (default|update)
16+
* @returns {Boolean}
17+
*/
18+
function setGtagAdsConsentMode(hasConsent, type = 'update') {
19+
// bail out if GTAG has not been created with GTMSnippet.loadSnippet
20+
// this needs to run before GTM snippet loads to set proper defaults
21+
if (typeof window.gtag === 'undefined') {
22+
return false;
23+
}
24+
if (hasConsent) {
25+
window.gtag('consent', type, {
26+
ad_user_data: 'granted',
27+
ad_personalization: 'granted',
28+
ad_storage: 'granted'
29+
});
30+
} else {
31+
window.gtag('consent', type, {
32+
ad_user_data: 'denied',
33+
ad_personalization: 'denied',
34+
ad_storage: 'denied'
35+
});
36+
}
37+
return true;
38+
}
39+
40+
/**
41+
* Sets GTAG analytics consent mode
42+
* @param {Boolean} hasConsent - based on analytics cookie
43+
* @param {String} type - one of consent mode types (default|update)
44+
* @returns {Boolean}
45+
*/
46+
function setGtagAnalyticsConsentMode(hasConsent, type = 'update') {
47+
// bail out if GTAG has not been created with GTMSnippet.loadSnippet
48+
// this needs to run before GTM snippet loads to set proper defaults
49+
if (typeof window.gtag === 'undefined') {
50+
return false;
51+
}
52+
if (hasConsent) {
53+
window.gtag('consent', type, {
54+
analytics_storage: 'granted'
55+
});
56+
} else {
57+
window.gtag('consent', type, {
58+
analytics_storage: 'denied'
59+
});
60+
}
61+
return true;
62+
}
63+
1264
/**
1365
* Determines if the current page requires consent.
1466
* Looks for a data attribute on the <html> tag.
@@ -106,6 +158,9 @@ function setConsentCookie(data) {
106158
'lax'
107159
);
108160

161+
setGtagAdsConsentMode(data.analytics);
162+
setGtagAnalyticsConsentMode(data.analytics);
163+
109164
return true;
110165
} catch (e) {
111166
return false;
@@ -232,5 +287,7 @@ export {
232287
isFirefoxDownloadThanks,
233288
isURLExceptionAllowed,
234289
isURLPermitted,
235-
setConsentCookie
290+
setConsentCookie,
291+
setGtagAdsConsentMode,
292+
setGtagAnalyticsConsentMode
236293
};

media/js/base/gtm/gtm-snippet.es6.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
dntEnabled,
1010
getConsentCookie,
1111
gpcEnabled,
12-
isFirefoxDownloadThanks
12+
isFirefoxDownloadThanks,
13+
setGtagAdsConsentMode,
14+
setGtagAnalyticsConsentMode
1315
} from '../consent/utils.es6';
1416

1517
const GTM_CONTAINER_ID = document
@@ -18,12 +20,43 @@ const GTM_CONTAINER_ID = document
1820

1921
const GTMSnippet = {};
2022

23+
if (typeof window.dataLayer === 'undefined') {
24+
window.dataLayer = [];
25+
}
26+
27+
/**
28+
* Set Gtag consent defaults based on consent cookie or
29+
* visitor region. Visitors outside EU/EAA default to
30+
* granted analytics; visitors inside EU/EAA default to
31+
* denied until explicit consent is given. Ads are always
32+
* denied by default unless the user has previously consented.
33+
*/
34+
GTMSnippet.setGtagConsentDefaults = () => {
35+
const cookie = getConsentCookie();
36+
const hasPref = cookie;
37+
38+
if (hasPref) {
39+
setGtagAdsConsentMode(cookie.analytics, 'default');
40+
setGtagAnalyticsConsentMode(cookie.analytics, 'default');
41+
} else {
42+
setGtagAdsConsentMode(false, 'default');
43+
setGtagAnalyticsConsentMode(!consentRequired(), 'default');
44+
}
45+
};
46+
2147
/**
2248
* Load the GTM snippet. Expects `GTM_CONTAINER_ID` to be
2349
* defined in the HTML tag via a data attribute.
2450
*/
2551
GTMSnippet.loadSnippet = () => {
2652
if (GTM_CONTAINER_ID) {
53+
window.gtag = function () {
54+
window.dataLayer.push(arguments);
55+
};
56+
// first: set default consent
57+
GTMSnippet.setGtagConsentDefaults();
58+
59+
// then: load GTM script (the order is important)
2760
// prettier-ignore
2861
(function(w,d,s,l,i,j,f,dl,k,q){
2962
w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});f=d.getElementsByTagName(s)[0];
@@ -48,6 +81,10 @@ GTMSnippet.isFirefoxDownloadThanks = () => {
4881
GTMSnippet.handleConsent = (e) => {
4982
const hasConsent = e.detail.analytics;
5083

84+
// update gtag consent according to pref
85+
setGtagAdsConsentMode(hasConsent);
86+
setGtagAnalyticsConsentMode(hasConsent);
87+
5188
if (hasConsent) {
5289
GTMSnippet.loadSnippet();
5390
window.removeEventListener(

tests/unit/spec/base/consent/consent-utils.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import {
1313
isFirefoxDownloadThanks,
1414
isURLExceptionAllowed,
1515
isURLPermitted,
16-
setConsentCookie
16+
setConsentCookie,
17+
setGtagAdsConsentMode,
18+
setGtagAnalyticsConsentMode
1719
} from '../../../../../media/js/base/consent/utils.es6';
1820

1921
describe('consentRequired()', function () {
@@ -379,3 +381,77 @@ describe('setConsentCookie()', function () {
379381
expect(result).toBeFalse();
380382
});
381383
});
384+
385+
describe('setGtagAdsConsentMode()', function () {
386+
afterEach(function () {
387+
delete window.gtag;
388+
});
389+
390+
it('should return false if window.gtag is not defined', function () {
391+
expect(setGtagAdsConsentMode(true)).toBeFalse();
392+
});
393+
394+
it('should set granted consent when hasConsent is true', function () {
395+
window.gtag = jasmine.createSpy('gtag');
396+
setGtagAdsConsentMode(true);
397+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
398+
ad_user_data: 'granted',
399+
ad_personalization: 'granted',
400+
ad_storage: 'granted'
401+
});
402+
});
403+
404+
it('should set denied consent when hasConsent is false', function () {
405+
window.gtag = jasmine.createSpy('gtag');
406+
setGtagAdsConsentMode(false);
407+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
408+
ad_user_data: 'denied',
409+
ad_personalization: 'denied',
410+
ad_storage: 'denied'
411+
});
412+
});
413+
414+
it('should respect an explicit type argument', function () {
415+
window.gtag = jasmine.createSpy('gtag');
416+
setGtagAdsConsentMode(false, 'default');
417+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
418+
ad_user_data: 'denied',
419+
ad_personalization: 'denied',
420+
ad_storage: 'denied'
421+
});
422+
});
423+
});
424+
425+
describe('setGtagAnalyticsConsentMode()', function () {
426+
afterEach(function () {
427+
delete window.gtag;
428+
});
429+
430+
it('should return false if window.gtag is not defined', function () {
431+
expect(setGtagAnalyticsConsentMode(true)).toBeFalse();
432+
});
433+
434+
it('should set granted consent when hasConsent is true', function () {
435+
window.gtag = jasmine.createSpy('gtag');
436+
setGtagAnalyticsConsentMode(true);
437+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
438+
analytics_storage: 'granted'
439+
});
440+
});
441+
442+
it('should set denied consent when hasConsent is false', function () {
443+
window.gtag = jasmine.createSpy('gtag');
444+
setGtagAnalyticsConsentMode(false);
445+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
446+
analytics_storage: 'denied'
447+
});
448+
});
449+
450+
it('should respect an explicit type argument', function () {
451+
window.gtag = jasmine.createSpy('gtag');
452+
setGtagAnalyticsConsentMode(true, 'default');
453+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
454+
analytics_storage: 'granted'
455+
});
456+
});
457+
});

tests/unit/spec/base/gtm/gtm-snippet.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,119 @@ describe('gtm-snippet.es6.js', function () {
162162
expect(GTMSnippet.loadSnippet).not.toHaveBeenCalled();
163163
});
164164
});
165+
166+
describe('GTMSnippet.setGtagConsentDefaults()', function () {
167+
beforeEach(function () {
168+
window.gtag = jasmine.createSpy('gtag');
169+
});
170+
171+
afterEach(function () {
172+
delete window.gtag;
173+
document
174+
.getElementsByTagName('html')[0]
175+
.removeAttribute('data-needs-consent');
176+
});
177+
178+
it('should set granted defaults when consent cookie accepts analytics', function () {
179+
const obj = { analytics: true, preference: true };
180+
spyOn(window.Mozilla.Cookies, 'getItem').and.returnValue(
181+
JSON.stringify(obj)
182+
);
183+
GTMSnippet.setGtagConsentDefaults();
184+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
185+
ad_user_data: 'granted',
186+
ad_personalization: 'granted',
187+
ad_storage: 'granted'
188+
});
189+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
190+
analytics_storage: 'granted'
191+
});
192+
});
193+
194+
it('should set denied defaults when consent cookie rejects analytics', function () {
195+
const obj = { analytics: false, preference: false };
196+
spyOn(window.Mozilla.Cookies, 'getItem').and.returnValue(
197+
JSON.stringify(obj)
198+
);
199+
GTMSnippet.setGtagConsentDefaults();
200+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
201+
ad_user_data: 'denied',
202+
ad_personalization: 'denied',
203+
ad_storage: 'denied'
204+
});
205+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
206+
analytics_storage: 'denied'
207+
});
208+
});
209+
210+
it('should deny ads and grant analytics defaults when no cookie and visitor is outside EU/EAA', function () {
211+
spyOn(window.Mozilla.Cookies, 'getItem').and.returnValue(false);
212+
document
213+
.getElementsByTagName('html')[0]
214+
.setAttribute('data-needs-consent', 'False');
215+
GTMSnippet.setGtagConsentDefaults();
216+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
217+
ad_user_data: 'denied',
218+
ad_personalization: 'denied',
219+
ad_storage: 'denied'
220+
});
221+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
222+
analytics_storage: 'granted'
223+
});
224+
});
225+
226+
it('should deny all defaults when no cookie and visitor is in EU/EAA', function () {
227+
spyOn(window.Mozilla.Cookies, 'getItem').and.returnValue(false);
228+
document
229+
.getElementsByTagName('html')[0]
230+
.setAttribute('data-needs-consent', 'True');
231+
GTMSnippet.setGtagConsentDefaults();
232+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
233+
ad_user_data: 'denied',
234+
ad_personalization: 'denied',
235+
ad_storage: 'denied'
236+
});
237+
expect(window.gtag).toHaveBeenCalledWith('consent', 'default', {
238+
analytics_storage: 'denied'
239+
});
240+
});
241+
});
242+
243+
describe('GTMSnippet.handleConsent()', function () {
244+
beforeEach(function () {
245+
window.gtag = jasmine.createSpy('gtag');
246+
});
247+
248+
afterEach(function () {
249+
delete window.gtag;
250+
});
251+
252+
it('should call gtag consent update when analytics are accepted', function () {
253+
GTMSnippet.handleConsent({
254+
detail: { analytics: true, preference: true }
255+
});
256+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
257+
ad_user_data: 'granted',
258+
ad_personalization: 'granted',
259+
ad_storage: 'granted'
260+
});
261+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
262+
analytics_storage: 'granted'
263+
});
264+
});
265+
266+
it('should call gtag consent update when analytics are rejected', function () {
267+
GTMSnippet.handleConsent({
268+
detail: { analytics: false, preference: false }
269+
});
270+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
271+
ad_user_data: 'denied',
272+
ad_personalization: 'denied',
273+
ad_storage: 'denied'
274+
});
275+
expect(window.gtag).toHaveBeenCalledWith('consent', 'update', {
276+
analytics_storage: 'denied'
277+
});
278+
});
279+
});
165280
});

0 commit comments

Comments
 (0)