Merge m-c to inbound on a CLOSED TREE.

This commit is contained in:
Ryan VanderMeulen 2014-04-17 22:53:02 -04:00
commit e9d4f36f17
130 changed files with 3393 additions and 1218 deletions

View File

@ -65,8 +65,12 @@ exports.install = function install(xpiPath) {
// Order AddonManager to install the addon
AddonManager.getInstallForFile(file, function(install) {
install.addListener(listener);
install.install();
if (install.error != null) {
install.addListener(listener);
install.install();
} else {
reject(install.error);
}
});
return promise;

View File

@ -286,17 +286,6 @@ const ContentWorker = Object.freeze({
value: self
});
// Deprecated use of on/postMessage from globals
exports.postMessage = function deprecatedPostMessage() {
console.error("DEPRECATED: The global `postMessage()` function in " +
"content scripts is deprecated in favor of the " +
"`self.postMessage()` function, which works the same. " +
"Replace calls to `postMessage()` with calls to " +
"`self.postMessage()`." +
"For more info on `self.on`, see " +
"<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
return self.postMessage.apply(null, arguments);
};
exports.on = function deprecatedOn() {
console.error("DEPRECATED: The global `on()` function in content " +
"scripts is deprecated in favor of the `self.on()` " +

View File

@ -46,9 +46,11 @@ function onDocumentReady2Translate(event) {
try {
// Finally display document when we finished replacing all text content
let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.removeSheet(hideSheetUri, winUtils.USER_SHEET);
if (document.defaultView) {
let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.removeSheet(hideSheetUri, winUtils.USER_SHEET);
}
}
catch(e) {
console.exception(e);

View File

@ -223,7 +223,6 @@ exports.show = show
function setupPanelFrame(frame) {
frame.setAttribute("flex", 1);
frame.setAttribute("transparent", "transparent");
frame.setAttribute("showcaret", true);
frame.setAttribute("autocompleteenabled", true);
if (platform === "darwin") {
frame.style.borderRadius = "6px";

View File

@ -191,9 +191,6 @@ exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
helper.createWorker(
'new ' + function ContentScriptScope() {
assert(postMessage === postMessage,
"verify that we doesn't generate multiple functions for the same method");
var json = JSON.stringify({foo : "bar\n \"escaped\"."});
document.getElementById("iframe").contentWindow.postMessage(json, "*");

View File

@ -26,7 +26,13 @@ const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
function makeWindow(contentURL) {
const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," +
"<script>window.addEventListener('message', function (e) {" +
" if (e.data === 'from -> content-script')" +
" window.postMessage('from -> window', '*');" +
"});</script>";
function makeWindow() {
let content =
"<?xml version=\"1.0\"?>" +
"<window " +
@ -782,50 +788,6 @@ exports["test:check worker API with page history"] = WorkerTest(
}
);
exports["test:global postMessage"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let { loader } = LoaderWithHookedConsole(module, onMessage);
setPref(DEPRECATE_PREF, true);
// Intercept all console method calls
let seenMessages = 0;
function onMessage(type, message) {
seenMessages++;
assert.equal(type, "error", "Should be an error");
assert.equal(message, "DEPRECATED: The global `postMessage()` function in " +
"content scripts is deprecated in favor of the " +
"`self.postMessage()` function, which works the same. " +
"Replace calls to `postMessage()` with calls to " +
"`self.postMessage()`." +
"For more info on `self.on`, see " +
"<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.",
"Should have seen the deprecation message")
}
assert.notEqual(browser.contentWindow.location.href, "about:blank",
"window is now on the right document");
let window = browser.contentWindow
let worker = loader.require("sdk/content/worker").Worker({
window: window,
contentScript: "new " + function WorkerScope() {
postMessage("success");
},
contentScriptWhen: "ready",
onMessage: function(msg) {
assert.equal("success", msg, "Should have seen the right postMessage call");
assert.equal(1, seenMessages, "Should have seen the deprecation message");
done();
}
});
assert.equal(worker.url, window.location.href,
"worker.url works");
worker.postMessage("hi!");
}
);
exports['test:conentScriptFile as URL instance'] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
@ -889,8 +851,8 @@ exports["test:onDetach in contentScript on destroy"] = WorkerTest(
})
},
});
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!',
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!',
"location.href is as expected");
done();
})
@ -910,8 +872,8 @@ exports["test:onDetach in contentScript on unload"] = WorkerTest(
})
},
});
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!shutdown',
browser.contentWindow.addEventListener('hashchange', _ => {
assert.equal(browser.contentWindow.location.hash, '#detach!shutdown',
"location.href is as expected");
done();
})
@ -954,4 +916,25 @@ exports["test:console method log functions properly"] = WorkerTest(
}
);
exports["test:global postMessage"] = WorkerTest(
WINDOW_SCRIPT_URL,
function(assert, browser, done) {
let contentScript = "window.addEventListener('message', function (e) {" +
" if (e.data === 'from -> window')" +
" self.port.emit('response', e.data, e.origin);" +
"});" +
"postMessage('from -> content-script', '*');";
let { loader } = LoaderWithHookedConsole(module);
let worker = loader.require("sdk/content/worker").Worker({
window: browser.contentWindow,
contentScriptWhen: "ready",
contentScript: contentScript
});
worker.port.on("response", (data, origin) => {
assert.equal(data, "from -> window", "Communication from content-script to window completed");
done();
});
});
require("test").run(exports);

View File

@ -1,12 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Loader } = require('sdk/test/loader');
const Pages = require("sdk/page-worker");
const Page = Pages.Page;
const { Page } = require("sdk/page-worker");
const { URL } = require("sdk/url");
const fixtures = require("./fixtures");
const testURI = fixtures.url("test.html");
@ -93,17 +91,17 @@ exports.testPageProperties = function(assert) {
exports.testConstructorAndDestructor = function(assert, done) {
let loader = Loader(module);
let Pages = loader.require("sdk/page-worker");
let { Page } = loader.require("sdk/page-worker");
let global = loader.sandbox("sdk/page-worker");
let pagesReady = 0;
let page1 = Pages.Page({
let page1 = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: pageReady
});
let page2 = Pages.Page({
let page2 = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: pageReady
@ -128,9 +126,9 @@ exports.testConstructorAndDestructor = function(assert, done) {
exports.testAutoDestructor = function(assert, done) {
let loader = Loader(module);
let Pages = loader.require("sdk/page-worker");
let { Page } = loader.require("sdk/page-worker");
let page = Pages.Page({
let page = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: function() {
@ -316,12 +314,12 @@ exports.testPingPong = function(assert, done) {
exports.testRedirect = function (assert, done) {
let page = Page({
contentURL: 'data:text/html;charset=utf-8,first-page',
contentScript: '(function () {' +
contentScriptWhen: "end",
contentScript: '' +
'if (/first-page/.test(document.location.href)) ' +
' document.location.href = "data:text/html;charset=utf-8,redirect";' +
'else ' +
' self.port.emit("redirect", document.location.href);' +
'})();'
' self.port.emit("redirect", document.location.href);'
});
page.port.on('redirect', function (url) {

View File

@ -1167,9 +1167,9 @@ let RemoteDebugger = {
};
#ifdef MOZ_WIDGET_GONK
DebuggerServer.onConnectionChange = function(what) {
DebuggerServer.on("connectionchange", function() {
AdbController.updateState();
}
});
#endif
}

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
@ -128,7 +128,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="f8e49f3837230cfbb2d6fbcf239d1f25b57b39a7"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8afce6d5d48b71b6e7cb917179fb6147fb747521"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="6a7ada569fd37c09ed4bbee6eb78cea5b467b229"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="52c909ccead537f8f9dbf634f3e6639078a8b0bd">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
@ -118,11 +118,11 @@
<default remote="caf" revision="jb_3.2" sync-j="4"/>
<!-- Flame specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
<project name="device/qcom/common" path="device/qcom/common" revision="c2c115bec6f5e9bc1daf7bb74bb4d14861c00b9c"/>
<project name="device/qcom/common" path="device/qcom/common" revision="234ed34543345f58c0d4dcb1aa012de68802b9dc"/>
<project name="device-flame" path="device/t2m/flame" remote="b2g" revision="92f9b79e3a5ecf24cb0f66e20d5292b300f8cac9"/>
<project name="kernel/msm" path="kernel" revision="39ca56deb6c4419c0220a762f6e340091032390d"/>
<project name="kernel/msm" path="kernel" revision="b3092c54430df89636fb0670d32058bc63474017"/>
<project name="platform/bootable/recovery" path="bootable/recovery" revision="f2914eacee9120680a41463708bb6ee8291749fc"/>
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="c53977638eb8a7091f752d870e8e0f00f7a23d95"/>
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="fa892235a9bd8983f8b591129fc1a9398f64e514"/>
<project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
<project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
<project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
@ -139,7 +139,7 @@
<project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
<project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
<project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
<project name="platform/system/core" path="system/core" revision="8b7736c56fa9a3fd8b4341eb243e12eec847efed"/>
<project name="platform/system/core" path="system/core" revision="e284280277c1312017a9450956a9676584441cdf"/>
<project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
<project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="b3001d5f1686f89995b7bf70963cf69c8faebd83"/>
</manifest>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "35bd8bfb0ee9dce0eb9dcfdbb43587517b655121",
"revision": "d253e50da6b54f14a1ffa6d8a8a287337f0e3f34",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -7,6 +7,7 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
});
const PREF_SYNC_START_DOORHANGER = "services.sync.ui.showSyncStartDoorhanger";
const DOORHANGER_ACTIVATE_DELAY_MS = 5000;
let gFxAccounts = {
@ -33,16 +34,6 @@ let gFxAccounts = {
];
},
// The set of topics that only the active window should handle.
get activeWindowTopics() {
// Do all this dance to lazy-load FxAccountsCommon.
delete this.activeWindowTopics;
return this.activeWindowTopics = new Set([
"weave:service:sync:start",
FxAccountsCommon.ONVERIFIED_NOTIFICATION
]);
},
get button() {
delete this.button;
return this.button = document.getElementById("PanelUI-fxa-status");
@ -64,9 +55,8 @@ let gFxAccounts = {
},
get isActiveWindow() {
let mostRecentNonPopupWindow =
RecentWindow.getMostRecentBrowserWindow({allowPopups: false});
return window == mostRecentNonPopupWindow;
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
return fm.activeWindow == window;
},
init: function () {
@ -79,6 +69,7 @@ let gFxAccounts = {
Services.obs.addObserver(this, topic, false);
}
addEventListener("activate", this);
gNavToolbox.addEventListener("customizationstarting", this);
gNavToolbox.addEventListener("customizationending", this);
@ -100,11 +91,6 @@ let gFxAccounts = {
},
observe: function (subject, topic) {
// Ignore certain topics if we're not the active window.
if (this.activeWindowTopics.has(topic) && !this.isActiveWindow) {
return;
}
switch (topic) {
case FxAccountsCommon.ONVERIFIED_NOTIFICATION:
Services.prefs.setBoolPref(PREF_SYNC_START_DOORHANGER, true);
@ -119,6 +105,10 @@ let gFxAccounts = {
},
onSyncStart: function () {
if (!this.isActiveWindow) {
return;
}
let showDoorhanger = false;
try {
@ -132,8 +122,17 @@ let gFxAccounts = {
},
handleEvent: function (event) {
this._inCustomizationMode = event.type == "customizationstarting";
this.updateUI();
if (event.type == "activate") {
// Our window might have been in the background while we received the
// sync:start notification. If still needed, show the doorhanger after
// a short delay. Without this delay the doorhanger would not show up
// or with a too small delay show up while we're still animating the
// window.
setTimeout(() => this.onSyncStart(), DOORHANGER_ACTIVATE_DELAY_MS);
} else {
this._inCustomizationMode = event.type == "customizationstarting";
this.updateUI();
}
},
showDoorhanger: function (id) {

View File

@ -6999,10 +6999,6 @@ var TabContextMenu = {
aPopupMenu.triggerNode : gBrowser.selectedTab;
let disabled = gBrowser.tabs.length == 1;
// Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
document.getElementById("context_closeTab").disabled =
disabled && gBrowser.tabContainer._closeWindowWithLastTab;
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
for (let menuItem of menuItems)
menuItem.disabled = disabled;

View File

@ -1,3 +1,7 @@
/* 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/. */
const kObservedTopics = [
"getUserMedia:response:allow",
"getUserMedia:revoke",
@ -736,6 +740,50 @@ let gTests = [
info("request video, stop sharing resets video only");
yield stopAndCheckPerm(false, true);
}
},
{
desc: "'Always Allow' ignored and not shown on http pages",
run: function checkNoAlwaysOnHttp() {
// Load an http page instead of the https version.
let deferred = Promise.defer();
let browser = gBrowser.selectedTab.linkedBrowser;
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
content.location = content.location.href.replace("https://", "http://");
yield deferred.promise;
// Initially set both permissions to 'allow'.
let Perms = Services.perms;
let uri = content.document.documentURIObject;
Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
Perms.add(uri, "camera", Perms.ALLOW_ACTION);
// Request devices and expect a prompt despite the saved 'Allow' permission,
// because the connection isn't secure.
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
content.wrappedJSObject.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
// Ensure that the 'Always Allow' action isn't shown.
let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label");
ok(!!alwaysLabel, "found the 'Always Allow' localized label");
let labels = [];
let notification = PopupNotifications.panel.firstChild;
for (let node of notification.childNodes) {
if (node.localName == "menuitem")
labels.push(node.getAttribute("label"));
}
is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown");
// Cleanup.
yield closeStream(true);
Perms.remove(uri.host, "camera");
Perms.remove(uri.host, "microphone");
}
}
];

View File

@ -163,8 +163,8 @@ let gSyncPane = {
checkbox.checked = false;
}
checkbox.disabled = !allowPasswordsEngine;
help.hidden = allowPasswordsEngine;
checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
help.hidden = allowPasswordsEngine || enginesListDisabled;
});
// If fxAccountEnabled is false and we are in a "not configured" state,
// then fxAccounts is probably fully disabled rather than just unconfigured,

View File

@ -163,8 +163,8 @@ let gSyncPane = {
checkbox.checked = false;
}
checkbox.disabled = !allowPasswordsEngine;
help.hidden = allowPasswordsEngine;
checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
help.hidden = allowPasswordsEngine || enginesListDisabled;
});
// If fxAccountEnabled is false and we are in a "not configured" state,
// then fxAccounts is probably fully disabled rather than just unconfigured,

View File

@ -7,7 +7,7 @@ function test() {
let originalTab = gBrowser.tabs[0];
popup(originalTab);
ok(document.getElementById("context_closeTab").disabled, "The 'Close tab' menu item is disabled");
ok(!document.getElementById("context_closeTab").disabled, "The 'Close tab' menu item is enabled");
ok(document.getElementById("context_openTabInWindow").disabled, "The 'Move to New Window' menu item is disabled");
let newTabOne = gBrowser.addTab("about:blank", {skipAnimation: true});

View File

@ -16,9 +16,13 @@ Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
Cu.import("resource://gre/modules/devtools/Loader.jsm");
let require = devtools.require;
let Telemetry = require("devtools/shared/telemetry");
let EventEmitter = require("devtools/toolkit/event-emitter");
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
let processes = Set();
/**
* Constructor for creating a process that will hold a chrome toolbox.
*
@ -30,15 +34,33 @@ this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
* An object with properties for configuring BrowserToolboxProcess.
*/
this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) {
let emitter = new EventEmitter();
this.on = emitter.on.bind(emitter);
this.off = emitter.off.bind(emitter);
this.once = emitter.once.bind(emitter);
// Forward any events to the shared emitter.
this.emit = function(...args) {
emitter.emit(...args);
BrowserToolboxProcess.emit(...args);
}
// If first argument is an object, use those properties instead of
// all three arguments
if (typeof aOnClose === "object") {
this._closeCallback = aOnClose.onClose;
this._runCallback = aOnClose.onRun;
if (aOnClose.onClose) {
this.on("close", aOnClose.onClose);
}
if (aOnClose.onRun) {
this.on("run", aOnClose.onRun);
}
this._options = aOnClose;
} else {
this._closeCallback = aOnClose;
this._runCallback = aOnRun;
if (aOnClose) {
this.on("close", aOnClose);
}
if (aOnRun) {
this.on("run", aOnRun);
}
this._options = aOptions || {};
}
@ -49,8 +71,12 @@ this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aO
this._initServer();
this._initProfile();
this._create();
processes.add(this);
};
EventEmitter.decorate(BrowserToolboxProcess);
/**
* Initializes and starts a chrome toolbox process.
* @return object
@ -59,6 +85,25 @@ BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) {
return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions);
};
/**
* Passes a set of options to the BrowserAddonActors for the given ID.
*
* @param aId string
* The ID of the add-on to pass the options to
* @param aOptions object
* The options.
* @return a promise that will be resolved when complete.
*/
BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) {
let promises = [];
for (let process of processes.values()) {
promises.push(process.debuggerServer.setAddonOptions(aId, aOptions));
}
return promise.all(promises);
};
BrowserToolboxProcess.prototype = {
/**
* Initializes the debugger server.
@ -77,6 +122,9 @@ BrowserToolboxProcess.prototype = {
this.loader.main("devtools/server/main");
this.debuggerServer = this.loader.DebuggerServer;
dumpn("Created a separate loader instance for the DebuggerServer.");
// Forward interesting events.
this.debuggerServer.on("connectionchange", this.emit.bind(this));
}
if (!this.debuggerServer.initialized) {
@ -169,9 +217,7 @@ BrowserToolboxProcess.prototype = {
this._telemetry.toolOpened("jsbrowserdebugger");
dumpn("Chrome toolbox is now running...");
if (typeof this._runCallback == "function") {
this._runCallback.call({}, this);
}
this.emit("run", this);
},
/**
@ -196,9 +242,8 @@ BrowserToolboxProcess.prototype = {
dumpn("Chrome toolbox is now closed...");
this.closed = true;
if (typeof this._closeCallback == "function") {
this._closeCallback.call({}, this);
}
this.emit("close", this);
processes.delete(this);
}
};

View File

@ -9,9 +9,11 @@
const Cu = Components.utils;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let gClient;
let gConnectionTimeout;
@ -75,59 +77,97 @@ function submit() {
/**
* Connection is ready. List actors and build buttons.
*/
function onConnectionReady(aType, aTraits) {
let onConnectionReady = Task.async(function*(aType, aTraits) {
clearTimeout(gConnectionTimeout);
gClient.listTabs(function(aResponse) {
document.body.classList.remove("connecting");
document.body.classList.add("actors-mode");
let parent = document.getElementById("tabActors");
// Add Global Process debugging...
let globals = JSON.parse(JSON.stringify(aResponse));
delete globals.tabs;
delete globals.selected;
// ...only if there are appropriate actors (a 'from' property will always
// be there).
// Add one entry for each open tab.
for (let i = 0; i < aResponse.tabs.length; i++) {
buildLink(aResponse.tabs[i], parent, i == aResponse.selected);
}
let gParent = document.getElementById("globalActors");
// Build the Remote Process button
if (Object.keys(globals).length > 1) {
let a = document.createElement("a");
a.onclick = function() {
openToolbox(globals, true);
let deferred = promise.defer();
gClient.listAddons(deferred.resolve);
let response = yield deferred.promise;
let parent = document.getElementById("addonActors")
if (!response.error && response.addons.length > 0) {
// Add one entry for each add-on.
for (let addon of response.addons) {
if (!addon.debuggable) {
continue;
}
a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
a.className = "remote-process";
a.href = "#";
gParent.appendChild(a);
}
// Move the selected tab on top
let selectedLink = parent.querySelector("a.selected");
if (selectedLink) {
parent.insertBefore(selectedLink, parent.firstChild);
buildAddonLink(addon, parent);
}
}
else {
// Hide the section when there are no add-ons
parent.previousElementSibling.remove();
parent.remove();
}
// Ensure the first link is focused
let firstLink = parent.querySelector("a:first-of-type");
if (firstLink) {
firstLink.focus();
}
deferred = promise.defer();
gClient.listTabs(deferred.resolve);
response = yield deferred.promise;
});
parent = document.getElementById("tabActors");
// Add Global Process debugging...
let globals = JSON.parse(JSON.stringify(response));
delete globals.tabs;
delete globals.selected;
// ...only if there are appropriate actors (a 'from' property will always
// be there).
// Add one entry for each open tab.
for (let i = 0; i < response.tabs.length; i++) {
buildTabLink(response.tabs[i], parent, i == response.selected);
}
let gParent = document.getElementById("globalActors");
// Build the Remote Process button
if (Object.keys(globals).length > 1) {
let a = document.createElement("a");
a.onclick = function() {
openToolbox(globals, true);
}
a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
a.className = "remote-process";
a.href = "#";
gParent.appendChild(a);
}
// Move the selected tab on top
let selectedLink = parent.querySelector("a.selected");
if (selectedLink) {
parent.insertBefore(selectedLink, parent.firstChild);
}
document.body.classList.remove("connecting");
document.body.classList.add("actors-mode");
// Ensure the first link is focused
let firstLink = parent.querySelector("a:first-of-type");
if (firstLink) {
firstLink.focus();
}
});
/**
* Build one button for an add-on actor.
*/
function buildAddonLink(addon, parent) {
let a = document.createElement("a");
a.onclick = function() {
openToolbox({ addonActor: addon.actor, title: addon.name }, true, "jsdebugger");
}
a.textContent = addon.name;
a.title = addon.id;
a.href = "#";
parent.appendChild(a);
}
/**
* Build one button for an actor.
* Build one button for a tab actor.
*/
function buildLink(tab, parent, selected) {
function buildTabLink(tab, parent, selected) {
let a = document.createElement("a");
a.onclick = function() {
openToolbox(tab);
@ -173,7 +213,7 @@ function handleConnectionTimeout() {
* The user clicked on one of the buttons.
* Opens the toolbox.
*/
function openToolbox(form, chrome=false) {
function openToolbox(form, chrome=false, tool="webconsole") {
let options = {
form: form,
client: gClient,
@ -181,7 +221,7 @@ function openToolbox(form, chrome=false) {
};
devtools.TargetFactory.forRemoteTab(options).then((target) => {
let hostType = devtools.Toolbox.HostType.WINDOW;
gDevTools.showToolbox(target, "webconsole", hostType).then((toolbox) => {
gDevTools.showToolbox(target, tool, hostType).then((toolbox) => {
toolbox.once("destroyed", function() {
gClient.close();
});

View File

@ -39,6 +39,8 @@
<section id="actors-list">
<p>&availableTabs;</p>
<ul class="actors" id="tabActors"></ul>
<p>&availableAddons;</p>
<ul class="actors" id="addonActors"></ul>
<p>&availableProcesses;</p>
<ul class="actors" id="globalActors"></ul>
</section>

View File

@ -822,11 +822,17 @@ MarkupView.prototype = {
/**
* Mark the given node expanded.
* @param aNode The NodeFront to mark as expanded.
* @param {NodeFront} aNode The NodeFront to mark as expanded.
* @param {Boolean} aExpanded Whether the expand or collapse.
* @param {Boolean} aExpandDescendants Whether to expand all descendants too
*/
setNodeExpanded: function(aNode, aExpanded) {
setNodeExpanded: function(aNode, aExpanded, aExpandDescendants) {
if (aExpanded) {
this.expandNode(aNode);
if (aExpandDescendants) {
this.expandAll(aNode);
} else {
this.expandNode(aNode);
}
} else {
this.collapseNode(aNode);
}
@ -1413,7 +1419,7 @@ MarkupContainer.prototype = {
_onToggle: function(event) {
this.markup.navigate(this);
if(this.hasChildren) {
this.markup.setNodeExpanded(this.node, !this.expanded);
this.markup.setNodeExpanded(this.node, !this.expanded, event.altKey);
}
event.stopPropagation();
},

View File

@ -9,6 +9,7 @@ support-files =
doc_markup_pagesize_01.html
doc_markup_pagesize_02.html
doc_markup_search.html
doc_markup_toggle.html
doc_markup_tooltip.png
head.js
helper_attributes_test_runner.js
@ -37,3 +38,6 @@ support-files =
[browser_markupview_tag_edit_07.js]
[browser_markupview_tag_edit_08.js]
[browser_markupview_textcontent_edit_01.js]
[browser_markupview_toggle_01.js]
[browser_markupview_toggle_02.js]
[browser_markupview_toggle_03.js]

View File

@ -113,14 +113,6 @@ function pressKey(key) {
}
}
function waitForChildrenUpdated(inspector) {
let def = promise.defer();
inspector.markup._waitForChildren().then(() => {
executeSoon(def.resolve);
});
return def.promise;
}
function checkSelectedNode(key, className, inspector) {
let node = inspector.selection.node;

View File

@ -0,0 +1,45 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test toggling (expand/collapse) elements by clicking on twisties
const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
let test = asyncTest(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Getting the container for the UL parent element");
let container = getContainerForRawNode("ul", inspector);
info("Clicking on the UL parent expander, and waiting for children");
let onChildren = waitForChildrenUpdated(inspector);
let onUpdated = inspector.once("inspector-updated");
EventUtils.synthesizeMouseAtCenter(container.expander, {},
inspector.markup.doc.defaultView);
yield onChildren;
yield onUpdated;
info("Checking that child LI elements have been created");
for (let li of content.document.querySelectorAll("li")) {
ok(getContainerForRawNode(li, inspector),
"A container for the child LI element was created");
}
ok(container.expanded, "Parent UL container is expanded");
info("Clicking again on the UL expander");
// No need to wait, this is a local, synchronous operation where nodes are
// only hidden from the view, not destroyed
EventUtils.synthesizeMouseAtCenter(container.expander, {},
inspector.markup.doc.defaultView);
info("Checking that child LI elements have been hidden");
for (let li of content.document.querySelectorAll("li")) {
let liContainer = getContainerForRawNode(li, inspector);
is(liContainer.elt.getClientRects().length, 0,
"The container for the child LI element was hidden");
}
ok(!container.expanded, "Parent UL container is collapsed");
});

View File

@ -0,0 +1,45 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test toggling (expand/collapse) elements by dbl-clicking on tag lines
const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
let test = asyncTest(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Getting the container for the UL parent element");
let container = getContainerForRawNode("ul", inspector);
info("Dbl-clicking on the UL parent expander, and waiting for children");
let onChildren = waitForChildrenUpdated(inspector);
let onUpdated = inspector.once("inspector-updated");
EventUtils.synthesizeMouseAtCenter(container.tagLine, {clickCount: 2},
inspector.markup.doc.defaultView);
yield onChildren;
yield onUpdated;
info("Checking that child LI elements have been created");
for (let li of content.document.querySelectorAll("li")) {
ok(getContainerForRawNode(li, inspector),
"A container for the child LI element was created");
}
ok(container.expanded, "Parent UL container is expanded");
info("Dbl-clicking again on the UL expander");
// No need to wait, this is a local, synchronous operation where nodes are
// only hidden from the view, not destroyed
EventUtils.synthesizeMouseAtCenter(container.tagLine, {clickCount: 2},
inspector.markup.doc.defaultView);
info("Checking that child LI elements have been hidden");
for (let li of content.document.querySelectorAll("li")) {
let liContainer = getContainerForRawNode(li, inspector);
is(liContainer.elt.getClientRects().length, 0,
"The container for the child LI element was hidden");
}
ok(!container.expanded, "Parent UL container is collapsed");
});

View File

@ -0,0 +1,44 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test toggling (expand/collapse) elements by alt-clicking on twisties, which
// should expand all the descendants
const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
let test = asyncTest(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Getting the container for the UL parent element");
let container = getContainerForRawNode("ul", inspector);
info("Alt-clicking on the UL parent expander, and waiting for children");
let onUpdated = inspector.once("inspector-updated");
EventUtils.synthesizeMouseAtCenter(container.expander, {altKey: true},
inspector.markup.doc.defaultView);
yield onUpdated;
yield waitForMultipleChildrenUpdates(inspector);
info("Checking that all nodes exist and are expanded");
for (let node of content.document.querySelectorAll("ul, li, span, em")) {
let nodeContainer = getContainerForRawNode(node, inspector);
ok(nodeContainer, "Container for node " + node.tagName + " exists");
ok(nodeContainer.expanded,
"Container for node " + node.tagName + " is expanded");
}
});
// The expand all operation of the markup-view calls itself recursively and
// there's not one event we can wait for to know when it's done
function* waitForMultipleChildrenUpdates(inspector) {
// As long as child updates are queued up while we wait for an update already
// wait again
if (inspector.markup._queuedChildUpdates &&
inspector.markup._queuedChildUpdates.size) {
yield waitForChildrenUpdated(inspector);
return yield waitForMultipleChildrenUpdates(inspector);
}
}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Expanding and collapsing markup-view containers</title>
</head>
<body>
<ul>
<li>
<span>list <em>item</em></span>
</li>
<li>
<span>list <em>item</em></span>
</li>
<li>
<span>list <em>item</em></span>
</li>
<li>
<span>list <em>item</em></span>
</li>
<li>
<span>list <em>item</em></span>
</li>
<li>
<span>list <em>item</em></span>
</li>
</ul>
</body>
</html>

View File

@ -166,6 +166,23 @@ function getContainerForRawNode(nodeOrSelector, {markup}) {
return container;
}
/**
* Using the markupview's _waitForChildren function, wait for all queued
* children updates to be handled.
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
* @return a promise that resolves when all queued children updates have been
* handled
*/
function waitForChildrenUpdated({markup}) {
info("Waiting for queued children updates to be handled");
let def = promise.defer();
markup._waitForChildren().then(() => {
executeSoon(def.resolve);
});
return def.promise;
}
/**
* Simulate a mouse-over on the markup-container (a line in the markup-view)
* that corresponds to the node or selector passed.
@ -352,3 +369,15 @@ function searchUsingSelectorSearch(selector, inspector) {
field.value = selector;
EventUtils.sendKey("return", inspector.panelWin);
}
/**
* This shouldn't be used in the tests, but is useful when writing new tests or
* debugging existing tests in order to introduce delays in the test steps
* @param {Number} ms The time to wait
* @return A promise that resolves when the time is passed
*/
function wait(ms) {
let def = promise.defer();
content.setTimeout(def.resolve, ms);
return def.promise;
}

View File

@ -42,11 +42,9 @@ XPCOMUtils.defineLazyGetter(this, "CertUtils",
return mod;
});
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
"@mozilla.org/xre/app-info;1",
"nsICrashReporter");
#endif
const FILE_CACHE = "experiments.json";
const OBSERVER_TOPIC = "experiments-changed";
@ -76,20 +74,29 @@ const TELEMETRY_LOG = {
// log(key, [kind, experimentId, details])
ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
ACTIVATION: {
ACTIVATED: "ACTIVATED", // successfully activated
INSTALL_FAILURE: "INSTALL_FAILURE", // failed to install the extension
REJECTED: "REJECTED", // experiment was rejected because of it's conditions,
// provides details on which
// Successfully activated.
ACTIVATED: "ACTIVATED",
// Failed to install the add-on.
INSTALL_FAILURE: "INSTALL_FAILURE",
// Experiment does not meet activation requirements. Details will
// be provided.
REJECTED: "REJECTED",
},
// log(key, [kind, experimentId, optionalDetails...])
TERMINATION_KEY: "EXPERIMENT_TERMINATION",
TERMINATION: {
USERDISABLED: "USERDISABLED", // the user disabled this experiment
FROM_API: "FROM_API", // the experiment disabled itself
EXPIRED: "EXPIRED", // experiment expired e.g. by exceeding the end-date
RECHECK: "RECHECK", // disabled after re-evaluating conditions,
// provides details on which
// The Experiments service was disabled.
SERVICE_DISABLED: "SERVICE_DISABLED",
// Add-on uninstalled.
ADDON_UNINSTALLED: "ADDON_UNINSTALLED",
// The experiment disabled itself.
FROM_API: "FROM_API",
// The experiment expired (e.g. by exceeding the end date).
EXPIRED: "EXPIRED",
// Disabled after re-evaluating conditions. If this is specified,
// details will be provided.
RECHECK: "RECHECK",
},
};
@ -106,6 +113,10 @@ let gExperimentEntryCounter = 0;
// installs.
let gActiveInstallURLs = new Set();
// Tracks add-on IDs that are being uninstalled by us. This allows us
// to differentiate between expected uninstalled and user-driven uninstalls.
let gActiveUninstallAddonIDs = new Set();
let gLogger;
let gLogDumping = false;
@ -316,6 +327,14 @@ Experiments.Policy.prototype = {
},
};
function AlreadyShutdownError(message="already shut down") {
this.name = "AlreadyShutdownError";
this.message = message;
}
AlreadyShutdownError.prototype = new Error();
AlreadyShutdownError.prototype.constructor = AlreadyShutdownError;
/**
* Manages the experiments and provides an interface to control them.
*/
@ -340,9 +359,6 @@ Experiments.Experiments = function (policy=new Experiments.Policy()) {
// Loading the cache happens once asynchronously on startup
this._loadTask = null;
// Ignore addon-manager notifications for addons that we are uninstalling ourself
this._pendingUninstall = null;
// The _main task handles all other actions:
// * refreshing the manifest off the network (if _refresh)
// * disabling/enabling experiments
@ -426,7 +442,11 @@ Experiments.Experiments.prototype = {
this._shutdown = true;
if (this._mainTask) {
yield this._mainTask;
try {
yield this._mainTask;
} catch (e if e instanceof AlreadyShutdownError) {
// We error out of tasks after shutdown via that exception.
}
}
this._log.info("Completed uninitialization.");
@ -449,7 +469,7 @@ Experiments.Experiments.prototype = {
*/
_checkForShutdown: function() {
if (this._shutdown) {
throw Error("uninit() already called");
throw new AlreadyShutdownError("uninit() already called");
}
},
@ -468,7 +488,7 @@ Experiments.Experiments.prototype = {
gPrefs.set(PREF_ENABLED, enabled);
},
_toggleExperimentsEnabled: function (enabled) {
_toggleExperimentsEnabled: Task.async(function* (enabled) {
this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
let wasEnabled = gExperimentsEnabled;
gExperimentsEnabled = enabled && telemetryEnabled();
@ -478,14 +498,14 @@ Experiments.Experiments.prototype = {
}
if (gExperimentsEnabled) {
this.updateManifest();
yield this.updateManifest();
} else {
this.disableExperiment();
yield this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED);
if (this._timer) {
this._timer.clear();
}
}
},
}),
_telemetryStatusChanged: function () {
this._toggleExperimentsEnabled(gExperimentsEnabled);
@ -667,21 +687,9 @@ Experiments.Experiments.prototype = {
// START OF ADD-ON LISTENERS
onDisabled: function (addon) {
this._log.trace("onDisabled() - addon id: " + addon.id);
if (addon.id == this._pendingUninstall) {
return;
}
let activeExperiment = this._getActiveExperiment();
if (!activeExperiment || activeExperiment._addonId != addon.id) {
return;
}
this.disableExperiment();
},
onUninstalled: function (addon) {
this._log.trace("onUninstalled() - addon id: " + addon.id);
if (addon.id == this._pendingUninstall) {
if (gActiveUninstallAddonIDs.has(addon.id)) {
this._log.trace("matches pending uninstall");
return;
}
@ -689,7 +697,8 @@ Experiments.Experiments.prototype = {
if (!activeExperiment || activeExperiment._addonId != addon.id) {
return;
}
this.disableExperiment();
this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED);
},
onInstallStarted: function (install) {
@ -927,15 +936,17 @@ Experiments.Experiments.prototype = {
},
/**
* Disable an experiment by id.
* @param experimentId The id of the experiment.
* @param userDisabled (optional) Whether this is disabled as a result of a user action.
* Disables all active experiments.
*
* @return Promise<> Promise that will get resolved once the task is done or failed.
*/
disableExperiment: function (userDisabled=true) {
this._log.trace("disableExperiment()");
disableExperiment: function (reason) {
if (!reason) {
throw new Error("Must specify a termination reason.");
}
this._terminateReason = userDisabled ? TELEMETRY_LOG.TERMINATION.USERDISABLED : TELEMETRY_LOG.TERMINATION.FROM_API;
this._log.trace("disableExperiment()");
this._terminateReason = reason;
return this._run();
},
@ -992,44 +1003,43 @@ Experiments.Experiments.prototype = {
gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
}
// Ensure the active experiment is in the proper state. This may install,
// uninstall, upgrade, or enable the experiment add-on. What exactly is
// abstracted away from us by design.
if (activeExperiment) {
this._pendingUninstall = activeExperiment._addonId;
try {
let wasStopped;
if (this._terminateReason) {
yield activeExperiment.stop(this._terminateReason);
wasStopped = true;
let changes;
let shouldStopResult = yield activeExperiment.shouldStop();
if (shouldStopResult.shouldStop) {
let expireReasons = ["endTime", "maxActiveSeconds"];
let kind, reason;
if (expireReasons.indexOf(shouldStopResult.reason[0]) != -1) {
kind = TELEMETRY_LOG.TERMINATION.EXPIRED;
reason = null;
} else {
wasStopped = yield activeExperiment.maybeStop();
kind = TELEMETRY_LOG.TERMINATION.RECHECK;
reason = shouldStopResult.reason;
}
if (wasStopped) {
this._dirty = true;
this._log.debug("evaluateExperiments() - stopped experiment "
+ activeExperiment.id);
activeExperiment = null;
activeChanged = true;
} else if (!gExperimentsEnabled) {
// No further actions if the feature is disabled.
} else if (activeExperiment.needsUpdate) {
this._log.debug("evaluateExperiments() - updating experiment "
+ activeExperiment.id);
try {
yield activeExperiment.stop();
yield activeExperiment.start();
} catch (e) {
this._log.error(e);
// On failure try the next experiment.
activeExperiment = null;
}
this._dirty = true;
activeChanged = true;
} else {
yield activeExperiment.ensureActive();
}
} finally {
this._pendingUninstall = null;
changes = yield activeExperiment.stop(kind, reason);
}
else if (this._terminateReason) {
changes = yield activeExperiment.stop(this._terminateReason);
}
else {
changes = yield activeExperiment.reconcileAddonState();
}
if (changes) {
this._dirty = true;
activeChanged = true;
}
if (!activeExperiment._enabled) {
activeExperiment = null;
activeChanged = true;
}
}
this._terminateReason = null;
if (!activeExperiment && gExperimentsEnabled) {
@ -1052,30 +1062,35 @@ Experiments.Experiments.prototype = {
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY, data);
}
if (applicable) {
this._log.debug("evaluateExperiments() - activating experiment " + id);
try {
yield experiment.start();
activeChanged = true;
activeExperiment = experiment;
this._dirty = true;
break;
} catch (e) {
// On failure try the next experiment.
}
if (!applicable) {
continue;
}
this._log.debug("evaluateExperiments() - activating experiment " + id);
try {
yield experiment.start();
activeChanged = true;
activeExperiment = experiment;
this._dirty = true;
break;
} catch (e) {
// On failure, clean up the best we can and try the next experiment.
this._log.error("evaluateExperiments() - Unable to start experiment: " + e.message);
experiment._enabled = false;
yield experiment.reconcileAddonState();
}
}
}
gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
if (activeChanged) {
Services.obs.notifyObservers(null, OBSERVER_TOPIC, null);
}
#ifdef MOZ_CRASHREPORTER
if (activeExperiment) {
if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
}
#endif
},
/*
@ -1127,7 +1142,7 @@ Experiments.ExperimentEntry = function (policy) {
"Browser.Experiments.Experiments",
"ExperimentEntry #" + gExperimentEntryCounter++ + "::");
// Is this experiment running?
// Is the experiment supposed to be running.
this._enabled = false;
// When this experiment was started, if ever.
this._startDate = null;
@ -1198,6 +1213,11 @@ Experiments.ExperimentEntry.prototype = {
"_endDate",
]),
ADDON_CHANGE_NONE: 0,
ADDON_CHANGE_INSTALL: 1,
ADDON_CHANGE_UNINSTALL: 2,
ADDON_CHANGE_ENABLE: 4,
/*
* Initialize entry from the manifest.
* @param data The experiment data from the manifest.
@ -1487,26 +1507,18 @@ Experiments.ExperimentEntry.prototype = {
/*
* Start running the experiment.
*
* @return Promise<> Resolved when the operation is complete.
*/
start: function () {
start: Task.async(function* () {
this._log.trace("start() for " + this.id);
return Task.spawn(function* ExperimentEntry_start_task() {
let addons = yield installedExperimentAddons();
if (addons.length > 0) {
this._log.error("start() - there are already "
+ addons.length + " experiment addons installed");
yield uninstallAddons(addons);
}
yield this._installAddon();
gPrefs.set(PREF_ACTIVE_EXPERIMENT, true);
}.bind(this));
},
this._enabled = true;
return yield this.reconcileAddonState();
}),
// Async install of the addon for this experiment, part of the start task above.
_installAddon: function* () {
_installAddon: Task.async(function* () {
let deferred = Promise.defer();
let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash;
@ -1605,75 +1617,105 @@ Experiments.ExperimentEntry.prototype = {
install.addListener(listener);
install.install();
return deferred.promise;
},
return yield deferred.promise;
}),
/*
/**
* Stop running the experiment if it is active.
* @param terminationKind (optional) The termination kind, e.g. USERDISABLED or EXPIRED.
* @param terminationReason (optional) The termination reason details for
* termination kind RECHECK.
*
* @param terminationKind (optional)
* The termination kind, e.g. ADDON_UNINSTALLED or EXPIRED.
* @param terminationReason (optional)
* The termination reason details for termination kind RECHECK.
* @return Promise<> Resolved when the operation is complete.
*/
stop: function (terminationKind, terminationReason) {
stop: Task.async(function* (terminationKind, terminationReason) {
this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind);
if (!this._enabled) {
this._log.warning("stop() - experiment not enabled: " + id);
return Promise.reject();
throw new Error("Must not call stop() on an inactive experiment.");
}
this._enabled = false;
gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
let deferred = Promise.defer();
let updateDates = () => {
let now = this._policy.now();
this._lastChangedDate = now;
this._endDate = now;
};
let changes = yield this.reconcileAddonState();
let now = this._policy.now();
this._lastChangedDate = now;
this._endDate = now;
this._logTermination(terminationKind, terminationReason);
this._getAddon().then((addon) => {
if (!addon) {
let message = "could not get Addon for " + this.id;
this._log.warn("stop() - " + message);
updateDates();
deferred.resolve();
return;
}
updateDates();
this._logTermination(terminationKind, terminationReason);
deferred.resolve(uninstallAddons([addon]));
});
return deferred.promise;
},
return changes;
}),
/**
* Try to ensure this experiment is active.
* Reconcile the state of the add-on against what it's supposed to be.
*
* The returned promise will be resolved if the experiment is active
* in the Addon Manager or rejected if it isn't.
* If we are active, ensure the add-on is enabled and up to date.
*
* @return Promise<>
* If we are inactive, ensure the add-on is not installed.
*/
ensureActive: Task.async(function* () {
this._log.trace("ensureActive() for " + this.id);
reconcileAddonState: Task.async(function* () {
this._log.trace("reconcileAddonState()");
if (!this._enabled) {
if (!this._addonId) {
this._log.trace("reconcileAddonState() - Experiment is not enabled and " +
"has no add-on. Doing nothing.");
return this.ADDON_CHANGE_NONE;
}
let addon = yield this._getAddon();
if (!addon) {
this._log.trace("reconcileAddonState() - Inactive experiment has no " +
"add-on. Doing nothing.");
return this.ADDON_CHANGE_NONE;
}
this._log.info("reconcileAddonState() - Uninstalling add-on for inactive " +
"experiment: " + addon.id);
gActiveUninstallAddonIDs.add(addon.id);
yield uninstallAddons([addon]);
gActiveUninstallAddonIDs.delete(addon.id);
return this.ADDON_CHANGE_UNINSTALL;
}
// If we get here, we're supposed to be active.
let changes = 0;
// That requires an add-on.
let currentAddon = yield this._getAddon();
// If we have an add-on but it isn't up to date, uninstall it
// (to prepare for reinstall).
if (currentAddon && this._needsUpdate) {
this._log.info("reconcileAddonState() - Uninstalling add-on because update " +
"needed: " + currentAddon.id);
gActiveUninstallAddonIDs.add(currentAddon.id);
yield uninstallAddons([currentAddon]);
gActiveUninstallAddonIDs.delete(currentAddon.id);
changes |= this.ADDON_CHANGE_UNINSTALL;
}
if (!currentAddon || this._needsUpdate) {
this._log.info("reconcileAddonState() - Installing add-on.");
yield this._installAddon();
changes |= this.ADDON_CHANGE_INSTALL;
}
let addon = yield this._getAddon();
if (!addon) {
this._log.warn("Experiment is not installed: " + this._addonId);
throw new Error("Experiment is not installed: " + this._addonId);
throw new Error("Could not obtain add-on for experiment that should be " +
"enabled.");
}
// User disabled likely means the experiment is disabled at startup,
// since the permissions don't allow it to be disabled by the user.
// If we have the add-on and it is enabled, we are done.
if (!addon.userDisabled) {
return;
return changes;
}
let deferred = Promise.defer();
// Else we need to enable it.
let listener = {
onEnabled: enabledAddon => {
if (enabledAddon.id != addon.id) {
@ -1689,7 +1731,11 @@ Experiments.ExperimentEntry.prototype = {
AddonManager.addAddonListener(listener);
addon.userDisabled = false;
yield deferred.promise;
}),
changes |= this.ADDON_CHANGE_ENABLE;
this._log.info("Add-on has been enabled: " + addon.id);
return changes;
}),
/**
* Obtain the underlying Addon from the Addon Manager.
@ -1697,6 +1743,10 @@ Experiments.ExperimentEntry.prototype = {
* @return Promise<Addon|null>
*/
_getAddon: function () {
if (!this._addonId) {
return Promise.resolve(null);
}
let deferred = Promise.defer();
AddonManager.getAddonByID(this._addonId, deferred.resolve);
@ -1722,43 +1772,18 @@ Experiments.ExperimentEntry.prototype = {
TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data);
},
/*
* Stop if experiment stop criteria are met.
* @return Promise<boolean> Resolved when done stopping or checking,
* the value indicates whether it was stopped.
/**
* Determine whether an active experiment should be stopped.
*/
maybeStop: function () {
this._log.trace("maybeStop()");
return Task.spawn(function* ExperimentEntry_maybeStop_task() {
if (!gExperimentsEnabled) {
this._log.warn("maybeStop() - should not get here");
yield this.stop(TELEMETRY_LOG.TERMINATION.FROM_API);
return true;
}
shouldStop: function () {
if (!this._enabled) {
throw new Error("shouldStop must not be called on disabled experiments.");
}
let result = yield this._shouldStop();
if (result.shouldStop) {
let expireReasons = ["endTime", "maxActiveSeconds"];
if (expireReasons.indexOf(result.reason[0]) != -1) {
yield this.stop(TELEMETRY_LOG.TERMINATION.EXPIRED);
} else {
yield this.stop(TELEMETRY_LOG.TERMINATION.RECHECK, result.reason);
}
}
return result.shouldStop;
}.bind(this));
},
_shouldStop: function () {
let data = this._manifestData;
let now = this._policy.now() / 1000; // The manifest times are in seconds.
let maxActiveSec = data.maxActiveSeconds || 0;
if (!this._enabled) {
return Promise.resolve({shouldStop: false});
}
let deferred = Promise.defer();
this.isApplicable().then(
() => deferred.resolve({shouldStop: false}),

View File

@ -9,7 +9,7 @@ EXTRA_COMPONENTS += [
JS_MODULES_PATH = 'modules/experiments'
EXTRA_PP_JS_MODULES += [
EXTRA_JS_MODULES += [
'Experiments.jsm',
]

View File

@ -102,7 +102,8 @@ add_task(function* test_startStop() {
Assert.equal(result.applicable, true, "Experiment should now be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
yield experiment.start();
let changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
@ -110,12 +111,14 @@ add_task(function* test_startStop() {
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
yield experiment.stop();
changes = yield experiment.stop();
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
yield experiment.start();
changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
@ -123,20 +126,18 @@ add_task(function* test_startStop() {
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
let result = yield experiment._shouldStop();
let result = yield experiment.shouldStop();
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
let maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, false, "Experiment should not have been stopped.");
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
Assert.ok(addons[0].isActive, "The add-on is still active.");
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
result = yield experiment._shouldStop();
result = yield experiment.shouldStop();
Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, true, "Experiment should have been stopped.");
changes = yield experiment.stop();
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");

View File

@ -424,7 +424,7 @@ add_task(function* test_disableExperiment() {
now = futureDate(now, 1 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment();
yield experiments.disableExperiment("foo");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
@ -749,7 +749,7 @@ add_task(function* test_userDisabledAndUpdated() {
now = futureDate(now, 20 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment();
yield experiments.disableExperiment("foo");
Assert.equal(observerFireCount, ++expectedObserverFireCount,
"Experiments observer should have been called.");
@ -1405,7 +1405,7 @@ add_task(function* testForeignExperimentInstall() {
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
let failed;
let failed = false;
try {
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
} catch (ex) {

View File

@ -243,7 +243,7 @@ add_task(function* test_cache() {
// Cleanup.
yield experiments.disableExperiment();
yield experiments._toggleExperimentsEnabled(false);
yield experiments.uninit();
yield removeCacheFile();
});

View File

@ -6,7 +6,7 @@
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://gre/modules/TelemetryLog.jsm");
Cu.import("resource://gre/modules/TelemetryPing.jsm");
Cu.import("resource:///modules/experiments/Experiments.jsm");
let bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
const FILE_MANIFEST = "experiments.manifest";
@ -25,25 +25,7 @@ let gPolicy = null;
let gManifestObject = null;
let gManifestHandlerURI = null;
const TLOG = {
// log(key, [kind, experimentId, details])
ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
ACTIVATION: {
ACTIVATED: "ACTIVATED",
INSTALL_FAILURE: "INSTALL_FAILURE",
REJECTED: "REJECTED",
},
// log(key, [kind, experimentId, optionalDetails...])
TERMINATION_KEY: "EXPERIMENT_TERMINATION",
TERMINATION: {
USERDISABLED: "USERDISABLED",
FROM_API: "FROM_API",
EXPIRED: "EXPIRED",
RECHECK: "RECHECK",
},
};
const TLOG = bsp.TELEMETRY_LOG;
let gGlobalScope = this;
function loadAddonManager() {
@ -263,12 +245,12 @@ add_task(function* test_telemetryBasics() {
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
// Fake user-disable of an experiment.
// Fake user uninstall of experiment via add-on manager.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment();
yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
@ -276,7 +258,7 @@ add_task(function* test_telemetryBasics() {
log = TelemetryPing.getPayload().log;
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
[TLOG.TERMINATION.USERDISABLED, EXPERIMENT2_ID]);
[TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
// Trigger update with experiment 1a ready to start.
@ -295,12 +277,12 @@ add_task(function* test_telemetryBasics() {
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
// Trigger non-user-disable of an experiment via the API
// Trigger disable of an experiment via the API.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment(false);
yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
list = yield experiments.getExperiments();
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");

View File

@ -230,6 +230,9 @@ Var ControlRightPX
!include "defines.nsi"
; Must be included after defines.nsi
!include "locale-fonts.nsh"
; The OFFICIAL define is a workaround to support different urls for Release and
; Beta since they share the same branding when building with other branches that
; set the update channel to beta.
@ -416,9 +419,27 @@ Function .onInit
!endif
StrCpy $WasOptionsButtonClicked "0"
CreateFont $FontBlurb "$(^Font)" "12" "500"
CreateFont $FontNormal "$(^Font)" "11" "500"
CreateFont $FontItalic "$(^Font)" "11" "500" /ITALIC
StrCpy $0 ""
!ifdef FONT_FILE1
${If} ${FileExists} "$FONTS\${FONT_FILE1}"
StrCpy $0 "${FONT_NAME1}"
${EndIf}
!endif
!ifdef FONT_FILE2
${If} $0 == ""
${AndIf} ${FileExists} "$FONTS\${FONT_FILE2}"
StrCpy $0 "${FONT_NAME2}"
${EndIf}
!endif
${If} $0 == ""
StrCpy $0 "$(^Font)"
${EndIf}
CreateFont $FontBlurb "$0" "12" "500"
CreateFont $FontNormal "$0" "11" "500"
CreateFont $FontItalic "$0" "11" "500" /ITALIC
InitPluginsDir
File /oname=$PLUGINSDIR\bgintro.bmp "bgintro.bmp"

View File

@ -13,6 +13,7 @@
<!ENTITY port "Port:">
<!ENTITY connect "Connect">
<!ENTITY connecting "Connecting…">
<!ENTITY availableAddons "Available remote add-ons:">
<!ENTITY availableTabs "Available remote tabs:">
<!ENTITY availableProcesses "Available remote processes:">
<!ENTITY connectionError "Error:">

View File

@ -140,14 +140,6 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
};
let secondaryActions = [
{
label: stringBundle.getString("getUserMedia.always.label"),
accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
callback: function () {
// don't save unless secure load!
mainAction.callback(aSecure);
}
},
{
label: stringBundle.getString("getUserMedia.denyRequest.label"),
accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
@ -160,8 +152,8 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
callback: function () {
denyRequest(aCallID);
// Let someone save "Never" for http sites so that they can be stopped from
// bothering you with doorhangers
// Let someone save "Never" for http sites so that they can be stopped from
// bothering you with doorhangers.
let perms = Services.perms;
if (audioDevices.length)
perms.add(uri, "microphone", perms.DENY_ACTION);
@ -171,6 +163,17 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
}
];
if (aSecure) {
// Don't show the 'Always' action if the connection isn't secure.
secondaryActions.unshift({
label: stringBundle.getString("getUserMedia.always.label"),
accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
callback: function () {
mainAction.callback(true);
}
});
}
let options = {
eventCallback: function(aTopic, aNewBrowser) {
if (aTopic == "swapping")

View File

@ -35,6 +35,11 @@
moveY 3.4s linear 0s infinite alternate;
}
#PanelUI-popup #PanelUI-contents:-moz-locale-dir(rtl):empty::before {
animation: moveXRTL 3.05s linear 0s infinite alternate,
moveY 3.4s linear 0s infinite alternate;
}
#PanelUI-popup #PanelUI-contents:empty:hover::before {
background-image: url(chrome://browser/skin/customizableui/whimsy.png);
}
@ -53,6 +58,12 @@
/* These values are adjusted for the padding on the panel. */
from { margin-left: -15px; } to { margin-left: calc(100% - 49px); }
}
@keyframes moveXRTL {
/* These values are adjusted for the padding on the panel. */
from { margin-right: -15px; } to { margin-right: calc(100% - 49px); }
}
@keyframes moveY {
/* These values are adjusted for the padding and height of the panel. */
from { margin-top: -.5em; } to { margin-top: calc(64px - .5em); }

View File

@ -293,12 +293,21 @@ case "$target" in
if test ! -d "$android_platform_tools" ; then
android_platform_tools="$android_sdk"/tools # SDK Tools < r8
fi
# The build tools got moved around to different directories in
# SDK Tools r22. Try to locate them.
dnl The build tools got moved around to different directories in SDK
dnl Tools r22. Try to locate them. This is awful, but, from
dnl http://stackoverflow.com/a/4495368, the following sorts versions
dnl of the form x.y.z.a.b from newest to oldest:
dnl sort -t. -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr -k 5,5nr
dnl We want to favour the newer versions that start with 'android-';
dnl that's what the sed is about.
dnl We might iterate over directories that aren't build-tools at all;
dnl we use the presence of aapt as a marker.
AC_MSG_CHECKING([for android build-tools directory])
android_build_tools=""
for suffix in android-4.4 android-4.3 android-4.2.2 19.0.3 19.0.2 19.0.0 18.1.0 18.0.1 18.0.0 17.0.0; do
tools_directory="$android_sdk_root/build-tools/$suffix"
if test -d "$tools_directory" ; then
for suffix in `ls "$android_sdk_root/build-tools" | sed -e "s,android-,999.," | sort -t. -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr -k 5,5nr`; do
tools_directory=`echo "$android_sdk_root/build-tools/$suffix" | sed -e "s,999.,android-,"`
if test -d "$tools_directory" -a -f "$tools_directory/aapt"; then
android_build_tools="$tools_directory"
break
fi
@ -306,6 +315,13 @@ case "$target" in
if test -z "$android_build_tools" ; then
android_build_tools="$android_platform_tools" # SDK Tools < r22
fi
if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
AC_MSG_RESULT([$android_build_tools])
else
AC_MSG_ERROR([not found. Please check your SDK for the subdirectory of build-tools. With the current configuration, it should be in $android_sdk_root/build_tools])
fi
ANDROID_SDK="${android_sdk}"
ANDROID_SDK_ROOT="${android_sdk_root}"
if test -e "${ANDROID_SDK_ROOT}/extras/android/compatibility/v4/android-support-v4.jar" ; then

View File

@ -288,6 +288,10 @@ DOMInterfaces = {
'nativeType': 'nsDOMDataChannel',
},
'DataStoreCursor': {
'wrapperCache': False,
},
'DedicatedWorkerGlobalScope': {
'headerFile': 'mozilla/dom/WorkerScope.h',
'workers': True,

View File

@ -603,40 +603,39 @@ BluetoothHfpManager::ProcessAtChld(bthf_chld_type_t aChld)
message.AppendInt((int)aChld);
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
NS_ConvertUTF8toUTF16(message));
SendResponse(BTHF_AT_RESPONSE_OK);
}
void BluetoothHfpManager::ProcessDialCall(char *aNumber)
{
nsAutoCString message(aNumber);
// There are three cases based on aNumber,
// 1) Empty value: Redial, BLDN
// 2) >xxx: Memory dial, ATD>xxx
// 3) xxx: Normal dial, ATDxxx
// We need to respond OK/Error for dial requests for every case listed above,
// 1) and 2): Respond in either RespondToBLDNTask or
// HandleCallStateChanged()
// 3): Respond here
if (message.IsEmpty()) {
// Redial: BLDN
mDialingRequestProcessed = false;
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
NS_LITERAL_STRING("BLDN"));
} else {
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN);
} else if (message[0] == '>') {
mDialingRequestProcessed = false;
nsAutoCString newMsg("ATD");
if (message[0] == '>') {
// Memory dial: ATD>xxx
mDialingRequestProcessed = false;
newMsg += message;
} else {
// Dial number: ATDxxx
int end = message.FindChar(';');
if (end < 0) {
BT_WARNING("Couldn't get the number to dial");
SendResponse(BTHF_AT_RESPONSE_OK);
return;
}
newMsg += nsDependentCSubstring(message, 0, end);
}
newMsg += StringHead(message, message.Length() - 1);
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
NS_ConvertUTF8toUTF16(newMsg));
}
if (!mDialingRequestProcessed) {
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN);
} else {
nsAutoCString newMsg("ATD");
newMsg += StringHead(message, message.Length() - 1);
BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
NS_ConvertUTF8toUTF16(newMsg));
SendResponse(BTHF_AT_RESPONSE_OK);
}
}
@ -699,6 +698,8 @@ BluetoothHfpManager::ProcessAtClcc()
SendCLCC(mCdmaSecondCall, 2);
}
SendResponse(BTHF_AT_RESPONSE_OK);
}
void
@ -1098,6 +1099,13 @@ BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
return;
}
// aCallIndex can be UINT32_MAX for the pending outgoing call state update.
// aCallIndex will be updated again after real call state changes. See Bug
// 990467.
if (aCallIndex == UINT32_MAX) {
return;
}
while (aCallIndex >= mCurrentCallArray.Length()) {
Call call;
mCurrentCallArray.AppendElement(call);

View File

@ -1154,8 +1154,8 @@ AppendDeviceName(BluetoothSignal& aSignal)
bool success = sDBusConnection->SendWithReply(
AppendDeviceNameReplyHandler::Callback, handler.get(), 1000,
NS_ConvertUTF16toUTF8(devicePath).get(), DBUS_DEVICE_IFACE,
"GetProperties", DBUS_TYPE_INVALID);
BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(devicePath).get(),
DBUS_DEVICE_IFACE, "GetProperties", DBUS_TYPE_INVALID);
NS_ENSURE_TRUE_VOID(success);
@ -1488,6 +1488,7 @@ private:
bool success = sDBusConnection->SendWithReply(
RegisterAgentReplyHandler::Callback, handler.get(), -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
DBUS_ADAPTER_IFACE, "RegisterAgent",
DBUS_TYPE_OBJECT_PATH, &agentPath,
@ -1527,6 +1528,7 @@ public:
bool success = sDBusConnection->SendWithReply(
DBusReplyHandler::Callback, handler.get(), -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
DBUS_ADAPTER_IFACE, "AddReservedServiceRecords",
DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
@ -1939,7 +1941,7 @@ public:
*/
if (sAdapterPath.IsEmpty()) {
bool success = sDBusConnection->SendWithReply(OnDefaultAdapterReply, nullptr,
1000, "/",
1000, BLUEZ_DBUS_BASE_IFC, "/",
DBUS_MANAGER_IFACE,
"DefaultAdapter",
DBUS_TYPE_INVALID);
@ -2211,6 +2213,7 @@ protected:
bool success = sDBusConnection->SendWithReply(
DefaultAdapterPathReplyHandler::Callback, handler.get(), 1000,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(mAdapterPath).get(),
DBUS_ADAPTER_IFACE, "GetProperties", DBUS_TYPE_INVALID);
@ -2274,7 +2277,7 @@ public:
bool success = sDBusConnection->SendWithReply(
DefaultAdapterPathReplyHandler::Callback,
handler.get(), 1000,
handler.get(), 1000, BLUEZ_DBUS_BASE_IFC,
"/", DBUS_MANAGER_IFACE, "DefaultAdapter",
DBUS_TYPE_INVALID);
NS_ENSURE_TRUE_VOID(success);
@ -2342,6 +2345,7 @@ public:
bool success = sDBusConnection->SendWithReply(
OnSendDiscoveryMessageReply,
static_cast<void*>(mRunnable.get()), -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
DBUS_ADAPTER_IFACE, mMessageName.get(),
DBUS_TYPE_INVALID);
@ -2420,8 +2424,8 @@ public:
bool success = sDBusConnection->SendWithReply(
mCallback, static_cast<void*>(mServiceClass), -1,
mObjectPath.get(), mInterface.get(), mMessage.get(),
DBUS_TYPE_INVALID);
BLUEZ_DBUS_BASE_IFC, mObjectPath.get(), mInterface.get(),
mMessage.get(), DBUS_TYPE_INVALID);
NS_ENSURE_TRUE_VOID(success);
mServiceClass.forget();
@ -2607,7 +2611,7 @@ protected:
bool success = sDBusConnection->SendWithReply(
BluetoothArrayOfDevicePropertiesReplyHandler::Callback,
handler.get(), 1000,
handler.get(), 1000, BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(mObjectPath).get(),
DBUS_DEVICE_IFACE, "GetProperties",
DBUS_TYPE_INVALID);
@ -2735,7 +2739,7 @@ public:
MOZ_ASSERT(!sAdapterPath.IsEmpty());
DBusMessage* msg =
dbus_message_new_method_call("org.bluez",
dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
sBluetoothDBusIfaces[mType],
"SetProperty");
@ -2905,6 +2909,7 @@ public:
// unregister it after pairing process is over
bool success = sDBusConnection->SendWithReply(
GetObjectPathCallback, static_cast<void*>(mRunnable), mTimeout,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
DBUS_ADAPTER_IFACE,
"CreatePairedDevice",
@ -2976,6 +2981,7 @@ public:
bool success = sDBusConnection->SendWithReply(
OnRemoveDeviceReply, static_cast<void*>(mRunnable.get()), -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(sAdapterPath).get(),
DBUS_ADAPTER_IFACE, "RemoveDevice",
DBUS_TYPE_OBJECT_PATH, &cstrDeviceObjectPath,
@ -3498,6 +3504,7 @@ public:
bool success = sDBusConnection->SendWithReply(
OnGetServiceChannelReplyHandler::Callback, handler, -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(objectPath).get(),
DBUS_DEVICE_IFACE, "GetServiceAttributeValue",
DBUS_TYPE_STRING, &cstrServiceUUID,
@ -3578,6 +3585,7 @@ public:
sDBusConnection->SendWithReply(DiscoverServicesCallback,
(void*)callbackRunnable, -1,
BLUEZ_DBUS_BASE_IFC,
NS_ConvertUTF16toUTF8(objectPath).get(),
DBUS_DEVICE_IFACE,
"DiscoverServices",
@ -3796,6 +3804,7 @@ public:
bool success = sDBusConnection->SendWithReply(
GetVoidCallback, static_cast<void*>(mRunnable.get()), -1,
BLUEZ_DBUS_BASE_IFC,
objectPath.get(),
DBUS_CTL_IFACE, "UpdateMetaData",
DBUS_TYPE_STRING, &title,
@ -3932,6 +3941,7 @@ public:
bool success = sDBusConnection->SendWithReply(
GetVoidCallback, static_cast<void*>(mRunnable.get()), -1,
BLUEZ_DBUS_BASE_IFC,
objectPath.get(),
DBUS_CTL_IFACE, "UpdatePlayStatus",
DBUS_TYPE_UINT32, &mDuration,
@ -4055,6 +4065,7 @@ public:
bool success = sDBusConnection->SendWithReply(
ControlCallback, nullptr, -1,
BLUEZ_DBUS_BASE_IFC,
objectPath.get(),
DBUS_CTL_IFACE, "UpdatePlayStatus",
DBUS_TYPE_UINT32, &mDuration,
@ -4121,6 +4132,7 @@ public:
bool success = sDBusConnection->SendWithReply(
ControlCallback, nullptr, -1,
BLUEZ_DBUS_BASE_IFC,
objectPath.get(),
DBUS_CTL_IFACE, "UpdateNotification",
DBUS_TYPE_UINT16, &eventId,

188
dom/datastore/DataStore.cpp Normal file
View File

@ -0,0 +1,188 @@
/* 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/. */
#include "mozilla/dom/DataStore.h"
#include "mozilla/dom/DataStoreCursor.h"
#include "mozilla/dom/DataStoreBinding.h"
#include "mozilla/dom/DataStoreImplBinding.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Preferences.h"
#include "AccessCheck.h"
namespace mozilla {
namespace dom {
NS_IMPL_ADDREF_INHERITED(DataStore, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(DataStore, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DataStore)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_1(DataStore, mStore)
DataStore::DataStore(nsPIDOMWindow* aWindow)
: DOMEventTargetHelper(aWindow)
{
}
already_AddRefed<DataStore>
DataStore::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<DataStore> store = new DataStore(window);
return store.forget();
}
JSObject*
DataStore::WrapObject(JSContext* aCx)
{
return DataStoreBinding::Wrap(aCx, this);
}
/*static*/ bool
DataStore::EnabledForScope(JSContext* aCx, JS::Handle<JSObject*> aObj)
{
// Only expose the interface when it is:
// 1. enabled by the preference and
// 2. accessed by the chrome codes in Gecko.
return (Navigator::HasDataStoreSupport(aCx, aObj) &&
nsContentUtils::ThreadsafeIsCallerChrome());
}
void
DataStore::GetName(nsAString& aName, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
nsAutoString name;
mStore->GetName(name, aRv);
aName.Assign(name);
}
void
DataStore::GetOwner(nsAString& aOwner, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
nsAutoString owner;
mStore->GetOwner(owner, aRv);
aOwner.Assign(owner);
}
bool
DataStore::GetReadOnly(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->GetReadOnly(aRv);
}
already_AddRefed<Promise>
DataStore::Get(const Sequence<OwningStringOrUnsignedLong>& aId,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Get(aId, aRv);
}
already_AddRefed<Promise>
DataStore::Put(JSContext* aCx,
JS::Handle<JS::Value> aObj,
const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Put(aObj, aId, aRevisionId, aRv);
}
already_AddRefed<Promise>
DataStore::Add(JSContext* aCx,
JS::Handle<JS::Value> aObj,
const Optional<StringOrUnsignedLong>& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Add(aObj, aId, aRevisionId, aRv);
}
already_AddRefed<Promise>
DataStore::Remove(const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Remove(aId, aRevisionId, aRv);
}
already_AddRefed<Promise>
DataStore::Clear(const nsAString& aRevisionId, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Clear(aRevisionId, aRv);
}
void
DataStore::GetRevisionId(nsAString& aRevisionId, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
nsAutoString revisionId;
mStore->GetRevisionId(revisionId, aRv);
aRevisionId.Assign(revisionId);
}
already_AddRefed<Promise>
DataStore::GetLength(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->GetLength(aRv);
}
already_AddRefed<DataStoreCursor>
DataStore::Sync(const nsAString& aRevisionId, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mStore);
return mStore->Sync(aRevisionId, aRv);
}
void
DataStore::SetDataStoreImpl(DataStoreImpl& aStore, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mStore);
mStore = &aStore;
mStore->SetEventTarget(*this, aRv);
}
} //namespace dom
} //namespace mozilla

95
dom/datastore/DataStore.h Normal file
View File

@ -0,0 +1,95 @@
/* 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/. */
#ifndef mozilla_dom_DataStore_h
#define mozilla_dom_DataStore_h
#include "mozilla/DOMEventTargetHelper.h"
namespace mozilla {
class ErrorResult;
namespace dom {
class Promise;
class DataStoreCursor;
class DataStoreImpl;
class StringOrUnsignedLong;
class OwningStringOrUnsignedLong;
class DataStore MOZ_FINAL : public DOMEventTargetHelper
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DataStore,
DOMEventTargetHelper)
explicit DataStore(nsPIDOMWindow* aWindow);
// WebIDL (internal functions)
static already_AddRefed<DataStore> Constructor(GlobalObject& aGlobal,
ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
static bool EnabledForScope(JSContext* aCx, JS::Handle<JSObject*> aObj);
// WebIDL (public APIs)
void GetName(nsAString& aName, ErrorResult& aRv);
void GetOwner(nsAString& aOwner, ErrorResult& aRv);
bool GetReadOnly(ErrorResult& aRv);
already_AddRefed<Promise> Get(const Sequence<OwningStringOrUnsignedLong>& aId,
ErrorResult& aRv);
already_AddRefed<Promise> Put(JSContext* aCx,
JS::Handle<JS::Value> aObj,
const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv);
already_AddRefed<Promise> Add(JSContext* aCx,
JS::Handle<JS::Value> aObj,
const Optional<StringOrUnsignedLong>& aId,
const nsAString& aRevisionId,
ErrorResult& aRv);
already_AddRefed<Promise> Remove(const StringOrUnsignedLong& aId,
const nsAString& aRevisionId,
ErrorResult& aRv);
already_AddRefed<Promise> Clear(const nsAString& aRevisionId,
ErrorResult& aRv);
void GetRevisionId(nsAString& aRevisionId, ErrorResult& aRv);
already_AddRefed<Promise> GetLength(ErrorResult& aRv);
already_AddRefed<DataStoreCursor> Sync(const nsAString& aRevisionId,
ErrorResult& aRv);
IMPL_EVENT_HANDLER(change)
// This internal function (ChromeOnly) is aimed to make the DataStore keep a
// reference to the DataStoreImpl which really implements the API's logic in
// JS. We also need to let the DataStoreImpl implementation keep the event
// target of DataStore, so that it can know where to fire the events.
void SetDataStoreImpl(DataStoreImpl& aStore, ErrorResult& aRv);
protected:
virtual ~DataStore() {}
private:
nsRefPtr<DataStoreImpl> mStore;
};
} //namespace dom
} //namespace mozilla
#endif

View File

@ -0,0 +1,71 @@
/* 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/. */
#include "mozilla/dom/DataStore.h"
#include "mozilla/dom/DataStoreCursor.h"
#include "mozilla/dom/DataStoreBinding.h"
#include "mozilla/dom/DataStoreImplBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorResult.h"
#include "nsPIDOMWindow.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DataStoreCursor, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DataStoreCursor, Release)
NS_IMPL_CYCLE_COLLECTION_1(DataStoreCursor, mCursor)
already_AddRefed<DataStoreCursor>
DataStoreCursor::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
{
nsRefPtr<DataStoreCursor> cursor = new DataStoreCursor();
return cursor.forget();
}
JSObject*
DataStoreCursor::WrapObject(JSContext* aCx)
{
return DataStoreCursorBinding::Wrap(aCx, this);
}
already_AddRefed<DataStore>
DataStoreCursor::GetStore(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCursor);
return mCursor->GetStore(aRv);
}
already_AddRefed<Promise>
DataStoreCursor::Next(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCursor);
return mCursor->Next(aRv);
}
void
DataStoreCursor::Close(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCursor);
mCursor->Close(aRv);
}
void
DataStoreCursor::SetDataStoreCursorImpl(DataStoreCursorImpl& aCursor)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mCursor);
mCursor = &aCursor;
}
} //namespace dom
} //namespace mozilla

View File

@ -0,0 +1,61 @@
/* 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/. */
#ifndef mozilla_dom_DataStoreCursor_h
#define mozilla_dom_DataStoreCursor_h
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class Promise;
class DataStore;
class GlobalObject;
class DataStoreCursorImpl;
class DataStoreCursor MOZ_FINAL
{
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DataStoreCursor)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(DataStoreCursor)
// WebIDL (internal functions)
static already_AddRefed<DataStoreCursor> Constructor(GlobalObject& aGlobal,
ErrorResult& aRv);
JSObject* WrapObject(JSContext *aCx);
// WebIDL (public APIs)
already_AddRefed<DataStore> GetStore(ErrorResult& aRv);
already_AddRefed<Promise> Next(ErrorResult& aRv);
void Close(ErrorResult& aRv);
// This internal function (ChromeOnly) is aimed to make the DataStoreCursor
// keep a reference to the DataStoreCursorImpl which really implements the
// API's logic in JS.
void SetDataStoreCursorImpl(DataStoreCursorImpl& aCursor);
protected:
virtual ~DataStoreCursor() {}
private:
nsRefPtr<DataStoreCursorImpl> mCursor;
};
} //namespace dom
} //namespace mozilla
#endif

View File

@ -77,13 +77,14 @@ function createDOMError(aWindow, aEvent) {
/* DataStoreCursor object */
this.DataStoreCursor = function(aWindow, aDataStore, aRevisionId) {
debug("DataStoreCursor created");
this.init(aWindow, aDataStore, aRevisionId);
}
this.DataStoreCursor.prototype = {
classDescription: 'DataStoreCursor XPCOM Component',
classID: Components.ID('{b6d14349-1eab-46b8-8513-584a7328a26b}'),
contractID: '@mozilla.org/dom/datastore-cursor;1',
contractID: '@mozilla.org/dom/datastore-cursor-impl;1',
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
_window: null,

View File

@ -23,7 +23,7 @@ const REVISION_VOID = "void";
// and yet we don't know if it's too low or too high.
const MAX_REQUESTS = 25;
Cu.import("resource://gre/modules/DataStoreCursor.jsm");
Cu.import("resource://gre/modules/DataStoreCursorImpl.jsm");
Cu.import("resource://gre/modules/DataStoreDB.jsm");
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
@ -67,7 +67,7 @@ this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
this.DataStore.prototype = {
classDescription: "DataStore XPCOM Component",
classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"),
contractID: "@mozilla.org/dom/datastore;1",
contractID: "@mozilla.org/dom/datastore-impl;1",
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
Components.interfaces.nsIObserver]),
@ -81,6 +81,7 @@ this.DataStore.prototype = {
_exposedObject: null,
_cursor: null,
_shuttingdown: false,
_eventTarget: null,
init: function(aWindow, aName, aOwner, aReadOnly) {
debug("DataStore init");
@ -116,6 +117,10 @@ this.DataStore.prototype = {
}
},
setEventTarget: function(aEventTarget) {
this._eventTarget = aEventTarget;
},
newDBPromise: function(aTxnType, aFunction) {
let self = this;
return new this._window.Promise(function(aResolve, aReject) {
@ -365,7 +370,7 @@ this.DataStore.prototype = {
let event = new self._window.DataStoreChangeEvent('change',
aMessage.data.message);
self.__DOM_IMPL__.dispatchEvent(event);
self._eventTarget.dispatchEvent(event);
}
);
},
@ -519,19 +524,15 @@ this.DataStore.prototype = {
);
},
set onchange(aCallback) {
debug("Set OnChange");
this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
},
get onchange() {
debug("Get OnChange");
return this.__DOM_IMPL__.getEventHandler("onchange");
},
sync: function(aRevisionId) {
debug("Sync");
this._cursor = new DataStoreCursor(this._window, this, aRevisionId);
return this._window.DataStoreCursor._create(this._window, this._cursor);
let cursorImpl = this._window.DataStoreCursorImpl.
_create(this._window, this._cursor);
let exposedCursor = new this._window.DataStoreCursor();
exposedCursor.setDataStoreCursorImpl(cursorImpl);
return exposedCursor;
}
};

View File

@ -16,7 +16,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DataStore.jsm');
Cu.import('resource://gre/modules/DataStoreImpl.jsm');
Cu.import("resource://gre/modules/DataStoreDB.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
@ -352,10 +352,15 @@ DataStoreService.prototype = {
for (let i = 0; i < aStores.length; ++i) {
let obj = new DataStore(aWindow, aStores[i].name,
aStores[i].owner, aStores[i].readOnly);
let exposedObj = aWindow.DataStore._create(aWindow, obj);
obj.exposedObject = exposedObj;
results.push(exposedObj);
let storeImpl = aWindow.DataStoreImpl._create(aWindow, obj);
let exposedStore = new aWindow.DataStore();
exposedStore.setDataStoreImpl(storeImpl);
obj.exposedObject = exposedStore;
results.push(exposedStore);
obj.retrieveRevisionId(
function() {

View File

@ -10,17 +10,33 @@ XPIDL_SOURCES += [
XPIDL_MODULE = 'dom_datastore'
EXPORTS.mozilla.dom += [
'DataStore.h',
'DataStoreCursor.h',
]
SOURCES += [
'DataStore.cpp',
'DataStoreCursor.cpp',
]
LOCAL_INCLUDES += [
'/js/xpconnect/wrappers',
]
EXTRA_COMPONENTS += [
'DataStore.manifest',
'DataStoreService.js',
]
EXTRA_JS_MODULES += [
'DataStore.jsm',
'DataStoreChangeNotifier.jsm',
'DataStoreCursor.jsm',
'DataStoreCursorImpl.jsm',
'DataStoreDB.jsm',
'DataStoreImpl.jsm',
'DataStoreServiceInternal.jsm',
]
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
FINAL_LIBRARY = 'xul'

View File

@ -4,6 +4,8 @@
# 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/.
TEST_DIRS += ['test']
PARALLEL_DIRS += ['interfaces']
EXTRA_COMPONENTS += [

View File

@ -0,0 +1,5 @@
[DEFAULT]
skip-if = e10s
[test_bug_993732.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage

View File

@ -0,0 +1,8 @@
# -*- Mode: python; c-basic-offset: 4; 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/.
MOCHITEST_MANIFESTS += ['mochitest.ini']

View File

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test Bug 993732</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<script type="application/javascript">
"use strict";
// The syndrome of Bug 993732 is that the running app (either foreground or background)
// is not able to receive system messages. Even worse, the app will be killed when the
// listening system message is broadcast. So this test case uses the alarm message
// to test if a running app can receive the system message.
function testAlarm(aMillisecondsFromNow) {
var at = new Date();
at.setTime(at.getTime() + aMillisecondsFromNow);
navigator.mozSetMessageHandler('alarm', function(message) {
ok(true, "We got alarm message!");
SimpleTest.finish();
});
var domRequest;
try {
domRequest = navigator.mozAlarms.add(at, "honorTimezone", {});
} catch (e) {
ok(false,
"Unexpected exception while adding alarm " + aMillisecondsFromNow + " ms from now.");
SimpleTest.finish();
}
domRequest.onsuccess = function(e) {
// Waiting for alarm message.
};
domRequest.onerror = function(e) {
ok(false, "Unable to add alarm for tomorrow`.");
SimpleTest.finish();
};
}
function startTests() {
SpecialPowers.pushPrefEnv({"set": [["dom.mozAlarms.enabled", true]]}, function() {
// Currently applicable only on FxOS
if (navigator.userAgent.indexOf("Mobile") != -1 &&
navigator.appVersion.indexOf("Android") == -1)
{
testAlarm(10000);
} else {
ok(true, "mozAlarms on Firefox OS only.");
SimpleTest.finish();
}
});
}
SimpleTest.expectAssertions(0, 9);
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPermissions([{'type': 'alarms', 'allow': true, 'context': document}], startTests);
</script>
</pre>
</body>
</html>

View File

@ -6,58 +6,91 @@
typedef (DOMString or unsigned long) DataStoreKey;
// TODO Bug 957086 - The constructor and the setDataStoreImpl(...) will be
// removed once the DataStore API is fully rewritten in C++,
// which currently plays a role of C++ proxy directing to the
// JS codes implemented by the DataStoreImpl WebIDL.
[Func="Navigator::HasDataStoreSupport",
JSImplementation="@mozilla.org/dom/datastore;1"]
ChromeConstructor]
interface DataStore : EventTarget {
// Returns the label of the DataSource.
[GetterThrows]
readonly attribute DOMString name;
// Returns the origin of the DataSource (e.g., 'facebook.com').
// This value is the manifest URL of the owner app.
[GetterThrows]
readonly attribute DOMString owner;
// is readOnly a F(current_app, datastore) function? yes
[GetterThrows]
readonly attribute boolean readOnly;
// Promise<any>
[Throws]
Promise get(DataStoreKey... id);
// Promise<void>
[Throws]
Promise put(any obj, DataStoreKey id, optional DOMString revisionId = "");
// Promise<DataStoreKey>
[Throws]
Promise add(any obj, optional DataStoreKey id,
optional DOMString revisionId = "");
// Promise<boolean>
[Throws]
Promise remove(DataStoreKey id, optional DOMString revisionId = "");
// Promise<void>
[Throws]
Promise clear(optional DOMString revisionId = "");
[GetterThrows]
readonly attribute DOMString revisionId;
attribute EventHandler onchange;
// Promise<unsigned long>
[Throws]
Promise getLength();
[NewObject, Throws]
DataStoreCursor sync(optional DOMString revisionId = "");
};
[Pref="dom.datastore.enabled",
JSImplementation="@mozilla.org/dom/datastore-cursor;1"]
interface DataStoreCursor {
partial interface DataStore {
[ChromeOnly, Throws]
void setDataStoreImpl(DataStoreImpl store);
};
// TODO Bug 957086 - The constructor and the setDataStoreCursorImpl(...) will be
// removed once the DataStore API is fully rewritten in C++,
// which currently plays a role of C++ proxy directing to the
// JS codes implemented by the DataStoreCursorImpl WebIDL.
[Pref="dom.datastore.enabled",
ChromeConstructor]
interface DataStoreCursor {
// the DataStore
[GetterThrows]
readonly attribute DataStore store;
// Promise<DataStoreTask>
[Throws]
Promise next();
[Throws]
void close();
};
partial interface DataStoreCursor {
[ChromeOnly]
void setDataStoreCursorImpl(DataStoreCursorImpl cursor);
};
enum DataStoreOperation {
"add",
"update",

View File

@ -0,0 +1,68 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
// TODO Bug 957086 - The DataStoreImpl WebIDL will be removed once the
// DataStore API is fully rewritten in C++ (i.e. should be
// directly implemented by the DataStore WebIDL).
[HeaderFile="mozilla/dom/DataStore.h",
Func="mozilla::dom::DataStore::EnabledForScope",
JSImplementation="@mozilla.org/dom/datastore-impl;1"]
interface DataStoreImpl {
void setEventTarget(EventTarget eventTarget);
// Returns the label of the DataSource.
readonly attribute DOMString name;
// Returns the origin of the DataSource (e.g., 'facebook.com').
// This value is the manifest URL of the owner app.
readonly attribute DOMString owner;
// is readOnly a F(current_app, datastore) function? yes
readonly attribute boolean readOnly;
// Promise<any>
Promise get(DataStoreKey... id);
// Promise<void>
Promise put(any obj, DataStoreKey id, optional DOMString revisionId = "");
// Promise<DataStoreKey>
Promise add(any obj, optional DataStoreKey id,
optional DOMString revisionId = "");
// Promise<boolean>
Promise remove(DataStoreKey id, optional DOMString revisionId = "");
// Promise<void>
Promise clear(optional DOMString revisionId = "");
readonly attribute DOMString revisionId;
// Promise<unsigned long>
Promise getLength();
[NewObject]
DataStoreCursor sync(optional DOMString revisionId = "");
};
// TODO Bug 957086 - The DataStoreCursorImpl WebIDL will be removed once the
// DataStore API is fully rewritten in C++ (i.e. should be
// directly implemented by the DataStoreCursor WebIDL).
[HeaderFile="mozilla/dom/DataStore.h",
Func="mozilla::dom::DataStore::EnabledForScope",
JSImplementation="@mozilla.org/dom/datastore-cursor-impl;1"]
interface DataStoreCursorImpl {
// the DataStore
readonly attribute DataStore store;
// Promise<DataStoreTask>
Promise next();
void close();
};

View File

@ -68,6 +68,7 @@ WEBIDL_FILES = [
'CSSValueList.webidl',
'DataContainerEvent.webidl',
'DataStore.webidl',
'DataStoreImpl.webidl',
'DataTransfer.webidl',
'DedicatedWorkerGlobalScope.webidl',
'DelayNode.webidl',

View File

@ -20,9 +20,6 @@
#define CHROMIUM_LOG(args...) printf(args);
#endif
/* TODO: Remove BlueZ constant */
#define BLUEZ_DBUS_BASE_IFC "org.bluez"
namespace mozilla {
namespace ipc {
@ -334,6 +331,7 @@ bool RawDBusConnection::SendWithReply(DBusReplyCallback aCallback,
bool RawDBusConnection::SendWithReply(DBusReplyCallback aCallback,
void* aData,
int aTimeout,
const char* aDestination,
const char* aPath,
const char* aIntf,
const char* aFunc,
@ -344,7 +342,7 @@ bool RawDBusConnection::SendWithReply(DBusReplyCallback aCallback,
va_list args;
va_start(args, aFirstArgType);
DBusMessage* msg = BuildDBusMessage(aPath, aIntf, aFunc,
DBusMessage* msg = BuildDBusMessage(aDestination, aPath, aIntf, aFunc,
aFirstArgType, args);
va_end(args);
@ -355,14 +353,16 @@ bool RawDBusConnection::SendWithReply(DBusReplyCallback aCallback,
return SendWithReply(aCallback, aData, aTimeout, msg);
}
DBusMessage* RawDBusConnection::BuildDBusMessage(const char* aPath,
DBusMessage* RawDBusConnection::BuildDBusMessage(const char* aDestination,
const char* aPath,
const char* aIntf,
const char* aFunc,
int aFirstArgType,
va_list aArgs)
{
DBusMessage* msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
aPath, aIntf, aFunc);
DBusMessage* msg = dbus_message_new_method_call(aDestination,
aPath, aIntf,
aFunc);
if (!msg) {
CHROMIUM_LOG("Could not allocate D-Bus message object!");
return nullptr;

View File

@ -44,11 +44,14 @@ public:
int aTimeout, DBusMessage* aMessage);
bool SendWithReply(DBusReplyCallback aCallback, void* aData,
int aTimeout, const char* aPath, const char* aIntf,
int aTimeout,
const char* aDestination,
const char* aPath, const char* aIntf,
const char *aFunc, int aFirstArgType, ...);
protected:
DBusMessage* BuildDBusMessage(const char* aPath, const char* aIntf,
DBusMessage* BuildDBusMessage(const char* aDestination,
const char* aPath, const char* aIntf,
const char* aFunc, int aFirstArgType,
va_list args);

View File

@ -154,7 +154,6 @@ public abstract class GeckoApp
public static final String EXTRA_STATE_BUNDLE = "stateBundle";
public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
public static final String PREFS_CRASHED = "crashed";
public static final String PREFS_OOM_EXCEPTION = "OOMException";
public static final String PREFS_VERSION_CODE = "versionCode";
public static final String PREFS_WAS_STOPPED = "wasStopped";
@ -1772,9 +1771,12 @@ public abstract class GeckoApp
});
shouldRestore = true;
} else if (savedInstanceState != null || getSessionRestorePreference().equals("always") || getRestartFromIntent()) {
} else if (savedInstanceState != null ||
getSessionRestorePreference().equals("always") ||
getRestartFromIntent() ||
prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, false)) {
// We're coming back from a background kill by the OS, the user
// has chosen to always restore, or we just restarted.
// has chosen to always restore, we restarted, or we crashed.
shouldRestore = true;
}

View File

@ -690,7 +690,6 @@ sync_java_files = [
'sync/NullClusterURLException.java',
'sync/PersistedMetaGlobal.java',
'sync/PrefsBackoffHandler.java',
'sync/PrefsSource.java',
'sync/receivers/SyncAccountDeletedReceiver.java',
'sync/receivers/SyncAccountDeletedService.java',
'sync/receivers/UpgradeReceiver.java',

View File

@ -176,32 +176,35 @@ public class FxAccountClient10 {
protected final byte[] tokenId;
protected final byte[] reqHMACKey;
protected final boolean payload;
protected final SkewHandler skewHandler;
/**
* Create a delegate for an un-authenticated resource.
*/
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
this(resource, delegate, null, null, false);
this(resource, delegate, null, null);
}
/**
* Create a delegate for a Hawk-authenticated resource.
* <p>
* Every Hawk request that encloses an entity (PATCH, POST, and PUT) will
* include the payload verification hash.
*/
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey, final boolean authenticatePayload) {
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey) {
super(resource);
this.delegate = delegate;
this.reqHMACKey = reqHMACKey;
this.tokenId = tokenId;
this.payload = authenticatePayload;
this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
}
@Override
public AuthHeaderProvider getAuthHeaderProvider() {
if (tokenId != null && reqHMACKey != null) {
return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, payload, skewHandler.getSkewInSeconds());
// We always include the payload verification hash for FxA Hawk-authenticated requests.
final boolean includePayloadVerificationHash = true;
return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, includePayloadVerificationHash, skewHandler.getSkewInSeconds());
}
return super.getAuthHeaderProvider();
}
@ -483,7 +486,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<TwoTokens>(resource, delegate, tokenId, reqHMACKey, false) {
resource.delegate = new ResourceDelegate<TwoTokens>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {
@ -519,7 +522,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
delegate.handleSuccess(null);
@ -608,7 +611,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey, false) {
resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {
@ -670,7 +673,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey, false) {
resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {
@ -712,7 +715,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey, true) {
resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
String cert = body.getString("cert");
@ -753,7 +756,7 @@ public class FxAccountClient10 {
return;
}
resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {

View File

@ -5,7 +5,10 @@
package org.mozilla.gecko.fxa.activities;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
@ -24,8 +27,11 @@ import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
@ -35,6 +41,8 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
@ -54,7 +62,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
protected int minimumPasswordLength = 8;
protected EditText emailEdit;
protected AutoCompleteTextView emailEdit;
protected EditText passwordEdit;
protected Button showPasswordButton;
protected TextView remoteErrorTextView;
@ -312,4 +320,54 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
protected PasswordStretcher makePasswordStretcher(String password) {
return new QuickPasswordStretcher(password);
}
protected abstract static class GetAccountsAsyncTask extends AsyncTask<Void, Void, Account[]> {
protected final Context context;
public GetAccountsAsyncTask(Context context) {
super();
this.context = context;
}
@Override
protected Account[] doInBackground(Void... params) {
return AccountManager.get(context).getAccounts();
}
}
/**
* This updates UI, so needs to be done on the foreground thread.
*/
protected void populateEmailAddressAutocomplete(Account[] accounts) {
// First a set, since we don't want repeats.
final Set<String> emails = new HashSet<String>();
for (Account account : accounts) {
if (!Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
continue;
}
emails.add(account.name);
}
// And then sorted in alphabetical order.
final String[] sortedEmails = emails.toArray(new String[0]);
Arrays.sort(sortedEmails);
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails);
emailEdit.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
// Getting Accounts accesses databases on disk, so needs to be done on a
// background thread.
final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) {
@Override
public void onPostExecute(Account[] accounts) {
populateEmailAddressAutocomplete(accounts);
}
};
task.execute();
}
}

View File

@ -35,6 +35,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
@ -66,7 +67,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
super.onCreate(icicle);
setContentView(R.layout.fxaccount_create_account);
emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit");

View File

@ -1,191 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.fxa.activities;
import android.support.v4.app.Fragment;
public class FxAccountCreateAccountFragment extends Fragment { // implements OnClickListener {
protected static final String LOG_TAG = FxAccountCreateAccountFragment.class.getSimpleName();
//
// protected FxAccountSetupActivity activity;
//
// protected EditText emailEdit;
// protected EditText passwordEdit;
// protected EditText password2Edit;
// protected Button button;
//
// protected TextView emailError;
// protected TextView passwordError;
//
// protected TextChangedListener textChangedListener;
// protected EditorActionListener editorActionListener;
// protected OnFocusChangeListener focusChangeListener;
// @Override
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// // Retain this fragment across configuration changes. See, for example,
// // http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
// // This fragment will own AsyncTask instances which should not be
// // interrupted by configuration changes (and activity changes).
// setRetainInstance(true);
// }
// @Override
// public View onCreateView(LayoutInflater inflater, ViewGroup container,
// Bundle savedInstanceState) {
// View v = inflater.inflate(R.layout.fxaccount_create_account_fragment, container, false);
//
// FxAccountSetupActivity.linkifyTextViews(v, new int[] { R.id.description, R.id.policy });
//
// emailEdit = (EditText) ensureFindViewById(v, R.id.email, "email");
// passwordEdit = (EditText) ensureFindViewById(v, R.id.password, "password");
// // Second password can be null.
// password2Edit = (EditText) v.findViewById(R.id.password2);
//
// emailError = (TextView) ensureFindViewById(v, R.id.email_error, "email error");
// passwordError = (TextView) ensureFindViewById(v, R.id.password_error, "password error");
//
// textChangedListener = new TextChangedListener();
// editorActionListener = new EditorActionListener();
// focusChangeListener = new FocusChangeListener();
//
// addListeners(emailEdit);
// addListeners(passwordEdit);
// if (password2Edit != null) {
// addListeners(password2Edit);
// }
//
// button = (Button) ensureFindViewById(v, R.id.create_account_button, "button");
// button.setOnClickListener(this);
// return v;
// }
// protected void onCreateAccount(View button) {
// Logger.debug(LOG_TAG, "onCreateAccount: Asking for username/password for new account.");
// String email = emailEdit.getText().toString();
// String password = passwordEdit.getText().toString();
// activity.signUp(email, password);
// }
//
// @Override
// public void onClick(View v) {
// switch (v.getId()) {
// case R.id.create_account_button:
// if (!validate(false)) {
// return;
// }
// onCreateAccount(v);
// break;
// }
// }
//
// protected void addListeners(EditText editText) {
// editText.addTextChangedListener(textChangedListener);
// editText.setOnEditorActionListener(editorActionListener);
// editText.setOnFocusChangeListener(focusChangeListener);
// }
//
// protected class FocusChangeListener implements OnFocusChangeListener {
// @Override
// public void onFocusChange(View v, boolean hasFocus) {
// if (hasFocus) {
// return;
// }
// validate(false);
// }
// }
//
// protected class EditorActionListener implements OnEditorActionListener {
// @Override
// public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// validate(false);
// return false;
// }
// }
//
// protected class TextChangedListener implements TextWatcher {
// @Override
// public void afterTextChanged(Editable s) {
// validate(true);
// }
//
// @Override
// public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// // Do nothing.
// }
//
// @Override
// public void onTextChanged(CharSequence s, int start, int before, int count) {
// // Do nothing.
// }
// }
//
// /**
// * Show or hide error messaging.
// *
// * @param removeOnly
// * if true, possibly remove existing error messages but do not set an
// * error message if one was not present.
// * @param errorResourceId
// * of error string, or -1 to hide.
// * @param errorView
// * <code>TextView</code> instance to display error message in.
// * @param edits
// * <code>EditText</code> instances to style.
// */
// protected void setError(boolean removeOnly, int errorResourceId, TextView errorView, EditText... edits) {
// if (removeOnly && errorResourceId != -1) {
// return;
// }
//
// int res = errorResourceId == -1 ? R.drawable.fxaccount_textfield_background : R.drawable.fxaccount_textfield_error_background;
// for (EditText edit : edits) {
// if (edit == null) {
// continue;
// }
// edit.setBackgroundResource(res);
// }
// if (errorResourceId == -1) {
// errorView.setVisibility(View.GONE);
// errorView.setText(null);
// } else {
// errorView.setText(errorResourceId);
// errorView.setVisibility(View.VISIBLE);
// }
// }
//
// protected boolean validate(boolean removeOnly) {
// boolean enabled = true;
// final String email = emailEdit.getText().toString();
// final String password = passwordEdit.getText().toString();
//
// if (email.length() == 0 || Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
// setError(removeOnly, -1, emailError, emailEdit);
// } else {
// enabled = false;
// setError(removeOnly, R.string.fxaccount_bad_email, emailError, emailEdit);
// }
//
// if (password2Edit != null) {
// final String password2 = password2Edit.getText().toString();
// enabled = enabled && password2.length() > 0;
//
// boolean passwordsMatch = password.equals(password2);
// if (passwordsMatch) {
// setError(removeOnly, -1, passwordError, passwordEdit, password2Edit);
// } else {
// enabled = false;
// setError(removeOnly, R.string.fxaccount_bad_passwords, passwordError, passwordEdit, password2Edit);
// }
// }
//
// if (enabled != button.isEnabled()) {
// Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
// button.setEnabled(enabled);
// }
//
// return enabled;
// }
}

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
@ -46,7 +47,7 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
super.onCreate(icicle);
setContentView(R.layout.fxaccount_sign_in);
emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");

View File

@ -28,6 +28,7 @@ import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
@ -59,7 +60,7 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
super.onCreate(icicle);
setContentView(R.layout.fxaccount_update_credentials);
emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");

View File

@ -5,13 +5,11 @@
package org.mozilla.gecko.fxa.sync;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -25,16 +23,12 @@ import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import android.content.Context;
public class FxAccountGlobalSession extends GlobalSession {
private static final String LOG_TAG = FxAccountGlobalSession.class.getSimpleName();
public FxAccountGlobalSession(String storageEndpoint, SyncConfiguration config, BaseGlobalSessionCallback callback,
Context context, ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException, URISyntaxException {
public FxAccountGlobalSession(SyncConfiguration config,
BaseGlobalSessionCallback callback,
Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException, URISyntaxException {
super(config, callback, context, clientsDelegate, null);
URI storageURI = new URI(storageEndpoint);
this.config.setClusterURL(storageURI);
FxAccountConstants.pii(LOG_TAG, "clusterURL is " + config.getClusterURLString());
}
@Override

View File

@ -20,8 +20,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.SkewHandler;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
@ -35,7 +33,6 @@ import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.fxa.login.StateFactory;
import org.mozilla.gecko.sync.BackoffHandler;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.PrefsBackoffHandler;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
@ -368,15 +365,21 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
// global session.
final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, storageServerSkew);
// We expect Sync to upload large sets of records. Calculating the
// payload verification hash for these record sets could be expensive,
// so we explicitly do not send payload verification hashes to the
// Sync storage endpoint.
final boolean includePayloadVerificationHash = false;
final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), includePayloadVerificationHash, storageServerSkew);
final Context context = getContext();
final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
syncConfig.setClusterURL(storageServerURI);
globalSession = new FxAccountGlobalSession(token.endpoint, syncConfig, callback, context, clientsDataDelegate);
globalSession = new FxAccountGlobalSession(syncConfig, callback, context, clientsDataDelegate);
globalSession.start();
} catch (Exception e) {
callback.handleError(globalSession, e);
@ -386,7 +389,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void handleFailure(TokenServerException e) {
debugAssertion(audience, assertion);
handleError(e);
}
@ -611,34 +613,4 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
Logger.info(LOG_TAG, "Syncing done.");
lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
}
protected void debugAssertion(String audience, String assertion) {
final CountDownLatch verifierLatch = new CountDownLatch(1);
BrowserIDRemoteVerifierClient client = new BrowserIDRemoteVerifierClient(URI.create(BrowserIDRemoteVerifierClient.DEFAULT_VERIFIER_URL));
client.verify(audience, assertion, new BrowserIDVerifierDelegate() {
@Override
public void handleSuccess(ExtendedJSONObject response) {
Logger.info(LOG_TAG, "Remote verifier returned success: " + response.toJSONString());
verifierLatch.countDown();
}
@Override
public void handleFailure(ExtendedJSONObject response) {
Logger.warn(LOG_TAG, "Remote verifier returned failure: " + response.toJSONString());
verifierLatch.countDown();
}
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Remote verifier returned error.", e);
verifierLatch.countDown();
}
});
try {
verifierLatch.await();
} catch (InterruptedException e) {
Logger.error(LOG_TAG, "Got error.", e);
}
}
}

View File

@ -54,6 +54,7 @@
<!ENTITY back "Back">
<!ENTITY stop "Stop">
<!ENTITY site_security "Site Security">
<!ENTITY edit_mode_cancel "Cancel">
<!ENTITY close_tab "Close Tab">
<!ENTITY one_tab "1 tab">

View File

@ -61,7 +61,9 @@ public class MenuItemActionView extends LinearLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
View parent = (View) getParent();
if ((right - left) < parent.getMeasuredWidth() || mActionButtons.size() != 0) {
final int padding = getPaddingLeft() + getPaddingRight();
final int parentPadding = parent.getPaddingLeft() + parent.getPaddingRight();
if ((right - left - padding) < (parent.getMeasuredWidth() - parentPadding) || mActionButtons.size() != 0) {
// Use the icon.
mMenuItem.setVisibility(View.GONE);
mMenuButton.setVisibility(View.VISIBLE);

View File

@ -439,6 +439,7 @@ gbjar.generated_sources += [
'org/mozilla/gecko/widget/ThemedRelativeLayout.java',
'org/mozilla/gecko/widget/ThemedTextSwitcher.java',
'org/mozilla/gecko/widget/ThemedTextView.java',
'org/mozilla/gecko/widget/ThemedView.java',
]
if CONFIG['MOZ_CRASHREPORTER']:
gbjar.sources += [ 'CrashReporter.java' ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/white" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
</shape>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/fxaccount_password_hide_backgroundcolor" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/fxaccount_password_show_backgroundcolor" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@android:color/white" />
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderInactive" />
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

View File

@ -10,10 +10,13 @@
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
right are swapped. These values correct this bug; the resources
that don't need correction are in res/drawable-v12. -->
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -10,10 +10,13 @@
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
right are swapped. These values correct this bug; the resources
that don't need correction are in res/drawable-v12. -->
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
android:bottomRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -10,10 +10,13 @@
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderActive" />
<!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
right are swapped. These values correct this bug; the resources
that don't need correction are in res/drawable-v12. -->
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="0dp"
android:topRightRadius="@dimen/fxaccount_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
android:bottomRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -10,10 +10,13 @@
<stroke
android:width="@dimen/fxaccount_stroke_width"
android:color="@color/fxaccount_input_borderInactive" />
<!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
right are swapped. These values correct this bug; the resources
that don't need correction are in res/drawable-v12. -->
<corners
android:radius="@dimen/fxaccount_corner_radius"
android:topLeftRadius="@dimen/fxaccount_corner_radius"
android:topRightRadius="0dp"
android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
android:bottomRightRadius="0dp" />
android:bottomLeftRadius="0dp"
android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#a6aeb4"/>
<size android:width="1dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#222"/>
<size android:width="1dp" />
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<!-- Note that android:color="@color/toolbar_separator" (which uses a selector)
directly in the <shape> does not appear to work, so instead we select
between two shapes with different colors. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<item gecko:state_private="true" android:drawable="@drawable/toolbar_separator_pb"/>
<item android:drawable="@drawable/toolbar_separator"/>
</selector>

View File

@ -12,6 +12,13 @@
<ImageButton android:id="@+id/forward"
style="@style/UrlBar.ImageButton.Unused"/>
<!-- Note: any layout parameters setting the right edge of
this View should be matched in the url_bar_translating_edge.
Note 2: On devices where the editing mode cancel items are
wider than the tabs and similar buttons (e.g. hardware menu
button), the url bar will shrink, in which case the LayoutParams
of this View are changed dynamically. -->
<ImageView android:id="@+id/url_bar_entry"
style="@style/UrlBar.Button"
android:layout_marginLeft="4dp"
@ -27,18 +34,22 @@
android:src="@drawable/url_bar_entry"
android:scaleType="fitXY"/>
<ImageView android:id="@+id/url_bar_right_edge"
<!-- A View that clips with url_bar_entry and translates
around it to animate shrinking or growing the url bar,
which occurs in the display/editing mode transitions. -->
<ImageView android:id="@+id/url_bar_translating_edge"
style="@style/UrlBar.Button"
android:layout_alignLeft="@id/url_bar_entry"
android:layout_alignRight="@id/url_bar_entry"
android:layout_toLeftOf="@+id/tabs"
android:layout_alignTop="@id/url_bar_entry"
android:layout_alignBottom="@id/url_bar_entry"
android:layout_marginRight="-19dp"
android:paddingRight="4dp"
android:duplicateParentState="true"
android:clickable="false"
android:focusable="false"
android:visibility="invisible"
android:src="@drawable/url_bar_right_edge"
android:src="@drawable/url_bar_translating_edge"
android:scaleType="fitXY"/>
<LinearLayout android:id="@+id/menu_items"
@ -86,8 +97,29 @@
android:layout_marginTop="12dip"
android:layout_alignRight="@id/tabs"/>
<!-- Note that the edit components are invisible so that the views
depending on their location can properly layout. -->
<ImageView android:id="@+id/edit_cancel"
style="@style/UrlBar.ImageButton.Icon"
android:layout_alignParentRight="true"
android:src="@drawable/close_edit_mode"
android:contentDescription="@string/edit_mode_cancel"
android:visibility="invisible"/>
<org.mozilla.gecko.widget.ThemedView android:id="@+id/edit_separator"
android:layout_toLeftOf="@id/edit_cancel"
android:layout_width="1dip"
android:layout_height="match_parent"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="2dp"
android:background="@drawable/toolbar_separator_selector"
android:visibility="invisible"/>
<org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
style="@style/UrlBar.Button"
android:layout_toLeftOf="@id/edit_separator"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:paddingLeft="8dp"

View File

@ -12,16 +12,17 @@
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
<AutoCompleteTextView
android:id="@+id/email"
style="@style/FxAccountEditItem"
android:layout_marginBottom="10dp"
android:completionThreshold="2"
android:ems="10"
android:hint="@string/fxaccount_email_hint"
android:inputType="textEmailAddress" >
<requestFocus />
</EditText>
</AutoCompleteTextView>
<LinearLayout
android:layout_width="fill_parent"
@ -39,20 +40,46 @@
android:hint="@string/fxaccount_password_hint"
android:inputType="textPassword" />
<Button
android:id="@+id/show_password"
style="@android:style/Widget.Button"
<!-- For the following, I beg forgiveness. The show/hide button is a
toggle button; its text depends on its state. The text for each
state could be a different length. We want to maintain the
button's width regardless of its state. To achieve this, we
size the actual button to its container, and include two
invisible (but present for layout purposes) buttons, one of
each state. The container wraps the larger of the two dummy
buttons; the actual button sizes to the container; and we're
happy. Be thankful there are not three buttons! -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="0"
android:background="@drawable/fxaccount_password_button_show_background"
android:minHeight="0dp"
android:padding="0dp"
android:text="@string/fxaccount_password_show"
android:textColor="@color/fxaccount_input_textColor"
android:textSize="20sp" >
</Button>
android:orientation="horizontal" >
<Button
android:id="@+id/show_password"
style="@style/FxAccountShowHidePasswordButton"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/fxaccount_password_show" >
</Button>
<Button
style="@style/FxAccountShowHidePasswordButton"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="@string/fxaccount_password_show"
android:visibility="invisible" >
</Button>
<Button
style="@style/FxAccountShowHidePasswordButton"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="@string/fxaccount_password_hide"
android:visibility="invisible" >
</Button>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</merge>
</merge>

View File

@ -13,11 +13,20 @@
<item name="android:minWidth">500dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_gravity">center</item>
<!-- layout_gravity controls where the middle container goes
in its parent, and gravity controls where the container
places its internal content. Usually, the middle
container is a LinearLayout and the container is a
ScrollView. In this case, layout_gravity="center"
interacts badly with vertical scrolling. What is needed
is layout_gravity="center_horizontal" and
gravity="center". -->
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:paddingTop">25dp</item>
<item name="android:paddingLeft">12dp</item>
<item name="android:paddingRight">12dp</item>
<item name="android:paddingBottom">15dp</item>
</style>
</resources>
</resources>

View File

@ -14,6 +14,11 @@
fields. -->
<dimen name="fxaccount_input_padding_vertical">10dp</dimen>
<!-- The amount of horizontal padding that appears around the show/hide
password button. Vertical padding is provided by the adjacent input
field. -->
<dimen name="fxaccount_show_password_padding_horizontal">8dp</dimen>
<!-- Preference fragment padding, bottom -->
<dimen name="preference_fragment_padding_bottom">0dp</dimen>
<!-- Preference fragment padding, sides -->

Some files were not shown because too many files have changed in this diff Show More