mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
3aa63e8b3e
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 1101170 - Linux desktop build changes libmozsandbox from shared to static.
|
||||
Bug 1102488 - Changes to Android build system.
|
||||
|
@ -223,6 +223,8 @@ loop.conversation = (function(mozL10n) {
|
||||
* At the moment, it does more than that, these parts need refactoring out.
|
||||
*/
|
||||
var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
@ -230,7 +232,8 @@ loop.conversation = (function(mozL10n) {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
conversationAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationAppStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -302,6 +305,8 @@ loop.conversation = (function(mozL10n) {
|
||||
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
this.play("terminated");
|
||||
|
||||
return (
|
||||
sharedViews.FeedbackView({
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
@ -552,7 +557,8 @@ loop.conversation = (function(mozL10n) {
|
||||
.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore),
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -223,6 +223,8 @@ loop.conversation = (function(mozL10n) {
|
||||
* At the moment, it does more than that, these parts need refactoring out.
|
||||
*/
|
||||
var IncomingConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
@ -230,7 +232,8 @@ loop.conversation = (function(mozL10n) {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
conversationAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationAppStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -302,6 +305,8 @@ loop.conversation = (function(mozL10n) {
|
||||
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
this.play("terminated");
|
||||
|
||||
return (
|
||||
<sharedViews.FeedbackView
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
@ -552,7 +557,8 @@ loop.conversation = (function(mozL10n) {
|
||||
.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore),
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -428,6 +428,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
* the different views that need displaying.
|
||||
*/
|
||||
var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
@ -493,6 +495,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
);
|
||||
}
|
||||
case CALL_STATES.FINISHED: {
|
||||
this.play("terminated");
|
||||
return this._renderFeedbackView();
|
||||
}
|
||||
case CALL_STATES.INIT: {
|
||||
|
@ -428,6 +428,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
* the different views that need displaying.
|
||||
*/
|
||||
var OutgoingConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
@ -493,6 +495,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
);
|
||||
}
|
||||
case CALL_STATES.FINISHED: {
|
||||
this.play("terminated");
|
||||
return this._renderFeedbackView();
|
||||
}
|
||||
case CALL_STATES.INIT: {
|
||||
|
@ -16,8 +16,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* ActiveRoomStore mixin.
|
||||
* @type {Object}
|
||||
@ -140,7 +138,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired,
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
@ -215,6 +215,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* User clicked on the "Leave" button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
@ -257,6 +264,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
cancelCall: this.closeWindow}
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
return sharedViews.FeedbackView({
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
onAfterFeedbackReceived: this.closeWindow}
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
React.DOM.div({className: "room-conversation-wrapper"},
|
||||
@ -273,7 +286,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
video: {enabled: !this.state.videoMuted, visible: true},
|
||||
audio: {enabled: !this.state.audioMuted, visible: true},
|
||||
publishStream: this.publishStream,
|
||||
hangup: noop})
|
||||
hangup: this.leaveRoom})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -16,8 +16,6 @@ loop.roomViews = (function(mozL10n) {
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* ActiveRoomStore mixin.
|
||||
* @type {Object}
|
||||
@ -140,7 +138,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
feedbackStore:
|
||||
React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired,
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
@ -215,6 +215,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* User clicked on the "Leave" button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
@ -257,6 +264,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
cancelCall={this.closeWindow}
|
||||
/>;
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
return <sharedViews.FeedbackView
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
onAfterFeedbackReceived={this.closeWindow}
|
||||
/>;
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
@ -273,7 +286,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
video={{enabled: !this.state.videoMuted, visible: true}}
|
||||
audio={{enabled: !this.state.audioMuted, visible: true}}
|
||||
publishStream={this.publishStream}
|
||||
hangup={noop} />
|
||||
hangup={this.leaveRoom} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -744,11 +744,8 @@ html, .fx-embedded, #main,
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the hangup button for room conversations.
|
||||
*/
|
||||
.room-conversation .conversation-toolbar .btn-hangup-entry {
|
||||
display: none;
|
||||
.fx-embedded .room-conversation .conversation-toolbar .btn-hangup {
|
||||
background-image: url("../img/icons-16x16.svg#leave");
|
||||
}
|
||||
|
||||
.room-invitation-overlay {
|
||||
|
@ -118,6 +118,10 @@ use[id$="-red"] {
|
||||
<polyline fill="#FFFFFF" points="9.25,7.542 8.75,7.542 8.75,11.542 9.25,11.542 "/>
|
||||
<rect x="6.75" y="7.542" fill="#FFFFFF" width="0.5" height="4"/>
|
||||
</g>
|
||||
<g id="leave-shape">
|
||||
<polygon fill="#FFFFFF" points="2.08,11.52 2.08,4 8,4 8,2.24 0.32,2.24 0.32,13.28 8,13.28 8,11.52"/>
|
||||
<polygon fill="#FFFFFF" points="15.66816,7.77344 9.6,2.27456 9.6,5.6 3.68,5.6 3.68,9.92 9.6,9.92 9.6,13.27232"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use id="audio" xlink:href="#audio-shape"/>
|
||||
<use id="audio-hover" xlink:href="#audio-shape"/>
|
||||
@ -137,6 +141,7 @@ use[id$="-red"] {
|
||||
<use id="history" xlink:href="#history-shape"/>
|
||||
<use id="history-hover" xlink:href="#history-shape"/>
|
||||
<use id="history-active" xlink:href="#history-shape"/>
|
||||
<use id="leave" xlink:href="#leave-shape"/>
|
||||
<use id="precall" xlink:href="#precall-shape"/>
|
||||
<use id="precall-hover" xlink:href="#precall-shape"/>
|
||||
<use id="precall-active" xlink:href="#precall-shape"/>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
@ -37,7 +37,9 @@ loop.store.ActiveRoomStore = (function() {
|
||||
// There was an issue with the room
|
||||
FAILED: "room-failed",
|
||||
// The room is full
|
||||
FULL: "room-full"
|
||||
FULL: "room-full",
|
||||
// The room conversation has ended
|
||||
ENDED: "room-ended"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -424,7 +426,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
roomState: nextState ? nextState : ROOM_STATES.READY
|
||||
roomState: nextState ? nextState : ROOM_STATES.ENDED
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -222,7 +222,7 @@ loop.shared.views.FeedbackView = (function(l10n) {
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({displayName: 'FeedbackView',
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
@ -242,10 +242,6 @@ loop.shared.views.FeedbackView = (function(l10n) {
|
||||
this.listenTo(this.props.feedbackStore, "change", this._onStoreStateChanged);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.feedbackStore);
|
||||
},
|
||||
|
@ -222,7 +222,7 @@ loop.shared.views.FeedbackView = (function(l10n) {
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
@ -242,10 +242,6 @@ loop.shared.views.FeedbackView = (function(l10n) {
|
||||
this.listenTo(this.props.feedbackStore, "change", this._onStoreStateChanged);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.feedbackStore);
|
||||
},
|
||||
|
@ -238,17 +238,18 @@ loop.shared.mixins = (function() {
|
||||
|
||||
function isConnectedToRoom(state) {
|
||||
return state === ROOM_STATES.HAS_PARTICIPANTS ||
|
||||
state === ROOM_STATES.SESSION_CONNECTED;
|
||||
state === ROOM_STATES.SESSION_CONNECTED;
|
||||
}
|
||||
|
||||
function notConnectedToRoom(state) {
|
||||
// Failed and full are states that the user is not
|
||||
// really connected o the room, but we don't want to
|
||||
// really connected to the room, but we don't want to
|
||||
// catch those here, as they get their own sounds.
|
||||
return state === ROOM_STATES.INIT ||
|
||||
state === ROOM_STATES.GATHER ||
|
||||
state === ROOM_STATES.READY ||
|
||||
state === ROOM_STATES.JOINED;
|
||||
state === ROOM_STATES.GATHER ||
|
||||
state === ROOM_STATES.READY ||
|
||||
state === ROOM_STATES.JOINED ||
|
||||
state === ROOM_STATES.ENDED;
|
||||
}
|
||||
|
||||
// Joining the room.
|
||||
|
@ -95,6 +95,13 @@ loop.shared.models = (function(l10n) {
|
||||
if (selectedCallType) {
|
||||
this.set("selectedCallType", selectedCallType);
|
||||
}
|
||||
this.trigger("call:outgoing:get-media-privs");
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to indicate that media privileges have been accepted.
|
||||
*/
|
||||
gotMediaPrivs: function() {
|
||||
this.trigger("call:outgoing:setup");
|
||||
},
|
||||
|
||||
|
@ -52,13 +52,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
return mozL10n.get("rooms_unavailable_notification_message");
|
||||
default:
|
||||
return mozL10n.get("status_error");
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_renderContent: function() {
|
||||
switch(this.props.roomState) {
|
||||
case ROOM_STATES.INIT:
|
||||
case ROOM_STATES.READY: {
|
||||
case ROOM_STATES.READY:
|
||||
case ROOM_STATES.ENDED: {
|
||||
// XXX: In ENDED state, we should rather display the feedback form.
|
||||
return (
|
||||
React.DOM.button({className: "btn btn-join btn-info",
|
||||
onClick: this.props.joinRoom},
|
||||
@ -219,13 +221,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Watches for when we transition from READY to JOINED room state, so we can
|
||||
* request user media access.
|
||||
* Watches for when we transition to JOINED room state, so we can request
|
||||
* user media access.
|
||||
*
|
||||
* @param {Object} nextProps (Unused)
|
||||
* @param {Object} nextState Next state object.
|
||||
*/
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
if (this.state.roomState === ROOM_STATES.READY &&
|
||||
if (this.state.roomState !== ROOM_STATES.JOINED &&
|
||||
nextState.roomState === ROOM_STATES.JOINED) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
|
@ -52,13 +52,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
return mozL10n.get("rooms_unavailable_notification_message");
|
||||
default:
|
||||
return mozL10n.get("status_error");
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_renderContent: function() {
|
||||
switch(this.props.roomState) {
|
||||
case ROOM_STATES.INIT:
|
||||
case ROOM_STATES.READY: {
|
||||
case ROOM_STATES.READY:
|
||||
case ROOM_STATES.ENDED: {
|
||||
// XXX: In ENDED state, we should rather display the feedback form.
|
||||
return (
|
||||
<button className="btn btn-join btn-info"
|
||||
onClick={this.props.joinRoom}>
|
||||
@ -219,13 +221,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Watches for when we transition from READY to JOINED room state, so we can
|
||||
* request user media access.
|
||||
* Watches for when we transition to JOINED room state, so we can request
|
||||
* user media access.
|
||||
*
|
||||
* @param {Object} nextProps (Unused)
|
||||
* @param {Object} nextState Next state object.
|
||||
*/
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
if (this.state.roomState === ROOM_STATES.READY &&
|
||||
if (this.state.roomState !== ROOM_STATES.JOINED &&
|
||||
nextState.roomState === ROOM_STATES.JOINED) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
|
@ -270,7 +270,80 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A view for when conversations are pending, displays any messages
|
||||
* and an option cancel button.
|
||||
*/
|
||||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||
propTypes: {
|
||||
callState: React.PropTypes.string.isRequired,
|
||||
// If not supplied, the cancel button is not displayed.
|
||||
cancelCallback: React.PropTypes.func
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cancelButtonClasses = React.addons.classSet({
|
||||
btn: true,
|
||||
"btn-large": true,
|
||||
"btn-cancel": true,
|
||||
hide: !this.props.cancelCallback
|
||||
});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "container"},
|
||||
React.DOM.div({className: "container-box"},
|
||||
React.DOM.header({className: "pending-header header-box"},
|
||||
ConversationBranding(null)
|
||||
),
|
||||
|
||||
React.DOM.div({id: "cameraPreview"}),
|
||||
|
||||
React.DOM.div({id: "messages"}),
|
||||
|
||||
React.DOM.p({className: "standalone-btn-label"},
|
||||
this.props.callState
|
||||
),
|
||||
|
||||
React.DOM.div({className: "btn-pending-cancel-group btn-group"},
|
||||
React.DOM.div({className: "flex-padding-1"}),
|
||||
React.DOM.button({className: cancelButtonClasses,
|
||||
onClick: this.props.cancelCallback},
|
||||
React.DOM.span({className: "standalone-call-btn-text"},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
)
|
||||
),
|
||||
React.DOM.div({className: "flex-padding-1"})
|
||||
)
|
||||
),
|
||||
ConversationFooter(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* View displayed whilst the get user media prompt is being displayed. Indicates
|
||||
* to the user to accept the prompt.
|
||||
*/
|
||||
var GumPromptConversationView = React.createClass({displayName: 'GumPromptConversationView',
|
||||
render: function() {
|
||||
var callState = mozL10n.get("call_progress_getting_media_description", {
|
||||
clientShortname: mozL10n.get("clientShortname2")
|
||||
});
|
||||
document.title = mozL10n.get("standalone_title_with_status", {
|
||||
clientShortname: mozL10n.get("clientShortname2"),
|
||||
currentStatus: mozL10n.get("call_progress_getting_media_title")
|
||||
});
|
||||
|
||||
return PendingConversationView({callState: callState});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* View displayed waiting for a call to be connected. Updates the display
|
||||
* once the websocket shows that the callee is being alerted.
|
||||
*/
|
||||
var WaitingConversationView = React.createClass({displayName: 'WaitingConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
getInitialState: function() {
|
||||
@ -306,33 +379,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
currentStatus: mozL10n.get(callStateStringEntityName)});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "container"},
|
||||
React.DOM.div({className: "container-box"},
|
||||
React.DOM.header({className: "pending-header header-box"},
|
||||
ConversationBranding(null)
|
||||
),
|
||||
|
||||
React.DOM.div({id: "cameraPreview"}),
|
||||
|
||||
React.DOM.div({id: "messages"}),
|
||||
|
||||
React.DOM.p({className: "standalone-btn-label"},
|
||||
callState
|
||||
),
|
||||
|
||||
React.DOM.div({className: "btn-pending-cancel-group btn-group"},
|
||||
React.DOM.div({className: "flex-padding-1"}),
|
||||
React.DOM.button({className: "btn btn-large btn-cancel",
|
||||
onClick: this._cancelOutgoingCall},
|
||||
React.DOM.span({className: "standalone-call-btn-text"},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
)
|
||||
),
|
||||
React.DOM.div({className: "flex-padding-1"})
|
||||
)
|
||||
),
|
||||
ConversationFooter(null)
|
||||
PendingConversationView({
|
||||
callState: callState,
|
||||
cancelCallback: this._cancelOutgoingCall}
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -458,15 +509,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
startCall: function(callType) {
|
||||
return function() {
|
||||
multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
|
||||
function(localStream) {
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
}.bind(this),
|
||||
function(errorCode) {
|
||||
multiplexGum.reset();
|
||||
}.bind(this)
|
||||
);
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
@ -627,6 +671,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.conversation.on("call:outgoing", this.startCall, this);
|
||||
this.props.conversation.on("call:outgoing:get-media-privs", this.getMediaPrivs, this);
|
||||
this.props.conversation.on("call:outgoing:setup", this.setupOutgoingCall, this);
|
||||
this.props.conversation.on("change:publishedStream", this._checkConnected, this);
|
||||
this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
|
||||
@ -674,8 +719,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
)
|
||||
);
|
||||
}
|
||||
case "gumPrompt": {
|
||||
return GumPromptConversationView(null);
|
||||
}
|
||||
case "pending": {
|
||||
return PendingConversationView({websocket: this._websocket});
|
||||
return WaitingConversationView({websocket: this._websocket});
|
||||
}
|
||||
case "connected": {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
@ -774,6 +822,22 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the user for the media privileges, handling the result appropriately.
|
||||
*/
|
||||
getMediaPrivs: function() {
|
||||
this.setState({callStatus: "gumPrompt"});
|
||||
multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
|
||||
function(localStream) {
|
||||
this.props.conversation.gotMediaPrivs();
|
||||
}.bind(this),
|
||||
function(errorCode) {
|
||||
multiplexGum.reset();
|
||||
this.setState({callStatus: "failure"});
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actually starts the call.
|
||||
*/
|
||||
@ -866,6 +930,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Handles ending a call by resetting the view to the start state.
|
||||
*/
|
||||
_endCall: function() {
|
||||
multiplexGum.reset();
|
||||
|
||||
if (this.state.callStatus !== "failure") {
|
||||
this.setState({callStatus: "end"});
|
||||
}
|
||||
@ -1050,6 +1116,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
return {
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
GumPromptConversationView: GumPromptConversationView,
|
||||
WaitingConversationView: WaitingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
FailedConversationView: FailedConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView,
|
||||
|
@ -270,7 +270,80 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A view for when conversations are pending, displays any messages
|
||||
* and an option cancel button.
|
||||
*/
|
||||
var PendingConversationView = React.createClass({
|
||||
propTypes: {
|
||||
callState: React.PropTypes.string.isRequired,
|
||||
// If not supplied, the cancel button is not displayed.
|
||||
cancelCallback: React.PropTypes.func
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cancelButtonClasses = React.addons.classSet({
|
||||
btn: true,
|
||||
"btn-large": true,
|
||||
"btn-cancel": true,
|
||||
hide: !this.props.cancelCallback
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="container-box">
|
||||
<header className="pending-header header-box">
|
||||
<ConversationBranding />
|
||||
</header>
|
||||
|
||||
<div id="cameraPreview" />
|
||||
|
||||
<div id="messages" />
|
||||
|
||||
<p className="standalone-btn-label">
|
||||
{this.props.callState}
|
||||
</p>
|
||||
|
||||
<div className="btn-pending-cancel-group btn-group">
|
||||
<div className="flex-padding-1" />
|
||||
<button className={cancelButtonClasses}
|
||||
onClick={this.props.cancelCallback} >
|
||||
<span className="standalone-call-btn-text">
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex-padding-1" />
|
||||
</div>
|
||||
</div>
|
||||
<ConversationFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* View displayed whilst the get user media prompt is being displayed. Indicates
|
||||
* to the user to accept the prompt.
|
||||
*/
|
||||
var GumPromptConversationView = React.createClass({
|
||||
render: function() {
|
||||
var callState = mozL10n.get("call_progress_getting_media_description", {
|
||||
clientShortname: mozL10n.get("clientShortname2")
|
||||
});
|
||||
document.title = mozL10n.get("standalone_title_with_status", {
|
||||
clientShortname: mozL10n.get("clientShortname2"),
|
||||
currentStatus: mozL10n.get("call_progress_getting_media_title")
|
||||
});
|
||||
|
||||
return <PendingConversationView callState={callState}/>;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* View displayed waiting for a call to be connected. Updates the display
|
||||
* once the websocket shows that the callee is being alerted.
|
||||
*/
|
||||
var WaitingConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
getInitialState: function() {
|
||||
@ -306,34 +379,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
currentStatus: mozL10n.get(callStateStringEntityName)});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="container-box">
|
||||
<header className="pending-header header-box">
|
||||
<ConversationBranding />
|
||||
</header>
|
||||
|
||||
<div id="cameraPreview" />
|
||||
|
||||
<div id="messages" />
|
||||
|
||||
<p className="standalone-btn-label">
|
||||
{callState}
|
||||
</p>
|
||||
|
||||
<div className="btn-pending-cancel-group btn-group">
|
||||
<div className="flex-padding-1" />
|
||||
<button className="btn btn-large btn-cancel"
|
||||
onClick={this._cancelOutgoingCall} >
|
||||
<span className="standalone-call-btn-text">
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex-padding-1" />
|
||||
</div>
|
||||
</div>
|
||||
<ConversationFooter />
|
||||
</div>
|
||||
<PendingConversationView
|
||||
callState={callState}
|
||||
cancelCallback={this._cancelOutgoingCall}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -458,15 +509,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
startCall: function(callType) {
|
||||
return function() {
|
||||
multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
|
||||
function(localStream) {
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
}.bind(this),
|
||||
function(errorCode) {
|
||||
multiplexGum.reset();
|
||||
}.bind(this)
|
||||
);
|
||||
this.props.conversation.setupOutgoingCall(callType);
|
||||
this.setState({disableCallButton: true});
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
@ -627,6 +671,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.conversation.on("call:outgoing", this.startCall, this);
|
||||
this.props.conversation.on("call:outgoing:get-media-privs", this.getMediaPrivs, this);
|
||||
this.props.conversation.on("call:outgoing:setup", this.setupOutgoingCall, this);
|
||||
this.props.conversation.on("change:publishedStream", this._checkConnected, this);
|
||||
this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
|
||||
@ -674,8 +719,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "gumPrompt": {
|
||||
return <GumPromptConversationView />;
|
||||
}
|
||||
case "pending": {
|
||||
return <PendingConversationView websocket={this._websocket} />;
|
||||
return <WaitingConversationView websocket={this._websocket} />;
|
||||
}
|
||||
case "connected": {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
@ -774,6 +822,22 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the user for the media privileges, handling the result appropriately.
|
||||
*/
|
||||
getMediaPrivs: function() {
|
||||
this.setState({callStatus: "gumPrompt"});
|
||||
multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
|
||||
function(localStream) {
|
||||
this.props.conversation.gotMediaPrivs();
|
||||
}.bind(this),
|
||||
function(errorCode) {
|
||||
multiplexGum.reset();
|
||||
this.setState({callStatus: "failure"});
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actually starts the call.
|
||||
*/
|
||||
@ -866,6 +930,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Handles ending a call by resetting the view to the start state.
|
||||
*/
|
||||
_endCall: function() {
|
||||
multiplexGum.reset();
|
||||
|
||||
if (this.state.callStatus !== "failure") {
|
||||
this.setState({callStatus: "end"});
|
||||
}
|
||||
@ -1050,6 +1116,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
return {
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
GumPromptConversationView: GumPromptConversationView,
|
||||
WaitingConversationView: WaitingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
FailedConversationView: FailedConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView,
|
||||
|
@ -58,6 +58,8 @@ vendor_alttext={{vendorShortname}} logo
|
||||
|
||||
## LOCALIZATION NOTE (call_url_creation_date_label): Example output: (from May 26, 2014)
|
||||
call_url_creation_date_label=(from {{call_url_creation_date}})
|
||||
call_progress_getting_media_description={{clientShortname}} requires access to your camera and microphone.
|
||||
call_progress_getting_media_title=Waiting for media…
|
||||
call_progress_connecting_description=Connecting…
|
||||
call_progress_ringing_description=Ringing…
|
||||
fxos_app_needed=Please install the {{fxosAppName}} app from the Firefox Marketplace.
|
||||
|
@ -510,6 +510,22 @@ describe("loop.conversationViews", function () {
|
||||
loop.shared.views.FeedbackView);
|
||||
});
|
||||
|
||||
it("should play the terminated sound when the call state is 'finished'",
|
||||
function() {
|
||||
var fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
pause: sinon.spy(),
|
||||
removeAttribute: sinon.spy()
|
||||
};
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
store.set({callState: CALL_STATES.FINISHED});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
sinon.assert.calledOnce(fakeAudio.play);
|
||||
});
|
||||
|
||||
it("should update the rendered views when the state is changed.",
|
||||
function() {
|
||||
store.set({
|
||||
|
@ -199,7 +199,10 @@ describe("loop.roomViews", function () {
|
||||
return TestUtils.renderIntoDocument(
|
||||
new loop.roomViews.DesktopRoomConversationView({
|
||||
dispatcher: dispatcher,
|
||||
roomStore: roomStore
|
||||
roomStore: roomStore,
|
||||
feedbackStore: new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: {}
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
@ -316,6 +319,16 @@ describe("loop.roomViews", function () {
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomConversationView);
|
||||
});
|
||||
|
||||
it("should render the FeedbackView if roomState is `ENDED`",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.shared.views.FeedbackView);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -574,10 +574,10 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
"fakeToken", "1627384950");
|
||||
});
|
||||
|
||||
it("should set the state to ready", function() {
|
||||
it("should set the state to ENDED", function() {
|
||||
store.windowUnload();
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.ENDED);
|
||||
});
|
||||
});
|
||||
|
||||
@ -619,10 +619,10 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
"fakeToken", "1627384950");
|
||||
});
|
||||
|
||||
it("should set the state to ready", function() {
|
||||
it("should set the state to ENDED", function() {
|
||||
store.leaveRoom();
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.ENDED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -16,28 +16,15 @@ var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
|
||||
describe("loop.shared.views.FeedbackView", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox, comp, dispatcher, feedbackStore, fakeAudioXHR, fakeFeedbackClient;
|
||||
var sandbox, comp, dispatcher, fakeFeedbackClient, feedbackStore;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
fakeAudioXHR = {
|
||||
open: sinon.spy(),
|
||||
send: function() {},
|
||||
abort: function() {},
|
||||
getResponseHeader: function(header) {
|
||||
if (header === "Content-Type")
|
||||
return "audio/ogg";
|
||||
},
|
||||
responseType: null,
|
||||
response: new ArrayBuffer(10),
|
||||
onload: null
|
||||
};
|
||||
dispatcher = new loop.Dispatcher();
|
||||
fakeFeedbackClient = {send: sandbox.stub()};
|
||||
feedbackStore = new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: fakeFeedbackClient
|
||||
});
|
||||
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
|
||||
comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
|
||||
feedbackStore: feedbackStore
|
||||
}));
|
||||
|
@ -91,12 +91,22 @@ describe("loop.shared.models", function() {
|
||||
expect(conversation.get("selectedCallType")).eql("audio-video");
|
||||
});
|
||||
|
||||
it("should trigger a `call:outgoing:get-media-privs` event", function(done) {
|
||||
conversation.once("call:outgoing:get-media-privs", function() {
|
||||
done();
|
||||
});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
});
|
||||
});
|
||||
|
||||
describe("#gotMediaPrivs", function() {
|
||||
it("should trigger a `call:outgoing:setup` event", function(done) {
|
||||
conversation.once("call:outgoing:setup", function() {
|
||||
done();
|
||||
});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
conversation.gotMediaPrivs();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -38,27 +38,42 @@ describe("loop.standaloneRoomViews", function() {
|
||||
}));
|
||||
}
|
||||
|
||||
function expectActionDispatched(view) {
|
||||
sinon.assert.calledOnce(dispatch);
|
||||
sinon.assert.calledWithExactly(dispatch,
|
||||
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
||||
sinon.assert.calledWithExactly(dispatch, sinon.match(function(value) {
|
||||
return value.getLocalElementFunc() ===
|
||||
view.getDOMNode().querySelector(".local");
|
||||
}));
|
||||
sinon.assert.calledWithExactly(dispatch, sinon.match(function(value) {
|
||||
return value.getRemoteElementFunc() ===
|
||||
view.getDOMNode().querySelector(".remote");
|
||||
}));
|
||||
}
|
||||
|
||||
describe("#componentWillUpdate", function() {
|
||||
it("dispatch an `SetupStreamElements` action on room joined", function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
|
||||
var view = mountTestComponent();
|
||||
it("should dispatch a `SetupStreamElements` action on room joined",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
|
||||
var view = mountTestComponent();
|
||||
|
||||
sinon.assert.notCalled(dispatch);
|
||||
sinon.assert.notCalled(dispatch);
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
|
||||
|
||||
sinon.assert.calledOnce(dispatch);
|
||||
sinon.assert.calledWithExactly(dispatch,
|
||||
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
||||
sinon.assert.calledWithExactly(dispatch, sinon.match(function(value) {
|
||||
return value.getLocalElementFunc() ===
|
||||
view.getDOMNode().querySelector(".local");
|
||||
}));
|
||||
sinon.assert.calledWithExactly(dispatch, sinon.match(function(value) {
|
||||
return value.getRemoteElementFunc() ===
|
||||
view.getDOMNode().querySelector(".remote");
|
||||
}));
|
||||
});
|
||||
expectActionDispatched(view);
|
||||
});
|
||||
|
||||
it("should dispatch a `SetupStreamElements` action on room rejoined",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
|
||||
var view = mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
|
||||
|
||||
expectActionDispatched(view);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#publishStream", function() {
|
||||
|
@ -369,6 +369,16 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
describe("session:ended", function() {
|
||||
it("should call multiplexGum.reset", function() {
|
||||
var multiplexGum = new standaloneMedia._MultiplexGum();
|
||||
standaloneMedia.setSingleton(multiplexGum);
|
||||
sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
|
||||
|
||||
conversation.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(multiplexGum.reset);
|
||||
});
|
||||
|
||||
it("should display the StartConversationView", function() {
|
||||
conversation.trigger("session:ended");
|
||||
|
||||
@ -474,14 +484,14 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
it("should display the FailedConversationView", function() {
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.FailedConversationView);
|
||||
});
|
||||
|
||||
it("should display an error", function() {
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
});
|
||||
@ -494,7 +504,8 @@ describe("loop.webapp", function() {
|
||||
|
||||
it("should call requestCallInfo on the client",
|
||||
function() {
|
||||
conversation.setupOutgoingCall("audio-video");
|
||||
conversation.set("selectedCallType", "audio-video");
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(client.requestCallInfo);
|
||||
sinon.assert.calledWith(client.requestCallInfo, "fakeToken",
|
||||
@ -506,7 +517,7 @@ describe("loop.webapp", function() {
|
||||
function() {
|
||||
client.requestCallInfo.callsArgWith(2, {errno: 105});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.CallUrlExpiredView);
|
||||
@ -516,7 +527,7 @@ describe("loop.webapp", function() {
|
||||
function() {
|
||||
client.requestCallInfo.callsArgWith(2, {errno: 104});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(ocView,
|
||||
loop.webapp.FailedConversationView);
|
||||
@ -525,7 +536,7 @@ describe("loop.webapp", function() {
|
||||
it("should notify the user on any other error", function() {
|
||||
client.requestCallInfo.callsArgWith(2, {errno: 104});
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
});
|
||||
@ -534,7 +545,7 @@ describe("loop.webapp", function() {
|
||||
"are successfully received", function() {
|
||||
client.requestCallInfo.callsArgWith(2, null, fakeSessionData);
|
||||
|
||||
conversation.setupOutgoingCall();
|
||||
ocView.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(conversation.outgoing);
|
||||
sinon.assert.calledWithExactly(conversation.outgoing, fakeSessionData);
|
||||
@ -542,6 +553,52 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMediaPrivs", function() {
|
||||
var multiplexGum;
|
||||
|
||||
beforeEach(function() {
|
||||
multiplexGum = new standaloneMedia._MultiplexGum();
|
||||
standaloneMedia.setSingleton(multiplexGum);
|
||||
sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
|
||||
|
||||
sandbox.stub(conversation, "gotMediaPrivs");
|
||||
});
|
||||
|
||||
it("should call getPermsAndCacheMedia", function() {
|
||||
conversation.trigger("call:outgoing:get-media-privs");
|
||||
|
||||
sinon.assert.calledOnce(stubGetPermsAndCacheMedia);
|
||||
});
|
||||
|
||||
it("should call gotMediaPrevs on the model when successful", function() {
|
||||
stubGetPermsAndCacheMedia.callsArgWith(1, {});
|
||||
|
||||
conversation.trigger("call:outgoing:get-media-privs");
|
||||
|
||||
sinon.assert.calledOnce(conversation.gotMediaPrivs);
|
||||
});
|
||||
|
||||
it("should call multiplexGum.reset when getPermsAndCacheMedia fails",
|
||||
function() {
|
||||
stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
|
||||
|
||||
conversation.trigger("call:outgoing:get-media-privs");
|
||||
|
||||
sinon.assert.calledOnce(multiplexGum.reset);
|
||||
});
|
||||
|
||||
it("should set state to `failure` when getPermsAndCacheMedia fails",
|
||||
function() {
|
||||
stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
|
||||
|
||||
conversation.trigger("call:outgoing:get-media-privs");
|
||||
|
||||
expect(ocView.state.callStatus).eql("failure");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe("FailedConversationView", function() {
|
||||
@ -693,7 +750,7 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("PendingConversationView", function() {
|
||||
describe("WaitingConversationView", function() {
|
||||
var view, websocket, fakeAudio;
|
||||
|
||||
beforeEach(function() {
|
||||
@ -713,7 +770,7 @@ describe("loop.webapp", function() {
|
||||
sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
|
||||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.PendingConversationView({
|
||||
loop.webapp.WaitingConversationView({
|
||||
websocket: websocket
|
||||
})
|
||||
);
|
||||
@ -802,27 +859,8 @@ describe("loop.webapp", function() {
|
||||
client: standaloneClientStub
|
||||
})
|
||||
);
|
||||
|
||||
// default to succeeding with a null local media object
|
||||
stubGetPermsAndCacheMedia.callsArgWith(1, {});
|
||||
});
|
||||
|
||||
it("should fire multiplexGum.reset when getPermsAndCacheMedia calls" +
|
||||
" back an error",
|
||||
function() {
|
||||
var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
|
||||
var multiplexGum = new standaloneMedia._MultiplexGum();
|
||||
standaloneMedia.setSingleton(multiplexGum);
|
||||
sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
|
||||
stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".btn-accept");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(multiplexGum.reset);
|
||||
sinon.assert.calledWithExactly(multiplexGum.reset);
|
||||
});
|
||||
|
||||
it("should start the audio-video conversation establishment process",
|
||||
function() {
|
||||
var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
|
||||
@ -1057,22 +1095,6 @@ describe("loop.webapp", function() {
|
||||
it("should render a FeedbackView", function() {
|
||||
TestUtils.findRenderedComponentWithType(view, sharedViews.FeedbackView);
|
||||
});
|
||||
|
||||
describe("#componentDidMount", function() {
|
||||
|
||||
it("should play a terminating sound, once", function() {
|
||||
fakeAudioXHR.onload();
|
||||
|
||||
sinon.assert.called(fakeAudioXHR.open);
|
||||
sinon.assert.calledWithExactly(
|
||||
fakeAudioXHR.open, "GET", "shared/sounds/terminated.ogg", true);
|
||||
|
||||
sinon.assert.calledOnce(fakeAudio.play);
|
||||
expect(fakeAudio.loop).to.not.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("PromoteFirefoxView", function() {
|
||||
|
@ -49,12 +49,14 @@ var fakeRooms = [
|
||||
*/
|
||||
navigator.mozLoop = {
|
||||
ensureRegistered: function() {},
|
||||
getAudioBlob: function(){},
|
||||
getLoopPref: function(pref) {
|
||||
// Ensure UI for rooms is displayed in the showcase.
|
||||
if (pref === "rooms.enabled") {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
setLoopPref: function(){},
|
||||
releaseCallData: function() {},
|
||||
copyString: function() {},
|
||||
contacts: {
|
||||
|
@ -28,7 +28,8 @@
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var GumPromptConversationView = loop.webapp.GumPromptConversationView;
|
||||
var WaitingConversationView = loop.webapp.WaitingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var FailedConversationView = loop.webapp.FailedConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
@ -128,7 +129,7 @@
|
||||
"audio", "audio-hover", "audio-active", "block",
|
||||
"block-red", "block-hover", "block-active", "contacts", "contacts-hover",
|
||||
"contacts-active", "copy", "checkmark", "google", "google-hover",
|
||||
"google-active", "history", "history-hover", "history-active",
|
||||
"google-active", "history", "history-hover", "history-active", "leave",
|
||||
"precall", "precall-hover", "precall-active", "settings", "settings-hover",
|
||||
"settings-active", "tag", "tag-hover", "tag-active", "trash", "unblock",
|
||||
"unblock-hover", "unblock-active", "video", "video-hover", "video-active"
|
||||
@ -326,16 +327,24 @@
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "PendingConversationView"},
|
||||
Example({summary: "Pending conversation view (connecting)", dashed: "true"},
|
||||
Section({name: "GumPromptConversationView"},
|
||||
Example({summary: "Gum Prompt conversation view", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
PendingConversationView({websocket: mockWebSocket,
|
||||
GumPromptConversationView(null)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "WaitingConversationView"},
|
||||
Example({summary: "Waiting conversation view (connecting)", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
WaitingConversationView({websocket: mockWebSocket,
|
||||
dispatcher: dispatcher})
|
||||
)
|
||||
),
|
||||
Example({summary: "Pending conversation view (ringing)", dashed: "true"},
|
||||
Example({summary: "Waiting conversation view (ringing)", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
PendingConversationView({websocket: mockWebSocket,
|
||||
WaitingConversationView({websocket: mockWebSocket,
|
||||
dispatcher: dispatcher,
|
||||
callState: "ringing"})
|
||||
)
|
||||
|
@ -28,7 +28,8 @@
|
||||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var GumPromptConversationView = loop.webapp.GumPromptConversationView;
|
||||
var WaitingConversationView = loop.webapp.WaitingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
var FailedConversationView = loop.webapp.FailedConversationView;
|
||||
var EndedConversationView = loop.webapp.EndedConversationView;
|
||||
@ -128,7 +129,7 @@
|
||||
"audio", "audio-hover", "audio-active", "block",
|
||||
"block-red", "block-hover", "block-active", "contacts", "contacts-hover",
|
||||
"contacts-active", "copy", "checkmark", "google", "google-hover",
|
||||
"google-active", "history", "history-hover", "history-active",
|
||||
"google-active", "history", "history-hover", "history-active", "leave",
|
||||
"precall", "precall-hover", "precall-active", "settings", "settings-hover",
|
||||
"settings-active", "tag", "tag-hover", "tag-active", "trash", "unblock",
|
||||
"unblock-hover", "unblock-active", "video", "video-hover", "video-active"
|
||||
@ -326,16 +327,24 @@
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section name="PendingConversationView">
|
||||
<Example summary="Pending conversation view (connecting)" dashed="true">
|
||||
<Section name="GumPromptConversationView">
|
||||
<Example summary="Gum Prompt conversation view" dashed="true">
|
||||
<div className="standalone">
|
||||
<PendingConversationView websocket={mockWebSocket}
|
||||
<GumPromptConversationView />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="WaitingConversationView">
|
||||
<Example summary="Waiting conversation view (connecting)" dashed="true">
|
||||
<div className="standalone">
|
||||
<WaitingConversationView websocket={mockWebSocket}
|
||||
dispatcher={dispatcher} />
|
||||
</div>
|
||||
</Example>
|
||||
<Example summary="Pending conversation view (ringing)" dashed="true">
|
||||
<Example summary="Waiting conversation view (ringing)" dashed="true">
|
||||
<div className="standalone">
|
||||
<PendingConversationView websocket={mockWebSocket}
|
||||
<WaitingConversationView websocket={mockWebSocket}
|
||||
dispatcher={dispatcher}
|
||||
callState="ringing"/>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s && os == 'linux' # bug 1104908
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_content_stylesheet.html
|
||||
|
@ -6,10 +6,11 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A collection of constants that pertain to the build and runtime state of the
|
||||
* application. Typically these are sourced from build-time definitions (see
|
||||
@ -19,7 +20,9 @@ import android.os.Build;
|
||||
* See also SysInfo.java, which includes some of the values available from
|
||||
* nsSystemInfo inside Gecko.
|
||||
*/
|
||||
@RobocopTarget
|
||||
// Normally, we'd annotate with @RobocopTarget. Since AppConstants is compiled
|
||||
// before RobocopTarget, we instead add o.m.g.AppConstants directly to the
|
||||
// Proguard configuration.
|
||||
public class AppConstants {
|
||||
public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
|
||||
public static final String MANGLED_ANDROID_PACKAGE_NAME = "@MANGLED_ANDROID_PACKAGE_NAME@";
|
||||
@ -262,4 +265,11 @@ public class AppConstants {
|
||||
//#else
|
||||
false;
|
||||
//#endif
|
||||
|
||||
public static final boolean MOZ_LINKER_EXTRACT =
|
||||
//#ifdef MOZ_LINKER_EXTRACT
|
||||
true;
|
||||
//#else
|
||||
false;
|
||||
//#endif
|
||||
}
|
||||
|
@ -88,18 +88,19 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
||||
}
|
||||
|
||||
private String initGeckoEnvironment() {
|
||||
// At some point while loading the gecko libs our default locale gets set
|
||||
// so just save it to locale here and reset it as default after the join
|
||||
Locale locale = Locale.getDefault();
|
||||
final Locale locale = Locale.getDefault();
|
||||
|
||||
final Context context = GeckoAppShell.getContext();
|
||||
final Resources res = context.getResources();
|
||||
if (locale.toString().equalsIgnoreCase("zh_hk")) {
|
||||
locale = Locale.TRADITIONAL_CHINESE;
|
||||
Locale.setDefault(locale);
|
||||
final Locale mappedLocale = Locale.TRADITIONAL_CHINESE;
|
||||
Locale.setDefault(mappedLocale);
|
||||
Configuration config = res.getConfiguration();
|
||||
config.locale = mappedLocale;
|
||||
res.updateConfiguration(config, null);
|
||||
}
|
||||
|
||||
Context context = GeckoAppShell.getContext();
|
||||
String resourcePath = "";
|
||||
Resources res = null;
|
||||
String[] pluginDirs = null;
|
||||
try {
|
||||
pluginDirs = GeckoAppShell.getPluginDirectories();
|
||||
@ -108,7 +109,6 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
||||
}
|
||||
|
||||
resourcePath = context.getPackageResourcePath();
|
||||
res = context.getResources();
|
||||
GeckoLoader.setupGeckoEnvironment(context, pluginDirs, context.getFilesDir().getPath());
|
||||
|
||||
GeckoLoader.loadSQLiteLibs(context, resourcePath);
|
||||
@ -116,12 +116,6 @@ public class GeckoThread extends Thread implements GeckoEventListener {
|
||||
GeckoLoader.loadGeckoLibs(context, resourcePath);
|
||||
GeckoJavaSampler.setLibsLoaded();
|
||||
|
||||
Locale.setDefault(locale);
|
||||
|
||||
Configuration config = res.getConfiguration();
|
||||
config.locale = locale;
|
||||
res.updateConfiguration(config, null);
|
||||
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ endif
|
||||
JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
|
||||
|
||||
ALL_JARS = \
|
||||
constants.jar \
|
||||
gecko-R.jar \
|
||||
gecko-browser.jar \
|
||||
gecko-mozglue.jar \
|
||||
@ -151,7 +152,7 @@ classycle_jar := $(topsrcdir)/mobile/android/build/classycle/classycle-1.4.1.jar
|
||||
# outputs are fresher than the target, preventing a subsequent
|
||||
# invocation from thinking Proguard's outputs are stale. This is safe
|
||||
# because Make removes the target file if any recipe command fails.
|
||||
.proguard.deps: .geckoview.deps $(ALL_JARS)
|
||||
.proguard.deps: .geckoview.deps $(ALL_JARS) $(topsrcdir)/mobile/android/config/proguard.cfg
|
||||
$(REPORT_BUILD)
|
||||
@$(TOUCH) $@
|
||||
java \
|
||||
@ -191,14 +192,6 @@ GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES)
|
||||
GeneratedJNIWrappers.cpp: $(ALL_JARS)
|
||||
$(JAVA) -classpath gecko-mozglue.jar:$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
|
||||
|
||||
# Like generated/org/mozilla/fennec_$USERID.
|
||||
android_package_dir = $(addprefix generated/,$(subst .,/,$(ANDROID_PACKAGE_NAME)))
|
||||
|
||||
# These _PP_JAVAFILES are specified in moz.build and defined in
|
||||
# backend.mk, which is included by config.mk. Therefore this needs to
|
||||
# be defined after config.mk is included.
|
||||
PP_JAVAFILES := $(filter-out generated/org/mozilla/gecko/R.java,$(gecko-mozglue_PP_JAVAFILES) $(gecko-browser_PP_JAVAFILES))
|
||||
|
||||
manifest := \
|
||||
AndroidManifest.xml.in \
|
||||
WebappManifestFragment.xml.frag.in \
|
||||
@ -210,28 +203,20 @@ PP_TARGETS += manifest
|
||||
# generates these files into generated/org/mozilla/gecko for
|
||||
# consumption by the build system and IDEs.
|
||||
|
||||
preprocessed := $(addsuffix .in,$(subst generated/org/mozilla/gecko/,,$(filter generated/org/mozilla/gecko/%,$(PP_JAVAFILES))))
|
||||
# The list in moz.build looks like
|
||||
# 'preprocessed/org/mozilla/gecko/AppConstants.java'. The list in
|
||||
# constants_PP_JAVAFILES looks like
|
||||
# 'generated/preprocessed/org/mozilla/gecko/AppConstants.java'. We
|
||||
# need to write AppConstants.java.in to
|
||||
# generated/preprocessed/org/mozilla/gecko.
|
||||
preprocessed := $(addsuffix .in,$(subst generated/preprocessed/org/mozilla/gecko/,,$(filter generated/preprocessed/org/mozilla/gecko/%,$(constants_PP_JAVAFILES))))
|
||||
|
||||
preprocessed_PATH := generated/org/mozilla/gecko
|
||||
preprocessed_PATH := generated/preprocessed/org/mozilla/gecko
|
||||
preprocessed_KEEP_PATH := 1
|
||||
preprocessed_FLAGS := --marker='//\\\#'
|
||||
|
||||
PP_TARGETS += preprocessed
|
||||
|
||||
# Certain source files have Java package name @ANDROID_PACKAGE_NAME@.
|
||||
# We hate these files but they are necessary for backwards
|
||||
# compatibility. These special rules generate these files into
|
||||
# generated/org/mozilla/{firefox,firefox_beta,fennec,fennec_$USER} for
|
||||
# consumption by the build system and IDEs.
|
||||
|
||||
preprocessed_package := $(addsuffix .in,$(subst $(android_package_dir)/,,$(filter $(android_package_dir)/%,$(PP_JAVAFILES))))
|
||||
|
||||
preprocessed_package_PATH := $(android_package_dir)
|
||||
preprocessed_package_KEEP_PATH := 1
|
||||
preprocessed_package_FLAGS := --marker='//\\\#'
|
||||
|
||||
PP_TARGETS += preprocessed_package
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
not_android_res_files := \
|
||||
|
@ -6,13 +6,17 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -21,27 +25,35 @@ import java.util.regex.Pattern;
|
||||
* nsSystemInfo. See also the constants in AppConstants, which reflect
|
||||
* much of nsIXULAppInfo.
|
||||
*/
|
||||
// Normally, we'd annotate with @RobocopTarget. Since SysInfo is compiled
|
||||
// before RobocopTarget, we instead add o.m.g.SysInfo directly to the Proguard
|
||||
// configuration.
|
||||
public final class SysInfo {
|
||||
private static final String LOG_TAG = "GeckoSysInfo";
|
||||
|
||||
// Number of bytes of /proc/meminfo to read in one go.
|
||||
private static final int MEMINFO_BUFFER_SIZE_BYTES = 256;
|
||||
|
||||
// We don't mind an instant of possible duplicate work, we only wish to
|
||||
// avoid inconsistency, so we don't bother with synchronization for
|
||||
// these.
|
||||
private static volatile int cpuCount = -1;
|
||||
|
||||
/**
|
||||
* Get the number of cores on the device.
|
||||
*
|
||||
* We can't use a nice tidy API call, because they're all wrong:
|
||||
*
|
||||
* <http://stackoverflow.com/questions/7962155/how-can-you-detect-a-dual-core-
|
||||
* cpu-on-an-android-device-from-code>
|
||||
*
|
||||
* This method is based on that code.
|
||||
*
|
||||
* @return the number of CPU cores, or 1 if the number could not be
|
||||
* determined.
|
||||
*/
|
||||
private static volatile int totalRAM = -1;
|
||||
|
||||
/**
|
||||
* Get the number of cores on the device.
|
||||
*
|
||||
* We can't use a nice tidy API call, because they're all wrong:
|
||||
*
|
||||
* <http://stackoverflow.com/questions/7962155/how-can-you-detect-a-dual-core-
|
||||
* cpu-on-an-android-device-from-code>
|
||||
*
|
||||
* This method is based on that code.
|
||||
*
|
||||
* @return the number of CPU cores, or 1 if the number could not be
|
||||
* determined.
|
||||
*/
|
||||
public static int getCPUCount() {
|
||||
if (cpuCount > 0) {
|
||||
return cpuCount;
|
||||
@ -73,10 +85,93 @@ public final class SysInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps HardwareUtils so callers don't need to know about it.
|
||||
* Helper functions used to extract key/value data from /proc/meminfo
|
||||
* Pulled from:
|
||||
* http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/com/android/internal/util/MemInfoReader.java
|
||||
*/
|
||||
private static boolean matchMemText(byte[] buffer, int index, int bufferLength, byte[] text) {
|
||||
final int N = text.length;
|
||||
if ((index + N) >= bufferLength) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (buffer[index + i] != text[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line like:
|
||||
*
|
||||
* MemTotal: 1605324 kB
|
||||
*
|
||||
* into 1605324.
|
||||
*
|
||||
* @return the first uninterrupted sequence of digits following the
|
||||
* specified index, parsed as an integer value in KB.
|
||||
*/
|
||||
private static int extractMemValue(byte[] buffer, int offset, int length) {
|
||||
if (offset >= length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (offset < length && buffer[offset] != '\n') {
|
||||
if (buffer[offset] >= '0' && buffer[offset] <= '9') {
|
||||
int start = offset++;
|
||||
while (offset < length &&
|
||||
buffer[offset] >= '0' &&
|
||||
buffer[offset] <= '9') {
|
||||
++offset;
|
||||
}
|
||||
return Integer.parseInt(new String(buffer, start, offset - start), 10);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the total memory of the device in MB by parsing /proc/meminfo.
|
||||
*
|
||||
* Of course, Android doesn't have a neat and tidy way to find total
|
||||
* RAM, so we do it by parsing /proc/meminfo.
|
||||
*
|
||||
* @return 0 if a problem occurred, or memory size in MB.
|
||||
*/
|
||||
public static int getMemSize() {
|
||||
return HardwareUtils.getMemSize();
|
||||
if (totalRAM >= 0) {
|
||||
return totalRAM;
|
||||
}
|
||||
|
||||
// This is the string "MemTotal" that we're searching for in the buffer.
|
||||
final byte[] MEMTOTAL = {'M', 'e', 'm', 'T', 'o', 't', 'a', 'l'};
|
||||
try {
|
||||
final byte[] buffer = new byte[MEMINFO_BUFFER_SIZE_BYTES];
|
||||
final FileInputStream is = new FileInputStream("/proc/meminfo");
|
||||
try {
|
||||
final int length = is.read(buffer);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (matchMemText(buffer, i, length, MEMTOTAL)) {
|
||||
i += 8;
|
||||
totalRAM = extractMemValue(buffer, i, length) / 1024;
|
||||
Log.d(LOG_TAG, "System memory: " + totalRAM + "MB.");
|
||||
return totalRAM;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
|
||||
Log.w(LOG_TAG, "Did not find MemTotal line in /proc/meminfo.");
|
||||
return totalRAM = 0;
|
||||
} catch (FileNotFoundException f) {
|
||||
return totalRAM = 0;
|
||||
} catch (IOException e) {
|
||||
return totalRAM = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1137,8 +1137,8 @@ sync_java_files = [
|
||||
]
|
||||
|
||||
sync_generated_java_files = [
|
||||
'org/mozilla/gecko/background/common/GlobalConstants.java',
|
||||
'org/mozilla/gecko/background/healthreport/HealthReportConstants.java',
|
||||
'org/mozilla/gecko/fxa/FxAccountConstants.java',
|
||||
'org/mozilla/gecko/sync/SyncConstants.java',
|
||||
'background/common/GlobalConstants.java',
|
||||
'background/healthreport/HealthReportConstants.java',
|
||||
'fxa/FxAccountConstants.java',
|
||||
'sync/SyncConstants.java',
|
||||
]
|
||||
|
@ -35,13 +35,13 @@ public class FxAccountAgeLockoutHelper {
|
||||
// Otherwise, find out how long it's been since we last failed.
|
||||
long millsecondsSinceLastFailedAgeCheck = elapsedRealtime - ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK;
|
||||
boolean isLockedOut = millsecondsSinceLastFailedAgeCheck < FxAccountConstants.MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS;
|
||||
FxAccountConstants.pii(LOG_TAG, "Checking if locked out: it's been " + millsecondsSinceLastFailedAgeCheck + "ms " +
|
||||
FxAccountUtils.pii(LOG_TAG, "Checking if locked out: it's been " + millsecondsSinceLastFailedAgeCheck + "ms " +
|
||||
"since last lockout, so " + (isLockedOut ? "yes." : "no."));
|
||||
return isLockedOut;
|
||||
}
|
||||
|
||||
public static synchronized void lockOut(long elapsedRealtime) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Locking out at time: " + elapsedRealtime);
|
||||
FxAccountUtils.pii(LOG_TAG, "Locking out at time: " + elapsedRealtime);
|
||||
ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = Math.max(elapsedRealtime, ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK);
|
||||
}
|
||||
|
||||
@ -60,8 +60,8 @@ public class FxAccountAgeLockoutHelper {
|
||||
int thisYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||
int approximateAge = thisYear - yearOfBirth;
|
||||
boolean oldEnough = approximateAge >= FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT;
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Age check " + (oldEnough ? "passes" : "fails") +
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Age check " + (oldEnough ? "passes" : "fails") +
|
||||
": age is " + approximateAge + " = " + thisYear + " - " + yearOfBirth);
|
||||
}
|
||||
return oldEnough;
|
||||
@ -79,7 +79,7 @@ public class FxAccountAgeLockoutHelper {
|
||||
}
|
||||
if (!Arrays.asList(yearItems).contains(yearText)) {
|
||||
// This should never happen, but let's be careful.
|
||||
FxAccountConstants.pii(LOG_TAG, "Failed age check: year text was not found in item list.");
|
||||
FxAccountUtils.pii(LOG_TAG, "Failed age check: year text was not found in item list.");
|
||||
return false;
|
||||
}
|
||||
Integer yearOfBirth;
|
||||
@ -88,7 +88,7 @@ public class FxAccountAgeLockoutHelper {
|
||||
} catch (NumberFormatException e) {
|
||||
// Any non-numbers in the list are ranges (and we say as much to
|
||||
// translators in the resource file), so these people pass the age check.
|
||||
FxAccountConstants.pii(LOG_TAG, "Passed age check: year text was found in item list but was not a number.");
|
||||
FxAccountUtils.pii(LOG_TAG, "Passed age check: year text was found in item list but was not a number.");
|
||||
return true;
|
||||
}
|
||||
return passesAgeCheck(yearOfBirth);
|
||||
|
@ -13,7 +13,6 @@ import java.util.concurrent.Executor;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
@ -206,7 +205,7 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
byte[] quickStretchedPW;
|
||||
try {
|
||||
FxAccountConstants.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
|
||||
FxAccountUtils.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
|
||||
quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8);
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
@ -233,7 +232,7 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
};
|
||||
|
||||
Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email.");
|
||||
FxAccountConstants.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
|
||||
FxAccountUtils.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
|
||||
|
||||
try {
|
||||
// Nota bene: this is not recursive, since we call the fixed password
|
||||
|
@ -35,6 +35,16 @@ public class FxAccountUtils {
|
||||
|
||||
public static final int NUMBER_OF_QUICK_STRETCH_ROUNDS = 1000;
|
||||
|
||||
// For extra debugging. Not final so it can be changed from Fennec, or from
|
||||
// an add-on.
|
||||
public static boolean LOG_PERSONAL_INFORMATION = false;
|
||||
|
||||
public static void pii(String tag, String message) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.info(tag, "$$FxA PII$$: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
public static String bytes(String string) throws UnsupportedEncodingException {
|
||||
return Utils.byte2Hex(string.getBytes("UTF-8"));
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.mozilla.gecko.fxa;
|
||||
|
||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
public class FxAccountConstants {
|
||||
public static final String GLOBAL_LOG_TAG = "FxAccounts";
|
||||
@ -18,16 +17,6 @@ public class FxAccountConstants {
|
||||
public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
|
||||
public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
|
||||
|
||||
// For extra debugging. Not final so it can be changed from Fennec, or from
|
||||
// an add-on.
|
||||
public static boolean LOG_PERSONAL_INFORMATION = false;
|
||||
|
||||
public static void pii(String tag, String message) {
|
||||
if (LOG_PERSONAL_INFORMATION) {
|
||||
Logger.info(tag, "$$FxA PII$$: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
// You must be at least 14 years old to create a Firefox Account.
|
||||
public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 14;
|
||||
|
||||
|
@ -324,7 +324,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
}
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
@ -403,9 +403,9 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
// This sets defaults as well as extracting from extras, so it's not conditional.
|
||||
updateServersFromIntentExtras(getIntent());
|
||||
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
|
||||
FxAccountConstants.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
|
||||
FxAccountUtils.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
|
||||
}
|
||||
|
||||
updateCustomServerView();
|
||||
|
@ -19,8 +19,8 @@ import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
@ -250,10 +250,10 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
? selectedEngines
|
||||
: null;
|
||||
if (FxAccountAgeLockoutHelper.passesAgeCheck(yearEdit.getText().toString(), yearItems)) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Passed age check.");
|
||||
FxAccountUtils.pii(LOG_TAG, "Passed age check.");
|
||||
createAccount(email, password, engines);
|
||||
} else {
|
||||
FxAccountConstants.pii(LOG_TAG, "Failed age check!");
|
||||
FxAccountUtils.pii(LOG_TAG, "Failed age check!");
|
||||
FxAccountAgeLockoutHelper.lockOut(SystemClock.elapsedRealtime());
|
||||
setResult(RESULT_CANCELED);
|
||||
redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
|
||||
@ -304,7 +304,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
selectedEngines.put("history", checkedItems[INDEX_HISTORY]);
|
||||
selectedEngines.put("tabs", checkedItems[INDEX_TABS]);
|
||||
selectedEngines.put("passwords", checkedItems[INDEX_PASSWORDS]);
|
||||
FxAccountConstants.pii(LOG_TAG, "Updating selectedEngines: " + selectedEngines.toString());
|
||||
FxAccountUtils.pii(LOG_TAG, "Updating selectedEngines: " + selectedEngines.toString());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import java.util.Locale;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
@ -133,7 +134,7 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
||||
TextView oldFirefox = (TextView) findViewById(R.id.old_firefox);
|
||||
String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox);
|
||||
final String url = FirefoxAccounts.getOldSyncUpgradeURL(getResources(), Locale.getDefault());
|
||||
FxAccountConstants.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular.
|
||||
FxAccountUtils.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular.
|
||||
ActivityUtils.linkTextView(oldFirefox, text, url);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.preferences.PreferenceFragment;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
@ -145,7 +146,7 @@ public class FxAccountStatusFragment
|
||||
tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs");
|
||||
passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords");
|
||||
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
removeDebugButtons();
|
||||
} else {
|
||||
connectDebugButtons();
|
||||
|
@ -17,7 +17,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClient
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
@ -156,7 +155,7 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
fxAccount.requestSync(FirefoxAccounts.FORCE);
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
|
@ -516,14 +516,14 @@ public class AndroidFxAccount {
|
||||
* <b>For debugging only!</b>
|
||||
*/
|
||||
public void dump() {
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
return;
|
||||
}
|
||||
ExtendedJSONObject o = toJSONObject();
|
||||
ArrayList<String> list = new ArrayList<String>(o.keySet());
|
||||
Collections.sort(list);
|
||||
for (String key : list) {
|
||||
FxAccountConstants.pii(LOG_TAG, key + ": " + o.get(key));
|
||||
FxAccountUtils.pii(LOG_TAG, key + ": " + o.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
@ -24,19 +24,19 @@ public class Cohabiting extends TokensAndKeysState {
|
||||
new BaseRequestDelegate<String>(this, delegate) {
|
||||
@Override
|
||||
public void handleSuccess(String certificate) {
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
try {
|
||||
FxAccountConstants.pii(LOG_TAG, "Fetched certificate: " + certificate);
|
||||
FxAccountUtils.pii(LOG_TAG, "Fetched certificate: " + certificate);
|
||||
ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate);
|
||||
if (c != null) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Header : " + c.getObject("header"));
|
||||
FxAccountConstants.pii(LOG_TAG, "Payload : " + c.getObject("payload"));
|
||||
FxAccountConstants.pii(LOG_TAG, "Signature: " + c.getString("signature"));
|
||||
FxAccountUtils.pii(LOG_TAG, "Header : " + c.getObject("header"));
|
||||
FxAccountUtils.pii(LOG_TAG, "Payload : " + c.getObject("payload"));
|
||||
FxAccountUtils.pii(LOG_TAG, "Signature: " + c.getString("signature"));
|
||||
} else {
|
||||
FxAccountConstants.pii(LOG_TAG, "Could not parse certificate!");
|
||||
FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Could not parse certificate!");
|
||||
FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
|
||||
}
|
||||
}
|
||||
delegate.handleTransition(new LogMessage("sign succeeded"), new Married(email, uid, sessionToken, kA, kB, keyPair, certificate));
|
||||
|
@ -9,7 +9,6 @@ import java.security.NoSuchAlgorithmException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.TwoKeys;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.AccountVerified;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LocalError;
|
||||
@ -61,10 +60,10 @@ public class Engaged extends State {
|
||||
byte[] kB;
|
||||
try {
|
||||
kB = FxAccountUtils.unwrapkB(unwrapkB, result.wrapkB);
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Fetched kA: " + Utils.byte2Hex(result.kA));
|
||||
FxAccountConstants.pii(LOG_TAG, "And wrapkB: " + Utils.byte2Hex(result.wrapkB));
|
||||
FxAccountConstants.pii(LOG_TAG, "Giving kB : " + Utils.byte2Hex(kB));
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Fetched kA: " + Utils.byte2Hex(result.kA));
|
||||
FxAccountUtils.pii(LOG_TAG, "And wrapkB: " + Utils.byte2Hex(result.wrapkB));
|
||||
FxAccountUtils.pii(LOG_TAG, "Giving kB : " + Utils.byte2Hex(kB));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delegate.handleTransition(new RemoteError(e), new Separated(email, uid, verified));
|
||||
|
@ -17,7 +17,6 @@ import org.json.simple.parser.ParseException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
@ -61,23 +60,23 @@ public class Married extends TokensAndKeysState {
|
||||
// invalid-timestamp errors from the token server.
|
||||
final long expiresAt = JSONWebTokenUtils.DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS;
|
||||
String assertion = JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience, issuer, null, expiresAt);
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
return assertion;
|
||||
}
|
||||
|
||||
try {
|
||||
FxAccountConstants.pii(LOG_TAG, "Generated assertion: " + assertion);
|
||||
FxAccountUtils.pii(LOG_TAG, "Generated assertion: " + assertion);
|
||||
ExtendedJSONObject a = JSONWebTokenUtils.parseAssertion(assertion);
|
||||
if (a != null) {
|
||||
FxAccountConstants.pii(LOG_TAG, "aHeader : " + a.getObject("header"));
|
||||
FxAccountConstants.pii(LOG_TAG, "aPayload : " + a.getObject("payload"));
|
||||
FxAccountConstants.pii(LOG_TAG, "aSignature: " + a.getString("signature"));
|
||||
FxAccountUtils.pii(LOG_TAG, "aHeader : " + a.getObject("header"));
|
||||
FxAccountUtils.pii(LOG_TAG, "aPayload : " + a.getObject("payload"));
|
||||
FxAccountUtils.pii(LOG_TAG, "aSignature: " + a.getString("signature"));
|
||||
String certificate = a.getString("certificate");
|
||||
if (certificate != null) {
|
||||
ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate);
|
||||
FxAccountConstants.pii(LOG_TAG, "cHeader : " + c.getObject("header"));
|
||||
FxAccountConstants.pii(LOG_TAG, "cPayload : " + c.getObject("payload"));
|
||||
FxAccountConstants.pii(LOG_TAG, "cSignature: " + c.getString("signature"));
|
||||
FxAccountUtils.pii(LOG_TAG, "cHeader : " + c.getObject("header"));
|
||||
FxAccountUtils.pii(LOG_TAG, "cPayload : " + c.getObject("payload"));
|
||||
FxAccountUtils.pii(LOG_TAG, "cSignature: " + c.getString("signature"));
|
||||
// Print the relevant timestamps in sorted order with labels.
|
||||
HashMap<Long, String> map = new HashMap<Long, String>();
|
||||
map.put(a.getObject("payload").getLong("iat"), "aiat");
|
||||
@ -87,16 +86,16 @@ public class Married extends TokensAndKeysState {
|
||||
ArrayList<Long> values = new ArrayList<Long>(map.keySet());
|
||||
Collections.sort(values);
|
||||
for (Long value : values) {
|
||||
FxAccountConstants.pii(LOG_TAG, map.get(value) + ": " + value);
|
||||
FxAccountUtils.pii(LOG_TAG, map.get(value) + ": " + value);
|
||||
}
|
||||
} else {
|
||||
FxAccountConstants.pii(LOG_TAG, "Could not parse certificate!");
|
||||
FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
|
||||
}
|
||||
} else {
|
||||
FxAccountConstants.pii(LOG_TAG, "Could not parse assertion!");
|
||||
FxAccountUtils.pii(LOG_TAG, "Could not parse assertion!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Got exception dumping assertion debug info.");
|
||||
FxAccountUtils.pii(LOG_TAG, "Got exception dumping assertion debug info.");
|
||||
}
|
||||
return assertion;
|
||||
}
|
||||
@ -107,8 +106,8 @@ public class Married extends TokensAndKeysState {
|
||||
}
|
||||
|
||||
public String getClientState() {
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Client state: " + this.clientState);
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Client state: " + this.clientState);
|
||||
}
|
||||
return this.clientState;
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.DSACryptoImplementation;
|
||||
import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
@ -135,15 +135,15 @@ public class StateFactory {
|
||||
}
|
||||
|
||||
protected static void logMigration(State from, State to) {
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FxAccountConstants.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
|
||||
FxAccountUtils.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
|
||||
}
|
||||
FxAccountConstants.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
|
||||
FxAccountUtils.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
|
||||
}
|
||||
|
||||
protected static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
|
||||
|
@ -10,6 +10,7 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
@ -112,7 +113,7 @@ public class FxAccountUpgradeReceiver extends BroadcastReceiver {
|
||||
try {
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
State state = fxAccount.getState();
|
||||
|
@ -7,7 +7,7 @@ package org.mozilla.gecko.fxa.sync;
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
@ -70,7 +70,7 @@ public class FxAccountNotificationManager {
|
||||
final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||
final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||
FxAccountConstants.pii(LOG_TAG, "And text: " + text);
|
||||
FxAccountUtils.pii(LOG_TAG, "And text: " + text);
|
||||
|
||||
final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
@ -17,6 +17,7 @@ import java.util.concurrent.Executors;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.SkewHandler;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
||||
@ -315,7 +316,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
@Override
|
||||
public void handleSuccess(final TokenServerToken token) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
|
||||
FxAccountUtils.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
|
||||
|
||||
if (!didReceiveBackoff) {
|
||||
// We must be OK to touch this token server.
|
||||
@ -358,9 +359,9 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
FxAccountGlobalSession globalSession = null;
|
||||
try {
|
||||
final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs, getContext());
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'.");
|
||||
FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp());
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'.");
|
||||
FxAccountUtils.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp());
|
||||
}
|
||||
|
||||
// We compute skew over time using SkewHandler. This yields an unchanging
|
||||
@ -444,7 +445,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
Logger.info(LOG_TAG, "Account last synced at: " + fxAccount.getLastSyncedTimestamp());
|
||||
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ show allResults
|
||||
org.mozilla.gecko.PrefsHelper \
|
||||
org.mozilla.gecko.SmsManager \
|
||||
org.mozilla.gecko.SurfaceBits \
|
||||
org.mozilla.gecko.SysInfo \
|
||||
org.mozilla.gecko.TouchEventInterceptor \
|
||||
org.mozilla.gecko.ZoomConstraints
|
||||
|
||||
|
@ -11,6 +11,14 @@ include('android-services.mozbuild')
|
||||
|
||||
thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
|
||||
|
||||
constants_jar = add_java_jar('constants')
|
||||
constants_jar.sources = []
|
||||
constants_jar.generated_sources = [
|
||||
'preprocessed/org/mozilla/gecko/AppConstants.java',
|
||||
'preprocessed/org/mozilla/gecko/SysInfo.java',
|
||||
]
|
||||
constants_jar.generated_sources += ['preprocessed/org/mozilla/gecko/' + f for f in sync_generated_java_files]
|
||||
|
||||
resjar = add_java_jar('gecko-R')
|
||||
resjar.sources = []
|
||||
resjar.generated_sources += [
|
||||
@ -29,6 +37,7 @@ mgjar.sources += [
|
||||
'mozglue/ByteBufferInputStream.java',
|
||||
'mozglue/ContextUtils.java',
|
||||
'mozglue/DirectBufferAllocator.java',
|
||||
'mozglue/GeckoLoader.java',
|
||||
'mozglue/generatorannotations/OptionalGeneratedParameter.java',
|
||||
'mozglue/generatorannotations/WrapElementForJNI.java',
|
||||
'mozglue/generatorannotations/WrapEntireClassForJNI.java',
|
||||
@ -38,9 +47,9 @@ mgjar.sources += [
|
||||
'mozglue/RobocopTarget.java',
|
||||
'mozglue/WebRTCJNITarget.java',
|
||||
]
|
||||
mgjar.generated_sources += [
|
||||
'org/mozilla/gecko/AppConstants.java',
|
||||
'org/mozilla/gecko/mozglue/GeckoLoader.java',
|
||||
mgjar.generated_sources = [] # Keep it this way.
|
||||
mgjar.extra_jars += [
|
||||
'constants.jar',
|
||||
]
|
||||
mgjar.javac_flags += ['-Xlint:all']
|
||||
|
||||
@ -77,7 +86,8 @@ gujar.sources += [
|
||||
'util/WebActivityMapper.java',
|
||||
]
|
||||
gujar.extra_jars = [
|
||||
'gecko-mozglue.jar'
|
||||
'constants.jar',
|
||||
'gecko-mozglue.jar',
|
||||
]
|
||||
gujar.javac_flags += ['-Xlint:all,-deprecation']
|
||||
|
||||
@ -480,22 +490,28 @@ gbjar.sources += [
|
||||
'widget/TwoWayView.java',
|
||||
'ZoomConstraints.java',
|
||||
]
|
||||
# The following sources are checked in to version control but
|
||||
# generated by a script (widget/generate_themed_views.py). If you're
|
||||
# editing this list, make sure to edit that script.
|
||||
gbjar.sources += [
|
||||
'widget/ThemedEditText.java',
|
||||
'widget/ThemedImageButton.java',
|
||||
'widget/ThemedImageView.java',
|
||||
'widget/ThemedLinearLayout.java',
|
||||
'widget/ThemedRelativeLayout.java',
|
||||
'widget/ThemedTextSwitcher.java',
|
||||
'widget/ThemedTextView.java',
|
||||
'widget/ThemedView.java',
|
||||
]
|
||||
gbjar.sources += [ thirdparty_source_dir + f for f in [
|
||||
'com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java',
|
||||
'com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java',
|
||||
'com/googlecode/eyesfree/braille/selfbraille/WriteData.java',
|
||||
] ]
|
||||
android_package_dir = CONFIG['ANDROID_PACKAGE_NAME'].replace('.', '/')
|
||||
gbjar.generated_sources += [
|
||||
'org/mozilla/gecko/SysInfo.java',
|
||||
'org/mozilla/gecko/widget/ThemedEditText.java',
|
||||
'org/mozilla/gecko/widget/ThemedImageButton.java',
|
||||
'org/mozilla/gecko/widget/ThemedImageView.java',
|
||||
'org/mozilla/gecko/widget/ThemedLinearLayout.java',
|
||||
'org/mozilla/gecko/widget/ThemedRelativeLayout.java',
|
||||
'org/mozilla/gecko/widget/ThemedTextSwitcher.java',
|
||||
'org/mozilla/gecko/widget/ThemedTextView.java',
|
||||
'org/mozilla/gecko/widget/ThemedView.java',
|
||||
gbjar.generated_sources = [] # Keep it this way.
|
||||
gbjar.extra_jars += [
|
||||
'constants.jar'
|
||||
]
|
||||
if CONFIG['MOZ_CRASHREPORTER']:
|
||||
gbjar.sources += [ 'CrashReporter.java' ]
|
||||
@ -534,8 +550,7 @@ if CONFIG['MOZ_ANDROID_NEW_TABLET_UI'] and max_sdk_version >= 11:
|
||||
ANDROID_RES_DIRS += [ SRCDIR + '/newtablet/res' ]
|
||||
|
||||
gbjar.sources += sync_java_files
|
||||
gbjar.generated_sources += sync_generated_java_files
|
||||
gbjar.extra_jars = [
|
||||
gbjar.extra_jars += [
|
||||
'gecko-R.jar',
|
||||
'gecko-mozglue.jar',
|
||||
'gecko-thirdparty.jar',
|
||||
@ -681,6 +696,7 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
|
||||
search_activity.sources += [search_source_dir + '/' + f for f in search_activity_sources]
|
||||
search_activity.javac_flags += ['-Xlint:all']
|
||||
search_activity.extra_jars = [
|
||||
'constants.jar',
|
||||
'gecko-R.jar',
|
||||
'gecko-browser.jar',
|
||||
'gecko-mozglue.jar',
|
||||
@ -778,11 +794,8 @@ else:
|
||||
# Manifest.java for all packages) but also preprocessed files. In the past, the
|
||||
# generated R.java files were used by the Eclipse build, but now Eclipse
|
||||
# generates these files itself. Therefore, we exclude those generated sources.
|
||||
main.add_classpathentry('generated', OBJDIR + '/generated',
|
||||
dstdir='generated',
|
||||
exclude_patterns=[
|
||||
'**/R.java',
|
||||
'**/Manifest.java'])
|
||||
main.add_classpathentry('generated', OBJDIR + '/generated/preprocessed',
|
||||
dstdir='generated')
|
||||
main.add_classpathentry('thirdparty', TOPSRCDIR + '/mobile/android/thirdparty',
|
||||
dstdir='thirdparty',
|
||||
ignore_warnings=True)
|
||||
|
@ -1,4 +1,3 @@
|
||||
//#filter substitution
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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
|
||||
@ -22,15 +21,12 @@ import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
|
||||
|
||||
public final class GeckoLoader {
|
||||
private static final String LOGTAG = "GeckoLoader";
|
||||
|
||||
// These match AppConstants, but we're built earlier.
|
||||
private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
|
||||
private static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
|
||||
|
||||
private static volatile SafeIntent sIntent;
|
||||
private static File sCacheFile;
|
||||
private static File sGREDir;
|
||||
@ -228,16 +224,16 @@ public final class GeckoLoader {
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef MOZ_LINKER_EXTRACT
|
||||
putenv("MOZ_LINKER_EXTRACT=1");
|
||||
// Ensure that the cache dir is world-writable
|
||||
File cacheDir = new File(linkerCache);
|
||||
if (cacheDir.isDirectory()) {
|
||||
cacheDir.setWritable(true, false);
|
||||
cacheDir.setExecutable(true, false);
|
||||
cacheDir.setReadable(true, false);
|
||||
if (AppConstants.MOZ_LINKER_EXTRACT) {
|
||||
putenv("MOZ_LINKER_EXTRACT=1");
|
||||
// Ensure that the cache dir is world-writable
|
||||
File cacheDir = new File(linkerCache);
|
||||
if (cacheDir.isDirectory()) {
|
||||
cacheDir.setWritable(true, false);
|
||||
cacheDir.setExecutable(true, false);
|
||||
cacheDir.setReadable(true, false);
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
@ -357,15 +353,17 @@ public final class GeckoLoader {
|
||||
}
|
||||
|
||||
private static String getLoadDiagnostics(final Context context, final String lib) {
|
||||
final String androidPackageName = context.getPackageName();
|
||||
|
||||
final StringBuilder message = new StringBuilder("LOAD ");
|
||||
message.append(lib);
|
||||
|
||||
// These might differ. If so, we know why the library won't load!
|
||||
message.append(": ABI: " + MOZ_APP_ABI + ", " + getCPUABI());
|
||||
message.append(": ABI: " + AppConstants.MOZ_APP_ABI + ", " + getCPUABI());
|
||||
message.append(": Data: " + context.getApplicationInfo().dataDir);
|
||||
try {
|
||||
final boolean appLibExists = new File("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so").exists();
|
||||
final boolean dataDataExists = new File("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so").exists();
|
||||
final boolean appLibExists = new File("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so").exists();
|
||||
final boolean dataDataExists = new File("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so").exists();
|
||||
message.append(", ax=" + appLibExists);
|
||||
message.append(", ddx=" + dataDataExists);
|
||||
} catch (Throwable e) {
|
||||
@ -373,8 +371,8 @@ public final class GeckoLoader {
|
||||
}
|
||||
|
||||
try {
|
||||
final String dashOne = "/data/data/" + ANDROID_PACKAGE_NAME + "-1";
|
||||
final String dashTwo = "/data/data/" + ANDROID_PACKAGE_NAME + "-2";
|
||||
final String dashOne = "/data/data/" + androidPackageName + "-1";
|
||||
final String dashTwo = "/data/data/" + androidPackageName + "-2";
|
||||
final boolean dashOneExists = new File(dashOne).exists();
|
||||
final boolean dashTwoExists = new File(dashTwo).exists();
|
||||
message.append(", -1x=" + dashOneExists);
|
||||
@ -475,12 +473,13 @@ public final class GeckoLoader {
|
||||
}
|
||||
|
||||
// Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
|
||||
if (attemptLoad("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so")) {
|
||||
final String androidPackageName = context.getPackageName();
|
||||
if (attemptLoad("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt 5: even more optimistic.
|
||||
if (attemptLoad("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so")) {
|
||||
if (attemptLoad("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so")) {
|
||||
return;
|
||||
}
|
||||
|
@ -5,9 +5,7 @@
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import org.mozilla.gecko.SysInfo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -27,14 +25,10 @@ public final class HardwareUtils {
|
||||
// reading list capabilities in HomePager.
|
||||
private static final int LOW_MEMORY_THRESHOLD_MB = 384;
|
||||
|
||||
// Number of bytes of /proc/meminfo to read in one go.
|
||||
private static final int MEMINFO_BUFFER_SIZE_BYTES = 256;
|
||||
|
||||
private static final boolean IS_AMAZON_DEVICE = Build.MANUFACTURER.equalsIgnoreCase("Amazon");
|
||||
public static final boolean IS_KINDLE_DEVICE = IS_AMAZON_DEVICE &&
|
||||
(Build.MODEL.equals("Kindle Fire") ||
|
||||
Build.MODEL.startsWith("KF"));
|
||||
private static volatile int sTotalRAM = -1;
|
||||
|
||||
private static volatile boolean sInited;
|
||||
|
||||
@ -99,95 +93,8 @@ public final class HardwareUtils {
|
||||
return sHasMenuButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions used to extract key/value data from /proc/meminfo
|
||||
* Pulled from:
|
||||
* http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/com/android/internal/util/MemInfoReader.java
|
||||
*/
|
||||
|
||||
private static boolean matchMemText(byte[] buffer, int index, int bufferLength, byte[] text) {
|
||||
final int N = text.length;
|
||||
if ((index + N) >= bufferLength) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (buffer[index + i] != text[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line like:
|
||||
*
|
||||
* MemTotal: 1605324 kB
|
||||
*
|
||||
* into 1605324.
|
||||
*
|
||||
* @return the first uninterrupted sequence of digits following the
|
||||
* specified index, parsed as an integer value in KB.
|
||||
*/
|
||||
private static int extractMemValue(byte[] buffer, int offset, int length) {
|
||||
if (offset >= length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (offset < length && buffer[offset] != '\n') {
|
||||
if (buffer[offset] >= '0' && buffer[offset] <= '9') {
|
||||
int start = offset++;
|
||||
while (offset < length &&
|
||||
buffer[offset] >= '0' &&
|
||||
buffer[offset] <= '9') {
|
||||
++offset;
|
||||
}
|
||||
return Integer.parseInt(new String(buffer, start, offset - start), 10);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the total memory of the device in MB by parsing /proc/meminfo.
|
||||
*
|
||||
* Of course, Android doesn't have a neat and tidy way to find total
|
||||
* RAM, so we do it by parsing /proc/meminfo.
|
||||
*
|
||||
* @return 0 if a problem occurred, or memory size in MB.
|
||||
*/
|
||||
public static int getMemSize() {
|
||||
if (sTotalRAM >= 0) {
|
||||
return sTotalRAM;
|
||||
}
|
||||
|
||||
// This is the string "MemTotal" that we're searching for in the buffer.
|
||||
final byte[] MEMTOTAL = {'M', 'e', 'm', 'T', 'o', 't', 'a', 'l'};
|
||||
try {
|
||||
final byte[] buffer = new byte[MEMINFO_BUFFER_SIZE_BYTES];
|
||||
final FileInputStream is = new FileInputStream("/proc/meminfo");
|
||||
try {
|
||||
final int length = is.read(buffer);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (matchMemText(buffer, i, length, MEMTOTAL)) {
|
||||
i += 8;
|
||||
sTotalRAM = extractMemValue(buffer, i, length) / 1024;
|
||||
Log.d(LOGTAG, "System memory: " + sTotalRAM + "MB.");
|
||||
return sTotalRAM;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
|
||||
Log.w(LOGTAG, "Did not find MemTotal line in /proc/meminfo.");
|
||||
return sTotalRAM = 0;
|
||||
} catch (FileNotFoundException f) {
|
||||
return sTotalRAM = 0;
|
||||
} catch (IOException e) {
|
||||
return sTotalRAM = 0;
|
||||
}
|
||||
return SysInfo.getMemSize();
|
||||
}
|
||||
|
||||
public static boolean isLowMemoryPlatform() {
|
||||
|
155
mobile/android/base/widget/ThemedEditText.java
Normal file
155
mobile/android/base/widget/ThemedEditText.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedEditText extends android.widget.EditText
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedEditText(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX EditText
|
||||
//#define BASE_TYPE android.widget.EditText
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
155
mobile/android/base/widget/ThemedImageButton.java
Normal file
155
mobile/android/base/widget/ThemedImageButton.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedImageButton extends android.widget.ImageButton
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedImageButton(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedImageButton(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX ImageButton
|
||||
//#define BASE_TYPE android.widget.ImageButton
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
155
mobile/android/base/widget/ThemedImageView.java
Normal file
155
mobile/android/base/widget/ThemedImageView.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedImageView extends android.widget.ImageView
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX ImageView
|
||||
//#define BASE_TYPE android.widget.ImageView
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
150
mobile/android/base/widget/ThemedLinearLayout.java
Normal file
150
mobile/android/base/widget/ThemedLinearLayout.java
Normal file
@ -0,0 +1,150 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedLinearLayout extends android.widget.LinearLayout
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedLinearLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX LinearLayout
|
||||
//#define BASE_TYPE android.widget.LinearLayout
|
||||
//#include ThemedView.java.frag
|
155
mobile/android/base/widget/ThemedRelativeLayout.java
Normal file
155
mobile/android/base/widget/ThemedRelativeLayout.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedRelativeLayout extends android.widget.RelativeLayout
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedRelativeLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX RelativeLayout
|
||||
//#define BASE_TYPE android.widget.RelativeLayout
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
150
mobile/android/base/widget/ThemedTextSwitcher.java
Normal file
150
mobile/android/base/widget/ThemedTextSwitcher.java
Normal file
@ -0,0 +1,150 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedTextSwitcher extends android.widget.TextSwitcher
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedTextSwitcher(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX TextSwitcher
|
||||
//#define BASE_TYPE android.widget.TextSwitcher
|
||||
//#include ThemedView.java.frag
|
155
mobile/android/base/widget/ThemedTextView.java
Normal file
155
mobile/android/base/widget/ThemedTextView.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedTextView extends android.widget.TextView
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX TextView
|
||||
//#define BASE_TYPE android.widget.TextView
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
155
mobile/android/base/widget/ThemedView.java
Normal file
155
mobile/android/base/widget/ThemedView.java
Normal file
@ -0,0 +1,155 @@
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class ThemedView extends android.view.View
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
private LightweightTheme mTheme;
|
||||
|
||||
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
|
||||
private static final int[] STATE_LIGHT = { R.attr.state_light };
|
||||
private static final int[] STATE_DARK = { R.attr.state_dark };
|
||||
|
||||
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
|
||||
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
|
||||
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
|
||||
|
||||
private boolean mIsPrivate;
|
||||
private boolean mIsLight;
|
||||
private boolean mIsDark;
|
||||
private boolean mAutoUpdateTheme; // always false if there's no theme.
|
||||
|
||||
public ThemedView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public ThemedView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(final Context context, final AttributeSet attrs) {
|
||||
// The theme can be null, particularly for webapps: Bug 1089266.
|
||||
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
|
||||
mAutoUpdateTheme = mTheme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsPrivate)
|
||||
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
|
||||
else if (mIsLight)
|
||||
mergeDrawableStates(drawableState, STATE_LIGHT);
|
||||
else if (mIsDark)
|
||||
mergeDrawableStates(drawableState, STATE_DARK);
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
if (mAutoUpdateTheme && mTheme.isEnabled())
|
||||
setTheme(mTheme.isLightTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
if (mAutoUpdateTheme)
|
||||
resetTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
public boolean isPrivateMode() {
|
||||
return mIsPrivate;
|
||||
}
|
||||
|
||||
public void setPrivateMode(boolean isPrivate) {
|
||||
if (mIsPrivate != isPrivate) {
|
||||
mIsPrivate = isPrivate;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTheme(boolean isLight) {
|
||||
// Set the theme only if it is different from existing theme.
|
||||
if ((isLight && mIsLight != isLight) ||
|
||||
(!isLight && mIsDark == isLight)) {
|
||||
if (isLight) {
|
||||
mIsLight = true;
|
||||
mIsDark = false;
|
||||
} else {
|
||||
mIsLight = false;
|
||||
mIsDark = true;
|
||||
}
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTheme() {
|
||||
if (mIsLight || mIsDark) {
|
||||
mIsLight = false;
|
||||
mIsDark = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoUpdateTheme(boolean autoUpdateTheme) {
|
||||
if (mTheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAutoUpdateTheme != autoUpdateTheme) {
|
||||
mAutoUpdateTheme = autoUpdateTheme;
|
||||
|
||||
if (mAutoUpdateTheme)
|
||||
mTheme.addListener(this);
|
||||
else
|
||||
mTheme.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorDrawable getColorDrawable(int id) {
|
||||
return new ColorDrawable(getResources().getColor(id));
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
//#filter substitution
|
||||
// This file is generated by generate_themed_views.py; do not edit.
|
||||
|
||||
/* 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/. */
|
||||
|
@ -1,5 +0,0 @@
|
||||
//#filter substitution
|
||||
//#define VIEW_NAME_SUFFIX View
|
||||
//#define BASE_TYPE android.view.View
|
||||
//#define STYLE_CONSTRUCTOR 1
|
||||
//#include ThemedView.java.frag
|
66
mobile/android/base/widget/generate_themed_views.py
Normal file
66
mobile/android/base/widget/generate_themed_views.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/bin/python
|
||||
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# 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/.
|
||||
|
||||
'''
|
||||
Script to generate Themed*.java source files for Fennec.
|
||||
|
||||
This script runs the preprocessor on a input template and writes
|
||||
updated files into the source directory.
|
||||
|
||||
To update the themed views, update the input template
|
||||
(ThemedView.java.frag) and run the script. Use version control to
|
||||
examine the differences, and don't forget to commit the changes to the
|
||||
template and the outputs.
|
||||
'''
|
||||
|
||||
from __future__ import (
|
||||
print_function,
|
||||
unicode_literals,
|
||||
)
|
||||
|
||||
import os
|
||||
|
||||
from mozbuild.preprocessor import Preprocessor
|
||||
|
||||
__DIR__ = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
template = os.path.join(__DIR__, 'ThemedView.java.frag')
|
||||
dest_format_string = 'Themed%(VIEW_NAME_SUFFIX)s.java'
|
||||
|
||||
views = [
|
||||
dict(VIEW_NAME_SUFFIX='EditText',
|
||||
BASE_TYPE='android.widget.EditText',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
dict(VIEW_NAME_SUFFIX='ImageButton',
|
||||
BASE_TYPE='android.widget.ImageButton',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
dict(VIEW_NAME_SUFFIX='ImageView',
|
||||
BASE_TYPE='android.widget.ImageView',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
dict(VIEW_NAME_SUFFIX='LinearLayout',
|
||||
BASE_TYPE='android.widget.LinearLayout'),
|
||||
dict(VIEW_NAME_SUFFIX='RelativeLayout',
|
||||
BASE_TYPE='android.widget.RelativeLayout',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
dict(VIEW_NAME_SUFFIX='TextSwitcher',
|
||||
BASE_TYPE='android.widget.TextSwitcher'),
|
||||
dict(VIEW_NAME_SUFFIX='TextView',
|
||||
BASE_TYPE='android.widget.TextView',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
dict(VIEW_NAME_SUFFIX='View',
|
||||
BASE_TYPE='android.view.View',
|
||||
STYLE_CONSTRUCTOR=1),
|
||||
]
|
||||
|
||||
for view in views:
|
||||
pp = Preprocessor(defines=view, marker='//#')
|
||||
|
||||
dest = os.path.join(__DIR__, dest_format_string % view)
|
||||
with open(template, 'rU') as input:
|
||||
with open(dest, 'wt') as output:
|
||||
pp.processFile(input=input, output=output)
|
||||
print('%s' % dest)
|
@ -194,6 +194,14 @@
|
||||
|
||||
-keep class **.R$*
|
||||
|
||||
# Keep classes, and all their contents, compiled before mozglue.RobocopTarget.
|
||||
-keep class org.mozilla.gecko.AppConstants {
|
||||
*;
|
||||
}
|
||||
-keep class org.mozilla.gecko.SysInfo {
|
||||
*;
|
||||
}
|
||||
|
||||
# Disable obfuscation because it makes exception stack traces more difficult to read.
|
||||
-dontobfuscate
|
||||
|
||||
|
@ -94,7 +94,7 @@ let Bookmarks = Object.freeze({
|
||||
TYPE_SEPARATOR: 3,
|
||||
|
||||
/**
|
||||
* Default index used to append a bookmark-item at the end of a folder.
|
||||
* Default index used to append a bookmark-item at the end of a folder.
|
||||
* This should stay consistent with nsINavBookmarksService.idl
|
||||
*/
|
||||
DEFAULT_INDEX: -1,
|
||||
@ -238,7 +238,7 @@ let Bookmarks = Object.freeze({
|
||||
let updateInfo = validateBookmarkObject(info,
|
||||
{ guid: { required: true }
|
||||
, index: { requiredIf: b => b.hasOwnProperty("parentGuid")
|
||||
, validIf: b => b.index >= 0 }
|
||||
, validIf: b => b.index >= 0 || b.index == this.DEFAULT_INDEX }
|
||||
, parentGuid: { requiredIf: b => b.hasOwnProperty("index") }
|
||||
});
|
||||
|
||||
@ -307,9 +307,15 @@ let Bookmarks = Object.freeze({
|
||||
// the same container. Thus we know it exists.
|
||||
if (!parent)
|
||||
parent = yield fetchBookmark({ guid: item.parentGuid });
|
||||
// Set index in the appending case.
|
||||
if (updateInfo.index > parent._childCount)
|
||||
updateInfo.index = parent._childCount;
|
||||
|
||||
if (updateInfo.index >= parent._childCount ||
|
||||
updateInfo.index == this.DEFAULT_INDEX) {
|
||||
updateInfo.index = parent._childCount;
|
||||
|
||||
// Fix the index when moving within the same container.
|
||||
if (parent.guid == item.parentGuid)
|
||||
updateInfo.index--;
|
||||
}
|
||||
}
|
||||
|
||||
let updatedItem = yield updateBookmark(updateInfo, item, parent);
|
||||
@ -364,14 +370,14 @@ let Bookmarks = Object.freeze({
|
||||
updatedItem.guid,
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
// If the item was move, notify onItemMoved.
|
||||
// If the item was moved, notify onItemMoved.
|
||||
if (item.parentGuid != updatedItem.parentGuid ||
|
||||
item.index != updatedItem.index) {
|
||||
notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
|
||||
item.index, updatedItem._parentId,
|
||||
updatedItem.index, updatedItem.type,
|
||||
updatedItem.guid, item.parentGuid,
|
||||
updatedItem.newParentGuid ]);
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
|
||||
// Remove non-enumerable properties.
|
||||
@ -975,8 +981,10 @@ function* fetchBookmarksByURL(info) {
|
||||
LEFT JOIN moz_keywords k ON k.id = b.keyword_id
|
||||
LEFT JOIN moz_places h ON h.id = b.fk
|
||||
WHERE h.url = :url
|
||||
AND _grandParentId <> :tags_folder
|
||||
ORDER BY b.lastModified DESC
|
||||
`, { url: info.url.href });
|
||||
`, { url: info.url.href,
|
||||
tags_folder: PlacesUtils.tagsFolderId });
|
||||
|
||||
return rows.length ? rowsToItemsArray(rows) : null;
|
||||
}
|
||||
@ -1158,7 +1166,7 @@ function rowsToItemsArray(rows) {
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1267,7 +1275,7 @@ function validateBookmarkObject(input, behavior={}) {
|
||||
}
|
||||
}
|
||||
if (required.size > 0)
|
||||
throw new Error(`The following properties were expected: ${[...required].join(", ")}`);
|
||||
throw new Error(`The following properties were expected: ${[...required].join(", ")}`);
|
||||
return normalizedInput;
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,8 @@ this.EXPORTED_SYMBOLS = ["PlacesTransactions"];
|
||||
* a live bookmark is associated.
|
||||
* - tag - a string.
|
||||
* - tags: an array of strings.
|
||||
* - guid, parentGuid, newParentGuid: a valid places GUID string.
|
||||
* - guid, parentGuid, newParentGuid: a valid Places GUID string.
|
||||
* - guids: an array of valid Places GUID strings.
|
||||
* - title: a string
|
||||
* - index, newIndex: the position of an item in its containing folder,
|
||||
* starting from 0.
|
||||
@ -463,7 +464,6 @@ Enqueuer.prototype = {
|
||||
get promise() this._promise
|
||||
};
|
||||
|
||||
|
||||
let TransactionsManager = {
|
||||
// See the documentation at the top of this file. |transact| calls are not
|
||||
// serialized with |batch| calls.
|
||||
@ -869,6 +869,7 @@ DefineTransaction.defineInputProps(["index", "newIndex"],
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
DefineTransaction.defineInputProps(["annotation"],
|
||||
DefineTransaction.annotationObjectValidate);
|
||||
DefineTransaction.defineArrayInputProp("guids", "guid");
|
||||
DefineTransaction.defineArrayInputProp("urls", "url");
|
||||
DefineTransaction.defineArrayInputProp("tags", "tag");
|
||||
DefineTransaction.defineArrayInputProp("annotations", "annotation");
|
||||
@ -1269,29 +1270,40 @@ PT.EditUrl.prototype = Object.seal({
|
||||
*
|
||||
* Required Input Properties: guid, annotationObject
|
||||
*/
|
||||
PT.Annotate = DefineTransaction(["guid", "annotations"]);
|
||||
PT.Annotate = DefineTransaction(["guids", "annotations"]);
|
||||
PT.Annotate.prototype = {
|
||||
execute: function* (aGuid, aNewAnnos) {
|
||||
let itemId = yield PlacesUtils.promiseItemId(aGuid);
|
||||
let currentAnnos = PlacesUtils.getAnnotationsForItem(itemId);
|
||||
let undoAnnos = [];
|
||||
for (let newAnno of aNewAnnos) {
|
||||
let currentAnno = currentAnnos.find( a => a.name == newAnno.name );
|
||||
if (!!currentAnno) {
|
||||
undoAnnos.push(currentAnno);
|
||||
}
|
||||
else {
|
||||
// An unset value removes the annotation.
|
||||
undoAnnos.push({ name: newAnno.name });
|
||||
*execute(aGuids, aNewAnnos) {
|
||||
let undoAnnosForItem = new Map(); // itemId => undoAnnos;
|
||||
for (let guid of aGuids) {
|
||||
let itemId = yield PlacesUtils.promiseItemId(guid);
|
||||
let currentAnnos = PlacesUtils.getAnnotationsForItem(itemId);
|
||||
|
||||
let undoAnnos = [];
|
||||
for (let newAnno of aNewAnnos) {
|
||||
let currentAnno = currentAnnos.find(a => a.name == newAnno.name);
|
||||
if (!!currentAnno) {
|
||||
undoAnnos.push(currentAnno);
|
||||
}
|
||||
else {
|
||||
// An unset value removes the annotation.
|
||||
undoAnnos.push({ name: newAnno.name });
|
||||
}
|
||||
}
|
||||
undoAnnosForItem.set(itemId, undoAnnos);
|
||||
|
||||
PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
|
||||
}
|
||||
|
||||
PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
|
||||
this.undo = () => {
|
||||
PlacesUtils.setAnnotationsForItem(itemId, undoAnnos);
|
||||
this.undo = function() {
|
||||
for (let [itemId, undoAnnos] of undoAnnosForItem) {
|
||||
PlacesUtils.setAnnotationsForItem(itemId, undoAnnos);
|
||||
}
|
||||
};
|
||||
this.redo = () => {
|
||||
PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
|
||||
this.redo = function* () {
|
||||
for (let guid of aGuids) {
|
||||
let itemId = yield PlacesUtils.promiseItemId(guid);
|
||||
PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -1384,28 +1396,61 @@ PT.SortByName.prototype = {
|
||||
/**
|
||||
* Transaction for removing an item (any type).
|
||||
*
|
||||
* Required Input Properties: guid.
|
||||
* Required Input Properties: guids.
|
||||
*/
|
||||
PT.Remove = DefineTransaction(["guid"]);
|
||||
PT.Remove = DefineTransaction(["guids"]);
|
||||
PT.Remove.prototype = {
|
||||
execute: function* (aGuid) {
|
||||
const bms = PlacesUtils.bookmarks;
|
||||
*execute(aGuids) {
|
||||
function promiseBookmarksTree(guid) {
|
||||
try {
|
||||
return PlacesUtils.promiseBookmarksTree(guid);
|
||||
}
|
||||
catch(ex) {
|
||||
throw new Error("Failed to get info for the specified item (guid: " +
|
||||
guid + "). Ex: " + ex);
|
||||
}
|
||||
}
|
||||
let toRestore = [for (guid of aGuids) yield promiseBookmarksTree(guid)];
|
||||
|
||||
let itemInfo = null;
|
||||
try {
|
||||
itemInfo = yield PlacesUtils.promiseBookmarksTree(aGuid);
|
||||
}
|
||||
catch(ex) {
|
||||
throw new Error("Failed to get info for the specified item (guid: " +
|
||||
aGuid + "). Ex: " + ex);
|
||||
}
|
||||
PlacesUtils.bookmarks.removeItem(yield PlacesUtils.promiseItemId(aGuid));
|
||||
this.undo = createItemsFromBookmarksTree.bind(null, itemInfo, true);
|
||||
let removeThem = Task.async(function* () {
|
||||
for (let guid of aGuids) {
|
||||
PlacesUtils.bookmarks.removeItem(yield PlacesUtils.promiseItemId(guid));
|
||||
}
|
||||
});
|
||||
yield removeThem();
|
||||
|
||||
this.undo = Task.async(function* () {
|
||||
for (let info of toRestore) {
|
||||
yield createItemsFromBookmarksTree(info, true);
|
||||
}
|
||||
});
|
||||
this.redo = removeThem;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction for tagging a URI.
|
||||
* Transactions for removing all bookmarks for one or more urls.
|
||||
*
|
||||
* Required Input Properties: urls.
|
||||
*/
|
||||
PT.RemoveBookmarksForUrls = DefineTransaction(["urls"]);
|
||||
PT.RemoveBookmarksForUrls.prototype = {
|
||||
*execute(aUrls) {
|
||||
let guids = [];
|
||||
for (let url of aUrls) {
|
||||
yield PlacesUtils.bookmarks.fetch({ url }, info => {
|
||||
guids.push(info.guid);
|
||||
});
|
||||
}
|
||||
let removeTxn = TransactionsHistory.getRawTransaction(PT.Remove(guids));
|
||||
yield removeTxn.execute();
|
||||
this.undo = removeTxn.undo.bind(removeTxn);
|
||||
this.redo = removeTxn.redo.bind(removeTxn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction for tagging urls.
|
||||
*
|
||||
* Required Input Properties: urls, tags.
|
||||
*/
|
||||
|
@ -101,7 +101,7 @@ interface mozILivemarkCallback : nsISupports
|
||||
in mozILivemark aLivemark);
|
||||
};
|
||||
|
||||
[scriptable, uuid(6e40d5b1-ce48-4458-8b68-6bee17d30ef3)]
|
||||
[scriptable, uuid(E52B2273-729D-4EBC-A039-E9CD9E18FF86)]
|
||||
interface mozILivemarkInfo : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -129,6 +129,11 @@ interface mozILivemarkInfo : nsISupports
|
||||
*/
|
||||
readonly attribute long index;
|
||||
|
||||
/**
|
||||
* Time this livemark was created.
|
||||
*/
|
||||
readonly attribute PRTime dateAdded;
|
||||
|
||||
/**
|
||||
* Time this livemark's details were last modified. Doesn't track changes to
|
||||
* the livemark contents.
|
||||
|
@ -113,8 +113,7 @@ interface nsINavBookmarkObserver : nsISupports
|
||||
* For certain properties, this is set to the new value of the
|
||||
* property (see the list below).
|
||||
* @param aLastModified
|
||||
* If lastModified changed, this parameter is the new value, otherwise
|
||||
* it's set to 0.
|
||||
* The updated last-modified value.
|
||||
* @param aItemType
|
||||
* The type of the item to be removed (see TYPE_* constants below).
|
||||
* @param aParentId
|
||||
|
@ -80,7 +80,8 @@ LivemarkService.prototype = {
|
||||
AND n.name = ${aAnnoParam}`;
|
||||
}
|
||||
|
||||
return `SELECT b.id, b.title, b.parent, b.position, b.guid, b.lastModified,
|
||||
return `SELECT b.id, b.title, b.parent, b.position, b.guid,
|
||||
b.dateAdded, b.lastModified,
|
||||
( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI,
|
||||
( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI
|
||||
FROM moz_bookmarks b
|
||||
@ -106,6 +107,7 @@ LivemarkService.prototype = {
|
||||
title: row.getResultByName("title"),
|
||||
parentId: row.getResultByName("parent"),
|
||||
index: row.getResultByName("position"),
|
||||
dateAdded: row.getResultByName("dateAdded"),
|
||||
lastModified: row.getResultByName("lastModified"),
|
||||
feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
|
||||
siteURI: siteURL ? NetUtil.newURI(siteURL) : null });
|
||||
@ -202,11 +204,15 @@ LivemarkService.prototype = {
|
||||
, feedURI: aLivemarkInfo.feedURI
|
||||
, siteURI: aLivemarkInfo.siteURI
|
||||
, guid: aLivemarkInfo.guid
|
||||
, dateAdded: aLivemarkInfo.dateAdded
|
||||
, lastModified: aLivemarkInfo.lastModified
|
||||
});
|
||||
if (this._itemAdded && this._itemAdded.id == livemark.id) {
|
||||
livemark.index = this._itemAdded.index;
|
||||
livemark.guid = this._itemAdded.guid;
|
||||
if (!aLivemarkInfo.dateAdded) {
|
||||
livemark.dateAdded = this._itemAdded.dateAdded;
|
||||
}
|
||||
if (!aLivemarkInfo.lastModified) {
|
||||
livemark.lastModified = this._itemAdded.lastModified;
|
||||
}
|
||||
@ -411,6 +417,7 @@ LivemarkService.prototype = {
|
||||
this._itemAdded = { id: aItemId
|
||||
, guid: aGUID
|
||||
, index: aIndex
|
||||
, dateAdded: aDateAdded
|
||||
, lastModified: aDateAdded
|
||||
};
|
||||
}
|
||||
@ -422,11 +429,15 @@ LivemarkService.prototype = {
|
||||
if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
|
||||
if (this._itemAdded && this._itemAdded.id == aItemId) {
|
||||
this._itemAdded.lastModified = aLastModified;
|
||||
}
|
||||
}
|
||||
if (aItemId in this._livemarks) {
|
||||
if (aProperty == "title") {
|
||||
this._livemarks[aItemId].title = aValue;
|
||||
}
|
||||
else if (aProperty == "dateAdded") {
|
||||
this._livemark[aItemId].dateAdded = parseInt(aValue, 10);
|
||||
}
|
||||
|
||||
this._livemarks[aItemId].lastModified = aLastModified;
|
||||
}
|
||||
}
|
||||
@ -526,8 +537,8 @@ function Livemark(aLivemarkInfo)
|
||||
this._nodes = new Map();
|
||||
|
||||
this._guid = "";
|
||||
this._dateAdded = 0;
|
||||
this._lastModified = 0;
|
||||
|
||||
this.loadGroup = null;
|
||||
this.feedURI = null;
|
||||
this.siteURI = null;
|
||||
@ -539,6 +550,7 @@ function Livemark(aLivemarkInfo)
|
||||
this.guid = aLivemarkInfo.guid;
|
||||
this.feedURI = aLivemarkInfo.feedURI;
|
||||
this.siteURI = aLivemarkInfo.siteURI;
|
||||
this.dateAdded = aLivemarkInfo.dateAdded;
|
||||
this.lastModified = aLivemarkInfo.lastModified;
|
||||
}
|
||||
else {
|
||||
@ -551,6 +563,10 @@ function Livemark(aLivemarkInfo)
|
||||
if (aLivemarkInfo.siteURI) {
|
||||
this.writeSiteURI(aLivemarkInfo.siteURI);
|
||||
}
|
||||
if (aLivemarkInfo.dateAdded) {
|
||||
this.dateAdded = aLivemarkInfo.dateAdded;
|
||||
PlacesUtils.bookmarks.setItemDateAdded(this.id, this.dateAdded);
|
||||
}
|
||||
// Last modified time must be the last change.
|
||||
if (aLivemarkInfo.lastModified) {
|
||||
this.lastModified = aLivemarkInfo.lastModified;
|
||||
@ -614,16 +630,13 @@ Livemark.prototype = {
|
||||
this.siteURI = aSiteURI;
|
||||
},
|
||||
|
||||
set guid(aGUID) {
|
||||
this._guid = aGUID;
|
||||
return aGUID;
|
||||
},
|
||||
set guid(aGUID) this._guid = aGUID,
|
||||
get guid() this._guid,
|
||||
|
||||
set lastModified(aLastModified) {
|
||||
this._lastModified = aLastModified;
|
||||
return aLastModified;
|
||||
},
|
||||
set dateAdded(aDateAdded) this._dateAdded = aDateAdded,
|
||||
get dateAdded() this._dateAdded,
|
||||
|
||||
set lastModified(aLastModified) this._lastModified = aLastModified,
|
||||
get lastModified() this._lastModified,
|
||||
|
||||
/**
|
||||
|
@ -210,6 +210,11 @@ public:
|
||||
nsresult GetDescendantFolders(int64_t aFolderId,
|
||||
nsTArray<int64_t>& aDescendantFoldersArray);
|
||||
|
||||
static const int32_t kGetChildrenIndex_Guid;
|
||||
static const int32_t kGetChildrenIndex_Position;
|
||||
static const int32_t kGetChildrenIndex_Type;
|
||||
static const int32_t kGetChildrenIndex_PlaceID;
|
||||
|
||||
private:
|
||||
static nsNavBookmarks* gBookmarksService;
|
||||
|
||||
@ -356,11 +361,6 @@ private:
|
||||
|
||||
int64_t RecursiveFindRedirectedBookmark(int64_t aPlaceId);
|
||||
|
||||
static const int32_t kGetChildrenIndex_Position;
|
||||
static const int32_t kGetChildrenIndex_Type;
|
||||
static const int32_t kGetChildrenIndex_PlaceID;
|
||||
static const int32_t kGetChildrenIndex_Guid;
|
||||
|
||||
class RemoveFolderTransaction MOZ_FINAL : public nsITransaction {
|
||||
public:
|
||||
explicit RemoveFolderTransaction(int64_t aID) : mID(aID) {}
|
||||
|
@ -1512,7 +1512,8 @@ PlacesSQLQueryBuilder::SelectAsURI()
|
||||
"SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, "
|
||||
"h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, "
|
||||
"b2.dateAdded, b2.lastModified, b2.parent, ") +
|
||||
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
|
||||
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
|
||||
"b2.guid, b2.position, b2.type, b2.fk "
|
||||
"FROM moz_bookmarks b2 "
|
||||
"JOIN (SELECT b.fk "
|
||||
"FROM moz_bookmarks b "
|
||||
@ -1536,7 +1537,8 @@ PlacesSQLQueryBuilder::SelectAsURI()
|
||||
"SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, "
|
||||
"h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
|
||||
"b.dateAdded, b.lastModified, b.parent, ") +
|
||||
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
|
||||
tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid,"
|
||||
"b.guid, b.position, b.type, b.fk "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_places h ON b.fk = h.id "
|
||||
"LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
|
||||
@ -3902,36 +3904,40 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
|
||||
}
|
||||
|
||||
if (IsQueryURI(url)) {
|
||||
// special case "place:" URIs: turn them into containers
|
||||
|
||||
// We should never expose the history title for query nodes if the
|
||||
// bookmark-item's title is set to null (the history title may be the
|
||||
// query string without the place: prefix). Thus we call getItemTitle
|
||||
// explicitly. Doing this in the SQL query would be less performant since
|
||||
// it should be done for all results rather than only for queries.
|
||||
if (itemId != -1) {
|
||||
nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
|
||||
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
rv = bookmarks->GetItemTitle(itemId, title);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Special case "place:" URIs: turn them into containers.
|
||||
nsRefPtr<nsNavHistoryResultNode> resultNode;
|
||||
rv = QueryRowToResult(itemId, url, title, accessCount, time, favicon,
|
||||
getter_AddRefs(resultNode));
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
|
||||
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
|
||||
if (itemId != -1) {
|
||||
rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
|
||||
resultNode->mBookmarkGuid);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// We should never expose the history title for query nodes if the
|
||||
// bookmark-item's title is set to null (the history title may be the
|
||||
// query string without the place: prefix). Thus we call getItemTitle
|
||||
// explicitly. Doing this in the SQL query would be less performant since
|
||||
// it should be done for all results rather than only for queries.
|
||||
nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
|
||||
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
rv = bookmarks->GetItemTitle(itemId, resultNode->mTitle);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (itemId != -1 ||
|
||||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
|
||||
// RESULTS_AS_TAG_QUERY has date columns
|
||||
resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
|
||||
resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
|
||||
}
|
||||
else if (resultNode->IsFolder()) {
|
||||
// If it's a simple folder node (i.e. a shortcut to another folder), apply
|
||||
// our options for it. However, if the parent type was tag query, we do not
|
||||
// apply them, because it would not yield any results.
|
||||
resultNode->GetAsContainer()->mOptions = aOptions;
|
||||
if (resultNode->IsFolder()) {
|
||||
// If it's a simple folder node (i.e. a shortcut to another folder), apply
|
||||
// our options for it. However, if the parent type was tag query, we do not
|
||||
// apply them, because it would not yield any results.
|
||||
resultNode->GetAsContainer()->mOptions = aOptions;
|
||||
}
|
||||
}
|
||||
|
||||
resultNode.forget(aResult);
|
||||
@ -3946,6 +3952,10 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
|
||||
resultNode->mFolderId = parentId;
|
||||
resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
|
||||
resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
|
||||
|
||||
rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
|
||||
resultNode->mBookmarkGuid);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency);
|
||||
@ -4135,7 +4145,8 @@ nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptio
|
||||
"SELECT b.fk, h.url, COALESCE(b.title, h.title), "
|
||||
"h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
|
||||
"b.dateAdded, b.lastModified, b.parent, "
|
||||
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
|
||||
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
|
||||
"b.guid, b.position, b.type, b.fk "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_places h ON b.fk = h.id "
|
||||
"LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
|
||||
|
@ -3584,6 +3584,9 @@ nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
|
||||
node = new nsNavHistorySeparatorResultNode();
|
||||
NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
|
||||
node->mItemId = aItemId;
|
||||
node->mBookmarkGuid = aGUID;
|
||||
node->mDateAdded = aDateAdded;
|
||||
node->mLastModified = aDateAdded;
|
||||
}
|
||||
|
||||
node->mBookmarkIndex = aIndex;
|
||||
|
@ -255,6 +255,9 @@ add_task(function* fetch_byurl() {
|
||||
title: "a bookmark" });
|
||||
checkBookmarkObject(bm1);
|
||||
|
||||
// Also ensure that fecth-by-url excludes the tags folder.
|
||||
PlacesUtils.tagging.tagURI(uri(bm1.url.href), ["Test Tag"]);
|
||||
|
||||
let bm2 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
|
||||
gAccumulator.callback);
|
||||
checkBookmarkObject(bm2);
|
||||
@ -293,6 +296,9 @@ add_task(function* fetch_byurl() {
|
||||
Assert.equal(gAccumulator.results.length, 2);
|
||||
gAccumulator.results.forEach(checkBookmarkObject);
|
||||
Assert.deepEqual(gAccumulator.results[0], bm5);
|
||||
|
||||
// cleanup
|
||||
PlacesUtils.tagging.untagURI(uri(bm1.url.href), ["Test Tag"]);
|
||||
});
|
||||
|
||||
add_task(function* fetch_bykeyword_nonexisting() {
|
||||
|
@ -110,10 +110,6 @@ add_task(function* insert_bookmark_tag_notification() {
|
||||
arguments: [ tagId, tagParentId, tag.index, tag.type,
|
||||
tag.url, null, tag.dateAdded,
|
||||
tag.guid, tag.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ tagId, "tags", false, "",
|
||||
tag.lastModified, tag.type, tagParentId,
|
||||
tag.guid, tag.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId, "tags", false, "",
|
||||
bm.lastModified, bm.type, parentId,
|
||||
@ -189,6 +185,65 @@ add_task(function* update_bookmark_keyword() {
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_move_same_folder() {
|
||||
// Ensure there are at least two items in place (others test do so for us,
|
||||
// but we don't have to depend on that).
|
||||
let sep = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: new URL("http://move.example.com/") });
|
||||
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let bmParentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
let bmOldIndex = bm.index;
|
||||
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
index: 0 });
|
||||
Assert.equal(bm.index, 0);
|
||||
observer.check([ { name: "onItemMoved",
|
||||
arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
|
||||
bm.type, bm.guid, bm.parentGuid, bm.parentGuid ] }
|
||||
]);
|
||||
|
||||
// Test that we get the right index for DEFAULT_INDEX input.
|
||||
bmOldIndex = 0;
|
||||
observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
|
||||
Assert.ok(bm.index > 0);
|
||||
observer.check([ { name: "onItemMoved",
|
||||
arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
|
||||
bm.type, bm.guid, bm.parentGuid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_move_different_folder() {
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: new URL("http://move.example.com/") });
|
||||
let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let bmOldParentId = PlacesUtils.unfiledBookmarksFolderId;
|
||||
let bmOldIndex = bm.index;
|
||||
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
parentGuid: folder.guid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
|
||||
Assert.equal(bm.index, 0);
|
||||
let bmNewParentId = yield PlacesUtils.promiseItemId(folder.guid);
|
||||
observer.check([ { name: "onItemMoved",
|
||||
arguments: [ bmItemId, bmOldParentId, bmOldIndex, bmNewParentId,
|
||||
bm.index, bm.type, bm.guid,
|
||||
PlacesUtils.bookmarks.unfiledGuid,
|
||||
bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* remove_bookmark() {
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
|
@ -402,6 +402,71 @@ add_task(function* update_move() {
|
||||
Assert.equal(descendant.index, 1);
|
||||
});
|
||||
|
||||
add_task(function* update_move_append() {
|
||||
let folder_a =
|
||||
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER });
|
||||
checkBookmarkObject(folder_a);
|
||||
let folder_b =
|
||||
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER });
|
||||
checkBookmarkObject(folder_b);
|
||||
|
||||
/* folder_a: [sep_1, sep_2, sep_3], folder_b: [] */
|
||||
let sep_1 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
|
||||
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
|
||||
checkBookmarkObject(sep_1);
|
||||
let sep_2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
|
||||
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
|
||||
checkBookmarkObject(sep_2);
|
||||
let sep_3 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
|
||||
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
|
||||
checkBookmarkObject(sep_3);
|
||||
|
||||
function ensurePosition(info, parentGuid, index) {
|
||||
checkBookmarkObject(info);
|
||||
Assert.equal(info.parentGuid, parentGuid);
|
||||
Assert.equal(info.index, index);
|
||||
}
|
||||
|
||||
// folder_a: [sep_2, sep_3, sep_1], folder_b: []
|
||||
sep_1.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
|
||||
// Note sep_1 includes parentGuid even though we're not moving the item to
|
||||
// another folder
|
||||
sep_1 = yield PlacesUtils.bookmarks.update(sep_1);
|
||||
ensurePosition(sep_1, folder_a.guid, 2);
|
||||
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
|
||||
ensurePosition(sep_2, folder_a.guid, 0);
|
||||
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
|
||||
ensurePosition(sep_3, folder_a.guid, 1);
|
||||
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
|
||||
ensurePosition(sep_1, folder_a.guid, 2);
|
||||
|
||||
// folder_a: [sep_2, sep_1], folder_b: [sep_3]
|
||||
sep_3.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
|
||||
sep_3.parentGuid = folder_b.guid;
|
||||
sep_3 = yield PlacesUtils.bookmarks.update(sep_3);
|
||||
ensurePosition(sep_3, folder_b.guid, 0);
|
||||
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
|
||||
ensurePosition(sep_2, folder_a.guid, 0);
|
||||
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
|
||||
ensurePosition(sep_1, folder_a.guid, 1);
|
||||
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
|
||||
ensurePosition(sep_3, folder_b.guid, 0);
|
||||
|
||||
// folder_a: [sep_1], folder_b: [sep_3, sep_2]
|
||||
sep_2.index = Number.MAX_SAFE_INTEGER;
|
||||
sep_2.parentGuid = folder_b.guid;
|
||||
sep_2 = yield PlacesUtils.bookmarks.update(sep_2);
|
||||
ensurePosition(sep_2, folder_b.guid, 1);
|
||||
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
|
||||
ensurePosition(sep_1, folder_a.guid, 0);
|
||||
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
|
||||
ensurePosition(sep_3, folder_b.guid, 0);
|
||||
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
|
||||
ensurePosition(sep_2, folder_b.guid, 1);
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
42
toolkit/components/places/tests/unit/test_1085291.js
Normal file
42
toolkit/components/places/tests/unit/test_1085291.js
Normal file
@ -0,0 +1,42 @@
|
||||
add_task(function* () {
|
||||
// test that nodes inserted by incremental update for bookmarks of all types
|
||||
// have the extra bookmark properties (bookmarkGuid, dateAdded, lastModified).
|
||||
|
||||
// getFolderContents opens the root node.
|
||||
let root = PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
|
||||
|
||||
function* insertAndTest(bmInfo) {
|
||||
bmInfo = yield PlacesUtils.bookmarks.insert(bmInfo);
|
||||
let node = root.getChild(root.childCount - 1);
|
||||
Assert.equal(node.bookmarkGuid, bmInfo.guid);
|
||||
Assert.equal(node.dateAdded, bmInfo.dateAdded * 1000);
|
||||
Assert.equal(node.lastModified, bmInfo.lastModified * 1000);
|
||||
}
|
||||
|
||||
// Normal bookmark.
|
||||
yield insertAndTest({ parentGuid: root.bookmarkGuid
|
||||
, type: PlacesUtils.bookmarks.TYPE_BOOKMARK
|
||||
, title: "Test Bookmark"
|
||||
, url: "http://test.url.tld" });
|
||||
|
||||
// place: query
|
||||
yield insertAndTest({ parentGuid: root.bookmarkGuid
|
||||
, type: PlacesUtils.bookmarks.TYPE_BOOKMARK
|
||||
, title: "Test Query"
|
||||
, url: "place:folder=BOOKMARKS_MENU" });
|
||||
|
||||
// folder
|
||||
yield insertAndTest({ parentGuid: root.bookmarkGuid
|
||||
, type: PlacesUtils.bookmarks.TYPE_FOLDER
|
||||
, title: "Test Folder" });
|
||||
|
||||
// separator
|
||||
yield insertAndTest({ parentGuid: root.bookmarkGuid
|
||||
, type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
|
||||
|
||||
root.containerOpen = false;
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -4,10 +4,11 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const bmsvc = PlacesUtils.bookmarks;
|
||||
const tagssvc = PlacesUtils.tagging;
|
||||
const annosvc = PlacesUtils.annotations;
|
||||
const PT = PlacesTransactions;
|
||||
const bmsvc = PlacesUtils.bookmarks;
|
||||
const tagssvc = PlacesUtils.tagging;
|
||||
const annosvc = PlacesUtils.annotations;
|
||||
const PT = PlacesTransactions;
|
||||
const rootGuid = PlacesUtils.bookmarks.rootGuid;
|
||||
|
||||
Components.utils.importGlobalProperties(["URL"]);
|
||||
|
||||
@ -106,9 +107,6 @@ observer.reset();
|
||||
// index at which items should begin
|
||||
let bmStartIndex = 0;
|
||||
|
||||
// get bookmarks root id
|
||||
let root = PlacesUtils.bookmarksMenuFolderId;
|
||||
|
||||
function run_test() {
|
||||
bmsvc.addObserver(observer, false);
|
||||
do_register_cleanup(function () {
|
||||
@ -248,9 +246,8 @@ function ensureTagsForURI(aURI, aTags) {
|
||||
do_check_true(aTags.every( t => tagsSet.indexOf(t) != -1 ));
|
||||
}
|
||||
|
||||
function* createTestFolderInfo(aTitle = "Test Folder") {
|
||||
return { parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, title: "Test Folder" };
|
||||
function createTestFolderInfo(aTitle = "Test Folder") {
|
||||
return { parentGuid: rootGuid, title: "Test Folder" };
|
||||
}
|
||||
|
||||
function isLivemarkTree(aTree) {
|
||||
@ -315,10 +312,12 @@ function* ensureEqualBookmarksTrees(aOriginal,
|
||||
yield ensureLivemarkCreatedByAddLivemark(aNew.guid);
|
||||
}
|
||||
|
||||
function* ensureBookmarksTreeRestoredCorrectly(aOriginalBookmarksTree) {
|
||||
let restoredTree =
|
||||
yield PlacesUtils.promiseBookmarksTree(aOriginalBookmarksTree.guid);
|
||||
yield ensureEqualBookmarksTrees(aOriginalBookmarksTree, restoredTree);
|
||||
function* ensureBookmarksTreeRestoredCorrectly(...aOriginalBookmarksTrees) {
|
||||
for (let originalTree of aOriginalBookmarksTrees) {
|
||||
let restoredTree =
|
||||
yield PlacesUtils.promiseBookmarksTree(originalTree.guid);
|
||||
yield ensureEqualBookmarksTrees(originalTree, restoredTree);
|
||||
}
|
||||
}
|
||||
|
||||
function* ensureNonExistent(...aGuids) {
|
||||
@ -338,7 +337,7 @@ add_task(function* test_recycled_transactions() {
|
||||
ensureUndoState(txns, undoPosition);
|
||||
}
|
||||
|
||||
let txn_a = PT.NewFolder(yield createTestFolderInfo());
|
||||
let txn_a = PT.NewFolder(createTestFolderInfo());
|
||||
yield txn_a.transact();
|
||||
ensureUndoState([[txn_a]], 0);
|
||||
yield ensureTransactThrowsFor(txn_a);
|
||||
@ -351,7 +350,7 @@ add_task(function* test_recycled_transactions() {
|
||||
ensureUndoState();
|
||||
ensureTransactThrowsFor(txn_a);
|
||||
|
||||
let txn_b = PT.NewFolder(yield createTestFolderInfo());
|
||||
let txn_b = PT.NewFolder(createTestFolderInfo());
|
||||
yield PT.batch(function* () {
|
||||
try {
|
||||
yield txn_a.transact();
|
||||
@ -375,7 +374,7 @@ add_task(function* test_recycled_transactions() {
|
||||
|
||||
add_task(function* test_new_folder_with_annotation() {
|
||||
const ANNO = { name: "TestAnno", value: "TestValue" };
|
||||
let folder_info = yield createTestFolderInfo();
|
||||
let folder_info = createTestFolderInfo();
|
||||
folder_info.index = bmStartIndex;
|
||||
folder_info.annotations = [ANNO];
|
||||
ensureUndoState();
|
||||
@ -410,7 +409,7 @@ add_task(function* test_new_folder_with_annotation() {
|
||||
});
|
||||
|
||||
add_task(function* test_new_bookmark() {
|
||||
let bm_info = { parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
let bm_info = { parentGuid: rootGuid
|
||||
, url: NetUtil.newURI("http://test_create_item.com")
|
||||
, index: bmStartIndex
|
||||
, title: "Test creating an item" };
|
||||
@ -447,7 +446,7 @@ add_task(function* test_new_bookmark() {
|
||||
});
|
||||
|
||||
add_task(function* test_merge_create_folder_and_item() {
|
||||
let folder_info = yield createTestFolderInfo();
|
||||
let folder_info = createTestFolderInfo();
|
||||
let bm_info = { url: NetUtil.newURI("http://test_create_item_to_folder.com")
|
||||
, title: "Test Bookmark"
|
||||
, index: bmStartIndex };
|
||||
@ -485,7 +484,7 @@ add_task(function* test_merge_create_folder_and_item() {
|
||||
});
|
||||
|
||||
add_task(function* test_move_items_to_folder() {
|
||||
let folder_a_info = yield createTestFolderInfo("Folder A");
|
||||
let folder_a_info = createTestFolderInfo("Folder A");
|
||||
let bkm_a_info = { url: new URL("http://test_move_items.com")
|
||||
, title: "Bookmark A" };
|
||||
let bkm_b_info = { url: NetUtil.newURI("http://test_move_items.com")
|
||||
@ -541,7 +540,7 @@ add_task(function* test_move_items_to_folder() {
|
||||
ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
|
||||
|
||||
// Test moving items between folders.
|
||||
let folder_b_info = yield createTestFolderInfo("Folder B");
|
||||
let folder_b_info = createTestFolderInfo("Folder B");
|
||||
let folder_b_txn = PT.NewFolder(folder_b_info);
|
||||
folder_b_info.guid = yield folder_b_txn.transact();
|
||||
ensureUndoState([ [folder_b_txn]
|
||||
@ -595,7 +594,7 @@ add_task(function* test_move_items_to_folder() {
|
||||
});
|
||||
|
||||
add_task(function* test_remove_folder() {
|
||||
let folder_level_1_info = yield createTestFolderInfo("Folder Level 1");
|
||||
let folder_level_1_info = createTestFolderInfo("Folder Level 1");
|
||||
let folder_level_2_info = { title: "Folder Level 2" };
|
||||
let [folder_level_1_txn,
|
||||
folder_level_2_txn] = yield PT.batch(function* () {
|
||||
@ -688,7 +687,7 @@ add_task(function* test_add_and_remove_bookmarks_with_additional_info() {
|
||||
, POST_DATA = "post_data"
|
||||
, ANNO = { name: "TestAnno", value: "TestAnnoValue" };
|
||||
|
||||
let folder_info = yield createTestFolderInfo();
|
||||
let folder_info = createTestFolderInfo();
|
||||
folder_info.guid = yield PT.NewFolder(folder_info).transact();
|
||||
let ensureTags = ensureTagsForURI.bind(null, testURI);
|
||||
|
||||
@ -803,7 +802,7 @@ add_task(function* test_add_and_remove_bookmarks_with_additional_info() {
|
||||
});
|
||||
|
||||
add_task(function* test_creating_and_removing_a_separator() {
|
||||
let folder_info = yield createTestFolderInfo();
|
||||
let folder_info = createTestFolderInfo();
|
||||
let separator_info = {};
|
||||
let undoEntries = [];
|
||||
|
||||
@ -872,7 +871,7 @@ add_task(function* test_creating_and_removing_a_separator() {
|
||||
add_task(function* test_add_and_remove_livemark() {
|
||||
let createLivemarkTxn = PT.NewLivemark(
|
||||
{ feedUrl: NetUtil.newURI("http://test.remove.livemark")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, parentGuid: rootGuid
|
||||
, title: "Test Remove Livemark" });
|
||||
let guid = yield createLivemarkTxn.transact();
|
||||
let originalInfo = yield PlacesUtils.promiseBookmarksTree(guid);
|
||||
@ -913,7 +912,7 @@ add_task(function* test_add_and_remove_livemark() {
|
||||
});
|
||||
|
||||
add_task(function* test_edit_title() {
|
||||
let bm_info = { parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
let bm_info = { parentGuid: rootGuid
|
||||
, url: NetUtil.newURI("http://test_create_item.com")
|
||||
, title: "Original Title" };
|
||||
|
||||
@ -951,10 +950,7 @@ add_task(function* test_edit_title() {
|
||||
add_task(function* test_edit_url() {
|
||||
let oldURI = NetUtil.newURI("http://old.test_editing_item_uri.com/");
|
||||
let newURI = NetUtil.newURI("http://new.test_editing_item_uri.com/");
|
||||
let bm_info = { parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, url: oldURI
|
||||
, tags: ["TestTag"]};
|
||||
|
||||
let bm_info = { parentGuid: rootGuid, url: oldURI, tags: ["TestTag"] };
|
||||
function ensureURIAndTags(aPreChangeURI, aPostChangeURI, aOLdURITagsPreserved) {
|
||||
ensureItemsChanged({ guid: bm_info.guid
|
||||
, property: "uri"
|
||||
@ -1016,8 +1012,8 @@ add_task(function* test_edit_url() {
|
||||
});
|
||||
|
||||
add_task(function* test_edit_keyword() {
|
||||
let bm_info = { parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, url: NetUtil.newURI("http://test.edit.keyword") };
|
||||
let bm_info = { parentGuid: rootGuid
|
||||
, url: NetUtil.newURI("http://test.edit.keyword") };
|
||||
const KEYWORD = "test_keyword";
|
||||
bm_info.guid = yield PT.NewBookmark(bm_info).transact();
|
||||
function ensureKeywordChange(aCurrentKeyword = "") {
|
||||
@ -1054,9 +1050,9 @@ add_task(function* test_edit_keyword() {
|
||||
add_task(function* test_tag_uri() {
|
||||
// This also tests passing uri specs.
|
||||
let bm_info_a = { url: "http://bookmarked.uri"
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root) };
|
||||
, parentGuid: rootGuid };
|
||||
let bm_info_b = { url: NetUtil.newURI("http://bookmarked2.uri")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root) };
|
||||
, parentGuid: rootGuid };
|
||||
let unbookmarked_uri = NetUtil.newURI("http://un.bookmarked.uri");
|
||||
|
||||
function* promiseIsBookmarked(aURI) {
|
||||
@ -1128,10 +1124,10 @@ add_task(function* test_tag_uri() {
|
||||
|
||||
add_task(function* test_untag_uri() {
|
||||
let bm_info_a = { url: NetUtil.newURI("http://bookmarked.uri")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, parentGuid: rootGuid
|
||||
, tags: ["A", "B"] };
|
||||
let bm_info_b = { url: NetUtil.newURI("http://bookmarked2.uri")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, parentGuid: rootGuid
|
||||
, tag: "B" };
|
||||
|
||||
yield PT.batch(function* () {
|
||||
@ -1206,7 +1202,7 @@ add_task(function* test_untag_uri() {
|
||||
|
||||
add_task(function* test_annotate() {
|
||||
let bm_info = { url: NetUtil.newURI("http://test.item.annotation")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root) };
|
||||
, parentGuid: rootGuid };
|
||||
let anno_info = { name: "TestAnno", value: "TestValue" };
|
||||
function ensureAnnoState(aSet) {
|
||||
ensureAnnotationsSet(bm_info.guid,
|
||||
@ -1248,7 +1244,7 @@ add_task(function* test_annotate() {
|
||||
});
|
||||
|
||||
add_task(function* test_annotate_multiple() {
|
||||
let guid = yield PT.NewFolder(yield createTestFolderInfo()).transact();
|
||||
let guid = yield PT.NewFolder(createTestFolderInfo()).transact();
|
||||
let itemId = yield PlacesUtils.promiseItemId(guid);
|
||||
|
||||
function AnnoObj(aName, aValue) {
|
||||
@ -1302,7 +1298,7 @@ add_task(function* test_annotate_multiple() {
|
||||
});
|
||||
|
||||
add_task(function* test_sort_folder_by_name() {
|
||||
let folder_info = yield createTestFolderInfo();
|
||||
let folder_info = createTestFolderInfo();
|
||||
|
||||
let url = NetUtil.newURI("http://sort.by.name/");
|
||||
let preSep = [{ title: i, url } for (i of ["3","2","1"])];
|
||||
@ -1349,7 +1345,7 @@ add_task(function* test_sort_folder_by_name() {
|
||||
add_task(function* test_livemark_txns() {
|
||||
let livemark_info =
|
||||
{ feedUrl: NetUtil.newURI("http://test.feed.uri")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, parentGuid: rootGuid
|
||||
, title: "Test Livemark" };
|
||||
function ensureLivemarkAdded() {
|
||||
ensureItemsAdded({ guid: livemark_info.guid
|
||||
@ -1396,11 +1392,9 @@ add_task(function* test_livemark_txns() {
|
||||
});
|
||||
|
||||
add_task(function* test_copy() {
|
||||
let rootGuid = yield PlacesUtils.promiseItemGuid(root);
|
||||
|
||||
function* duplicate_and_test(aOriginalGuid) {
|
||||
yield duplicateGuid =
|
||||
yield PT.Copy({ guid: aOriginalGuid, newParentGuid: rootGuid }).transact();
|
||||
let txn = PT.Copy({ guid: aOriginalGuid, newParentGuid: rootGuid });
|
||||
yield duplicateGuid = yield txn.transact();
|
||||
let originalInfo = yield PlacesUtils.promiseBookmarksTree(aOriginalGuid);
|
||||
let duplicateInfo = yield PlacesUtils.promiseBookmarksTree(duplicateGuid);
|
||||
yield ensureEqualBookmarksTrees(originalInfo, duplicateInfo, false);
|
||||
@ -1436,9 +1430,9 @@ add_task(function* test_copy() {
|
||||
let sepTxn = PT.NewSeparator({ parentGuid: rootGuid, index: 1 });
|
||||
let livemarkTxn = PT.NewLivemark(
|
||||
{ feedUrl: new URL("http://test.feed.uri")
|
||||
, parentGuid: yield PlacesUtils.promiseItemGuid(root)
|
||||
, parentGuid: rootGuid
|
||||
, title: "Test Livemark", index: 1 });
|
||||
let emptyFolderTxn = PT.NewFolder(yield createTestFolderInfo());
|
||||
let emptyFolderTxn = PT.NewFolder(createTestFolderInfo());
|
||||
for (let txn of [livemarkTxn, sepTxn, emptyFolderTxn]) {
|
||||
let guid = yield txn.transact();
|
||||
yield duplicate_and_test(guid);
|
||||
@ -1446,8 +1440,7 @@ add_task(function* test_copy() {
|
||||
|
||||
// Test duplicating a folder having some contents.
|
||||
let filledFolderGuid = yield PT.batch(function *() {
|
||||
let folderGuid =
|
||||
yield PT.NewFolder(yield createTestFolderInfo()).transact();
|
||||
let folderGuid = yield PT.NewFolder(createTestFolderInfo()).transact();
|
||||
let nestedFolderGuid =
|
||||
yield PT.NewFolder({ parentGuid: folderGuid
|
||||
, title: "Nested Folder" }).transact();
|
||||
@ -1469,9 +1462,7 @@ add_task(function* test_copy() {
|
||||
});
|
||||
|
||||
add_task(function* test_array_input_for_batch() {
|
||||
let rootGuid = yield PlacesUtils.promiseItemGuid(root);
|
||||
|
||||
let folderTxn = PT.NewFolder(yield createTestFolderInfo());
|
||||
let folderTxn = PT.NewFolder(createTestFolderInfo());
|
||||
let folderGuid = yield folderTxn.transact();
|
||||
|
||||
let sep1_txn = PT.NewSeparator({ parentGuid: folderGuid });
|
||||
@ -1503,9 +1494,7 @@ add_task(function* test_array_input_for_batch() {
|
||||
});
|
||||
|
||||
add_task(function* test_copy_excluding_annotations() {
|
||||
let rootGuid = yield PlacesUtils.promiseItemGuid(root);
|
||||
|
||||
let folderInfo = yield createTestFolderInfo();
|
||||
let folderInfo = createTestFolderInfo();
|
||||
let anno = n => { return { name: n, value: 1 } };
|
||||
folderInfo.annotations = [anno("a"), anno("b"), anno("c")];
|
||||
let folderGuid = yield PT.NewFolder(folderInfo).transact();
|
||||
@ -1539,7 +1528,6 @@ add_task(function* test_copy_excluding_annotations() {
|
||||
});
|
||||
|
||||
add_task(function* test_invalid_uri_spec_throws() {
|
||||
let rootGuid = yield PlacesUtils.promiseItemGuid(root);
|
||||
Assert.throws(() =>
|
||||
PT.NewBookmark({ parentGuid: rootGuid
|
||||
, url: "invalid uri spec"
|
||||
@ -1551,3 +1539,120 @@ add_task(function* test_invalid_uri_spec_throws() {
|
||||
PT.Tag({ tag: "TheTag"
|
||||
, urls: ["about:blank", "invalid uri spec"] }));
|
||||
});
|
||||
|
||||
add_task(function* test_annotate_multiple_items() {
|
||||
let parentGuid = rootGuid;
|
||||
let guids = [
|
||||
yield PT.NewBookmark({ url: "about:blank", parentGuid }).transact(),
|
||||
yield PT.NewFolder({ title: "Test Folder", parentGuid }).transact()];
|
||||
|
||||
let annotation = { name: "TestAnno", value: "TestValue" };
|
||||
yield PT.Annotate({ guids, annotation }).transact();
|
||||
|
||||
function *ensureAnnoSet() {
|
||||
for (let guid of guids) {
|
||||
let itemId = yield PlacesUtils.promiseItemId(guid);
|
||||
Assert.equal(annosvc.getItemAnnotation(itemId, annotation.name),
|
||||
annotation.value);
|
||||
}
|
||||
}
|
||||
function *ensureAnnoUnset() {
|
||||
for (let guid of guids) {
|
||||
let itemId = yield PlacesUtils.promiseItemId(guid);
|
||||
Assert.ok(!annosvc.itemHasAnnotation(itemId, annotation.name));
|
||||
}
|
||||
}
|
||||
|
||||
yield ensureAnnoSet();
|
||||
yield PT.undo();
|
||||
yield ensureAnnoUnset();
|
||||
yield PT.redo();
|
||||
yield ensureAnnoSet();
|
||||
yield PT.undo();
|
||||
yield ensureAnnoUnset();
|
||||
|
||||
// Cleanup
|
||||
yield PT.undo();
|
||||
yield PT.undo();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.clearTransactionsHistory();
|
||||
observer.reset();
|
||||
});
|
||||
|
||||
add_task(function* test_remove_multiple() {
|
||||
let guids = [];
|
||||
yield PT.batch(function* () {
|
||||
let folderGuid = yield PT.NewFolder({ title: "Test Folder"
|
||||
, parentGuid: rootGuid }).transact();
|
||||
let nestedFolderGuid =
|
||||
yield PT.NewFolder({ title: "Nested Test Folder"
|
||||
, parentGuid: folderGuid }).transact();
|
||||
let nestedSepGuid = yield PT.NewSeparator(nestedFolderGuid).transact();
|
||||
|
||||
guids.push(folderGuid);
|
||||
|
||||
let bmGuid =
|
||||
yield PT.NewBookmark({ url: new URL("http://test.bookmark.removed")
|
||||
, parentGuid: rootGuid }).transact();
|
||||
guids.push(bmGuid);
|
||||
});
|
||||
|
||||
let originalInfos = [for (guid of guids)
|
||||
yield PlacesUtils.promiseBookmarksTree(guid)];
|
||||
|
||||
yield PT.Remove(guids).transact();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.undo();
|
||||
yield ensureBookmarksTreeRestoredCorrectly(...originalInfos);
|
||||
yield PT.redo();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.undo();
|
||||
yield ensureBookmarksTreeRestoredCorrectly(...originalInfos);
|
||||
|
||||
// Undo the New* transactions batch.
|
||||
yield PT.undo();
|
||||
yield ensureNonExistent(...guids);
|
||||
|
||||
// Redo it.
|
||||
yield PT.redo();
|
||||
yield ensureBookmarksTreeRestoredCorrectly(...originalInfos);
|
||||
|
||||
// Redo remove.
|
||||
yield PT.redo();
|
||||
yield ensureNonExistent(...guids);
|
||||
|
||||
// Cleanup
|
||||
yield PT.clearTransactionsHistory();
|
||||
observer.reset();
|
||||
});
|
||||
|
||||
add_task(function* test_remove_bookmarks_for_urls() {
|
||||
let urls = [new URL("http://test.url.1"), new URL("http://test.url.2")];
|
||||
let guids = [];
|
||||
yield PT.batch(function* () {
|
||||
for (let url of urls) {
|
||||
for (let title of ["test title a", "test title b"]) {
|
||||
let txn = PT.NewBookmark({ url, title, parentGuid: rootGuid });
|
||||
guids.push(yield txn.transact());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let originalInfos = [for (guid of guids)
|
||||
yield PlacesUtils.promiseBookmarksTree(guid)];
|
||||
|
||||
yield PT.RemoveBookmarksForUrls(urls).transact();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.undo();
|
||||
yield ensureBookmarksTreeRestoredCorrectly(...originalInfos);
|
||||
yield PT.redo();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.undo();
|
||||
yield ensureBookmarksTreeRestoredCorrectly(...originalInfos);
|
||||
|
||||
// Cleanup.
|
||||
yield PT.redo();
|
||||
yield ensureNonExistent(...guids);
|
||||
yield PT.clearTransactionsHistory();
|
||||
observer.reset();
|
||||
});
|
||||
|
@ -192,6 +192,7 @@ add_task(function test_addLivemark_noSiteURI_succeeds()
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id));
|
||||
do_check_eq(livemark.dateAdded, PlacesUtils.bookmarks.getItemDateAdded(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(livemark.siteURI, null);
|
||||
});
|
||||
@ -211,6 +212,7 @@ add_task(function test_addLivemark_succeeds()
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_eq(livemark.dateAdded, PlacesUtils.bookmarks.getItemDateAdded(livemark.id));
|
||||
do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_true(livemark.siteURI.equals(SITE_URI));
|
||||
@ -287,6 +289,18 @@ add_task(function test_addLivemark_forceGuid_succeeds()
|
||||
checkLivemark(livemark);
|
||||
});
|
||||
|
||||
add_task(function* test_addLivemark_dateAdded_succeeds() {
|
||||
let dateAdded = new Date("2013-03-01T01:10:00") * 1000;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
, dateAdded
|
||||
});
|
||||
do_check_eq(livemark.dateAdded, dateAdded);
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_lastModified_succeeds()
|
||||
{
|
||||
let now = Date.now() * 1000;
|
||||
|
@ -55,6 +55,7 @@ skip-if = os == "android"
|
||||
[test_485442_crash_bug_nsNavHistoryQuery_GetUri.js]
|
||||
[test_486978_sort_by_date_queries.js]
|
||||
[test_536081.js]
|
||||
[test_1085291.js]
|
||||
[test_adaptive.js]
|
||||
# Bug 676989: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
|
Loading…
Reference in New Issue
Block a user