mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 10:00:54 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
2a69ec4f96
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "0e1227044422a4fae14ec6b1d035380d693cff97",
|
||||
"revision": "7736b561cce1e1738de0901b796a1da3cbcc8985",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
};
|
||||
})();
|
||||
|
@ -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() {
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -143,7 +143,7 @@ let AboutHome = {
|
||||
break;
|
||||
|
||||
case "AboutHome:Apps":
|
||||
window.openUILinkIn("https://marketplace.mozilla.org/", "tab");
|
||||
window.BrowserOpenApps();
|
||||
break;
|
||||
|
||||
case "AboutHome:Addons":
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -175,7 +175,7 @@
|
||||
border-color: initial!important;
|
||||
}
|
||||
|
||||
#waterfall-details {
|
||||
#timeline-waterfall-details {
|
||||
-moz-padding-start: 8px;
|
||||
-moz-padding-end: 8px;
|
||||
padding-top: 8vh;
|
||||
|
@ -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. */
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -99,6 +99,12 @@ treecol {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.category-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* header */
|
||||
|
||||
#header-advanced {
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -38,4 +38,5 @@ public interface LocaleManager {
|
||||
* use.
|
||||
*/
|
||||
Locale onSystemConfigurationChanged(Context context, Resources resources, Configuration configuration, Locale currentActivityLocale);
|
||||
String getFallbackLocaleTag();
|
||||
}
|
||||
|
116
mobile/android/base/Locales.java
Normal file
116
mobile/android/base/Locales.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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 -->
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
19
mobile/android/config/proguard/play-services-keeps.cfg
Normal file
19
mobile/android/config/proguard/play-services-keeps.cfg
Normal 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;
|
||||
}
|
@ -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.**
|
40
mobile/android/config/proguard/strip-libs.cfg
Normal file
40
mobile/android/config/proguard/strip-libs.cfg
Normal 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.**
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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-";
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
|
37
services/sync/tests/unit/test_block_sync.js
Normal file
37
services/sync/tests/unit/test_block_sync.js
Normal 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();
|
||||
}
|
@ -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;
|
||||
|
150
services/sync/tests/unit/test_fxa_migration_sentinel.js
Normal file
150
services/sync/tests/unit/test_fxa_migration_sentinel.js
Normal 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();
|
||||
}
|
@ -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]
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user