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
|
||||
# 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%;
|
||||
}
|
||||
|
||||
.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.
|
||||
*/
|
||||
@ -745,7 +777,7 @@ html, .fx-embedded, #main,
|
||||
|
||||
.standalone .room-inner-info-area {
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 25%;
|
||||
z-index: 1000;
|
||||
@ -767,6 +799,7 @@ html, .fx-embedded, #main,
|
||||
padding: .5em 3em .3em 3em;
|
||||
border-radius: 3px;
|
||||
font-weight: normal;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.standalone .room-conversation h2.room-name {
|
||||
@ -796,7 +829,7 @@ html, .fx-embedded, #main,
|
||||
|
||||
.standalone .room-conversation .conversation-toolbar {
|
||||
background: #000;
|
||||
border-top: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
|
||||
|
@ -10,6 +10,15 @@ loop.store.ActiveRoomStore = (function() {
|
||||
"use strict";
|
||||
|
||||
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 = {
|
||||
// The initial state of the room
|
||||
@ -84,7 +93,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
this._storeState = {
|
||||
roomState: ROOM_STATES.INIT,
|
||||
audioMuted: false,
|
||||
videoMuted: false
|
||||
videoMuted: false,
|
||||
failureReason: undefined
|
||||
};
|
||||
}
|
||||
|
||||
@ -112,13 +122,24 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* @param {sharedActions.RoomFailure} 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 + "`:",
|
||||
actionData.error);
|
||||
|
||||
this.setStoreState({
|
||||
error: actionData.error,
|
||||
roomState: actionData.error.errno === 202 ? ROOM_STATES.FULL
|
||||
: ROOM_STATES.FAILED
|
||||
failureReason: getReason(actionData.error.errno),
|
||||
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.
|
||||
*/
|
||||
joinRoom: function() {
|
||||
// Reset the failure reason if necessary.
|
||||
if (this.getStoreState().failureReason) {
|
||||
this.setStoreState({failureReason: undefined});
|
||||
}
|
||||
|
||||
this._mozLoop.rooms.join(this._storeState.roomToken,
|
||||
function(error, responseData) {
|
||||
if (error) {
|
||||
@ -275,11 +301,17 @@ loop.store.ActiveRoomStore = (function() {
|
||||
|
||||
/**
|
||||
* 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
|
||||
// could be a success case, but there's no way we should be intentionally
|
||||
// sending that and still have the window open.
|
||||
this.setStoreState({
|
||||
failureReason: actionData.reason
|
||||
});
|
||||
|
||||
this._leaveRoom(ROOM_STATES.FAILED);
|
||||
},
|
||||
|
||||
|
@ -8,6 +8,7 @@ var loop = loop || {};
|
||||
loop.OTSdkDriver = (function() {
|
||||
|
||||
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
|
||||
@ -47,8 +48,11 @@ loop.OTSdkDriver = (function() {
|
||||
// the initial connect of the session. This saves time when setting up
|
||||
// the media.
|
||||
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
|
||||
this.publisherConfig,
|
||||
this._onPublishComplete.bind(this));
|
||||
this.publisherConfig);
|
||||
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() {
|
||||
if (this.session) {
|
||||
this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||
this.session.off("connectionDestroyed",
|
||||
this._onConnectionDestroyed.bind(this));
|
||||
this.session.off("sessionDisconnected",
|
||||
this._onSessionDisconnected.bind(this));
|
||||
|
||||
this.session.off("streamCreated connectionDestroyed sessionDisconnected");
|
||||
this.session.disconnect();
|
||||
delete this.session;
|
||||
}
|
||||
if (this.publisher) {
|
||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened");
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
}
|
||||
@ -126,7 +126,7 @@ loop.OTSdkDriver = (function() {
|
||||
if (error) {
|
||||
console.error("Failed to complete connection", error);
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: "couldNotConnect"
|
||||
reason: FAILURE_REASONS.COULD_NOT_CONNECT
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -159,7 +159,7 @@ loop.OTSdkDriver = (function() {
|
||||
// We only need to worry about the network disconnected reason here.
|
||||
if (event.reason === "networkDisconnected") {
|
||||
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.
|
||||
*
|
||||
* @param {Error} error An OT error object, null if there was no error.
|
||||
* @param {OT.Event} event
|
||||
*/
|
||||
_onPublishComplete: function(error) {
|
||||
if (error) {
|
||||
console.error("Failed to initialize publisher", error);
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: "noMedia"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
_onPublishComplete: function(event) {
|
||||
event.preventDefault();
|
||||
this._publisherReady = true;
|
||||
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
|
||||
* and the publisher is ready.
|
||||
|
@ -17,6 +17,14 @@ loop.shared.utils = (function(mozL10n) {
|
||||
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.
|
||||
*
|
||||
@ -110,6 +118,7 @@ loop.shared.utils = (function(mozL10n) {
|
||||
|
||||
return {
|
||||
CALL_TYPES: CALL_TYPES,
|
||||
FAILURE_REASONS: FAILURE_REASONS,
|
||||
Helper: Helper,
|
||||
composeCallUrlEmail: composeCallUrlEmail,
|
||||
formatDate: formatDate,
|
||||
|
@ -18,6 +18,13 @@ body,
|
||||
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 {
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
|
@ -11,8 +11,10 @@ var loop = loop || {};
|
||||
loop.standaloneRoomViews = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
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() {
|
||||
switch(this.props.roomState) {
|
||||
case ROOM_STATES.INIT:
|
||||
@ -67,6 +83,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
React.DOM.p(null, this._renderCallToActionLink())
|
||||
)
|
||||
);
|
||||
case ROOM_STATES.FAILED:
|
||||
return (
|
||||
React.DOM.p({className: "failed-room-message"},
|
||||
this._getFailureString()
|
||||
)
|
||||
);
|
||||
default:
|
||||
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',
|
||||
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() {
|
||||
this.stopListening(this.props.activeRoomStore);
|
||||
},
|
||||
@ -207,7 +271,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
StandaloneRoomHeader(null),
|
||||
StandaloneRoomInfoArea({roomState: this.state.roomState,
|
||||
failureReason: this.state.failureReason,
|
||||
joinRoom: this.joinRoom,
|
||||
helper: this.props.helper}),
|
||||
React.DOM.div({className: "video-layout-wrapper"},
|
||||
@ -229,7 +295,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
hangupButtonLabel: mozL10n.get("rooms_leave_button_label"),
|
||||
enableHangup: this._roomIsActive()})
|
||||
)
|
||||
)
|
||||
),
|
||||
StandaloneRoomFooter(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -11,8 +11,10 @@ var loop = loop || {};
|
||||
loop.standaloneRoomViews = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
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() {
|
||||
switch(this.props.roomState) {
|
||||
case ROOM_STATES.INIT:
|
||||
@ -67,6 +83,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
<p>{this._renderCallToActionLink()}</p>
|
||||
</div>
|
||||
);
|
||||
case ROOM_STATES.FAILED:
|
||||
return (
|
||||
<p className="failed-room-message">
|
||||
{this._getFailureString()}
|
||||
</p>
|
||||
);
|
||||
default:
|
||||
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({
|
||||
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() {
|
||||
this.stopListening(this.props.activeRoomStore);
|
||||
},
|
||||
@ -207,7 +271,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
<StandaloneRoomHeader />
|
||||
<StandaloneRoomInfoArea roomState={this.state.roomState}
|
||||
failureReason={this.state.failureReason}
|
||||
joinRoom={this.joinRoom}
|
||||
helper={this.props.helper} />
|
||||
<div className="video-layout-wrapper">
|
||||
@ -230,6 +296,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
enableHangup={this._roomIsActive()} />
|
||||
</div>
|
||||
</div>
|
||||
<StandaloneRoomFooter />
|
||||
</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_join_label=Join the conversation
|
||||
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
|
||||
## replaced by the brand name and {{currentStatus}} will be replaced
|
||||
|
@ -65,6 +65,7 @@ describe("loop.roomViews", function () {
|
||||
roomState: ROOM_STATES.INIT,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
failureReason: undefined,
|
||||
foo: "bar"
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,9 @@ var sharedActions = loop.shared.actions;
|
||||
describe("loop.store.ActiveRoomStore", function () {
|
||||
"use strict";
|
||||
|
||||
var SERVER_CODES = loop.store.SERVER_CODES;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||
var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
|
||||
var fakeMultiplexGum;
|
||||
|
||||
@ -91,8 +93,8 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
sinon.match(ROOM_STATES.READY), fakeError);
|
||||
});
|
||||
|
||||
it("should set the state to `FULL` on server errno 202", function() {
|
||||
fakeError.errno = 202;
|
||||
it("should set the state to `FULL` on server error room full", function() {
|
||||
fakeError.errno = SERVER_CODES.ROOM_FULL;
|
||||
|
||||
store.roomFailure({error: fakeError});
|
||||
|
||||
@ -103,7 +105,28 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
store.roomFailure({error: fakeError});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setupWindowData", function() {
|
||||
@ -244,6 +267,14 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
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() {
|
||||
store.joinRoom();
|
||||
|
||||
@ -380,22 +411,34 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
});
|
||||
|
||||
describe("#connectionFailure", function() {
|
||||
var connectionFailureAction;
|
||||
|
||||
beforeEach(function() {
|
||||
store.setStoreState({
|
||||
roomState: ROOM_STATES.JOINED,
|
||||
roomToken: "fakeToken",
|
||||
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() {
|
||||
store.leaveRoom();
|
||||
store.connectionFailure(connectionFailureAction);
|
||||
|
||||
sinon.assert.calledOnce(fakeMultiplexGum.reset);
|
||||
});
|
||||
|
||||
it("should disconnect from the servers via the sdk", function() {
|
||||
store.connectionFailure();
|
||||
store.connectionFailure(connectionFailureAction);
|
||||
|
||||
sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
|
||||
});
|
||||
@ -404,13 +447,13 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
sandbox.stub(window, "clearTimeout");
|
||||
store._timeout = {};
|
||||
|
||||
store.connectionFailure();
|
||||
store.connectionFailure(connectionFailureAction);
|
||||
|
||||
sinon.assert.calledOnce(clearTimeout);
|
||||
});
|
||||
|
||||
it("should call mozLoop.rooms.leave", function() {
|
||||
store.connectionFailure();
|
||||
store.connectionFailure(connectionFailureAction);
|
||||
|
||||
sinon.assert.calledOnce(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() {
|
||||
store.connectionFailure();
|
||||
store.connectionFailure(connectionFailureAction);
|
||||
|
||||
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
|
||||
});
|
||||
|
@ -7,16 +7,19 @@ describe("loop.OTSdkDriver", function () {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
|
||||
var sandbox;
|
||||
var dispatcher, driver, publisher, sdk, session, sessionData;
|
||||
var fakeLocalElement, fakeRemoteElement, publisherConfig;
|
||||
var fakeLocalElement, fakeRemoteElement, publisherConfig, fakeEvent;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
fakeLocalElement = {fake: 1};
|
||||
fakeRemoteElement = {fake: 2};
|
||||
fakeEvent = {
|
||||
preventDefault: sinon.stub()
|
||||
};
|
||||
publisherConfig = {
|
||||
fake: "config"
|
||||
};
|
||||
@ -34,14 +37,14 @@ describe("loop.OTSdkDriver", function () {
|
||||
subscribe: sinon.stub()
|
||||
}, Backbone.Events);
|
||||
|
||||
publisher = {
|
||||
publisher = _.extend({
|
||||
destroy: sinon.stub(),
|
||||
publishAudio: sinon.stub(),
|
||||
publishVideo: sinon.stub()
|
||||
};
|
||||
}, Backbone.Events);
|
||||
|
||||
sdk = {
|
||||
initPublisher: sinon.stub(),
|
||||
initPublisher: sinon.stub().returns(publisher),
|
||||
initSession: sinon.stub().returns(session)
|
||||
};
|
||||
|
||||
@ -80,51 +83,6 @@ describe("loop.OTSdkDriver", function () {
|
||||
sinon.assert.calledOnce(sdk.initPublisher);
|
||||
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() {
|
||||
@ -194,7 +152,7 @@ describe("loop.OTSdkDriver", function () {
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
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.match.hasOwn("name", "connectionFailure"));
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
function getJoinButton(view) {
|
||||
return view.getDOMNode().querySelector(".btn-join");
|
||||
|
@ -608,6 +608,16 @@
|
||||
roomState: ROOM_STATES.FULL,
|
||||
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}} />
|
||||
</div>
|
||||
</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 name="SVG icons preview">
|
||||
|
@ -9,11 +9,6 @@ function test() {
|
||||
}
|
||||
|
||||
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);
|
||||
registerCleanupFunction(function () {
|
||||
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/#8" }], 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 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
is(aBrowser.currentURI.spec, state.windows[0].tabs[loadCount - 1].entries[0].url,
|
||||
gBrowser.tabContainer.addEventListener("SSTabRestored", function onRestored(event) {
|
||||
let tab = event.target;
|
||||
let browser = tab.linkedBrowser;
|
||||
let tabData = state.windows[0].tabs[loadCount++];
|
||||
|
||||
// double check that this tab was the right one
|
||||
is(browser.currentURI.spec, tabData.entries[0].url,
|
||||
"load " + loadCount + " - browser loaded correct url");
|
||||
is(ss.getTabValue(tab, "uniq"), tabData.extData.uniq,
|
||||
"load " + loadCount + " - correct tab was restored");
|
||||
|
||||
if (loadCount <= state.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.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 restored");
|
||||
if (loadCount == state.windows[0].tabs.length) {
|
||||
gBrowser.tabContainer.removeEventListener("SSTabRestored", onRestored);
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length) {
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
reloadAllTabs(state, function () {
|
||||
waitForBrowserState(TestRunner.backupState, testCascade);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// reload the next tab
|
||||
window.gBrowser.reloadTab(window.gBrowser.tabs[loadCount]);
|
||||
}
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(TestRunner.backupState, finish);
|
||||
});
|
||||
} else {
|
||||
// reload the next tab
|
||||
gBrowser.browsers[loadCount].reload();
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
testdir = $(abspath $(DEPTH)/_tests/xpcshell/browser/experiments/test/xpcshell)
|
||||
|
||||
libs::
|
||||
misc:: $(call mkdir_deps,$(testdir))
|
||||
$(EXIT_ON_ERROR) \
|
||||
$(NSINSTALL) -D $(testdir); \
|
||||
for dir in $(addondir)/*; do \
|
||||
base=`basename $$dir`; \
|
||||
(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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
HAS_MISC_RULE = True
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'Experiments.manifest',
|
||||
'ExperimentsService.js',
|
||||
|
@ -1,2 +1,6 @@
|
||||
title=You're browsing privately
|
||||
title.normal=Open a private window?
|
||||
# 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.normal=Open a private window?
|
||||
|
@ -41,7 +41,7 @@ public class FennecNativeActions implements Actions {
|
||||
}
|
||||
|
||||
class GeckoEventExpecter implements RepeatedEventExpecter {
|
||||
private static final int MAX_WAIT_MS = 90000;
|
||||
private static final int MAX_WAIT_MS = 180000;
|
||||
|
||||
private volatile boolean mIsRegistered;
|
||||
|
||||
|
@ -23,5 +23,6 @@ css_properties.js: host_ListCSSProperties$(HOST_BIN_SUFFIX) css_properties_like_
|
||||
GARBAGE += css_properties.js
|
||||
TEST_FILES := css_properties.js
|
||||
TEST_DEST = $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
||||
TEST_TARGET := misc
|
||||
INSTALL_TARGETS += TEST
|
||||
endif
|
||||
|
@ -4,6 +4,8 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
HAS_MISC_RULE = True
|
||||
|
||||
HostSimplePrograms([
|
||||
'host_ListCSSProperties',
|
||||
])
|
||||
|
@ -27,6 +27,10 @@ public final class SharedPreferencesHelper
|
||||
{
|
||||
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 {
|
||||
APP("app"),
|
||||
PROFILE("profile"),
|
||||
@ -214,7 +218,7 @@ public final class SharedPreferencesHelper
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Got onSharedPreferenceChanged");
|
||||
}
|
||||
try {
|
||||
@ -279,19 +283,19 @@ public final class SharedPreferencesHelper
|
||||
// overwriting an in-progress response.
|
||||
try {
|
||||
if (event.equals("SharedPreferences:Set")) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Got SharedPreferences:Set message.");
|
||||
}
|
||||
handleSet(message);
|
||||
} else if (event.equals("SharedPreferences:Get")) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
|
||||
}
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("values", handleGet(message));
|
||||
EventDispatcher.sendResponse(message, obj);
|
||||
} else if (event.equals("SharedPreferences:Observe")) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
|
||||
}
|
||||
handleObserve(message);
|
||||
|
@ -24,7 +24,6 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
@ -42,6 +41,7 @@ import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.FileUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
@ -286,7 +286,7 @@ public class Distribution {
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject all = new JSONObject(getFileContents(descFile));
|
||||
JSONObject all = new JSONObject(FileUtils.getFileContents(descFile));
|
||||
|
||||
if (!all.has("Global")) {
|
||||
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
||||
@ -314,7 +314,7 @@ public class Distribution {
|
||||
}
|
||||
|
||||
try {
|
||||
return new JSONArray(getFileContents(bookmarks));
|
||||
return new JSONArray(FileUtils.getFileContents(bookmarks));
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error getting bookmarks", e);
|
||||
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
|
||||
@ -739,19 +739,6 @@ public class Distribution {
|
||||
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() {
|
||||
return context.getApplicationInfo().dataDir;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
/**
|
||||
* A fragment that displays the status of an AndroidFxAccount.
|
||||
@ -86,6 +87,7 @@ public class FxAccountStatusFragment
|
||||
protected EditTextPreference deviceNamePreference;
|
||||
protected Preference syncServerPreference;
|
||||
protected Preference morePreference;
|
||||
protected Preference syncNowPreference;
|
||||
|
||||
protected volatile AndroidFxAccount fxAccount;
|
||||
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
||||
@ -167,6 +169,10 @@ public class FxAccountStatusFragment
|
||||
morePreference = ensureFindPreference("more");
|
||||
morePreference.setOnPreferenceClickListener(this);
|
||||
|
||||
syncNowPreference = ensureFindPreference("sync_now");
|
||||
syncNowPreference.setEnabled(true);
|
||||
syncNowPreference.setOnPreferenceClickListener(this);
|
||||
|
||||
if (HardwareUtils.hasMenuButton()) {
|
||||
syncCategory.removePreference(morePreference);
|
||||
}
|
||||
@ -229,6 +235,13 @@ public class FxAccountStatusFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == syncNowPreference) {
|
||||
if (fxAccount != null) {
|
||||
FirefoxAccounts.requestSync(fxAccount.getAndroidAccount(), FirefoxAccounts.FORCE, null, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -250,6 +263,7 @@ public class FxAccountStatusFragment
|
||||
passwordsPreference.setEnabled(enabled);
|
||||
// Since we can't sync, we can't update our remote client record.
|
||||
deviceNamePreference.setEnabled(enabled);
|
||||
syncNowPreference.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -470,6 +484,26 @@ public class FxAccountStatusFragment
|
||||
final String clientName = clientsDataDelegate.getClientName();
|
||||
deviceNamePreference.setSummary(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() {
|
||||
|
@ -65,6 +65,8 @@ public class AndroidFxAccount {
|
||||
|
||||
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 AccountManager accountManager;
|
||||
protected final Account account;
|
||||
@ -565,4 +567,22 @@ public class AndroidFxAccount {
|
||||
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY, account.name);
|
||||
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
|
||||
public void onSuccessfulSync(int otherClientsCount) {
|
||||
this.account.setLastSyncedTimestamp(System.currentTimeMillis());
|
||||
// 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
|
||||
// into the client-count-dependent interval.
|
||||
|
@ -434,11 +434,20 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
|
||||
Logger.resetLogging();
|
||||
|
||||
final Context context = getContext();
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
|
||||
Logger.info(LOG_TAG, "Syncing FxAccount" +
|
||||
" account named like " + Utils.obfuscateEmail(account.name) +
|
||||
" for authority " + authority +
|
||||
" 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);
|
||||
FirefoxAccounts.logSyncHints(syncHints);
|
||||
|
||||
@ -450,12 +459,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
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.
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
|
@ -187,6 +187,8 @@
|
||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||
<!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_sync_server 'Sync server'>
|
||||
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
||||
|
@ -45,7 +45,7 @@
|
||||
style="@style/FxAccountLinkItem"
|
||||
android:text="@string/fxaccount_confirm_account_resend_email" />
|
||||
|
||||
<TextView
|
||||
<TextView
|
||||
android:id="@+id/change_confirmation_email_link"
|
||||
style="@style/FxAccountLinkItem"
|
||||
android:text="@string/fxaccount_confirm_account_change_email" />
|
||||
|
@ -55,6 +55,14 @@
|
||||
android:persistent="false"
|
||||
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
|
||||
android:key="bookmarks"
|
||||
android:persistent="false"
|
||||
|
@ -96,6 +96,7 @@
|
||||
<string name="share_image_failed">&share_image_failed;</string>
|
||||
<string name="save_as_pdf">&save_as_pdf;</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="page">&page;</string>
|
||||
<string name="tools">&tools;</string>
|
||||
|
@ -37,7 +37,6 @@ public class SyncConfiguration {
|
||||
this.editor = config.getEditor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
// Android <=r8 SharedPreferences.Editor does not contain apply() for overriding.
|
||||
this.editor.commit();
|
||||
@ -86,7 +85,6 @@ public class SyncConfiguration {
|
||||
|
||||
// Not marking as Override, because Android <= 10 doesn't have
|
||||
// putStringSet. Neither can we implement it.
|
||||
@Override
|
||||
public Editor putStringSet(String key, Set<String> value) {
|
||||
throw new RuntimeException("putStringSet not available.");
|
||||
}
|
||||
@ -162,7 +160,6 @@ public class SyncConfiguration {
|
||||
|
||||
// Not marking as Override, because Android <= 10 doesn't have
|
||||
// getStringSet. Neither can we implement it.
|
||||
@Override
|
||||
public Set<String> getStringSet(String key, Set<String> defValue) {
|
||||
throw new RuntimeException("getStringSet not available.");
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public class BaseResource implements Resource {
|
||||
|
||||
private boolean retryOnFailedRequest = true;
|
||||
|
||||
public static final boolean rewriteLocalhost = true;
|
||||
public static boolean rewriteLocalhost = true;
|
||||
|
||||
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 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;
|
||||
|
||||
public JavascriptTest(String javascriptUrl) {
|
||||
@ -39,11 +44,11 @@ public class JavascriptTest extends BaseTest {
|
||||
new JavascriptMessageParser(mAsserter, false);
|
||||
try {
|
||||
while (!testMessageParser.isTestFinished()) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
|
||||
}
|
||||
String data = expecter.blockForEventData();
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
if (logVerbose) {
|
||||
Log.v(LOGTAG, "Got event with data '" + data + "'");
|
||||
}
|
||||
|
||||
@ -60,7 +65,7 @@ public class JavascriptTest extends BaseTest {
|
||||
testMessageParser.logMessage(message);
|
||||
}
|
||||
|
||||
if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
|
||||
if (logDebug) {
|
||||
Log.d(LOGTAG, "Got test finished message");
|
||||
}
|
||||
} finally {
|
||||
|
@ -9,6 +9,7 @@ import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.Scanner;
|
||||
|
||||
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
|
||||
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",
|
||||
|
||||
// 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() {
|
||||
Services.obs.addObserver(this, "SearchEngines:Add", false);
|
||||
@ -7047,34 +7047,7 @@ var SearchEngines = {
|
||||
|
||||
// Updates the search activity pref when the default engine changes.
|
||||
_setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
|
||||
// Helper function copied from nsSearchService.js. This is the logic that is used
|
||||
// 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);
|
||||
SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name);
|
||||
},
|
||||
|
||||
// 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_32BIT="r8c"
|
||||
ANDROID_SDK_VERSION="20"
|
||||
ANDROID_SDK_VERSION="21"
|
||||
|
||||
# Build Fennec
|
||||
ac_add_options --enable-application=mobile/android
|
||||
|
@ -6,8 +6,8 @@
|
||||
"filename": "android-ndk.tar.bz2"
|
||||
},
|
||||
{
|
||||
"size": 207966812,
|
||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
||||
"size": 227988048,
|
||||
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||
"algorithm": "sha512",
|
||||
"filename": "android-sdk.tar.xz"
|
||||
},
|
||||
|
@ -6,8 +6,8 @@
|
||||
"filename": "android-ndk.tar.bz2"
|
||||
},
|
||||
{
|
||||
"size": 207966812,
|
||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
||||
"size": 227988048,
|
||||
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||
"algorithm": "sha512",
|
||||
"filename": "android-sdk.tar.xz"
|
||||
},
|
||||
|
@ -6,8 +6,8 @@
|
||||
"filename": "android-ndk.tar.bz2"
|
||||
},
|
||||
{
|
||||
"size": 207966812,
|
||||
"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
|
||||
"size": 227988048,
|
||||
"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
|
||||
"algorithm": "sha512",
|
||||
"filename": "android-sdk.tar.xz"
|
||||
},
|
||||
|
@ -3,12 +3,14 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
gradle := \
|
||||
local.properties.in \
|
||||
gradle.properties.in \
|
||||
$(NULL)
|
||||
|
||||
gradle_PATH := $(CURDIR)
|
||||
gradle_FLAGS += -Dtopsrcdir=$(abspath $(topsrcdir))
|
||||
gradle_FLAGS += -Dtopobjdir=$(abspath $(DEPTH))
|
||||
gradle_FLAGS += -DANDROID_SDK_ROOT=$(ANDROID_SDK_ROOT)
|
||||
gradle_KEEP_PATH := 1
|
||||
PP_TARGETS += gradle
|
||||
|
||||
|
@ -14,7 +14,7 @@ buildscript {
|
||||
}
|
||||
|
||||
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
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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 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.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||
import org.mozilla.search.autocomplete.SearchBar;
|
||||
import org.mozilla.search.autocomplete.SuggestionsFragment;
|
||||
@ -101,7 +102,7 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
||||
suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
|
||||
postSearchFragment = (PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch);
|
||||
|
||||
searchEngineManager = new SearchEngineManager(this);
|
||||
searchEngineManager = new SearchEngineManager(this, Distribution.init(this));
|
||||
searchEngineManager.setChangeCallback(this);
|
||||
|
||||
// Initialize the fragments with the selected search engine.
|
||||
@ -279,7 +280,10 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
||||
searchEngineManager.getEngine(new SearchEngineCallback() {
|
||||
@Override
|
||||
public void execute(SearchEngine engine) {
|
||||
postSearchFragment.startSearch(engine, query);
|
||||
// TODO: If engine is null, we should show an error message.
|
||||
if (engine != null) {
|
||||
postSearchFragment.startSearch(engine, query);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -293,6 +297,10 @@ public class SearchActivity extends LocaleAware.LocaleAwareFragmentActivity
|
||||
*/
|
||||
@Override
|
||||
public void execute(SearchEngine engine) {
|
||||
// TODO: If engine is null, we should show an error message.
|
||||
if (engine == null) {
|
||||
return;
|
||||
}
|
||||
this.engine = engine;
|
||||
suggestionsFragment.setEngine(engine);
|
||||
searchBar.setEngine(engine);
|
||||
|
@ -52,7 +52,9 @@ public class SearchEngine {
|
||||
"document.getElementsByTagName('head')[0].appendChild(tag);" +
|
||||
"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 String shortName;
|
||||
private String iconURL;
|
||||
|
||||
@ -189,7 +191,9 @@ public class SearchEngine {
|
||||
public String getInjectableJs() {
|
||||
final String css;
|
||||
|
||||
if (identifier.equals("bing")) {
|
||||
if (identifier == null) {
|
||||
css = "";
|
||||
} else if (identifier.equals("bing")) {
|
||||
css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
|
||||
} else if (identifier.equals("google")) {
|
||||
css = "#sfcnt,#top_nav{display:none}";
|
||||
@ -247,7 +251,7 @@ public class SearchEngine {
|
||||
public String resultsUriForQuery(String query) {
|
||||
final Uri resultsUri = getResultsUri();
|
||||
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 "";
|
||||
}
|
||||
final String template = Uri.decode(resultsUri.toString());
|
||||
@ -261,7 +265,7 @@ public class SearchEngine {
|
||||
*/
|
||||
public String getSuggestionTemplate(String query) {
|
||||
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 "";
|
||||
}
|
||||
final String template = Uri.decode(suggestUri.toString());
|
||||
|
@ -6,15 +6,21 @@ package org.mozilla.search.providers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.util.FileUtils;
|
||||
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.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@ -30,9 +36,16 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
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 Distribution distribution;
|
||||
private SearchEngineCallback changeCallback;
|
||||
private SearchEngine engine;
|
||||
|
||||
@ -40,11 +53,19 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
public void execute(SearchEngine engine);
|
||||
}
|
||||
|
||||
public SearchEngineManager(Context context) {
|
||||
public SearchEngineManager(Context context, Distribution distribution) {
|
||||
this.context = context;
|
||||
this.distribution = distribution;
|
||||
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) {
|
||||
this.changeCallback = changeCallback;
|
||||
}
|
||||
@ -60,80 +81,230 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
if (engine != null) {
|
||||
callback.execute(engine);
|
||||
} else {
|
||||
getEngineFromPrefs(callback);
|
||||
getDefaultEngine(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
|
||||
context = null;
|
||||
distribution = null;
|
||||
changeCallback = null;
|
||||
engine = null;
|
||||
}
|
||||
|
||||
private int ignorePreferenceChange = 0;
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
getEngineFromPrefs(changeCallback);
|
||||
|
||||
if (ignorePreferenceChange > 0) {
|
||||
ignorePreferenceChange--;
|
||||
return;
|
||||
}
|
||||
|
||||
getDefaultEngine(changeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the current search engine in shared preferences.
|
||||
* Creates a SearchEngine instance and caches it for use on the main thread.
|
||||
* Runs a SearchEngineCallback on the main thread.
|
||||
*/
|
||||
private void runCallback(final SearchEngine engine, final SearchEngineCallback callback) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Cache engine for future calls to getEngine.
|
||||
SearchEngineManager.this.engine = engine;
|
||||
callback.execute(engine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method finds and creates the default search engine. It will first look for
|
||||
* the default engine name, then create the engine from that name.
|
||||
*
|
||||
* @param callback a SearchEngineCallback to be called after successfully looking
|
||||
* 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 getEngineFromPrefs(final SearchEngineCallback callback) {
|
||||
final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
|
||||
private void getDefaultEngine(final SearchEngineCallback callback) {
|
||||
// This runnable is posted to the background thread.
|
||||
distribution.addOnDistributionReadyCallback(new Runnable() {
|
||||
@Override
|
||||
protected SearchEngine doInBackground(Void... params) {
|
||||
String identifier = GeckoSharedPrefs.forApp(context).getString(Constants.PREF_SEARCH_ENGINE_KEY, null);
|
||||
if (!TextUtils.isEmpty(identifier)) {
|
||||
try {
|
||||
return createEngine(identifier);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(LOG_TAG, "Exception creating search engine from pref. Falling back to default engine.", e);
|
||||
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();
|
||||
}
|
||||
|
||||
try {
|
||||
return createEngine(Constants.DEFAULT_ENGINE_IDENTIFIER);
|
||||
} catch (IllegalArgumentException e) {
|
||||
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);
|
||||
}
|
||||
|
||||
return null;
|
||||
final SearchEngine engine = createEngineFromName(name);
|
||||
runCallback(engine, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SearchEngine engine) {
|
||||
if (engine != null) {
|
||||
// Only touch engine on the main thread.
|
||||
SearchEngineManager.this.engine = engine;
|
||||
if (callback != null) {
|
||||
callback.execute(engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of SearchEngine instances from all available open search plugins.
|
||||
* This method does disk I/O, call it from a background thread.
|
||||
* Looks for a default search engine included in a distribution.
|
||||
* This method must be called after the distribution is ready.
|
||||
*
|
||||
* @return List of SearchEngine instances
|
||||
* @return search engine name.
|
||||
*/
|
||||
public List<SearchEngine> getAllEngines() {
|
||||
// First try to read the engine list from the jar.
|
||||
InputStream in = getInputStreamFromJar("list.txt");
|
||||
private String getDefaultEngineNameFromDistribution() {
|
||||
if (!distribution.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<SearchEngine> list = new ArrayList<SearchEngine>();
|
||||
final File prefFile = distribution.getDistributionFile("preferences.json");
|
||||
if (prefFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the default search engine shipped in the locale.
|
||||
*
|
||||
* @return search engine name.
|
||||
*/
|
||||
private String getDefaultEngineNameFromLocale() {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
try {
|
||||
@ -141,10 +312,14 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String identifier;
|
||||
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) {
|
||||
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 {
|
||||
if (isr != null) {
|
||||
try {
|
||||
@ -159,27 +334,62 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SearchEngine instance from an open search plugin.
|
||||
* This method does disk I/O, call it from a background thread.
|
||||
* Creates a SearchEngine instance for a search plugin in the profile directory.
|
||||
*
|
||||
* @param identifier search engine identifier (e.g. "google")
|
||||
* @return SearchEngine instance for identifier
|
||||
* This method iterates through the profile searchplugins directory, creating
|
||||
* 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) {
|
||||
InputStream in = getInputStreamFromJar(identifier + ".xml");
|
||||
|
||||
if (in == null) {
|
||||
in = getEngineFromProfile(identifier);
|
||||
private SearchEngine createEngineFromProfile(String name) {
|
||||
final File pluginsDir = GeckoProfile.get(context).getFile("searchplugins");
|
||||
if (pluginsDir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException("Couldn't find search engine for identifier: " + identifier);
|
||||
}
|
||||
final File[] files = pluginsDir.listFiles();
|
||||
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 {
|
||||
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
|
||||
* if the search activity is built as part of mozilla-central.
|
||||
* Reads a file from the searchplugins directory in the Gecko jar.
|
||||
*
|
||||
* @param fileName name of the file to read
|
||||
* @return InputStream for file
|
||||
* @param fileName name of the file to read.
|
||||
* @return InputStream for file.
|
||||
*/
|
||||
private InputStream getInputStreamFromJar(String fileName) {
|
||||
private InputStream getInputStreamFromSearchPluginsJar(String fileName) {
|
||||
final Locale locale = Locale.getDefault();
|
||||
|
||||
// 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 fileName name of the file to read
|
||||
* @return URL for jar file
|
||||
* @param locale String representing the Gecko locale (e.g. "en-US").
|
||||
* @param fileName The name of the file to read.
|
||||
* @return URL for jar file.
|
||||
*/
|
||||
private String getSearchPluginsJarURL(String locale, String fileName) {
|
||||
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
|
||||
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_signed_in_as">&fxaccount_status_signed_in_as;</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_sync_server">&fxaccount_status_sync_server;</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_toast">&fxaccount_remove_account_toast;</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 {
|
||||
byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -501,6 +501,22 @@ VARIABLES = {
|
||||
delimiters.
|
||||
""", 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,
|
||||
"""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]
|
||||
self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
|
||||
|
||||
self.assertEqual(objs[3].affected_tiers, {'misc'})
|
||||
|
||||
dirs = [o.dirs for o in objs]
|
||||
self.assertEqual(dirs, [
|
||||
[
|
||||
|
Loading…
Reference in New Issue
Block a user