);
@@ -887,14 +892,14 @@ loop.webapp = (function($, _, OT, mozL10n) {
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
sdk: React.PropTypes.object.isRequired,
- feedbackApiClient: React.PropTypes.object.isRequired,
// XXX New types for flux style
standaloneAppStore: React.PropTypes.instanceOf(
loop.store.StandaloneAppStore).isRequired,
activeRoomStore: React.PropTypes.instanceOf(
loop.store.ActiveRoomStore).isRequired,
- dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
+ dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+ feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
},
getInitialState: function() {
@@ -931,7 +936,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper={this.props.helper}
notifications={this.props.notifications}
sdk={this.props.sdk}
- feedbackApiClient={this.props.feedbackApiClient}
+ feedbackStore={this.props.feedbackStore}
/>
);
}
@@ -992,7 +997,14 @@ loop.webapp = (function($, _, OT, mozL10n) {
dispatcher: dispatcher,
sdk: OT
});
+ var feedbackClient = new loop.FeedbackAPIClient(
+ loop.config.feedbackApiUrl, {
+ product: loop.config.feedbackProductName,
+ user_agent: navigator.userAgent,
+ url: document.location.origin
+ });
+ // Stores
var standaloneAppStore = new loop.store.StandaloneAppStore({
conversation: conversation,
dispatcher: dispatcher,
@@ -1003,6 +1015,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
mozLoop: standaloneMozLoop,
sdkDriver: sdkDriver
});
+ var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: feedbackClient
+ });
window.addEventListener("unload", function() {
dispatcher.dispatch(new sharedActions.WindowUnload());
@@ -1014,7 +1029,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper={helper}
notifications={notifications}
sdk={OT}
- feedbackApiClient={feedbackApiClient}
+ feedbackStore={feedbackStore}
standaloneAppStore={standaloneAppStore}
activeRoomStore={activeRoomStore}
dispatcher={dispatcher}
diff --git a/browser/components/loop/standalone/content/l10n/en-US/loop.properties b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
index 86ed08c1a43..47f98f1c616 100644
--- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -124,4 +124,4 @@ standalone_title_with_status={{clientShortname}} — {{currentStatus}}
status_in_conversation=In conversation
status_conversation_ended=Conversation ended
status_error=Something went wrong
-
+support_link=Get Help
diff --git a/browser/components/loop/standalone/server.js b/browser/components/loop/standalone/server.js
index ac19b4f2e39..0893f3c5e94 100644
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -30,7 +30,9 @@ function getConfigFile(req, res) {
"loop.config.legalWebsiteUrl = '/legal/terms';",
"loop.config.fxosApp = loop.config.fxosApp || {};",
"loop.config.fxosApp.name = 'Loop';",
- "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';"
+ "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';",
+ "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';",
+ "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';"
].join("\n"));
}
diff --git a/browser/components/loop/test/desktop-local/conversationViews_test.js b/browser/components/loop/test/desktop-local/conversationViews_test.js
index 90d431c94b2..c8b190ebe27 100644
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -445,13 +445,14 @@ describe("loop.conversationViews", function () {
});
describe("OutgoingConversationView", function() {
- var store;
+ var store, feedbackStore;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
loop.conversationViews.OutgoingConversationView({
dispatcher: dispatcher,
- store: store
+ store: store,
+ feedbackStore: feedbackStore
}));
}
@@ -461,6 +462,9 @@ describe("loop.conversationViews", function () {
client: {},
sdkDriver: {}
});
+ feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: {}
+ });
});
it("should render the CallFailedView when the call state is 'terminated'",
diff --git a/browser/components/loop/test/desktop-local/conversation_test.js b/browser/components/loop/test/desktop-local/conversation_test.js
index 7bbc8d63a16..524a657afc2 100644
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -233,7 +233,8 @@ describe("loop.conversation", function() {
});
describe("IncomingConversationView", function() {
- var conversationAppStore, conversation, client, icView, oldTitle;
+ var conversationAppStore, conversation, client, icView, oldTitle,
+ feedbackStore;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@@ -241,7 +242,8 @@ describe("loop.conversation", function() {
client: client,
conversation: conversation,
sdk: {},
- conversationAppStore: conversationAppStore
+ conversationAppStore: conversationAppStore,
+ feedbackStore: feedbackStore
}));
}
@@ -257,6 +259,9 @@ describe("loop.conversation", function() {
dispatcher: dispatcher,
mozLoop: navigator.mozLoop
});
+ feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: {}
+ });
sandbox.stub(conversation, "setOutgoingSessionData");
});
diff --git a/browser/components/loop/test/desktop-local/index.html b/browser/components/loop/test/desktop-local/index.html
index ec206b20fa3..5c64e30752c 100644
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -46,6 +46,8 @@
+
+
diff --git a/browser/components/loop/test/desktop-local/panel_test.js b/browser/components/loop/test/desktop-local/panel_test.js
index 7e051a7e172..e678c4a9405 100644
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -356,6 +356,31 @@ describe("loop.panel", function() {
});
});
+ describe("Help", function() {
+ var supportUrl = "https://example.com";
+
+ beforeEach(function() {
+ navigator.mozLoop.getLoopPref = function(pref) {
+ if (pref === "support_url")
+ return supportUrl;
+ return "unseen";
+ };
+
+ sandbox.stub(window, "open");
+ sandbox.stub(window, "close");
+ });
+
+ it("should open a tab to the support page", function() {
+ var view = TestUtils.renderIntoDocument(loop.panel.SettingsDropdown());
+
+ TestUtils.Simulate
+ .click(view.getDOMNode().querySelector(".icon-help"));
+
+ sinon.assert.calledOnce(window.open);
+ sinon.assert.calledWithExactly(window.open, supportUrl);
+ });
+ });
+
describe("#render", function() {
it("should render a ToSView", function() {
var view = createTestPanelView();
diff --git a/browser/components/loop/test/shared/feedbackStore_test.js b/browser/components/loop/test/shared/feedbackStore_test.js
new file mode 100644
index 00000000000..8213189842a
--- /dev/null
+++ b/browser/components/loop/test/shared/feedbackStore_test.js
@@ -0,0 +1,108 @@
+/* global chai, loop */
+
+var expect = chai.expect;
+var sharedActions = loop.shared.actions;
+
+describe("loop.store.FeedbackStore", function () {
+ "use strict";
+
+ var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
+ var sandbox, dispatcher, store, feedbackClient;
+
+ beforeEach(function() {
+ sandbox = sinon.sandbox.create();
+
+ dispatcher = new loop.Dispatcher();
+
+ feedbackClient = new loop.FeedbackAPIClient("http://invalid", {
+ product: "Loop"
+ });
+
+ store = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: feedbackClient
+ });
+ });
+
+ afterEach(function() {
+ sandbox.restore();
+ });
+
+ describe("#constructor", function() {
+ it("should throw an error if feedbackClient is missing", function() {
+ expect(function() {
+ new loop.store.FeedbackStore(dispatcher);
+ }).to.Throw(/feedbackClient/);
+ });
+
+ it("should set the store to the INIT feedback state", function() {
+ var store = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: feedbackClient
+ });
+
+ expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.INIT);
+ });
+ });
+
+ describe("#requireFeedbackDetails", function() {
+ it("should transition to DETAILS state", function() {
+ store.requireFeedbackDetails(new sharedActions.RequireFeedbackDetails());
+
+ expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.DETAILS);
+ });
+ });
+
+ describe("#sendFeedback", function() {
+ var sadFeedbackData = {
+ happy: false,
+ category: "fakeCategory",
+ description: "fakeDescription"
+ };
+
+ beforeEach(function() {
+ store.requireFeedbackDetails();
+ });
+
+ it("should send feedback data over the feedback client", function() {
+ sandbox.stub(feedbackClient, "send");
+
+ store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
+
+ sinon.assert.calledOnce(feedbackClient.send);
+ sinon.assert.calledWithMatch(feedbackClient.send, sadFeedbackData);
+ });
+
+ it("should transition to PENDING state", function() {
+ sandbox.stub(feedbackClient, "send");
+
+ store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
+
+ expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.PENDING);
+ });
+
+ it("should transition to SENT state on successful submission", function(done) {
+ sandbox.stub(feedbackClient, "send", function(data, cb) {
+ cb(null);
+ });
+
+ store.once("change:feedbackState", function() {
+ expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.SENT);
+ done();
+ });
+
+ store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
+ });
+
+ it("should transition to FAILED state on failed submission", function(done) {
+ sandbox.stub(feedbackClient, "send", function(data, cb) {
+ cb(new Error("failed"));
+ });
+
+ store.once("change:feedbackState", function() {
+ expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.FAILED);
+ done();
+ });
+
+ store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
+ });
+ });
+});
diff --git a/browser/components/loop/test/shared/feedbackViews_test.js b/browser/components/loop/test/shared/feedbackViews_test.js
new file mode 100644
index 00000000000..052149bfdef
--- /dev/null
+++ b/browser/components/loop/test/shared/feedbackViews_test.js
@@ -0,0 +1,209 @@
+/* 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/. */
+
+/*global loop, sinon, React */
+/* jshint newcap:false */
+
+var expect = chai.expect;
+var l10n = navigator.mozL10n || document.mozL10n;
+var TestUtils = React.addons.TestUtils;
+var sharedActions = loop.shared.actions;
+var sharedViews = loop.shared.views;
+
+var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
+
+describe("loop.shared.views.FeedbackView", function() {
+ "use strict";
+
+ var sandbox, comp, dispatcher, feedbackStore, fakeAudioXHR, fakeFeedbackClient;
+
+ 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
+ }));
+ });
+
+ afterEach(function() {
+ sandbox.restore();
+ });
+
+ // local test helpers
+ function clickHappyFace(comp) {
+ var happyFace = comp.getDOMNode().querySelector(".face-happy");
+ TestUtils.Simulate.click(happyFace);
+ }
+
+ function clickSadFace(comp) {
+ var sadFace = comp.getDOMNode().querySelector(".face-sad");
+ TestUtils.Simulate.click(sadFace);
+ }
+
+ function fillSadFeedbackForm(comp, category, text) {
+ TestUtils.Simulate.change(
+ comp.getDOMNode().querySelector("[value='" + category + "']"));
+
+ if (text) {
+ TestUtils.Simulate.change(
+ comp.getDOMNode().querySelector("[name='description']"), {
+ target: {value: "fake reason"}
+ });
+ }
+ }
+
+ function submitSadFeedbackForm(comp, category, text) {
+ TestUtils.Simulate.submit(comp.getDOMNode().querySelector("form"));
+ }
+
+ describe("Happy feedback", function() {
+ it("should dispatch a SendFeedback action", function() {
+ var dispatch = sandbox.stub(dispatcher, "dispatch");
+
+ clickHappyFace(comp);
+
+ sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({
+ happy: true,
+ category: "",
+ description: ""
+ }));
+ });
+
+ it("should thank the user once feedback data is sent", function() {
+ feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
+
+ expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null);
+ expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back"))
+ .eql(null);
+ });
+ });
+
+ describe("Sad feedback", function() {
+ it("should bring the user to feedback form when clicking on the sad face",
+ function() {
+ clickSadFace(comp);
+
+ expect(comp.getDOMNode().querySelectorAll("form")).not.eql(null);
+ });
+
+ it("should render a back button", function() {
+ feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
+
+ expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back"))
+ .not.eql(null);
+ });
+
+ it("should reset the view when clicking the back button", function() {
+ feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
+
+ TestUtils.Simulate.click(
+ comp.getDOMNode().querySelector("button.fx-embedded-btn-back"));
+
+ expect(comp.getDOMNode().querySelector(".faces")).not.eql(null);
+ });
+
+ it("should disable the form submit button when no category is chosen",
+ function() {
+ clickSadFace(comp);
+
+ expect(comp.getDOMNode().querySelector("form button").disabled).eql(true);
+ });
+
+ it("should disable the form submit button when the 'other' category is " +
+ "chosen but no description has been entered yet",
+ function() {
+ clickSadFace(comp);
+ fillSadFeedbackForm(comp, "other");
+
+ expect(comp.getDOMNode().querySelector("form button").disabled).eql(true);
+ });
+
+ it("should enable the form submit button when the 'other' category is " +
+ "chosen and a description is entered",
+ function() {
+ clickSadFace(comp);
+ fillSadFeedbackForm(comp, "other", "fake");
+
+ expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
+ });
+
+ it("should empty the description field when a predefined category is " +
+ "chosen",
+ function() {
+ clickSadFace(comp);
+
+ fillSadFeedbackForm(comp, "confusing");
+
+ expect(comp.getDOMNode().querySelector(".feedback-description").value).eql("");
+ });
+
+ it("should enable the form submit button once a predefined category is " +
+ "chosen",
+ function() {
+ clickSadFace(comp);
+
+ fillSadFeedbackForm(comp, "confusing");
+
+ expect(comp.getDOMNode().querySelector("form button").disabled).eql(false);
+ });
+
+ it("should send feedback data when the form is submitted", function() {
+ var dispatch = sandbox.stub(dispatcher, "dispatch");
+ feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
+ fillSadFeedbackForm(comp, "confusing");
+
+ submitSadFeedbackForm(comp);
+
+ sinon.assert.calledOnce(dispatch);
+ sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({
+ happy: false,
+ category: "confusing",
+ description: ""
+ }));
+ });
+
+ it("should send feedback data when user has entered a custom description",
+ function() {
+ clickSadFace(comp);
+
+ fillSadFeedbackForm(comp, "other", "fake reason");
+ submitSadFeedbackForm(comp);
+
+ sinon.assert.calledOnce(fakeFeedbackClient.send);
+ sinon.assert.calledWith(fakeFeedbackClient.send, {
+ happy: false,
+ category: "other",
+ description: "fake reason"
+ });
+ });
+
+ it("should thank the user when feedback data has been sent", function() {
+ fakeFeedbackClient.send = function(data, cb) {
+ cb();
+ };
+ clickSadFace(comp);
+ fillSadFeedbackForm(comp, "confusing");
+ submitSadFeedbackForm(comp);
+
+ expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null);
+ });
+ });
+});
diff --git a/browser/components/loop/test/shared/index.html b/browser/components/loop/test/shared/index.html
index 8d697a75cb4..2e1b8cb11ed 100644
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -47,6 +47,8 @@
+
+
@@ -55,10 +57,12 @@
+
+
diff --git a/browser/components/loop/test/shared/views_test.js b/browser/components/loop/test/shared/views_test.js
index fb72e059cf3..ede4b97e75d 100644
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -526,177 +526,6 @@ describe("loop.shared.views", function() {
});
});
- describe("FeedbackView", function() {
- var comp, fakeFeedbackApiClient;
-
- beforeEach(function() {
- fakeFeedbackApiClient = {send: sandbox.stub()};
- sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
- comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
- feedbackApiClient: fakeFeedbackApiClient
- }));
- });
-
- // local test helpers
- function clickHappyFace(comp) {
- var happyFace = comp.getDOMNode().querySelector(".face-happy");
- TestUtils.Simulate.click(happyFace);
- }
-
- function clickSadFace(comp) {
- var sadFace = comp.getDOMNode().querySelector(".face-sad");
- TestUtils.Simulate.click(sadFace);
- }
-
- function fillSadFeedbackForm(comp, category, text) {
- TestUtils.Simulate.change(
- comp.getDOMNode().querySelector("[value='" + category + "']"));
-
- if (text) {
- TestUtils.Simulate.change(
- comp.getDOMNode().querySelector("[name='description']"), {
- target: {value: "fake reason"}
- });
- }
- }
-
- function submitSadFeedbackForm(comp, category, text) {
- TestUtils.Simulate.submit(comp.getDOMNode().querySelector("form"));
- }
-
- describe("Happy feedback", function() {
- it("should send feedback data when clicking on the happy face",
- function() {
- clickHappyFace(comp);
-
- sinon.assert.calledOnce(fakeFeedbackApiClient.send);
- sinon.assert.calledWith(fakeFeedbackApiClient.send, {happy: true});
- });
-
- it("should thank the user once happy feedback data is sent", function() {
- fakeFeedbackApiClient.send = function(data, cb) {
- cb();
- };
-
- clickHappyFace(comp);
-
- expect(comp.getDOMNode()
- .querySelectorAll(".feedback .thank-you").length).eql(1);
- expect(comp.getDOMNode().querySelector("button.back")).to.be.a("null");
- });
- });
-
- describe("Sad feedback", function() {
- it("should bring the user to feedback form when clicking on the sad face",
- function() {
- clickSadFace(comp);
-
- expect(comp.getDOMNode().querySelectorAll("form").length).eql(1);
- });
-
- it("should disable the form submit button when no category is chosen",
- function() {
- clickSadFace(comp);
-
- expect(comp.getDOMNode()
- .querySelector("form button").disabled).eql(true);
- });
-
- it("should disable the form submit button when the 'other' category is " +
- "chosen but no description has been entered yet",
- function() {
- clickSadFace(comp);
- fillSadFeedbackForm(comp, "other");
-
- expect(comp.getDOMNode()
- .querySelector("form button").disabled).eql(true);
- });
-
- it("should enable the form submit button when the 'other' category is " +
- "chosen and a description is entered",
- function() {
- clickSadFace(comp);
- fillSadFeedbackForm(comp, "other", "fake");
-
- expect(comp.getDOMNode()
- .querySelector("form button").disabled).eql(false);
- });
-
- it("should empty the description field when a predefined category is " +
- "chosen",
- function() {
- clickSadFace(comp);
-
- fillSadFeedbackForm(comp, "confusing");
-
- expect(comp.getDOMNode()
- .querySelector(".feedback-description").value).eql("");
- });
-
- it("should enable the form submit button once a predefined category is " +
- "chosen",
- function() {
- clickSadFace(comp);
-
- fillSadFeedbackForm(comp, "confusing");
-
- expect(comp.getDOMNode()
- .querySelector("form button").disabled).eql(false);
- });
-
- it("should disable the form submit button once the form is submitted",
- function() {
- clickSadFace(comp);
- fillSadFeedbackForm(comp, "confusing");
-
- submitSadFeedbackForm(comp);
-
- expect(comp.getDOMNode()
- .querySelector("form button").disabled).eql(true);
- });
-
- it("should send feedback data when the form is submitted", function() {
- clickSadFace(comp);
- fillSadFeedbackForm(comp, "confusing");
-
- submitSadFeedbackForm(comp);
-
- sinon.assert.calledOnce(fakeFeedbackApiClient.send);
- sinon.assert.calledWithMatch(fakeFeedbackApiClient.send, {
- happy: false,
- category: "confusing"
- });
- });
-
- it("should send feedback data when user has entered a custom description",
- function() {
- clickSadFace(comp);
-
- fillSadFeedbackForm(comp, "other", "fake reason");
- submitSadFeedbackForm(comp);
-
- sinon.assert.calledOnce(fakeFeedbackApiClient.send);
- sinon.assert.calledWith(fakeFeedbackApiClient.send, {
- happy: false,
- category: "other",
- description: "fake reason"
- });
- });
-
- it("should thank the user when feedback data has been sent", function() {
- fakeFeedbackApiClient.send = function(data, cb) {
- cb();
- };
- clickSadFace(comp);
- fillSadFeedbackForm(comp, "confusing");
- submitSadFeedbackForm(comp);
-
- expect(comp.getDOMNode()
- .querySelectorAll(".feedback .thank-you").length).eql(1);
- });
- });
- });
-
describe("NotificationListView", function() {
var coll, view, testNotif;
diff --git a/browser/components/loop/test/standalone/index.html b/browser/components/loop/test/standalone/index.html
index b5f6054d634..f26016dd322 100644
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -42,6 +42,8 @@
+
+
diff --git a/browser/components/loop/test/standalone/webapp_test.js b/browser/components/loop/test/standalone/webapp_test.js
index 21ce7f1690a..7b989db3d04 100644
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -19,14 +19,20 @@ describe("loop.webapp", function() {
notifications,
feedbackApiClient,
stubGetPermsAndCacheMedia,
- fakeAudioXHR;
+ fakeAudioXHR,
+ dispatcher,
+ feedbackStore;
beforeEach(function() {
sandbox = sinon.sandbox.create();
+ dispatcher = new loop.Dispatcher();
notifications = new sharedModels.NotificationCollection();
feedbackApiClient = new loop.FeedbackAPIClient("http://invalid", {
product: "Loop"
});
+ feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: {}
+ });
stubGetPermsAndCacheMedia = sandbox.stub(
loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
@@ -123,7 +129,7 @@ describe("loop.webapp", function() {
conversation: conversation,
notifications: notifications,
sdk: {},
- feedbackApiClient: feedbackApiClient
+ feedbackStore: feedbackStore
});
});
@@ -582,7 +588,7 @@ describe("loop.webapp", function() {
describe("WebappRootView", function() {
var helper, sdk, conversationModel, client, props, standaloneAppStore;
- var dispatcher, activeRoomStore;
+ var activeRoomStore;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@@ -609,7 +615,6 @@ describe("loop.webapp", function() {
client = new loop.StandaloneClient({
baseServerUrl: "fakeUrl"
});
- dispatcher = new loop.Dispatcher();
activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
mozLoop: {},
sdkDriver: {}
@@ -1039,7 +1044,7 @@ describe("loop.webapp", function() {
loop.webapp.EndedConversationView({
conversation: conversation,
sdk: {},
- feedbackApiClient: feedbackApiClient,
+ feedbackStore: feedbackStore,
onAfterFeedbackReceived: function(){}
})
);
diff --git a/browser/components/loop/ui/index.html b/browser/components/loop/ui/index.html
index 85f8740774d..f5822eaff53 100644
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -45,6 +45,8 @@
+
+
diff --git a/browser/components/loop/ui/ui-showcase.js b/browser/components/loop/ui/ui-showcase.js
index cc74e6afb60..d1d8f7f2706 100644
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -39,8 +39,9 @@
var ConversationView = loop.shared.views.ConversationView;
var FeedbackView = loop.shared.views.FeedbackView;
- // Room constants
+ // Store constants
var ROOM_STATES = loop.store.ROOM_STATES;
+ var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
// Local helpers
function returnTrue() {
@@ -69,6 +70,9 @@
var roomStore = new loop.store.RoomStore(dispatcher, {
mozLoop: navigator.mozLoop
});
+ var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: stageFeedbackApiClient
+ });
// Local mocks
@@ -460,13 +464,13 @@
React.DOM.a({href: "https://input.allizom.org/"}, "input.allizom.org"), "."
),
Example({summary: "Default (useable demo)", dashed: "true", style: {width: "260px"}},
- FeedbackView({feedbackApiClient: stageFeedbackApiClient})
+ FeedbackView({feedbackStore: feedbackStore})
),
Example({summary: "Detailed form", dashed: "true", style: {width: "260px"}},
- FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "form"})
+ FeedbackView({feedbackStore: feedbackStore, feedbackState: FEEDBACK_STATES.DETAILS})
),
Example({summary: "Thank you!", dashed: "true", style: {width: "260px"}},
- FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "finished"})
+ FeedbackView({feedbackStore: feedbackStore, feedbackState: FEEDBACK_STATES.SENT})
)
),
@@ -486,7 +490,7 @@
video: {enabled: true},
audio: {enabled: true},
conversation: mockConversationModel,
- feedbackApiClient: stageFeedbackApiClient,
+ feedbackStore: feedbackStore,
onAfterFeedbackReceived: noop})
)
)
diff --git a/browser/components/loop/ui/ui-showcase.jsx b/browser/components/loop/ui/ui-showcase.jsx
index 43668b50018..281ccff64cd 100644
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -39,8 +39,9 @@
var ConversationView = loop.shared.views.ConversationView;
var FeedbackView = loop.shared.views.FeedbackView;
- // Room constants
+ // Store constants
var ROOM_STATES = loop.store.ROOM_STATES;
+ var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
// Local helpers
function returnTrue() {
@@ -69,6 +70,9 @@
var roomStore = new loop.store.RoomStore(dispatcher, {
mozLoop: navigator.mozLoop
});
+ var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+ feedbackClient: stageFeedbackApiClient
+ });
// Local mocks
@@ -460,13 +464,13 @@
input.allizom.org.
-
+
-
+
-
+
@@ -486,7 +490,7 @@
video={{enabled: true}}
audio={{enabled: true}}
conversation={mockConversationModel}
- feedbackApiClient={stageFeedbackApiClient}
+ feedbackStore={feedbackStore}
onAfterFeedbackReceived={noop} />
diff --git a/browser/devtools/app-manager/app-projects.js b/browser/devtools/app-manager/app-projects.js
index 0fe287975ce..eb3c2ab32c6 100644
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -16,14 +16,15 @@ const { indexedDB } = require("sdk/indexed-db");
const IDB = {
_db: null,
+ databaseName: "AppProjects",
open: function () {
let deferred = promise.defer();
- let request = indexedDB.open("AppProjects", 5);
+ let request = indexedDB.open(IDB.databaseName, 5);
request.onerror = function(event) {
- deferred.reject("Unable to open AppProjects indexedDB. " +
- "Error code: " + event.target.errorCode);
+ deferred.reject("Unable to open AppProjects indexedDB: " +
+ this.error.name + " - " + this.error.message );
};
request.onupgradeneeded = function(event) {
let db = event.target.result;
@@ -147,11 +148,10 @@ const store = new ObservableObject({ projects:[] });
let loadDeferred = promise.defer();
-IDB.open().then(function (projects) {
+loadDeferred.resolve(IDB.open().then(function (projects) {
store.object.projects = projects;
AppProjects.emit("ready", store.object.projects);
- loadDeferred.resolve();
-});
+}));
const AppProjects = {
load: function() {
diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js
index 95a0e33bf11..f3618742aca 100644
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -72,6 +72,9 @@ let UI = {
AppProjects.load().then(() => {
this.autoSelectProject();
+ }, e => {
+ console.error(e);
+ this.reportError("error_appProjectsLoadFailed");
});
// Auto install the ADB Addon Helper and Tools Adapters. Only once.
@@ -256,7 +259,7 @@ let UI = {
this._busyTimeout = setTimeout(() => {
this.unbusy();
UI.reportError("error_operationTimeout", this._busyOperationDescription);
- }, 6000);
+ }, Services.prefs.getIntPref("devtools.webide.busyTimeout"));
},
cancelBusyTimeout: function() {
diff --git a/browser/devtools/webide/webide-prefs.js b/browser/devtools/webide/webide-prefs.js
index 8b3c64c7440..15fae4dac3b 100644
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -32,3 +32,4 @@ pref("devtools.webide.widget.enabled", false);
pref("devtools.webide.widget.inNavbarByDefault", false);
#endif
pref("devtools.webide.zoom", "1");
+pref("devtools.webide.busyTimeout", 10000);
diff --git a/browser/locales/en-US/chrome/browser/devtools/webide.properties b/browser/locales/en-US/chrome/browser/devtools/webide.properties
index 314cdf31eb5..c658c439920 100644
--- a/browser/locales/en-US/chrome/browser/devtools/webide.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties
@@ -20,7 +20,7 @@ importHostedApp_title=Open Hosted App
importHostedApp_header=Enter Manifest URL
notification_showTroubleShooting_label=Troubleshooting
-notification_showTroubleShooting_accesskey=t
+notification_showTroubleShooting_accesskey=T
# LOCALIZATION NOTE (project_tab_loading): This is shown as a temporary tab
# title for browser tab projects when the tab is still loading.
@@ -42,6 +42,8 @@ error_cantConnectToApp=Can't connect to app: %1$S
# Variable: error message (in english)
error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
+error_appProjectsLoadFailed=Unable to load project list. This can occur if you've used this profile with a newer version of Firefox.
+
addons_stable=stable
addons_unstable=unstable
# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
diff --git a/configure.in b/configure.in
index 2b8db8d4a4f..1aae34cf00f 100644
--- a/configure.in
+++ b/configure.in
@@ -2212,7 +2212,7 @@ ia64*-hpux*)
if test "$CPU_ARCH" = "x86"; then
WIN32_SUBSYSTEM_VERSION=5.01
else
- WIN32_SUBSYSTEM_VERSION=5.02
+ WIN32_SUBSYSTEM_VERSION=6.01
fi
WIN32_CONSOLE_EXE_LDFLAGS=-SUBSYSTEM:CONSOLE,$WIN32_SUBSYSTEM_VERSION
WIN32_GUI_EXE_LDFLAGS=-SUBSYSTEM:WINDOWS,$WIN32_SUBSYSTEM_VERSION
@@ -5935,53 +5935,57 @@ if test -n "$MOZ_ANGLE_RENDERER"; then
######################################
# Find _43 for use by XP.
- # Get the SDK path from the registry.
- # First try to get the June 2010 SDK
- MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK (June 2010)' | head -n 1`
- if test -z "$MOZ_DIRECTX_SDK_REG_KEY" ; then
- # Otherwise just take whatever comes first
- MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK' | head -n 1`
- fi
- MOZ_DIRECTX_SDK_PATH=`reg query "$MOZ_DIRECTX_SDK_REG_KEY" //v InstallPath | grep REG_SZ | sed 's/.*\([[a-zA-Z]]\)\\:\\\\/\\1\\:\\\\/' | sed 's,\\\\,/,g'`
-
- if test -n "$MOZ_DIRECTX_SDK_PATH" &&
- test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/dxguid.lib ; then
- AC_MSG_RESULT([Found DirectX SDK via registry, using $MOZ_DIRECTX_SDK_PATH])
+ if test "$HAVE_64BIT_BUILD"; then
+ AC_MSG_RESULT([We are building a 64-bit binary, skip checking d3dcompiler_43.])
else
- AC_MSG_RESULT([DirectX SDK not found.])
- MOZ_DIRECTX_SDK_PATH=
- fi
+ # Get the SDK path from the registry.
+ # First try to get the June 2010 SDK
+ MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK (June 2010)' | head -n 1`
+ if test -z "$MOZ_DIRECTX_SDK_REG_KEY" ; then
+ # Otherwise just take whatever comes first
+ MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK' | head -n 1`
+ fi
+ MOZ_DIRECTX_SDK_PATH=`reg query "$MOZ_DIRECTX_SDK_REG_KEY" //v InstallPath | grep REG_SZ | sed 's/.*\([[a-zA-Z]]\)\\:\\\\/\\1\\:\\\\/' | sed 's,\\\\,/,g'`
- # Check that our DirectX SDK is acceptable.
- if test -n "$MOZ_DIRECTX_SDK_PATH"; then
- if test -n "`echo $MOZ_DIRECTX_SDK_REG_KEY | grep 'February 2010'`" ; then
- AC_MSG_RESULT([Found the February 2010 DirectX SDK, which is unacceptable to ANGLE.])
+ if test -n "$MOZ_DIRECTX_SDK_PATH" &&
+ test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/dxguid.lib ; then
+ AC_MSG_RESULT([Found DirectX SDK via registry, using $MOZ_DIRECTX_SDK_PATH])
+ else
+ AC_MSG_RESULT([DirectX SDK not found.])
MOZ_DIRECTX_SDK_PATH=
fi
- fi
- if test -n "$MOZ_DIRECTX_SDK_PATH"; then
- # Find a D3D compiler DLL in the DirectX SDK, if we didn't find one already.
- # Get the SDK numeric version (e.g. 43) by looking at the dependencies of d3dx9.lib
- MOZ_D3DX9_VERSION=`dumpbin //headers "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/d3dx9.lib | egrep d3dx9_[[0-9]][[0-9]]\.dll | head -n1 | sed 's/.*\([[0-9]][[0-9]]\).*/\\1/g'`
+ # Check that our DirectX SDK is acceptable.
+ if test -n "$MOZ_DIRECTX_SDK_PATH"; then
+ if test -n "`echo $MOZ_DIRECTX_SDK_REG_KEY | grep 'February 2010'`" ; then
+ AC_MSG_RESULT([Found the February 2010 DirectX SDK, which is unacceptable to ANGLE.])
+ MOZ_DIRECTX_SDK_PATH=
+ fi
+ fi
- if test -n "$MOZ_D3DX9_VERSION" ; then
- MOZ_D3DCOMPILER_XP_CAB=`find "$MOZ_DIRECTX_SDK_PATH"/Redist -name *D3DCompiler_${MOZ_D3DX9_VERSION}_${MOZ_D3D_CPU_SUFFIX}.cab | head -n1`
+ if test -n "$MOZ_DIRECTX_SDK_PATH"; then
+ # Find a D3D compiler DLL in the DirectX SDK, if we didn't find one already.
+ # Get the SDK numeric version (e.g. 43) by looking at the dependencies of d3dx9.lib
+ MOZ_D3DX9_VERSION=`dumpbin //headers "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/d3dx9.lib | egrep d3dx9_[[0-9]][[0-9]]\.dll | head -n1 | sed 's/.*\([[0-9]][[0-9]]\).*/\\1/g'`
- if test -n "$MOZ_D3DCOMPILER_XP_CAB"; then
- MOZ_D3DCOMPILER_XP_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll
+ if test -n "$MOZ_D3DX9_VERSION" ; then
+ MOZ_D3DCOMPILER_XP_CAB=`find "$MOZ_DIRECTX_SDK_PATH"/Redist -name *D3DCompiler_${MOZ_D3DX9_VERSION}_${MOZ_D3D_CPU_SUFFIX}.cab | head -n1`
+
+ if test -n "$MOZ_D3DCOMPILER_XP_CAB"; then
+ MOZ_D3DCOMPILER_XP_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll
+ else
+ AC_MSG_RESULT([Couldn't find a CAB containing the D3D compiler DLL.])
+ AC_MSG_ERROR([DirectX SDK at "$MOZ_DIRECTX_SDK_PATH" appears broken.])
+ MOZ_DIRECTX_SDK_PATH=
+ fi
else
- AC_MSG_RESULT([Couldn't find a CAB containing the D3D compiler DLL.])
- AC_MSG_ERROR([DirectX SDK at "$MOZ_DIRECTX_SDK_PATH" appears broken.])
+ AC_MSG_RESULT([Couldn't determine the D3DX9 version for the DirectX SDK.])
MOZ_DIRECTX_SDK_PATH=
fi
else
- AC_MSG_RESULT([Couldn't determine the D3DX9 version for the DirectX SDK.])
- MOZ_DIRECTX_SDK_PATH=
+ AC_MSG_RESULT([Couldn't find an acceptable DirectX SDK for ANGLE, needed for d3dcompiler_43.])
+ AC_MSG_RESULT([ Either ignore, install DirectX SDK (June 2010 version or newer), or reconfigure with --disable-webgl.])
fi
- else
- AC_MSG_RESULT([Couldn't find an acceptable DirectX SDK for ANGLE, needed for d3dcompiler_43.])
- AC_MSG_RESULT([ Either ignore, install DirectX SDK (June 2010 version or newer), or reconfigure with --disable-webgl.])
fi
######################################
diff --git a/content/base/crashtests/894137.html b/content/base/crashtests/894137.html
deleted file mode 100644
index 41e7f98d9dd..00000000000
--- a/content/base/crashtests/894137.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-