Skip to content
This repository has been archived by the owner on Oct 14, 2020. It is now read-only.

Commit

Permalink
Merge pull request #120 from motin/v5-init
Browse files Browse the repository at this point in the history
[develop] Start of version 5 - WebExtension Experiments
  • Loading branch information
gregglind authored Apr 3, 2018
2 parents 21a0503 + beda8c2 commit aa1f884
Show file tree
Hide file tree
Showing 67 changed files with 10,392 additions and 1,860 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

- run:
name: webpack and eslint
command: npm run dist
command: npm run build

- run:
name: Build .XPI
Expand Down
7 changes: 7 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# do not lint/format generated artifacts
dist/
package-lock.json
# makes sure that eslintrc.js gets linted/formatted
!.eslintrc.js
# don't lint/format package.json since npm install formats it differently by default
package.json
47 changes: 33 additions & 14 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
/* eslint-env node */

"use strict";

// All Mozilla specific rules and environments at:
// http://firefox-source-docs.mozilla.org/tools/lint/linters/eslint-plugin-mozilla.html

module.exports = {
env: {
"node": true
es6: true,
},
extends: [
"eslint:recommended",
// list of rules at: https://dxr.mozilla.org/mozilla-central/source/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
"plugin:mozilla/recommended",
],

plugins: [
"mozilla",
"json"
overrides: [
{
files: "src/**",
env: {
browser: true,
webextensions: true,
},
},
],

parserOptions: {
ecmaVersion: 8,
sourceType: "module",
ecmaFeatures: {
jsx: false,
experimentalObjectRestSpread: true,
},
},
plugins: ["json", "mozilla"],
root: true,
rules: {
"babel/new-cap": "off",
"comma-dangle": ["error", "always-multiline"],
"eqeqeq": "error",
"indent": ["warn", 2, {SwitchCase: 1}],
"mozilla/no-aArgs": "warn",
"mozilla/balanced-listeners": 0,
"mozilla/balanced-listeners": "off",
"comma-dangle": ["error", "always-multiline"],
eqeqeq: "error",
indent: ["warn", 2, { SwitchCase: 1 }],
"no-console": "warn",
"no-debugger": "warn",
"no-shadow": ["error"],
"no-var": "error",
"no-shadow": "error",
"no-unused-vars": "error",
"prefer-const": "warn",
"semi": ["error", "always"],
"require-jsdoc": "warn",
"prefer-spread": "error",
semi: ["error", "always"],
"valid-jsdoc": "warn",
"max-len": ["warn", 80],
},
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ node_modules
*.xpi
*.tgz
*.*.swp
dist/
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RUN apt-get update -y && \
apt-get install -y zip firefox xvfb nodejs xsel git ssh openbox && \
npm install -g [email protected]

ENV PATH="/shield-study-utils/node_modules/.bin:$PATH"
ENV PATH="/shield-study-addon-utils/node_modules/.bin:$PATH"
90 changes: 61 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,24 @@

[![Build Status](https://travis-ci.org/mozilla/shield-studies-addon-utils.svg?branch=master)](https://travis-ci.org/mozilla/shield-studies-addon-utils)

A Firefox JavaScript module to be bundled with shield study add-ons (as `StudyUtils.jsm`). Provides these capabilities:
APIs and tooling that allows add-on developers to build [Shield/Pioneer](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) ([Normandy](https://wiki.mozilla.org/Firefox/Shield#Normandy_-_User_Profile_Matching_and_Recipe_Deployment)) study add-ons efficiently.

1. **Suggest variation for a client** (Deterministically! i.e. based on a hash of non-PII user info, they will always get assigned to the same branch every time the study launches)
2. **Report study lifecycle data** using Telemetry
3. **Report feature interaction and success data** using Telemetry
4. **Registers/uregisters the study as an active experiment** (By annotating the Telemetry Environment, marking the user as special in the `main` ping).
5. **Validates schema for study config**
6. **Handles study endings** (endStudy method bundles lots of tasks in one, including appending survey URLs specified in Config.jsm with query strings/sending the user to a survey and uninstalling the add-on)

The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis.

Allows add-on developers to build [Shield Study](https://wiki.mozilla.org/Firefox/Shield/Shield_Studies) ([Normandy](https://wiki.mozilla.org/Firefox/Shield#Normandy_-_User_Profile_Matching_and_Recipe_Deployment)) compatible add-ons without having to think very much.

## What You are Building
## Overview

* You are building a [legacy add-on](https://developer.mozilla.org/Add-ons/Legacy_add_ons). To deploy these after 57, you will need the magic special signing.
* Shield study add-ons can not be based on Web Extensions [yet](https://github.com/mozilla/shield-studies-addon-utils/issues/45).
* Jetpack / addon-sdk is not at all supported since v4 of this utils library.
* `webExtensionApis` - Firefox WebExtension Experiments APIs providing capabilities for study add-ons that are yet not available in the built-in WebExtension APIs
* `testUtils` - Test utils (helper classes to write functional/unit tests for your study add-on)
* `examples` - Tested and verified example add-ons using the WebExtension Experiments APIs and test utils

## Get started

Check out [mozilla/shield-studies-addon-template/](https://github.com/mozilla/shield-studies-addon-template/) to get started with an example study where shield-studies-addon-utils is already installed and configured.

## Installing the `StudyUtils.jsm` in your add-on
## Installing the utils in your add-on

```
npm install --save-dev shield-studies-addon-utils
npm install --save shield-studies-addon-utils
```

Copy `dist/StudyUtils.jsm` to your `addon` source directory, where it will be zipped up.

## Engineering and Process

* [Shield article on Mozilla Wiki](https://wiki.mozilla.org/Firefox/Shield)
Expand All @@ -46,24 +33,69 @@ Copy `dist/StudyUtils.jsm` to your `addon` source directory, where it will be zi
* [Long, rambling engineering docs](./docs/engineering.md)
* Come to slack: #shield

## WebExtension APIs

### `browser.study.*`

Provides these capabilities:

1. **Suggest variation for a client** (Deterministically! i.e. based on a hash of non-PII user info, they will always get assigned to the same branch every time the study launches)
2. **Report study lifecycle data** using Telemetry
3. **Report feature interaction and success data** using Telemetry
4. **Registers/unregisters the study as an active experiment** (By annotating the Telemetry Environment, marking the user as special in the `main` ping).
5. **Validates schema for study config**
6. **Handles study endings** (endStudy method bundles lots of tasks in one, including appending survey URLs specified in Config.jsm with query strings/sending the user to a survey and uninstalling the add-on)

To use, copy `webExtensionApis/study/api.js` and `webExtensionApis/study/schema.json` to your add-on's source directory under `privileged/study`, then add-the following to your add-on's manifest.json:

```
"experiment_apis": {
"study": {
"schema": "./privileged/study/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "./privileged/study/api.js",
"paths": [["study"]]
}
}
},
```

#### Data processing pipelines

Depending on which data processing pipeline the study add-on is configured to use, the pings end up in different destinations:

* `shield-parquet` - The pings end up in the `shield-study` and `shield-study-addon` Telemetry buckets for faster analysis.
* `pioneer` - The pings are encrypted and end up in the Pioneer processing pipeline
* `custom-telemetry-events` - The pings end up in the ordinary destination for custom telemetry events

To use, copy and adjust the files as per the `study` API above.

### `browser.prefs.*`

Allows your web extension add-on to set and read preferences.

## What You are Building

* You are building . To deploy these after 57, you will need the magic special signing.
* Shield study add-ons can not be based on Web Extensions [yet](https://github.com/mozilla/shield-studies-addon-utils/issues/45).

## Gotchas, Opinions, Side Effects, and Misfeatures

1. No handling of 'timers'. No saved state at all (including the variation name), unless you handle it yourself.

2. No 'running' pings in v4 (yet).

3. User disable also uninstalls (and cleans up).

## Development on the Utils

* open an issue
* hack and file a PR
* Open an issue
* Hack and file a PR

## History of major versions

* v5: (In development) API exposed as a Web Extension Experiment
* v5: (In development) API exposed as a Web Extension Experiment. Minimal viable add-on example added. Test coverage improved. Test utils added.
* v4.1: Improved utils for common cases
* v4: First `.jsm` release. Uses packet format for PACKET version 3.
* v4: First `.jsm` release for shipping studies as [legacy add-ons](https://developer.mozilla.org/Add-ons/Legacy_add_ons). Used packet format for PACKET version 3. (Jetpack / addon-sdk is not at all supported since v4 of this utils library)
* v3: Attempt to formalize on `shield-study` PACKET version 3. Jetpack based. Prototype used for `raymak/page-reload`. All work abandoned, and no formal npm release in this series. Work done at `v3-shield-packet-format` branch. LAST JETPACK (addon-sdk) RELEASE.
* v2: Code refactor to es6 `class` with event models. Added cli tooling. Packet format is still arbitrary and per-study. Jetpack based. Last used in studies in Q2 2017.
* v1: Initial work and thinking. Telemetry packets are rather arbitrary. Jetpack based.
Expand All @@ -72,6 +104,6 @@ Copy `dist/StudyUtils.jsm` to your `addon` source directory, where it will be zi

Repositories that should not be used as templates for new studies:

<https://github.com/gregglind/template-shield-study> - The incubation repo for the updated structure and contents of this repo, ported to the official template in late 2017.
<https://github.com/benmiroglio/shield-study-embedded-webextension-hello-world-example> - A repository that was created in 2017 to help new Shield/Pioneer engineers to quickly get up and running with a Shield add-on, built upon an older and much more verbose add-on template. It's documentation has been ported to the official template repo.
<https://github.com/johngruen/shield-template> - Despite its name, this repo is for static amo consent pages and does not contain any template for Shield studies
* <https://github.com/gregglind/template-shield-study> - The incubation repo for the updated structure and contents of the template repo, ported to the official template in late 2017.
* <https://github.com/benmiroglio/shield-study-embedded-webextension-hello-world-example> - A repository that was created in 2017 to help new Shield/Pioneer engineers to quickly get up and running with a Shield add-on, built upon an older and much more verbose add-on template. It's documentation has been ported to the official template repo.
* <https://github.com/johngruen/shield-template> - Despite its name, this repo is for static amo consent pages and does not contain any template for Shield studies
26 changes: 0 additions & 26 deletions bin/make_xpi.sh

This file was deleted.

22 changes: 22 additions & 0 deletions examples/test-addon/bin/bundle-shield-studies-addon-utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# fail on any error
set -o errexit

# always run from the repository root directory
script_path=`dirname $0`
cd "$script_path/../../../"

# paths
WEBEXTAPIS_PATH="webExtensionApis"
ADDON_SRC_PATH="examples/test-addon/src"

# bundle the study web extension experiment
mkdir -p $ADDON_SRC_PATH/privileged/study
cp $WEBEXTAPIS_PATH/study/api.js $ADDON_SRC_PATH/privileged/study/api.js
cp $WEBEXTAPIS_PATH/study/schema.json $ADDON_SRC_PATH/privileged/study/schema.json

# bundle the prefs web extension experiment
mkdir -p $ADDON_SRC_PATH/privileged/prefs
cp $WEBEXTAPIS_PATH/prefs/api.js $ADDON_SRC_PATH/privileged/prefs/api.js
cp $WEBEXTAPIS_PATH/prefs/schema.json $ADDON_SRC_PATH/privileged/prefs/schema.json
2 changes: 2 additions & 0 deletions examples/test-addon/dist/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!*.gitignore
84 changes: 84 additions & 0 deletions examples/test-addon/src/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint no-console:off */
/* global studySetup */

"use strict";

class Study {
constructor(variation) {}

// Will run only during first install attempt
// Use web extension experiments to get whatever prefs, add-ons,
// telemetry, anything necessary for the check
static async isEligible() {
//browser.prefs.get('my.favorite.pref');
return true;
}

// Expiration checks should be implemented in a very reliable way by
// the add-on since Normandy does not handle study expiration in a reliable manner
static async hasExpired() {
return false;
}
}

/**
* Fired when the extension is first installed, when the extension is updated
* to a new version, and when the browser is updated to a new version.
* @param details
*/
function handleInstalled(details) {
console.log(
"The 'handleInstalled' event was fired.",
details.reason,
details,
);
}

/**
* Fired when a profile that has this extension installed first starts up.
* This event is not fired when a private browsing/incognito profile is started.
*/
async function handleStartup() {
console.log("The 'handleStartup' event was fired.", arguments);
}

// todo: on shutdown
// Run shutdown-related non-privileged code

browser.runtime.onStartup.addListener(handleStartup);
browser.runtime.onInstalled.addListener(handleInstalled);

async function initiateStudy() {
// Set dynamic study configuration flags
studySetup.eligible = await Study.isEligible();
studySetup.expired = await Study.hasExpired();
// Ensure we have configured study and are supposed to run our feature
await browser.study.configure(studySetup);
// Run the startup study checks
await browser.study.startup();
// Read the active study variation
const { variation } = await browser.study.info();
// Initiate our study-specific background logic
new Study(variation);
}

// Since this is a test-addon, we don't initiate any code directly, but wait
// for events sent by tests. This allows us to control and test the execution
// properly.
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log("request", request);
if (request === "test:initiateStudy") {
initiateStudy();
}
});

// The tests that probe the web extensions APIs directly rely on an extension
// page opening up in a new window/tab.
// For more information, see shield-studies-addon-utils/testUtils/executeJs.js
const createData = {
type: "detached_panel",
url: "extension-page-for-tests/index.html",
width: 500,
height: 500,
};
const creating = browser.windows.create(createData);
31 changes: 31 additions & 0 deletions examples/test-addon/src/extension-page-for-tests/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="chrome://browser/content/extension.css">
<style>

body {
margin: 1em;
}

</style>
</head>
<body>

<h1>Shield Study Utils Test Add-on</h1>
<p>This is an extension page for shield-studies-addon-utils/test-addon</p>

<p>This add-on initiates no background logic on it's own, so that the tests
can test each lifecycle event in isolation.</p>

<p>For manual testing, use the buttons below to run the code
corresponding to each lifecycle event.</p>

<p>
<button id="initiateStudy-button" class="button-style">initiateStudy</button>
</p>

<script src="./page.js"></script>
</body>
</html>
Loading

0 comments on commit aa1f884

Please sign in to comment.