Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-12-10 13:01:03 +01:00
commit 2a69ec4f96
88 changed files with 1061 additions and 429 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -135,7 +135,7 @@
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
<project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
<project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
<project name="kernel/common" path="kernel" revision="291a7d55be6b0117786bf4d25366186c301185c2"/>
<project name="kernel/common" path="kernel" revision="6c6f012cea17fb8b3263605737816cf6663432f1"/>
<project name="platform/system/core" path="system/core" revision="53d584d4a4b4316e4de9ee5f210d662f89b44e7e"/>
<project name="u-boot" path="u-boot" revision="982c1fd67b89d5573317c1796cf5b0143de44e8a"/>
<project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="6974f8e771d4d8e910357a6739ab124768891e8f"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a3e4614c7fb0a6ffa8c748bf5d49b34612c9d6d4"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a3e4614c7fb0a6ffa8c748bf5d49b34612c9d6d4"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "0e1227044422a4fae14ec6b1d035380d693cff97",
"revision": "7736b561cce1e1738de0901b796a1da3cbcc8985",
"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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a3e4614c7fb0a6ffa8c748bf5d49b34612c9d6d4"/>

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="4cdeee67b449db90aae9384337311547c280093c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e17c5656dbf517d48fb61ac9bc92119e023fd717"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -64,7 +64,7 @@
<button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
<button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
<button class="launchButton" id="history">&abouthome.historyButton.label;</button>
<button class="launchButton" id="apps" hidden="true">&abouthome.appsButton.label;</button>
<button class="launchButton" id="apps">&abouthome.appsButton.label;</button>
<button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
<button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
#ifdef XP_WIN

View File

@ -2550,9 +2550,9 @@ let gMenuButtonUpdateBadge = {
updateButtonText = gNavigatorBundle.getFormattedString(stringId,
[brandShortName]);
updateButton.label = updateButtonText;
updateButton.hidden = false;
updateButton.setAttribute("label", updateButtonText);
updateButton.setAttribute("update-status", "succeeded");
updateButton.hidden = false;
PanelUI.panel.addEventListener("popupshowing", this, true);
@ -2565,9 +2565,9 @@ let gMenuButtonUpdateBadge = {
stringId = "appmenu.updateFailed.description";
updateButtonText = gNavigatorBundle.getString(stringId);
updateButton.label = updateButtonText;
updateButton.hidden = false;
updateButton.setAttribute("label", updateButtonText);
updateButton.setAttribute("update-status", "failed");
updateButton.hidden = false;
PanelUI.panel.addEventListener("popupshowing", this, true);

View File

@ -327,12 +327,6 @@ let AboutHomeListener = {
doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui");
}
// XXX bug 738646 - when Marketplace is launched, remove this statement and
// the hidden attribute set on the apps button in aboutHome.xhtml
if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL &&
Services.prefs.getBoolPref("browser.aboutHome.apps"))
doc.getElementById("apps").removeAttribute("hidden");
sendAsyncMessage("AboutHome:RequestUpdate");
doc.addEventListener("AboutHomeSearchEvent", this, true, true);
doc.addEventListener("AboutHomeSearchPanel", this, true, true);

View File

@ -258,7 +258,10 @@ loop.contacts = (function(_, mozL10n) {
});
const ContactsList = React.createClass({displayName: 'ContactsList',
mixins: [React.addons.LinkedStateMixin],
mixins: [
React.addons.LinkedStateMixin,
loop.shared.mixins.WindowCloseMixin
],
/**
* Contacts collection object
@ -435,11 +438,13 @@ loop.contacts = (function(_, mozL10n) {
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.closeWindow();
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.closeWindow();
}
break;
default:

View File

@ -258,7 +258,10 @@ loop.contacts = (function(_, mozL10n) {
});
const ContactsList = React.createClass({
mixins: [React.addons.LinkedStateMixin],
mixins: [
React.addons.LinkedStateMixin,
loop.shared.mixins.WindowCloseMixin
],
/**
* Contacts collection object
@ -435,11 +438,13 @@ loop.contacts = (function(_, mozL10n) {
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.closeWindow();
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.closeWindow();
}
break;
default:

View File

@ -223,7 +223,7 @@ loop.conversation = (function(mozL10n) {
* At the moment, it does more than that, these parts need refactoring out.
*/
var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
mixins: [sharedMixins.AudioMixin],
mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
propTypes: {
client: React.PropTypes.instanceOf(loop.Client).isRequired,
@ -315,7 +315,7 @@ loop.conversation = (function(mozL10n) {
);
}
case "close": {
window.close();
this.closeWindow();
return (React.DOM.div(null));
}
}
@ -459,10 +459,6 @@ loop.conversation = (function(mozL10n) {
setTimeout(this.closeWindow, 0);
},
closeWindow: function() {
window.close();
},
/**
* Accepts an incoming call.
*/
@ -541,7 +537,7 @@ loop.conversation = (function(mozL10n) {
* in progress, and hence, which view to display.
*/
var AppControllerView = React.createClass({displayName: 'AppControllerView',
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
propTypes: {
// XXX Old types required for incoming call view.
@ -575,10 +571,6 @@ loop.conversation = (function(mozL10n) {
this.stopListening(this.props.conversationAppStore);
},
closeWindow: function() {
window.close();
},
render: function() {
switch(this.state.windowType) {
case "incoming": {

View File

@ -223,7 +223,7 @@ loop.conversation = (function(mozL10n) {
* At the moment, it does more than that, these parts need refactoring out.
*/
var IncomingConversationView = React.createClass({
mixins: [sharedMixins.AudioMixin],
mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
propTypes: {
client: React.PropTypes.instanceOf(loop.Client).isRequired,
@ -315,7 +315,7 @@ loop.conversation = (function(mozL10n) {
);
}
case "close": {
window.close();
this.closeWindow();
return (<div/>);
}
}
@ -459,10 +459,6 @@ loop.conversation = (function(mozL10n) {
setTimeout(this.closeWindow, 0);
},
closeWindow: function() {
window.close();
},
/**
* Accepts an incoming call.
*/
@ -541,7 +537,7 @@ loop.conversation = (function(mozL10n) {
* in progress, and hence, which view to display.
*/
var AppControllerView = React.createClass({
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
propTypes: {
// XXX Old types required for incoming call view.
@ -575,10 +571,6 @@ loop.conversation = (function(mozL10n) {
this.stopListening(this.props.conversationAppStore);
},
closeWindow: function() {
window.close();
},
render: function() {
switch(this.state.windowType) {
case "incoming": {

View File

@ -193,7 +193,11 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({displayName: 'CallFailedView',
mixins: [Backbone.Events, sharedMixins.AudioMixin],
mixins: [
Backbone.Events,
sharedMixins.AudioMixin,
sharedMixins.WindowCloseMixin
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -227,7 +231,7 @@ loop.conversationViews = (function(mozL10n) {
var emailLink = this.props.store.getStoreState("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
this.closeWindow();
},
_onEmailLinkError: function() {

View File

@ -193,7 +193,11 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails.
*/
var CallFailedView = React.createClass({
mixins: [Backbone.Events, sharedMixins.AudioMixin],
mixins: [
Backbone.Events,
sharedMixins.AudioMixin,
sharedMixins.WindowCloseMixin
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@ -227,7 +231,7 @@ loop.conversationViews = (function(mozL10n) {
var emailLink = this.props.store.getStoreState("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
this.closeWindow();
},
_onEmailLinkError: function() {

View File

@ -595,6 +595,8 @@ loop.panel = (function(_, mozL10n) {
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
},
mixins: [loop.shared.mixins.WindowCloseMixin],
getInitialState: function() {
return { urlCopied: false };
},
@ -609,6 +611,7 @@ loop.panel = (function(_, mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
this.closeWindow();
},
handleCopyButtonClick: function(event) {

View File

@ -595,6 +595,8 @@ loop.panel = (function(_, mozL10n) {
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
},
mixins: [loop.shared.mixins.WindowCloseMixin],
getInitialState: function() {
return { urlCopied: false };
},
@ -609,6 +611,7 @@ loop.panel = (function(_, mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
this.closeWindow();
},
handleCopyButtonClick: function(event) {

View File

@ -16,13 +16,15 @@ loop.shared.mixins = (function() {
var rootObject = window;
/**
* Sets a new root object. This is useful for testing native DOM events so we
* can fake them.
* Sets a new root object. This is useful for testing native DOM events so we
* can fake them. In beforeEach(), loop.shared.mixins.setRootObject is used to
* substitute a fake window, and in afterEach(), the real window object is
* replaced.
*
* @param {Object}
*/
function setRootObject(obj) {
console.info("loop.shared.mixins: rootObject set to " + obj);
console.log("loop.shared.mixins: rootObject set to " + obj);
rootObject = obj;
}
@ -64,6 +66,21 @@ loop.shared.mixins = (function() {
}
};
/**
* Window close mixin, for more testable closing of windows. Instead of
* calling window.close() directly, use this mixin and call
* this.closeWindow from your component.
*
* @type {Object}
*
* @see setRootObject for info on how to unit test code that uses this mixin
*/
var WindowCloseMixin = {
closeWindow: function() {
rootObject.close();
}
};
/**
* Dropdown menu mixin.
* @type {Object}
@ -291,6 +308,7 @@ loop.shared.mixins = (function() {
DocumentVisibilityMixin: DocumentVisibilityMixin,
DocumentLocationMixin: DocumentLocationMixin,
DocumentTitleMixin: DocumentTitleMixin,
UrlHashChangeMixin: UrlHashChangeMixin
UrlHashChangeMixin: UrlHashChangeMixin,
WindowCloseMixin: WindowCloseMixin
};
})();

View File

@ -14,8 +14,11 @@ describe("loop.contacts", function() {
var fakeAddContactButtonText = "Fake Add Contact";
var fakeEditContactButtonText = "Fake Edit Contact";
var fakeDoneButtonText = "Fake Done";
var sandbox;
var fakeWindow;
beforeEach(function(done) {
sandbox = sinon.sandbox.create();
navigator.mozLoop = {
getStrings: function(entityName) {
var textContentValue = "fakeText";
@ -30,11 +33,58 @@ describe("loop.contacts", function() {
},
};
fakeWindow = {
close: sandbox.stub(),
};
loop.shared.mixins.setRootObject(fakeWindow);
document.mozL10n.initialize(navigator.mozLoop);
// XXX prevent a race whenever mozL10n hasn't been initialized yet
setTimeout(done, 0);
});
afterEach(function() {
loop.shared.mixins.setRootObject(window);
sandbox.restore();
});
describe("ContactsList", function () {
var listView;
beforeEach(function() {
navigator.mozLoop.calls = {
startDirectCall: sandbox.stub(),
clearCallInProgress: sandbox.stub()
};
navigator.mozLoop.contacts = {getAll: sandbox.stub()};
listView = TestUtils.renderIntoDocument(loop.contacts.ContactsList());
});
afterEach(function() {
listView = null;
delete navigator.mozLoop.calls;
delete navigator.mozLoop.contacts;
});
describe("#handleContactAction", function() {
it("should call window.close when called with 'video-call' action",
function() {
listView.handleContactAction({}, "video-call");
sinon.assert.calledOnce(fakeWindow.close);
});
it("should call window.close when called with 'audio-call' action",
function() {
listView.handleContactAction({}, "audio-call");
sinon.assert.calledOnce(fakeWindow.close);
});
});
});
describe("ContactDetailsForm", function() {
describe("#render", function() {
describe("add mode", function() {

View File

@ -8,7 +8,7 @@ describe("loop.conversationViews", function () {
var sharedUtils = loop.shared.utils;
var sandbox, oldTitle, view, dispatcher, contact, fakeAudioXHR;
var fakeMozLoop;
var fakeMozLoop, fakeWindow;
var CALL_STATES = loop.store.CALL_STATES;
@ -58,9 +58,17 @@ describe("loop.conversationViews", function () {
callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
})
};
fakeWindow = {
navigator: { mozLoop: fakeMozLoop },
close: sandbox.stub(),
};
loop.shared.mixins.setRootObject(fakeWindow);
});
afterEach(function() {
loop.shared.mixins.setRootObject(window);
document.title = oldTitle;
view = undefined;
delete navigator.mozLoop;
@ -316,12 +324,11 @@ describe("loop.conversationViews", function () {
it("should close the conversation window once the email link is received",
function() {
sandbox.stub(window, "close");
view = mountTestComponent();
store.setStoreState({emailLink: "http://fake.invalid/"});
sinon.assert.calledOnce(window.close);
sinon.assert.calledOnce(fakeWindow.close);
});
it("should display an error message in case email link retrieval failed",

View File

@ -11,6 +11,7 @@ describe("loop.conversation", function() {
var sharedModels = loop.shared.models,
sharedView = loop.shared.views,
fakeWindow,
sandbox;
// XXX refactor to Just Work with "sandbox.stubComponent" or else
@ -68,6 +69,12 @@ describe("loop.conversation", function() {
})
};
fakeWindow = {
navigator: { mozLoop: navigator.mozLoop },
close: sandbox.stub(),
};
loop.shared.mixins.setRootObject(fakeWindow);
// XXX These stubs should be hoisted in a common file
// Bug 1040968
sandbox.stub(document.mozL10n, "get", function(x) {
@ -77,6 +84,7 @@ describe("loop.conversation", function() {
});
afterEach(function() {
loop.shared.mixins.setRootObject(window);
delete navigator.mozLoop;
sandbox.restore();
});
@ -408,7 +416,6 @@ describe("loop.conversation", function() {
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
sandbox.stub(window, "close");
});
describe("progress - terminated (previousState = alerting)", function() {
@ -445,12 +452,13 @@ describe("loop.conversation", function() {
sandbox.clock.tick(1);
sinon.assert.calledOnce(window.close);
sinon.assert.calledOnce(fakeWindow.close);
done();
});
});
});
describe("progress - terminated (previousState not init" +
" nor alerting)",
function() {
@ -521,7 +529,6 @@ describe("loop.conversation", function() {
beforeEach(function() {
icView = mountTestComponent();
sandbox.stub(window, "close");
icView._websocket = {
decline: sinon.stub(),
close: sinon.stub()
@ -539,7 +546,7 @@ describe("loop.conversation", function() {
sandbox.clock.tick(1);
sinon.assert.calledOnce(window.close);
sinon.assert.calledOnce(fakeWindow.close);
});
it("should stop alerting", function() {
@ -567,7 +574,6 @@ describe("loop.conversation", function() {
decline: sinon.spy(),
close: sinon.stub()
};
sandbox.stub(window, "close");
mozLoop = {
LOOP_SESSION_TYPE: {
@ -626,7 +632,7 @@ describe("loop.conversation", function() {
sandbox.clock.tick(1);
sinon.assert.calledOnce(window.close);
sinon.assert.calledOnce(fakeWindow.close);
});
});
});

View File

@ -13,7 +13,7 @@ var sharedUtils = loop.shared.utils;
describe("loop.panel", function() {
"use strict";
var sandbox, notifications, fakeXHR, requests = [];
var sandbox, notifications, fakeXHR, fakeWindow, requests = [];
beforeEach(function(done) {
sandbox = sinon.sandbox.create();
@ -22,7 +22,14 @@ describe("loop.panel", function() {
// https://github.com/cjohansen/Sinon.JS/issues/393
fakeXHR.xhr.onCreate = function (xhr) {
requests.push(xhr);
}
fakeWindow = {
close: sandbox.stub(),
document: { addEventListener: function(){} }
};
loop.shared.mixins.setRootObject(fakeWindow);
notifications = new loop.shared.models.NotificationCollection();
navigator.mozLoop = {
@ -65,6 +72,7 @@ describe("loop.panel", function() {
afterEach(function() {
delete navigator.mozLoop;
loop.shared.mixins.setRootObject(window);
sandbox.restore();
});
@ -840,22 +848,32 @@ describe("loop.panel", function() {
});
describe("Room URL click", function() {
var roomEntry;
it("should dispatch an OpenRoom action", function() {
var roomEntry, urlLink;
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
roomEntry = mountRoomEntry({
dispatcher: dispatcher,
room: new loop.store.Room(roomData)
});
var urlLink = roomEntry.getDOMNode().querySelector("p > a");
urlLink = roomEntry.getDOMNode().querySelector("p > a");
});
it("should dispatch an OpenRoom action", function() {
TestUtils.Simulate.click(urlLink);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.OpenRoom({roomToken: roomData.roomToken}));
});
it("should call window.close", function() {
TestUtils.Simulate.click(urlLink);
sinon.assert.calledOnce(fakeWindow.close);
});
});
describe("Room name updated", function() {

View File

@ -117,6 +117,34 @@ describe("loop.shared.mixins", function() {
});
});
describe("loop.shared.mixins.WindowCloseMixin", function() {
var TestComp, rootObject;
beforeEach(function() {
rootObject = {
close: sandbox.stub()
};
sharedMixins.setRootObject(rootObject);
TestComp = React.createClass({
mixins: [loop.shared.mixins.WindowCloseMixin],
render: function() {
return React.DOM.div();
}
});
});
it("should call window.close", function() {
var comp = TestUtils.renderIntoDocument(TestComp());
comp.closeWindow();
sinon.assert.calledOnce(rootObject.close);
sinon.assert.calledWithExactly(rootObject.close);
});
});
describe("loop.shared.mixins.DocumentVisibilityMixin", function() {
var comp, TestComp, onDocumentVisibleStub, onDocumentHiddenStub;

View File

@ -143,7 +143,7 @@ let AboutHome = {
break;
case "AboutHome:Apps":
window.openUILinkIn("https://marketplace.mozilla.org/", "tab");
window.BrowserOpenApps();
break;
case "AboutHome:Addons":

View File

@ -121,15 +121,19 @@ this.UITour = {
}],
["loop", {query: "#loop-button"}],
["loop-newRoom", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".new-room-button");
// Use the parentElement full-width container of the button so our arrow
// doesn't overlap the panel contents much.
return loopBrowser.contentDocument.querySelector(".new-room-button").parentElement;
},
}],
["loop-roomList", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
if (!loopBrowser) {
@ -150,6 +154,7 @@ this.UITour = {
["privateWindow", {query: "#privatebrowsing-button"}],
["quit", {query: "#PanelUI-quit"}],
["search", {
infoPanelPosition: "after_start",
query: "#searchbar",
widgetName: "search-container",
}],
@ -877,6 +882,7 @@ this.UITour = {
deferred.resolve({
addTargetListener: targetObject.addTargetListener,
infoPanelPosition: targetObject.infoPanelPosition,
node: node,
removeTargetListener: targetObject.removeTargetListener,
targetName: aTargetName,
@ -1209,10 +1215,13 @@ this.UITour = {
tooltip.setAttribute("targetName", aAnchor.targetName);
tooltip.hidden = false;
let xOffset = 0, yOffset = 0;
let alignment = "bottomcenter topright";
if (aAnchor.infoPanelPosition) {
alignment = aAnchor.infoPanelPosition;
}
let xOffset = 0, yOffset = 0;
if (aAnchor.targetName == "search") {
alignment = "after_start";
xOffset = 18;
}
this._addAnnotationPanelMutationObserver(tooltip);

View File

@ -100,8 +100,28 @@ let tests = [
});
LoopRooms.open("fakeTourRoom");
},
taskify(function* test_arrow_panel_position() {
ise(loopButton.open, false, "Menu should initially be closed");
let popup = document.getElementById("UITourTooltip");
yield showMenuPromise("loop");
let currentTarget = "loop-newRoom";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-roomList";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-signInUpLink";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be underneath");
is(popup.popupBoxObject.alignmentPosition, "after_end", "Check " + currentTarget + " position");
}),
];
// End tests
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");

View File

@ -135,6 +135,12 @@ function showInfoPromise(...args) {
return promisePanelElementShown(window, popup);
}
function showMenuPromise(name) {
return new Promise(resolve => {
gContentAPI.showMenu(name, () => resolve());
});
}
function waitForCallbackResultPromise() {
return waitForConditionPromise(() => {
return gContentWindow.callbackResult;

View File

@ -384,15 +384,6 @@ div.CodeMirror span.eval-text {
border-bottom: 0;
}
.devtools-horizontal-splitter {
border-bottom: 1px solid var(--theme-splitter-color);
}
.devtools-side-splitter {
-moz-border-end: 1px solid var(--theme-splitter-color);
border-color: var(--theme-splitter-color); /* Needed for responsive container at low width. */
}
.devtools-textinput,
.devtools-searchinput {
background-color: rgba(24, 29, 32, 1);

View File

@ -104,11 +104,11 @@
color: rgba(128,128,128,0.4);
}
#sources .selected > .black-boxed {
#sources .selected .black-boxed {
color: rgba(255,255,255,0.4);
}
#sources .black-boxed > .dbg-breakpoint {
#sources .black-boxed ~ .dbg-breakpoint {
display: none;
}

View File

@ -393,15 +393,6 @@ div.CodeMirror span.eval-text {
border-bottom: 0;
}
.devtools-horizontal-splitter {
border-bottom: 1px solid var(--theme-splitter-color);
}
.devtools-side-splitter {
-moz-border-end: 1px solid var(--theme-splitter-color);
border-color: var(--theme-splitter-color); /* Needed for responsive container at low width. */
}
.CodeMirror-hints,
.CodeMirror-Tern-tooltip {
box-shadow: 0 0 4px rgba(128, 128, 128, .5);

View File

@ -175,7 +175,7 @@
border-color: initial!important;
}
#waterfall-details {
#timeline-waterfall-details {
-moz-padding-start: 8px;
-moz-padding-end: 8px;
padding-top: 8vh;

View File

@ -647,7 +647,7 @@
.devtools-tabbar {
-moz-appearance: none;
min-height: 28px;
min-height: 24px;
border: 0px solid;
border-bottom-width: 1px;
padding: 0;
@ -672,7 +672,7 @@
-moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
-moz-box-align: center;
min-width: 32px;
min-height: 28px;
min-height: 24px;
max-width: 127px;
margin: 0;
padding: 0;
@ -853,3 +853,13 @@
.devtools-invisible-splitter {
border-color: transparent;
}
.devtools-horizontal-splitter {
border-bottom: 1px solid var(--theme-splitter-color);
}
.devtools-side-splitter {
-moz-border-end: 1px solid var(--theme-splitter-color);
border-color: var(--theme-splitter-color); /* Needed for responsive container at low width. */
}

View File

@ -50,13 +50,20 @@
}
.devtools-responsive-container > .devtools-side-splitter {
border-width: 0;
border-top-width: 1px;
border-top-style: solid;
margin: 0;
/* This is a normally vertical splitter, but we have turned it horizontal
due to the smaller resolution */
min-height: 3px;
height: 3px;
margin-bottom: -3px;
margin-top: -3px;
/* Reset the vertical splitter styles */
border-width: 0;
border-bottom-width: 1px;
border-bottom-style: solid;
-moz-margin-start: 0;
width: auto;
min-width: 0;
/* In some edge case the cursor is not changed to n-resize */
cursor: n-resize;
}

View File

@ -99,6 +99,12 @@ treecol {
}
}
@media (max-width: 800px) {
.category-name {
display: none;
}
}
/* header */
#header-advanced {

View File

@ -55,6 +55,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
let SettingsPermissions = {
checkPermission: function(aPrincipal, aPerm) {
if (!aPrincipal) {
Cu.reportError("SettingsPermissions.checkPermission was passed a null principal. Denying all permissions.");
return false;
}
if (aPrincipal.origin == "[System Principal]" ||
Services.perms.testExactPermissionFromPrincipal(aPrincipal, aPerm) == Ci.nsIPermissionManager.ALLOW_ACTION) {
return true;
@ -85,7 +89,7 @@ let SettingsPermissions = {
};
function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock, aWindowID) {
function SettingsLockInfo(aDB, aMsgMgr, aPrincipal, aLockID, aIsServiceLock, aWindowID) {
return {
// ID Shared with the object on the child side
lockID: aLockID,
@ -114,7 +118,11 @@ function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock, aWindowID) {
canClear: true,
// Lets us know if this lock has been used to clear at any point.
hasCleared: false,
getObjectStore: function(aPrincipal) {
// Principal the lock was created under. We assume that the lock
// will continue to exist under this principal for the duration of
// its lifetime.
principal: aPrincipal,
getObjectStore: function() {
if (VERBOSE) debug("Getting transaction for " + this.lockID);
let store;
// Test for transaction validity via trying to get the
@ -136,7 +144,7 @@ function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock, aWindowID) {
// slightly slower on apps with full settings permissions, but
// it means we don't have to do our own transaction order
// bookkeeping.
if (!SettingsPermissions.hasSomeWritePermission(aPrincipal)) {
if (!SettingsPermissions.hasSomeWritePermission(this.principal)) {
if (VERBOSE) debug("Making READONLY transaction for " + this.lockID);
this._transaction = aDB._db.transaction(SETTINGSSTORE_NAME, "readonly");
} else {
@ -180,7 +188,11 @@ let SettingsRequestManager = {
// until they hit the front of the queue.
settingsLockQueue: [],
children: [],
mmPrincipals: new Map(),
// Since we need to call observers at times when we may not have
// just received a message from a child process, we cache principals
// for message managers and check permissions on them before we send
// settings notifications to child processes.
observerPrincipalCache: new Map(),
tasksConsumed: 0,
init: function() {
@ -225,7 +237,7 @@ let SettingsRequestManager = {
});
},
queueTask: function(aOperation, aData, aPrincipal) {
queueTask: function(aOperation, aData) {
if (VERBOSE) debug("Queueing task: " + aOperation);
let defer = {};
@ -243,7 +255,6 @@ let SettingsRequestManager = {
this.lockInfo[aData.lockID].tasks.push({
operation: aOperation,
data: aData,
principal: aPrincipal,
defer: defer
});
@ -266,7 +277,7 @@ let SettingsRequestManager = {
if (VERBOSE) debug("Making task queuing transaction request.");
let data = aTask.data;
let lock = this.lockInfo[data.lockID];
let store = lock.getObjectStore(aTask.principal);
let store = lock.getObjectStore(lock.principal);
if (!store) {
if (DEBUG) debug("Rejecting task queue on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
@ -316,7 +327,7 @@ let SettingsRequestManager = {
lock.canClear = false;
if (!SettingsPermissions.hasReadPermission(aTask.principal, data.name)) {
if (!SettingsPermissions.hasReadPermission(lock.principal, data.name)) {
if (DEBUG) debug("get not allowed for " + data.name);
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to get " + data.name});
@ -332,7 +343,7 @@ let SettingsRequestManager = {
// Create/Get transaction and make request
if (VERBOSE) debug("Making get transaction request for " + data.name);
let store = lock.getObjectStore(aTask.principal);
let store = lock.getObjectStore(lock.principal);
if (!store) {
if (DEBUG) debug("Rejecting Get task on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
@ -399,7 +410,7 @@ let SettingsRequestManager = {
}
for (let i = 0; i < keys.length; i++) {
if (!SettingsPermissions.hasWritePermission(aTask.principal, keys[i])) {
if (!SettingsPermissions.hasWritePermission(lock.principal, keys[i])) {
if (DEBUG) debug("set not allowed on " + keys[i]);
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to set " + keys[i]});
@ -470,7 +481,7 @@ let SettingsRequestManager = {
return Promise.resolve({task: aTask});
}
let store = lock.getObjectStore(aTask.principal);
let store = lock.getObjectStore(lock.principal);
if (!store) {
if (DEBUG) debug("Rejecting Set task on lock " + aTask.data.lockID);
this.removeLock(data.lockID);
@ -566,7 +577,7 @@ let SettingsRequestManager = {
return Promise.reject({task: aTask, error: "Cannot call clear after queuing other tasks, all requests now failing."});
}
if (!SettingsPermissions.hasClearPermission(aTask.principal)) {
if (!SettingsPermissions.hasClearPermission(lock.principal)) {
if (DEBUG) debug("clear not allowed");
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to clear DB"});
@ -574,7 +585,7 @@ let SettingsRequestManager = {
lock.hasCleared = true;
let store = lock.getObjectStore(aTask.principal);
let store = lock.getObjectStore(lock.principal);
if (!store) {
if (DEBUG) debug("Rejecting Clear task on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
@ -758,7 +769,7 @@ let SettingsRequestManager = {
broadcastMessage: function broadcastMessage(aMsgName, aContent) {
if (VERBOSE) debug("Broadcast");
this.children.forEach(function(msgMgr) {
let principal = this.mmPrincipals.get(msgMgr);
let principal = this.observerPrincipalCache.get(msgMgr);
if (!principal) {
if (DEBUG) debug("Cannot find principal for message manager to check permissions");
}
@ -777,13 +788,13 @@ let SettingsRequestManager = {
if (VERBOSE) debug("Add observer for " + aPrincipal.origin);
if (this.children.indexOf(aMsgMgr) == -1) {
this.children.push(aMsgMgr);
this.mmPrincipals.set(aMsgMgr, aPrincipal);
this.observerPrincipalCache.set(aMsgMgr, aPrincipal);
}
},
removeObserver: function(aMsgMgr) {
if (VERBOSE) {
let principal = this.mmPrincipals.get(aMsgMgr);
let principal = this.observerPrincipalCache.get(aMsgMgr);
if (principal) {
debug("Remove observer for " + principal.origin);
}
@ -791,9 +802,9 @@ let SettingsRequestManager = {
let index = this.children.indexOf(aMsgMgr);
if (index != -1) {
this.children.splice(index, 1);
this.mmPrincipals.delete(aMsgMgr);
this.observerPrincipalCache.delete(aMsgMgr);
}
if (VERBOSE) debug("Principal/MessageManager pairs left: " + this.mmPrincipals.size);
if (VERBOSE) debug("Principal/MessageManager pairs left in observer cache: " + this.observerPrincipalCache.size);
},
removeLock: function(aLockID) {
@ -837,10 +848,10 @@ let SettingsRequestManager = {
return false;
},
enqueueForceFinalize: function(lock, principal) {
enqueueForceFinalize: function(lock) {
if (!this.hasLockFinalizeTask(lock)) {
if (VERBOSE) debug("Alive lock has pending tasks: " + lock.lockID);
this.queueTask("finalize", {lockID: lock.lockID}, principal).then(
this.queueTask("finalize", {lockID: lock.lockID}).then(
function() {
if (VERBOSE) debug("Alive lock " + lock.lockID + " succeeded to force-finalize");
},
@ -860,8 +871,7 @@ let SettingsRequestManager = {
for (let lockId of Object.keys(this.lockInfo)) {
let lock = this.lockInfo[lockId];
if (lock.windowID === windowId) {
let principal = this.mmPrincipals.get(lock._mm);
this.enqueueForceFinalize(lock, principal);
this.enqueueForceFinalize(lock);
}
}
},
@ -872,8 +882,7 @@ let SettingsRequestManager = {
for (let lockId of Object.keys(this.lockInfo)) {
let lock = this.lockInfo[lockId];
if (lock._mm === aMsgMgr) {
let principal = this.mmPrincipals.get(lock._mm);
this.enqueueForceFinalize(lock, principal);
this.enqueueForceFinalize(lock);
}
}
},
@ -959,13 +968,14 @@ let SettingsRequestManager = {
this.settingsLockQueue.push(msg.lockID);
this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB,
mm,
aMessage.principal,
msg.lockID,
msg.isServiceLock,
msg.windowID);
break;
case "Settings:Get":
if (VERBOSE) debug("Received getRequest from " + msg.lockID);
this.queueTask("get", msg, aMessage.principal).then(function(settings) {
this.queueTask("get", msg).then(function(settings) {
returnMessage("Settings:Get:OK", {
lockID: msg.lockID,
requestID: msg.requestID,
@ -982,7 +992,7 @@ let SettingsRequestManager = {
break;
case "Settings:Set":
if (VERBOSE) debug("Received Set Request from " + msg.lockID);
this.queueTask("set", msg, aMessage.principal).then(function(settings) {
this.queueTask("set", msg).then(function(settings) {
returnMessage("Settings:Set:OK", {
lockID: msg.lockID,
requestID: msg.requestID
@ -997,7 +1007,7 @@ let SettingsRequestManager = {
break;
case "Settings:Clear":
if (VERBOSE) debug("Received Clear Request from " + msg.lockID);
this.queueTask("clear", msg, aMessage.principal).then(function() {
this.queueTask("clear", msg).then(function() {
returnMessage("Settings:Clear:OK", {
lockID: msg.lockID,
requestID: msg.requestID
@ -1012,7 +1022,7 @@ let SettingsRequestManager = {
break;
case "Settings:Finalize":
if (VERBOSE) debug("Received Finalize");
this.queueTask("finalize", msg, aMessage.principal).then(function() {
this.queueTask("finalize", msg).then(function() {
returnMessage("Settings:Finalize:OK", {
lockID: msg.lockID
});

View File

@ -1088,11 +1088,6 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
BEFOREUNLOAD_DISABLED_PREFNAME);
}
// If the user has turned off onbeforeunload warnings, no need to check.
if (sIsBeforeUnloadDisabled) {
return NS_OK;
}
// First, get the script global object from the document...
nsPIDOMWindow *window = mDocument->GetWindow();
@ -1147,8 +1142,10 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
nsCOMPtr<nsIDocShell> docShell(mContainer);
nsAutoString text;
beforeUnload->GetReturnValue(text);
if (*aShouldPrompt && (event->GetInternalNSEvent()->mFlags.mDefaultPrevented ||
!text.IsEmpty())) {
if (!sIsBeforeUnloadDisabled && *aShouldPrompt &&
(event->GetInternalNSEvent()->mFlags.mDefaultPrevented ||
!text.IsEmpty())) {
// Ask the user if it's ok to unload the current page
nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);

View File

@ -2197,7 +2197,7 @@ public class BrowserApp extends GeckoApp
if (locale == null) {
return;
}
onLocaleChanged(BrowserLocaleManager.getLanguageTag(locale));
onLocaleChanged(Locales.getLanguageTag(locale));
}
});
break;
@ -2930,7 +2930,7 @@ public class BrowserApp extends GeckoApp
if (itemId == R.id.help) {
final String VERSION = AppConstants.MOZ_APP_VERSION;
final String OS = AppConstants.OS_TARGET;
final String LOCALE = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
final String LOCALE = Locales.getLanguageTag(Locale.getDefault());
final String URL = getResources().getString(R.string.help_link, VERSION, OS, LOCALE);
Tabs.getInstance().loadUrlInTab(URL);

View File

@ -80,64 +80,6 @@ public class BrowserLocaleManager implements LocaleManager {
return AppConstants.MOZ_LOCALE_SWITCHER;
}
/**
* Sometimes we want just the language for a locale, not the entire
* language tag. But Java's .getLanguage method is wrong.
*
* This method is equivalent to the first part of {@link #getLanguageTag(Locale)}.
*
* @return a language string, such as "he" for the Hebrew locales.
*/
public static String getLanguage(final Locale locale) {
final String language = locale.getLanguage(); // Can, but should never be, an empty string.
// Modernize certain language codes.
if (language.equals("iw")) {
return "he";
}
if (language.equals("in")) {
return "id";
}
if (language.equals("ji")) {
return "yi";
}
return language;
}
/**
* Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
* stringifies as "es_ES".
*
* This method approximates the Java 7 method <code>Locale#toLanguageTag()</code>.
*
* @return a locale string suitable for passing to Gecko.
*/
public static String getLanguageTag(final Locale locale) {
// If this were Java 7:
// return locale.toLanguageTag();
final String language = getLanguage(locale);
final String country = locale.getCountry(); // Can be an empty string.
if (country.equals("")) {
return language;
}
return language + "-" + country;
}
public static Locale parseLocaleCode(final String localeCode) {
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
final String langCode = localeCode.substring(0, index);
final String countryCode = localeCode.substring(index + 1);
return new Locale(langCode, countryCode);
} else {
return new Locale(localeCode);
}
}
/**
* Ensure that you call this early in your application startup,
* and with a context that's sufficiently long-lived (typically
@ -275,7 +217,7 @@ public class BrowserLocaleManager implements LocaleManager {
// The value we send to Gecko should be a language tag, not
// a Java locale string.
final String osLanguageTag = BrowserLocaleManager.getLanguageTag(osLocale);
final String osLanguageTag = Locales.getLanguageTag(osLocale);
final GeckoEvent localeOSEvent = GeckoEvent.createBroadcastEvent("Locale:OS", osLanguageTag);
GeckoAppShell.sendEventToGecko(localeOSEvent);
}
@ -321,7 +263,7 @@ public class BrowserLocaleManager implements LocaleManager {
persistLocale(context, localeCode);
// Tell Gecko.
GeckoEvent ev = GeckoEvent.createBroadcastEvent(EVENT_LOCALE_CHANGED, BrowserLocaleManager.getLanguageTag(getCurrentLocale(context)));
GeckoEvent ev = GeckoEvent.createBroadcastEvent(EVENT_LOCALE_CHANGED, Locales.getLanguageTag(getCurrentLocale(context)));
GeckoAppShell.sendEventToGecko(ev);
return resultant;
@ -389,7 +331,7 @@ public class BrowserLocaleManager implements LocaleManager {
if (current == null) {
return null;
}
return currentLocale = parseLocaleCode(current);
return currentLocale = Locales.parseLocaleCode(current);
}
/**
@ -409,7 +351,7 @@ public class BrowserLocaleManager implements LocaleManager {
return null;
}
final Locale locale = parseLocaleCode(localeCode);
final Locale locale = Locales.parseLocaleCode(localeCode);
return updateLocale(context, locale);
}
@ -495,7 +437,8 @@ public class BrowserLocaleManager implements LocaleManager {
* @return the single default locale baked into this application.
* Applicable when there is no multilocale.json present.
*/
public static String getFallbackLocaleTag() {
@SuppressWarnings("static-method")
public String getFallbackLocaleTag() {
return FALLBACK_LOCALE_TAG;
}
}

View File

@ -1383,7 +1383,7 @@ public abstract class GeckoApp
throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
}
final Locale loc = BrowserLocaleManager.parseLocaleCode(locale);
final Locale loc = Locales.parseLocaleCode(locale);
if (loc.equals(mLastLocale)) {
Log.d(LOGTAG, "New locale same as old; onLocaleReady has nothing to do.");
}
@ -2119,7 +2119,7 @@ public abstract class GeckoApp
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, mLastLocale);
if (changed != null) {
onLocaleChanged(BrowserLocaleManager.getLanguageTag(changed));
onLocaleChanged(Locales.getLanguageTag(changed));
}
// onConfigurationChanged is not called for 180 degree orientation changes,

View File

@ -1,52 +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;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.LocaleManager;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
/**
* This is a helper class to do typical locale switching operations
* without hitting StrictMode errors or adding boilerplate to common
* activity subclasses.
*
* Either call {@link LocaleAware#initializeLocale(Context)} in your
* <code>onCreate</code> method, or inherit from <code>LocaleAwareFragmentActivity</code>
* or <code>LocaleAwareActivity</code>.
*/
public class LocaleAware {
public static void initializeLocale(Context context) {
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
StrictMode.allowThreadDiskWrites();
try {
localeManager.getAndApplyPersistedLocale(context);
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
public static class LocaleAwareFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
LocaleAware.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
public static class LocaleAwareActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
LocaleAware.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
}

View File

@ -38,4 +38,5 @@ public interface LocaleManager {
* use.
*/
Locale onSystemConfigurationChanged(Context context, Resources resources, Configuration configuration, Locale currentActivityLocale);
String getFallbackLocaleTag();
}

View File

@ -0,0 +1,116 @@
/* 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;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.LocaleManager;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
/**
* This is a helper class to do typical locale switching operations without
* hitting StrictMode errors or adding boilerplate to common activity
* subclasses.
*
* Either call {@link Locales#initializeLocale(Context)} in your
* <code>onCreate</code> method, or inherit from
* <code>LocaleAwareFragmentActivity</code> or <code>LocaleAwareActivity</code>.
*/
public class Locales {
public static void initializeLocale(Context context) {
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
StrictMode.allowThreadDiskWrites();
try {
localeManager.getAndApplyPersistedLocale(context);
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
public static class LocaleAwareFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
public static class LocaleAwareActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
/**
* Sometimes we want just the language for a locale, not the entire language
* tag. But Java's .getLanguage method is wrong.
*
* This method is equivalent to the first part of
* {@link Locales#getLanguageTag(Locale)}.
*
* @return a language string, such as "he" for the Hebrew locales.
*/
public static String getLanguage(final Locale locale) {
// Can, but should never be, an empty string.
final String language = locale.getLanguage();
// Modernize certain language codes.
if (language.equals("iw")) {
return "he";
}
if (language.equals("in")) {
return "id";
}
if (language.equals("ji")) {
return "yi";
}
return language;
}
/**
* Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
* stringifies as "es_ES".
*
* This method approximates the Java 7 method
* <code>Locale#toLanguageTag()</code>.
*
* @return a locale string suitable for passing to Gecko.
*/
public static String getLanguageTag(final Locale locale) {
// If this were Java 7:
// return locale.toLanguageTag();
final String language = getLanguage(locale);
final String country = locale.getCountry(); // Can be an empty string.
if (country.equals("")) {
return language;
}
return language + "-" + country;
}
public static Locale parseLocaleCode(final String localeCode) {
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
final String langCode = localeCode.substring(0, index);
final String countryCode = localeCode.substring(index + 1);
return new Locale(langCode, countryCode);
}
return new Locale(localeCode);
}
}

View File

@ -60,14 +60,20 @@ GARBAGE += \
GARBAGE_DIRS += classes db jars res sync services generated
JAVA_BOOTCLASSPATH = \
# The bootclasspath is functionally identical to the classpath, but allows the
# classes given to redefine classes in core packages, such as java.lang.
# android.jar is here as it provides Android's definition of the Java Standard
# Library. The compatability lib here tweaks a few of the core classes to paint
# over changes in behaviour between versions.
JAVA_BOOTCLASSPATH := \
$(ANDROID_SDK)/android.jar \
$(ANDROID_COMPAT_LIB) \
$(NULL)
JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH)))
# If native devices are enabled, add Google Play Services and some of the v7 compat libraries
# If native devices are enabled, add Google Play Services and some of the v7
# compat libraries.
ifdef MOZ_NATIVE_DEVICES
JAVA_CLASSPATH += \
$(GOOGLE_PLAY_SERVICES_LIB) \
@ -78,6 +84,24 @@ endif
JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
# Library jars that we're bundling: these are subject to Proguard before inclusion
# into classes.dex.
java_bundled_libs := \
$(ANDROID_COMPAT_LIB) \
$(NULL)
ifdef MOZ_NATIVE_DEVICES
java_bundled_libs += \
$(GOOGLE_PLAY_SERVICES_LIB) \
$(ANDROID_MEDIAROUTER_LIB) \
$(ANDROID_APPCOMPAT_LIB) \
$(NULL)
endif
java_bundled_libs := $(subst $(NULL) ,:,$(strip $(java_bundled_libs)))
# All the jars we're compiling from source. (not to be confused with
# java_bundled_libs, which holds the jars which we're including as binaries).
ALL_JARS = \
constants.jar \
gecko-R.jar \
@ -102,24 +126,20 @@ ALL_JARS += ../stumbler/stumbler.jar
generated/org/mozilla/mozstumbler/R.java: .aapt.deps ;
endif
# The list of jars in Java classpath notation (colon-separated).
all_jars_classpath := $(subst $(NULL) ,:,$(strip $(ALL_JARS)))
include $(topsrcdir)/config/config.mk
# Note that we're going to set up a dependency directly between embed_android.dex and the java files
# Instead of on the .class files, since more than one .class file might be produced per .java file
# Sync dependencies are provided in a single jar. Sync classes themselves are delivered as source,
# because Android resource classes must be compiled together in order to avoid overlapping resource
# indices.
library_jars = \
$(JAVA_CLASSPATH) \
$(JAVA_BOOTCLASSPATH) \
library_jars := \
$(ANDROID_SDK)/android.jar \
$(NULL)
library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
classes.dex: .proguard.deps
$(REPORT_BUILD)
$(DX) --dex --output=classes.dex jars-proguarded $(subst :, ,$(ANDROID_COMPAT_LIB):$(JAVA_CLASSPATH))
$(DX) --dex --output=classes.dex jars-proguarded
ifdef MOZ_DISABLE_PROGUARD
PROGUARD_PASSES=0
@ -135,6 +155,8 @@ else
endif
endif
proguard_config_dir=$(topsrcdir)/mobile/android/config/proguard
# This stanza ensures that the set of GeckoView classes does not depend on too
# much of Fennec, where "too much" is defined as the set of potentially
# non-GeckoView classes that GeckoView already depended on at a certain point in
@ -148,19 +170,41 @@ classycle_jar := $(topsrcdir)/mobile/android/build/classycle/classycle-1.4.1.jar
$(ALL_JARS)
@$(TOUCH) $@
# We touch the target file before invoking Proguard so that Proguard's
# outputs are fresher than the target, preventing a subsequent
# invocation from thinking Proguard's outputs are stale. This is safe
# because Make removes the target file if any recipe command fails.
.proguard.deps: .geckoview.deps $(ALL_JARS) $(topsrcdir)/mobile/android/config/proguard.cfg
# First, we delete debugging information from libraries. Having line-number
# information for libraries for which we lack the source isn't useful, so this
# saves us a bit of space. Importantly, Proguard has a bug causing it to
# sometimes corrupt this information if present (which it does for some of the
# included libraries). This corruption prevents dex from completing, so we need
# to get rid of it. This prevents us from seeing line numbers in stack traces
# for stack frames inside libraries.
#
# This step can occur much earlier than the main Proguard pass: it needs only
# gecko-R.jar to have been compiled (as that's where the library R.java files
# end up), but it does block the main Proguard pass.
.bundled.proguard.deps: gecko-R.jar $(proguard_config_dir)/strip-libs.cfg
$(REPORT_BUILD)
@$(TOUCH) $@
java \
-Xmx512m -Xms128m \
-jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
@$(topsrcdir)/mobile/android/config/proguard.cfg \
@$(proguard_config_dir)/strip-libs.cfg \
-injars $(subst ::,:,$(java_bundled_libs))\
-outjars bundled-jars-nodebug \
-libraryjars $(library_jars):gecko-R.jar
# We touch the target file before invoking Proguard so that Proguard's
# outputs are fresher than the target, preventing a subsequent
# invocation from thinking Proguard's outputs are stale. This is safe
# because Make removes the target file if any recipe command fails.
.proguard.deps: .geckoview.deps .bundled.proguard.deps $(ALL_JARS) $(proguard_config_dir)/proguard.cfg
$(REPORT_BUILD)
@$(TOUCH) $@
java \
-Xmx512m -Xms128m \
-jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
@$(proguard_config_dir)/proguard.cfg \
-optimizationpasses $(PROGUARD_PASSES) \
-injars $(subst ::,:,$(subst $(NULL) ,:,$(strip $(ALL_JARS)))) \
-injars $(subst ::,:,$(all_jars_classpath)):bundled-jars-nodebug \
-outjars jars-proguarded \
-libraryjars $(library_jars)

View File

@ -34,10 +34,9 @@ import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.db.BrowserContract;
@ -334,7 +333,7 @@ public class SuggestedSites {
static Map<String, Site> loadFromDistribution(Distribution dist) {
for (Locale locale : getAcceptableLocales()) {
try {
final String languageTag = BrowserLocaleManager.getLanguageTag(locale);
final String languageTag = Locales.getLanguageTag(locale);
final String path = String.format("suggestedsites/locales/%s/%s",
languageTag, FILENAME);

View File

@ -9,7 +9,7 @@ import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
import org.mozilla.gecko.Locales.LocaleAwareActivity;
import android.accounts.Account;
import android.app.Activity;

View File

@ -13,7 +13,7 @@ import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import org.mozilla.gecko.LocaleAware;
import org.mozilla.gecko.Locales;
import android.accounts.AccountAuthenticatorActivity;
import android.content.Intent;
@ -39,7 +39,7 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
LocaleAware.initializeLocale(getApplicationContext());
Locales.initializeLocale(getApplicationContext());
super.onCreate(icicle);

View File

@ -5,7 +5,7 @@
package org.mozilla.gecko.fxa.activities;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
import org.mozilla.gecko.Locales.LocaleAwareFragmentActivity;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FirefoxAccounts;

View File

@ -15,8 +15,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
@ -238,7 +238,7 @@ public class TopSitesPanel extends HomeFragment {
final Tab tab = Tabs.getInstance().getSelectedTab();
if (!tab.isPrivate()) {
final Locale locale = Locale.getDefault();
final String localeTag = BrowserLocaleManager.getLanguageTag(locale);
final String localeTag = Locales.getLanguageTag(locale);
mTilesRecorder.recordAction(tab, TilesRecorder.ACTION_CLICK, position, getTilesSnapshot(), localeTag);
}

View File

@ -326,8 +326,8 @@ gbjar.sources += [
'InputMethods.java',
'IntentHelper.java',
'JavaAddonManager.java',
'LocaleAware.java',
'LocaleManager.java',
'Locales.java',
'lwt/LightweightTheme.java',
'lwt/LightweightThemeDrawable.java',
'MediaCastingBar.java',

View File

@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingTop="4dp"/>
android:paddingTop="8dp"/>
<!-- The right margin creates a "dead area" on the right side of the button
which we compensate for with a touch delegate. See TabStrip -->

View File

@ -10,7 +10,7 @@ import java.net.URISyntaxException;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.LocaleAware;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
@ -50,7 +50,7 @@ import android.widget.Toast;
/**
* A transparent activity that displays the share overlay.
*/
public class ShareDialog extends LocaleAware.LocaleAwareActivity implements SendTabTargetSelectedListener {
public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabTargetSelectedListener {
private static final String LOGTAG = "GeckoShareDialog";
private String url;

View File

@ -12,6 +12,7 @@ import java.util.Locale;
import java.util.Map;
import android.os.Build;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.AppConstants.Versions;
@ -27,6 +28,7 @@ import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.GuestSession;
import org.mozilla.gecko.LocaleManager;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
@ -1014,7 +1016,7 @@ OnSharedPreferenceChangeListener
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PREFS_BROWSER_LOCALE.equals(key)) {
onLocaleSelected(BrowserLocaleManager.getLanguageTag(lastLocale),
onLocaleSelected(Locales.getLanguageTag(lastLocale),
sharedPreferences.getString(key, null));
} else if (PREFS_SUGGESTED_SITES.equals(key)) {
refreshSuggestedSites();
@ -1054,7 +1056,7 @@ OnSharedPreferenceChangeListener
if (PREFS_BROWSER_LOCALE.equals(prefName)) {
// Even though this is a list preference, we don't want to handle it
// below, so we return here.
return onLocaleSelected(BrowserLocaleManager.getLanguageTag(lastLocale), (String) newValue);
return onLocaleSelected(Locales.getLanguageTag(lastLocale), (String) newValue);
}
if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {

View File

@ -14,6 +14,7 @@ import java.util.Set;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import android.content.Context;
@ -117,7 +118,7 @@ public class LocaleListPreference extends ListPreference {
private final String nativeName;
public LocaleDescriptor(String tag) {
this(BrowserLocaleManager.parseLocaleCode(tag), tag);
this(Locales.parseLocaleCode(tag), tag);
}
public LocaleDescriptor(Locale locale, String tag) {
@ -221,7 +222,7 @@ public class LocaleListPreference extends ListPreference {
// Future: single-locale builds should be specified, too.
if (shippingLocales == null) {
final String fallbackTag = BrowserLocaleManager.getFallbackLocaleTag();
final String fallbackTag = BrowserLocaleManager.getInstance().getFallbackLocaleTag();
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
}
@ -262,7 +263,7 @@ public class LocaleListPreference extends ListPreference {
if (tag == null || tag.equals("")) {
return Locale.getDefault();
}
return BrowserLocaleManager.parseLocaleCode(tag);
return Locales.parseLocaleCode(tag);
}
@Override

View File

@ -22,7 +22,7 @@
<dimen name="new_tablet_nav_button_width_half">21dp</dimen>
<dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
<dimen name="new_tablet_tab_strip_height">44dp</dimen>
<dimen name="new_tablet_tab_strip_height">48dp</dimen>
<dimen name="new_tablet_tab_strip_item_width">208dp</dimen>
<dimen name="new_tablet_tab_strip_item_margin">-28dp</dimen>
<dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
@ -138,6 +138,7 @@
<dimen name="new_tablet_tab_thumbnail_height">140dp</dimen>
<dimen name="new_tablet_tab_panel_column_width">178dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding">19dp</dimen>
<dimen name="new_tablet_tab_panel_grid_vspacing">21dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding_top">24dp</dimen>
<dimen name="new_tablet_tab_highlight_stroke_width">5dp</dimen>

View File

@ -202,7 +202,7 @@
<item name="android:numColumns">auto_fit</item>
<item name="android:columnWidth">@dimen/tabs_grid_view_column_width</item>
<item name="android:horizontalSpacing">2dp</item>
<item name="android:verticalSpacing">21dp</item>
<item name="android:verticalSpacing">@dimen/new_tablet_tab_panel_grid_vspacing</item>
<item name="android:drawSelectorOnTop">true</item>
<item name="android:clipToPadding">false</item>
</style>

View File

@ -27,7 +27,7 @@ import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
import org.mozilla.gecko.Locales.LocaleAwareActivity;
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
import android.accounts.Account;

View File

@ -7,7 +7,7 @@ package org.mozilla.gecko.tabs;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
@ -58,7 +58,7 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
learnMore.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String locale = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
final String locale = Locales.getLanguageTag(Locale.getDefault());
final String url =
getResources().getString(R.string.private_tabs_panel_learn_more_link, locale);
Tabs.getInstance().loadUrlInTab(url);

View File

@ -164,7 +164,8 @@ class TabsGridLayout extends GridView
// via a sweet animation
final int removedHeight = getChildAt(0).getMeasuredHeight();
final int verticalSpacing = getVerticalSpacing();
final int verticalSpacing =
getResources().getDimensionPixelOffset(R.dimen.new_tablet_tab_panel_grid_vspacing);
ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
paddingAnimator.setDuration(ANIM_TIME_MS * 2);

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.PrefsHelper;
import android.content.SharedPreferences;
@ -90,7 +91,7 @@ public class testOSLocale extends BaseTest {
// have been set.
//
// Instead, we always send a new locale code, and see what we get.
final Locale fr = BrowserLocaleManager.parseLocaleCode("fr");
final Locale fr = Locales.parseLocaleCode("fr");
BrowserLocaleManager.storeAndNotifyOSLocale(prefs, fr);
state.fetch();
@ -125,7 +126,7 @@ public class testOSLocale extends BaseTest {
mAsserter.is(state.acceptLanguages, EXPECTED, "We have the right es-ES+fr Accept-Languages for this build.");
// And back to en-US.
final Locale en_US = BrowserLocaleManager.parseLocaleCode("en-US");
final Locale en_US = Locales.parseLocaleCode("en-US");
BrowserLocaleManager.storeAndNotifyOSLocale(prefs, en_US);
BrowserLocaleManager.getInstance().resetToSystemLocale(getActivity());

View File

@ -35,6 +35,7 @@ public class ResizablePathDrawable extends ShapeDrawable {
int newColor = colorStateList.getColorForState(stateSet, Color.WHITE);
if (newColor != currentColor) {
currentColor = newColor;
alpha = Color.alpha(currentColor);
invalidateSelf();
return true;
}
@ -56,11 +57,15 @@ public class ResizablePathDrawable extends ShapeDrawable {
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
paint.setColor(currentColor);
// setAlpha overrides the alpha value in set color. Since we just set the color,
// the alpha value is reset: override the alpha value with the old value.
// the alpha value is reset: override the alpha value with the old value. We don't
// set alpha if the color is transparent.
//
// Note: We *should* be able to call Shape.setAlpha, rather than Paint.setAlpha, but
// then the opacity doesn't change - dunno why but probably not worth the time.
paint.setAlpha(alpha);
if (currentColor != Color.TRANSPARENT) {
paint.setAlpha(alpha);
}
super.onDraw(shape, canvas, paint);
}

View File

@ -244,7 +244,7 @@ var Addons = {
init: function init() {
let self = this;
AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(aAddons) {
AddonManager.getAllAddons(function(aAddons) {
// Clear all content before filling the addons
let list = document.getElementById("addons-list");
list.innerHTML = "";
@ -348,15 +348,11 @@ var Addons = {
let event = document.createEvent("Events");
event.initEvent("AddonOptionsLoad", true, false);
window.dispatchEvent(event);
// Also send a notification to match the behavior of desktop Firefox
let id = aListItem.getAttribute("addonID");
Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
} else {
// No options, so hide the header and reset the list item
detailItem.setAttribute("optionsURL", "");
aListItem.setAttribute("optionsURL", "");
}
// Also send a notification to match the behavior of desktop Firefox
let id = aListItem.getAttribute("addonID");
Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
}
}
xhr.send(null);

View File

@ -0,0 +1,19 @@
# Rules to prevent Google Play Services from exploding
# (From http://developer.android.com/google/play-services/setup.html#Proguard
# With the reference to "Object" changed so it'll actually *work*...)
-keep class * extends java.util.ListResourceBundle {
protected java.lang.Object[][] getContents();
}
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
public static final *** NULL;
}
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
@com.google.android.gms.common.annotation.KeepName *;
}
-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}

View File

@ -110,6 +110,11 @@
-optimizations !class/merging/horizontal
-optimizations !class/merging/vertical
# This optimisation causes corrupt bytecode if we run more than two passes.
# Testing shows that running the extra passes of everything else saves us
# more than this optimisation does, so bye bye!
-optimizations !code/allocation/variable
# Keep miscellaneous targets.
# Keep the annotation.
@ -207,3 +212,9 @@
# Suppress warnings about missing descriptor classes.
#-dontnote **,!ch.boye.**,!org.mozilla.gecko.sync.**
-include "play-services-keeps.cfg"
# Don't print spurious warnings from the support library.
# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl
-dontnote android.support.**

View File

@ -0,0 +1,40 @@
# Proguard step for stripping debug information.
#
# This is useful to work around a bug in the way Proguard handles debug information: it
# sometimes corrupts it. Classes with corrupt debug information cannot be dexed, but
# classes with *no* debug information can be. There's no way to configure Proguard to
# delete debug information on a per-class basis, so we need this special extra step for
# stripping debug information only from those classes for which the Proguard bug is
# encountered.
#
# Currently, this pass is applied to all bundled library jars for which we are not
# compiling the source. This is slightly more than is strictly necessary to work around
# the Proguard bug, but such debug information is of negligible value and stripping it
# too slightly simplifies the makefile and saves us a handful of kilobytes of binary size.
#
# Configuring Proguard to do nothing except strip metadata is done by having it run only
# the obfuscation pass, but with a configuration that prevents it from renaming any classes.
# It then attempts to delete class metadata, so we further configure it not to do so for
# anything except the problematic debug information.
# Run only the obfuscator.
-dontoptimize
-dontshrink
-dontpreverify
-verbose
# Don't rename anything.
-keeppackagenames
# Seriously, don't rename anything.
-keep class *
-keepclassmembers class * {
*;
}
# Don't delete other useful metadata.
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,*Annotation*,EnclosingMethod
# Don't print spurious warnings from the support library.
# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl
-dontnote android.support.**

View File

@ -5,7 +5,7 @@
package org.mozilla.search;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.LocaleAware;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
@ -39,7 +39,7 @@ import com.nineoldandroids.animation.ObjectAnimator;
* State management is delegated to child fragments. Fragments communicate
* with each other by passing messages through this activity.
*/
public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
public class SearchActivity extends Locales.LocaleAwareFragmentActivity
implements AcceptsSearchQuery, SearchEngineCallback {
private static final String LOGTAG = "GeckoSearchActivity";

View File

@ -5,7 +5,7 @@
package org.mozilla.search;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.LocaleAware;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
@ -41,7 +41,7 @@ public class SearchPreferenceActivity extends PreferenceActivity {
@Override
@SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
LocaleAware.initializeLocale(getApplicationContext());
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME);

View File

@ -12,9 +12,9 @@ import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoJarReader;
@ -188,7 +188,7 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
final JSONObject all = new JSONObject(FileUtils.getFileContents(prefFile));
// First, check to see if there's a locale-specific override.
final String languageTag = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
final String languageTag = Locales.getLanguageTag(Locale.getDefault());
final String overridesKey = "LocalizablePreferences." + languageTag;
if (all.has(overridesKey)) {
final JSONObject overridePrefs = all.getJSONObject(overridesKey);
@ -421,7 +421,7 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
final Locale locale = Locale.getDefault();
// First, try a file path for the full locale.
final String languageTag = BrowserLocaleManager.getLanguageTag(locale);
final String languageTag = Locales.getLanguageTag(locale);
String url = getSearchPluginsJarURL(languageTag, fileName);
InputStream in = GeckoJarReader.getStream(url);
@ -430,7 +430,7 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
}
// If that doesn't work, try a file path for just the language.
final String language = BrowserLocaleManager.getLanguage(locale);
final String language = Locales.getLanguage(locale);
if (!languageTag.equals(language)) {
url = getSearchPluginsJarURL(language, fileName);
in = GeckoJarReader.getStream(url);

View File

@ -96,7 +96,7 @@ public class TestSuggestedSites extends BrowserTestCase {
@Override
public File getDistributionFile(String name) {
for (Locale locale : filesPerLocale.keySet()) {
if (name.startsWith("suggestedsites/locales/" + BrowserLocaleManager.getLanguageTag(locale))) {
if (name.startsWith("suggestedsites/locales/" + Locales.getLanguageTag(locale))) {
return filesPerLocale.get(locale);
}
}

View File

@ -39,10 +39,8 @@
}
.addon-item[isDisabled="true"] .options-header,
.addon-item:not([optionsURL]) .options-header,
.addon-item[optionsURL=""] .options-header,
.addon-item[isDisabled="true"] .options-box,
.addon-item:not([optionsURL]) .options-box,
.addon-item[optionsURL=""] .options-box {
display: none;
}

View File

@ -249,11 +249,7 @@ Migrator.prototype = {
verified: signedInUser.verified,
prefs: this._getSentinelPrefs(),
};
if (Weave.Service.setFxaMigrationSentinel) {
yield Weave.Service.setFxaMigrationSentinel(sentinel);
} else {
this.log.warn("Waiting on bug 1017433; no sync sentinel");
}
yield Weave.Service.setFxAMigrationSentinel(sentinel);
}),
/* Ask sync to upload the migration sentinal if we (or any other linked device)
@ -269,11 +265,7 @@ Migrator.prototype = {
/* Ask sync to return a migration sentinel if one exists, otherwise return null */
_getSyncMigrationSentinel: Task.async(function* () {
yield WeaveService.whenLoaded();
if (!Weave.Service.getFxaMigrationSentinel) {
this.log.warn("Waiting on bug 1017433; no sync sentinel");
return null;
}
let sentinel = yield Weave.Service.getFxaMigrationSentinel();
let sentinel = yield Weave.Service.getFxAMigrationSentinel();
this.log.debug("got migration sentinel ${}", sentinel);
return sentinel;
}),
@ -301,19 +293,11 @@ Migrator.prototype = {
// Prevent sync from automatically starting
_blockSync() {
if (Weave.Service.scheduler.blockSync) {
Weave.Service.scheduler.blockSync();
} else {
this.log.warn("Waiting on bug 1019408; sync not blocked");
}
Weave.Service.scheduler.blockSync();
},
_unblockSync() {
if (Weave.Service.scheduler.unblockSync) {
Weave.Service.scheduler.unblockSync();
} else {
this.log.warn("Waiting on bug 1019408; sync not unblocked");
}
Weave.Service.scheduler.unblockSync();
},
/*

View File

@ -399,6 +399,13 @@ this.BrowserIDManager.prototype = {
this._shouldHaveSyncKeyBundle = false;
},
/**
* Return credentials hosts for this identity only.
*/
_getSyncCredentialsHosts: function() {
return Utils.getSyncCredentialsHostsFxA();
},
/**
* The current state of the auth credentials.
*

View File

@ -54,6 +54,9 @@ HMAC_EVENT_INTERVAL: 600000,
// How long to wait between sync attempts if the Master Password is locked.
MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000, // 15 minutes
// The default for how long we "block" sync from running when doing a migration.
DEFAULT_BLOCK_PERIOD: 2 * 24 * 60 * 60 * 1000, // 2 days
// Separate from the ID fetch batch size to allow tuning for mobile.
MOBILE_BATCH_SIZE: 50,

View File

@ -484,11 +484,18 @@ IdentityManager.prototype = {
Services.logins.addLogin(login);
},
/**
* Return credentials hosts for this identity only.
*/
_getSyncCredentialsHosts: function() {
return Utils.getSyncCredentialsHostsLegacy();
},
/**
* Deletes Sync credentials from the password manager.
*/
deleteSyncCredentials: function deleteSyncCredentials() {
for (let host of Utils.getSyncCredentialsHosts()) {
for (let host of this._getSyncCredentialsHosts()) {
let logins = Services.logins.findLogins({}, host, "", "");
for each (let login in logins) {
Services.logins.removeLogin(login);

View File

@ -497,6 +497,46 @@ SyncScheduler.prototype = {
if (this.syncTimer)
this.syncTimer.clear();
},
/**
* Prevent new syncs from starting. This is used by the FxA migration code
* where we can't afford to have a sync start partway through the migration.
* To handle the edge-case of a sync starting and not stopping, we store
* this state in a pref, so on the next startup we remain blocked (and thus
* sync will never start) so the migration can complete.
*
* As a safety measure, we only block for some period of time, and after
* that it will automatically unblock. This ensures that if things go
* really pear-shaped and we never end up calling unblockSync() we haven't
* completely broken the world.
*/
blockSync: function(until = null) {
if (!until) {
until = Date.now() + DEFAULT_BLOCK_PERIOD;
}
// until is specified in ms, but Prefs can't hold that much
Svc.Prefs.set("scheduler.blocked-until", Math.floor(until / 1000));
},
unblockSync: function() {
Svc.Prefs.reset("scheduler.blocked-until");
// the migration code should be ready to roll, so resume normal operations.
this.checkSyncStatus();
},
get isBlocked() {
let until = Svc.Prefs.get("scheduler.blocked-until");
if (until === undefined) {
return false;
}
if (until <= Math.floor(Date.now() / 1000)) {
// we were previously blocked but the time has expired.
Svc.Prefs.reset("scheduler.blocked-until");
return false;
}
// we remain blocked.
return true;
},
};
const LOG_PREFIX_SUCCESS = "success-";

View File

@ -105,68 +105,6 @@ WBORecord.prototype = {
Utils.deferGetSet(WBORecord, "data", ["id", "modified", "sortindex", "payload"]);
/**
* An interface and caching layer for records.
*/
this.RecordManager = function RecordManager(service) {
this.service = service;
this._log = Log.repository.getLogger(this._logName);
this._records = {};
}
RecordManager.prototype = {
_recordType: WBORecord,
_logName: "Sync.RecordManager",
import: function RecordMgr_import(url) {
this._log.trace("Importing record: " + (url.spec ? url.spec : url));
try {
// Clear out the last response with empty object if GET fails
this.response = {};
this.response = this.service.resource(url).get();
// Don't parse and save the record on failure
if (!this.response.success)
return null;
let record = new this._recordType(url);
record.deserialize(this.response);
return this.set(url, record);
} catch(ex) {
this._log.debug("Failed to import record: " + Utils.exceptionStr(ex));
return null;
}
},
get: function RecordMgr_get(url) {
// Use a url string as the key to the hash
let spec = url.spec ? url.spec : url;
if (spec in this._records)
return this._records[spec];
return this.import(url);
},
set: function RecordMgr_set(url, record) {
let spec = url.spec ? url.spec : url;
return this._records[spec] = record;
},
contains: function RecordMgr_contains(url) {
if ((url.spec || url) in this._records)
return true;
return false;
},
clearCache: function recordMgr_clearCache() {
this._records = {};
},
del: function RecordMgr_del(url) {
delete this._records[url];
}
};
this.CryptoWrapper = function CryptoWrapper(collection, id) {
this.cleartext = {};
WBORecord.call(this, collection, id);
@ -269,6 +207,67 @@ CryptoWrapper.prototype = {
Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]);
Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");
/**
* An interface and caching layer for records.
*/
this.RecordManager = function RecordManager(service) {
this.service = service;
this._log = Log.repository.getLogger(this._logName);
this._records = {};
}
RecordManager.prototype = {
_recordType: CryptoWrapper,
_logName: "Sync.RecordManager",
import: function RecordMgr_import(url) {
this._log.trace("Importing record: " + (url.spec ? url.spec : url));
try {
// Clear out the last response with empty object if GET fails
this.response = {};
this.response = this.service.resource(url).get();
// Don't parse and save the record on failure
if (!this.response.success)
return null;
let record = new this._recordType(url);
record.deserialize(this.response);
return this.set(url, record);
} catch(ex) {
this._log.debug("Failed to import record: " + Utils.exceptionStr(ex));
return null;
}
},
get: function RecordMgr_get(url) {
// Use a url string as the key to the hash
let spec = url.spec ? url.spec : url;
if (spec in this._records)
return this._records[spec];
return this.import(url);
},
set: function RecordMgr_set(url, record) {
let spec = url.spec ? url.spec : url;
return this._records[spec] = record;
},
contains: function RecordMgr_contains(url) {
if ((url.spec || url) in this._records)
return true;
return false;
},
clearCache: function recordMgr_clearCache() {
this._records = {};
},
del: function RecordMgr_del(url) {
delete this._records[url];
}
};
/**
* Keeps track of mappings between collection names ('tabs') and KeyBundles.

View File

@ -1280,7 +1280,16 @@ Sync11Service.prototype = {
histogram = Services.telemetry.getHistogramById("WEAVE_COMPLETE_SUCCESS_COUNT");
histogram.add(1);
// We successfully synchronized. Now let's update our declined engines.
// We successfully synchronized.
// Try and fetch the migration sentinel - it will end up in the recordManager
// cache, so a sync migration doesn't need a server round-trip.
// If we have no clusterURL, we are probably doing a node reassignment
// do don't attempt to get the credentials.
if (this.clusterURL) {
this.recordManager.get(this.storageURL + "meta/fxa_credentials");
}
// Now let's update our declined engines.
let meta = this.recordManager.get(this.metaURL);
if (!meta) {
this._log.warn("No meta/global; can't update declined state.");
@ -1316,6 +1325,92 @@ Sync11Service.prototype = {
this.recordManager.set(this.metaURL, meta);
},
/**
* Get a migration sentinel for the Firefox Accounts migration.
* Returns a JSON blob - it is up to callers of this to make sense of the
* data.
*
* Returns a promise that resolves with the sentinel, or null.
*/
getFxAMigrationSentinel: function() {
if (this._shouldLogin()) {
this._log.debug("In getFxAMigrationSentinel: should login.");
if (!this.login()) {
this._log.debug("Can't get migration sentinel: login returned false.");
return Promise.resolve(null);
}
}
if (!this.identity.syncKeyBundle) {
this._log.error("Can't get migration sentinel: no syncKeyBundle.");
return Promise.resolve(null);
}
try {
let collectionURL = this.storageURL + "meta/fxa_credentials";
let cryptoWrapper = this.recordManager.get(collectionURL);
if (!cryptoWrapper.payload) {
// nothing to decrypt - .decrypt is noisy in that case, so just bail
// now.
return Promise.resolve(null);
}
// If the payload has a sentinel it means we must have put back the
// decrypted version last time we were called.
if (cryptoWrapper.payload.sentinel) {
return Promise.resolve(cryptoWrapper.payload.sentinel);
}
// If decryption fails it almost certainly means the key is wrong - but
// it's not clear if we need to take special action for that case?
let payload = cryptoWrapper.decrypt(this.identity.syncKeyBundle);
// After decrypting the ciphertext is lost, so we just stash the
// decrypted payload back into the wrapper.
cryptoWrapper.payload = payload;
return Promise.resolve(payload.sentinel);
} catch (ex) {
this._log.error("Failed to fetch the migration sentinel: ${}", ex);
return Promise.resolve(null);
}
},
/**
* Set a migration sentinel for the Firefox Accounts migration.
* Accepts a JSON blob - it is up to callers of this to make sense of the
* data.
*
* Returns a promise that resolves with a boolean which indicates if the
* sentinel was successfully written.
*/
setFxAMigrationSentinel: function(sentinel) {
if (this._shouldLogin()) {
this._log.debug("In setFxAMigrationSentinel: should login.");
if (!this.login()) {
this._log.debug("Can't set migration sentinel: login returned false.");
return Promise.resolve(false);
}
}
if (!this.identity.syncKeyBundle) {
this._log.error("Can't set migration sentinel: no syncKeyBundle.");
return Promise.resolve(false);
}
try {
let collectionURL = this.storageURL + "meta/fxa_credentials";
let cryptoWrapper = new CryptoWrapper("meta", "fxa_credentials");
cryptoWrapper.cleartext.sentinel = sentinel;
cryptoWrapper.encrypt(this.identity.syncKeyBundle);
let res = this.resource(collectionURL);
let response = res.put(cryptoWrapper.toJSON());
if (!response.success) {
throw response;
}
this.recordManager.set(collectionURL, cryptoWrapper);
} catch (ex) {
this._log.error("Failed to set the migration sentinel: ${}", ex);
return Promise.resolve(false);
}
return Promise.resolve(true);
},
/**
* If we have a passphrase, rather than a 25-alphadigit sync key,
* use the provided sync ID to bootstrap it using PBKDF2.

View File

@ -596,13 +596,30 @@ this.Utils = {
* reset when we drop sync credentials, etc.
*/
getSyncCredentialsHosts: function() {
let result = new Set(this.getSyncCredentialsHostsLegacy());
for (let host of this.getSyncCredentialsHostsFxA()) {
result.add(host);
}
return result;
},
/*
* Get the "legacy" identity hosts.
*/
getSyncCredentialsHostsLegacy: function() {
// the legacy sync host
return new Set([PWDMGR_HOST]);
},
/*
* Get the FxA identity hosts.
*/
getSyncCredentialsHostsFxA: function() {
// This is somewhat expensive and the result static, so we cache the result.
if (this._syncCredentialsHosts) {
return this._syncCredentialsHosts;
if (this._syncCredentialsHostsFxA) {
return this._syncCredentialsHostsFxA;
}
let result = new Set();
// the legacy sync host
result.add(PWDMGR_HOST);
// the FxA host
result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
//
@ -621,7 +638,7 @@ this.Utils = {
let uri = Services.io.newURI(prefVal, null, null);
result.add(uri.prePath);
}
return this._syncCredentialsHosts = result;
return this._syncCredentialsHostsFxA = result;
},
};

View File

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");
// Simple test for block/unblock.
add_task(function *() {
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
Weave.Service.scheduler.blockSync();
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
Weave.Service.scheduler.unblockSync();
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
// now check the "until" functionality.
let until = Date.now() + 1000;
Weave.Service.scheduler.blockSync(until);
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
// wait for 'until' to pass.
yield new Promise((resolve, reject) => {
CommonUtils.namedTimer(resolve, 1000, {}, "timer");
});
// should have automagically unblocked and removed the pref.
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
});
function run_test() {
run_next_test();
}

View File

@ -103,20 +103,18 @@ add_task(function *testMigration() {
// monkey-patch the migration sentinel code so we know it was called.
let haveStartedSentinel = false;
// (This is waiting on bug 1017433)
/**
let origSetFxaMigrationSentinel = Service.setFxaMigrationSentinel;
let origSetFxAMigrationSentinel = Service.setFxAMigrationSentinel;
let promiseSentinelWritten = new Promise((resolve, reject) => {
Service.setFxaMigrationSentinel = function(arg) {
Service.setFxAMigrationSentinel = function(arg) {
haveStartedSentinel = true;
return origSetFxaMigrationSentinel.call(Service, arg).then(result => {
Service.setFxaMigrationSentinel = origSetFxaMigrationSentinel;
return origSetFxAMigrationSentinel.call(Service, arg).then(result => {
Service.setFxAMigrationSentinel = origSetFxAMigrationSentinel;
resolve(result);
return result;
});
}
});
**/
// We are now configured for legacy sync, but we aren't in an EOL state yet,
// so should still be not waiting for a user.
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null,
@ -199,10 +197,7 @@ add_task(function *testMigration() {
Assert.ok(!haveStartedSentinel, "haven't written a sentinel yet");
// sync should be blocked from continuing
// (This is waiting on bug 1019408)
/**
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.")
**/
wasWaiting = true;
throw ex;
@ -227,16 +222,10 @@ add_task(function *testMigration() {
// The migration is now going to run to completion.
// sync should still be "blocked"
// (This is waiting on bug 1019408)
/**
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.");
**/
// We should see the migration sentinel written and it should return true.
// (This is waiting on bug 1017433)
/**
Assert.ok((yield promiseSentinelWritten), "wrote the sentinel");
**/
// And we should see a new sync start
yield promiseFinalSync;

View File

@ -0,0 +1,150 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the reading and writing of the sync migration sentinel.
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://testing-common/services/common/logging.js");
Cu.import("resource://services-sync/record.js");
// Set our username pref early so sync initializes with the legacy provider.
Services.prefs.setCharPref("services.sync.username", "foo");
// Now import sync
Cu.import("resource://services-sync/service.js");
const USER = "foo";
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";
function promiseStopServer(server) {
return new Promise((resolve, reject) => {
server.stop(resolve);
});
}
let numServerRequests = 0;
// Helpers
function configureLegacySync() {
let contents = {
meta: {global: {}},
crypto: {},
};
setBasicCredentials(USER, "password", PASSPHRASE);
numServerRequests = 0;
let server = new SyncServer({
onRequest: () => {
++numServerRequests
}
});
server.registerUser(USER, "password");
server.createContents(USER, contents);
server.start();
Service.serverURL = server.baseURI;
Service.clusterURL = server.baseURI;
Service.identity.username = USER;
Service._updateCachedURLs();
return server;
}
// Test a simple round-trip of the get/set functions.
add_task(function *() {
// Arrange for a legacy sync user.
let server = configureLegacySync();
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
let sentinel = {foo: "bar"};
yield Service.setFxAMigrationSentinel(sentinel);
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
yield promiseStopServer(server);
});
// Test the records are cached by the record manager.
add_task(function *() {
// Arrange for a legacy sync user.
let server = configureLegacySync();
Service.login();
// Reset the request count here as the login would have made some.
numServerRequests = 0;
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
Assert.equal(numServerRequests, 1, "first fetch should hit the server");
let sentinel = {foo: "bar"};
yield Service.setFxAMigrationSentinel(sentinel);
Assert.equal(numServerRequests, 2, "setting sentinel should hit the server");
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
Assert.equal(numServerRequests, 2, "second fetch should not should hit the server");
// Clobber the caches and ensure we still get the correct value back when we
// do hit the server.
Service.recordManager.clearCache();
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
Assert.equal(numServerRequests, 3, "should have re-hit the server with empty caches");
yield promiseStopServer(server);
});
// Test the records are cached by a sync.
add_task(function* () {
let server = configureLegacySync();
// A first sync clobbers meta/global due to it being empty, so we first
// do a sync which forces a good set of data on the server.
Service.sync();
// Now create a sentinel exists on the server. It's encrypted, so we need to
// put an encrypted version.
let cryptoWrapper = new CryptoWrapper("meta", "fxa_credentials");
let sentinel = {foo: "bar"};
cryptoWrapper.cleartext = {
id: "fxa_credentials",
sentinel: sentinel,
deleted: false,
}
cryptoWrapper.encrypt(Service.identity.syncKeyBundle);
let payload = {
ciphertext: cryptoWrapper.ciphertext,
IV: cryptoWrapper.IV,
hmac: cryptoWrapper.hmac,
};
server.createContents(USER, {
meta: {fxa_credentials: payload},
crypto: {},
});
// Another sync - this will cause the encrypted record to be fetched.
Service.sync();
// Reset the request count here as the sync will have made many!
numServerRequests = 0;
// Asking for the sentinel should use the copy cached in the record manager.
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it");
Assert.equal(numServerRequests, 0, "should not have hit the server");
// And asking for it again should work (we have to work around the fact the
// ciphertext is clobbered on first decrypt...)
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it again");
Assert.equal(numServerRequests, 0, "should not have hit the server");
yield promiseStopServer(server);
});
function run_test() {
initTestLogging();
run_next_test();
}

View File

@ -174,4 +174,6 @@ skip-if = ! healthreport
[test_warn_on_truncated_response.js]
# FxA migration
[test_block_sync.js]
[test_fxa_migration.js]
[test_fxa_migration_sentinel.js]

View File

@ -675,8 +675,8 @@ var PageStyleActor = protocol.ActorClass({
// the size of the element.
let clientRect = node.rawNode.getBoundingClientRect();
layout.width = Math.round(clientRect.width);
layout.height = Math.round(clientRect.height);
layout.width = Math.ceil(clientRect.width);
layout.height = Math.ceil(clientRect.height);
// We compute and update the values of margins & co.
let style = CssLogic.getComputedStyle(node.rawNode);