merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-09-08 14:52:35 +02:00
Родитель 24d86f5309 0aa9ee6fdd
Коммит cd7ed331d3
38 изменённых файлов: 521 добавлений и 192 удалений

Просмотреть файл

@ -32,5 +32,5 @@ exports.locale = function locale() {
// Returns the short locale code: ja, en, fr
exports.language = function language() {
return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
: null;
: "en";
}

Просмотреть файл

@ -103,9 +103,11 @@ let gSyncUI = {
// We want to treat "account needs verification" as "needs setup". So
// "reach in" to Weave.Status._authManager to check whether we the signed-in
// user is verified.
// Referencing Weave.Status spins a nested event loop to initialize the
// authManager, so this should always return a value directly.
// This only applies to fxAccounts-based Sync.
// NOTE: We used to have this _authManager hack to avoid a nested
// event-loop from querying Weave.Status.checkSetup() - while that's no
// longer true, we do still have the FxA-specific requirement of checking
// the verified state - so the hack remains. We should consider refactoring
// Sync's "check setup" capabilities to take this into account at some point...
if (Weave.Status._authManager._signedInUser !== undefined) {
// If we have a signed in user already, and that user is not verified,
// revert to the "needs setup" state.

Просмотреть файл

@ -340,7 +340,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
let engine = Services.search.getEngineByName(action.params.engineName);
let query = action.params.searchSuggestion ||
action.params.searchQuery;
let submission = engine.getSubmission(query);
let submission = engine.getSubmission(query, null, "keyword");
url = submission.uri.spec;
postData = submission.postData;
@ -1390,7 +1390,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
let engine = Services.search.getEngineByName(action.params.engineName);
let query = action.params.searchSuggestion ||
action.params.searchQuery;
let submission = engine.getSubmission(query);
let submission = engine.getSubmission(query, null, "keyword");
url = submission.uri.spec;
options.postData = submission.postData;
break;

Просмотреть файл

@ -293,6 +293,15 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
font-size: 2.2rem;
}
.room-list-loading {
margin: 5rem 15px;
text-align: center;
}
.room-list-loading > img {
width: 66px;
}
/* Rooms */
.rooms {
@ -321,10 +330,10 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
}
.new-room-view > .context {
border-top: 1px solid #ebebeb;
flex: 1;
border-radius: 3px 3px 0 0;
margin: 1rem 0 .5rem;
padding: 1rem 15px;
margin: .5rem 0;
padding: .5rem 15px 1rem;
}
.new-room-view > .context > .context-enabled {

Просмотреть файл

@ -809,6 +809,21 @@ loop.panel = (function(_, mozL10n) {
mozL10n.get("display_name_guest");
},
/**
* Let the user know we're loading rooms
* @returns {Object} React render
*/
_renderLoadingRoomsView: function() {
return (
React.createElement("div", {className: "room-list"},
React.createElement("div", {className: "room-list-loading"},
React.createElement("img", {src: "loop/shared/img/animated-spinner.svg"})
),
this._renderNewRoomButton()
)
);
},
_renderNoRoomsView: function() {
return (
React.createElement("div", {className: "room-list"},
@ -841,6 +856,10 @@ loop.panel = (function(_, mozL10n) {
console.error("RoomList error", this.state.error);
}
if (this.state.pendingInitialRetrieval) {
return this._renderLoadingRoomsView();
}
if (!this.state.rooms.length) {
return this._renderNoRoomsView();
}

Просмотреть файл

@ -809,6 +809,21 @@ loop.panel = (function(_, mozL10n) {
mozL10n.get("display_name_guest");
},
/**
* Let the user know we're loading rooms
* @returns {Object} React render
*/
_renderLoadingRoomsView: function() {
return (
<div className="room-list">
<div className="room-list-loading">
<img src="loop/shared/img/animated-spinner.svg" />
</div>
{this._renderNewRoomButton()}
</div>
);
},
_renderNoRoomsView: function() {
return (
<div className="room-list">
@ -841,6 +856,10 @@ loop.panel = (function(_, mozL10n) {
console.error("RoomList error", this.state.error);
}
if (this.state.pendingInitialRetrieval) {
return this._renderLoadingRoomsView();
}
if (!this.state.rooms.length) {
return this._renderNoRoomsView();
}

Просмотреть файл

@ -114,7 +114,7 @@ loop.store = loop.store || {};
activeRoom: this.activeRoomStore ? this.activeRoomStore.getStoreState() : {},
error: null,
pendingCreation: false,
pendingInitialRetrieval: false,
pendingInitialRetrieval: true,
rooms: [],
savingContext: false
};

Просмотреть файл

@ -593,8 +593,20 @@ loop.roomViews = (function(mozL10n) {
}));
},
/**
* Determine if the invitation controls should be shown.
*
* @return {Boolean} True if there's no guests.
*/
_shouldRenderInvitationOverlay: function() {
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
var hasGuests = typeof this.state.participants === "object" &&
this.state.participants.filter(function(participant) {
return !participant.owner;
}).length > 0;
// Don't show if the room has participants whether from the room state or
// there being non-owner guests in the participants array.
return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
},
/**

Просмотреть файл

@ -593,8 +593,20 @@ loop.roomViews = (function(mozL10n) {
}));
},
/**
* Determine if the invitation controls should be shown.
*
* @return {Boolean} True if there's no guests.
*/
_shouldRenderInvitationOverlay: function() {
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
var hasGuests = typeof this.state.participants === "object" &&
this.state.participants.filter(function(participant) {
return !participant.owner;
}).length > 0;
// Don't show if the room has participants whether from the room state or
// there being non-owner guests in the participants array.
return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
},
/**

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill="#0095FF"><path opacity=".15" d="M4.9 11.8c.2-.4.7-.5 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.3-.7.5-1.1.2-.4-.2-.5-.7-.3-1.1l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite"/></path><path opacity=".2" d="M3.4 9.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.08s"/></path><path opacity=".25" d="M3.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8H.8C.4 8.8 0 8.4 0 8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.16s"/></path><path opacity=".35" d="M1.4 5.1C1 4.9.8 4.4 1.1 4c.2-.4.7-.5 1.1-.3l2.1 1.2c.3.2.5.7.2 1.1-.2.4-.7.5-1.1.3l-2-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.24s"/></path><path opacity=".45" d="M3.7 2.2c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.3.1.8-.3 1-.4.3-.9.1-1.1-.3l-1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.32s"/></path><path opacity=".5" d="M8.8 3.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8V.8c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.40s"/></path><path opacity=".55" d="M10.9 1.4c.2-.4.7-.6 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.4-.7.5-1.1.3-.4-.3-.5-.8-.3-1.2l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.48s"/></path><path opacity=".6" d="M13.8 3.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.56s"/></path><path opacity=".65" d="M15.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8h-2.4c-.4 0-.8-.4-.8-.8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.64s"/></path><path opacity=".7" d="M11.8 11.1c-.4-.2-.5-.7-.3-1.1.2-.4.7-.5 1.1-.3l2.1 1.2c.4.2.5.7.3 1.1-.2.4-.7.5-1.1.3l-2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.70s"/></path><path opacity=".8" d="M9.7 12.6c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.4.1.9-.3 1.1-.4.2-.9.1-1.1-.3l-1.2-2.1z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.78s"/></path><path d="M8.8 15.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8v-2.4c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.86s"/></path></g><path fill-rule="evenodd" clip-rule="evenodd" fill="#0095FF" d="M8 8.7c-1.5 0-2.6-.5-2.6-.5S5.9 10 8 10s2.6-1.9 2.6-1.9-1.1.6-2.6.6zM9.3 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7zM6.7 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

Просмотреть файл

@ -51,6 +51,7 @@ loop.store.ActiveRoomStore = (function() {
var OPTIONAL_ROOMINFO_FIELDS = {
urls: "roomContextUrls",
description: "roomDescription",
participants: "participants",
roomInfoFailure: "roomInfoFailure",
roomName: "roomName",
roomState: "roomState"
@ -296,6 +297,7 @@ loop.store.ActiveRoomStore = (function() {
}
this.dispatchAction(new sharedActions.SetupRoomInfo({
participants: roomData.participants,
roomToken: actionData.roomToken,
roomContextUrls: roomData.decryptedContext.urls,
roomDescription: roomData.decryptedContext.description,
@ -418,6 +420,7 @@ loop.store.ActiveRoomStore = (function() {
}
this.setStoreState({
participants: actionData.participants,
roomContextUrls: actionData.roomContextUrls,
roomDescription: actionData.roomDescription,
roomName: actionData.roomName,
@ -449,7 +452,7 @@ loop.store.ActiveRoomStore = (function() {
// Iterate over the optional fields that _may_ be present on the actionData
// object.
Object.keys(OPTIONAL_ROOMINFO_FIELDS).forEach(function(field) {
if (actionData[field]) {
if (actionData[field] !== undefined) {
newState[OPTIONAL_ROOMINFO_FIELDS[field]] = actionData[field];
}
});
@ -478,6 +481,7 @@ loop.store.ActiveRoomStore = (function() {
this.dispatchAction(new sharedActions.UpdateRoomInfo({
urls: roomData.decryptedContext.urls,
description: roomData.decryptedContext.description,
participants: roomData.participants,
roomName: roomData.decryptedContext.roomName,
roomUrl: roomData.roomUrl
}));
@ -792,7 +796,16 @@ loop.store.ActiveRoomStore = (function() {
* one participantleaves.
*/
remotePeerDisconnected: function() {
// Update the participants to just the owner.
var participants = this.getStoreState("participants");
if (participants) {
participants = participants.filter(function(participant) {
return participant.owner;
});
}
this.setStoreState({
participants: participants,
roomState: ROOM_STATES.SESSION_CONNECTED,
remoteSrcVideoObject: null
});

Просмотреть файл

@ -94,6 +94,7 @@ browser.jar:
content/browser/loop/shared/img/empty_contacts.svg (content/shared/img/empty_contacts.svg)
content/browser/loop/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
content/browser/loop/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
content/browser/loop/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
content/browser/loop/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)

Просмотреть файл

@ -850,6 +850,13 @@ describe("loop.panel", function() {
sinon.assert.calledWithExactly(document.mozL10n.get,
"no_conversations_message_heading");
});
it("should display a loading animation when rooms are pending", function() {
var view = createTestComponent();
roomStore.setStoreState({pendingInitialRetrieval: true});
expect(view.getDOMNode().querySelectorAll(".room-list-loading").length).to.eql(1);
});
});
describe("loop.panel.NewRoomView", function() {

Просмотреть файл

@ -69,7 +69,7 @@ describe("loop.store.RoomStore", function () {
var defaultStoreState = {
error: undefined,
pendingCreation: false,
pendingInitialRetrieval: false,
pendingInitialRetrieval: true,
rooms: [],
activeRoom: {}
};

Просмотреть файл

@ -470,8 +470,51 @@ describe("loop.roomViews", function () {
view = mountTestComponent();
expect(TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
});
it("should render the DesktopRoomInvitationView if roomState is `JOINED` with just owner",
function() {
activeRoomStore.setStoreState({
participants: [{owner: true}],
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
expect(TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
});
it("should render the DesktopRoomConversationView if roomState is `JOINED` with remote participant",
function() {
activeRoomStore.setStoreState({
participants: [{}],
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView);
loop.roomViews.DesktopRoomConversationView);
expect(TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
});
it("should render the DesktopRoomConversationView if roomState is `JOINED` with participants",
function() {
activeRoomStore.setStoreState({
participants: [{owner: true}, {}],
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomConversationView);
expect(TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
});
it("should render the DesktopRoomConversationView if roomState is `HAS_PARTICIPANTS`",
@ -482,6 +525,8 @@ describe("loop.roomViews", function () {
TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomConversationView);
expect(TestUtils.findRenderedComponentWithType(view,
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
});
it("should call onCallTerminated when the call ended", function() {

Просмотреть файл

@ -310,6 +310,7 @@ describe("loop.store.ActiveRoomStore", function () {
decryptedContext: {
roomName: "Monkeys"
},
participants: [],
roomUrl: "http://invalid"
};
@ -350,6 +351,7 @@ describe("loop.store.ActiveRoomStore", function () {
new sharedActions.SetupRoomInfo({
roomContextUrls: undefined,
roomDescription: undefined,
participants: [],
roomToken: fakeToken,
roomName: fakeRoomData.decryptedContext.roomName,
roomUrl: fakeRoomData.roomUrl,
@ -1277,6 +1279,30 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().remoteSrcVideoObject).eql(null);
});
it("should remove non-owner participants", function() {
store.setStoreState({
participants: [{owner: true}, {}]
});
store.remotePeerDisconnected();
var participants = store.getStoreState().participants;
expect(participants).to.have.length.of(1);
expect(participants[0].owner).eql(true);
});
it("should keep the owner participant", function() {
store.setStoreState({
participants: [{owner: true}]
});
store.remotePeerDisconnected();
var participants = store.getStoreState().participants;
expect(participants).to.have.length.of(1);
expect(participants[0].owner).eql(true);
});
});
describe("#connectionStatus", function() {
@ -1518,6 +1544,7 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
description: "fakeDescription",
participants: undefined,
roomName: fakeRoomData.decryptedContext.roomName,
roomUrl: fakeRoomData.roomUrl,
urls: {

Просмотреть файл

@ -186,6 +186,17 @@ let gSyncPane = {
}
},
_closeSyncStatusMessageBox: function() {
document.getElementById("syncStatusMessage").removeAttribute("message-type");
document.getElementById("syncStatusMessageTitle").textContent = "";
document.getElementById("syncStatusMessageDescription").textContent = "";
let learnMoreLink = document.getElementById("learnMoreLink");
if (learnMoreLink) {
learnMoreLink.parentNode.removeChild(learnMoreLink);
}
document.getElementById("sync-migration-buttons-deck").hidden = true;
},
_setupEventListeners: function() {
function setEventListener(aId, aEventType, aCallback)
{
@ -193,6 +204,9 @@ let gSyncPane = {
.addEventListener(aEventType, aCallback.bind(gSyncPane));
}
setEventListener("syncStatusMessageClose", "command", function () {
gSyncPane._closeSyncStatusMessageBox();
});
setEventListener("noAccountSetup", "click", function (aEvent) {
aEvent.stopPropagation();
gSyncPane.openSetup(null);
@ -431,16 +445,17 @@ let gSyncPane = {
},
updateMigrationState: function(subject, state) {
this._closeSyncStatusMessageBox();
let selIndex;
let sb = this._accountsStringBundle;
switch (state) {
case fxaMigrator.STATE_USER_FXA: {
let sb = this._accountsStringBundle;
// There are 2 cases here - no email address means it is an offer on
// the first device (so the user is prompted to create an account).
// If there is an email address it is the "join the party" flow, so the
// user is prompted to sign in with the address they previously used.
let email = subject ? subject.QueryInterface(Components.interfaces.nsISupportsString).data : null;
let elt = document.getElementById("sync-migrate-upgrade-description");
let elt = document.getElementById("syncStatusMessageDescription");
elt.textContent = email ?
sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
[email], 1) :
@ -449,11 +464,12 @@ let gSyncPane = {
// The "Learn more" link.
if (!email) {
let learnMoreLink = document.createElement("label");
learnMoreLink.id = "learnMoreLink";
learnMoreLink.className = "text-link";
let { text, href } = fxaMigrator.learnMoreLink;
learnMoreLink.setAttribute("value", text);
learnMoreLink.href = href;
elt.appendChild(learnMoreLink);
elt.parentNode.insertBefore(learnMoreLink, elt.nextSibling);
}
// The "upgrade" button.
@ -481,7 +497,7 @@ let gSyncPane = {
let sb = this._accountsStringBundle;
let email = subject.QueryInterface(Components.interfaces.nsISupportsString).data;
let label = sb.formatStringFromName("needVerifiedUserLong", [email], 1);
let elt = document.getElementById("sync-migrate-verify-label");
let elt = document.getElementById("syncStatusMessageDescription");
elt.setAttribute("value", label);
// The "resend" button.
let button = document.getElementById("sync-migrate-resend");
@ -501,8 +517,8 @@ let gSyncPane = {
document.getElementById("sync-migration").hidden = true;
return;
}
document.getElementById("sync-migration").hidden = false;
document.getElementById("sync-migration-deck").selectedIndex = selIndex;
document.getElementById("sync-migration-buttons-deck").selectedIndex = selIndex;
document.getElementById("syncStatusMessage").setAttribute("message-type", "migration");
},
// Called whenever one of the sync engine preferences is changed.
@ -678,23 +694,39 @@ let gSyncPane = {
},
verifyFirefoxAccount: function() {
fxAccounts.resendVerificationEmail().then(() => {
fxAccounts.getSignedInUser().then(data => {
let sb = this._accountsStringBundle;
let title = sb.GetStringFromName("verificationSentTitle");
let heading = sb.formatStringFromName("verificationSentHeading",
[data.email], 1);
let description = sb.GetStringFromName("verificationSentDescription");
this._closeSyncStatusMessageBox();
let changesyncStatusMessage = (data) => {
let isError = !data;
let syncStatusMessage = document.getElementById("syncStatusMessage");
let syncStatusMessageTitle = document.getElementById("syncStatusMessageTitle");
let syncStatusMessageDescription = document.getElementById("syncStatusMessageDescription");
let maybeNot = isError ? "Not" : "";
let sb = this._accountsStringBundle;
let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
let email = !isError && data ? data.email : "";
let description = sb.formatStringFromName("verification" + maybeNot + "SentFull", [email], 1)
let factory = Cc["@mozilla.org/prompter;1"]
.getService(Ci.nsIPromptFactory);
let prompt = factory.getPrompt(window, Ci.nsIPrompt);
let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
bag.setPropertyAsBool("allowTabModal", true);
syncStatusMessageTitle.textContent = title;
syncStatusMessageDescription.textContent = description;
let messageType = isError ? "verify-error" : "verify-success";
syncStatusMessage.setAttribute("message-type", messageType);
}
prompt.alert(title, heading + "\n\n" + description);
});
});
let onError = () => {
changesyncStatusMessage();
};
let onSuccess = data => {
if (data) {
changesyncStatusMessage(data);
} else {
onError();
}
};
fxAccounts.resendVerificationEmail()
.then(fxAccounts.getSignedInUser, onError)
.then(onSuccess, onError);
},
openOldSyncSupportPage: function() {

Просмотреть файл

@ -38,31 +38,29 @@
<label class="header-name">&paneSync.title;</label>
</hbox>
<hbox id="sync-migration-container"
data-category="paneSync"
hidden="true">
<vbox id="sync-migration" flex="1" hidden="true">
<deck id="sync-migration-deck">
<!-- When we are in the "need FxA user" state -->
<hbox align="center">
<description id="sync-migrate-upgrade-description" flex="1"/>
<spacer flex="1"/>
<button id="sync-migrate-unlink"/>
<button id="sync-migrate-upgrade"/>
</hbox>
<!-- When we are in the "need the user to be verified" state -->
<hbox align="center">
<label id="sync-migrate-verify-label"/>
<spacer flex="1"/>
<button id="sync-migrate-forget"/>
<button id="sync-migrate-resend"/>
</hbox>
</deck>
</vbox>
</hbox>
<vbox id="syncStatusMessage-container" data-category="paneSync" hidden="true">
<hbox id="syncStatusMessage">
<vbox id="syncStatusMessageWrapper">
<label id="syncStatusMessageTitle"></label>
<description id="syncStatusMessageDescription"></description>
<deck id="sync-migration-buttons-deck">
<!-- When we are in the "need FxA user" state -->
<hbox>
<button id="sync-migrate-unlink"/>
<button id="sync-migrate-upgrade"/>
</hbox>
<!-- When we are in the "need the user to be verified" state -->
<hbox>
<button id="sync-migrate-forget"/>
<button id="sync-migrate-resend"/>
</hbox>
</deck>
</vbox>
<vbox>
<button id="syncStatusMessageClose" class="close-icon"/>
</vbox>
</hbox>
</vbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
<!-- These panels are for the "legacy" sync provider -->

Просмотреть файл

@ -19,10 +19,11 @@ function checkElements(expectedPane) {
continue;
}
let attributeValue = element.getAttribute("data-category");
let suffix = " (id=" + element.id + ")";
if (attributeValue == "pane" + expectedPane) {
is_element_visible(element, expectedPane + " elements should be visible");
is_element_visible(element, expectedPane + " elements should be visible" + suffix);
} else {
is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden");
is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
}
}
}

Просмотреть файл

@ -23,6 +23,7 @@ loader.lazyRequireGetter(this, "AnimationsFront",
const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const V3_UI_PREF = "devtools.inspector.animationInspectorV3";
// Global toolbox/inspector, set when startup is called.
let gToolbox, gInspector;
@ -76,19 +77,20 @@ function destroy() {
* @return {Object} An object with boolean properties.
*/
let getServerTraits = Task.async(function*(target) {
let config = [{
name: "hasToggleAll", actor: "animations", method: "toggleAll"
}, {
name: "hasSetCurrentTime", actor: "animationplayer", method: "setCurrentTime"
}, {
name: "hasMutationEvents", actor: "animations", method: "stopAnimationPlayerUpdates"
}, {
name: "hasSetPlaybackRate", actor: "animationplayer", method: "setPlaybackRate"
}, {
name: "hasTargetNode", actor: "domwalker", method: "getNodeFromActor"
}, {
name: "hasSetCurrentTimes", actor: "animations", method: "setCurrentTimes"
}];
let config = [
{ name: "hasToggleAll", actor: "animations",
method: "toggleAll" },
{ name: "hasSetCurrentTime", actor: "animationplayer",
method: "setCurrentTime" },
{ name: "hasMutationEvents", actor: "animations",
method: "stopAnimationPlayerUpdates" },
{ name: "hasSetPlaybackRate", actor: "animationplayer",
method: "setPlaybackRate" },
{ name: "hasTargetNode", actor: "domwalker",
method: "getNodeFromActor" },
{ name: "hasSetCurrentTimes", actor: "animations",
method: "setCurrentTimes" }
];
let traits = {};
for (let {name, actor, method} of config) {
@ -96,7 +98,7 @@ let getServerTraits = Task.async(function*(target) {
}
// Special pref-based UI trait.
traits.isNewUI = Services.prefs.getBoolPref("devtools.inspector.animationInspectorV3");
traits.isNewUI = Services.prefs.getBoolPref(V3_UI_PREF);
return traits;
});
@ -114,7 +116,8 @@ let getServerTraits = Task.async(function*(target) {
*
* Usage example:
*
* AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT, onPlayers);
* AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
* onPlayers);
* function onPlayers() {
* for (let player of AnimationsController.animationPlayers) {
* // do something with player
@ -126,7 +129,8 @@ let AnimationsController = {
initialize: Task.async(function*() {
if (this.initialized) {
return this.initialized.promise;
yield this.initialized.promise;
return;
}
this.initialized = promise.defer();
@ -157,7 +161,8 @@ let AnimationsController = {
}
if (this.destroyed) {
return this.destroyed.promise;
yield this.destroyed.promise;
return;
}
this.destroyed = promise.defer();
@ -272,7 +277,8 @@ let AnimationsController = {
refreshAnimationPlayers: Task.async(function*(nodeFront) {
yield this.destroyAnimationPlayers();
this.animationPlayers = yield this.animationsFront.getAnimationPlayersForNode(nodeFront);
this.animationPlayers = yield this.animationsFront
.getAnimationPlayersForNode(nodeFront);
this.startAllAutoRefresh();
// Start listening for animation mutations only after the first method call
@ -308,6 +314,25 @@ let AnimationsController = {
this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
}),
/**
* Get the latest known current time of document.timeline.
* This value is sent along with all AnimationPlayerActors' states, but it
* isn't updated after that, so this function loops over all know animations
* to find the highest value.
* @return {Number|Boolean} False is returned if this server version doesn't
* provide document's current time.
*/
get documentCurrentTime() {
let time = 0;
for (let {state} of this.animationPlayers) {
if (!state.documentCurrentTime) {
return false;
}
time = Math.max(time, state.documentCurrentTime);
}
return time;
},
startAllAutoRefresh: function() {
if (this.traits.isNewUI) {
return;

Просмотреть файл

@ -30,7 +30,8 @@ let AnimationsPanel = {
return;
}
if (this.initialized) {
return this.initialized.promise;
yield this.initialized.promise;
return;
}
this.initialized = promise.defer();
@ -74,7 +75,8 @@ let AnimationsPanel = {
}
if (this.destroyed) {
return this.destroyed.promise;
yield this.destroyed.promise;
return;
}
this.destroyed = promise.defer();
@ -157,7 +159,8 @@ let AnimationsPanel = {
currentWidgetStateChange.push(btnClass.contains("paused")
? widget.play() : widget.pause());
}
yield promise.all(currentWidgetStateChange).catch(e => console.error(e));
yield promise.all(currentWidgetStateChange)
.catch(error => console.error(error));
}
}
@ -170,7 +173,8 @@ let AnimationsPanel = {
},
onTimelineTimeChanged: function(e, time) {
AnimationsController.setCurrentTimeAll(time, true).catch(e => console.error(e));
AnimationsController.setCurrentTimeAll(time, true)
.catch(error => console.error(error));
},
refreshAnimations: Task.async(function*() {
@ -183,7 +187,8 @@ let AnimationsPanel = {
// Re-render the timeline component.
if (this.animationsTimelineComponent) {
this.animationsTimelineComponent.render(
AnimationsController.animationPlayers);
AnimationsController.animationPlayers,
AnimationsController.documentCurrentTime);
}
// If there are no players to show, show the error message instead and

Просмотреть файл

@ -567,7 +567,12 @@ let TimeScale = {
addAnimation: function(state) {
let {startTime, delay, duration, iterationCount, playbackRate} = state;
this.minStartTime = Math.min(this.minStartTime, startTime);
// Negative-delayed animations have their startTimes set such that we would
// be displaying the delay outside the time window if we didn't take it into
// account here.
let relevantDelay = delay < 0 ? delay / playbackRate : 0;
this.minStartTime = Math.min(this.minStartTime, startTime + relevantDelay);
let length = (delay / playbackRate) +
((duration / playbackRate) *
(!iterationCount ? 1 : iterationCount));
@ -791,7 +796,7 @@ AnimationsTimeline.prototype = {
this.emit("current-time-changed", time);
},
render: function(animations) {
render: function(animations, documentCurrentTime) {
this.unrender();
this.animations = animations;
@ -849,12 +854,11 @@ AnimationsTimeline.prototype = {
// doesn't provide it, hide the scrubber entirely).
// Note that because the currentTime was sent via the protocol, some time
// may have gone by since then, and so the scrubber might be a bit late.
let time = this.animations[0].state.documentCurrentTime;
if (!time) {
if (!documentCurrentTime) {
this.scrubberEl.style.display = "none";
} else {
this.scrubberEl.style.display = "block";
this.startAnimatingScrubber(time);
this.startAnimatingScrubber(documentCurrentTime);
}
},
@ -953,23 +957,31 @@ AnimationsTimeline.prototype = {
});
// The animation name is displayed over the iterations.
// Note that in case of negative delay, we push the name towards the right
// so the delay can be shown.
createNode({
parent: iterations,
attributes: {
"class": "name",
"title": this.getAnimationTooltipText(state)
"title": this.getAnimationTooltipText(state),
"style": delay < 0
? "margin-left:" +
TimeScale.durationToDistance(Math.abs(delay), width) + "px"
: ""
},
textContent: state.name
});
// Delay.
if (delay) {
let w = TimeScale.durationToDistance(delay / rate, width);
// Negative delays need to start at 0.
let x = TimeScale.durationToDistance((delay < 0 ? 0 : delay) / rate, width);
let w = TimeScale.durationToDistance(Math.abs(delay) / rate, width);
createNode({
parent: iterations,
attributes: {
"class": "delay",
"style": `left:-${w}px;
"style": `left:-${x}px;
width:${w}px;`
}
});

Просмотреть файл

@ -22,18 +22,23 @@ add_task(function*() {
let timeBlocks = timelineEl.querySelectorAll(".time-block");
is(timeBlocks.length, 2, "2 animations are displayed");
info("The first animation has its rate to 1, let's measure it");
info("The first animation has its rate set to 1, let's measure it");
let el = timeBlocks[0];
let duration = el.querySelector(".iterations").getBoundingClientRect().width;
let delay = el.querySelector(".delay").getBoundingClientRect().width;
let duration = parseInt(el.querySelector(".iterations").style.width, 10);
let delay = parseInt(el.querySelector(".delay").style.width, 10);
info("The second animation has its rate set to 2, so should be shorter");
let el2 = timeBlocks[1];
let duration2 = el2.querySelector(".iterations").getBoundingClientRect().width;
let delay2 = el2.querySelector(".delay").getBoundingClientRect().width;
let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10);
let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
is(duration, 2 * duration2, "The duration width is correct");
is(delay, 2 * delay2, "The delay width is correct");
// The width are calculated by the animation-inspector dynamically depending
// on the size of the panel, and therefore depends on the test machine/OS.
// Let's not try to be too precise here and compare numbers.
let durationDelta = (2 * duration2) - duration;
ok(durationDelta <= 1, "The duration width is correct");
let delayDelta = (2 * delay2) - delay;
ok(delayDelta <= 1, "The delay width is correct");
});

Просмотреть файл

@ -46,7 +46,10 @@ verificationSentTitle = Verification Sent
# LOCALIZATION NOTE (verificationSentHeading) - %S = Email address of user's Firefox Account
verificationSentHeading = A verification link has been sent to %S
verificationSentDescription = Please check your email and click the link to begin syncing.
# LOCALIZATION NOTE (verificationSentFull) - %S = Email address of user's Firefox Account
verificationSentFull = A verification link has been sent to %S. Please check your email and click the link to begin syncing.
verificationNotSentTitle = Unable to Send Verification
verificationNotSentHeading = We are unable to send a verification mail at this time
verificationNotSentDescription = Please try again later.
verificationNotSentFull = We are unable to send a verification mail at this time, please try again later.

Просмотреть файл

@ -1614,7 +1614,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
#urlbar {
-moz-padding-end: 4px;
border-radius: @toolbarbuttonCornerRadius@;
}
@ -1906,6 +1905,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
var(--urlbar-separator-color) 15%,
var(--urlbar-separator-color) 85%,
transparent 85%);
border-image-slice: 1;
}
#urlbar-go-button {

Просмотреть файл

@ -238,7 +238,7 @@ body {
border: 1px solid var(--timelime-border-color);
/* The background color is set independently */
background: var(--timeline-background-color);
background-color: var(--timeline-background-color);
}
.animation-timeline .animation .cssanimation {
@ -287,25 +287,16 @@ body {
.animation-timeline .animation .delay {
position: absolute;
top: 0;
/* Make sure the delay covers up the animation border */
transform: translate(-1px, -1px);
height: 100%;
background-image: linear-gradient(to bottom,
transparent,
transparent 9px,
var(--timelime-border-color) 9px,
var(--timelime-border-color) 11px,
transparent 11px,
transparent);
}
.animation-timeline .animation .delay::before {
position: absolute;
content: "";
left: 0;
width: 2px;
height: 8px;
top: 50%;
margin-top: -4px;
background: var(--timelime-border-color);
background-image: repeating-linear-gradient(45deg,
transparent,
transparent 1px,
var(--theme-selection-color) 1px,
var(--theme-selection-color) 4px);
background-color: var(--timelime-border-color);
border: 1px solid var(--timelime-border-color);
}
/* Animation target node gutter, contains a preview of the dom node */

Просмотреть файл

@ -415,6 +415,101 @@ description > html|a {
-moz-box-align: start;
}
#syncStatusMessage {
visibility: collapse;
opacity: 0;
transition: opacity 1s linear;
padding: 14px 8px 14px 14px;
border-radius: 2px;
}
#syncStatusMessage[message-type] {
visibility: visible;
opacity: 1;
}
#syncStatusMessage[message-type="verify-success"] {
background-color: #74BF43;
}
#syncStatusMessage[message-type="verify-error"] {
background-color: #D74345;
}
#syncStatusMessage[message-type="migration"] {
background-color: #FF9500;
}
#sync-migration-buttons-deck {
visibility: collapse;
}
#learnMoreLink {
margin: 0;
color: #FBFBFB;
text-decoration: underline;
}
#syncStatusMessage[message-type="migration"] #sync-migration-buttons-deck {
visibility: visible;
}
#sync-migration-buttons-deck {
margin-top: 20px;
}
#sync-migration-buttons-deck button {
margin: 0 10px 0 0;
border: 0;
border-radius: 2px;
}
#sync-migrate-upgrade,
#sync-migrate-resend {
background-color: #0095DD;
color: #FBFBFB;
}
#sync-migrate-upgrade:hover,
#sync-migrate-resend:hover {
background-color: #008ACB;
}
#sync-migrate-upgrade:hover:active,
#sync-migrate-resend:hover:active {
background-color: #006B9D;
}
#syncStatusMessageWrapper {
-moz-box-flex: 1;
padding-right: 5px;
}
#syncStatusMessageTitle, #syncStatusMessageDescription {
color: #FBFBFB;
}
#syncStatusMessage[message-type="migration"] #syncStatusMessageTitle {
display: none;
}
#syncStatusMessageTitle {
font-weight: bold !important;
font-size: 16px;
line-height: 157%;
margin: 0 0 20px;
}
#syncStatusMessageDescription {
font-size: 14px;
line-height: 158%;
margin: 0 !important;
}
#syncStatusMessageClose {
margin: 0px;
}
#fxaSyncEngines > vbox:first-child {
margin-right: 80px;
}

Просмотреть файл

@ -1230,10 +1230,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
border: 1px solid ThreeDShadow;
}
#urlbar {
-moz-padding-end: 2px;
}
/* overlap the urlbar's border */
#PopupAutoCompleteRichResult {
margin-top: -1px;
@ -1267,7 +1263,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
.searchbar-textbox:not(:-moz-lwtheme) {
border-color: hsl(0,0%,90%);
padding: 1px;
-moz-padding-end: 3px;
transition-property: border-color, box-shadow;
transition-duration: .1s;
}

Просмотреть файл

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<dimen name="tab_panel_column_width">156dip</dimen>
<dimen name="tab_thumbnail_height">110dip</dimen>
<dimen name="tab_thumbnail_width">148dip</dimen>
</resources>

Просмотреть файл

@ -4,7 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<dimen name="tab_panel_column_width">174dip</dimen>
<dimen name="tab_panel_column_width">176dip</dimen>
<dimen name="tab_thumbnail_height">120dip</dimen>
<dimen name="tab_thumbnail_width">168dip</dimen>
</resources>

Просмотреть файл

@ -104,12 +104,6 @@ this.BrowserIDManager.prototype = {
// we don't consider the lack of a keybundle as a failure state.
_shouldHaveSyncKeyBundle: false,
get readyToAuthenticate() {
// We are finished initializing when we *should* have a sync key bundle,
// although we might not actually have one due to auth failures etc.
return this._shouldHaveSyncKeyBundle;
},
get needsCustomization() {
try {
return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
@ -122,7 +116,19 @@ this.BrowserIDManager.prototype = {
for (let topic of OBSERVER_TOPICS) {
Services.obs.addObserver(this, topic, false);
}
return this.initializeWithCurrentIdentity();
// and a background fetch of account data just so we can set this.account,
// so we have a username available before we've actually done a login.
// XXX - this is actually a hack just for tests and really shouldn't be
// necessary. Also, you'd think it would be safe to allow this.account to
// be set to null when there's no user logged in, but argue with the test
// suite, not with me :)
this._fxaService.getSignedInUser().then(accountData => {
if (accountData) {
this.account = accountData.email;
}
}).catch(err => {
// As above, this is only for tests so it is safe to ignore.
});
},
/**
@ -130,7 +136,7 @@ this.BrowserIDManager.prototype = {
* the user is logged in, or is rejected if the login attempt has failed.
*/
ensureLoggedIn: function() {
if (!this._shouldHaveSyncKeyBundle) {
if (!this._shouldHaveSyncKeyBundle && this.whenReadyToAuthenticate) {
// We are already in the process of logging in.
return this.whenReadyToAuthenticate.promise;
}
@ -160,7 +166,6 @@ this.BrowserIDManager.prototype = {
}
this.resetCredentials();
this._signedInUser = null;
return Promise.resolve();
},
offerSyncOptions: function () {
@ -294,7 +299,8 @@ this.BrowserIDManager.prototype = {
// reauth with the server - in that case we will also get here, but
// should have the same identity.
// initializeWithCurrentIdentity will throw and log if these constraints
// aren't met, so just go ahead and do the init.
// aren't met (indirectly, via _updateSignedInUser()), so just go ahead
// and do the init.
this.initializeWithCurrentIdentity(true);
break;
@ -636,7 +642,6 @@ this.BrowserIDManager.prototype = {
// that there is no authentication dance still under way.
this._shouldHaveSyncKeyBundle = true;
Weave.Status.login = this._authFailureReason;
Services.obs.notifyObservers(null, "weave:ui:login:error", null);
throw err;
});
},

Просмотреть файл

@ -122,7 +122,6 @@ LOGIN_FAILED_NETWORK_ERROR: "error.login.reason.network",
LOGIN_FAILED_SERVER_ERROR: "error.login.reason.server",
LOGIN_FAILED_INVALID_PASSPHRASE: "error.login.reason.recoverykey",
LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.account",
LOGIN_FAILED_NOT_READY: "error.login.reason.initializing",
// sync failure status codes
METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",

Просмотреть файл

@ -85,18 +85,14 @@ IdentityManager.prototype = {
_syncKeyBundle: null,
/**
* Initialize the identity provider. Returns a promise that is resolved
* when initialization is complete and the provider can be queried for
* its state
* Initialize the identity provider.
*/
initialize: function() {
// Nothing to do for this identity provider.
return Promise.resolve();
},
finalize: function() {
// Nothing to do for this identity provider.
return Promise.resolve();
},
/**
@ -115,14 +111,6 @@ IdentityManager.prototype = {
return Promise.resolve();
},
/**
* Indicates if the identity manager is still initializing
*/
get readyToAuthenticate() {
// We initialize in a fully sync manner, so we are always finished.
return true;
},
get account() {
return Svc.Prefs.get("account", this.username);
},

Просмотреть файл

@ -690,13 +690,6 @@ Sync11Service.prototype = {
},
verifyLogin: function verifyLogin(allow40XRecovery = true) {
// If the identity isn't ready it might not know the username...
if (!this.identity.readyToAuthenticate) {
this._log.info("Not ready to authenticate in verifyLogin.");
this.status.login = LOGIN_FAILED_NOT_READY;
return false;
}
if (!this.identity.username) {
this._log.warn("No username in verifyLogin.");
this.status.login = LOGIN_FAILED_NO_USERNAME;
@ -943,25 +936,22 @@ Sync11Service.prototype = {
return;
}
this.identity.finalize().then(
() => {
// an observer so the FxA migration code can take some action before
// the new identity is created.
Svc.Obs.notify("weave:service:start-over:init-identity");
this.identity.username = "";
this.status.__authManager = null;
this.identity = Status._authManager;
this._clusterManager = this.identity.createClusterManager(this);
Svc.Obs.notify("weave:service:start-over:finish");
}
).then(null,
err => {
this._log.error("startOver failed to re-initialize the identity manager: " + err);
// Still send the observer notification so the current state is
// reflected in the UI.
Svc.Obs.notify("weave:service:start-over:finish");
}
);
try {
this.identity.finalize();
// an observer so the FxA migration code can take some action before
// the new identity is created.
Svc.Obs.notify("weave:service:start-over:init-identity");
this.identity.username = "";
this.status.__authManager = null;
this.identity = Status._authManager;
this._clusterManager = this.identity.createClusterManager(this);
Svc.Obs.notify("weave:service:start-over:finish");
} catch (err) {
this._log.error("startOver failed to re-initialize the identity manager: " + err);
// Still send the observer notification so the current state is
// reflected in the UI.
Svc.Obs.notify("weave:service:start-over:finish");
}
},
persistLogin: function persistLogin() {

Просмотреть файл

@ -30,10 +30,7 @@ this.Status = {
.wrappedJSObject;
let idClass = service.fxAccountsEnabled ? BrowserIDManager : IdentityManager;
this.__authManager = new idClass();
// .initialize returns a promise, so we need to spin until it resolves.
let cb = Async.makeSpinningCallback();
this.__authManager.initialize().then(cb, cb);
cb.wait();
this.__authManager.initialize();
return this.__authManager;
},

Просмотреть файл

@ -652,8 +652,8 @@ Livemark.prototype = {
let nodes = this._nodes.get(container);
for (let node of nodes) {
// Workaround for bug 449811.
localObserver = observer;
localNode = node;
let localObserver = observer;
let localNode = node;
if (!aURI || node.uri == aURI.spec) {
Services.tm.mainThread.dispatch(() => {
localObserver.nodeHistoryDetailsChanged(localNode, 0, aVisitedStatus);

Просмотреть файл

@ -75,12 +75,13 @@ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12
* This is thrown by |TelemetryStorage.loadPingFile| when reading the ping
* from the disk fails.
*/
function PingReadError(message="Error reading the ping file") {
function PingReadError(message="Error reading the ping file", becauseNoSuchFile = false) {
Error.call(this, message);
let error = new Error();
this.name = "PingReadError";
this.message = message;
this.stack = error.stack;
this.becauseNoSuchFile = becauseNoSuchFile;
}
PingReadError.prototype = Object.create(Error.prototype);
PingReadError.prototype.constructor = PingReadError;
@ -1445,7 +1446,7 @@ let TelemetryStorageImpl = {
array = yield OS.File.read(aFilePath, options);
} catch(e) {
this._log.trace("loadPingfile - unreadable ping " + aFilePath, e);
throw new PingReadError(e.message);
throw new PingReadError(e.message, e.becauseNoSuchFile);
}
let decoder = new TextDecoder();

Просмотреть файл

@ -324,7 +324,14 @@ let AnimationPlayerActor = ActorClass({
*/
onAnimationMutation: function(mutations) {
let hasChanged = false;
for (let {changedAnimations} of mutations) {
for (let {removedAnimations, changedAnimations} of mutations) {
if (removedAnimations.length) {
// Reset the local copy of the state on removal, since the animation can
// be kept on the client and re-added, its state needs to be sent in
// full.
this.currentState = null;
}
if (!changedAnimations.length) {
return;
}
@ -689,11 +696,14 @@ let AnimationsActor = exports.AnimationsActor = ActorClass({
// already have, it means it's a transition that's re-starting. So send
// a "removed" event for the one we already have.
let index = this.actors.findIndex(a => {
return a.player.constructor === player.constructor &&
((a.isAnimation() &&
a.player.animationName === player.animationName) ||
(a.isTransition() &&
a.player.transitionProperty === player.transitionProperty));
let isSameType = a.player.constructor === player.constructor;
let isSameName = (a.isAnimation() &&
a.player.animationName === player.animationName) ||
(a.isTransition() &&
a.player.transitionProperty === player.transitionProperty);
let isSameNode = a.player.effect.target === player.effect.target;
return isSameType && isSameNode && isSameName;
});
if (index !== -1) {
eventData.push({