Commit e690d9da authored by Robert Helmer's avatar Robert Helmer
Browse files

Bug 1640306 - add about:pioneer MVP r=mstriemer,fluent-reviewers,flod

Differential Revision: https://phabricator.services.mozilla.com/D76935
parent 75b624f4
......@@ -277,6 +277,7 @@ toolbar[customizing] > .overflow-button {
display: none;
}
toolbar[customizing] #pioneer-button,
toolbar[customizing] #whats-new-menu-button {
display: none;
}
......
......@@ -1156,6 +1156,12 @@
<toolbaritem id="PanelUI-button"
removable="false">
<toolbarbutton id="pioneer-button"
class="toolbarbutton-1"
hidden="true"
badged="true"
onmousedown="switchToTabHavingURI('about:pioneer', true);"
onkeypress="switchToTabHavingURI('about:pioneer', true);"/>
<toolbarbutton id="whats-new-menu-button"
class="toolbarbutton-1"
hidden="true"
......
......@@ -2099,6 +2099,38 @@ BrowserGlue.prototype = {
_checkHTTPSOnlyPref();
},
_monitorPioneerPref() {
const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId";
const _checkPioneerPref = async () => {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
win.document.getElementById(
"pioneer-button"
).hidden = !Services.prefs.getStringPref(PREF_PIONEER_ID, null);
}
};
const windowListener = {
onOpenWindow(xulWindow) {
const win = xulWindow.docShell.domWindow;
win.addEventListener("load", () => {
const pioneerButton = win.document.getElementById("pioneer-button");
if (pioneerButton) {
pioneerButton.hidden = !Services.prefs.getStringPref(
PREF_PIONEER_ID,
null
);
}
});
},
onCloseWindow() {},
};
Services.prefs.addObserver(PREF_PIONEER_ID, _checkPioneerPref);
Services.wm.addListener(windowListener);
_checkPioneerPref();
},
_showNewInstallModal() {
// Allow other observers of the same topic to run while we open the dialog.
Services.tm.dispatchToMainThread(() => {
......@@ -2187,6 +2219,7 @@ BrowserGlue.prototype = {
this._monitorScreenshotsPref();
this._monitorWebcompatReporterPref();
this._monitorHTTPSOnlyPref();
this._monitorPioneerPref();
let pService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
Ci.nsIToolkitProfileService
......
......@@ -118,6 +118,8 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS},
{"pioneer", "chrome://browser/content/pioneer.html",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
};
static nsAutoCString GetAboutModuleName(nsIURI* aURI) {
......
......@@ -14,6 +14,7 @@ pages = [
'logins',
'newinstall',
'newtab',
'pioneer',
'pocket-saved',
'pocket-signup',
'policies',
......
......@@ -42,6 +42,7 @@ DIRS += [
'migration',
'newtab',
'originattributes',
'pioneer',
'places',
'pocket',
'preferences',
......
/* 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/. */
body {
margin: 70px auto;
max-width: 664px;
}
@media (max-width: 830px) {
body {
margin-inline-start: 16px;
}
}
details > summary {
user-select: none;
padding: 2px 6px;
width: 18em;
cursor: pointer;
outline: none;
}
#report-title {
font-size: 1.46em;
font-weight: 300;
line-height: 1.3em;
}
#available-studies {
font-weight: 600;
}
#enrollment-button {
float: right;
}
.card {
display: flex;
flex-wrap: wrap;
}
.card-icon {
width: 32px;
height: 32px;
flex-shrink: 0;
}
.card-body {
flex-grow: 1;
margin-inline-start: 16px;
}
.card-name {
margin: 0;
font-size: 16px;
font-weight: 600;
line-height: 1;
}
.card-creator {
margin: 0;
font-size: 14px;
font-weight: 400;
}
.card-actions {
align-self: center;
flex-shrink: 0;
}
.join-button {
max-width: 200px;
margin: 0;
margin-inline-end: 16px;
}
.card-description {
font-size: 14px;
font-weight: normal;
}
*[hidden] {
display: none !important;
}
\ No newline at end of file
# 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/.
### This file is not in a locales directory to prevent it from
### being translated as the feature is still in heavy development
### and strings are likely to change often.
-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-enrollment-button = Enroll
pioneer-unenrollment-button = Leave
pioneer-available-studies = Available Studies
pioneer-end-study = End Study
<!-- 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/. -->
<!DOCTYPE html>
<html>
<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">
<button id="enrollment-button" class="primary"></button>
<h1 id="report-title" data-l10n-id="pioneer-title"></h1>
<div>
<p data-l10n-id="pioneer-summary"></p>
<p><strong data-l10n-id="pioneer-voluntary-notice"></strong></p>
<p id="report-details" data-l10n-id="pioneer-details"></p>
</div>
<details>
<summary>What happens next?</summary>
<p><strong>If you agree</strong> to enroll:</p>
<ol>
<li>
<p>
This <img src="chrome://browser/skin/pioneer.svg" /> icon will appear
on the Firefox toolbar. The icon reminds you that you are a part
of Pioneer. You can reach your Pioneer settings at any time by
clicking this icon.
</p>
<p>
We’ll change a Firefox preference, too. This will tell us you are
willing to participate in Pioneer studies. Continue using Firefox
as you normally would.
<strong
>The Pioneer program will not actually collect any data until
you decide to join a study.</strong
>
</p>
</li>
<li>
<p>
Periodically, we will offer you opportunities to join studies. The
purpose of these studies will vary, but might focus on aspects of
Firefox or what you experience on the web.
</p>
<p>
Studies will collect different types of information about your
online behavior, possibly including what web pages you visit and
how you interact with them. Before you enroll in a study, you will
have access to information explaining the data being collected,
who will have access to use it, and the study’s purpose.
</p>
</li>
</ol>
</details>
<details>
<summary>Leaving a Study or Leaving Pioneer</summary>
<p>
You are welcome to leave any individual study or the Firefox Pioneer
program entirely at any time.
</p>
To leave the Firefox Pioneer program entirely:
<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>Click the “Leave" button above.</li>
<li>
Leaving the Pioneer program will immediately unenroll you from any
in-progress studies that you had already joined. When you leave
Pioneer, you will no longer receive offers to join new studies.
</li>
</ol>
<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.
</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>
Leaving an individual study will not affect opportunities to join future
studies.
</p>
</details>
<details>
<summary>Your Privacy</summary>
<p>
Mozilla is sensitive to the nature of the data it may collect in some
Pioneer studies. We are always working hard to keep your data private
and secure:
</p>
<ul>
<li>
We will not collect any data without first telling you what we
intend to collect and getting your permission to do so.
</li>
<li>
The <strong>data you submit is encrypted by Firefox</strong> and not
decrypted until it is in a secure datastore that is not connected to
the wider internet.
</li>
<li>
This data will be linked to a randomly generated ID we only use for
Pioneer. It is used to keep a specific user's data together on the
server and helps us improve our analysis. No data will be collected
from inside Private Browsing windows.
</li>
<li>
All proposed studies will be reviewed by a panel consisting of
privacy experts, internet researchers and security engineers to
ensure that the data being collected is appropriate and respects
participant privacy.
</li>
<li>
Individual level raw data will never be shared publicly or sold.
</li>
<li>
Only a small number of researchers will have access to a study’s raw
data. If Mozilla shares aggregate information from studies, we will
disclose it in a way that minimizes the risk of participants being
identified.
</li>
<li>
In the future, Mozilla may pursue commercial opportunities that
advance our mission or create more opportunities for competition on
the internet using aggregated, non-personally identifiable data
collected during the Pioneer program studies. We will disclose our
commercial intent ahead of data collection. Mozilla will never sell
your personally identifiable information.
</li>
</ul>
</details>
<h2 id="header-available-studies"></h2>
<div id="available-studies"></div>
</body>
</html>
/* 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/. */
/**
* MVP UI for Pioneer v2
*
* This is an experimental MVP for the Pioneer v2 project. It supports a
* single study add-on, and the only entry point is the `about:pioneer` page.
*
* The purpose of this page is to demonstrate a fully-working Pioneer study
* data collection pipeline, and also to potentially advertise to real users
* in order to observe enrollment behavior.
*
* Results from the above will inform the next version of Pioneer UI.
*/
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
FileUtils: "resource://gre/modules/FileUtils.jsm",
});
const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId";
// For the pilot program (Fx78), only allow a single add-on ID.
const STUDY_ADDON_WHITELIST = ["pioneer-v2-example@mozilla.org"];
function showEnrollmentStatus() {
const pioneerId = Services.prefs.getStringPref(PREF_PIONEER_ID, null);
const enrollmentButton = document.getElementById("enrollment-button");
document.l10n.setAttributes(
enrollmentButton,
`pioneer-${pioneerId ? "un" : ""}enrollment-button`
);
enrollmentButton.classList.toggle("primary", !pioneerId);
}
async function showAvailableStudies(cachedAddons) {
for (const cachedAddon of cachedAddons) {
if (!cachedAddon) {
console.error(
`about:pioneer - Study addon ID not found in AMO cache: ${studyAddonId}`
);
return;
}
const studyAddonId = cachedAddon.id;
const study = document.createElement("div");
study.setAttribute("id", studyAddonId);
study.setAttribute("class", "card card-no-hover");
if (cachedAddon.icons && 32 in cachedAddon.icons) {
const iconName = document.createElement("img");
iconName.setAttribute("class", "card-icon");
iconName.setAttribute("src", cachedAddon.icons[32]);
study.appendChild(iconName);
}
const studyBody = document.createElement("div");
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;
studyBody.appendChild(studyCreator);
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);
async function toggleEnrolled() {
let addon;
let install;
if (Cu.isInAutomation) {
if (
Services.prefs.getBoolPref(
"toolkit.pioneer.testAddonInstalled",
false
)
) {
addon = { uninstall() {} };
}
install = { install() {} };
} else {
addon = await AddonManager.getAddonByID(studyAddonId);
install = await AddonManager.getInstallForURL(
cachedAddon.sourceURI.spec
);
}
if (addon) {
await addon.uninstall();
} else {
joinBtn.disabled = true;
await install.install();
}
updateStudy(studyAddonId);
}
enrollStudyBtn.addEventListener("input", toggleEnrolled);
joinBtn.addEventListener("click", toggleEnrolled);
const availableStudies = document.getElementById("available-studies");
availableStudies.appendChild(study);
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)
) {
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;
enrollStudyBtn.disabled = false;
enrollStudyBtn.checked = !!addon;
joinBtn.disabled = false;
joinBtn.hidden = !!addon;
} else {
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)
.generateUUID()
.toString();
return str.substring(1, str.length - 1);
}
function setup() {
document
.getElementById("enrollment-button")
.addEventListener("click", async event => {
const pioneerId = Services.prefs.getStringPref(PREF_PIONEER_ID, null);
if (pioneerId) {
Services.prefs.clearUserPref(PREF_PIONEER_ID);
for (const studyAddonId of STUDY_ADDON_WHITELIST) {
const study = document.getElementById(studyAddonId);
if (study) {
const addon = await AddonManager.getAddonByID(studyAddonId);
if (addon) {
await addon.uninstall();
}
updateStudy(studyAddonId);
}
}
} else {
let uuid = generateUUID();
Services.prefs.setStringPref(PREF_PIONEER_ID, uuid);
for (const studyAddonId of STUDY_ADDON_WHITELIST) {
const study = document.getElementById(studyAddonId);
if (study) {
updateStudy(studyAddonId);
}