mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
98164c5baf
2
CLOBBER
2
CLOBBER
@ -22,4 +22,4 @@
|
|||||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||||
# don't change CLOBBER for WebIDL changes any more.
|
# don't change CLOBBER for WebIDL changes any more.
|
||||||
|
|
||||||
Bug 1095234 - Bug 1091260 stopped packaging a devtools file with EXTRA_JS_MODULES while making it require pre-processing.
|
Bug 1084498 - Android build tools dependency.
|
||||||
|
@ -697,6 +697,38 @@ html, .fx-embedded, #main,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.standalone .room-conversation-wrapper {
|
||||||
|
height: calc(100% - 50px - 60px);
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-conversation-wrapper header {
|
||||||
|
background: #000;
|
||||||
|
height: 50px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-conversation-wrapper header h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 50px;
|
||||||
|
text-indent: 50px;
|
||||||
|
background-image: url("../img/firefox-logo.png");
|
||||||
|
background-size: 30px;
|
||||||
|
background-position: 10px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-conversation-wrapper footer {
|
||||||
|
background: #000;
|
||||||
|
height: 60px;
|
||||||
|
margin-top: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-conversation-wrapper footer a {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the hangup button for room conversations.
|
* Hides the hangup button for room conversations.
|
||||||
*/
|
*/
|
||||||
@ -745,7 +777,7 @@ html, .fx-embedded, #main,
|
|||||||
|
|
||||||
.standalone .room-inner-info-area {
|
.standalone .room-inner-info-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 35%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 25%;
|
right: 25%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -767,6 +799,7 @@ html, .fx-embedded, #main,
|
|||||||
padding: .5em 3em .3em 3em;
|
padding: .5em 3em .3em 3em;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.standalone .room-conversation h2.room-name {
|
.standalone .room-conversation h2.room-name {
|
||||||
@ -796,7 +829,7 @@ html, .fx-embedded, #main,
|
|||||||
|
|
||||||
.standalone .room-conversation .conversation-toolbar {
|
.standalone .room-conversation .conversation-toolbar {
|
||||||
background: #000;
|
background: #000;
|
||||||
border-top: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
|
.standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
|
||||||
|
@ -10,6 +10,15 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
|
|
||||||
|
// Error numbers taken from
|
||||||
|
// https://github.com/mozilla-services/loop-server/blob/master/loop/errno.json
|
||||||
|
var SERVER_CODES = loop.store.SERVER_CODES = {
|
||||||
|
INVALID_TOKEN: 105,
|
||||||
|
EXPIRED: 111,
|
||||||
|
ROOM_FULL: 202
|
||||||
|
};
|
||||||
|
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES = {
|
var ROOM_STATES = loop.store.ROOM_STATES = {
|
||||||
// The initial state of the room
|
// The initial state of the room
|
||||||
@ -84,7 +93,8 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
this._storeState = {
|
this._storeState = {
|
||||||
roomState: ROOM_STATES.INIT,
|
roomState: ROOM_STATES.INIT,
|
||||||
audioMuted: false,
|
audioMuted: false,
|
||||||
videoMuted: false
|
videoMuted: false,
|
||||||
|
failureReason: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,13 +122,24 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
* @param {sharedActions.RoomFailure} actionData
|
* @param {sharedActions.RoomFailure} actionData
|
||||||
*/
|
*/
|
||||||
roomFailure: function(actionData) {
|
roomFailure: function(actionData) {
|
||||||
|
function getReason(serverCode) {
|
||||||
|
switch (serverCode) {
|
||||||
|
case SERVER_CODES.INVALID_TOKEN:
|
||||||
|
case SERVER_CODES.EXPIRED:
|
||||||
|
return FAILURE_REASONS.EXPIRED_OR_INVALID;
|
||||||
|
default:
|
||||||
|
return FAILURE_REASONS.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.error("Error in state `" + this._storeState.roomState + "`:",
|
console.error("Error in state `" + this._storeState.roomState + "`:",
|
||||||
actionData.error);
|
actionData.error);
|
||||||
|
|
||||||
this.setStoreState({
|
this.setStoreState({
|
||||||
error: actionData.error,
|
error: actionData.error,
|
||||||
roomState: actionData.error.errno === 202 ? ROOM_STATES.FULL
|
failureReason: getReason(actionData.error.errno),
|
||||||
: ROOM_STATES.FAILED
|
roomState: actionData.error.errno === SERVER_CODES.ROOM_FULL ?
|
||||||
|
ROOM_STATES.FULL : ROOM_STATES.FAILED
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -228,6 +249,11 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
* Handles the action to join to a room.
|
* Handles the action to join to a room.
|
||||||
*/
|
*/
|
||||||
joinRoom: function() {
|
joinRoom: function() {
|
||||||
|
// Reset the failure reason if necessary.
|
||||||
|
if (this.getStoreState().failureReason) {
|
||||||
|
this.setStoreState({failureReason: undefined});
|
||||||
|
}
|
||||||
|
|
||||||
this._mozLoop.rooms.join(this._storeState.roomToken,
|
this._mozLoop.rooms.join(this._storeState.roomToken,
|
||||||
function(error, responseData) {
|
function(error, responseData) {
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -275,11 +301,17 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles disconnection of this local client from the sdk servers.
|
* Handles disconnection of this local client from the sdk servers.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.ConnectionFailure} actionData
|
||||||
*/
|
*/
|
||||||
connectionFailure: function() {
|
connectionFailure: function(actionData) {
|
||||||
// Treat all reasons as something failed. In theory, clientDisconnected
|
// Treat all reasons as something failed. In theory, clientDisconnected
|
||||||
// could be a success case, but there's no way we should be intentionally
|
// could be a success case, but there's no way we should be intentionally
|
||||||
// sending that and still have the window open.
|
// sending that and still have the window open.
|
||||||
|
this.setStoreState({
|
||||||
|
failureReason: actionData.reason
|
||||||
|
});
|
||||||
|
|
||||||
this._leaveRoom(ROOM_STATES.FAILED);
|
this._leaveRoom(ROOM_STATES.FAILED);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ var loop = loop || {};
|
|||||||
loop.OTSdkDriver = (function() {
|
loop.OTSdkDriver = (function() {
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a wrapper for the OT sdk. It is used to translate the SDK events into
|
* This is a wrapper for the OT sdk. It is used to translate the SDK events into
|
||||||
@ -47,8 +48,11 @@ loop.OTSdkDriver = (function() {
|
|||||||
// the initial connect of the session. This saves time when setting up
|
// the initial connect of the session. This saves time when setting up
|
||||||
// the media.
|
// the media.
|
||||||
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
|
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
|
||||||
this.publisherConfig,
|
this.publisherConfig);
|
||||||
this._onPublishComplete.bind(this));
|
this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
|
||||||
|
this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
|
||||||
|
this.publisher.on("accessDialogOpened",
|
||||||
|
this._onAccessDialogOpened.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,16 +100,12 @@ loop.OTSdkDriver = (function() {
|
|||||||
*/
|
*/
|
||||||
disconnectSession: function() {
|
disconnectSession: function() {
|
||||||
if (this.session) {
|
if (this.session) {
|
||||||
this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
|
this.session.off("streamCreated connectionDestroyed sessionDisconnected");
|
||||||
this.session.off("connectionDestroyed",
|
|
||||||
this._onConnectionDestroyed.bind(this));
|
|
||||||
this.session.off("sessionDisconnected",
|
|
||||||
this._onSessionDisconnected.bind(this));
|
|
||||||
|
|
||||||
this.session.disconnect();
|
this.session.disconnect();
|
||||||
delete this.session;
|
delete this.session;
|
||||||
}
|
}
|
||||||
if (this.publisher) {
|
if (this.publisher) {
|
||||||
|
this.publisher.off("accessAllowed accessDenied accessDialogOpened");
|
||||||
this.publisher.destroy();
|
this.publisher.destroy();
|
||||||
delete this.publisher;
|
delete this.publisher;
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ loop.OTSdkDriver = (function() {
|
|||||||
if (error) {
|
if (error) {
|
||||||
console.error("Failed to complete connection", error);
|
console.error("Failed to complete connection", error);
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
reason: "couldNotConnect"
|
reason: FAILURE_REASONS.COULD_NOT_CONNECT
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ loop.OTSdkDriver = (function() {
|
|||||||
// We only need to worry about the network disconnected reason here.
|
// We only need to worry about the network disconnected reason here.
|
||||||
if (event.reason === "networkDisconnected") {
|
if (event.reason === "networkDisconnected") {
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
reason: "networkDisconnected"
|
reason: FAILURE_REASONS.NETWORK_DISCONNECTED
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -188,24 +188,42 @@ loop.OTSdkDriver = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the sdk when the media access dialog is opened.
|
||||||
|
* Prevents the default action, to prevent the SDK's "allow access"
|
||||||
|
* dialog from being shown.
|
||||||
|
*
|
||||||
|
* @param {OT.Event} event
|
||||||
|
*/
|
||||||
|
_onAccessDialogOpened: function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the publishing being complete.
|
* Handles the publishing being complete.
|
||||||
*
|
*
|
||||||
* @param {Error} error An OT error object, null if there was no error.
|
* @param {OT.Event} event
|
||||||
*/
|
*/
|
||||||
_onPublishComplete: function(error) {
|
_onPublishComplete: function(event) {
|
||||||
if (error) {
|
event.preventDefault();
|
||||||
console.error("Failed to initialize publisher", error);
|
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
|
||||||
reason: "noMedia"
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._publisherReady = true;
|
this._publisherReady = true;
|
||||||
this._maybePublishLocalStream();
|
this._maybePublishLocalStream();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles publishing of media being denied.
|
||||||
|
*
|
||||||
|
* @param {OT.Event} event
|
||||||
|
*/
|
||||||
|
_onPublishDenied: function(event) {
|
||||||
|
// This prevents the SDK's "access denied" dialog showing.
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
|
reason: FAILURE_REASONS.MEDIA_DENIED
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes the local stream if the session is connected
|
* Publishes the local stream if the session is connected
|
||||||
* and the publisher is ready.
|
* and the publisher is ready.
|
||||||
|
@ -17,6 +17,14 @@ loop.shared.utils = (function(mozL10n) {
|
|||||||
AUDIO_ONLY: "audio"
|
AUDIO_ONLY: "audio"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var FAILURE_REASONS = {
|
||||||
|
MEDIA_DENIED: "reason-media-denied",
|
||||||
|
COULD_NOT_CONNECT: "reason-could-not-connect",
|
||||||
|
NETWORK_DISCONNECTED: "reason-network-disconnected",
|
||||||
|
EXPIRED_OR_INVALID: "reason-expired-or-invalid",
|
||||||
|
UNKNOWN: "reason-unknown"
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a given date into an l10n-friendly string.
|
* Format a given date into an l10n-friendly string.
|
||||||
*
|
*
|
||||||
@ -110,6 +118,7 @@ loop.shared.utils = (function(mozL10n) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
CALL_TYPES: CALL_TYPES,
|
CALL_TYPES: CALL_TYPES,
|
||||||
|
FAILURE_REASONS: FAILURE_REASONS,
|
||||||
Helper: Helper,
|
Helper: Helper,
|
||||||
composeCallUrlEmail: composeCallUrlEmail,
|
composeCallUrlEmail: composeCallUrlEmail,
|
||||||
formatDate: formatDate,
|
formatDate: formatDate,
|
||||||
|
@ -18,6 +18,13 @@ body,
|
|||||||
font-family: Open Sans,sans-serif;
|
font-family: Open Sans,sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: the is-standalone-room class is dynamically set by the StandaloneRoomView.
|
||||||
|
*/
|
||||||
|
.standalone.is-standalone-room {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
.standalone-header {
|
.standalone-header {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
@ -11,8 +11,10 @@ var loop = loop || {};
|
|||||||
loop.standaloneRoomViews = (function(mozL10n) {
|
loop.standaloneRoomViews = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
var StandaloneRoomInfoArea = React.createClass({displayName: 'StandaloneRoomInfoArea',
|
var StandaloneRoomInfoArea = React.createClass({displayName: 'StandaloneRoomInfoArea',
|
||||||
@ -39,6 +41,20 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return String An appropriate string according to the failureReason.
|
||||||
|
*/
|
||||||
|
_getFailureString: function() {
|
||||||
|
switch(this.props.failureReason) {
|
||||||
|
case FAILURE_REASONS.MEDIA_DENIED:
|
||||||
|
return mozL10n.get("rooms_media_denied_message");
|
||||||
|
case FAILURE_REASONS.EXPIRED_OR_INVALID:
|
||||||
|
return mozL10n.get("rooms_unavailable_notification_message");
|
||||||
|
default:
|
||||||
|
return mozL10n.get("status_error");
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
_renderContent: function() {
|
_renderContent: function() {
|
||||||
switch(this.props.roomState) {
|
switch(this.props.roomState) {
|
||||||
case ROOM_STATES.INIT:
|
case ROOM_STATES.INIT:
|
||||||
@ -67,6 +83,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
React.DOM.p(null, this._renderCallToActionLink())
|
React.DOM.p(null, this._renderCallToActionLink())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
case ROOM_STATES.FAILED:
|
||||||
|
return (
|
||||||
|
React.DOM.p({className: "failed-room-message"},
|
||||||
|
this._getFailureString()
|
||||||
|
)
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -81,6 +103,43 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var StandaloneRoomHeader = React.createClass({displayName: 'StandaloneRoomHeader',
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
React.DOM.header(null,
|
||||||
|
React.DOM.h1(null, mozL10n.get("clientShortname2"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var StandaloneRoomFooter = React.createClass({displayName: 'StandaloneRoomFooter',
|
||||||
|
_getContent: function() {
|
||||||
|
return mozL10n.get("legal_text_and_links", {
|
||||||
|
"clientShortname": mozL10n.get("clientShortname2"),
|
||||||
|
"terms_of_use_url": React.renderComponentToStaticMarkup(
|
||||||
|
React.DOM.a({href: loop.config.legalWebsiteUrl, target: "_blank"},
|
||||||
|
mozL10n.get("terms_of_use_link_text")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"privacy_notice_url": React.renderComponentToStaticMarkup(
|
||||||
|
React.DOM.a({href: loop.config.privacyWebsiteUrl, target: "_blank"},
|
||||||
|
mozL10n.get("privacy_notice_link_text")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
React.DOM.footer(null,
|
||||||
|
React.DOM.p({dangerouslySetInnerHTML: {__html: this._getContent()}}),
|
||||||
|
React.DOM.div({className: "footer-logo"})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
|
var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
|
||||||
mixins: [Backbone.Events],
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
@ -144,6 +203,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
// Adding a class to the document body element from here to ease styling it.
|
||||||
|
document.body.classList.add("is-standalone-room");
|
||||||
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this.stopListening(this.props.activeRoomStore);
|
this.stopListening(this.props.activeRoomStore);
|
||||||
},
|
},
|
||||||
@ -207,7 +271,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "room-conversation-wrapper"},
|
React.DOM.div({className: "room-conversation-wrapper"},
|
||||||
|
StandaloneRoomHeader(null),
|
||||||
StandaloneRoomInfoArea({roomState: this.state.roomState,
|
StandaloneRoomInfoArea({roomState: this.state.roomState,
|
||||||
|
failureReason: this.state.failureReason,
|
||||||
joinRoom: this.joinRoom,
|
joinRoom: this.joinRoom,
|
||||||
helper: this.props.helper}),
|
helper: this.props.helper}),
|
||||||
React.DOM.div({className: "video-layout-wrapper"},
|
React.DOM.div({className: "video-layout-wrapper"},
|
||||||
@ -229,7 +295,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),
|
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),
|
||||||
enableHangup: this._roomIsActive()})
|
enableHangup: this._roomIsActive()})
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
StandaloneRoomFooter(null)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,10 @@ var loop = loop || {};
|
|||||||
loop.standaloneRoomViews = (function(mozL10n) {
|
loop.standaloneRoomViews = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
var StandaloneRoomInfoArea = React.createClass({
|
var StandaloneRoomInfoArea = React.createClass({
|
||||||
@ -39,6 +41,20 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return String An appropriate string according to the failureReason.
|
||||||
|
*/
|
||||||
|
_getFailureString: function() {
|
||||||
|
switch(this.props.failureReason) {
|
||||||
|
case FAILURE_REASONS.MEDIA_DENIED:
|
||||||
|
return mozL10n.get("rooms_media_denied_message");
|
||||||
|
case FAILURE_REASONS.EXPIRED_OR_INVALID:
|
||||||
|
return mozL10n.get("rooms_unavailable_notification_message");
|
||||||
|
default:
|
||||||
|
return mozL10n.get("status_error");
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
_renderContent: function() {
|
_renderContent: function() {
|
||||||
switch(this.props.roomState) {
|
switch(this.props.roomState) {
|
||||||
case ROOM_STATES.INIT:
|
case ROOM_STATES.INIT:
|
||||||
@ -67,6 +83,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
<p>{this._renderCallToActionLink()}</p>
|
<p>{this._renderCallToActionLink()}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
case ROOM_STATES.FAILED:
|
||||||
|
return (
|
||||||
|
<p className="failed-room-message">
|
||||||
|
{this._getFailureString()}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -81,6 +103,43 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var StandaloneRoomHeader = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<h1>{mozL10n.get("clientShortname2")}</h1>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var StandaloneRoomFooter = React.createClass({
|
||||||
|
_getContent: function() {
|
||||||
|
return mozL10n.get("legal_text_and_links", {
|
||||||
|
"clientShortname": mozL10n.get("clientShortname2"),
|
||||||
|
"terms_of_use_url": React.renderComponentToStaticMarkup(
|
||||||
|
<a href={loop.config.legalWebsiteUrl} target="_blank">
|
||||||
|
{mozL10n.get("terms_of_use_link_text")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
"privacy_notice_url": React.renderComponentToStaticMarkup(
|
||||||
|
<a href={loop.config.privacyWebsiteUrl} target="_blank">
|
||||||
|
{mozL10n.get("privacy_notice_link_text")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
|
||||||
|
<div className="footer-logo" />
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var StandaloneRoomView = React.createClass({
|
var StandaloneRoomView = React.createClass({
|
||||||
mixins: [Backbone.Events],
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
@ -144,6 +203,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
// Adding a class to the document body element from here to ease styling it.
|
||||||
|
document.body.classList.add("is-standalone-room");
|
||||||
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this.stopListening(this.props.activeRoomStore);
|
this.stopListening(this.props.activeRoomStore);
|
||||||
},
|
},
|
||||||
@ -207,7 +271,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="room-conversation-wrapper">
|
<div className="room-conversation-wrapper">
|
||||||
|
<StandaloneRoomHeader />
|
||||||
<StandaloneRoomInfoArea roomState={this.state.roomState}
|
<StandaloneRoomInfoArea roomState={this.state.roomState}
|
||||||
|
failureReason={this.state.failureReason}
|
||||||
joinRoom={this.joinRoom}
|
joinRoom={this.joinRoom}
|
||||||
helper={this.props.helper} />
|
helper={this.props.helper} />
|
||||||
<div className="video-layout-wrapper">
|
<div className="video-layout-wrapper">
|
||||||
@ -230,6 +296,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
enableHangup={this._roomIsActive()} />
|
enableHangup={this._roomIsActive()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<StandaloneRoomFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -116,6 +116,8 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
|||||||
rooms_room_joined_label=Someone has joined the conversation!
|
rooms_room_joined_label=Someone has joined the conversation!
|
||||||
rooms_room_join_label=Join the conversation
|
rooms_room_join_label=Join the conversation
|
||||||
rooms_display_name_guest=Guest
|
rooms_display_name_guest=Guest
|
||||||
|
rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
|
||||||
|
rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.
|
||||||
|
|
||||||
## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
|
## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
|
||||||
## replaced by the brand name and {{currentStatus}} will be replaced
|
## replaced by the brand name and {{currentStatus}} will be replaced
|
||||||
|
@ -65,6 +65,7 @@ describe("loop.roomViews", function () {
|
|||||||
roomState: ROOM_STATES.INIT,
|
roomState: ROOM_STATES.INIT,
|
||||||
audioMuted: false,
|
audioMuted: false,
|
||||||
videoMuted: false,
|
videoMuted: false,
|
||||||
|
failureReason: undefined,
|
||||||
foo: "bar"
|
foo: "bar"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,9 @@ var sharedActions = loop.shared.actions;
|
|||||||
describe("loop.store.ActiveRoomStore", function () {
|
describe("loop.store.ActiveRoomStore", function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var SERVER_CODES = loop.store.SERVER_CODES;
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
|
var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
|
||||||
var fakeMultiplexGum;
|
var fakeMultiplexGum;
|
||||||
|
|
||||||
@ -91,8 +93,8 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
sinon.match(ROOM_STATES.READY), fakeError);
|
sinon.match(ROOM_STATES.READY), fakeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set the state to `FULL` on server errno 202", function() {
|
it("should set the state to `FULL` on server error room full", function() {
|
||||||
fakeError.errno = 202;
|
fakeError.errno = SERVER_CODES.ROOM_FULL;
|
||||||
|
|
||||||
store.roomFailure({error: fakeError});
|
store.roomFailure({error: fakeError});
|
||||||
|
|
||||||
@ -103,6 +105,27 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
store.roomFailure({error: fakeError});
|
store.roomFailure({error: fakeError});
|
||||||
|
|
||||||
expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
|
expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
|
||||||
|
expect(store._storeState.failureReason).eql(FAILURE_REASONS.UNKNOWN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the failureReason to EXPIRED_OR_INVALID on server error: " +
|
||||||
|
"invalid token", function() {
|
||||||
|
fakeError.errno = SERVER_CODES.INVALID_TOKEN;
|
||||||
|
|
||||||
|
store.roomFailure({error: fakeError});
|
||||||
|
|
||||||
|
expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
|
||||||
|
expect(store._storeState.failureReason).eql(FAILURE_REASONS.EXPIRED_OR_INVALID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the failureReason to EXPIRED_OR_INVALID on server error: " +
|
||||||
|
"expired", function() {
|
||||||
|
fakeError.errno = SERVER_CODES.EXPIRED;
|
||||||
|
|
||||||
|
store.roomFailure({error: fakeError});
|
||||||
|
|
||||||
|
expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
|
||||||
|
expect(store._storeState.failureReason).eql(FAILURE_REASONS.EXPIRED_OR_INVALID);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -244,6 +267,14 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
store.setStoreState({roomToken: "tokenFake"});
|
store.setStoreState({roomToken: "tokenFake"});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reset failureReason", function() {
|
||||||
|
store.setStoreState({failureReason: "Test"});
|
||||||
|
|
||||||
|
store.joinRoom();
|
||||||
|
|
||||||
|
expect(store.getStoreState().failureReason).eql(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it("should call rooms.join on mozLoop", function() {
|
it("should call rooms.join on mozLoop", function() {
|
||||||
store.joinRoom();
|
store.joinRoom();
|
||||||
|
|
||||||
@ -380,22 +411,34 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#connectionFailure", function() {
|
describe("#connectionFailure", function() {
|
||||||
|
var connectionFailureAction;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
store.setStoreState({
|
store.setStoreState({
|
||||||
roomState: ROOM_STATES.JOINED,
|
roomState: ROOM_STATES.JOINED,
|
||||||
roomToken: "fakeToken",
|
roomToken: "fakeToken",
|
||||||
sessionToken: "1627384950"
|
sessionToken: "1627384950"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connectionFailureAction = new sharedActions.ConnectionFailure({
|
||||||
|
reason: "FAIL"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should store the failure reason", function() {
|
||||||
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
|
expect(store.getStoreState().failureReason).eql("FAIL");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reset the multiplexGum", function() {
|
it("should reset the multiplexGum", function() {
|
||||||
store.leaveRoom();
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
sinon.assert.calledOnce(fakeMultiplexGum.reset);
|
sinon.assert.calledOnce(fakeMultiplexGum.reset);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should disconnect from the servers via the sdk", function() {
|
it("should disconnect from the servers via the sdk", function() {
|
||||||
store.connectionFailure();
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
||||||
});
|
});
|
||||||
@ -404,13 +447,13 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
sandbox.stub(window, "clearTimeout");
|
sandbox.stub(window, "clearTimeout");
|
||||||
store._timeout = {};
|
store._timeout = {};
|
||||||
|
|
||||||
store.connectionFailure();
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
sinon.assert.calledOnce(clearTimeout);
|
sinon.assert.calledOnce(clearTimeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call mozLoop.rooms.leave", function() {
|
it("should call mozLoop.rooms.leave", function() {
|
||||||
store.connectionFailure();
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
|
sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
|
||||||
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
|
sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
|
||||||
@ -418,7 +461,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should set the state to `FAILED`", function() {
|
it("should set the state to `FAILED`", function() {
|
||||||
store.connectionFailure();
|
store.connectionFailure(connectionFailureAction);
|
||||||
|
|
||||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
|
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
|
||||||
});
|
});
|
||||||
|
@ -7,16 +7,19 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
|
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||||
var sandbox;
|
var sandbox;
|
||||||
var dispatcher, driver, publisher, sdk, session, sessionData;
|
var dispatcher, driver, publisher, sdk, session, sessionData;
|
||||||
var fakeLocalElement, fakeRemoteElement, publisherConfig;
|
var fakeLocalElement, fakeRemoteElement, publisherConfig, fakeEvent;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
fakeLocalElement = {fake: 1};
|
fakeLocalElement = {fake: 1};
|
||||||
fakeRemoteElement = {fake: 2};
|
fakeRemoteElement = {fake: 2};
|
||||||
|
fakeEvent = {
|
||||||
|
preventDefault: sinon.stub()
|
||||||
|
};
|
||||||
publisherConfig = {
|
publisherConfig = {
|
||||||
fake: "config"
|
fake: "config"
|
||||||
};
|
};
|
||||||
@ -34,14 +37,14 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
subscribe: sinon.stub()
|
subscribe: sinon.stub()
|
||||||
}, Backbone.Events);
|
}, Backbone.Events);
|
||||||
|
|
||||||
publisher = {
|
publisher = _.extend({
|
||||||
destroy: sinon.stub(),
|
destroy: sinon.stub(),
|
||||||
publishAudio: sinon.stub(),
|
publishAudio: sinon.stub(),
|
||||||
publishVideo: sinon.stub()
|
publishVideo: sinon.stub()
|
||||||
};
|
}, Backbone.Events);
|
||||||
|
|
||||||
sdk = {
|
sdk = {
|
||||||
initPublisher: sinon.stub(),
|
initPublisher: sinon.stub().returns(publisher),
|
||||||
initSession: sinon.stub().returns(session)
|
initSession: sinon.stub().returns(session)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,51 +83,6 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
sinon.assert.calledOnce(sdk.initPublisher);
|
sinon.assert.calledOnce(sdk.initPublisher);
|
||||||
sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
|
sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("On Publisher Complete", function() {
|
|
||||||
it("should publish the stream if the connection is ready", function() {
|
|
||||||
sdk.initPublisher.callsArgWith(2, null);
|
|
||||||
|
|
||||||
driver.session = session;
|
|
||||||
driver._sessionConnected = true;
|
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
|
||||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
|
||||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
|
||||||
publisherConfig: publisherConfig
|
|
||||||
}));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(session.publish);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should dispatch connectionFailure if connecting failed", function() {
|
|
||||||
sdk.initPublisher.callsArgWith(2, new Error("Failure"));
|
|
||||||
|
|
||||||
// Special stub, as we want to use the dispatcher, but also know that
|
|
||||||
// we've been called correctly for the second dispatch.
|
|
||||||
var dispatchStub = (function() {
|
|
||||||
var originalDispatch = dispatcher.dispatch.bind(dispatcher);
|
|
||||||
return sandbox.stub(dispatcher, "dispatch", function(action) {
|
|
||||||
originalDispatch(action);
|
|
||||||
});
|
|
||||||
}());
|
|
||||||
|
|
||||||
driver.session = session;
|
|
||||||
driver._sessionConnected = true;
|
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
|
||||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
|
||||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
|
||||||
publisherConfig: publisherConfig
|
|
||||||
}));
|
|
||||||
|
|
||||||
sinon.assert.called(dispatcher.dispatch);
|
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
||||||
sinon.match.hasOwn("name", "connectionFailure"));
|
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
||||||
sinon.match.hasOwn("reason", "noMedia"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#setMute", function() {
|
describe("#setMute", function() {
|
||||||
@ -194,7 +152,7 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("name", "connectionFailure"));
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("reason", "couldNotConnect"));
|
sinon.match.hasOwn("reason", FAILURE_REASONS.COULD_NOT_CONNECT));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -269,7 +227,7 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("name", "connectionFailure"));
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("reason", "networkDisconnected"));
|
sinon.match.hasOwn("reason", FAILURE_REASONS.NETWORK_DISCONNECTED));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -328,5 +286,41 @@ describe("loop.OTSdkDriver", function () {
|
|||||||
sinon.assert.notCalled(dispatcher.dispatch);
|
sinon.assert.notCalled(dispatcher.dispatch);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("accessAllowed", function() {
|
||||||
|
it("should publish the stream if the connection is ready", function() {
|
||||||
|
driver._sessionConnected = true;
|
||||||
|
|
||||||
|
publisher.trigger("accessAllowed", fakeEvent);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.publish);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("accessDenied", function() {
|
||||||
|
it("should prevent the default event behavior", function() {
|
||||||
|
publisher.trigger("accessDenied", fakeEvent);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch connectionFailure", function() {
|
||||||
|
publisher.trigger("accessDenied", fakeEvent);
|
||||||
|
|
||||||
|
sinon.assert.called(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("reason", FAILURE_REASONS.MEDIA_DENIED));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("accessDialogOpened", function() {
|
||||||
|
it("should prevent the default event behavior", function() {
|
||||||
|
publisher.trigger("accessDialogOpened", fakeEvent);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeEvent.preventDefault);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -139,6 +139,16 @@ describe("loop.standaloneRoomViews", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Failed room message", function() {
|
||||||
|
it("should display a failed room message on FAILED",
|
||||||
|
function() {
|
||||||
|
activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
|
||||||
|
|
||||||
|
expect(view.getDOMNode().querySelector(".failed-room-message"))
|
||||||
|
.not.eql(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Join button", function() {
|
describe("Join button", function() {
|
||||||
function getJoinButton(view) {
|
function getJoinButton(view) {
|
||||||
return view.getDOMNode().querySelector(".btn-join");
|
return view.getDOMNode().querySelector(".btn-join");
|
||||||
|
@ -608,6 +608,16 @@
|
|||||||
roomState: ROOM_STATES.FULL,
|
roomState: ROOM_STATES.FULL,
|
||||||
helper: {isFirefox: returnFalse}})
|
helper: {isFirefox: returnFalse}})
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
Example({summary: "Standalone room conversation (failed)"},
|
||||||
|
React.DOM.div({className: "standalone"},
|
||||||
|
StandaloneRoomView({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
activeRoomStore: activeRoomStore,
|
||||||
|
roomState: ROOM_STATES.FAILED,
|
||||||
|
helper: {isFirefox: returnFalse}})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -609,6 +609,16 @@
|
|||||||
helper={{isFirefox: returnFalse}} />
|
helper={{isFirefox: returnFalse}} />
|
||||||
</div>
|
</div>
|
||||||
</Example>
|
</Example>
|
||||||
|
|
||||||
|
<Example summary="Standalone room conversation (failed)">
|
||||||
|
<div className="standalone">
|
||||||
|
<StandaloneRoomView
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
activeRoomStore={activeRoomStore}
|
||||||
|
roomState={ROOM_STATES.FAILED}
|
||||||
|
helper={{isFirefox: returnFalse}} />
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section name="SVG icons preview">
|
<Section name="SVG icons preview">
|
||||||
|
@ -9,11 +9,6 @@ function test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
// Request a longer timeout because the test takes quite a while
|
|
||||||
// to complete on slow Windows debug machines and we would otherwise
|
|
||||||
// see a lot of (not so) intermittent test failures.
|
|
||||||
requestLongerTimeout(2);
|
|
||||||
|
|
||||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
||||||
registerCleanupFunction(function () {
|
registerCleanupFunction(function () {
|
||||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||||
@ -29,112 +24,31 @@ function runTests() {
|
|||||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||||
{ entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
|
{ entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
|
||||||
{ entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
|
{ entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
|
||||||
{ entries: [{ url: "http://example.org/#10" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#11" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#12" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#13" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#14" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#15" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#16" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#17" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.org/#18" }], extData: { "uniq": r() } }
|
|
||||||
], selected: 1 }] };
|
], selected: 1 }] };
|
||||||
|
|
||||||
let loadCount = 0;
|
let loadCount = 0;
|
||||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
gBrowser.tabContainer.addEventListener("SSTabRestored", function onRestored(event) {
|
||||||
loadCount++;
|
let tab = event.target;
|
||||||
is(aBrowser.currentURI.spec, state.windows[0].tabs[loadCount - 1].entries[0].url,
|
let browser = tab.linkedBrowser;
|
||||||
"load " + loadCount + " - browser loaded correct url");
|
let tabData = state.windows[0].tabs[loadCount++];
|
||||||
|
|
||||||
if (loadCount <= state.windows[0].tabs.length) {
|
|
||||||
// double check that this tab was the right one
|
// double check that this tab was the right one
|
||||||
let expectedData = state.windows[0].tabs[loadCount - 1].extData.uniq;
|
is(browser.currentURI.spec, tabData.entries[0].url,
|
||||||
let tab;
|
"load " + loadCount + " - browser loaded correct url");
|
||||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
is(ss.getTabValue(tab, "uniq"), tabData.extData.uniq,
|
||||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
|
||||||
tab = window.gBrowser.tabs[i];
|
|
||||||
}
|
|
||||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
|
||||||
"load " + loadCount + " - correct tab was restored");
|
"load " + loadCount + " - correct tab was restored");
|
||||||
|
|
||||||
if (loadCount == state.windows[0].tabs.length) {
|
if (loadCount == state.windows[0].tabs.length) {
|
||||||
gProgressListener.unsetCallback();
|
gBrowser.tabContainer.removeEventListener("SSTabRestored", onRestored);
|
||||||
|
|
||||||
executeSoon(function () {
|
executeSoon(function () {
|
||||||
reloadAllTabs(state, function () {
|
waitForBrowserState(TestRunner.backupState, finish);
|
||||||
waitForBrowserState(TestRunner.backupState, testCascade);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// reload the next tab
|
// reload the next tab
|
||||||
window.gBrowser.reloadTab(window.gBrowser.tabs[loadCount]);
|
gBrowser.browsers[loadCount].reload();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
yield ss.setBrowserState(JSON.stringify(state));
|
yield ss.setBrowserState(JSON.stringify(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
function testCascade() {
|
|
||||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
|
||||||
|
|
||||||
let state = { windows: [{ tabs: [
|
|
||||||
{ entries: [{ url: "http://example.com/#1" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.com/#2" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.com/#3" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.com/#4" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.com/#5" }], extData: { "uniq": r() } },
|
|
||||||
{ entries: [{ url: "http://example.com/#6" }], extData: { "uniq": r() } }
|
|
||||||
] }] };
|
|
||||||
|
|
||||||
let loadCount = 0;
|
|
||||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
|
||||||
if (++loadCount < state.windows[0].tabs.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gProgressListener.unsetCallback();
|
|
||||||
executeSoon(function () {
|
|
||||||
reloadAllTabs(state, next);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ss.setBrowserState(JSON.stringify(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadAllTabs(aState, aCallback) {
|
|
||||||
// Simulate a left mouse button click with no modifiers, which is what
|
|
||||||
// Command-R, or clicking reload does.
|
|
||||||
let fakeEvent = {
|
|
||||||
button: 0,
|
|
||||||
metaKey: false,
|
|
||||||
altKey: false,
|
|
||||||
ctrlKey: false,
|
|
||||||
shiftKey: false
|
|
||||||
};
|
|
||||||
|
|
||||||
let loadCount = 0;
|
|
||||||
gWebProgressListener.setCallback(function (aBrowser) {
|
|
||||||
if (++loadCount <= aState.windows[0].tabs.length) {
|
|
||||||
// double check that this tab was the right one
|
|
||||||
let expectedData = aState.windows[0].tabs[loadCount - 1].extData.uniq;
|
|
||||||
let tab;
|
|
||||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
|
||||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
|
||||||
tab = window.gBrowser.tabs[i];
|
|
||||||
}
|
|
||||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
|
||||||
"load " + loadCount + " - correct tab was reloaded");
|
|
||||||
|
|
||||||
if (loadCount == aState.windows[0].tabs.length) {
|
|
||||||
gWebProgressListener.unsetCallback();
|
|
||||||
executeSoon(aCallback);
|
|
||||||
} else {
|
|
||||||
// reload the next tab
|
|
||||||
window.gBrowser.selectTabAtIndex(loadCount);
|
|
||||||
BrowserReloadOrDuplicate(fakeEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
BrowserReloadOrDuplicate(fakeEvent);
|
|
||||||
}
|
|
||||||
|
@ -8,9 +8,8 @@ include $(topsrcdir)/config/rules.mk
|
|||||||
addondir = $(srcdir)/test/addons
|
addondir = $(srcdir)/test/addons
|
||||||
testdir = $(abspath $(DEPTH)/_tests/xpcshell/browser/experiments/test/xpcshell)
|
testdir = $(abspath $(DEPTH)/_tests/xpcshell/browser/experiments/test/xpcshell)
|
||||||
|
|
||||||
libs::
|
misc:: $(call mkdir_deps,$(testdir))
|
||||||
$(EXIT_ON_ERROR) \
|
$(EXIT_ON_ERROR) \
|
||||||
$(NSINSTALL) -D $(testdir); \
|
|
||||||
for dir in $(addondir)/*; do \
|
for dir in $(addondir)/*; do \
|
||||||
base=`basename $$dir`; \
|
base=`basename $$dir`; \
|
||||||
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
|
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
HAS_MISC_RULE = True
|
||||||
|
|
||||||
EXTRA_COMPONENTS += [
|
EXTRA_COMPONENTS += [
|
||||||
'Experiments.manifest',
|
'Experiments.manifest',
|
||||||
'ExperimentsService.js',
|
'ExperimentsService.js',
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
title=You're browsing privately
|
title=You're browsing privately
|
||||||
title.normal=Open a private window?
|
title.normal=Open a private window?
|
@ -41,7 +41,7 @@ public class FennecNativeActions implements Actions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GeckoEventExpecter implements RepeatedEventExpecter {
|
class GeckoEventExpecter implements RepeatedEventExpecter {
|
||||||
private static final int MAX_WAIT_MS = 90000;
|
private static final int MAX_WAIT_MS = 180000;
|
||||||
|
|
||||||
private volatile boolean mIsRegistered;
|
private volatile boolean mIsRegistered;
|
||||||
|
|
||||||
|
@ -23,5 +23,6 @@ css_properties.js: host_ListCSSProperties$(HOST_BIN_SUFFIX) css_properties_like_
|
|||||||
GARBAGE += css_properties.js
|
GARBAGE += css_properties.js
|
||||||
TEST_FILES := css_properties.js
|
TEST_FILES := css_properties.js
|
||||||
TEST_DEST = $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
TEST_DEST = $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
||||||
|
TEST_TARGET := misc
|
||||||
INSTALL_TARGETS += TEST
|
INSTALL_TARGETS += TEST
|
||||||
endif
|
endif
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
HAS_MISC_RULE = True
|
||||||
|
|
||||||
HostSimplePrograms([
|
HostSimplePrograms([
|
||||||
'host_ListCSSProperties',
|
'host_ListCSSProperties',
|
||||||
])
|
])
|
||||||
|
@ -27,6 +27,10 @@ public final class SharedPreferencesHelper
|
|||||||
{
|
{
|
||||||
public static final String LOGTAG = "GeckoAndSharedPrefs";
|
public static final String LOGTAG = "GeckoAndSharedPrefs";
|
||||||
|
|
||||||
|
// Calculate this once, at initialization. isLoggable is too expensive to
|
||||||
|
// have in-line in each log call.
|
||||||
|
private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
|
||||||
|
|
||||||
private enum Scope {
|
private enum Scope {
|
||||||
APP("app"),
|
APP("app"),
|
||||||
PROFILE("profile"),
|
PROFILE("profile"),
|
||||||
@ -214,7 +218,7 @@ public final class SharedPreferencesHelper
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Got onSharedPreferenceChanged");
|
Log.v(LOGTAG, "Got onSharedPreferenceChanged");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -279,19 +283,19 @@ public final class SharedPreferencesHelper
|
|||||||
// overwriting an in-progress response.
|
// overwriting an in-progress response.
|
||||||
try {
|
try {
|
||||||
if (event.equals("SharedPreferences:Set")) {
|
if (event.equals("SharedPreferences:Set")) {
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Got SharedPreferences:Set message.");
|
Log.v(LOGTAG, "Got SharedPreferences:Set message.");
|
||||||
}
|
}
|
||||||
handleSet(message);
|
handleSet(message);
|
||||||
} else if (event.equals("SharedPreferences:Get")) {
|
} else if (event.equals("SharedPreferences:Get")) {
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
|
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
|
||||||
}
|
}
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("values", handleGet(message));
|
obj.put("values", handleGet(message));
|
||||||
EventDispatcher.sendResponse(message, obj);
|
EventDispatcher.sendResponse(message, obj);
|
||||||
} else if (event.equals("SharedPreferences:Observe")) {
|
} else if (event.equals("SharedPreferences:Observe")) {
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
|
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
|
||||||
}
|
}
|
||||||
handleObserve(message);
|
handleObserve(message);
|
||||||
|
@ -24,7 +24,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Scanner;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarInputStream;
|
import java.util.jar.JarInputStream;
|
||||||
@ -42,6 +41,7 @@ import org.mozilla.gecko.GeckoEvent;
|
|||||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||||
import org.mozilla.gecko.Telemetry;
|
import org.mozilla.gecko.Telemetry;
|
||||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||||
|
import org.mozilla.gecko.util.FileUtils;
|
||||||
import org.mozilla.gecko.util.ThreadUtils;
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@ -286,7 +286,7 @@ public class Distribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject all = new JSONObject(getFileContents(descFile));
|
JSONObject all = new JSONObject(FileUtils.getFileContents(descFile));
|
||||||
|
|
||||||
if (!all.has("Global")) {
|
if (!all.has("Global")) {
|
||||||
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
||||||
@ -314,7 +314,7 @@ public class Distribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new JSONArray(getFileContents(bookmarks));
|
return new JSONArray(FileUtils.getFileContents(bookmarks));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(LOGTAG, "Error getting bookmarks", e);
|
Log.e(LOGTAG, "Error getting bookmarks", e);
|
||||||
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
|
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
|
||||||
@ -739,19 +739,6 @@ public class Distribution {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shortcut to slurp a file without messing around with streams.
|
|
||||||
private String getFileContents(File file) throws IOException {
|
|
||||||
Scanner scanner = null;
|
|
||||||
try {
|
|
||||||
scanner = new Scanner(file, "UTF-8");
|
|
||||||
return scanner.useDelimiter("\\A").next();
|
|
||||||
} finally {
|
|
||||||
if (scanner != null) {
|
|
||||||
scanner.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDataDir() {
|
private String getDataDir() {
|
||||||
return context.getApplicationInfo().dataDir;
|
return context.getApplicationInfo().dataDir;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ import android.preference.Preference.OnPreferenceClickListener;
|
|||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment that displays the status of an AndroidFxAccount.
|
* A fragment that displays the status of an AndroidFxAccount.
|
||||||
@ -86,6 +87,7 @@ public class FxAccountStatusFragment
|
|||||||
protected EditTextPreference deviceNamePreference;
|
protected EditTextPreference deviceNamePreference;
|
||||||
protected Preference syncServerPreference;
|
protected Preference syncServerPreference;
|
||||||
protected Preference morePreference;
|
protected Preference morePreference;
|
||||||
|
protected Preference syncNowPreference;
|
||||||
|
|
||||||
protected volatile AndroidFxAccount fxAccount;
|
protected volatile AndroidFxAccount fxAccount;
|
||||||
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
||||||
@ -167,6 +169,10 @@ public class FxAccountStatusFragment
|
|||||||
morePreference = ensureFindPreference("more");
|
morePreference = ensureFindPreference("more");
|
||||||
morePreference.setOnPreferenceClickListener(this);
|
morePreference.setOnPreferenceClickListener(this);
|
||||||
|
|
||||||
|
syncNowPreference = ensureFindPreference("sync_now");
|
||||||
|
syncNowPreference.setEnabled(true);
|
||||||
|
syncNowPreference.setOnPreferenceClickListener(this);
|
||||||
|
|
||||||
if (HardwareUtils.hasMenuButton()) {
|
if (HardwareUtils.hasMenuButton()) {
|
||||||
syncCategory.removePreference(morePreference);
|
syncCategory.removePreference(morePreference);
|
||||||
}
|
}
|
||||||
@ -229,6 +235,13 @@ public class FxAccountStatusFragment
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preference == syncNowPreference) {
|
||||||
|
if (fxAccount != null) {
|
||||||
|
FirefoxAccounts.requestSync(fxAccount.getAndroidAccount(), FirefoxAccounts.FORCE, null, null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +263,7 @@ public class FxAccountStatusFragment
|
|||||||
passwordsPreference.setEnabled(enabled);
|
passwordsPreference.setEnabled(enabled);
|
||||||
// Since we can't sync, we can't update our remote client record.
|
// Since we can't sync, we can't update our remote client record.
|
||||||
deviceNamePreference.setEnabled(enabled);
|
deviceNamePreference.setEnabled(enabled);
|
||||||
|
syncNowPreference.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -470,6 +484,26 @@ public class FxAccountStatusFragment
|
|||||||
final String clientName = clientsDataDelegate.getClientName();
|
final String clientName = clientsDataDelegate.getClientName();
|
||||||
deviceNamePreference.setSummary(clientName);
|
deviceNamePreference.setSummary(clientName);
|
||||||
deviceNamePreference.setText(clientName);
|
deviceNamePreference.setText(clientName);
|
||||||
|
|
||||||
|
updateSyncNowPreference();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span.
|
||||||
|
private String getLastSyncedString(final long startTime) {
|
||||||
|
final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime);
|
||||||
|
return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateSyncNowPreference() {
|
||||||
|
final boolean currentlySyncing = fxAccount.isCurrentlySyncing();
|
||||||
|
syncNowPreference.setEnabled(!currentlySyncing);
|
||||||
|
if (currentlySyncing) {
|
||||||
|
syncNowPreference.setTitle(R.string.fxaccount_status_syncing);
|
||||||
|
} else {
|
||||||
|
syncNowPreference.setTitle(R.string.fxaccount_status_sync_now);
|
||||||
|
}
|
||||||
|
final String lastSynced = getLastSyncedString(fxAccount.getLastSyncedTimestamp());
|
||||||
|
syncNowPreference.setSummary(lastSynced);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateAuthServerPreference() {
|
protected void updateAuthServerPreference() {
|
||||||
|
@ -65,6 +65,8 @@ public class AndroidFxAccount {
|
|||||||
|
|
||||||
protected static final List<String> ANDROID_AUTHORITIES = Collections.unmodifiableList(Arrays.asList(BrowserContract.AUTHORITY));
|
protected static final List<String> ANDROID_AUTHORITIES = Collections.unmodifiableList(Arrays.asList(BrowserContract.AUTHORITY));
|
||||||
|
|
||||||
|
private static final String PREF_KEY_LAST_SYNCED_TIMESTAMP = "lastSyncedTimestamp";
|
||||||
|
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
protected final AccountManager accountManager;
|
protected final AccountManager accountManager;
|
||||||
protected final Account account;
|
protected final Account account;
|
||||||
@ -565,4 +567,22 @@ public class AndroidFxAccount {
|
|||||||
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY, account.name);
|
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY, account.name);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLastSyncedTimestamp(long now) {
|
||||||
|
try {
|
||||||
|
getSyncPrefs().edit().putLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, now).commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.warn(LOG_TAG, "Got exception setting last synced time; ignoring.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastSyncedTimestamp() {
|
||||||
|
final long neverSynced = -1L;
|
||||||
|
try {
|
||||||
|
return getSyncPrefs().getLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, neverSynced);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.warn(LOG_TAG, "Got exception getting last synced time; ignoring.", e);
|
||||||
|
return neverSynced;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,7 @@ public class FxAccountSchedulePolicy implements SchedulePolicy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccessfulSync(int otherClientsCount) {
|
public void onSuccessfulSync(int otherClientsCount) {
|
||||||
|
this.account.setLastSyncedTimestamp(System.currentTimeMillis());
|
||||||
// This undoes the change made in observeBackoffMillis -- once we hit backoff we'll
|
// This undoes the change made in observeBackoffMillis -- once we hit backoff we'll
|
||||||
// periodically sync at the backoff duration, but as soon as we succeed we'll switch
|
// periodically sync at the backoff duration, but as soon as we succeed we'll switch
|
||||||
// into the client-count-dependent interval.
|
// into the client-count-dependent interval.
|
||||||
|
@ -434,11 +434,20 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
|
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
|
||||||
Logger.resetLogging();
|
Logger.resetLogging();
|
||||||
|
|
||||||
|
final Context context = getContext();
|
||||||
|
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||||
|
|
||||||
Logger.info(LOG_TAG, "Syncing FxAccount" +
|
Logger.info(LOG_TAG, "Syncing FxAccount" +
|
||||||
" account named like " + Utils.obfuscateEmail(account.name) +
|
" account named like " + Utils.obfuscateEmail(account.name) +
|
||||||
" for authority " + authority +
|
" for authority " + authority +
|
||||||
" with instance " + this + ".");
|
" with instance " + this + ".");
|
||||||
|
|
||||||
|
Logger.info(LOG_TAG, "Account last synced at: " + fxAccount.getLastSyncedTimestamp());
|
||||||
|
|
||||||
|
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||||
|
fxAccount.dump();
|
||||||
|
}
|
||||||
|
|
||||||
final EnumSet<FirefoxAccounts.SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
|
final EnumSet<FirefoxAccounts.SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
|
||||||
FirefoxAccounts.logSyncHints(syncHints);
|
FirefoxAccounts.logSyncHints(syncHints);
|
||||||
|
|
||||||
@ -450,12 +459,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Context context = getContext();
|
|
||||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
|
||||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
|
||||||
fxAccount.dump();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pickle in a background thread to avoid strict mode warnings.
|
// Pickle in a background thread to avoid strict mode warnings.
|
||||||
ThreadPool.run(new Runnable() {
|
ThreadPool.run(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,6 +187,8 @@
|
|||||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||||
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
||||||
|
<!ENTITY fxaccount_status_sync_now 'Sync now'>
|
||||||
|
<!ENTITY fxaccount_status_syncing 'Syncing...'>
|
||||||
<!ENTITY fxaccount_status_device_name 'Device name'>
|
<!ENTITY fxaccount_status_device_name 'Device name'>
|
||||||
<!ENTITY fxaccount_status_sync_server 'Sync server'>
|
<!ENTITY fxaccount_status_sync_server 'Sync server'>
|
||||||
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
||||||
|
@ -55,6 +55,14 @@
|
|||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/fxaccount_status_needs_account_enabled" />
|
android:title="@string/fxaccount_status_needs_account_enabled" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:editable="false"
|
||||||
|
android:key="sync_now"
|
||||||
|
android:defaultValue=""
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="Sync now"
|
||||||
|
android:summary="" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="bookmarks"
|
android:key="bookmarks"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
|
@ -96,6 +96,7 @@
|
|||||||
<string name="share_image_failed">&share_image_failed;</string>
|
<string name="share_image_failed">&share_image_failed;</string>
|
||||||
<string name="save_as_pdf">&save_as_pdf;</string>
|
<string name="save_as_pdf">&save_as_pdf;</string>
|
||||||
<string name="find_in_page">&find_in_page;</string>
|
<string name="find_in_page">&find_in_page;</string>
|
||||||
|
<string name="find_matchcase">&find_matchcase;</string>
|
||||||
<string name="desktop_mode">&desktop_mode;</string>
|
<string name="desktop_mode">&desktop_mode;</string>
|
||||||
<string name="page">&page;</string>
|
<string name="page">&page;</string>
|
||||||
<string name="tools">&tools;</string>
|
<string name="tools">&tools;</string>
|
||||||
|
@ -37,7 +37,6 @@ public class SyncConfiguration {
|
|||||||
this.editor = config.getEditor();
|
this.editor = config.getEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply() {
|
public void apply() {
|
||||||
// Android <=r8 SharedPreferences.Editor does not contain apply() for overriding.
|
// Android <=r8 SharedPreferences.Editor does not contain apply() for overriding.
|
||||||
this.editor.commit();
|
this.editor.commit();
|
||||||
@ -86,7 +85,6 @@ public class SyncConfiguration {
|
|||||||
|
|
||||||
// Not marking as Override, because Android <= 10 doesn't have
|
// Not marking as Override, because Android <= 10 doesn't have
|
||||||
// putStringSet. Neither can we implement it.
|
// putStringSet. Neither can we implement it.
|
||||||
@Override
|
|
||||||
public Editor putStringSet(String key, Set<String> value) {
|
public Editor putStringSet(String key, Set<String> value) {
|
||||||
throw new RuntimeException("putStringSet not available.");
|
throw new RuntimeException("putStringSet not available.");
|
||||||
}
|
}
|
||||||
@ -162,7 +160,6 @@ public class SyncConfiguration {
|
|||||||
|
|
||||||
// Not marking as Override, because Android <= 10 doesn't have
|
// Not marking as Override, because Android <= 10 doesn't have
|
||||||
// getStringSet. Neither can we implement it.
|
// getStringSet. Neither can we implement it.
|
||||||
@Override
|
|
||||||
public Set<String> getStringSet(String key, Set<String> defValue) {
|
public Set<String> getStringSet(String key, Set<String> defValue) {
|
||||||
throw new RuntimeException("getStringSet not available.");
|
throw new RuntimeException("getStringSet not available.");
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public class BaseResource implements Resource {
|
|||||||
|
|
||||||
private boolean retryOnFailedRequest = true;
|
private boolean retryOnFailedRequest = true;
|
||||||
|
|
||||||
public static final boolean rewriteLocalhost = true;
|
public static boolean rewriteLocalhost = true;
|
||||||
|
|
||||||
private static final String LOG_TAG = "BaseResource";
|
private static final String LOG_TAG = "BaseResource";
|
||||||
|
|
||||||
|
@ -11,6 +11,11 @@ public class JavascriptTest extends BaseTest {
|
|||||||
private static final String LOGTAG = "JavascriptTest";
|
private static final String LOGTAG = "JavascriptTest";
|
||||||
private static final String EVENT_TYPE = JavascriptBridge.EVENT_TYPE;
|
private static final String EVENT_TYPE = JavascriptBridge.EVENT_TYPE;
|
||||||
|
|
||||||
|
// Calculate these once, at initialization. isLoggable is too expensive to
|
||||||
|
// have in-line in each log call.
|
||||||
|
private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
|
||||||
|
private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
|
||||||
|
|
||||||
private final String javascriptUrl;
|
private final String javascriptUrl;
|
||||||
|
|
||||||
public JavascriptTest(String javascriptUrl) {
|
public JavascriptTest(String javascriptUrl) {
|
||||||
@ -39,11 +44,11 @@ public class JavascriptTest extends BaseTest {
|
|||||||
new JavascriptMessageParser(mAsserter, false);
|
new JavascriptMessageParser(mAsserter, false);
|
||||||
try {
|
try {
|
||||||
while (!testMessageParser.isTestFinished()) {
|
while (!testMessageParser.isTestFinished()) {
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
|
Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
|
||||||
}
|
}
|
||||||
String data = expecter.blockForEventData();
|
String data = expecter.blockForEventData();
|
||||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
if (logVerbose) {
|
||||||
Log.v(LOGTAG, "Got event with data '" + data + "'");
|
Log.v(LOGTAG, "Got event with data '" + data + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +65,7 @@ public class JavascriptTest extends BaseTest {
|
|||||||
testMessageParser.logMessage(message);
|
testMessageParser.logMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
|
if (logDebug) {
|
||||||
Log.d(LOGTAG, "Got test finished message");
|
Log.d(LOGTAG, "Got test finished message");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -9,6 +9,7 @@ import android.util.Log;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||||
|
|
||||||
@ -81,4 +82,17 @@ public class FileUtils {
|
|||||||
// Even if this is a dir, it should now be empty and delete should work
|
// Even if this is a dir, it should now be empty and delete should work
|
||||||
return file.delete();
|
return file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shortcut to slurp a file without messing around with streams.
|
||||||
|
public static String getFileContents(File file) throws IOException {
|
||||||
|
Scanner scanner = null;
|
||||||
|
try {
|
||||||
|
scanner = new Scanner(file, "UTF-8");
|
||||||
|
return scanner.useDelimiter("\\A").next();
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6907,7 +6907,7 @@ var SearchEngines = {
|
|||||||
PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted",
|
PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted",
|
||||||
|
|
||||||
// Shared preference key used for search activity default engine.
|
// Shared preference key used for search activity default engine.
|
||||||
PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.default",
|
PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.defaultname",
|
||||||
|
|
||||||
init: function init() {
|
init: function init() {
|
||||||
Services.obs.addObserver(this, "SearchEngines:Add", false);
|
Services.obs.addObserver(this, "SearchEngines:Add", false);
|
||||||
@ -7047,34 +7047,7 @@ var SearchEngines = {
|
|||||||
|
|
||||||
// Updates the search activity pref when the default engine changes.
|
// Updates the search activity pref when the default engine changes.
|
||||||
_setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
|
_setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
|
||||||
// Helper function copied from nsSearchService.js. This is the logic that is used
|
SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name);
|
||||||
// to create file names for search plugin XML serialized to disk.
|
|
||||||
function sanitizeName(aName) {
|
|
||||||
const maxLength = 60;
|
|
||||||
const minLength = 1;
|
|
||||||
let name = aName.toLowerCase();
|
|
||||||
name = name.replace(/\s+/g, "-");
|
|
||||||
name = name.replace(/[^-a-z0-9]/g, "");
|
|
||||||
|
|
||||||
if (name.length < minLength) {
|
|
||||||
// Well, in this case, we're kinda screwed. In this case, the search service
|
|
||||||
// generates a random file name, so to do this the right way, we'd need
|
|
||||||
// to open up search.json and see what file name is stored.
|
|
||||||
Cu.reportError("Couldn't create search plugin file name from engine name: " + aName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force max length.
|
|
||||||
return name.substring(0, maxLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
let identifier = engine.identifier;
|
|
||||||
if (identifier === null) {
|
|
||||||
// The identifier will be null for non-built-in engines. In this case, we need to
|
|
||||||
// figure out an identifier to store from the engine name.
|
|
||||||
identifier = sanitizeName(engine.name);
|
|
||||||
}
|
|
||||||
SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, identifier);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Display context menu listing names of the search engines available to be added.
|
// Display context menu listing names of the search engines available to be added.
|
||||||
|
@ -15,7 +15,7 @@ ac_add_options --enable-elf-hack
|
|||||||
|
|
||||||
ANDROID_NDK_VERSION="r8e"
|
ANDROID_NDK_VERSION="r8e"
|
||||||
ANDROID_NDK_VERSION_32BIT="r8c"
|
ANDROID_NDK_VERSION_32BIT="r8c"
|
||||||
ANDROID_SDK_VERSION="20"
|
ANDROID_SDK_VERSION="21"
|
||||||
|
|
||||||
# Build Fennec
|
# Build Fennec
|
||||||
ac_add_options --enable-application=mobile/android
|
ac_add_options --enable-application=mobile/android
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
"filename": "android-ndk.tar.bz2"
|
"filename": "android-ndk.tar.bz2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 207966812,
|
"size": 227988048,
|
||||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||||
"algorithm": "sha512",
|
"algorithm": "sha512",
|
||||||
"filename": "android-sdk.tar.xz"
|
"filename": "android-sdk.tar.xz"
|
||||||
},
|
},
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
"filename": "android-ndk.tar.bz2"
|
"filename": "android-ndk.tar.bz2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 207966812,
|
"size": 227988048,
|
||||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||||
"algorithm": "sha512",
|
"algorithm": "sha512",
|
||||||
"filename": "android-sdk.tar.xz"
|
"filename": "android-sdk.tar.xz"
|
||||||
},
|
},
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
"filename": "android-ndk.tar.bz2"
|
"filename": "android-ndk.tar.bz2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 207966812,
|
"size": 227988048,
|
||||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||||
"algorithm": "sha512",
|
"algorithm": "sha512",
|
||||||
"filename": "android-sdk.tar.xz"
|
"filename": "android-sdk.tar.xz"
|
||||||
},
|
},
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
gradle := \
|
gradle := \
|
||||||
|
local.properties.in \
|
||||||
gradle.properties.in \
|
gradle.properties.in \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
gradle_PATH := $(CURDIR)
|
gradle_PATH := $(CURDIR)
|
||||||
gradle_FLAGS += -Dtopsrcdir=$(abspath $(topsrcdir))
|
gradle_FLAGS += -Dtopsrcdir=$(abspath $(topsrcdir))
|
||||||
gradle_FLAGS += -Dtopobjdir=$(abspath $(DEPTH))
|
gradle_FLAGS += -Dtopobjdir=$(abspath $(DEPTH))
|
||||||
|
gradle_FLAGS += -DANDROID_SDK_ROOT=$(ANDROID_SDK_ROOT)
|
||||||
gradle_KEEP_PATH := 1
|
gradle_KEEP_PATH := 1
|
||||||
PP_TARGETS += gradle
|
PP_TARGETS += gradle
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
classpath 'com.android.tools.build:gradle:0.14.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
#Wed Apr 10 15:27:10 PDT 2013
|
#Thu Nov 13 14:57:51 PST 2014
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
|
||||||
|
2
mobile/android/gradle/local.properties.in
Normal file
2
mobile/android/gradle/local.properties.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#filter substitution
|
||||||
|
sdk.dir=@ANDROID_SDK_ROOT@
|
@ -17,9 +17,4 @@ package org.mozilla.search;
|
|||||||
public class Constants {
|
public class Constants {
|
||||||
|
|
||||||
public static final String ABOUT_BLANK = "about:blank";
|
public static final String ABOUT_BLANK = "about:blank";
|
||||||
|
|
||||||
// TODO: Localize this with region.properties (or a similar solution). See bug 1065306.
|
|
||||||
public static final String DEFAULT_ENGINE_IDENTIFIER = "yahoo";
|
|
||||||
|
|
||||||
public static final String PREF_SEARCH_ENGINE_KEY = "search.engines.default";
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import org.mozilla.gecko.R;
|
|||||||
import org.mozilla.gecko.Telemetry;
|
import org.mozilla.gecko.Telemetry;
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
||||||
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||||
import org.mozilla.search.autocomplete.SearchBar;
|
import org.mozilla.search.autocomplete.SearchBar;
|
||||||
import org.mozilla.search.autocomplete.SuggestionsFragment;
|
import org.mozilla.search.autocomplete.SuggestionsFragment;
|
||||||
@ -101,7 +102,7 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
|||||||
suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
|
suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
|
||||||
postSearchFragment = (PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch);
|
postSearchFragment = (PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch);
|
||||||
|
|
||||||
searchEngineManager = new SearchEngineManager(this);
|
searchEngineManager = new SearchEngineManager(this, Distribution.init(this));
|
||||||
searchEngineManager.setChangeCallback(this);
|
searchEngineManager.setChangeCallback(this);
|
||||||
|
|
||||||
// Initialize the fragments with the selected search engine.
|
// Initialize the fragments with the selected search engine.
|
||||||
@ -279,8 +280,11 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
|||||||
searchEngineManager.getEngine(new SearchEngineCallback() {
|
searchEngineManager.getEngine(new SearchEngineCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void execute(SearchEngine engine) {
|
public void execute(SearchEngine engine) {
|
||||||
|
// TODO: If engine is null, we should show an error message.
|
||||||
|
if (engine != null) {
|
||||||
postSearchFragment.startSearch(engine, query);
|
postSearchFragment.startSearch(engine, query);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +297,10 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void execute(SearchEngine engine) {
|
public void execute(SearchEngine engine) {
|
||||||
|
// TODO: If engine is null, we should show an error message.
|
||||||
|
if (engine == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
suggestionsFragment.setEngine(engine);
|
suggestionsFragment.setEngine(engine);
|
||||||
searchBar.setEngine(engine);
|
searchBar.setEngine(engine);
|
||||||
|
@ -52,7 +52,9 @@ public class SearchEngine {
|
|||||||
"document.getElementsByTagName('head')[0].appendChild(tag);" +
|
"document.getElementsByTagName('head')[0].appendChild(tag);" +
|
||||||
"tag.innerText='%s'})();";
|
"tag.innerText='%s'})();";
|
||||||
|
|
||||||
|
// The Gecko search identifier. This will be null for engines that don't ship with the locale.
|
||||||
private final String identifier;
|
private final String identifier;
|
||||||
|
|
||||||
private String shortName;
|
private String shortName;
|
||||||
private String iconURL;
|
private String iconURL;
|
||||||
|
|
||||||
@ -189,7 +191,9 @@ public class SearchEngine {
|
|||||||
public String getInjectableJs() {
|
public String getInjectableJs() {
|
||||||
final String css;
|
final String css;
|
||||||
|
|
||||||
if (identifier.equals("bing")) {
|
if (identifier == null) {
|
||||||
|
css = "";
|
||||||
|
} else if (identifier.equals("bing")) {
|
||||||
css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
|
css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
|
||||||
} else if (identifier.equals("google")) {
|
} else if (identifier.equals("google")) {
|
||||||
css = "#sfcnt,#top_nav{display:none}";
|
css = "#sfcnt,#top_nav{display:none}";
|
||||||
@ -247,7 +251,7 @@ public class SearchEngine {
|
|||||||
public String resultsUriForQuery(String query) {
|
public String resultsUriForQuery(String query) {
|
||||||
final Uri resultsUri = getResultsUri();
|
final Uri resultsUri = getResultsUri();
|
||||||
if (resultsUri == null) {
|
if (resultsUri == null) {
|
||||||
Log.e(LOG_TAG, "No results URL for search engine: " + identifier);
|
Log.e(LOG_TAG, "No results URL for search engine: " + shortName);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
final String template = Uri.decode(resultsUri.toString());
|
final String template = Uri.decode(resultsUri.toString());
|
||||||
@ -261,7 +265,7 @@ public class SearchEngine {
|
|||||||
*/
|
*/
|
||||||
public String getSuggestionTemplate(String query) {
|
public String getSuggestionTemplate(String query) {
|
||||||
if (suggestUri == null) {
|
if (suggestUri == null) {
|
||||||
Log.e(LOG_TAG, "No suggestions template for search engine: " + identifier);
|
Log.e(LOG_TAG, "No suggestions template for search engine: " + shortName);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
final String template = Uri.decode(suggestUri.toString());
|
final String template = Uri.decode(suggestUri.toString());
|
||||||
|
@ -6,15 +6,21 @@ package org.mozilla.search.providers;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.gecko.AppConstants;
|
import org.mozilla.gecko.AppConstants;
|
||||||
import org.mozilla.gecko.BrowserLocaleManager;
|
import org.mozilla.gecko.BrowserLocaleManager;
|
||||||
import org.mozilla.gecko.GeckoProfile;
|
import org.mozilla.gecko.GeckoProfile;
|
||||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||||
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.util.FileUtils;
|
||||||
import org.mozilla.gecko.util.GeckoJarReader;
|
import org.mozilla.gecko.util.GeckoJarReader;
|
||||||
|
import org.mozilla.gecko.util.RawResource;
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
import org.mozilla.search.Constants;
|
import org.mozilla.search.Constants;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
@ -30,9 +36,16 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final String LOG_TAG = "SearchEngineManager";
|
private static final String LOG_TAG = "GeckoSearchEngineManager";
|
||||||
|
|
||||||
|
// Gecko pref that defines the name of the default search engine.
|
||||||
|
private static final String PREF_GECKO_DEFAULT_ENGINE = "browser.search.defaultenginename";
|
||||||
|
|
||||||
|
// Key for shared preference that stores default engine name.
|
||||||
|
private static final String PREF_DEFAULT_ENGINE_KEY = "search.engines.defaultname";
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
private Distribution distribution;
|
||||||
private SearchEngineCallback changeCallback;
|
private SearchEngineCallback changeCallback;
|
||||||
private SearchEngine engine;
|
private SearchEngine engine;
|
||||||
|
|
||||||
@ -40,11 +53,19 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
public void execute(SearchEngine engine);
|
public void execute(SearchEngine engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchEngineManager(Context context) {
|
public SearchEngineManager(Context context, Distribution distribution) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.distribution = distribution;
|
||||||
GeckoSharedPrefs.forApp(context).registerOnSharedPreferenceChangeListener(this);
|
GeckoSharedPrefs.forApp(context).registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a callback to be called when the default engine changes.
|
||||||
|
*
|
||||||
|
* @param callback SearchEngineCallback to be called after the search engine
|
||||||
|
* changed. This will run on the UI thread.
|
||||||
|
* Note: callback may be called with null engine.
|
||||||
|
*/
|
||||||
public void setChangeCallback(SearchEngineCallback changeCallback) {
|
public void setChangeCallback(SearchEngineCallback changeCallback) {
|
||||||
this.changeCallback = changeCallback;
|
this.changeCallback = changeCallback;
|
||||||
}
|
}
|
||||||
@ -60,80 +81,230 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
callback.execute(engine);
|
callback.execute(engine);
|
||||||
} else {
|
} else {
|
||||||
getEngineFromPrefs(callback);
|
getDefaultEngine(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
|
GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
|
||||||
context = null;
|
context = null;
|
||||||
|
distribution = null;
|
||||||
changeCallback = null;
|
changeCallback = null;
|
||||||
engine = null;
|
engine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int ignorePreferenceChange = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
|
||||||
if (!TextUtils.equals(Constants.PREF_SEARCH_ENGINE_KEY, key)) {
|
if (!TextUtils.equals(PREF_DEFAULT_ENGINE_KEY, key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getEngineFromPrefs(changeCallback);
|
|
||||||
|
if (ignorePreferenceChange > 0) {
|
||||||
|
ignorePreferenceChange--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultEngine(changeCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up the current search engine in shared preferences.
|
* Runs a SearchEngineCallback on the main thread.
|
||||||
* Creates a SearchEngine instance and caches it for use on the main thread.
|
|
||||||
*
|
|
||||||
* @param callback a SearchEngineCallback to be called after successfully looking
|
|
||||||
* up the search engine. This will run on the UI thread.
|
|
||||||
*/
|
*/
|
||||||
private void getEngineFromPrefs(final SearchEngineCallback callback) {
|
private void runCallback(final SearchEngine engine, final SearchEngineCallback callback) {
|
||||||
final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
|
ThreadUtils.postToUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
protected SearchEngine doInBackground(Void... params) {
|
public void run() {
|
||||||
String identifier = GeckoSharedPrefs.forApp(context).getString(Constants.PREF_SEARCH_ENGINE_KEY, null);
|
// Cache engine for future calls to getEngine.
|
||||||
if (!TextUtils.isEmpty(identifier)) {
|
SearchEngineManager.this.engine = engine;
|
||||||
try {
|
callback.execute(engine);
|
||||||
return createEngine(identifier);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.e(LOG_TAG, "Exception creating search engine from pref. Falling back to default engine.", e);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
/**
|
||||||
return createEngine(Constants.DEFAULT_ENGINE_IDENTIFIER);
|
* This method finds and creates the default search engine. It will first look for
|
||||||
} catch (IllegalArgumentException e) {
|
* the default engine name, then create the engine from that name.
|
||||||
Log.e(LOG_TAG, "Exception creating search engine from default identifier. " +
|
*
|
||||||
"This will happen if the locale doesn't contain the default search plugin.", e);
|
* To find the default engine name, we first look in shared preferences, then
|
||||||
|
* the distribution (if one exists), and finally fall back to the localized default.
|
||||||
|
*
|
||||||
|
* @param callback SearchEngineCallback to be called after successfully looking
|
||||||
|
* up the search engine. This will run on the UI thread.
|
||||||
|
* Note: callback may be called with null engine.
|
||||||
|
*/
|
||||||
|
private void getDefaultEngine(final SearchEngineCallback callback) {
|
||||||
|
// This runnable is posted to the background thread.
|
||||||
|
distribution.addOnDistributionReadyCallback(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// First look for a default name stored in shared preferences.
|
||||||
|
String name = GeckoSharedPrefs.forApp(context).getString(PREF_DEFAULT_ENGINE_KEY, null);
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
Log.d(LOG_TAG, "Found default engine name in SharedPreferences: " + name);
|
||||||
|
} else {
|
||||||
|
// First, look for the default search engine in a distribution.
|
||||||
|
name = getDefaultEngineNameFromDistribution();
|
||||||
|
if (name == null) {
|
||||||
|
// Otherwise, get the default engine that we ship.
|
||||||
|
name = getDefaultEngineNameFromLocale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the default engine name for the future.
|
||||||
|
// Increment an 'ignore' counter so that this preference change
|
||||||
|
// won'tcause getDefaultEngine to be called again.
|
||||||
|
ignorePreferenceChange++;
|
||||||
|
GeckoSharedPrefs.forApp(context)
|
||||||
|
.edit()
|
||||||
|
.putString(PREF_DEFAULT_ENGINE_KEY, name)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
final SearchEngine engine = createEngineFromName(name);
|
||||||
|
runCallback(engine, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for a default search engine included in a distribution.
|
||||||
|
* This method must be called after the distribution is ready.
|
||||||
|
*
|
||||||
|
* @return search engine name.
|
||||||
|
*/
|
||||||
|
private String getDefaultEngineNameFromDistribution() {
|
||||||
|
if (!distribution.exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
final File prefFile = distribution.getDistributionFile("preferences.json");
|
||||||
protected void onPostExecute(SearchEngine engine) {
|
if (prefFile == null) {
|
||||||
if (engine != null) {
|
return null;
|
||||||
// Only touch engine on the main thread.
|
}
|
||||||
SearchEngineManager.this.engine = engine;
|
|
||||||
if (callback != null) {
|
try {
|
||||||
callback.execute(engine);
|
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 overridesKey = "LocalizablePreferences." + languageTag;
|
||||||
|
if (all.has(overridesKey)) {
|
||||||
|
final JSONObject overridePrefs = all.getJSONObject(overridesKey);
|
||||||
|
if (overridePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
|
||||||
|
Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences override.");
|
||||||
|
return overridePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next, check to see if there's a non-override default pref.
|
||||||
|
if (all.has("LocalizablePreferences")) {
|
||||||
|
final JSONObject localizablePrefs = all.getJSONObject("LocalizablePreferences");
|
||||||
|
if (localizablePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
|
||||||
|
Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences.");
|
||||||
|
return localizablePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
task.execute();
|
} catch (IOException e) {
|
||||||
|
Log.e(LOG_TAG, "Error getting search engine name from preferences.json", e);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(LOG_TAG, "Error parsing preferences.json", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a list of SearchEngine instances from all available open search plugins.
|
* Looks for the default search engine shipped in the locale.
|
||||||
* This method does disk I/O, call it from a background thread.
|
|
||||||
*
|
*
|
||||||
* @return List of SearchEngine instances
|
* @return search engine name.
|
||||||
*/
|
*/
|
||||||
public List<SearchEngine> getAllEngines() {
|
private String getDefaultEngineNameFromLocale() {
|
||||||
// First try to read the engine list from the jar.
|
try {
|
||||||
InputStream in = getInputStreamFromJar("list.txt");
|
final JSONObject browsersearch = new JSONObject(RawResource.getAsString(context, R.raw.browsersearch));
|
||||||
|
if (browsersearch.has("default")) {
|
||||||
|
Log.d(LOG_TAG, "Found default engine name in browsersearch.json.");
|
||||||
|
return browsersearch.getString("default");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(LOG_TAG, "Error getting search engine name from browsersearch.json", e);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(LOG_TAG, "Error parsing browsersearch.json", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final List<SearchEngine> list = new ArrayList<SearchEngine>();
|
/**
|
||||||
|
* Creates a SearchEngine instance from an engine name.
|
||||||
|
*
|
||||||
|
* To create the engine, we first try to find the search plugin in the distribution
|
||||||
|
* (if one exists), followed by the localized plugins we ship with the browser, and
|
||||||
|
* then finally third-party plugins that are installed in the profile directory.
|
||||||
|
*
|
||||||
|
* This method must be called after the distribution is ready.
|
||||||
|
*
|
||||||
|
* @param name The search engine name (e.g. "Google" or "Amazon.com")
|
||||||
|
* @return SearchEngine instance for name.
|
||||||
|
*/
|
||||||
|
private SearchEngine createEngineFromName(String name) {
|
||||||
|
// First, look in the distribution.
|
||||||
|
SearchEngine engine = createEngineFromDistribution(name);
|
||||||
|
|
||||||
|
// Second, look in the jar for plugins shipped with the locale.
|
||||||
|
if (engine == null) {
|
||||||
|
engine = createEngineFromLocale(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, look in the profile for third-party plugins.
|
||||||
|
if (engine == null) {
|
||||||
|
engine = createEngineFromProfile(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine == null) {
|
||||||
|
Log.e(LOG_TAG, "Could not create search engine from name: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a SearchEngine instance for a distribution search plugin.
|
||||||
|
*
|
||||||
|
* This method iterates through the distribution searchplugins directory,
|
||||||
|
* creating SearchEngine instances until it finds one with the right name.
|
||||||
|
*
|
||||||
|
* This method must be called after the distribution is ready.
|
||||||
|
*
|
||||||
|
* @param name Search engine name.
|
||||||
|
* @return SearchEngine instance for name.
|
||||||
|
*/
|
||||||
|
private SearchEngine createEngineFromDistribution(String name) {
|
||||||
|
if (!distribution.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File pluginsDir = distribution.getDistributionFile("searchplugins");
|
||||||
|
if (pluginsDir == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File[] files = (new File(pluginsDir, "common")).listFiles();
|
||||||
|
return createEngineFromFileList(files, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a SearchEngine instance for a search plugin shipped in the locale.
|
||||||
|
*
|
||||||
|
* This method reads the list of search plugin file names from list.txt, then
|
||||||
|
* iterates through the files, creating SearchEngine instances until it finds one
|
||||||
|
* with the right name. Unfortunately, we need to do this because there is no
|
||||||
|
* other way to map the search engine "name" to the file for the search plugin.
|
||||||
|
*
|
||||||
|
* @param name Search engine name.
|
||||||
|
* @return SearchEngine instance for name.
|
||||||
|
*/
|
||||||
|
private SearchEngine createEngineFromLocale(String name) {
|
||||||
|
final InputStream in = getInputStreamFromSearchPluginsJar("list.txt");
|
||||||
InputStreamReader isr = null;
|
InputStreamReader isr = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -141,10 +312,14 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
BufferedReader br = new BufferedReader(isr);
|
BufferedReader br = new BufferedReader(isr);
|
||||||
String identifier;
|
String identifier;
|
||||||
while ((identifier = br.readLine()) != null) {
|
while ((identifier = br.readLine()) != null) {
|
||||||
list.add(createEngine(identifier));
|
final InputStream pluginIn = getInputStreamFromSearchPluginsJar(identifier + ".xml");
|
||||||
|
final SearchEngine engine = createEngineFromInputStream(identifier, pluginIn);
|
||||||
|
if (engine != null && engine.getName().equals(name)) {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException("Error creating all search engines from list.txt");
|
Log.e(LOG_TAG, "Error creating shipped search engine from name: " + name, e);
|
||||||
} finally {
|
} finally {
|
||||||
if (isr != null) {
|
if (isr != null) {
|
||||||
try {
|
try {
|
||||||
@ -159,27 +334,62 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
// Ignore.
|
// Ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a SearchEngine instance from an open search plugin.
|
* Creates a SearchEngine instance for a search plugin in the profile directory.
|
||||||
* This method does disk I/O, call it from a background thread.
|
|
||||||
*
|
*
|
||||||
* @param identifier search engine identifier (e.g. "google")
|
* This method iterates through the profile searchplugins directory, creating
|
||||||
* @return SearchEngine instance for identifier
|
* SearchEngine instances until it finds one with the right name.
|
||||||
|
*
|
||||||
|
* @param name Search engine name.
|
||||||
|
* @return SearchEngine instance for name.
|
||||||
*/
|
*/
|
||||||
private SearchEngine createEngine(String identifier) {
|
private SearchEngine createEngineFromProfile(String name) {
|
||||||
InputStream in = getInputStreamFromJar(identifier + ".xml");
|
final File pluginsDir = GeckoProfile.get(context).getFile("searchplugins");
|
||||||
|
if (pluginsDir == null) {
|
||||||
if (in == null) {
|
return null;
|
||||||
in = getEngineFromProfile(identifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in == null) {
|
final File[] files = pluginsDir.listFiles();
|
||||||
throw new IllegalArgumentException("Couldn't find search engine for identifier: " + identifier);
|
return createEngineFromFileList(files, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method iterates through an array of search plugin files, creating
|
||||||
|
* SearchEngine instances until it finds one with the right name.
|
||||||
|
*
|
||||||
|
* @param files Array of search plugin files.
|
||||||
|
* @param name Search engine name.
|
||||||
|
* @return SearchEngine instance for name.
|
||||||
|
*/
|
||||||
|
private SearchEngine createEngineFromFileList(File[] files, String name) {
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
try {
|
||||||
|
final FileInputStream fis = new FileInputStream(files[i]);
|
||||||
|
final SearchEngine engine = createEngineFromInputStream(null, fis);
|
||||||
|
if (engine != null && engine.getName().equals(name)) {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(LOG_TAG, "Error creating earch engine from name: " + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a SearchEngine instance from an InputStream.
|
||||||
|
*
|
||||||
|
* This method closes the stream after it is done reading it.
|
||||||
|
*
|
||||||
|
* @param identifier Seach engine identifier. This only exists for search engines that
|
||||||
|
* ship with the default set of engines in the locale.
|
||||||
|
* @param in InputStream for search plugin XML file.
|
||||||
|
* @return SearchEngine instance.
|
||||||
|
*/
|
||||||
|
private SearchEngine createEngineFromInputStream(String identifier, InputStream in) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
return new SearchEngine(identifier, in);
|
return new SearchEngine(identifier, in);
|
||||||
@ -194,13 +404,12 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a file from the searchplugins directory in the Gecko jar. This will only work
|
* Reads a file from the searchplugins directory in the Gecko jar.
|
||||||
* if the search activity is built as part of mozilla-central.
|
|
||||||
*
|
*
|
||||||
* @param fileName name of the file to read
|
* @param fileName name of the file to read.
|
||||||
* @return InputStream for file
|
* @return InputStream for file.
|
||||||
*/
|
*/
|
||||||
private InputStream getInputStreamFromJar(String fileName) {
|
private InputStream getInputStreamFromSearchPluginsJar(String fileName) {
|
||||||
final Locale locale = Locale.getDefault();
|
final Locale locale = Locale.getDefault();
|
||||||
|
|
||||||
// First, try a file path for the full locale.
|
// First, try a file path for the full locale.
|
||||||
@ -228,32 +437,14 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the jar URL for a file in the searchplugins directory
|
* Gets the jar URL for a file in the searchplugins directory.
|
||||||
*
|
*
|
||||||
* @param locale String representing the Gecko locale (e.g. "en-US")
|
* @param locale String representing the Gecko locale (e.g. "en-US").
|
||||||
* @param fileName name of the file to read
|
* @param fileName The name of the file to read.
|
||||||
* @return URL for jar file
|
* @return URL for jar file.
|
||||||
*/
|
*/
|
||||||
private String getSearchPluginsJarURL(String locale, String fileName) {
|
private String getSearchPluginsJarURL(String locale, String fileName) {
|
||||||
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
|
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
|
||||||
return "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
|
return "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the search plugin XML file from the searchplugins directory in the Gecko profile.
|
|
||||||
*
|
|
||||||
* @param identifier
|
|
||||||
* @return InputStream for search plugin file
|
|
||||||
*/
|
|
||||||
private InputStream getEngineFromProfile(String identifier) {
|
|
||||||
final File f = GeckoProfile.get(context).getFile("searchplugins/" + identifier + ".xml");
|
|
||||||
if (f.exists()) {
|
|
||||||
try {
|
|
||||||
return new FileInputStream(f);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.e(LOG_TAG, "Exception getting search engine from profile", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,9 @@
|
|||||||
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
||||||
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
||||||
<string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
|
<string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
|
||||||
|
<string name="fxaccount_status_sync_now">&fxaccount_status_sync_now;</string>
|
||||||
|
<string name="fxaccount_status_syncing">&fxaccount_status_syncing;</string>
|
||||||
|
<string name="fxaccount_status_last_synced">&remote_tabs_last_synced;</string>
|
||||||
<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
|
<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
|
||||||
<string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
|
<string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
|
||||||
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
|
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
|
||||||
@ -216,6 +219,3 @@
|
|||||||
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
||||||
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
||||||
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
||||||
|
|
||||||
<!-- Find-In-Page strings -->
|
|
||||||
<string name="find_matchcase">&find_matchcase;</string>
|
|
||||||
|
@ -274,7 +274,9 @@ public class Hex implements BinaryEncoder, BinaryDecoder {
|
|||||||
try {
|
try {
|
||||||
byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
|
byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
|
||||||
return encodeHex(byteArray);
|
return encodeHex(byteArray);
|
||||||
} catch (ClassCastException | UnsupportedEncodingException e) {
|
} catch (ClassCastException e) {
|
||||||
|
throw new EncoderException(e.getMessage(), e);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new EncoderException(e.getMessage(), e);
|
throw new EncoderException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,6 +501,22 @@ VARIABLES = {
|
|||||||
delimiters.
|
delimiters.
|
||||||
""", None),
|
""", None),
|
||||||
|
|
||||||
|
'HAS_MISC_RULE': (bool, bool,
|
||||||
|
"""Whether this directory should be traversed in the ``misc`` tier.
|
||||||
|
|
||||||
|
Many ``libs`` rules still exist in Makefile.in files. We highly prefer
|
||||||
|
that these rules exist in the ``misc`` tier/target so that they can be
|
||||||
|
executed concurrently during tier traversal (the ``misc`` tier is
|
||||||
|
fully concurrent).
|
||||||
|
|
||||||
|
Presence of this variable indicates that this directory should be
|
||||||
|
traversed by the ``misc`` tier.
|
||||||
|
|
||||||
|
Please note that converting ``libs`` rules to the ``misc`` tier must
|
||||||
|
be done with care, as there are many implicit dependencies that can
|
||||||
|
break the build in subtle ways.
|
||||||
|
""", 'misc'),
|
||||||
|
|
||||||
'FINAL_TARGET_FILES': (HierarchicalStringList, list,
|
'FINAL_TARGET_FILES': (HierarchicalStringList, list,
|
||||||
"""List of files to be installed into the application directory.
|
"""List of files to be installed into the application directory.
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
HAS_MISC_RULE = True
|
@ -98,6 +98,8 @@ class TestEmitterBasic(unittest.TestCase):
|
|||||||
reldirs = [o.relativedir for o in objs]
|
reldirs = [o.relativedir for o in objs]
|
||||||
self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
|
self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
|
||||||
|
|
||||||
|
self.assertEqual(objs[3].affected_tiers, {'misc'})
|
||||||
|
|
||||||
dirs = [o.dirs for o in objs]
|
dirs = [o.dirs for o in objs]
|
||||||
self.assertEqual(dirs, [
|
self.assertEqual(dirs, [
|
||||||
[
|
[
|
||||||
|
Loading…
Reference in New Issue
Block a user