author | Robert Helmer <rhelmer@mozilla.com> |
Thu, 06 Aug 2020 19:57:27 +0000 | |
changeset 543683 | b41de9e8fd9a2c93f2e64833a230d72f8c16374e |
parent 543682 | 4101493c2f41fdcea2d14635215d5babde0a8064 |
child 543684 | 9b48053f30035789d9c05aa2847c70b9bb892103 |
push id | 123616 |
push user | rhelmer@mozilla.com |
push date | Thu, 06 Aug 2020 21:49:18 +0000 |
treeherder | autoland@b41de9e8fd9a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | fluent-reviewers, sfoster |
bugs | 1653119 |
milestone | 81.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
|
--- a/browser/components/pioneer/content/pioneer.css +++ b/browser/components/pioneer/content/pioneer.css @@ -53,36 +53,43 @@ details > summary { .card-name { margin: 0; font-size: 16px; font-weight: 600; line-height: 1; } -.card-creator { +.card-author { margin: 0; font-size: 14px; font-weight: 400; } .card-actions { align-self: center; flex-shrink: 0; + min-width: 120px; } .join-button { max-width: 200px; margin: 0; margin-inline-end: 16px; } .card-description { font-size: 14px; font-weight: normal; + width: 100%; +} + +.card-data-collected { + font-size: 14px; + font-weight: normal; } *[hidden] { display: none !important; } #pioneer-icon { -moz-context-properties: fill;
--- a/browser/components/pioneer/content/pioneer.ftl +++ b/browser/components/pioneer/content/pioneer.ftl @@ -8,12 +8,13 @@ -pioneer-brand-short-name = Pioneer pioneer = { -pioneer-brand-short-name } pioneer-title = Introducing { -brand-short-name } { -pioneer-brand-short-name } pioneer-summary = { -pioneer-brand-short-name } is an experimental program that allows you to donate your data to public interest scientific studies in areas such as internet misinformation, data privacy, and ethical AI. pioneer-voluntary-notice = Participation in { -pioneer-brand-short-name } is strictly voluntary. pioneer-details = When you enroll in the { -pioneer-brand-short-name } program, you will receive periodic offers to join studies led by top research teams in collaborations with Mozilla. Your participation in each study is voluntary. You can leave an individual study or leave the { -pioneer-brand-short-name } program entirely at any time. pioneer-join-study = Join Study +pioneer-leave-study = Leave Study pioneer-enrollment-button = Enroll pioneer-unenrollment-button = Leave pioneer-available-studies = Available Studies pioneer-end-study = End Study
--- a/browser/components/pioneer/content/pioneer.html +++ b/browser/components/pioneer/content/pioneer.html @@ -7,17 +7,16 @@ <head> <meta charset="utf-8"> <meta http-equiv="Content-Security-Policy" content="default-src chrome: blob:; img-src https:; object-src 'none'"> <link rel="localization" href="browser/branding/brandings.ftl"> <link rel="localization" href="branding/brand.ftl"> <!-- Temporary "en-US"-only l10n strings --> <link rel="localization" href="preview/pioneer.ftl"> <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"> - <link rel="stylesheet" href="chrome://global/skin/in-content/toggle-button.css"> <link rel="stylesheet" href="chrome://browser/content/pioneer.css"> <script src="chrome://browser/content/pioneer.js"></script> <link rel="icon" href="chrome://browser/skin/pioneer.svg"> <title data-l10n-id="pioneer"></title> </head> <body> <div id="report-content"> @@ -88,17 +87,17 @@ <p>To leave individual studies:</p> <ol> <li> Return to this page by clicking the Pioneer toolbar icon in Firefox or typing about:pioneer into the location bar and pressing Enter. </li> <li> All the studies that you have joined will be listed. To leave a - specific study, click on the toggle button next to the study. + specific study, click on the Leave Study button next to the study. </li> <li> Leaving a study will immediately stop collection of data associated with that study and remove any changes made to the browser as part of the study. </li> </ol> <p>
--- a/browser/components/pioneer/content/pioneer.js +++ b/browser/components/pioneer/content/pioneer.js @@ -34,17 +34,20 @@ XPCOMUtils.defineLazyModuleGetters(this, const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId"; const PREF_PIONEER_NEW_STUDIES_AVAILABLE = "toolkit.telemetry.pioneer-new-studies-available"; /** * This is the Remote Settings key that we use to get the list of available studies. */ -const STUDY_ADDON_COLLECTION_KEY = "pioneer-study-addons"; +const STUDY_ADDON_COLLECTION_KEY = "pioneer-study-addons-v1"; + +const PREF_TEST_CACHED_ADDONS = "toolkit.pioneer.testCachedAddons"; +const PREF_TEST_ADDONS = "toolkit.pioneer.testAddons"; function showEnrollmentStatus() { const pioneerId = Services.prefs.getStringPref(PREF_PIONEER_ID, null); const enrollmentButton = document.getElementById("enrollment-button"); document.l10n.setAttributes( enrollmentButton, @@ -58,17 +61,17 @@ async function showAvailableStudies(cach if (!cachedAddon) { console.error( `about:pioneer - Study addon ID not found in cache: ${studyAddonId}` ); return; } if (cachedAddon.isDefault) { - return; + continue; } const studyAddonId = cachedAddon.addon_id; const study = document.createElement("div"); study.setAttribute("id", studyAddonId); study.setAttribute("class", "card card-no-hover"); @@ -83,129 +86,151 @@ async function showAvailableStudies(cach studyBody.classList.add("card-body"); study.appendChild(studyBody); const studyName = document.createElement("h3"); studyName.setAttribute("class", "card-name"); studyName.textContent = cachedAddon.name; studyBody.appendChild(studyName); - const studyCreator = document.createElement("span"); - studyCreator.setAttribute("class", "card-creator"); - studyCreator.textContent = cachedAddon.creator.name; - studyBody.appendChild(studyCreator); + const studyAuthor = document.createElement("span"); + studyAuthor.setAttribute("class", "card-author"); + studyAuthor.textContent = cachedAddon.authors.name; + studyBody.appendChild(studyAuthor); const actions = document.createElement("div"); actions.classList.add("card-actions"); study.appendChild(actions); const joinBtn = document.createElement("button"); joinBtn.setAttribute("id", `${studyAddonId}-join-button`); joinBtn.classList.add("primary"); joinBtn.classList.add("join-button"); document.l10n.setAttributes(joinBtn, "pioneer-join-study"); actions.appendChild(joinBtn); - const enrollStudyBtn = document.createElement("input"); - enrollStudyBtn.setAttribute("id", `${studyAddonId}-toggle-button`); - enrollStudyBtn.setAttribute("class", "toggle-button"); - enrollStudyBtn.setAttribute("type", "checkbox"); - actions.appendChild(enrollStudyBtn); - const studyDesc = document.createElement("div"); studyDesc.setAttribute("class", "card-description"); study.appendChild(studyDesc); const shortDesc = document.createElement("p"); shortDesc.textContent = cachedAddon.description; studyDesc.appendChild(shortDesc); - const fullDesc = document.createElement("p"); - fullDesc.textContent = cachedAddon.fullDescription; - studyDesc.appendChild(fullDesc); + const privacyPolicyLink = document.createElement("a"); + privacyPolicyLink.href = cachedAddon.privacyPolicy.spec; + privacyPolicyLink.textContent = "privacy policy"; + + const privacyPolicy = document.createElement("p"); + const privacyPolicyStart = document.createElement("span"); + privacyPolicyStart.textContent = "You can always find the "; + privacyPolicy.append(privacyPolicyStart); + privacyPolicy.append(privacyPolicyLink); + const privacyPolicyEnd = document.createElement("span"); + privacyPolicyEnd.textContent = " at our website."; + privacyPolicy.append(privacyPolicyEnd); + studyDesc.appendChild(privacyPolicy); + + const studyDataCollected = document.createElement("div"); + studyDataCollected.setAttribute("class", "card-data-collected"); + study.appendChild(studyDataCollected); + + const dataCollectionDetailsHeader = document.createElement("p"); + dataCollectionDetailsHeader.textContent = "This study will collect:"; + studyDataCollected.appendChild(dataCollectionDetailsHeader); + + const dataCollectionDetails = document.createElement("ul"); + for (const dataCollectionDetail of cachedAddon.dataCollectionDetails) { + const detailsBullet = document.createElement("li"); + detailsBullet.textContent = dataCollectionDetail; + dataCollectionDetails.append(detailsBullet); + } + studyDataCollected.appendChild(dataCollectionDetails); async function toggleEnrolled() { let addon; let install; if (Cu.isInAutomation) { - if ( - Services.prefs.getBoolPref( - "toolkit.pioneer.testAddonInstalled", - false - ) - ) { - addon = { uninstall() {} }; + let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, null); + testAddons = JSON.parse(testAddons); + install = { + install: () => {}, + }; + for (const testAddon of testAddons) { + if (testAddon.id == studyAddonId) { + addon = testAddon; + addon.install = () => {}; + addon.uninstall = () => { + Services.prefs.setStringPref(PREF_TEST_ADDONS, "[]"); + }; + } } - - install = { install() {} }; } else { addon = await AddonManager.getAddonByID(studyAddonId); install = await AddonManager.getInstallForURL( cachedAddon.sourceURI.spec ); } if (addon) { + joinBtn.disabled = true; await addon.uninstall(); + document.l10n.setAttributes(joinBtn, "pioneer-join-study"); + joinBtn.disabled = false; } else { joinBtn.disabled = true; await install.install(); + document.l10n.setAttributes(joinBtn, "pioneer-leave-study"); + joinBtn.disabled = false; } await updateStudy(studyAddonId); } - enrollStudyBtn.addEventListener("input", toggleEnrolled); joinBtn.addEventListener("click", toggleEnrolled); const availableStudies = document.getElementById("available-studies"); availableStudies.appendChild(study); await updateStudy(studyAddonId); } const availableStudies = document.getElementById("header-available-studies"); document.l10n.setAttributes(availableStudies, "pioneer-available-studies"); } async function updateStudy(studyAddonId) { let addon; if (Cu.isInAutomation) { - if ( - Services.prefs.getBoolPref("toolkit.pioneer.testAddonInstalled", false) - ) { + if (Services.prefs.getBoolPref(PREF_TEST_ADDONS, false)) { addon = { uninstall() {} }; } } else { addon = await AddonManager.getAddonByID(studyAddonId); } const study = document.querySelector(`.card[id="${studyAddonId}"`); - const enrollStudyBtn = study.querySelector(".toggle-button"); const joinBtn = study.querySelector(".join-button"); const pioneerId = Services.prefs.getStringPref(PREF_PIONEER_ID, null); if (pioneerId) { study.style.opacity = 1; + joinBtn.disabled = false; - enrollStudyBtn.disabled = false; - enrollStudyBtn.checked = !!addon; - - joinBtn.disabled = false; - joinBtn.hidden = !!addon; + if (addon) { + document.l10n.setAttributes(joinBtn, "pioneer-leave-study"); + } else { + document.l10n.setAttributes(joinBtn, "pioneer-join-study"); + } } else { + document.l10n.setAttributes(joinBtn, "pioneer-join-study"); study.style.opacity = 0.5; - - enrollStudyBtn.disabled = true; - enrollStudyBtn.checked = false; - joinBtn.disabled = true; - joinBtn.hidden = false; } } // equivalent to what we use for Telemetry IDs // https://searchfox.org/mozilla-central/rev/9193635dca8cfdcb68f114306194ffc860456044/toolkit/components/telemetry/app/TelemetryUtils.jsm#222 function generateUUID() { let str = Cc["@mozilla.org/uuid-generator;1"] .getService(Ci.nsIUUIDGenerator) @@ -305,17 +330,17 @@ document.addEventListener("DOMContentLoa showEnrollmentStatus(); document.addEventListener("focus", removeBadge); removeBadge(); let cachedAddons; if (Cu.isInAutomation) { let testCachedAddons = Services.prefs.getStringPref( - "toolkit.pioneer.testCachedAddons", + PREF_TEST_CACHED_ADDONS, null ); if (testCachedAddons) { cachedAddons = JSON.parse(testCachedAddons); } } else { cachedAddons = await RemoteSettings(STUDY_ADDON_COLLECTION_KEY).get(); }
--- a/browser/components/pioneer/moz.build +++ b/browser/components/pioneer/moz.build @@ -5,8 +5,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini'] JAR_MANIFESTS += ['jar.mn'] with Files('**'): BUG_COMPONENT = ('Firefox', 'General') + +TESTING_JS_MODULES += [ + 'schemas/PioneerStudyAddonsSchema.json', +]
new file mode 100644 --- /dev/null +++ b/browser/components/pioneer/schemas/PioneerStudyAddonsSchema.json @@ -0,0 +1,96 @@ +{ + "type": "object", + "properties": { + "addon_id": { + "type": "string" + }, + "icons": { + "type": "object", + "properties": { + "32": { + "type": "string" + }, + "64": { + "type": "string" + }, + "128": { + "type": "string" + } + } + }, + "title": { + "type": "string", + "maxLength": 75 + }, + "version": { + "type": "string" + }, + "sourceURI": { + "type": "object", + "properties": { + "spec": { + "type": "string" + } + } + }, + "description": { + "type": "string" + }, + "privacyPolicy": { + "type": "object", + "properties": { + "spec": { + "type": "string" + } + } + }, + "studyType": { + "type": "string" + }, + "moreInfo": { + "type": "object", + "properties": { + "spec": { + "type": "string" + } + } + }, + "dataCollectionDetails": { + "type": "array", + "properties": { + "collectionDetail": { + "type": "string" + } + } + }, + "authors": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 75 + }, + "moreInfo": { + "type": "object", + "properties": { + "spec": { + "type": "string" + } + } + } + } + } + }, + "required": [ + "addon_id", + "icons", + "version", + "sourceURI", + "description", + "privacyPolicy", + "studyType", + "moreInfo", + "dataCollectionDetails", + "authors" + ] +}
--- a/browser/components/pioneer/test/browser/browser_pioneer_ui.js +++ b/browser/components/pioneer/test/browser/browser_pioneer_ui.js @@ -3,110 +3,160 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +ChromeUtils.defineModuleGetter( + this, + "Ajv", + "resource://testing-common/ajv-4.1.1.js" +); + const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId"; const PREF_PIONEER_NEW_STUDIES_AVAILABLE = "toolkit.telemetry.pioneer-new-studies-available"; -const PREF_CACHED_ADDONS = "toolkit.pioneer.testCachedAddons"; -const PREF_TEST_ADDON_INSTALLED = "toolkit.pioneer.testAddonInstalled"; +const PREF_TEST_CACHED_ADDONS = "toolkit.pioneer.testCachedAddons"; +const PREF_TEST_ADDONS = "toolkit.pioneer.testAddons"; const CACHED_ADDONS = [ { addon_id: "pioneer-v2-example@mozilla.org", icons: { "32": "https://localhost/user-media/addon_icons/2644/2644632-32.png?modified=4a64e2bc", "64": "https://localhost/user-media/addon_icons/2644/2644632-64.png?modified=4a64e2bc", "128": "https://localhost/user-media/addon_icons/2644/2644632-128.png?modified=4a64e2bc", }, - _unsupportedProperties: {}, name: "Demo Study", version: "1.0", sourceURI: { spec: "https://localhost", }, - homepageURL: "https://github.com/rhelmer/pioneer-v2-example", - supportURL: null, description: "Study purpose: Testing Pioneer.", - fullDescription: - "Data collected: An encrypted ping containing the current date and time is sent to Mozilla's servers.", - weeklyDownloads: 0, - type: "extension", - creator: { + privacyPolicy: { + spec: "http://localhost", + }, + studyType: "extension", + authors: { name: "Pioneer Developers", url: "https://addons.mozilla.org/en-US/firefox/user/6510522/", }, - developers: [], - screenshots: [], - contributionURL: "", - averageRating: 0, - reviewCount: 0, - reviewURL: - "https://addons.mozilla.org/en-US/firefox/addon/pioneer-v2-example/reviews/", - updateDate: "2020-05-27T20:47:35.000Z", + dataCollectionDetails: ["test123", "test345"], + moreInfo: { + spec: "http://localhost", + }, + isDefault: false, }, { addon_id: "pioneer-v2-default-example@mozilla.org", icons: { "32": "https://localhost/user-media/addon_icons/2644/2644632-32.png?modified=4a64e2bc", "64": "https://localhost/user-media/addon_icons/2644/2644632-64.png?modified=4a64e2bc", "128": "https://localhost/user-media/addon_icons/2644/2644632-128.png?modified=4a64e2bc", }, - _unsupportedProperties: {}, name: "Demo Default Study", version: "1.0", sourceURI: { spec: "https://localhost", }, - homepageURL: "https://github.com/rhelmer/pioneer-v2-example", - supportURL: null, description: "Study purpose: Testing Pioneer.", - fullDescription: - "Data collected: An encrypted ping containing the current date and time is sent to Mozilla's servers.", - weeklyDownloads: 0, - type: "extension", - creator: { + privacyPolicy: { + spec: "http://localhost", + }, + studyType: "extension", + authors: { name: "Pioneer Developers", url: "https://addons.mozilla.org/en-US/firefox/user/6510522/", }, - developers: [], - screenshots: [], - contributionURL: "", - averageRating: 0, - reviewCount: 0, - reviewURL: - "https://addons.mozilla.org/en-US/firefox/addon/pioneer-v2-example/reviews/", - updateDate: "2020-05-27T20:47:35.000Z", + dataCollectionDetails: ["test123", "test345"], + moreInfo: { + spec: "http://localhost", + }, isDefault: true, }, + { + addon_id: "study@partner", + icons: { + "32": + "https://localhost/user-media/addon_icons/2644/2644632-32.png?modified=4a64e2bc", + "64": + "https://localhost/user-media/addon_icons/2644/2644632-64.png?modified=4a64e2bc", + "128": + "https://localhost/user-media/addon_icons/2644/2644632-128.png?modified=4a64e2bc", + }, + name: "Example Partner Study", + version: "1.0", + sourceURI: { + spec: "https://localhost", + }, + description: "Study purpose: Testing Pioneer.", + privacyPolicy: { + spec: "http://localhost", + }, + studyType: "extension", + authors: { + name: "Stusdy Partners", + url: "https://addons.mozilla.org/en-US/firefox/user/6510522/", + }, + dataCollectionDetails: ["test123", "test345"], + moreInfo: { + spec: "http://localhost", + }, + isDefault: false, + }, +]; + +const TEST_ADDONS = [ + { id: "pioneer-v2-example@pioneer.mozilla.org" }, + { id: "pioneer-v2-default-example@mozilla.org" }, + { id: "study@partner" }, ]; const waitForAnimationFrame = () => new Promise(resolve => { content.window.requestAnimationFrame(resolve); }); +add_task(async function testMockSchema() { + const response = await fetch( + "resource://testing-common/PioneerStudyAddonsSchema.json" + ); + const schema = await response.json(); + if (!schema) { + throw new Error("Failed to load PioneerStudyAddonsSchema"); + } + + const ajv = new Ajv(); + const validate = ajv.compile(schema); + + for (const addon of CACHED_ADDONS) { + const valid = validate(addon); + if (!valid) { + throw new Error(JSON.stringify(validate.errors)); + } + } +}); + add_task(async function testAboutPage() { const cachedAddons = JSON.stringify(CACHED_ADDONS); + await SpecialPowers.pushPrefEnv({ set: [ - [PREF_CACHED_ADDONS, cachedAddons], - [PREF_TEST_ADDON_INSTALLED, false], + [PREF_TEST_CACHED_ADDONS, cachedAddons], + [PREF_TEST_ADDONS, "[]"], ], clear: [[PREF_PIONEER_ID, ""]], }); await BrowserTestUtils.withNewTab( { url: "about:pioneer", gBrowser, @@ -174,24 +224,34 @@ add_task(async function testAboutPage() `${addonId}-join-button` ); if (cachedAddon.isDefault) { ok(!joinButton, "There is no join button for default study."); continue; } - ok(!joinButton.hidden, "Join button is not hidden."); - - Services.prefs.setBoolPref(PREF_TEST_ADDON_INSTALLED, true); + ok(!joinButton.disabled, "After enrollment, join button is enabled."); + ok(!joinButton.hidden, "After enrollment, join button is not hidden."); + for (const testAddon of TEST_ADDONS) { + if (testAddon.id == addonId) { + Services.prefs.setStringPref( + PREF_TEST_ADDONS, + JSON.stringify([testAddon]) + ); + } + } joinButton.click(); await waitForAnimationFrame(); - ok(joinButton.hidden, "Join button is hidden."); + ok( + Services.prefs.getStringPref(PREF_TEST_ADDONS, null) == "[]", + "Correct add-on was uninstalled" + ); } enrollmentButton.click(); await waitForAnimationFrame(); const pioneerUnenrolled = Services.prefs.getStringPref( PREF_PIONEER_ID, @@ -200,34 +260,37 @@ add_task(async function testAboutPage() ok(!pioneerUnenrolled, "after unenrollment, Pioneer pref is null."); const unenrolledToolbarButton = document.getElementById("pioneer-button"); ok( unenrolledToolbarButton.hidden, "after unenrollment, Pioneer toolbar button is hidden." ); + Services.prefs.setStringPref(PREF_TEST_ADDONS, "[]"); for (const cachedAddon of CACHED_ADDONS) { const addonId = cachedAddon.addon_id; const joinButton = content.document.getElementById( `${addonId}-join-button` ); - Services.prefs.setBoolPref(PREF_TEST_ADDON_INSTALLED, false); if (cachedAddon.isDefault) { ok(!joinButton, "There is no join button for default study."); } else { ok( joinButton.disabled, "After unenrollment, join button is disabled." ); ok( !joinButton.hidden, "After unenrollment, join button is not hidden." ); + + joinButton.click(); + await waitForAnimationFrame(); } } } ); }); add_task(async function testPioneerBadge() { await SpecialPowers.pushPrefEnv({