summaryrefslogtreecommitdiffstats
path: root/b2g/components/test
diff options
context:
space:
mode:
Diffstat (limited to 'b2g/components/test')
-rw-r--r--b2g/components/test/mochitest/SandboxPromptTest.html57
-rw-r--r--b2g/components/test/mochitest/filepicker_path_handler_chrome.js31
-rw-r--r--b2g/components/test/mochitest/mochitest.ini28
-rw-r--r--b2g/components/test/mochitest/permission_handler_chrome.js36
-rw-r--r--b2g/components/test/mochitest/presentation_prompt_handler_chrome.js94
-rw-r--r--b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js32
-rw-r--r--b2g/components/test/mochitest/screenshot_helper.js40
-rw-r--r--b2g/components/test/mochitest/systemapp_helper.js173
-rw-r--r--b2g/components/test/mochitest/test_filepicker_path.html130
-rw-r--r--b2g/components/test/mochitest/test_permission_deny.html83
-rw-r--r--b2g/components/test/mochitest/test_permission_gum_remember.html170
-rw-r--r--b2g/components/test/mochitest/test_permission_visibilitychange.html57
-rw-r--r--b2g/components/test/mochitest/test_presentation_device_prompt.html145
-rw-r--r--b2g/components/test/mochitest/test_presentation_request_ui_glue.html105
-rw-r--r--b2g/components/test/mochitest/test_sandbox_permission.html104
-rw-r--r--b2g/components/test/mochitest/test_screenshot.html31
-rw-r--r--b2g/components/test/mochitest/test_systemapp.html31
-rw-r--r--b2g/components/test/moz.build8
-rw-r--r--b2g/components/test/unit/data/test_logger_filebin0 -> 4037 bytes
-rw-r--r--b2g/components/test/unit/head_identity.js159
-rw-r--r--b2g/components/test/unit/head_logshake_gonk.js58
-rw-r--r--b2g/components/test/unit/test_aboutserviceworkers.js142
-rw-r--r--b2g/components/test/unit/test_bug793310.js39
-rw-r--r--b2g/components/test/unit/test_bug832946.js18
-rw-r--r--b2g/components/test/unit/test_fxaccounts.js212
-rw-r--r--b2g/components/test/unit/test_logcapture.js13
-rw-r--r--b2g/components/test/unit/test_logcapture_gonk.js70
-rw-r--r--b2g/components/test/unit/test_logparser.js75
-rw-r--r--b2g/components/test/unit/test_logshake.js218
-rw-r--r--b2g/components/test/unit/test_logshake_gonk.js61
-rw-r--r--b2g/components/test/unit/test_logshake_gonk_compression.js76
-rw-r--r--b2g/components/test/unit/test_logshake_readLog_gonk.js65
-rw-r--r--b2g/components/test/unit/test_signintowebsite.js322
-rw-r--r--b2g/components/test/unit/xpcshell.ini49
34 files changed, 2932 insertions, 0 deletions
diff --git a/b2g/components/test/mochitest/SandboxPromptTest.html b/b2g/components/test/mochitest/SandboxPromptTest.html
new file mode 100644
index 000000000..54f5fdd48
--- /dev/null
+++ b/b2g/components/test/mochitest/SandboxPromptTest.html
@@ -0,0 +1,57 @@
+<html>
+<body>
+<script>
+
+var actions = [
+ {
+ permissions: ["video-capture"],
+ action: function() {
+ // invoke video-capture permission prompt
+ navigator.mozGetUserMedia({video: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["audio-capture", "video-capture"],
+ action: function() {
+ // invoke audio-capture + video-capture permission prompt
+ navigator.mozGetUserMedia({audio: true, video: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["audio-capture"],
+ action: function() {
+ // invoke audio-capture permission prompt
+ navigator.mozGetUserMedia({audio: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["geolocation"],
+ action: function() {
+ // invoke geolocation permission prompt
+ navigator.geolocation.getCurrentPosition(function (pos) {});
+ }
+ },
+ {
+ permissions: ["desktop-notification"],
+ action: function() {
+ // invoke desktop-notification prompt
+ Notification.requestPermission(function (perm) {});
+ }
+ },
+];
+
+// The requested permissions are specified in query string.
+var permissions = JSON.parse(decodeURIComponent(window.location.search.substring(1)));
+for (var i = 0; i < actions.length; i++) {
+ if(permissions.length === actions[i].permissions.length &&
+ permissions.every(function(permission) {
+ return actions[i].permissions.indexOf(permission) >= 0;
+ })) {
+ actions[i].action();
+ break;
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/filepicker_path_handler_chrome.js b/b2g/components/test/mochitest/filepicker_path_handler_chrome.js
new file mode 100644
index 000000000..a175746cb
--- /dev/null
+++ b/b2g/components/test/mochitest/filepicker_path_handler_chrome.js
@@ -0,0 +1,31 @@
+/* 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';
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// use ppmm to handle file-picker message.
+var ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
+ .getService(Ci.nsIMessageListenerManager);
+
+var pickResult = null;
+
+function processPickMessage(message) {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ // reply FilePicker's message
+ sender.sendAsyncMessage('file-picked', pickResult);
+ // notify caller
+ sendAsyncMessage('file-picked-posted', { type: 'file-picked-posted' });
+}
+
+function updatePickResult(result) {
+ pickResult = result;
+ sendAsyncMessage('pick-result-updated', { type: 'pick-result-updated' });
+}
+
+ppmm.addMessageListener('file-picker', processPickMessage);
+// use update-pick-result to change the expected pick result.
+addMessageListener('update-pick-result', updatePickResult);
diff --git a/b2g/components/test/mochitest/mochitest.ini b/b2g/components/test/mochitest/mochitest.ini
new file mode 100644
index 000000000..97df32ea2
--- /dev/null
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+support-files =
+ permission_handler_chrome.js
+ SandboxPromptTest.html
+ filepicker_path_handler_chrome.js
+ screenshot_helper.js
+ systemapp_helper.js
+ presentation_prompt_handler_chrome.js
+ presentation_ui_glue_handler_chrome.js
+
+[test_filepicker_path.html]
+skip-if = toolkit != "gonk"
+[test_permission_deny.html]
+skip-if = toolkit != "gonk"
+[test_permission_gum_remember.html]
+skip-if = true # Bug 1019572 - frequent timeouts
+[test_sandbox_permission.html]
+skip-if = toolkit != "gonk"
+[test_screenshot.html]
+skip-if = toolkit != "gonk"
+[test_systemapp.html]
+skip-if = toolkit != "gonk"
+[test_presentation_device_prompt.html]
+skip-if = toolkit != "gonk"
+[test_permission_visibilitychange.html]
+skip-if = toolkit != "gonk"
+[test_presentation_request_ui_glue.html]
+skip-if = toolkit != "gonk"
diff --git a/b2g/components/test/mochitest/permission_handler_chrome.js b/b2g/components/test/mochitest/permission_handler_chrome.js
new file mode 100644
index 000000000..9bf3e7819
--- /dev/null
+++ b/b2g/components/test/mochitest/permission_handler_chrome.js
@@ -0,0 +1,36 @@
+/* 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";
+
+function debug(str) {
+ dump("CHROME PERMISSON HANDLER -- " + str + "\n");
+}
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm");
+
+var eventHandler = function(evt) {
+ if (!evt.detail || evt.detail.type !== "permission-prompt") {
+ return;
+ }
+
+ sendAsyncMessage("permission-request", evt.detail);
+};
+
+SystemAppProxy.addEventListener("mozChromeEvent", eventHandler);
+
+// need to remove ChromeEvent listener after test finished.
+addMessageListener("teardown", function() {
+ SystemAppProxy.removeEventListener("mozChromeEvent", eventHandler);
+});
+
+addMessageListener("permission-response", function(detail) {
+ SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
+});
+
diff --git a/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js b/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
new file mode 100644
index 000000000..4407e58d2
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+ function debug(str) {
+ dump('presentation_prompt_handler_chrome: ' + str + '\n');
+ }
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const manager = Cc["@mozilla.org/presentation-device/manager;1"]
+ .getService(Ci.nsIPresentationDeviceManager);
+
+const prompt = Cc['@mozilla.org/presentation-device/prompt;1']
+ .getService(Ci.nsIPresentationDevicePrompt);
+
+function TestPresentationDevice(options) {
+ this.id = options.id;
+ this.name = options.name;
+ this.type = options.type;
+}
+
+TestPresentationDevice.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ establishSessionTransport: function() {
+ return null;
+ },
+};
+
+function TestPresentationRequest(options) {
+ this.origin = options.origin;
+ this.requestURL = options.requestURL;
+}
+
+TestPresentationRequest.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceRequest]),
+ select: function(device) {
+ let result = {
+ type: 'select',
+ device: {
+ id: device.id,
+ name: device.name,
+ type: device.type,
+ },
+ };
+ sendAsyncMessage('presentation-select-result', result);
+ },
+ cancel: function() {
+ let result = {
+ type: 'cancel',
+ };
+ sendAsyncMessage('presentation-select-result', result);
+ },
+};
+
+var testDevice = null;
+
+addMessageListener('setup', function(device_options) {
+ testDevice = new TestPresentationDevice(device_options);
+ manager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(testDevice);
+ sendAsyncMessage('setup-complete');
+});
+
+var eventHandler = function(evt) {
+ if (!evt.detail || evt.detail.type !== 'presentation-select-device') {
+ return;
+ }
+
+ sendAsyncMessage('presentation-select-device', evt.detail);
+};
+
+SystemAppProxy.addEventListener('mozChromeEvent', eventHandler);
+
+// need to remove ChromeEvent listener after test finished.
+addMessageListener('teardown', function() {
+ if (testDevice) {
+ manager.removeDevice(testDevice);
+ }
+ SystemAppProxy.removeEventListener('mozChromeEvent', eventHandler);
+});
+
+addMessageListener('trigger-device-prompt', function(request_options) {
+ let request = new TestPresentationRequest(request_options);
+ prompt.promptDeviceSelection(request);
+});
+
+addMessageListener('presentation-select-response', function(detail) {
+ SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
+});
+
diff --git a/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
new file mode 100644
index 000000000..fac16db6c
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
+ .createInstance(Ci.nsIPresentationRequestUIGlue);
+
+SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
+ if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
+ return;
+ }
+ sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
+});
+
+addMessageListener('trigger-ui-glue', function(aData) {
+ var promise = glue.sendRequest(aData.url, aData.sessionId);
+ promise.then(function(aFrame) {
+ sendAsyncMessage('iframe-resolved', aFrame);
+ }).catch(function() {
+ sendAsyncMessage('iframe-rejected');
+ });
+});
+
+addMessageListener('trigger-presentation-content-event', function(aDetail) {
+ SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', aDetail);
+});
diff --git a/b2g/components/test/mochitest/screenshot_helper.js b/b2g/components/test/mochitest/screenshot_helper.js
new file mode 100644
index 000000000..0320a14c1
--- /dev/null
+++ b/b2g/components/test/mochitest/screenshot_helper.js
@@ -0,0 +1,40 @@
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+Cu.importGlobalProperties(['File']);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+// Load a duplicated copy of the jsm to prevent messing with the currently running one
+var scope = {};
+Services.scriptloader.loadSubScript("resource://gre/modules/Screenshot.jsm", scope);
+const { Screenshot } = scope;
+
+var index = -1;
+function next() {
+ index++;
+ if (index >= steps.length) {
+ assert.ok(false, "Shouldn't get here!");
+ return;
+ }
+ try {
+ steps[index]();
+ } catch(ex) {
+ assert.ok(false, "Caught exception: " + ex);
+ }
+}
+
+var steps = [
+ function getScreenshot() {
+ let screenshot = Screenshot.get();
+ assert.ok(screenshot instanceof File,
+ "Screenshot.get() returns a File");
+ next();
+ },
+
+ function endOfTest() {
+ sendAsyncMessage("finish");
+ }
+];
+
+next();
diff --git a/b2g/components/test/mochitest/systemapp_helper.js b/b2g/components/test/mochitest/systemapp_helper.js
new file mode 100644
index 000000000..768b221fe
--- /dev/null
+++ b/b2g/components/test/mochitest/systemapp_helper.js
@@ -0,0 +1,173 @@
+var Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+// Load a duplicated copy of the jsm to prevent messing with the currently running one
+var scope = {};
+Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
+const { SystemAppProxy } = scope;
+
+var frame;
+var customEventTarget;
+
+var index = -1;
+function next() {
+ index++;
+ if (index >= steps.length) {
+ assert.ok(false, "Shouldn't get here!");
+ return;
+ }
+ try {
+ steps[index]();
+ } catch(ex) {
+ assert.ok(false, "Caught exception: " + ex);
+ }
+}
+
+// Listen for events received by the system app document
+// to ensure that we receive all of them, in an expected order and time
+var isLoaded = false;
+var isReady = false;
+var n = 0;
+function listener(event) {
+ if (!isLoaded) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+ n++;
+ if (n == 1) {
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "first");
+ } else if (n == 2) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "second");
+
+ next(); // call checkEventPendingBeforeLoad
+ } else if (n == 3) {
+ if (!isReady) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "third");
+ } else if (n == 4) {
+ if (!isReady) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "fourth");
+
+ next(); // call checkEventDispatching
+ } else if (n == 5) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "fifth");
+ } else if (n === 6) {
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "sixth");
+ } else if (n === 7) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "seventh");
+ assert.equal(event.target, customEventTarget);
+
+ next(); // call checkEventListening();
+ } else {
+ assert.ok(false, "Unexpected event of type " + event.type);
+ }
+}
+
+
+var steps = [
+ function earlyEvents() {
+ // Immediately try to send events
+ SystemAppProxy._sendCustomEvent("mozChromeEvent", { name: "first" }, true);
+ SystemAppProxy._sendCustomEvent("custom", { name: "second" }, true);
+ next();
+ },
+
+ function createFrame() {
+ // Create a fake system app frame
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let doc = win.document;
+ frame = doc.createElement("iframe");
+ doc.documentElement.appendChild(frame);
+
+ customEventTarget = frame.contentDocument.body;
+
+ // Ensure that events are correctly sent to the frame.
+ // `listener` is going to call next()
+ frame.contentWindow.addEventListener("mozChromeEvent", listener);
+ frame.contentWindow.addEventListener("custom", listener);
+
+ // Ensure that listener being registered before the system app is ready
+ // are correctly removed from the pending list
+ function removedListener() {
+ assert.ok(false, "Listener isn't correctly removed from the pending list");
+ }
+ SystemAppProxy.addEventListener("mozChromeEvent", removedListener);
+ SystemAppProxy.removeEventListener("mozChromeEvent", removedListener);
+
+ // Register it to the JSM
+ SystemAppProxy.registerFrame(frame);
+ assert.ok(true, "Frame created and registered");
+
+ frame.contentWindow.addEventListener("load", function onload() {
+ frame.contentWindow.removeEventListener("load", onload);
+ assert.ok(true, "Frame document loaded");
+
+ // Declare that the iframe is now loaded.
+ // That should dispatch early events
+ isLoaded = true;
+ SystemAppProxy.setIsLoaded();
+ assert.ok(true, "Frame declared as loaded");
+
+ let gotFrame = SystemAppProxy.getFrame();
+ assert.equal(gotFrame, frame, "getFrame returns the frame we passed");
+
+ // Once pending events are received,
+ // we will run checkEventDispatching from `listener` function
+ });
+
+ frame.setAttribute("src", "data:text/html,system app");
+ },
+
+ function checkEventPendingBeforeLoad() {
+ // Frame is loaded but not ready,
+ // these events should queue before the System app is ready.
+ SystemAppProxy._sendCustomEvent("custom", { name: "third" });
+ SystemAppProxy.dispatchEvent({ name: "fourth" });
+
+ isReady = true;
+ SystemAppProxy.setIsReady();
+ // Once this 4th event is received, we will run checkEventDispatching
+ },
+
+ function checkEventDispatching() {
+ // Send events after the iframe is ready,
+ // they should be dispatched right away
+ SystemAppProxy._sendCustomEvent("custom", { name: "fifth" });
+ SystemAppProxy.dispatchEvent({ name: "sixth" });
+ SystemAppProxy._sendCustomEvent("custom", { name: "seventh" }, false, customEventTarget);
+ // Once this 7th event is received, we will run checkEventListening
+ },
+
+ function checkEventListening() {
+ SystemAppProxy.addEventListener("mozContentEvent", function onContentEvent(event) {
+ assert.equal(event.detail.name, "first-content", "received a system app event");
+ SystemAppProxy.removeEventListener("mozContentEvent", onContentEvent);
+
+ next();
+ });
+ let win = frame.contentWindow;
+ win.dispatchEvent(new win.CustomEvent("mozContentEvent", { detail: {name: "first-content"} }));
+ },
+
+ function endOfTest() {
+ frame.remove();
+ sendAsyncMessage("finish");
+ }
+];
+
+next();
diff --git a/b2g/components/test/mochitest/test_filepicker_path.html b/b2g/components/test/mochitest/test_filepicker_path.html
new file mode 100644
index 000000000..92c00dc68
--- /dev/null
+++ b/b2g/components/test/mochitest/test_filepicker_path.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949944
+-->
+<head>
+<meta charset="utf-8">
+<title>Permission Prompt Test</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="processTestCase()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949944"> [B2G][Helix][Browser][Wallpaper] use new File([blob], filename) to return a blob with filename when picking</a>
+<script type="application/javascript">
+
+'use strict';
+
+var testCases = [
+ // case 1: returns blob with name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: 'test1.txt'
+ }
+ },
+ fileName: 'test1.txt' },
+ // case 2: returns blob without name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' })
+ }
+ },
+ fileName: 'blob.txt' },
+ // case 3: returns blob with full path name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: '/full/path/test3.txt'
+ }
+ },
+ fileName: 'test3.txt' },
+ // case 4: returns blob relative path name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: 'relative/path/test4.txt'
+ }
+ },
+ fileName: 'test4.txt' },
+ // case 5: returns file with name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new File(['1234567890'],
+ 'useless-name.txt',
+ { type: 'text/plain' }),
+ name: 'test5.txt'
+ }
+ },
+ fileName: 'test5.txt'},
+ // case 6: returns file without name. This case may fail because we
+ // need to make sure the File can be sent through
+ // sendAsyncMessage API.
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new File(['1234567890'],
+ 'test6.txt',
+ { type: 'text/plain' })
+ }
+ },
+ fileName: 'test6.txt'}
+];
+
+var chromeJS = SimpleTest.getTestFileURL('filepicker_path_handler_chrome.js');
+var chromeScript = SpecialPowers.loadChromeScript(chromeJS);
+var activeTestCase;
+
+chromeScript.addMessageListener('pick-result-updated', handleMessage);
+chromeScript.addMessageListener('file-picked-posted', handleMessage);
+
+// handle messages returned from chromeScript
+function handleMessage(data) {
+ var fileInput = document.getElementById('fileInput');
+ switch (data.type) {
+ case 'pick-result-updated':
+ fileInput.click();
+ break;
+ case 'file-picked-posted':
+ is(fileInput.value, activeTestCase.fileName,
+ 'File should be able to send through message.');
+ processTestCase();
+ break;
+ }
+}
+
+function processTestCase() {
+ if (!testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+ activeTestCase = testCases.shift();
+ var expectedResult = activeTestCase.pickedResult;
+ if (navigator.userAgent.indexOf('Windows') > -1 &&
+ expectedResult.result.name) {
+ // If we run at a window box, we need to translate the path from '/' to '\\'
+ var name = expectedResult.result.name;
+ name = name.replace('/', '\\');
+ // If the name is an absolute path, we need to prepend drive letter.
+ if (name.startsWith('\\')) {
+ name = 'C:' + name;
+ }
+ // update the expected name.
+ expectedResult.result.name = name
+ }
+ chromeScript.sendAsyncMessage('update-pick-result', expectedResult);
+}
+
+</script>
+<input type="file" id="fileInput">
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_deny.html b/b2g/components/test/mochitest/test_permission_deny.html
new file mode 100644
index 000000000..29c35bdec
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_deny.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=981113
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Deny Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981113">Test Permission Deny</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION;
+
+var gUrl = SimpleTest.getTestFileURL('permission_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+var gTests = [
+ {
+ 'video': true,
+ },
+ {
+ 'audio': true,
+ 'video': true,
+ },
+ {
+ 'audio': true,
+ },
+];
+
+function runNext() {
+ if (gTests.length > 0) {
+ // Put the requested permission in query string
+ let requestedType = gTests.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success() {
+ ok(false, 'unexpected success, permission request should be denied');
+ runNext();
+ }, function failure(err) {
+ is(err.name, 'SecurityError', 'expected permission denied');
+ runNext();
+ });
+ } else {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown', '');
+ gScript.destroy();
+ SimpleTest.finish();
+ }
+}
+
+gScript.addMessageListener('permission-request', function(detail) {
+ let response = {
+ id: detail.id,
+ type: 'permission-deny',
+ remember: false,
+ };
+ gScript.sendAsyncMessage('permission-response', response);
+});
+
+// Need to change camera permission from ALLOW to PROMPT, otherwise
+// MediaManager will automatically allow video-only gUM request.
+SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: PROMPT_ACTION, context: document},
+ {type: 'audio-capture', allow: PROMPT_ACTION, context: document},
+ {type: 'camera', allow: PROMPT_ACTION, context: document},
+ ], function() {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['media.navigator.permission.disabled', false],
+ ]
+ }, runNext);
+ }
+);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_gum_remember.html b/b2g/components/test/mochitest/test_permission_gum_remember.html
new file mode 100644
index 000000000..1ebfea1ca
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_gum_remember.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=978660
+-->
+<head>
+ <meta charset="utf-8">
+ <title>gUM Remember Permission Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978660">Test remembering gUM Permission</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION;
+
+var gUrl = SimpleTest.getTestFileURL('permission_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+gScript.addMessageListener('permission-request', function(detail) {
+ ok(false, 'unexpected mozChromeEvent for permission prompt');
+ let response = {
+ id: detail.id,
+ type: 'permission-deny',
+ remember: false,
+ };
+ gScript.sendAsyncMessage('permission-response', response);
+});
+
+var gTests = [
+ {
+ 'audio': true,
+ 'video': {facingMode: 'environment', required: ['facingMode']},
+ },
+ {
+ 'video': {facingMode: 'environment', required: ['facingMode']},
+ },
+ {
+ 'audio': true,
+ },
+];
+
+function testGranted() {
+ info('test remember permission granted');
+ return new Promise(function(resolve, reject) {
+ let steps = [].concat(gTests);
+ function nextStep() {
+ if (steps.length > 0) {
+ let requestedType = steps.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success(stream) {
+ ok(true, 'expected gUM success');
+ stream.stop();
+ nextStep();
+ }, function failure(err) {
+ ok(false, 'unexpected gUM fail: ' + err);
+ nextStep();
+ });
+ } else {
+ resolve();
+ }
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: true, context: document},
+ {type: 'audio-capture', allow: true, context: document},
+ ], nextStep);
+ });
+}
+
+function testDenied() {
+ info('test remember permission denied');
+ return new Promise(function(resolve, reject) {
+ let steps = [].concat(gTests);
+ function nextStep() {
+ if (steps.length > 0) {
+ let requestedType = steps.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ nextStep();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ nextStep();
+ });
+ } else {
+ resolve();
+ }
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: false, context: document},
+ {type: 'audio-capture', allow: false, context: document},
+ ], nextStep);
+ });
+}
+
+function testPartialDeniedAudio() {
+ info('test remember permission partial denied: audio');
+ return new Promise(function(resolve, reject) {
+ info('getUserMedia for video and audio');
+ function nextStep() {
+ navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+ audio: true}, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ resolve();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ resolve();
+ });
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: true, context: document},
+ {type: 'audio-capture', allow: false, context: document},
+ ], nextStep);
+ });
+}
+
+function testPartialDeniedVideo() {
+ info('test remember permission partial denied: video');
+ return new Promise(function(resolve, reject) {
+ info('getUserMedia for video and audio');
+ function nextStep() {
+ navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+ audio: true}, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ resolve();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ resolve();
+ });
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: false, context: document},
+ {type: 'audio-capture', allow: true, context: document},
+ ], nextStep);
+ });
+}
+
+function runTests() {
+ testGranted()
+ .then(testDenied)
+ .then(testPartialDeniedAudio)
+ .then(testPartialDeniedVideo)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown', '');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['media.navigator.permission.disabled', false],
+ ]
+}, runTests);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_visibilitychange.html b/b2g/components/test/mochitest/test_permission_visibilitychange.html
new file mode 100644
index 000000000..cd5694b42
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_visibilitychange.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=951997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Prompt Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020179">Permission prompt visibilitychange test</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("permission_handler_chrome.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testDone() {
+ gScript.sendAsyncMessage("teardown", "");
+ gScript.destroy();
+ SimpleTest.finish();
+ alert("setVisible::true");
+}
+
+function runTest() {
+ navigator.geolocation.getCurrentPosition(
+ function (pos) {
+ ok(false, "unexpected success, permission request should be canceled");
+ testDone();
+ }, function (err) {
+ ok(true, "success, permission request is canceled");
+ testDone();
+ });
+}
+
+gScript.addMessageListener("permission-request", function (detail) {
+ info("got permission-request!!!!\n");
+ alert("setVisible::false");
+});
+
+// Add permissions to this app. We use ALLOW_ACTION here. The ContentPermissionPrompt
+// should prompt for permission, not allow it without prompt.
+SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
+ function() {
+ SpecialPowers.addPermission("geolocation",
+ SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION, document);
+ runTest();
+ });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_presentation_device_prompt.html b/b2g/components/test/mochitest/test_presentation_device_prompt.html
new file mode 100644
index 000000000..9feeca795
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_device_prompt.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Presentation Device Selection</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation Device Selection</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var contentEventHandler = null;
+
+var gUrl = SimpleTest.getTestFileURL('presentation_prompt_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testSetup() {
+ info('setup for device selection');
+ return new Promise(function(resolve, reject) {
+ let device = {
+ id: 'test-id',
+ name: 'test-name',
+ type: 'test-type',
+ };
+ gScript.addMessageListener('setup-complete', function() {
+ resolve(device);
+ });
+ gScript.sendAsyncMessage('setup', device);
+ });
+}
+
+function testSelected(device) {
+ info('test device selected by user');
+ return new Promise(function(resolve, reject) {
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ is(detail.origin, request.origin, 'expected origin');
+ is(detail.requestURL, request.requestURL, 'expected requestURL');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-result',
+ deviceId: device.id,
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'select', 'expect device selected');
+ is(result.device.id, device.id, 'expected device id');
+ is(result.device.name, device.name, 'expected device name');
+ is(result.device.type, device.type, 'expected devcie type');
+ resolve();
+ });
+ });
+
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function testSelectedNotExisted() {
+ info('test selected device doesn\'t exist');
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-deny',
+ deviceId: undefined, // simulate device Id that doesn't exist
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'cancel', 'expect user cancel');
+ resolve();
+ });
+ });
+
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function testDenied() {
+ info('test denial of device selection');
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-deny',
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'cancel', 'expect user cancel');
+ resolve();
+ });
+ });
+
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function runTests() {
+ testSetup()
+ .then(testSelected)
+ .then(testSelectedNotExisted)
+ .then(testDenied)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_presentation_request_ui_glue.html b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
new file mode 100644
index 000000000..29ac37221
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Presentation UI Glue</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('presentation_ui_glue_handler_chrome.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var url = 'http://example.com';
+var sessionId = 'sessionId';
+
+function testLaunchReceiver() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+ gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+ ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+ is(aDetail.url, url, "Url should be the same.");
+ is(aDetail.id, sessionId, "Session ID should be the same.");
+
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-ui-glue',
+ { url: url,
+ sessionId : sessionId });
+ });
+}
+
+function testReceiverLaunched() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('iframe-resolved', function iframeResolvedHandler(aFrame) {
+ gScript.removeMessageListener('iframe-resolved', iframeResolvedHandler);
+ ok(true, "The promise should be resolved.");
+
+ aResolve();
+ });
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('remote', 'true');
+ iframe.setAttribute('mozbrowser', 'true');
+ iframe.setAttribute('src', 'http://example.com');
+ document.body.appendChild(iframe);
+
+ gScript.sendAsyncMessage('trigger-presentation-content-event',
+ { type: 'presentation-receiver-launched',
+ id: sessionId,
+ frame: iframe });
+ });
+}
+
+function testLaunchError() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+ gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+ ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+ is(aDetail.url, url, "Url should be the same.");
+ is(aDetail.id, sessionId, "Session ID should be the same.");
+
+ gScript.addMessageListener('iframe-rejected', function iframeRejectedHandler() {
+ gScript.removeMessageListener('iframe-rejected', iframeRejectedHandler);
+ ok(true, "The promise should be rejected.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-presentation-content-event',
+ { type: 'presentation-receiver-permission-denied',
+ id: sessionId });
+ });
+
+ gScript.sendAsyncMessage('trigger-ui-glue',
+ { url: url,
+ sessionId : sessionId });
+ });
+}
+
+function runTests() {
+ testLaunchReceiver()
+ .then(testReceiverLaunched)
+ .then(testLaunchError)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_sandbox_permission.html b/b2g/components/test/mochitest/test_sandbox_permission.html
new file mode 100644
index 000000000..cd13599a3
--- /dev/null
+++ b/b2g/components/test/mochitest/test_sandbox_permission.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=951997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Prompt Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=951997">Permission prompt web content test</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+const APP_URL = "SandboxPromptTest.html";
+
+var iframe;
+var gUrl = SimpleTest.getTestFileURL("permission_handler_chrome.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+var gResult = [
+ {
+ "video-capture": ["back"],
+ },
+ {
+ "audio-capture": [""],
+ "video-capture": ["back"],
+ },
+ {
+ "audio-capture": [""],
+ },
+ {
+ "geolocation": [],
+ },
+ {
+ "desktop-notification": [],
+ }
+];
+
+function runNext() {
+ if (gResult.length > 0) {
+ // Put the requested permission in query string
+ let requestedPermission = JSON.stringify(Object.keys(gResult[0]));
+ info('request permissions for ' + requestedPermission);
+ iframe.src = APP_URL + '?' + encodeURIComponent(requestedPermission);
+ } else {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage("teardown", "");
+ gScript.destroy();
+ SimpleTest.finish();
+ }
+}
+
+// Create a sanbox iframe.
+function loadBrowser() {
+ iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.src = 'about:blank';
+ document.body.appendChild(iframe);
+
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ runNext();
+ });
+}
+
+gScript.addMessageListener("permission-request", function (detail) {
+ let permissions = detail.permissions;
+ let expectedValue = gResult.shift();
+ let permissionTypes = Object.keys(permissions);
+
+ is(permissionTypes.length, Object.keys(expectedValue).length, "expected number of permissions");
+
+ for (let type of permissionTypes) {
+ ok(expectedValue.hasOwnProperty(type), "expected permission type");
+ for (let i in permissions[type]) {
+ is(permissions[type][i], expectedValue[type][i], "expected permission option");
+ }
+ }
+ runNext();
+});
+
+// Add permissions to this app. We use ALLOW_ACTION here. The ContentPermissionPrompt
+// should prompt for permission, not allow it without prompt.
+SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
+ function() {
+ SpecialPowers.addPermission('video-capture',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('audio-capture',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('geolocation',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('desktop-notification',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ loadBrowser();
+ });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_screenshot.html b/b2g/components/test/mochitest/test_screenshot.html
new file mode 100644
index 000000000..d2eeb8d48
--- /dev/null
+++ b/b2g/components/test/mochitest/test_screenshot.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1136784
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Screenshot Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136784">Screenshot.jsm</a>
+<script type="application/javascript">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("screenshot_helper.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+SimpleTest.waitForExplicitFinish();
+gScript.addMessageListener("finish", function () {
+ SimpleTest.ok(true, "chrome test script finished");
+ gScript.destroy();
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_systemapp.html b/b2g/components/test/mochitest/test_systemapp.html
new file mode 100644
index 000000000..450094a50
--- /dev/null
+++ b/b2g/components/test/mochitest/test_systemapp.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=963239
+-->
+<head>
+ <meta charset="utf-8">
+ <title>SystemAppProxy Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963239">SystemAppProxy.jsm</a>
+<script type="application/javascript">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("systemapp_helper.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+SimpleTest.waitForExplicitFinish();
+gScript.addMessageListener("finish", function () {
+ SimpleTest.ok(true, "chrome test script finished");
+ gScript.destroy();
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/moz.build b/b2g/components/test/moz.build
new file mode 100644
index 000000000..387e3b811
--- /dev/null
+++ b/b2g/components/test/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
diff --git a/b2g/components/test/unit/data/test_logger_file b/b2g/components/test/unit/data/test_logger_file
new file mode 100644
index 000000000..b1ed7f10a
--- /dev/null
+++ b/b2g/components/test/unit/data/test_logger_file
Binary files differ
diff --git a/b2g/components/test/unit/head_identity.js b/b2g/components/test/unit/head_identity.js
new file mode 100644
index 000000000..604a77284
--- /dev/null
+++ b/b2g/components/test/unit/head_identity.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+// The following boilerplate makes sure that XPCOM calls
+// that use the profile directory work.
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Logger",
+ "resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "uuidGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const TEST_URL = "https://myfavoriteflan.com";
+const TEST_USER = "uumellmahaye1969@hotmail.com";
+const TEST_PRIVKEY = "i-am-a-secret";
+const TEST_CERT = "i~like~pie";
+
+// The following are utility functions for Identity testing
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
+}
+
+function partial(fn) {
+ let args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
+ };
+}
+
+function uuid() {
+ return uuidGenerator.generateUUID().toString();
+}
+
+// create a mock "doc" object, which the Identity Service
+// uses as a pointer back into the doc object
+function mockDoc(aParams, aDoFunc) {
+ let mockedDoc = {};
+ mockedDoc.id = uuid();
+
+ // Properties of aParams may include loggedInUser
+ Object.keys(aParams).forEach(function(param) {
+ mockedDoc[param] = aParams[param];
+ });
+
+ // the origin is set inside nsDOMIdentity by looking at the
+ // document.nodePrincipal.origin. Here we, we must satisfy
+ // ourselves with pretending.
+ mockedDoc.origin = "https://jedp.gov";
+
+ mockedDoc['do'] = aDoFunc;
+ mockedDoc.doReady = partial(aDoFunc, 'ready');
+ mockedDoc.doLogin = partial(aDoFunc, 'login');
+ mockedDoc.doLogout = partial(aDoFunc, 'logout');
+ mockedDoc.doError = partial(aDoFunc, 'error');
+ mockedDoc.doCancel = partial(aDoFunc, 'cancel');
+ mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
+
+ return mockedDoc;
+}
+
+// create a mock "pipe" object that would normally communicate
+// messages up to gaia (either the trusty ui or the hidden iframe),
+// and convey messages back down from gaia to the controller through
+// the message callback.
+
+// The mock receiving pipe simulates gaia which, after receiving messages
+// through the pipe, will call back with instructions to invoke
+// certain methods. It mocks what comes back from the other end of
+// the pipe.
+function mockReceivingPipe() {
+ let MockedPipe = {
+ communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
+ switch (aGaiaOptions.message) {
+ case "identity-delegate-watch":
+ aMessageCallback({json: {method: "ready"}});
+ break;
+ case "identity-delegate-request":
+ aMessageCallback({json: {method: "login", assertion: TEST_CERT}});
+ break;
+ case "identity-delegate-logout":
+ aMessageCallback({json: {method: "logout"}});
+ break;
+ default:
+ throw("what the what?? " + aGaiaOptions.message);
+ break;
+ }
+ }
+ };
+ return MockedPipe;
+}
+
+// The mock sending pipe lets us test what's actually getting put in the
+// pipe.
+function mockSendingPipe(aMessageCallback) {
+ let MockedPipe = {
+ communicate: function(aRpOptions, aGaiaOptions, aDummyCallback) {
+ aMessageCallback(aRpOptions, aGaiaOptions);
+ }
+ };
+ return MockedPipe;
+}
+
+// mimicking callback funtionality for ease of testing
+// this observer auto-removes itself after the observe function
+// is called, so this is meant to observe only ONE event.
+function makeObserver(aObserveTopic, aObserveFunc) {
+ let observer = {
+ // nsISupports provides type management in C++
+ // nsIObserver is to be an observer
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic == aObserveTopic) {
+ Services.obs.removeObserver(observer, aObserveTopic);
+ aObserveFunc(aSubject, aTopic, aData);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, aObserveTopic, false);
+}
+
+// a hook to set up the ID service with an identity with keypair and all
+// when ready, invoke callback with the identity. It's there if we need it.
+function setup_test_identity(identity, cert, cb) {
+ cb();
+}
+
+// takes a list of functions and returns a function that
+// when called the first time, calls the first func,
+// then the next time the second, etc.
+function call_sequentially() {
+ let numCalls = 0;
+ let funcs = arguments;
+
+ return function() {
+ if (!funcs[numCalls]) {
+ let argString = Array.prototype.slice.call(arguments).join(",");
+ do_throw("Too many calls: " + argString);
+ return;
+ }
+ funcs[numCalls].apply(funcs[numCalls],arguments);
+ numCalls += 1;
+ };
+}
diff --git a/b2g/components/test/unit/head_logshake_gonk.js b/b2g/components/test/unit/head_logshake_gonk.js
new file mode 100644
index 000000000..e94234f1f
--- /dev/null
+++ b/b2g/components/test/unit/head_logshake_gonk.js
@@ -0,0 +1,58 @@
+/**
+ * Boostrap LogShake's tests that need gonk support.
+ * This is creating a fake sdcard for LogShake tests and importing LogShake and
+ * osfile
+ */
+
+/* jshint moz: true */
+/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ do_get_profile, OS, volumeService, equal, XPCOMUtils */
+/* exported setup_logshake_mocks */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+ "@mozilla.org/telephony/volume-service;1",
+ "nsIVolumeService");
+
+var sdcard;
+
+function setup_logshake_mocks() {
+ do_get_profile();
+ setup_fs();
+}
+
+function setup_fs() {
+ OS.File.makeDir("/data/local/tmp/sdcard/", {from: "/data"}).then(function() {
+ setup_sdcard();
+ });
+}
+
+function setup_sdcard() {
+ let volName = "sdcard";
+ let mountPoint = "/data/local/tmp/sdcard";
+ volumeService.createFakeVolume(volName, mountPoint);
+
+ let vol = volumeService.getVolumeByName(volName);
+ ok(vol, "volume shouldn't be null");
+ equal(volName, vol.name, "name");
+ equal(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
+
+ ensure_sdcard();
+}
+
+function ensure_sdcard() {
+ sdcard = volumeService.getVolumeByName("sdcard").mountPoint;
+ ok(sdcard, "Should have a valid sdcard mountpoint");
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/test_aboutserviceworkers.js b/b2g/components/test/unit/test_aboutserviceworkers.js
new file mode 100644
index 000000000..d1a7d41aa
--- /dev/null
+++ b/b2g/components/test/unit/test_aboutserviceworkers.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutServiceWorkers",
+ "resource://gre/modules/AboutServiceWorkers.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager");
+
+const CHROME_MSG = "mozAboutServiceWorkersChromeEvent";
+
+const ORIGINAL_SENDRESULT = AboutServiceWorkers.sendResult;
+const ORIGINAL_SENDERROR = AboutServiceWorkers.sendError;
+
+do_get_profile();
+
+var mockSendResult = (aId, aResult) => {
+ let msg = {
+ id: aId,
+ result: aResult
+ };
+ Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
+};
+
+var mockSendError = (aId, aError) => {
+ let msg = {
+ id: aId,
+ result: aError
+ };
+ Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
+};
+
+function attachMocks() {
+ AboutServiceWorkers.sendResult = mockSendResult;
+ AboutServiceWorkers.sendError = mockSendError;
+}
+
+function restoreMocks() {
+ AboutServiceWorkers.sendResult = ORIGINAL_SENDRESULT;
+ AboutServiceWorkers.sendError = ORIGINAL_SENDERROR;
+}
+
+do_register_cleanup(restoreMocks);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * "init" tests
+ */
+[
+// Pref disabled, no registrations
+{
+ prefEnabled: false,
+ expectedMessage: {
+ id: Date.now(),
+ result: {
+ enabled: false,
+ registrations: []
+ }
+ }
+},
+// Pref enabled, no registrations
+{
+ prefEnabled: true,
+ expectedMessage: {
+ id: Date.now(),
+ result: {
+ enabled: true,
+ registrations: []
+ }
+ }
+}].forEach(test => {
+ add_test(function() {
+ Services.prefs.setBoolPref("dom.serviceWorkers.enabled", test.prefEnabled);
+
+ let id = test.expectedMessage.id;
+
+ function onMessage(subject, topic, data) {
+ let message = subject.wrappedJSObject;
+ let expected = test.expectedMessage;
+
+ do_check_true(message.id, "Message should have id");
+ do_check_eq(message.id, test.expectedMessage.id,
+ "Id should be the expected one");
+ do_check_eq(message.result.enabled, expected.result.enabled,
+ "Pref should be disabled");
+ do_check_true(message.result.registrations, "Registrations should exist");
+ do_check_eq(message.result.registrations.length,
+ expected.result.registrations.length,
+ "Registrations length should be the expected one");
+
+ Services.obs.removeObserver(onMessage, CHROME_MSG);
+
+ run_next_test();
+ }
+
+ Services.obs.addObserver(onMessage, CHROME_MSG, false);
+
+ attachMocks();
+
+ AboutServiceWorkers.handleEvent({ detail: {
+ id: id,
+ name: "init"
+ }});
+ });
+});
+
+/**
+ * ServiceWorkerManager tests.
+ */
+
+// We cannot register a sw via ServiceWorkerManager cause chrome
+// registrations are not allowed.
+// All we can do for now is to test the interface of the swm.
+add_test(function test_swm() {
+ do_check_true(gServiceWorkerManager, "SWM exists");
+ do_check_true(gServiceWorkerManager.getAllRegistrations,
+ "SWM.getAllRegistrations exists");
+ do_check_true(typeof gServiceWorkerManager.getAllRegistrations == "function",
+ "SWM.getAllRegistrations is a function");
+ do_check_true(gServiceWorkerManager.propagateSoftUpdate,
+ "SWM.propagateSoftUpdate exists");
+ do_check_true(typeof gServiceWorkerManager.propagateSoftUpdate == "function",
+
+ "SWM.propagateSoftUpdate is a function");
+ do_check_true(gServiceWorkerManager.propagateUnregister,
+ "SWM.propagateUnregister exists");
+ do_check_true(typeof gServiceWorkerManager.propagateUnregister == "function",
+ "SWM.propagateUnregister exists");
+
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_bug793310.js b/b2g/components/test/unit/test_bug793310.js
new file mode 100644
index 000000000..2bdb8252e
--- /dev/null
+++ b/b2g/components/test/unit/test_bug793310.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ Components.utils.import("resource:///modules/TelURIParser.jsm")
+
+ // global-phone-number
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+1234'), '+1234');
+
+ // global-phone-number => white space separator
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+123 456 789'), '+123 456 789');
+
+ // global-phone-number => ignored chars
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+1234_123'), '+1234');
+
+ // global-phone-number => visualSeparator + digits
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+-.()1234567890'), '+-.()1234567890');
+
+ // local-phone-number
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:1234'), '1234');
+
+ // local-phone-number => visualSeparator + digits + dtmfDigits + pauseCharacter
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:-.()1234567890ABCDpw'), '-.()1234567890ABCDpw');
+
+ // local-phone-number => visualSeparator + digits + dtmfDigits + pauseCharacter + ignored chars
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:-.()1234567890ABCDpw_'), '-.()1234567890ABCDpw');
+
+ // local-phone-number => isdn-subaddress
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;isub=123'), '123');
+
+ // local-phone-number => post-dial
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;postd=123'), '123');
+
+ // local-phone-number => prefix
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;phone-context=+0321'), '+0321123');
+
+ // local-phone-number => isdn-subaddress + post-dial + prefix
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;isub=123;postd=123;phone-context=+0321'), '+0321123');
+}
diff --git a/b2g/components/test/unit/test_bug832946.js b/b2g/components/test/unit/test_bug832946.js
new file mode 100644
index 000000000..4ddbd4280
--- /dev/null
+++ b/b2g/components/test/unit/test_bug832946.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ Components.utils.import("resource:///modules/TelURIParser.jsm")
+
+ // blocked numbers
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234#'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234#'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*#*#7780#*#*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234AB'), null);
+
+ // white list
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234'), '*1234');
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234'), '#1234');
+}
diff --git a/b2g/components/test/unit/test_fxaccounts.js b/b2g/components/test/unit/test_fxaccounts.js
new file mode 100644
index 000000000..5de0d6565
--- /dev/null
+++ b/b2g/components/test/unit/test_fxaccounts.js
@@ -0,0 +1,212 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsMgmtService",
+ "resource://gre/modules/FxAccountsMgmtService.jsm",
+ "FxAccountsMgmtService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
+ "resource://gre/modules/FxAccountsManager.jsm");
+
+// At end of test, restore original state
+const ORIGINAL_AUTH_URI = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
+var { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm");
+const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
+do_register_cleanup(function() {
+ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
+ SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
+ Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration");
+});
+
+// Make profile available so that fxaccounts can store user data
+do_get_profile();
+
+// Mock the system app proxy; make message passing possible
+var mockSendCustomEvent = function(aEventName, aMsg) {
+ Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
+};
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test_overall() {
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ do_check_neq(FxAccountsMgmtService, null);
+});
+
+// Check that invalid email capitalization is corrected on signIn.
+// https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountlogin
+add_test(function test_invalidEmailCase_signIn() {
+ do_test_pending();
+ let clientEmail = "greta.garbo@gmail.com";
+ let canonicalEmail = "Greta.Garbo@gmail.COM";
+ let attempts = 0;
+
+ function writeResp(response, msg) {
+ if (typeof msg === "object") {
+ msg = JSON.stringify(msg);
+ }
+ response.bodyOutputStream.write(msg, msg.length);
+ }
+
+ // Mock of the fxa accounts auth server, reproducing the behavior of
+ // /account/login when email capitalization is incorrect on signIn.
+ let server = httpd_setup({
+ "/account/login": function(request, response) {
+ response.setHeader("Content-Type", "application/json");
+ attempts += 1;
+
+ // Ensure we don't get in an endless loop
+ if (attempts > 2) {
+ response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance");
+ writeResp(response, {});
+ return;
+ }
+
+ let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+ let jsonBody = JSON.parse(body);
+ let email = jsonBody.email;
+
+ // The second time through, the accounts client will call the api with
+ // the correct email capitalization.
+ if (email == canonicalEmail) {
+ response.setStatusLine(request.httpVersion, 200, "Yay");
+ writeResp(response, {
+ uid: "your-uid",
+ sessionToken: "your-sessionToken",
+ keyFetchToken: "your-keyFetchToken",
+ verified: true,
+ authAt: 1392144866,
+ });
+ return;
+ }
+
+ // If the client has the wrong case on the email, we return a 400, with
+ // the capitalization of the email as saved in the accounts database.
+ response.setStatusLine(request.httpVersion, 400, "Incorrect email case");
+ writeResp(response, {
+ code: 400,
+ errno: 120,
+ error: "Incorrect email case",
+ email: canonicalEmail,
+ });
+ return;
+ },
+ });
+
+ // Point the FxAccountsClient's hawk rest request client to the mock server
+ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", server.baseURI);
+
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ // Receive a mozFxAccountsChromeEvent message
+ function onMessage(subject, topic, data) {
+ let message = subject.wrappedJSObject;
+
+ switch (message.id) {
+ // When we signed in as "Greta.Garbo", the server should have told us
+ // that the proper capitalization is really "greta.garbo". Call
+ // getAccounts to get the signed-in user and ensure that the
+ // capitalization is correct.
+ case "signIn":
+ FxAccountsMgmtService.handleEvent({
+ detail: {
+ id: "getAccounts",
+ data: {
+ method: "getAccounts",
+ }
+ }
+ });
+ break;
+
+ // Having initially signed in as "Greta.Garbo", getAccounts should show
+ // us that the signed-in user has the properly-capitalized email,
+ // "greta.garbo".
+ case "getAccounts":
+ Services.obs.removeObserver(onMessage, "mozFxAccountsChromeEvent");
+
+ do_check_eq(message.data.email, canonicalEmail);
+
+ do_test_finished();
+ server.stop(run_next_test);
+ break;
+
+ // We should not receive any other mozFxAccountsChromeEvent messages
+ default:
+ do_throw("wat!");
+ break;
+ }
+ }
+
+ Services.obs.addObserver(onMessage, "mozFxAccountsChromeEvent", false);
+
+ SystemAppProxy._sendCustomEvent = mockSendCustomEvent;
+
+ // Trigger signIn using an email with incorrect capitalization
+ FxAccountsMgmtService.handleEvent({
+ detail: {
+ id: "signIn",
+ data: {
+ method: "signIn",
+ email: clientEmail,
+ password: "123456",
+ },
+ },
+ });
+});
+
+add_test(function testHandleGetAssertionError_defaultCase() {
+ do_test_pending();
+
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ FxAccountsManager.getAssertion(null).then(
+ success => {
+ // getAssertion should throw with invalid audience
+ ok(false);
+ },
+ reason => {
+ equal("INVALID_AUDIENCE", reason.error);
+ do_test_finished();
+ run_next_test();
+ }
+ )
+});
+
+// End of tests
+// Utility functions follow
+
+function httpd_setup (handlers, port=-1) {
+ let server = new HttpServer();
+ for (let path in handlers) {
+ server.registerPathHandler(path, handlers[path]);
+ }
+ try {
+ server.start(port);
+ } catch (ex) {
+ dump("ERROR starting server on port " + port + ". Already a process listening?");
+ do_throw(ex);
+ }
+
+ // Set the base URI for convenience.
+ let i = server.identity;
+ server.baseURI = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
+
+ return server;
+}
+
+
diff --git a/b2g/components/test/unit/test_logcapture.js b/b2g/components/test/unit/test_logcapture.js
new file mode 100644
index 000000000..9dbe2bc77
--- /dev/null
+++ b/b2g/components/test/unit/test_logcapture.js
@@ -0,0 +1,13 @@
+/**
+ * Testing non Gonk-specific code path
+ */
+function run_test() {
+ Components.utils.import("resource:///modules/LogCapture.jsm");
+ run_next_test();
+}
+
+// Trivial test just to make sure we have no syntax error
+add_test(function test_logCapture_loads() {
+ ok(LogCapture, "LogCapture object exists");
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_logcapture_gonk.js b/b2g/components/test/unit/test_logcapture_gonk.js
new file mode 100644
index 000000000..d80f33dd9
--- /dev/null
+++ b/b2g/components/test/unit/test_logcapture_gonk.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+/**
+ * Test that LogCapture successfully reads from the /dev/log devices, returning
+ * a Uint8Array of some length, including zero. This tests a few standard
+ * log devices
+ */
+function run_test() {
+ Components.utils.import("resource:///modules/LogCapture.jsm");
+ run_next_test();
+}
+
+function verifyLog(log) {
+ // log exists
+ notEqual(log, null);
+ // log has a length and it is non-negative (is probably array-like)
+ ok(log.length >= 0);
+}
+
+add_test(function test_readLogFile() {
+ let mainLog = LogCapture.readLogFile("/dev/log/main");
+ verifyLog(mainLog);
+
+ let meminfoLog = LogCapture.readLogFile("/proc/meminfo");
+ verifyLog(meminfoLog);
+
+ run_next_test();
+});
+
+add_test(function test_readProperties() {
+ let propertiesLog = LogCapture.readProperties();
+ notEqual(propertiesLog, null, "Properties should not be null");
+ notEqual(propertiesLog, undefined, "Properties should not be undefined");
+
+ for (let propertyName in propertiesLog) {
+ equal(typeof(propertiesLog[propertyName]), "string",
+ "Property " + propertyName + " should be a string");
+ }
+
+ equal(propertiesLog["ro.product.locale.language"], "en",
+ "Locale language should be read correctly. See bug 1171577.");
+
+ equal(propertiesLog["ro.product.locale.region"], "US",
+ "Locale region should be read correctly. See bug 1171577.");
+
+ run_next_test();
+});
+
+add_test(function test_readAppIni() {
+ let appIni = LogCapture.readLogFile("/system/b2g/application.ini");
+ verifyLog(appIni);
+
+ run_next_test();
+});
+
+add_test(function test_get_about_memory() {
+ let memLog = LogCapture.readAboutMemory();
+
+ ok(memLog, "Should have returned a valid Promise object");
+
+ memLog.then(file => {
+ ok(file, "Should have returned a filename");
+ run_next_test();
+ }, error => {
+ ok(false, "Dumping about:memory promise rejected: " + error);
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_logparser.js b/b2g/components/test/unit/test_logparser.js
new file mode 100644
index 000000000..624dcc6e2
--- /dev/null
+++ b/b2g/components/test/unit/test_logparser.js
@@ -0,0 +1,75 @@
+/* jshint moz: true */
+
+var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
+
+function debug(msg) {
+ var timestamp = Date.now();
+ dump("LogParser: " + timestamp + ": " + msg + "\n");
+}
+
+function run_test() {
+ Cu.import("resource:///modules/LogParser.jsm");
+ debug("Starting");
+ run_next_test();
+}
+
+function makeStream(file) {
+ var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, -1, -1, 0);
+ var bis = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fileStream);
+ return bis;
+}
+
+add_test(function test_parse_logfile() {
+ let loggerFile = do_get_file("data/test_logger_file");
+
+ let loggerStream = makeStream(loggerFile);
+
+ // Initialize arrays to hold the file contents (lengths are hardcoded)
+ let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
+
+ loggerStream.close();
+
+ let logMessages = LogParser.parseLogArray(loggerArray);
+
+ ok(logMessages.length === 58, "There should be 58 messages in the log");
+
+ let expectedLogEntry = {
+ processId: 271, threadId: 271,
+ seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
+ priority: 4, tag: "Vold",
+ message: "Vold 2.1 (the revenge) firing up\n"
+ };
+
+ deepEqual(expectedLogEntry, logMessages[0]);
+ run_next_test();
+});
+
+add_test(function test_print_properties() {
+ let properties = {
+ "ro.secure": "1",
+ "sys.usb.state": "diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb"
+ };
+
+ let logMessagesRaw = LogParser.prettyPrintPropertiesArray(properties);
+ let logMessages = new TextDecoder("utf-8").decode(logMessagesRaw);
+ let logMessagesArray = logMessages.split("\n");
+
+ ok(logMessagesArray.length === 3, "There should be 3 lines in the log.");
+ notEqual(logMessagesArray[0], "", "First line should not be empty");
+ notEqual(logMessagesArray[1], "", "Second line should not be empty");
+ equal(logMessagesArray[2], "", "Last line should be empty");
+
+ let expectedLog = [
+ "[ro.secure]: [1]",
+ "[sys.usb.state]: [diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb]",
+ ""
+ ].join("\n");
+
+ deepEqual(expectedLog, logMessages);
+
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_logshake.js b/b2g/components/test/unit/test_logshake.js
new file mode 100644
index 000000000..cfb81b893
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake.js
@@ -0,0 +1,218 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm
+ */
+
+/* jshint moz: true */
+/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+"use strict";
+
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/LogCapture.jsm");
+Cu.import("resource://gre/modules/LogShake.jsm");
+
+const EVENTS_PER_SECOND = 6.25;
+const GRAVITY = 9.8;
+
+/**
+ * Force logshake to handle a device motion event with given components.
+ * Does not use SystemAppProxy because event needs special
+ * accelerationIncludingGravity property.
+ */
+function sendDeviceMotionEvent(x, y, z) {
+ let event = {
+ type: "devicemotion",
+ accelerationIncludingGravity: {
+ x: x,
+ y: y,
+ z: z
+ }
+ };
+ LogShake.handleEvent(event);
+}
+
+/**
+ * Send a screen change event directly, does not use SystemAppProxy due to race
+ * conditions.
+ */
+function sendScreenChangeEvent(screenEnabled) {
+ let event = {
+ type: "screenchange",
+ detail: {
+ screenEnabled: screenEnabled
+ }
+ };
+ LogShake.handleEvent(event);
+}
+
+/**
+ * Mock the readLogFile function of LogCapture.
+ * Used to detect whether LogShake activates.
+ * @return {Array<String>} Locations that LogShake tries to read
+ */
+function mockReadLogFile() {
+ let readLocations = [];
+
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ return null; // we don't want to provide invalid data to a parser
+ };
+
+ // Allow inspection of readLocations by caller
+ return readLocations;
+}
+
+/**
+ * Send a series of events that corresponds to a shake
+ */
+function sendSustainedShake() {
+ // Fire a series of devicemotion events that are of shake magnitude
+ for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
+ sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
+ }
+
+}
+
+add_test(function test_do_log_capture_after_shaking() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_resting() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ // Fire several devicemotion events that are relatively tiny
+ for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
+ sendDeviceMotionEvent(0, GRAVITY, GRAVITY);
+ }
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_disabled() {
+ // Disable LogShake
+ LogShake.uninit();
+
+ let readLocations = mockReadLogFile();
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_screen_off() {
+ // Enable LogShake
+ LogShake.init();
+
+ // Send an event as if the screen has been turned off
+ sendScreenChangeEvent(false);
+
+ let readLocations = mockReadLogFile();
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ // Restore the screen
+ sendScreenChangeEvent(true);
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_log_capture_resilient_readLogFile() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = [];
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ throw new Error("Exception during readLogFile for: " + loc);
+ };
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_log_capture_resilient_parseLog() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = [];
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ LogShake.LOGS_WITH_PARSERS[loc] = function() {
+ throw new Error("Exception during LogParser for: " + loc);
+ };
+ return null;
+ };
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_dropped() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ // We want a series of spikes to be ignored by LogShake. This roughly
+ // corresponds to the compare_stairs_sock graph on bug #1101994
+
+ for (let i = 0; i < 10 * EVENTS_PER_SECOND; i++) {
+ // Fire a devicemotion event that is at rest
+ sendDeviceMotionEvent(0, 0, GRAVITY);
+ // Fire a spike of motion
+ sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
+ }
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/test_logshake_gonk.js b/b2g/components/test/unit/test_logshake_gonk.js
new file mode 100644
index 000000000..28de0263f
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_gonk.js
@@ -0,0 +1,61 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm, checking
+ * for Gonk-specific parts
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ setup_logshake_mocks, OS, sdcard */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_writes() {
+ // Enable LogShake
+ LogShake.init();
+
+ let expectedFiles = [];
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logFilenames.length > 0, "Should have filenames");
+ ok(logResults.logPaths.length > 0, "Should have paths");
+ ok(!logResults.compressed, "Should not be compressed");
+
+ logResults.logPaths.forEach(f => {
+ let p = OS.Path.join(sdcard, f);
+ ok(p, "Should have a valid result path: " + p);
+
+ let t = OS.File.exists(p).then(rv => {
+ ok(rv, "File exists: " + p);
+ });
+
+ expectedFiles.push(t);
+ });
+
+ Promise.all(expectedFiles).then(() => {
+ ok(true, "Completed all files checks");
+ run_next_test();
+ });
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_logshake_gonk_compression.js b/b2g/components/test/unit/test_logshake_gonk_compression.js
new file mode 100644
index 000000000..b5af46081
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_gonk_compression.js
@@ -0,0 +1,76 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm, checking
+ * for Gonk-specific parts
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cc, Ci, Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ setup_logshake_mocks, OS, sdcard, FileUtils */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_writes_zip() {
+ // Enable LogShake
+ LogShake.init();
+
+ let expectedFiles = [];
+
+ LogShake.enableQAMode();
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logPaths.length === 1, "Should have zip path");
+ ok(logResults.logFilenames.length >= 1, "Should have log filenames");
+ ok(logResults.compressed, "Log files should be compressed");
+
+ let zipPath = OS.Path.join(sdcard, logResults.logPaths[0]);
+ ok(zipPath, "Should have a valid archive path: " + zipPath);
+
+ let zipFile = new FileUtils.File(zipPath);
+ ok(zipFile, "Should have a valid archive file: " + zipFile);
+
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
+ .createInstance(Ci.nsIZipReader);
+ zipReader.open(zipFile);
+
+ let logFilenamesSeen = {};
+
+ let zipEntries = zipReader.findEntries(null); // Find all entries
+ while (zipEntries.hasMore()) {
+ let entryName = zipEntries.getNext();
+ let entry = zipReader.getEntry(entryName);
+ logFilenamesSeen[entryName] = true;
+ ok(!entry.isDirectory, "Archive entry " + entryName + " should be a file");
+ }
+ zipReader.close();
+
+ // TODO: Verify archive contents
+ logResults.logFilenames.forEach(filename => {
+ ok(logFilenamesSeen[filename], "File " + filename + " should be present in archive");
+ });
+ run_next_test();
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
+
diff --git a/b2g/components/test/unit/test_logshake_readLog_gonk.js b/b2g/components/test/unit/test_logshake_readLog_gonk.js
new file mode 100644
index 000000000..003723ad5
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_readLog_gonk.js
@@ -0,0 +1,65 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm under conditions that
+ * could cause races
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ XPCOMUtils, do_get_profile, OS, volumeService, Promise, equal,
+ setup_logshake_mocks */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_waits_to_read() {
+ // Enable LogShake
+ LogShake.init();
+
+ // Save no logs synchronously (except properties)
+ LogShake.LOGS_WITH_PARSERS = {};
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logFilenames.length > 0, "Should have filenames");
+ ok(logResults.logPaths.length > 0, "Should have paths");
+ ok(!logResults.compressed, "Should not be compressed");
+
+ // This assumes that the about:memory reading will only fail under abnormal
+ // circumstances. It does not check for screenshot.png because
+ // systemAppFrame is unavailable during xpcshell tests.
+ let hasAboutMemory = false;
+
+ logResults.logFilenames.forEach(filename => {
+ // Because the about:memory log's filename has the PID in it we can not
+ // use simple equality but instead search for the "about_memory" part of
+ // the filename which will look like logshake-about_memory-{PID}.json.gz
+ if (filename.indexOf("about_memory") < 0) {
+ return;
+ }
+ hasAboutMemory = true;
+ });
+
+ ok(hasAboutMemory,
+ "LogShake's asynchronous read of about:memory should have succeeded.");
+
+ run_next_test();
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_signintowebsite.js b/b2g/components/test/unit/test_signintowebsite.js
new file mode 100644
index 000000000..38d4fa79e
--- /dev/null
+++ b/b2g/components/test/unit/test_signintowebsite.js
@@ -0,0 +1,322 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests for b2g/components/SignInToWebsite.jsm
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteController",
+ "resource://gre/modules/SignInToWebsite.jsm",
+ "SignInToWebsiteController");
+
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test_signintowebsite"].concat(aMessageArgs));
+}
+
+function test_overall() {
+ do_check_neq(MinimalIDService, null);
+ run_next_test();
+}
+
+function objectContains(object, subset) {
+ let objectKeys = Object.keys(object);
+ let subsetKeys = Object.keys(subset);
+
+ // can't have fewer keys than the subset
+ if (objectKeys.length < subsetKeys.length) {
+ return false;
+ }
+
+ let key;
+ let success = true;
+ if (subsetKeys.length > 0) {
+ for (let i=0; i<subsetKeys.length; i++) {
+ key = subsetKeys[i];
+
+ // key exists in the source object
+ if (typeof object[key] === 'undefined') {
+ success = false;
+ break;
+ }
+
+ // recursively check object values
+ else if (typeof subset[key] === 'object') {
+ if (typeof object[key] !== 'object') {
+ success = false;
+ break;
+ }
+ if (! objectContains(object[key], subset[key])) {
+ success = false;
+ break;
+ }
+ }
+
+ else if (object[key] !== subset[key]) {
+ success = false;
+ break;
+ }
+ }
+ }
+
+ return success;
+}
+
+function test_object_contains() {
+ do_test_pending();
+
+ let someObj = {
+ pies: 42,
+ green: "spam",
+ flan: {yes: "please"}
+ };
+ let otherObj = {
+ pies: 42,
+ flan: {yes: "please"}
+ };
+ do_check_true(objectContains(someObj, otherObj));
+ do_test_finished();
+ run_next_test();
+}
+
+function test_mock_doc() {
+ do_test_pending();
+ let mockedDoc = mockDoc({loggedInUser: null}, function(action, params) {
+ do_check_eq(action, 'coffee');
+ do_test_finished();
+ run_next_test();
+ });
+
+ // A smoke test to ensure that mockedDoc is functioning correctly.
+ // There is presently no doCoffee method in Persona.
+ mockedDoc.doCoffee();
+}
+
+function test_watch() {
+ do_test_pending();
+
+ setup_test_identity("pie@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, function(action, params) {
+ do_check_eq(action, 'ready');
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ });
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ });
+}
+
+function test_request_login() {
+ do_test_pending();
+
+ setup_test_identity("flan@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_eq(params, TEST_CERT);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ });
+}
+
+function test_request_logout() {
+ do_test_pending();
+
+ setup_test_identity("flan@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ });
+}
+
+function test_request_login_logout() {
+ do_test_pending();
+
+ setup_test_identity("unagi@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_eq(params, TEST_CERT);
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ });
+}
+
+function test_logout_everywhere() {
+ do_test_pending();
+ let logouts = 0;
+
+ setup_test_identity("fugu@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc1 = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ },
+ function(action, params) {
+ // Result of logout from doc2.
+ // We don't know what order the logouts will occur in.
+ do_check_eq(action, 'logout');
+ if (++logouts === 2) {
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ ));
+
+ let mockedDoc2 = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ if (++logouts === 2) {
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc1, {});
+ MinimalIDService.RP.request(mockedDoc1.id, {});
+
+ MinimalIDService.RP.watch(mockedDoc2, {});
+ MinimalIDService.RP.request(mockedDoc2.id, {});
+
+ // Logs out of both docs because they share the
+ // same origin.
+ MinimalIDService.RP.logout(mockedDoc2.id, {});
+ });
+}
+
+function test_options_pass_through() {
+ do_test_pending();
+
+ // An meaningless structure for testing that RP messages preserve
+ // objects and their parameters as they are passed back and forth.
+ let randomMixedParams = {
+ loggedInUser: "juanita@mozilla.com",
+ forceAuthentication: true,
+ forceIssuer: "foo.com",
+ someThing: {
+ name: "Pertelote",
+ legs: 4,
+ nested: {bee: "Eric", remaining: "1/2"}
+ }
+ };
+
+ let mockedDoc = mockDoc(randomMixedParams, function(action, params) {});
+
+ function pipeOtherEnd(rpOptions, gaiaOptions) {
+ // Ensure that every time we receive a message, our mixed
+ // random params are contained in that message
+ do_check_true(objectContains(rpOptions, randomMixedParams));
+
+ switch (gaiaOptions.message) {
+ case "identity-delegate-watch":
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ break;
+ case "identity-delegate-request":
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ break;
+ case "identity-delegate-logout":
+ do_test_finished();
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ run_next_test();
+ break;
+ }
+ }
+
+ let controller = SignInToWebsiteController;
+ controller.init({pipe: mockSendingPipe(pipeOtherEnd)});
+
+ MinimalIDService.RP.watch(mockedDoc, {});
+}
+
+var TESTS = [
+ test_overall,
+ test_mock_doc,
+ test_object_contains,
+
+ test_watch,
+ test_request_login,
+ test_request_logout,
+ test_request_login_logout,
+ test_logout_everywhere,
+
+ test_options_pass_through
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/xpcshell.ini b/b2g/components/test/unit/xpcshell.ini
new file mode 100644
index 000000000..ca3df5bf6
--- /dev/null
+++ b/b2g/components/test/unit/xpcshell.ini
@@ -0,0 +1,49 @@
+[DEFAULT]
+head =
+tail =
+
+support-files =
+ data/test_logger_file
+
+[test_bug793310.js]
+
+[test_bug832946.js]
+
+[test_fxaccounts.js]
+[test_signintowebsite.js]
+head = head_identity.js
+tail =
+
+# testing non gonk-specific stuff
+[test_logcapture.js]
+
+[test_logcapture_gonk.js]
+# can be slow because of what the test does, so let's give it some more time
+# to avoid intermittents: bug 1212395
+requesttimeoutfactor = 2
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = toolkit != "gonk"
+
+[test_logparser.js]
+
+[test_logshake.js]
+
+[test_logshake_gonk.js]
+# can be slow because of what the test does, so let's give it some more time
+# to avoid intermittents: bug 1144499
+requesttimeoutfactor = 2
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_logshake_gonk_compression.js]
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_logshake_readLog_gonk.js]
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_aboutserviceworkers.js]