Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#750 - Add about:studies #801

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions recipe-client-addon/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ this.shutdown = function(data, reason) {
// modules are not unloaded accidentally during tests.
const log = LogManager.getLogger("bootstrap");
const modules = [
"lib/AboutStudiesProtocol.jsm",
"lib/ActionSandboxManager.jsm",
"lib/CleanupManager.jsm",
"lib/ClientEnvironment.jsm",
Expand Down
11 changes: 11 additions & 0 deletions recipe-client-addon/lib/AboutStudies.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>about:studies</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
</head>
<body id="about-studies-page">
<h1>about:studies</h1>
<p>Hello world!</p>
</body>
</html>
111 changes: 111 additions & 0 deletions recipe-client-addon/lib/AboutStudiesProtocol.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* 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";

const {utils: Cu, interfaces: Ci, manager: Cm, results: Cr} = Components;
Cm.QueryInterface(Ci.nsIComponentRegistrar);

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.EXPORTED_SYMBOLS = ["AboutStudiesProtocol"];


/**
* Required data for registering a protocol handler. This data is referred to
* when creating the new channel, as well as actually registering the
* component factory.
*/
const protocolInfo = {
// The file/destination for the protocol.
uri: Services.io.newURI("resource://shield-recipe-client/lib/AboutStudies.html"),
// Other properties are used internally by the protocol handler.
classDescription: "about:studies page module",
classID: Components.ID("c7c3dd48-c1cf-4bbf-a5df-69eaf6cb27d9"),
contractID: "@mozilla.org/network/protocol/about;1?what=studies",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStudiesProtocolHandler]),
};

/**
* Component definition for the about:studies protocol handler.
* Registers a component with the browser that establishes an `about:studies`
* protocol handler. Navigating to `about:studies` displays `AboutStudies.html`.
*/
class StudiesProtocolHandler {
newChannel(uri) {
let chan;
try {
chan = Services.io.newChannelFromURI2(
protocolInfo.uri,
null,
Services.scriptSecurityManager.getSystemPrincipal(),
null,
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_DOCUMENT
);
} catch (ex) {
throw new Error(`Error creating about:studies protocol - ${ex}`);
}

return chan;
}

// Required by the protocol handler, despite not doing anything.
getURIFlags() {}
}


/**
* Protocol-handling manager. Exposes functions to un/register the protocol
* handler, effectively enabling or disabling the ability for users to navigate
* to `about:studies`.
*/
const AboutStudiesProtocol = {
instance: null,

/**
* Enable the `about:studies` protocol handler.
*/
register() {
// We only need to register the component once.
if (this.instance) {
return;
}

// Component factory definition for the protocol handler,
// required for Cm.registerFactory.
const protocolFactory = {
createInstance(outer) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return new StudiesProtocolHandler();
}
};

const {
classID,
classDescription,
contractID
} = protocolInfo;

// Actually register the component (and therefor protocol) with the browser.
Cm.registerFactory(classID, classDescription, contractID, protocolFactory);

// Save the registered factory's information, to unregister later.
this.instance = protocolFactory;
},

/**
* Unregister component, disabling the `about:studies` handler.
*/
unregister() {
if (this.instance) {
const {classID} = protocolInfo;
Cm.unregisterFactory(classID, this.instance);
}

this.instance = null;
}
};
7 changes: 7 additions & 0 deletions recipe-client-addon/lib/ShieldRecipeClient.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "CleanupManager",
"resource://shield-recipe-client/lib/CleanupManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PreferenceExperiments",
"resource://shield-recipe-client/lib/PreferenceExperiments.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutStudiesProtocol",
"resource://shield-recipe-client/lib/AboutStudiesProtocol.jsm");

this.EXPORTED_SYMBOLS = ["ShieldRecipeClient"];

Expand Down Expand Up @@ -82,6 +84,9 @@ this.ShieldRecipeClient = {
}

await RecipeRunner.init();

// Enable the about:studies page
AboutStudiesProtocol.register();
},

shutdown(reason) {
Expand All @@ -91,6 +96,8 @@ this.ShieldRecipeClient = {
if (reason === REASONS.ADDON_DISABLE || reason === REASONS.ADDON_UNINSTALL) {
Services.prefs.setBoolPref(PREF_SELF_SUPPORT_ENABLED, true);
}

AboutStudiesProtocol.unregister();
},

setDefaultPrefs() {
Expand Down
2 changes: 1 addition & 1 deletion recipe-client-addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": "MPL-2.0",
"devDependencies": {
"babel-eslint": "7.2.3",
"eslint": "^3.19.0",
"eslint": "3.19.0",
"eslint-config-normandy": "1.0.0",
"eslint-plugin-babel": "4.1.1",
"eslint-plugin-mozilla": "0.3.2"
Expand Down
1 change: 1 addition & 0 deletions recipe-client-addon/test/browser/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ support-files =
[browser_ClientEnvironment.js]
[browser_ShieldRecipeClient.js]
[browser_PreferenceExperiments.js]
[browser_AboutStudiesProtocol.js]
64 changes: 64 additions & 0 deletions recipe-client-addon/test/browser/browser_AboutStudiesProtocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use strict";

Cu.import("resource://shield-recipe-client/lib/SandboxManager.jsm", this);
Cu.import("resource://shield-recipe-client/lib/AboutStudiesProtocol.jsm", this);

const sandboxManager = new SandboxManager();
sandboxManager.addHold("test running");

// Test un/register for basic errors.
add_task(async function() {
let passed = true;
try {
AboutStudiesProtocol.register();
AboutStudiesProtocol.unregister();
} catch (e) {
passed = false;
}
Assert.ok(passed, "Did not throw when un/registering about:studies");
});

// Test 'one-sided' protocol unregistration. Determines if the protocol throws an
// error if it is asked to unregister while not already registered.
add_task(async function() {
let passed = true;
try {
AboutStudiesProtocol.unregister();
} catch (e) {
passed = false;
}
Assert.ok(passed, "Did not throw when removing a non-registered about:studies protocol");
});

// Test to determine the `about:studies` page is actually loaded and displayed in
// the browser.
add_task(async function() {
AboutStudiesProtocol.register();

await BrowserTestUtils.withNewTab("about:studies", (tab) => {
// Grab the HTMLDocument from the tab's XULDocument. This allows us to
// perform basic DOM searches in the tab to determine if the content matches
// what we expect.
const dom = tab.contentDocument;
const aboutPage = dom.querySelector("#about-studies-page");

// About page should exist, and the content `Hello world!` should be present.
Assert.ok(!!aboutPage, "Found about:studies page");
Assert.equal(aboutPage.querySelector("h1").textContent, "about:studies", "Correct header content.");
Assert.equal(aboutPage.querySelector("p").textContent, "Hello world!", "Correct text content.");
});

AboutStudiesProtocol.unregister();
});

// Cleanup
add_task(async function() {
// Unregister the protocol, just in case.
AboutStudiesProtocol.unregister();

// Make sure the sandbox is clean.
sandboxManager.removeHold("test running");
await sandboxManager.isNuked()
.then(() => ok(true, "sandbox is nuked"))
.catch(e => ok(false, "sandbox is nuked", e));
});
2 changes: 1 addition & 1 deletion recipe-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"babel-plugin-transform-object-rest-spread": "6.23.0",
"babel-plugin-transform-runtime": "6.15.0",
"babel-preset-react": "6.11.1",
"babili-webpack-plugin": "^0.1.1",
"babili-webpack-plugin": "0.1.1",
"css-loader": "0.24.0",
"enzyme": "2.4.1",
"eslint": "3.19.0",
Expand Down