зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound a=merge
This commit is contained in:
Коммит
a52d89e75d
|
@ -121,7 +121,7 @@ static bool IsArg(const char* arg, const char* s)
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
#if defined(XP_WIN) && defined(MOZ_METRO)
|
||||
/*
|
||||
* AttachToTestHarness - Windows helper for when we are running
|
||||
* in the immersive environment. Firefox is launched by Windows in
|
||||
|
|
|
@ -1669,6 +1669,7 @@ pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
|
|||
pref("loop.rooms.enabled", true);
|
||||
pref("loop.fxa_oauth.tokendata", "");
|
||||
pref("loop.fxa_oauth.profile", "");
|
||||
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
|
||||
|
||||
// serverURL to be assigned by services team
|
||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||
|
|
|
@ -12,6 +12,22 @@ function parseQueryString() {
|
|||
|
||||
document.title = parseQueryString();
|
||||
|
||||
addEventListener("DOMContentLoaded", () => {
|
||||
let tryAgain = document.getElementById("tryAgain");
|
||||
let sendCrashReport = document.getElementById("checkSendReport");
|
||||
|
||||
tryAgain.addEventListener("click", () => {
|
||||
let event = new CustomEvent("AboutTabCrashedTryAgain", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
sendCrashReport: sendCrashReport.checked,
|
||||
},
|
||||
});
|
||||
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
// Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
|
||||
var event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
|
|
@ -1142,6 +1142,28 @@ var gBrowserInit = {
|
|||
#endif
|
||||
}, false, true);
|
||||
|
||||
gBrowser.addEventListener("AboutTabCrashedTryAgain", function(event) {
|
||||
let ownerDoc = event.originalTarget;
|
||||
|
||||
if (!ownerDoc.documentURI.startsWith("about:tabcrashed")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
|
||||
if (!isTopFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = gBrowser.getBrowserForDocument(ownerDoc);
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
if (event.detail.sendCrashReport) {
|
||||
TabCrashReporter.submitCrashReport(browser);
|
||||
}
|
||||
#endif
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
SessionStore.reviveCrashedTab(tab);
|
||||
}, false, true);
|
||||
|
||||
if (uriToLoad && uriToLoad != "about:blank") {
|
||||
if (uriToLoad instanceof Ci.nsISupportsArray) {
|
||||
let count = uriToLoad.Count();
|
||||
|
@ -2606,9 +2628,6 @@ let BrowserOnClick = {
|
|||
ownerDoc.documentURI.toLowerCase() == "about:newtab") {
|
||||
this.onE10sAboutNewTab(event, ownerDoc);
|
||||
}
|
||||
else if (ownerDoc.documentURI.startsWith("about:tabcrashed")) {
|
||||
this.onAboutTabCrashed(event, ownerDoc);
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (msg) {
|
||||
|
@ -2869,29 +2888,6 @@ let BrowserOnClick = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The about:tabcrashed can't do window.reload() because that
|
||||
* would reload the page but not use a remote browser.
|
||||
*/
|
||||
onAboutTabCrashed: function(event, ownerDoc) {
|
||||
let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
|
||||
if (!isTopFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
let button = event.originalTarget;
|
||||
if (button.id == "tryAgain") {
|
||||
let browser = gBrowser.getBrowserForDocument(ownerDoc);
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
if (ownerDoc.getElementById("checkSendReport").checked) {
|
||||
TabCrashReporter.submitCrashReport(browser);
|
||||
}
|
||||
#endif
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
SessionStore.reviveCrashedTab(tab);
|
||||
}
|
||||
},
|
||||
|
||||
ignoreWarningButton: function (isMalware) {
|
||||
// Allow users to override and continue through to the site,
|
||||
// but add a notify bar as a reminder, so that they don't lose
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
<script type="text/javascript" src="loop/shared/js/roomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/feedbackStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/feedbackViews.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversationAppStore.js"></script>
|
||||
|
|
|
@ -229,7 +229,8 @@ loop.conversation = (function(mozL10n) {
|
|||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
conversationAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationAppStore).isRequired
|
||||
loop.store.ConversationAppStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -301,21 +302,9 @@ loop.conversation = (function(mozL10n) {
|
|||
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopPref(
|
||||
"feedback.baseUrl");
|
||||
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
|
||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
return (
|
||||
sharedViews.FeedbackView({
|
||||
feedbackApiClient: feedbackClient,
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
onAfterFeedbackReceived: this.closeWindow.bind(this)}
|
||||
)
|
||||
);
|
||||
|
@ -562,7 +551,8 @@ loop.conversation = (function(mozL10n) {
|
|||
conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore)
|
||||
.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore),
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -590,26 +580,26 @@ loop.conversation = (function(mozL10n) {
|
|||
client: this.props.client,
|
||||
conversation: this.props.conversation,
|
||||
sdk: this.props.sdk,
|
||||
conversationAppStore: this.props.conversationAppStore}
|
||||
conversationAppStore: this.props.conversationAppStore,
|
||||
feedbackStore: this.props.feedbackStore}
|
||||
));
|
||||
}
|
||||
case "outgoing": {
|
||||
return (OutgoingConversationView({
|
||||
store: this.props.conversationStore,
|
||||
dispatcher: this.props.dispatcher}
|
||||
dispatcher: this.props.dispatcher,
|
||||
feedbackStore: this.props.feedbackStore}
|
||||
));
|
||||
}
|
||||
case "room": {
|
||||
return (DesktopRoomConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore,
|
||||
dispatcher: this.props.dispatcher}
|
||||
feedbackStore: this.props.feedbackStore}
|
||||
));
|
||||
}
|
||||
case "failed": {
|
||||
return (GenericFailureView({
|
||||
cancelCall: this.closeWindow}
|
||||
));
|
||||
return GenericFailureView({cancelCall: this.closeWindow});
|
||||
}
|
||||
default: {
|
||||
// If we don't have a windowType, we don't know what we are yet,
|
||||
|
@ -646,6 +636,14 @@ loop.conversation = (function(mozL10n) {
|
|||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
var feedbackClient = new loop.FeedbackAPIClient(
|
||||
navigator.mozLoop.getLoopPref("feedback.baseUrl"), {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
// Create the stores.
|
||||
var conversationAppStore = new loop.store.ConversationAppStore({
|
||||
|
@ -665,6 +663,9 @@ loop.conversation = (function(mozL10n) {
|
|||
mozLoop: navigator.mozLoop,
|
||||
activeRoomStore: activeRoomStore
|
||||
});
|
||||
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: feedbackClient
|
||||
});
|
||||
|
||||
// XXX Old class creation for the incoming conversation view, whilst
|
||||
// we transition across (bug 1072323).
|
||||
|
@ -697,6 +698,7 @@ loop.conversation = (function(mozL10n) {
|
|||
React.renderComponent(AppControllerView({
|
||||
conversationAppStore: conversationAppStore,
|
||||
roomStore: roomStore,
|
||||
feedbackStore: feedbackStore,
|
||||
conversationStore: conversationStore,
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
|
|
|
@ -229,7 +229,8 @@ loop.conversation = (function(mozL10n) {
|
|||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
conversationAppStore: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationAppStore).isRequired
|
||||
loop.store.ConversationAppStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -301,21 +302,9 @@ loop.conversation = (function(mozL10n) {
|
|||
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopPref(
|
||||
"feedback.baseUrl");
|
||||
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
|
||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
return (
|
||||
<sharedViews.FeedbackView
|
||||
feedbackApiClient={feedbackClient}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
onAfterFeedbackReceived={this.closeWindow.bind(this)}
|
||||
/>
|
||||
);
|
||||
|
@ -562,7 +551,8 @@ loop.conversation = (function(mozL10n) {
|
|||
conversationStore: React.PropTypes.instanceOf(loop.store.ConversationStore)
|
||||
.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore),
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -591,25 +581,25 @@ loop.conversation = (function(mozL10n) {
|
|||
conversation={this.props.conversation}
|
||||
sdk={this.props.sdk}
|
||||
conversationAppStore={this.props.conversationAppStore}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
/>);
|
||||
}
|
||||
case "outgoing": {
|
||||
return (<OutgoingConversationView
|
||||
store={this.props.conversationStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
/>);
|
||||
}
|
||||
case "room": {
|
||||
return (<DesktopRoomConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
dispatcher={this.props.dispatcher}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
/>);
|
||||
}
|
||||
case "failed": {
|
||||
return (<GenericFailureView
|
||||
cancelCall={this.closeWindow}
|
||||
/>);
|
||||
return <GenericFailureView cancelCall={this.closeWindow} />;
|
||||
}
|
||||
default: {
|
||||
// If we don't have a windowType, we don't know what we are yet,
|
||||
|
@ -646,6 +636,14 @@ loop.conversation = (function(mozL10n) {
|
|||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
var feedbackClient = new loop.FeedbackAPIClient(
|
||||
navigator.mozLoop.getLoopPref("feedback.baseUrl"), {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
// Create the stores.
|
||||
var conversationAppStore = new loop.store.ConversationAppStore({
|
||||
|
@ -665,6 +663,9 @@ loop.conversation = (function(mozL10n) {
|
|||
mozLoop: navigator.mozLoop,
|
||||
activeRoomStore: activeRoomStore
|
||||
});
|
||||
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: feedbackClient
|
||||
});
|
||||
|
||||
// XXX Old class creation for the incoming conversation view, whilst
|
||||
// we transition across (bug 1072323).
|
||||
|
@ -697,6 +698,7 @@ loop.conversation = (function(mozL10n) {
|
|||
React.renderComponent(<AppControllerView
|
||||
conversationAppStore={conversationAppStore}
|
||||
roomStore={roomStore}
|
||||
feedbackStore={feedbackStore}
|
||||
conversationStore={conversationStore}
|
||||
client={client}
|
||||
conversation={conversation}
|
||||
|
|
|
@ -356,7 +356,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
nameDisplayMode: "off",
|
||||
videoDisabledDisplayMode: "off"
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -431,7 +431,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired
|
||||
loop.store.ConversationStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -462,22 +463,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||
_renderFeedbackView: function() {
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopPref(
|
||||
"feedback.baseUrl");
|
||||
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
|
||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
return (
|
||||
sharedViews.FeedbackView({
|
||||
feedbackApiClient: feedbackClient,
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
onAfterFeedbackReceived: this._closeWindow.bind(this)}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -356,7 +356,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
nameDisplayMode: "off",
|
||||
videoDisabledDisplayMode: "off"
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -431,7 +431,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired
|
||||
loop.store.ConversationStore).isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -462,22 +463,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||
_renderFeedbackView: function() {
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopPref(
|
||||
"feedback.baseUrl");
|
||||
|
||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||
|
||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||
product: navigator.mozLoop.getLoopPref("feedback.product"),
|
||||
platform: appVersionInfo.OS,
|
||||
channel: appVersionInfo.channel,
|
||||
version: appVersionInfo.version
|
||||
});
|
||||
|
||||
return (
|
||||
<sharedViews.FeedbackView
|
||||
feedbackApiClient={feedbackClient}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
onAfterFeedbackReceived={this._closeWindow.bind(this)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -281,6 +281,13 @@ loop.panel = (function(_, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
handleHelpEntry: function(event) {
|
||||
event.preventDefault();
|
||||
var helloSupportUrl = navigator.mozLoop.getLoopPref('support_url');
|
||||
window.open(helloSupportUrl);
|
||||
window.close();
|
||||
},
|
||||
|
||||
_isSignedIn: function() {
|
||||
return !!navigator.mozLoop.userProfile;
|
||||
},
|
||||
|
@ -318,7 +325,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
mozL10n.get("settings_menu_item_signin"),
|
||||
onClick: this.handleClickAuthEntry,
|
||||
displayed: navigator.mozLoop.fxAEnabled,
|
||||
icon: this._isSignedIn() ? "signout" : "signin"})
|
||||
icon: this._isSignedIn() ? "signout" : "signin"}),
|
||||
SettingsDropdownEntry({label: mozL10n.get("help_label"),
|
||||
onClick: this.handleHelpEntry,
|
||||
icon: "help"})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -281,6 +281,13 @@ loop.panel = (function(_, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
handleHelpEntry: function(event) {
|
||||
event.preventDefault();
|
||||
var helloSupportUrl = navigator.mozLoop.getLoopPref('support_url');
|
||||
window.open(helloSupportUrl);
|
||||
window.close();
|
||||
},
|
||||
|
||||
_isSignedIn: function() {
|
||||
return !!navigator.mozLoop.userProfile;
|
||||
},
|
||||
|
@ -319,6 +326,9 @@ loop.panel = (function(_, mozL10n) {
|
|||
onClick={this.handleClickAuthEntry}
|
||||
displayed={navigator.mozLoop.fxAEnabled}
|
||||
icon={this._isSignedIn() ? "signout" : "signin"} />
|
||||
<SettingsDropdownEntry label={mozL10n.get("help_label")}
|
||||
onClick={this.handleHelpEntry}
|
||||
icon="help" />
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -706,6 +706,7 @@ html, .fx-embedded, #main,
|
|||
background: #000;
|
||||
height: 50px;
|
||||
text-align: left;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.room-conversation-wrapper header h1 {
|
||||
|
@ -717,6 +718,20 @@ html, .fx-embedded, #main,
|
|||
background-size: 30px;
|
||||
background-position: 10px;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.room-conversation-wrapper header a {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.room-conversation-wrapper header .icon-help {
|
||||
display: inline-block;
|
||||
background-size: contain;
|
||||
margin-top: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: transparent url("../img/svg/glyph-help-16x16.svg") no-repeat;
|
||||
}
|
||||
|
||||
.room-conversation-wrapper footer {
|
||||
|
|
|
@ -659,6 +659,10 @@ body[dir=rtl] .generate-url-spinner {
|
|||
background: transparent url(../img/svg/glyph-signout-16x16.svg) no-repeat center center;
|
||||
}
|
||||
|
||||
.settings-menu .icon-help {
|
||||
background: transparent url(../img/svg/glyph-help-16x16.svg) no-repeat center center;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
.footer {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?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/. -->
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill="#5A5A5A" cx="8" cy="8" r="8"/>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M10.716,5.643c0,1.943-2.158,1.812-2.158,3.154v0.3H6.831V8.726c0-2.075,1.907-1.932,1.907-2.915
|
||||
c0-0.432-0.312-0.684-0.84-0.684c-0.491,0-0.972,0.24-1.403,0.731L5.284,4.923C5.967,4.121,6.855,3.64,8.09,3.64
|
||||
C9.841,3.64,10.716,4.576,10.716,5.643z M8.797,11.268c0,0.6-0.479,1.092-1.079,1.092s-1.079-0.492-1.079-1.092
|
||||
c0-0.588,0.479-1.079,1.079-1.079S8.797,10.68,8.797,11.268z"/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.0 KiB |
|
@ -325,6 +325,28 @@ loop.shared.actions = (function() {
|
|||
* Used to indicate the user wishes to leave the room.
|
||||
*/
|
||||
LeaveRoom: Action.define("leaveRoom", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Requires detailed information on sad feedback.
|
||||
*/
|
||||
RequireFeedbackDetails: Action.define("requireFeedbackDetails", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Send feedback data.
|
||||
*/
|
||||
SendFeedback: Action.define("sendFeedback", {
|
||||
happy: Boolean,
|
||||
category: String,
|
||||
description: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Reacts on feedback submission error.
|
||||
*/
|
||||
SendFeedbackError: Action.define("sendFeedbackError", {
|
||||
error: Error
|
||||
})
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -107,8 +107,8 @@ loop.FeedbackAPIClient = (function($, _) {
|
|||
req.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
var message = "Error posting user feedback data";
|
||||
var httpError = jqXHR.status + " " + errorThrown;
|
||||
console.error(message, httpError, JSON.stringify(jqXHR.responseJSON));
|
||||
cb(new Error(message + ": " + httpError));
|
||||
cb(new Error(message + ": " + httpError + "; " +
|
||||
(jqXHR.responseJSON && jqXHR.responseJSON.detail || "")));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* 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:true */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.store = loop.store || {};
|
||||
|
||||
loop.store.FeedbackStore = (function() {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES = {
|
||||
// Initial state (mood selection)
|
||||
INIT: "feedback-init",
|
||||
// User detailed feedback form step
|
||||
DETAILS: "feedback-details",
|
||||
// Pending feedback data submission
|
||||
PENDING: "feedback-pending",
|
||||
// Feedback has been sent
|
||||
SENT: "feedback-sent",
|
||||
// There was an issue with the feedback API
|
||||
FAILED: "feedback-failed"
|
||||
};
|
||||
|
||||
/**
|
||||
* Feedback store.
|
||||
*
|
||||
* @param {loop.Dispatcher} dispatcher The dispatcher for dispatching actions
|
||||
* and registering to consume actions.
|
||||
* @param {Object} options Options object:
|
||||
* - {mozLoop} mozLoop The MozLoop API object.
|
||||
* - {feedbackClient} loop.FeedbackAPIClient The feedback API client.
|
||||
*/
|
||||
var FeedbackStore = loop.store.createStore({
|
||||
actions: [
|
||||
"requireFeedbackDetails",
|
||||
"sendFeedback",
|
||||
"sendFeedbackError"
|
||||
],
|
||||
|
||||
initialize: function(options) {
|
||||
if (!options.feedbackClient) {
|
||||
throw new Error("Missing option feedbackClient");
|
||||
}
|
||||
this._feedbackClient = options.feedbackClient;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns initial state data for this active room.
|
||||
*/
|
||||
getInitialStoreState: function() {
|
||||
return {feedbackState: FEEDBACK_STATES.INIT};
|
||||
},
|
||||
|
||||
/**
|
||||
* Requires user detailed feedback.
|
||||
*/
|
||||
requireFeedbackDetails: function() {
|
||||
this.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends feedback data to the feedback server.
|
||||
*
|
||||
* @param {sharedActions.SendFeedback} actionData The action data.
|
||||
*/
|
||||
sendFeedback: function(actionData) {
|
||||
delete actionData.name;
|
||||
this._feedbackClient.send(actionData, function(err) {
|
||||
if (err) {
|
||||
this.dispatchAction(new sharedActions.SendFeedbackError({
|
||||
error: err
|
||||
}));
|
||||
return;
|
||||
}
|
||||
this.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
|
||||
}.bind(this));
|
||||
|
||||
this.setStoreState({feedbackState: FEEDBACK_STATES.PENDING});
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies a store from any error encountered while sending feedback data.
|
||||
*
|
||||
* @param {sharedActions.SendFeedback} actionData The action data.
|
||||
*/
|
||||
sendFeedbackError: function(actionData) {
|
||||
this.setStoreState({
|
||||
feedbackState: FEEDBACK_STATES.FAILED,
|
||||
error: actionData.error
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return FeedbackStore;
|
||||
})();
|
|
@ -0,0 +1,326 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* jshint newcap:false */
|
||||
/* global loop:true, React */
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.FeedbackView = (function(l10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
|
||||
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
|
||||
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
|
||||
|
||||
/**
|
||||
* Feedback outer layout.
|
||||
*
|
||||
* Props:
|
||||
* -
|
||||
*/
|
||||
var FeedbackLayout = React.createClass({displayName: 'FeedbackLayout',
|
||||
propTypes: {
|
||||
children: React.PropTypes.component.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
reset: React.PropTypes.func // if not specified, no Back btn is shown
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var backButton = React.DOM.div(null);
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
React.DOM.button({className: "fx-embedded-btn-back", type: "button",
|
||||
onClick: this.props.reset},
|
||||
"« ", l10n.get("feedback_back_button")
|
||||
)
|
||||
);
|
||||
}
|
||||
return (
|
||||
React.DOM.div({className: "feedback"},
|
||||
backButton,
|
||||
React.DOM.h3(null, this.props.title),
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Detailed feedback form.
|
||||
*/
|
||||
var FeedbackForm = React.createClass({displayName: 'FeedbackForm',
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
pending: React.PropTypes.bool,
|
||||
reset: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {category: "", description: ""};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {pending: false};
|
||||
},
|
||||
|
||||
_getCategories: function() {
|
||||
return {
|
||||
audio_quality: l10n.get("feedback_category_audio_quality"),
|
||||
video_quality: l10n.get("feedback_category_video_quality"),
|
||||
disconnected : l10n.get("feedback_category_was_disconnected"),
|
||||
confusing: l10n.get("feedback_category_confusing"),
|
||||
other: l10n.get("feedback_category_other")
|
||||
};
|
||||
},
|
||||
|
||||
_getCategoryFields: function() {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
React.DOM.label({key: key, className: "feedback-category-label"},
|
||||
React.DOM.input({type: "radio", ref: "category", name: "category",
|
||||
className: "feedback-category-radio",
|
||||
value: category,
|
||||
onChange: this.handleCategoryChange,
|
||||
checked: this.state.category === category}),
|
||||
categories[category]
|
||||
)
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the form is ready for submission:
|
||||
*
|
||||
* - no feedback submission should be pending.
|
||||
* - a category (reason) must be chosen;
|
||||
* - if the "other" category is chosen, a custom description must have been
|
||||
* entered by the end user;
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isFormReady: function() {
|
||||
if (this.props.pending || !this.state.category) {
|
||||
return false;
|
||||
}
|
||||
if (this.state.category === "other" && !this.state.description) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
handleCategoryChange: function(event) {
|
||||
var category = event.target.value;
|
||||
this.setState({
|
||||
category: category,
|
||||
description: category == "other" ? "" : this._getCategories()[category]
|
||||
});
|
||||
if (category == "other") {
|
||||
this.refs.description.getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
handleDescriptionFieldChange: function(event) {
|
||||
this.setState({description: event.target.value});
|
||||
},
|
||||
|
||||
handleDescriptionFieldFocus: function(event) {
|
||||
this.setState({category: "other", description: ""});
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX this feels ugly, we really want a feedbackActions object here.
|
||||
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
|
||||
happy: false,
|
||||
category: this.state.category,
|
||||
description: this.state.description
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var descriptionDisplayValue = this.state.category === "other" ?
|
||||
this.state.description : "";
|
||||
return (
|
||||
FeedbackLayout({title: l10n.get("feedback_what_makes_you_sad"),
|
||||
reset: this.props.reset},
|
||||
React.DOM.form({onSubmit: this.handleFormSubmit},
|
||||
this._getCategoryFields(),
|
||||
React.DOM.p(null,
|
||||
React.DOM.input({type: "text", ref: "description", name: "description",
|
||||
className: "feedback-description",
|
||||
onChange: this.handleDescriptionFieldChange,
|
||||
onFocus: this.handleDescriptionFieldFocus,
|
||||
value: descriptionDisplayValue,
|
||||
placeholder:
|
||||
l10n.get("feedback_custom_category_text_placeholder")})
|
||||
),
|
||||
React.DOM.button({type: "submit", className: "btn btn-success",
|
||||
disabled: !this._isFormReady()},
|
||||
l10n.get("feedback_submit_button")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback received view.
|
||||
*
|
||||
* Props:
|
||||
* - {Function} onAfterFeedbackReceived Function to execute after the
|
||||
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
|
||||
*/
|
||||
var FeedbackReceived = React.createClass({displayName: 'FeedbackReceived',
|
||||
propTypes: {
|
||||
onAfterFeedbackReceived: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._timer = setInterval(function() {
|
||||
this.setState({countdown: this.state.countdown - 1});
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.countdown < 1) {
|
||||
clearInterval(this._timer);
|
||||
if (this.props.onAfterFeedbackReceived) {
|
||||
this.props.onAfterFeedbackReceived();
|
||||
}
|
||||
}
|
||||
return (
|
||||
FeedbackLayout({title: l10n.get("feedback_thank_you_heading")},
|
||||
React.DOM.p({className: "info thank-you"},
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({displayName: 'FeedbackView',
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
onAfterFeedbackReceived: React.PropTypes.func,
|
||||
// Used by the UI showcase.
|
||||
feedbackState: React.PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.feedbackStore.getStoreState();
|
||||
return _.extend({}, storeState, {
|
||||
feedbackState: this.props.feedbackState || storeState.feedbackState
|
||||
});
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.feedbackStore, "change", this._onStoreStateChanged);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.feedbackStore);
|
||||
},
|
||||
|
||||
_onStoreStateChanged: function() {
|
||||
this.setState(this.props.feedbackStore.getStoreState());
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setState(this.props.feedbackStore.getInitialStoreState());
|
||||
},
|
||||
|
||||
handleHappyClick: function() {
|
||||
// XXX: If the user is happy, we directly send this information to the
|
||||
// feedback API; this is a behavior we might want to revisit later.
|
||||
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
|
||||
happy: true,
|
||||
category: "",
|
||||
description: ""
|
||||
}));
|
||||
},
|
||||
|
||||
handleSadClick: function() {
|
||||
this.props.feedbackStore.dispatchAction(
|
||||
new sharedActions.RequireFeedbackDetails());
|
||||
},
|
||||
|
||||
_onFeedbackSent: function(err) {
|
||||
if (err) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Unable to send user feedback", err);
|
||||
}
|
||||
this.setState({pending: false, step: "finished"});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch(this.state.feedbackState) {
|
||||
default:
|
||||
case FEEDBACK_STATES.INIT: {
|
||||
return (
|
||||
FeedbackLayout({title:
|
||||
l10n.get("feedback_call_experience_heading2")},
|
||||
React.DOM.div({className: "faces"},
|
||||
React.DOM.button({className: "face face-happy",
|
||||
onClick: this.handleHappyClick}),
|
||||
React.DOM.button({className: "face face-sad",
|
||||
onClick: this.handleSadClick})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
case FEEDBACK_STATES.DETAILS: {
|
||||
return (
|
||||
FeedbackForm({
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
reset: this.reset,
|
||||
pending: this.state.feedbackState === FEEDBACK_STATES.PENDING})
|
||||
);
|
||||
}
|
||||
case FEEDBACK_STATES.PENDING:
|
||||
case FEEDBACK_STATES.SENT:
|
||||
case FEEDBACK_STATES.FAILED: {
|
||||
if (this.state.error) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Error encountered while submitting feedback",
|
||||
this.state.error);
|
||||
}
|
||||
return (
|
||||
FeedbackReceived({
|
||||
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return FeedbackView;
|
||||
})(navigator.mozL10n || document.mozL10n);
|
|
@ -0,0 +1,326 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* jshint newcap:false */
|
||||
/* global loop:true, React */
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.FeedbackView = (function(l10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
|
||||
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
|
||||
var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
|
||||
|
||||
/**
|
||||
* Feedback outer layout.
|
||||
*
|
||||
* Props:
|
||||
* -
|
||||
*/
|
||||
var FeedbackLayout = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.component.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
reset: React.PropTypes.func // if not specified, no Back btn is shown
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var backButton = <div />;
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
<button className="fx-embedded-btn-back" type="button"
|
||||
onClick={this.props.reset}>
|
||||
« {l10n.get("feedback_back_button")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="feedback">
|
||||
{backButton}
|
||||
<h3>{this.props.title}</h3>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Detailed feedback form.
|
||||
*/
|
||||
var FeedbackForm = React.createClass({
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
pending: React.PropTypes.bool,
|
||||
reset: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {category: "", description: ""};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {pending: false};
|
||||
},
|
||||
|
||||
_getCategories: function() {
|
||||
return {
|
||||
audio_quality: l10n.get("feedback_category_audio_quality"),
|
||||
video_quality: l10n.get("feedback_category_video_quality"),
|
||||
disconnected : l10n.get("feedback_category_was_disconnected"),
|
||||
confusing: l10n.get("feedback_category_confusing"),
|
||||
other: l10n.get("feedback_category_other")
|
||||
};
|
||||
},
|
||||
|
||||
_getCategoryFields: function() {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
<label key={key} className="feedback-category-label">
|
||||
<input type="radio" ref="category" name="category"
|
||||
className="feedback-category-radio"
|
||||
value={category}
|
||||
onChange={this.handleCategoryChange}
|
||||
checked={this.state.category === category} />
|
||||
{categories[category]}
|
||||
</label>
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the form is ready for submission:
|
||||
*
|
||||
* - no feedback submission should be pending.
|
||||
* - a category (reason) must be chosen;
|
||||
* - if the "other" category is chosen, a custom description must have been
|
||||
* entered by the end user;
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isFormReady: function() {
|
||||
if (this.props.pending || !this.state.category) {
|
||||
return false;
|
||||
}
|
||||
if (this.state.category === "other" && !this.state.description) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
handleCategoryChange: function(event) {
|
||||
var category = event.target.value;
|
||||
this.setState({
|
||||
category: category,
|
||||
description: category == "other" ? "" : this._getCategories()[category]
|
||||
});
|
||||
if (category == "other") {
|
||||
this.refs.description.getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
handleDescriptionFieldChange: function(event) {
|
||||
this.setState({description: event.target.value});
|
||||
},
|
||||
|
||||
handleDescriptionFieldFocus: function(event) {
|
||||
this.setState({category: "other", description: ""});
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX this feels ugly, we really want a feedbackActions object here.
|
||||
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
|
||||
happy: false,
|
||||
category: this.state.category,
|
||||
description: this.state.description
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var descriptionDisplayValue = this.state.category === "other" ?
|
||||
this.state.description : "";
|
||||
return (
|
||||
<FeedbackLayout title={l10n.get("feedback_what_makes_you_sad")}
|
||||
reset={this.props.reset}>
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
{this._getCategoryFields()}
|
||||
<p>
|
||||
<input type="text" ref="description" name="description"
|
||||
className="feedback-description"
|
||||
onChange={this.handleDescriptionFieldChange}
|
||||
onFocus={this.handleDescriptionFieldFocus}
|
||||
value={descriptionDisplayValue}
|
||||
placeholder={
|
||||
l10n.get("feedback_custom_category_text_placeholder")} />
|
||||
</p>
|
||||
<button type="submit" className="btn btn-success"
|
||||
disabled={!this._isFormReady()}>
|
||||
{l10n.get("feedback_submit_button")}
|
||||
</button>
|
||||
</form>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback received view.
|
||||
*
|
||||
* Props:
|
||||
* - {Function} onAfterFeedbackReceived Function to execute after the
|
||||
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
|
||||
*/
|
||||
var FeedbackReceived = React.createClass({
|
||||
propTypes: {
|
||||
onAfterFeedbackReceived: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._timer = setInterval(function() {
|
||||
this.setState({countdown: this.state.countdown - 1});
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.countdown < 1) {
|
||||
clearInterval(this._timer);
|
||||
if (this.props.onAfterFeedbackReceived) {
|
||||
this.props.onAfterFeedbackReceived();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
|
||||
<p className="info thank-you">{
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
})}</p>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
onAfterFeedbackReceived: React.PropTypes.func,
|
||||
// Used by the UI showcase.
|
||||
feedbackState: React.PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.feedbackStore.getStoreState();
|
||||
return _.extend({}, storeState, {
|
||||
feedbackState: this.props.feedbackState || storeState.feedbackState
|
||||
});
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.feedbackStore, "change", this._onStoreStateChanged);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.feedbackStore);
|
||||
},
|
||||
|
||||
_onStoreStateChanged: function() {
|
||||
this.setState(this.props.feedbackStore.getStoreState());
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setState(this.props.feedbackStore.getInitialStoreState());
|
||||
},
|
||||
|
||||
handleHappyClick: function() {
|
||||
// XXX: If the user is happy, we directly send this information to the
|
||||
// feedback API; this is a behavior we might want to revisit later.
|
||||
this.props.feedbackStore.dispatchAction(new sharedActions.SendFeedback({
|
||||
happy: true,
|
||||
category: "",
|
||||
description: ""
|
||||
}));
|
||||
},
|
||||
|
||||
handleSadClick: function() {
|
||||
this.props.feedbackStore.dispatchAction(
|
||||
new sharedActions.RequireFeedbackDetails());
|
||||
},
|
||||
|
||||
_onFeedbackSent: function(err) {
|
||||
if (err) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Unable to send user feedback", err);
|
||||
}
|
||||
this.setState({pending: false, step: "finished"});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch(this.state.feedbackState) {
|
||||
default:
|
||||
case FEEDBACK_STATES.INIT: {
|
||||
return (
|
||||
<FeedbackLayout title={
|
||||
l10n.get("feedback_call_experience_heading2")}>
|
||||
<div className="faces">
|
||||
<button className="face face-happy"
|
||||
onClick={this.handleHappyClick}></button>
|
||||
<button className="face face-sad"
|
||||
onClick={this.handleSadClick}></button>
|
||||
</div>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
case FEEDBACK_STATES.DETAILS: {
|
||||
return (
|
||||
<FeedbackForm
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
reset={this.reset}
|
||||
pending={this.state.feedbackState === FEEDBACK_STATES.PENDING} />
|
||||
);
|
||||
}
|
||||
case FEEDBACK_STATES.PENDING:
|
||||
case FEEDBACK_STATES.SENT:
|
||||
case FEEDBACK_STATES.FAILED: {
|
||||
if (this.state.error) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Error encountered while submitting feedback",
|
||||
this.state.error);
|
||||
}
|
||||
return (
|
||||
<FeedbackReceived
|
||||
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return FeedbackView;
|
||||
})(navigator.mozL10n || document.mozL10n);
|
|
@ -14,8 +14,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
var sharedModels = loop.shared.models;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
|
||||
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
|
||||
|
||||
/**
|
||||
* Media control button.
|
||||
*
|
||||
|
@ -345,287 +343,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback outer layout.
|
||||
*
|
||||
* Props:
|
||||
* -
|
||||
*/
|
||||
var FeedbackLayout = React.createClass({displayName: 'FeedbackLayout',
|
||||
propTypes: {
|
||||
children: React.PropTypes.component.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
reset: React.PropTypes.func // if not specified, no Back btn is shown
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var backButton = React.DOM.div(null);
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
React.DOM.button({className: "fx-embedded-btn-back", type: "button",
|
||||
onClick: this.props.reset},
|
||||
"« ", l10n.get("feedback_back_button")
|
||||
)
|
||||
);
|
||||
}
|
||||
return (
|
||||
React.DOM.div({className: "feedback"},
|
||||
backButton,
|
||||
React.DOM.h3(null, this.props.title),
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Detailed feedback form.
|
||||
*/
|
||||
var FeedbackForm = React.createClass({displayName: 'FeedbackForm',
|
||||
propTypes: {
|
||||
pending: React.PropTypes.bool,
|
||||
sendFeedback: React.PropTypes.func,
|
||||
reset: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {category: "", description: ""};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {pending: false};
|
||||
},
|
||||
|
||||
_getCategories: function() {
|
||||
return {
|
||||
audio_quality: l10n.get("feedback_category_audio_quality"),
|
||||
video_quality: l10n.get("feedback_category_video_quality"),
|
||||
disconnected : l10n.get("feedback_category_was_disconnected"),
|
||||
confusing: l10n.get("feedback_category_confusing"),
|
||||
other: l10n.get("feedback_category_other")
|
||||
};
|
||||
},
|
||||
|
||||
_getCategoryFields: function() {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
React.DOM.label({key: key, className: "feedback-category-label"},
|
||||
React.DOM.input({type: "radio", ref: "category", name: "category",
|
||||
className: "feedback-category-radio",
|
||||
value: category,
|
||||
onChange: this.handleCategoryChange,
|
||||
checked: this.state.category === category}),
|
||||
categories[category]
|
||||
)
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the form is ready for submission:
|
||||
*
|
||||
* - no feedback submission should be pending.
|
||||
* - a category (reason) must be chosen;
|
||||
* - if the "other" category is chosen, a custom description must have been
|
||||
* entered by the end user;
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isFormReady: function() {
|
||||
if (this.props.pending || !this.state.category) {
|
||||
return false;
|
||||
}
|
||||
if (this.state.category === "other" && !this.state.description) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
handleCategoryChange: function(event) {
|
||||
var category = event.target.value;
|
||||
this.setState({
|
||||
category: category,
|
||||
description: category == "other" ? "" : this._getCategories()[category]
|
||||
});
|
||||
if (category == "other") {
|
||||
this.refs.description.getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
handleDescriptionFieldChange: function(event) {
|
||||
this.setState({description: event.target.value});
|
||||
},
|
||||
|
||||
handleDescriptionFieldFocus: function(event) {
|
||||
this.setState({category: "other", description: ""});
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.sendFeedback({
|
||||
happy: false,
|
||||
category: this.state.category,
|
||||
description: this.state.description
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var descriptionDisplayValue = this.state.category === "other" ?
|
||||
this.state.description : "";
|
||||
return (
|
||||
FeedbackLayout({title: l10n.get("feedback_what_makes_you_sad"),
|
||||
reset: this.props.reset},
|
||||
React.DOM.form({onSubmit: this.handleFormSubmit},
|
||||
this._getCategoryFields(),
|
||||
React.DOM.p(null,
|
||||
React.DOM.input({type: "text", ref: "description", name: "description",
|
||||
className: "feedback-description",
|
||||
onChange: this.handleDescriptionFieldChange,
|
||||
onFocus: this.handleDescriptionFieldFocus,
|
||||
value: descriptionDisplayValue,
|
||||
placeholder:
|
||||
l10n.get("feedback_custom_category_text_placeholder")})
|
||||
),
|
||||
React.DOM.button({type: "submit", className: "btn btn-success",
|
||||
disabled: !this._isFormReady()},
|
||||
l10n.get("feedback_submit_button")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback received view.
|
||||
*
|
||||
* Props:
|
||||
* - {Function} onAfterFeedbackReceived Function to execute after the
|
||||
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
|
||||
*/
|
||||
var FeedbackReceived = React.createClass({displayName: 'FeedbackReceived',
|
||||
propTypes: {
|
||||
onAfterFeedbackReceived: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._timer = setInterval(function() {
|
||||
this.setState({countdown: this.state.countdown - 1});
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.countdown < 1) {
|
||||
clearInterval(this._timer);
|
||||
if (this.props.onAfterFeedbackReceived) {
|
||||
this.props.onAfterFeedbackReceived();
|
||||
}
|
||||
}
|
||||
return (
|
||||
FeedbackLayout({title: l10n.get("feedback_thank_you_heading")},
|
||||
React.DOM.p({className: "info thank-you"},
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({displayName: 'FeedbackView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
// A loop.FeedbackAPIClient instance
|
||||
feedbackApiClient: React.PropTypes.object.isRequired,
|
||||
onAfterFeedbackReceived: React.PropTypes.func,
|
||||
// The current feedback submission flow step name
|
||||
step: React.PropTypes.oneOf(["start", "form", "finished"])
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {pending: false, step: this.props.step || "start"};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {step: "start"};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
|
||||
handleHappyClick: function() {
|
||||
this.sendFeedback({happy: true}, this._onFeedbackSent);
|
||||
},
|
||||
|
||||
handleSadClick: function() {
|
||||
this.setState({step: "form"});
|
||||
},
|
||||
|
||||
sendFeedback: function(fields) {
|
||||
// Setting state.pending to true will disable the submit button to avoid
|
||||
// multiple submissions
|
||||
this.setState({pending: true});
|
||||
// Sends feedback data
|
||||
this.props.feedbackApiClient.send(fields, this._onFeedbackSent);
|
||||
},
|
||||
|
||||
_onFeedbackSent: function(err) {
|
||||
if (err) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Unable to send user feedback", err);
|
||||
}
|
||||
this.setState({pending: false, step: "finished"});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch(this.state.step) {
|
||||
case "finished":
|
||||
return (
|
||||
FeedbackReceived({
|
||||
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived})
|
||||
);
|
||||
case "form":
|
||||
return FeedbackForm({feedbackApiClient: this.props.feedbackApiClient,
|
||||
sendFeedback: this.sendFeedback,
|
||||
reset: this.reset,
|
||||
pending: this.state.pending});
|
||||
default:
|
||||
return (
|
||||
FeedbackLayout({title:
|
||||
l10n.get("feedback_call_experience_heading2")},
|
||||
React.DOM.div({className: "faces"},
|
||||
React.DOM.button({className: "face face-happy",
|
||||
onClick: this.handleHappyClick}),
|
||||
React.DOM.button({className: "face face-sad",
|
||||
onClick: this.handleSadClick})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Notification view.
|
||||
*/
|
||||
|
@ -743,7 +460,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
React.DOM.span({className: "button-caption"}, this.props.caption),
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -768,7 +485,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
React.DOM.div({className: cx(classObject)},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -777,7 +494,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
ButtonGroup: ButtonGroup,
|
||||
ConversationView: ConversationView,
|
||||
ConversationToolbar: ConversationToolbar,
|
||||
FeedbackView: FeedbackView,
|
||||
MediaControlButton: MediaControlButton,
|
||||
NotificationListView: NotificationListView
|
||||
};
|
||||
|
|
|
@ -14,8 +14,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
var sharedModels = loop.shared.models;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
|
||||
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
|
||||
|
||||
/**
|
||||
* Media control button.
|
||||
*
|
||||
|
@ -345,287 +343,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback outer layout.
|
||||
*
|
||||
* Props:
|
||||
* -
|
||||
*/
|
||||
var FeedbackLayout = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.component.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
reset: React.PropTypes.func // if not specified, no Back btn is shown
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var backButton = <div />;
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
<button className="fx-embedded-btn-back" type="button"
|
||||
onClick={this.props.reset}>
|
||||
« {l10n.get("feedback_back_button")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="feedback">
|
||||
{backButton}
|
||||
<h3>{this.props.title}</h3>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Detailed feedback form.
|
||||
*/
|
||||
var FeedbackForm = React.createClass({
|
||||
propTypes: {
|
||||
pending: React.PropTypes.bool,
|
||||
sendFeedback: React.PropTypes.func,
|
||||
reset: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {category: "", description: ""};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {pending: false};
|
||||
},
|
||||
|
||||
_getCategories: function() {
|
||||
return {
|
||||
audio_quality: l10n.get("feedback_category_audio_quality"),
|
||||
video_quality: l10n.get("feedback_category_video_quality"),
|
||||
disconnected : l10n.get("feedback_category_was_disconnected"),
|
||||
confusing: l10n.get("feedback_category_confusing"),
|
||||
other: l10n.get("feedback_category_other")
|
||||
};
|
||||
},
|
||||
|
||||
_getCategoryFields: function() {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
<label key={key} className="feedback-category-label">
|
||||
<input type="radio" ref="category" name="category"
|
||||
className="feedback-category-radio"
|
||||
value={category}
|
||||
onChange={this.handleCategoryChange}
|
||||
checked={this.state.category === category} />
|
||||
{categories[category]}
|
||||
</label>
|
||||
);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the form is ready for submission:
|
||||
*
|
||||
* - no feedback submission should be pending.
|
||||
* - a category (reason) must be chosen;
|
||||
* - if the "other" category is chosen, a custom description must have been
|
||||
* entered by the end user;
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isFormReady: function() {
|
||||
if (this.props.pending || !this.state.category) {
|
||||
return false;
|
||||
}
|
||||
if (this.state.category === "other" && !this.state.description) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
handleCategoryChange: function(event) {
|
||||
var category = event.target.value;
|
||||
this.setState({
|
||||
category: category,
|
||||
description: category == "other" ? "" : this._getCategories()[category]
|
||||
});
|
||||
if (category == "other") {
|
||||
this.refs.description.getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
handleDescriptionFieldChange: function(event) {
|
||||
this.setState({description: event.target.value});
|
||||
},
|
||||
|
||||
handleDescriptionFieldFocus: function(event) {
|
||||
this.setState({category: "other", description: ""});
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.sendFeedback({
|
||||
happy: false,
|
||||
category: this.state.category,
|
||||
description: this.state.description
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var descriptionDisplayValue = this.state.category === "other" ?
|
||||
this.state.description : "";
|
||||
return (
|
||||
<FeedbackLayout title={l10n.get("feedback_what_makes_you_sad")}
|
||||
reset={this.props.reset}>
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
{this._getCategoryFields()}
|
||||
<p>
|
||||
<input type="text" ref="description" name="description"
|
||||
className="feedback-description"
|
||||
onChange={this.handleDescriptionFieldChange}
|
||||
onFocus={this.handleDescriptionFieldFocus}
|
||||
value={descriptionDisplayValue}
|
||||
placeholder={
|
||||
l10n.get("feedback_custom_category_text_placeholder")} />
|
||||
</p>
|
||||
<button type="submit" className="btn btn-success"
|
||||
disabled={!this._isFormReady()}>
|
||||
{l10n.get("feedback_submit_button")}
|
||||
</button>
|
||||
</form>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback received view.
|
||||
*
|
||||
* Props:
|
||||
* - {Function} onAfterFeedbackReceived Function to execute after the
|
||||
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
|
||||
*/
|
||||
var FeedbackReceived = React.createClass({
|
||||
propTypes: {
|
||||
onAfterFeedbackReceived: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._timer = setInterval(function() {
|
||||
this.setState({countdown: this.state.countdown - 1});
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.countdown < 1) {
|
||||
clearInterval(this._timer);
|
||||
if (this.props.onAfterFeedbackReceived) {
|
||||
this.props.onAfterFeedbackReceived();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
|
||||
<p className="info thank-you">{
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
})}</p>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Feedback view.
|
||||
*/
|
||||
var FeedbackView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
// A loop.FeedbackAPIClient instance
|
||||
feedbackApiClient: React.PropTypes.object.isRequired,
|
||||
onAfterFeedbackReceived: React.PropTypes.func,
|
||||
// The current feedback submission flow step name
|
||||
step: React.PropTypes.oneOf(["start", "form", "finished"])
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {pending: false, step: this.props.step || "start"};
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {step: "start"};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
|
||||
handleHappyClick: function() {
|
||||
this.sendFeedback({happy: true}, this._onFeedbackSent);
|
||||
},
|
||||
|
||||
handleSadClick: function() {
|
||||
this.setState({step: "form"});
|
||||
},
|
||||
|
||||
sendFeedback: function(fields) {
|
||||
// Setting state.pending to true will disable the submit button to avoid
|
||||
// multiple submissions
|
||||
this.setState({pending: true});
|
||||
// Sends feedback data
|
||||
this.props.feedbackApiClient.send(fields, this._onFeedbackSent);
|
||||
},
|
||||
|
||||
_onFeedbackSent: function(err) {
|
||||
if (err) {
|
||||
// XXX better end user error reporting, see bug 1046738
|
||||
console.error("Unable to send user feedback", err);
|
||||
}
|
||||
this.setState({pending: false, step: "finished"});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch(this.state.step) {
|
||||
case "finished":
|
||||
return (
|
||||
<FeedbackReceived
|
||||
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived} />
|
||||
);
|
||||
case "form":
|
||||
return <FeedbackForm feedbackApiClient={this.props.feedbackApiClient}
|
||||
sendFeedback={this.sendFeedback}
|
||||
reset={this.reset}
|
||||
pending={this.state.pending} />;
|
||||
default:
|
||||
return (
|
||||
<FeedbackLayout title={
|
||||
l10n.get("feedback_call_experience_heading2")}>
|
||||
<div className="faces">
|
||||
<button className="face face-happy"
|
||||
onClick={this.handleHappyClick}></button>
|
||||
<button className="face face-sad"
|
||||
onClick={this.handleSadClick}></button>
|
||||
</div>
|
||||
</FeedbackLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Notification view.
|
||||
*/
|
||||
|
@ -743,7 +460,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
<span className="button-caption">{this.props.caption}</span>
|
||||
{this.props.children}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -768,7 +485,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
<div className={cx(classObject)}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -777,7 +494,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
ButtonGroup: ButtonGroup,
|
||||
ConversationView: ConversationView,
|
||||
ConversationToolbar: ConversationToolbar,
|
||||
FeedbackView: FeedbackView,
|
||||
MediaControlButton: MediaControlButton,
|
||||
NotificationListView: NotificationListView
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ browser.jar:
|
|||
content/browser/loop/shared/img/svg/glyph-account-16x16.svg (content/shared/img/svg/glyph-account-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-help-16x16.svg (content/shared/img/svg/glyph-help-16x16.svg)
|
||||
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
||||
content/browser/loop/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
||||
content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
||||
|
@ -70,12 +71,14 @@ browser.jar:
|
|||
content/browser/loop/shared/js/store.js (content/shared/js/store.js)
|
||||
content/browser/loop/shared/js/roomStore.js (content/shared/js/roomStore.js)
|
||||
content/browser/loop/shared/js/activeRoomStore.js (content/shared/js/activeRoomStore.js)
|
||||
content/browser/loop/shared/js/feedbackStore.js (content/shared/js/feedbackStore.js)
|
||||
content/browser/loop/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
||||
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||
content/browser/loop/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
||||
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
||||
content/browser/loop/shared/js/feedbackViews.js (content/shared/js/feedbackViews.js)
|
||||
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
||||
content/browser/loop/shared/js/validate.js (content/shared/js/validate.js)
|
||||
content/browser/loop/shared/js/websocket.js (content/shared/js/websocket.js)
|
||||
|
|
|
@ -84,3 +84,5 @@ config:
|
|||
@echo "loop.config.fxosApp = loop.config.fxosApp || {};" >> content/config.js
|
||||
@echo "loop.config.fxosApp.name = 'Loop';" >> content/config.js
|
||||
@echo "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" >> content/config.js
|
||||
@echo "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';" >> content/config.js
|
||||
@echo "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" >> content/config.js
|
||||
|
|
|
@ -87,15 +87,19 @@ body,
|
|||
color: #777;
|
||||
}
|
||||
|
||||
.footer-external-links a {
|
||||
.footer-external-links {
|
||||
padding: .2rem .7rem;
|
||||
margin: 0 .5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-external-links a:hover {
|
||||
color: #111;
|
||||
}
|
||||
.footer-external-links a {
|
||||
margin: 0 .5rem;
|
||||
text-decoration: none;
|
||||
color: #adadad;
|
||||
}
|
||||
|
||||
.footer-external-links a:hover {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
width: 100px;
|
||||
|
|
|
@ -99,6 +99,8 @@
|
|||
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
|
||||
<script type="text/javascript" src="shared/js/store.js"></script>
|
||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="shared/js/feedbackStore.js"></script>
|
||||
<script type="text/javascript" src="shared/js/feedbackViews.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneAppStore.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneClient.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneMozLoop.js"></script>
|
||||
|
|
|
@ -107,7 +107,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
render: function() {
|
||||
return (
|
||||
React.DOM.header(null,
|
||||
React.DOM.h1(null, mozL10n.get("clientShortname2"))
|
||||
React.DOM.h1(null, mozL10n.get("clientShortname2")),
|
||||
React.DOM.a({target: "_blank", href: loop.config.roomsSupportUrl},
|
||||
React.DOM.i({className: "icon icon-help"})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -108,6 +108,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
return (
|
||||
<header>
|
||||
<h1>{mozL10n.get("clientShortname2")}</h1>
|
||||
<a target="_blank" href={loop.config.roomsSupportUrl}>
|
||||
<i className="icon icon-help"></i>
|
||||
</a>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -259,7 +259,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
React.DOM.div({className: "standalone-footer container-box"},
|
||||
React.DOM.div({title: mozL10n.get("vendor_alttext",
|
||||
{vendorShortname: mozL10n.get("vendorShortname")}),
|
||||
className: "footer-logo"})
|
||||
className: "footer-logo"}),
|
||||
React.DOM.div({className: "footer-external-links"},
|
||||
React.DOM.a({target: "_blank", href: loop.config.guestSupportUrl},
|
||||
mozL10n.get("support_link")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -538,7 +543,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
feedbackApiClient: React.PropTypes.object.isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
|
@ -549,7 +554,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
return (
|
||||
React.DOM.div({className: "ended-conversation"},
|
||||
sharedViews.FeedbackView({
|
||||
feedbackApiClient: this.props.feedbackApiClient,
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived}
|
||||
),
|
||||
sharedViews.ConversationView({
|
||||
|
@ -611,7 +616,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
feedbackApiClient: React.PropTypes.object.isRequired
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -690,7 +695,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
EndedConversationView({
|
||||
sdk: this.props.sdk,
|
||||
conversation: this.props.conversation,
|
||||
feedbackApiClient: this.props.feedbackApiClient,
|
||||
feedbackStore: this.props.feedbackStore,
|
||||
onAfterFeedbackReceived: this.callStatusSwitcher("start")}
|
||||
)
|
||||
);
|
||||
|
@ -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}
|
||||
|
|
|
@ -260,6 +260,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
<div title={mozL10n.get("vendor_alttext",
|
||||
{vendorShortname: mozL10n.get("vendorShortname")})}
|
||||
className="footer-logo"></div>
|
||||
<div className="footer-external-links">
|
||||
<a target="_blank" href={loop.config.guestSupportUrl}>
|
||||
{mozL10n.get("support_link")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -538,7 +543,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
feedbackApiClient: React.PropTypes.object.isRequired,
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore),
|
||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
|
@ -549,7 +554,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
return (
|
||||
<div className="ended-conversation">
|
||||
<sharedViews.FeedbackView
|
||||
feedbackApiClient={this.props.feedbackApiClient}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived}
|
||||
/>
|
||||
<sharedViews.ConversationView
|
||||
|
@ -611,7 +616,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
feedbackApiClient: React.PropTypes.object.isRequired
|
||||
feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore)
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -690,7 +695,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
<EndedConversationView
|
||||
sdk={this.props.sdk}
|
||||
conversation={this.props.conversation}
|
||||
feedbackApiClient={this.props.feedbackApiClient}
|
||||
feedbackStore={this.props.feedbackStore}
|
||||
onAfterFeedbackReceived={this.callStatusSwitcher("start")}
|
||||
/>
|
||||
);
|
||||
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -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'",
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
<script src="../../content/shared/js/store.js"></script>
|
||||
<script src="../../content/shared/js/roomStore.js"></script>
|
||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackViews.js"></script>
|
||||
<script src="../../content/js/client.js"></script>
|
||||
<script src="../../content/js/conversationAppStore.js"></script>
|
||||
<script src="../../content/js/roomViews.js"></script>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -47,6 +47,8 @@
|
|||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/roomStore.js"></script>
|
||||
<script src="../../content/shared/js/conversationStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackViews.js"></script>
|
||||
|
||||
<!-- Test scripts -->
|
||||
<script src="models_test.js"></script>
|
||||
|
@ -55,10 +57,12 @@
|
|||
<script src="views_test.js"></script>
|
||||
<script src="websocket_test.js"></script>
|
||||
<script src="feedbackApiClient_test.js"></script>
|
||||
<script src="feedbackViews_test.js"></script>
|
||||
<script src="validate_test.js"></script>
|
||||
<script src="dispatcher_test.js"></script>
|
||||
<script src="activeRoomStore_test.js"></script>
|
||||
<script src="conversationStore_test.js"></script>
|
||||
<script src="feedbackStore_test.js"></script>
|
||||
<script src="otSdkDriver_test.js"></script>
|
||||
<script src="store_test.js"></script>
|
||||
<script src="roomStore_test.js"></script>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/store.js"></script>
|
||||
<script src="../../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackStore.js"></script>
|
||||
<script src="../../content/shared/js/feedbackViews.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../standalone/content/js/multiplexGum.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneAppStore.js"></script>
|
||||
|
|
|
@ -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(){}
|
||||
})
|
||||
);
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
<script src="../content/shared/js/roomStore.js"></script>
|
||||
<script src="../content/shared/js/conversationStore.js"></script>
|
||||
<script src="../content/shared/js/activeRoomStore.js"></script>
|
||||
<script src="../content/shared/js/feedbackStore.js"></script>
|
||||
<script src="../content/shared/js/feedbackViews.js"></script>
|
||||
<script src="../content/js/roomViews.js"></script>
|
||||
<script src="../content/js/conversationViews.js"></script>
|
||||
<script src="../content/js/client.js"></script>
|
||||
|
|
|
@ -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})
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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 @@
|
|||
<a href="https://input.allizom.org/">input.allizom.org</a>.
|
||||
</p>
|
||||
<Example summary="Default (useable demo)" dashed="true" style={{width: "260px"}}>
|
||||
<FeedbackView feedbackApiClient={stageFeedbackApiClient} />
|
||||
<FeedbackView feedbackStore={feedbackStore} />
|
||||
</Example>
|
||||
<Example summary="Detailed form" dashed="true" style={{width: "260px"}}>
|
||||
<FeedbackView feedbackApiClient={stageFeedbackApiClient} step="form" />
|
||||
<FeedbackView feedbackStore={feedbackStore} feedbackState={FEEDBACK_STATES.DETAILS} />
|
||||
</Example>
|
||||
<Example summary="Thank you!" dashed="true" style={{width: "260px"}}>
|
||||
<FeedbackView feedbackApiClient={stageFeedbackApiClient} step="finished" />
|
||||
<FeedbackView feedbackStore={feedbackStore} feedbackState={FEEDBACK_STATES.SENT} />
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
|
@ -486,7 +490,7 @@
|
|||
video={{enabled: true}}
|
||||
audio={{enabled: true}}
|
||||
conversation={mockConversationModel}
|
||||
feedbackApiClient={stageFeedbackApiClient}
|
||||
feedbackStore={feedbackStore}
|
||||
onAfterFeedbackReceived={noop} />
|
||||
</div>
|
||||
</Example>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
76
configure.in
76
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
|
||||
|
||||
######################################
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<q>
|
||||
<form>
|
||||
<fieldset>
|
||||
<input>
|
||||
<input>
|
||||
</fieldset>
|
||||
</form>
|
||||
<p>
|
||||
<div><span>
|
||||
<img>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><map><map><map><map><map><map><map><map><map>
|
||||
<map><map><map><area><area><area><area><area><area></map>
|
||||
<hgroup>
|
||||
<object>
|
||||
<form dir="auto">
|
||||
<keygen dir="auto">
|
||||
</keygen>
|
||||
</form>
|
||||
<input>
|
||||
<input>
|
||||
</form>
|
||||
<script>
|
||||
function boom(){
|
||||
location.reload()
|
||||
}
|
||||
setInterval('boom()', 2000)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -9593,7 +9593,22 @@ nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr,
|
|||
// the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
|
||||
// unlink
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mPreloadingImages.AppendObject(request);
|
||||
mPreloadingImages.Put(uri, request.forget());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::ForgetImagePreload(nsIURI* aURI)
|
||||
{
|
||||
// Checking count is faster than hashing the URI in the common
|
||||
// case of empty table.
|
||||
if (mPreloadingImages.Count() != 0) {
|
||||
nsCOMPtr<imgIRequest> req;
|
||||
mPreloadingImages.Remove(aURI, getter_AddRefs(req));
|
||||
if (req) {
|
||||
// Make sure to cancel the request so imagelib knows it's gone.
|
||||
req->CancelAndForgetObserver(NS_BINDING_ABORTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1094,6 +1094,7 @@ public:
|
|||
virtual void MaybePreLoadImage(nsIURI* uri,
|
||||
const nsAString &aCrossOriginAttr,
|
||||
ReferrerPolicy aReferrerPolicy) MOZ_OVERRIDE;
|
||||
virtual void ForgetImagePreload(nsIURI* aURI) MOZ_OVERRIDE;
|
||||
|
||||
virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
|
||||
const nsAString& aCrossOriginAttr,
|
||||
|
@ -1742,8 +1743,11 @@ private:
|
|||
|
||||
nsExternalResourceMap mExternalResourceMap;
|
||||
|
||||
// All images in process of being preloaded
|
||||
nsCOMArray<imgIRequest> mPreloadingImages;
|
||||
// All images in process of being preloaded. This is a hashtable so
|
||||
// we can remove them as the real image loads start; that way we
|
||||
// make sure to not keep the image load going when no one cares
|
||||
// about it anymore.
|
||||
nsRefPtrHashtable<nsURIHashKey, imgIRequest> mPreloadingImages;
|
||||
|
||||
nsRefPtr<mozilla::dom::DOMImplementation> mDOMImplementation;
|
||||
|
||||
|
|
|
@ -146,8 +146,8 @@ struct FullScreenOptions {
|
|||
} // namespace mozilla
|
||||
|
||||
#define NS_IDOCUMENT_IID \
|
||||
{ 0x1f343423, 0x957c, 0x4da3, \
|
||||
{ 0xaa, 0xa3, 0x07, 0x37, 0x54, 0x3e, 0x79, 0x2a } }
|
||||
{ 0xf63d2f6e, 0xd1c1, 0x49b9, \
|
||||
{ 0x88, 0x26, 0xd5, 0x9e, 0x5d, 0x72, 0x2a, 0x42 } }
|
||||
|
||||
// Enum for requesting a particular type of document when creating a doc
|
||||
enum DocumentFlavor {
|
||||
|
@ -1922,6 +1922,12 @@ public:
|
|||
const nsAString& aCrossOriginAttr,
|
||||
ReferrerPolicy aReferrerPolicy) = 0;
|
||||
|
||||
/**
|
||||
* Called by images to forget an image preload when they start doing
|
||||
* the real load.
|
||||
*/
|
||||
virtual void ForgetImagePreload(nsIURI* aURI) = 0;
|
||||
|
||||
/**
|
||||
* Called by nsParser to preload style sheets. Can also be merged into the
|
||||
* parser if and when the parser is merged with libgklayout. aCrossOriginAttr
|
||||
|
|
|
@ -893,6 +893,11 @@ nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
|
|||
getter_AddRefs(req),
|
||||
policyType);
|
||||
|
||||
// Tell the document to forget about the image preload, if any, for
|
||||
// this URI, now that we might have another imgRequestProxy for it.
|
||||
// That way if we get canceled later the image load won't continue.
|
||||
aDocument->ForgetImagePreload(aNewURI);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
TrackImage(req);
|
||||
ResetAnimationIfNeeded();
|
||||
|
|
|
@ -459,7 +459,7 @@ CreateInterfaceObject(JSContext* cx, JS::Handle<JSObject*> global,
|
|||
}
|
||||
|
||||
if (!JS_DefineProperty(cx, constructor, "length", ctorNargs,
|
||||
JSPROP_READONLY | JSPROP_PERMANENT)) {
|
||||
JSPROP_READONLY)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -2089,7 +2089,9 @@ ConstructJSImplementation(JSContext* aCx, const char* aContractId,
|
|||
nsresult rv;
|
||||
nsCOMPtr<nsISupports> implISupports = do_CreateInstance(aContractId, &rv);
|
||||
if (!implISupports) {
|
||||
NS_WARNING("Failed to get JS implementation for contract");
|
||||
nsPrintfCString msg("Failed to get JS implementation for contract \"%s\"",
|
||||
aContractId);
|
||||
NS_WARNING(msg.get());
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -11882,6 +11882,7 @@ class CGResolveSystemBinding(CGAbstractMethod):
|
|||
def definition_body(self):
|
||||
descriptors = self.config.getDescriptors(hasInterfaceObject=True,
|
||||
isExposedInSystemGlobals=True,
|
||||
workers=False,
|
||||
register=True,
|
||||
skipGen=False)
|
||||
|
||||
|
|
|
@ -1150,15 +1150,15 @@ IdlInterface.prototype.test_self = function()
|
|||
|
||||
if (!this.is_callback()) {
|
||||
test(function() {
|
||||
// This function tests WebIDL as of 2013-08-25.
|
||||
// http://dev.w3.org/2006/webapi/WebIDL/#es-interface-call
|
||||
// This function tests WebIDL as of 2014-10-25.
|
||||
// https://heycam.github.io/webidl/#es-interface-call
|
||||
|
||||
assert_own_property(window, this.name,
|
||||
"window does not have own property " + format_value(this.name));
|
||||
|
||||
// "Interface objects for non-callback interfaces MUST have a
|
||||
// property named “length” with attributes { [[Writable]]: false,
|
||||
// [[Enumerable]]: false, [[Configurable]]: false } whose value is
|
||||
// [[Enumerable]]: false, [[Configurable]]: true } whose value is
|
||||
// a Number."
|
||||
assert_own_property(window[this.name], "length");
|
||||
var desc = Object.getOwnPropertyDescriptor(window[this.name], "length");
|
||||
|
@ -1166,7 +1166,7 @@ IdlInterface.prototype.test_self = function()
|
|||
assert_false("set" in desc, this.name + ".length has setter");
|
||||
assert_false(desc.writable, this.name + ".length is writable");
|
||||
assert_false(desc.enumerable, this.name + ".length is enumerable");
|
||||
assert_false(desc.configurable, this.name + ".length is configurable");
|
||||
assert_true(desc.configurable, this.name + ".length is not configurable");
|
||||
|
||||
var constructors = this.extAttrs
|
||||
.filter(function(attr) { return attr.name == "Constructor"; });
|
||||
|
|
|
@ -119,3 +119,38 @@ nsSVGPolyElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks)
|
|||
aMarks->LastElement().angle = prevAngle;
|
||||
aMarks->LastElement().type = nsSVGMark::eEnd;
|
||||
}
|
||||
|
||||
bool
|
||||
nsSVGPolyElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
|
||||
const Matrix& aTransform)
|
||||
{
|
||||
const SVGPointList &points = mPoints.GetAnimValue();
|
||||
|
||||
if (!points.Length()) {
|
||||
// Rendering of the element is disabled
|
||||
aBounds->SetEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aStrokeWidth > 0) {
|
||||
// We don't handle stroke-miterlimit etc. yet
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aTransform.IsRectilinear()) {
|
||||
// We can avoid transforming each point and just transform the result.
|
||||
// Important for large point lists.
|
||||
Rect bounds(points[0], Size());
|
||||
for (uint32_t i = 1; i < points.Length(); ++i) {
|
||||
bounds.ExpandToEnclose(points[i]);
|
||||
}
|
||||
*aBounds = aTransform.TransformBounds(bounds);
|
||||
} else {
|
||||
*aBounds = Rect(aTransform * points[0], Size());
|
||||
for (uint32_t i = 1; i < points.Length(); ++i) {
|
||||
aBounds->ExpandToEnclose(aTransform * points[i]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
virtual bool AttributeDefinesGeometry(const nsIAtom *aName) MOZ_OVERRIDE;
|
||||
virtual bool IsMarkable() MOZ_OVERRIDE { return true; }
|
||||
virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks) MOZ_OVERRIDE;
|
||||
virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
|
||||
const Matrix& aTransform) MOZ_OVERRIDE;
|
||||
|
||||
// WebIDL
|
||||
already_AddRefed<mozilla::DOMSVGPointList> Points();
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
[Constructor(optional USVString init = ""),
|
||||
Constructor(URLSearchParams init),
|
||||
Exposed=(Window,Worker)]
|
||||
Exposed=(Window,Worker,System)]
|
||||
interface URLSearchParams {
|
||||
void append(USVString name, USVString value);
|
||||
void delete(USVString name);
|
||||
|
|
|
@ -34,7 +34,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1086996
|
|||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086996">Mozilla Bug 1086996</a>
|
||||
<div id="boundContent" style="-moz-binding: url(#mainBinding)"></div>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
|
@ -56,5 +55,8 @@ function gotEvent() {
|
|||
]]>
|
||||
</script>
|
||||
</pre>
|
||||
<!-- This div needs to come after the <script> so we don't run the binding ctor
|
||||
before the <script> has been parsed -->
|
||||
<div id="boundContent" style="-moz-binding: url(#mainBinding)"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -177,6 +177,23 @@ struct BaseRect {
|
|||
*static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2);
|
||||
}
|
||||
|
||||
// Expands the rect to include the point
|
||||
void ExpandToEnclose(const Point& aPoint)
|
||||
{
|
||||
if (aPoint.x < x) {
|
||||
width = XMost() - aPoint.x;
|
||||
x = aPoint.x;
|
||||
} else if (aPoint.x > XMost()) {
|
||||
width = aPoint.x - x;
|
||||
}
|
||||
if (aPoint.y < y) {
|
||||
height = YMost() - aPoint.y;
|
||||
y = aPoint.y;
|
||||
} else if (aPoint.y > YMost()) {
|
||||
height = aPoint.y - y;
|
||||
}
|
||||
}
|
||||
|
||||
void SetRect(T aX, T aY, T aWidth, T aHeight)
|
||||
{
|
||||
x = aX; y = aY; width = aWidth; height = aHeight;
|
||||
|
|
|
@ -396,7 +396,7 @@ DWriteGlyphRunFromGlyphs(const GlyphBuffer &aGlyphs, ScaledFontDWrite *aFont, Au
|
|||
run->isSideways = FALSE;
|
||||
}
|
||||
|
||||
static TemporaryRef<ID2D1Geometry>
|
||||
static inline TemporaryRef<ID2D1Geometry>
|
||||
ConvertRectToGeometry(const D2D1_RECT_F& aRect)
|
||||
{
|
||||
RefPtr<ID2D1RectangleGeometry> rectGeom;
|
||||
|
@ -404,7 +404,7 @@ ConvertRectToGeometry(const D2D1_RECT_F& aRect)
|
|||
return rectGeom.forget();
|
||||
}
|
||||
|
||||
static TemporaryRef<ID2D1Geometry>
|
||||
static inline TemporaryRef<ID2D1Geometry>
|
||||
GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform)
|
||||
{
|
||||
RefPtr<ID2D1PathGeometry> tmpGeometry;
|
||||
|
@ -417,7 +417,7 @@ GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTrans
|
|||
return tmpGeometry.forget();
|
||||
}
|
||||
|
||||
static TemporaryRef<ID2D1Geometry>
|
||||
static inline TemporaryRef<ID2D1Geometry>
|
||||
IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB)
|
||||
{
|
||||
RefPtr<ID2D1PathGeometry> pathGeom;
|
||||
|
@ -430,7 +430,7 @@ IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB)
|
|||
return pathGeom.forget();
|
||||
}
|
||||
|
||||
static TemporaryRef<ID2D1StrokeStyle>
|
||||
static inline TemporaryRef<ID2D1StrokeStyle>
|
||||
CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions)
|
||||
{
|
||||
RefPtr<ID2D1StrokeStyle> style;
|
||||
|
@ -510,7 +510,7 @@ CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions)
|
|||
// This creates a (partially) uploaded bitmap for a DataSourceSurface. It
|
||||
// uploads the minimum requirement and possibly downscales. It adjusts the
|
||||
// input Matrix to compensate.
|
||||
static TemporaryRef<ID2D1Bitmap>
|
||||
static inline TemporaryRef<ID2D1Bitmap>
|
||||
CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestinationTransform,
|
||||
const IntSize &aDestinationSize, ExtendMode aExtendMode,
|
||||
Matrix &aSourceTransform, ID2D1RenderTarget *aRT,
|
||||
|
|
|
@ -42,10 +42,9 @@ class APZTestData {
|
|||
friend struct APZTestDataToJSConverter;
|
||||
public:
|
||||
void StartNewPaint(SequenceNumber aSequenceNumber) {
|
||||
// We should never get more than one paint with the same sequence number.
|
||||
MOZ_ASSERT(mPaints.find(aSequenceNumber) == mPaints.end());
|
||||
mPaints.insert(DataStore::value_type(aSequenceNumber, Bucket()));
|
||||
// TODO(botond): MOZ_ASSERT() that we didn't already have a paint with this
|
||||
// sequence number once we get rid ofAPZCTreeManager::UpdatePanZoomControllerTree()
|
||||
// calls for repeat transactions (bug 1007728).
|
||||
}
|
||||
void LogTestDataForPaint(SequenceNumber aSequenceNumber,
|
||||
ViewID aScrollId,
|
||||
|
@ -93,10 +92,9 @@ private:
|
|||
}
|
||||
Bucket& bucket = bucketIterator->second;
|
||||
ScrollFrameData& scrollFrameData = bucket[aScrollId]; // create if doesn't exist
|
||||
MOZ_ASSERT(scrollFrameData.find(aKey) == scrollFrameData.end()
|
||||
|| scrollFrameData[aKey] == aValue);
|
||||
scrollFrameData.insert(ScrollFrameData::value_type(aKey, aValue));
|
||||
// TODO(botond): MOZ_ASSERT() that we don't already have this key once we
|
||||
// get rid of APZCTreeManager::UpdatePanZoomControllerTree() calls for
|
||||
// repeat transactions (bug 1007728).
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <windows.h>
|
||||
#include <dwrite.h>
|
||||
|
||||
static DWRITE_FONT_STRETCH
|
||||
static inline DWRITE_FONT_STRETCH
|
||||
DWriteFontStretchFromStretch(int16_t aStretch)
|
||||
{
|
||||
switch (aStretch) {
|
||||
|
@ -47,7 +47,7 @@ DWriteFontStretchFromStretch(int16_t aStretch)
|
|||
}
|
||||
}
|
||||
|
||||
static int16_t
|
||||
static inline int16_t
|
||||
FontStretchFromDWriteStretch(DWRITE_FONT_STRETCH aStretch)
|
||||
{
|
||||
switch (aStretch) {
|
||||
|
|
|
@ -1266,7 +1266,6 @@ CallTraceCallbackOnNonHeap(T *v, const TraceCallbacks &aCallbacks, const char *a
|
|||
MOZ_ASSERT(!IsInsideNursery(cell));
|
||||
JS::Heap<T> *asHeapT = reinterpret_cast<JS::Heap<T>*>(v);
|
||||
aCallbacks.Trace(asHeapT, aName, aClosure);
|
||||
MOZ_ASSERT(GCMethods<T>::asGCThingOrNull(*v) == cell);
|
||||
}
|
||||
|
||||
} /* namespace gc */
|
||||
|
|
|
@ -559,10 +559,6 @@ struct Or {
|
|||
static inline T apply(T l, T r) { return l | r; }
|
||||
};
|
||||
template<typename T>
|
||||
struct Scale {
|
||||
static inline T apply(int32_t lane, T scalar, T x) { return scalar * x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithX {
|
||||
static inline T apply(int32_t lane, T scalar, T x) { return lane == 0 ? scalar : x; }
|
||||
};
|
||||
|
@ -578,22 +574,6 @@ template<typename T>
|
|||
struct WithW {
|
||||
static inline T apply(int32_t lane, T scalar, T x) { return lane == 3 ? scalar : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithFlagX {
|
||||
static inline T apply(T l, T f, T x) { return l == 0 ? (f ? 0xFFFFFFFF : 0x0) : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithFlagY {
|
||||
static inline T apply(T l, T f, T x) { return l == 1 ? (f ? 0xFFFFFFFF : 0x0) : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithFlagZ {
|
||||
static inline T apply(T l, T f, T x) { return l == 2 ? (f ? 0xFFFFFFFF : 0x0) : x; }
|
||||
};
|
||||
template<typename T>
|
||||
struct WithFlagW {
|
||||
static inline T apply(T l, T f, T x) { return l == 3 ? (f ? 0xFFFFFFFF : 0x0) : x; }
|
||||
};
|
||||
struct ShiftLeft {
|
||||
static inline int32_t apply(int32_t v, int32_t bits) { return v << bits; }
|
||||
};
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
* https://github.com/johnmccutchan/ecmascript_simd/blob/master/src/ecmascript_simd.js
|
||||
*/
|
||||
|
||||
#define FLOAT32X4_NULLARY_FUNCTION_LIST(V) \
|
||||
V(zero, (FuncZero<Float32x4>), 0, 0)
|
||||
|
||||
#define FLOAT32X4_UNARY_FUNCTION_LIST(V) \
|
||||
V(abs, (UnaryFunc<Float32x4, Abs, Float32x4>), 1, 0) \
|
||||
V(fromInt32x4, (FuncConvert<Int32x4, Float32x4> ), 1, 0) \
|
||||
|
@ -52,7 +49,6 @@
|
|||
V(mul, (BinaryFunc<Float32x4, Mul, Float32x4>), 2, 0) \
|
||||
V(notEqual, (CompareFunc<Float32x4, NotEqual>), 2, 0) \
|
||||
V(or, (CoercedBinaryFunc<Float32x4, Int32x4, Or, Float32x4>), 2, 0) \
|
||||
V(scale, (FuncWith<Float32x4, Scale>), 2, 0) \
|
||||
V(store, (Store<Float32x4, 4>), 3, 0) \
|
||||
V(storeXYZ, (Store<Float32x4, 3>), 3, 0) \
|
||||
V(storeXY, (Store<Float32x4, 2>), 3, 0) \
|
||||
|
@ -73,15 +69,11 @@
|
|||
V(shuffle, Shuffle<Float32x4>, 3, 0)
|
||||
|
||||
#define FLOAT32X4_FUNCTION_LIST(V) \
|
||||
FLOAT32X4_NULLARY_FUNCTION_LIST(V) \
|
||||
FLOAT32X4_UNARY_FUNCTION_LIST(V) \
|
||||
FLOAT32X4_BINARY_FUNCTION_LIST(V) \
|
||||
FLOAT32X4_TERNARY_FUNCTION_LIST(V) \
|
||||
FLOAT32X4_SHUFFLE_FUNCTION_LIST(V)
|
||||
|
||||
#define INT32X4_NULLARY_FUNCTION_LIST(V) \
|
||||
V(zero, (FuncZero<Int32x4>), 0, 0)
|
||||
|
||||
#define INT32X4_UNARY_FUNCTION_LIST(V) \
|
||||
V(fromFloat32x4, (FuncConvert<Float32x4, Int32x4>), 1, 0) \
|
||||
V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Int32x4>), 1, 0) \
|
||||
|
@ -94,12 +86,15 @@
|
|||
V(and, (BinaryFunc<Int32x4, And, Int32x4>), 2, 0) \
|
||||
V(equal, (CompareFunc<Int32x4, Equal>), 2, 0) \
|
||||
V(greaterThan, (CompareFunc<Int32x4, GreaterThan>), 2, 0) \
|
||||
V(greaterThanOrEqual, (CompareFunc<Int32x4, GreaterThanOrEqual>), 2, 0) \
|
||||
V(lessThan, (CompareFunc<Int32x4, LessThan>), 2, 0) \
|
||||
V(lessThanOrEqual, (CompareFunc<Int32x4, LessThanOrEqual>), 2, 0) \
|
||||
V(load, (Load<Int32x4, 4>), 2, 0) \
|
||||
V(loadXYZ, (Load<Int32x4, 3>), 2, 0) \
|
||||
V(loadXY, (Load<Int32x4, 2>), 2, 0) \
|
||||
V(loadX, (Load<Int32x4, 1>), 2, 0) \
|
||||
V(mul, (BinaryFunc<Int32x4, Mul, Int32x4>), 2, 0) \
|
||||
V(notEqual, (CompareFunc<Int32x4, NotEqual>), 2, 0) \
|
||||
V(or, (BinaryFunc<Int32x4, Or, Int32x4>), 2, 0) \
|
||||
V(sub, (BinaryFunc<Int32x4, Sub, Int32x4>), 2, 0) \
|
||||
V(shiftLeft, (Int32x4BinaryScalar<ShiftLeft>), 2, 0) \
|
||||
|
@ -109,10 +104,6 @@
|
|||
V(storeXYZ, (Store<Int32x4, 3>), 3, 0) \
|
||||
V(storeXY, (Store<Int32x4, 2>), 3, 0) \
|
||||
V(storeX, (Store<Int32x4, 1>), 3, 0) \
|
||||
V(withFlagX, (FuncWith<Int32x4, WithFlagX>), 2, 0) \
|
||||
V(withFlagY, (FuncWith<Int32x4, WithFlagY>), 2, 0) \
|
||||
V(withFlagZ, (FuncWith<Int32x4, WithFlagZ>), 2, 0) \
|
||||
V(withFlagW, (FuncWith<Int32x4, WithFlagW>), 2, 0) \
|
||||
V(withX, (FuncWith<Int32x4, WithX>), 2, 0) \
|
||||
V(withY, (FuncWith<Int32x4, WithY>), 2, 0) \
|
||||
V(withZ, (FuncWith<Int32x4, WithZ>), 2, 0) \
|
||||
|
@ -130,7 +121,6 @@
|
|||
V(shuffle, Shuffle<Int32x4>, 3, 0)
|
||||
|
||||
#define INT32X4_FUNCTION_LIST(V) \
|
||||
INT32X4_NULLARY_FUNCTION_LIST(V) \
|
||||
INT32X4_UNARY_FUNCTION_LIST(V) \
|
||||
INT32X4_BINARY_FUNCTION_LIST(V) \
|
||||
INT32X4_TERNARY_FUNCTION_LIST(V) \
|
||||
|
@ -155,16 +145,16 @@
|
|||
_(max) \
|
||||
_(min) \
|
||||
_(maxNum) \
|
||||
_(minNum) \
|
||||
_(lessThanOrEqual) \
|
||||
_(notEqual) \
|
||||
_(greaterThanOrEqual)
|
||||
_(minNum)
|
||||
#define FOREACH_COMMONX4_SIMD_OP(_) \
|
||||
_(add) \
|
||||
_(sub) \
|
||||
_(lessThan) \
|
||||
_(lessThanOrEqual) \
|
||||
_(equal) \
|
||||
_(notEqual) \
|
||||
_(greaterThan) \
|
||||
_(greaterThanOrEqual) \
|
||||
_(and) \
|
||||
_(or) \
|
||||
_(xor) \
|
||||
|
|
|
@ -1770,7 +1770,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
|
||||
|
|
|
@ -611,6 +611,7 @@ class GCRuntime
|
|||
void updateAllCellPointersSerial(MovingTracer *trc, ArenasToUpdate &source);
|
||||
void updatePointersToRelocatedCells();
|
||||
void releaseRelocatedArenas(ArenaHeader *relocatedList);
|
||||
void releaseRelocatedArenasWithoutUnlocking(ArenaHeader *relocatedList, const AutoLockGC& lock);
|
||||
#ifdef DEBUG
|
||||
void protectRelocatedArenas(ArenaHeader *relocatedList);
|
||||
void unprotectRelocatedArenas(ArenaHeader *relocatedList);
|
||||
|
|
|
@ -590,26 +590,38 @@ CheckI4(WWI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 3, 42]);
|
|||
// yields all bits set to 0 (i.e 0).
|
||||
const T = -1;
|
||||
const F = 0;
|
||||
assertAsmTypeFail('glob', USE_ASM + I32 + "var lt=i4.lessThanOrEqual; function f() {} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + I32 + "var ge=i4.greaterThanOrEqual; function f() {} return f");
|
||||
assertAsmTypeFail('glob', USE_ASM + I32 + "var ne=i4.notEqual; function f() {} return f");
|
||||
|
||||
const EQI32 = 'var eq = i4.equal';
|
||||
const NEI32 = 'var ne = i4.notEqual';
|
||||
const LTI32 = 'var lt = i4.lessThan;';
|
||||
const LEI32 = 'var le = i4.lessThanOrEqual';
|
||||
const GTI32 = 'var gt = i4.greaterThan;';
|
||||
const EQI32 = 'var eq = i4.equal;';
|
||||
|
||||
CheckI4(LTI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=lt(x,y)', [F, F, F, F]);
|
||||
CheckI4(LTI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=lt(x,y)', [T, T, T, T]);
|
||||
CheckI4(LTI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=lt(x,y)', [F, T, T, F]);
|
||||
const GEI32 = 'var ge = i4.greaterThanOrEqual';
|
||||
|
||||
CheckI4(EQI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=eq(x,y)', [F, F, F, F]);
|
||||
CheckI4(EQI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=eq(x,y)', [F, F, F, F]);
|
||||
CheckI4(EQI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=eq(x,y)', [T, F, F, F]);
|
||||
|
||||
CheckI4(NEI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=ne(x,y)', [T, T, T, T]);
|
||||
CheckI4(NEI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=ne(x,y)', [T, T, T, T]);
|
||||
CheckI4(NEI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=ne(x,y)', [F, T, T, T]);
|
||||
|
||||
CheckI4(LTI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=lt(x,y)', [F, F, F, F]);
|
||||
CheckI4(LTI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=lt(x,y)', [T, T, T, T]);
|
||||
CheckI4(LTI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=lt(x,y)', [F, T, T, F]);
|
||||
|
||||
CheckI4(LEI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=le(x,y)', [F, F, F, F]);
|
||||
CheckI4(LEI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=le(x,y)', [T, T, T, T]);
|
||||
CheckI4(LEI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=le(x,y)', [T, T, T, F]);
|
||||
|
||||
CheckI4(GTI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=gt(x,y)', [T, T, T, T]);
|
||||
CheckI4(GTI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=gt(x,y)', [F, F, F, F]);
|
||||
CheckI4(GTI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=gt(x,y)', [F, F, F, T]);
|
||||
|
||||
CheckI4(GEI32, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=ge(x,y)', [T, T, T, T]);
|
||||
CheckI4(GEI32, 'var x=i4(-1,1,0,2); var y=i4(1,2,3,4); x=ge(x,y)', [F, F, F, F]);
|
||||
CheckI4(GEI32, 'var x=i4(1,0,3,4); var y=i4(1,1,7,0); x=ge(x,y)', [T, F, F, T]);
|
||||
|
||||
const LTF32 = 'var lt=f4.lessThan;';
|
||||
const LEF32 = 'var le=f4.lessThanOrEqual;';
|
||||
const GTF32 = 'var gt=f4.greaterThan;';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
setJitCompilerOption("ion.warmup.trigger", 30);
|
||||
|
||||
var arr = [];
|
||||
function f (cond, a) {
|
||||
var obj = { a: 0 };
|
||||
var x = 2 * a + 1;
|
||||
if (cond) {
|
||||
obj.a = x;
|
||||
arr.push(obj.a);
|
||||
obj.a = 1;
|
||||
} else {
|
||||
obj.a = 1;
|
||||
}
|
||||
return obj.a;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
assertEq(f(i % 2, i), 1);
|
||||
}
|
|
@ -309,11 +309,15 @@ BaselineCompiler::emitInitializeLocals(size_t n, const Value &v)
|
|||
bool
|
||||
BaselineCompiler::emitPrologue()
|
||||
{
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
// Push link register from generateEnterJIT()'s BLR.
|
||||
masm.pushReturnAddress();
|
||||
masm.checkStackAlignment();
|
||||
#endif
|
||||
masm.push(BaselineFrameReg);
|
||||
masm.mov(BaselineStackReg, BaselineFrameReg);
|
||||
|
||||
masm.subPtr(Imm32(BaselineFrame::Size()), BaselineStackReg);
|
||||
masm.checkStackAlignment();
|
||||
|
||||
// Initialize BaselineFrame. For eval scripts, the scope chain
|
||||
// is passed in R1, so we have to be careful not to clobber
|
||||
|
@ -3450,7 +3454,7 @@ typedef bool (*InterpretResumeFn)(JSContext *, HandleObject, HandleValue, Handle
|
|||
static const VMFunction InterpretResumeInfo = FunctionInfo<InterpretResumeFn>(jit::InterpretResume);
|
||||
|
||||
typedef bool (*GeneratorThrowFn)(JSContext *, BaselineFrame *, HandleObject, HandleValue, uint32_t);
|
||||
static const VMFunction GeneratorThrowInfo = FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrClose);
|
||||
static const VMFunction GeneratorThrowInfo = FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrClose, TailCall);
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_RESUME()
|
||||
|
|
|
@ -660,6 +660,7 @@ ICStubCompiler::tailCallVM(const VMFunction &fun, MacroAssembler &masm)
|
|||
if (!code)
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(fun.expectTailCall == TailCall);
|
||||
uint32_t argSize = fun.explicitStackSlots() * sizeof(void *);
|
||||
EmitTailCallVM(code, masm, argSize);
|
||||
return true;
|
||||
|
@ -672,6 +673,7 @@ ICStubCompiler::callVM(const VMFunction &fun, MacroAssembler &masm)
|
|||
if (!code)
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(fun.expectTailCall == NonTailCall);
|
||||
EmitCallVM(code, masm);
|
||||
return true;
|
||||
}
|
||||
|
@ -1120,7 +1122,7 @@ DoProfilerFallback(JSContext *cx, BaselineFrame *frame, ICProfiler_Fallback *stu
|
|||
|
||||
typedef bool (*DoProfilerFallbackFn)(JSContext *, BaselineFrame *frame, ICProfiler_Fallback *);
|
||||
static const VMFunction DoProfilerFallbackInfo =
|
||||
FunctionInfo<DoProfilerFallbackFn>(DoProfilerFallback);
|
||||
FunctionInfo<DoProfilerFallbackFn>(DoProfilerFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICProfiler_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1322,7 +1324,7 @@ DoTypeMonitorFallback(JSContext *cx, BaselineFrame *frame, ICTypeMonitor_Fallbac
|
|||
typedef bool (*DoTypeMonitorFallbackFn)(JSContext *, BaselineFrame *, ICTypeMonitor_Fallback *,
|
||||
HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoTypeMonitorFallbackInfo =
|
||||
FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback);
|
||||
FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICTypeMonitor_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1553,7 +1555,7 @@ DoTypeUpdateFallback(JSContext *cx, BaselineFrame *frame, ICUpdatedStub *stub, H
|
|||
typedef bool (*DoTypeUpdateFallbackFn)(JSContext *, BaselineFrame *, ICUpdatedStub *, HandleValue,
|
||||
HandleValue);
|
||||
const VMFunction DoTypeUpdateFallbackInfo =
|
||||
FunctionInfo<DoTypeUpdateFallbackFn>(DoTypeUpdateFallback);
|
||||
FunctionInfo<DoTypeUpdateFallbackFn>(DoTypeUpdateFallback, NonTailCall);
|
||||
|
||||
bool
|
||||
ICTypeUpdate_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1696,7 +1698,7 @@ DoThisFallback(JSContext *cx, ICThis_Fallback *stub, HandleValue thisv, MutableH
|
|||
}
|
||||
|
||||
typedef bool (*DoThisFallbackFn)(JSContext *, ICThis_Fallback *, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoThisFallbackInfo = FunctionInfo<DoThisFallbackFn>(DoThisFallback);
|
||||
static const VMFunction DoThisFallbackInfo = FunctionInfo<DoThisFallbackFn>(DoThisFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICThis_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1732,7 +1734,7 @@ DoNewArray(JSContext *cx, ICNewArray_Fallback *stub, uint32_t length,
|
|||
|
||||
typedef bool(*DoNewArrayFn)(JSContext *, ICNewArray_Fallback *, uint32_t, HandleTypeObject,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray);
|
||||
static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray, TailCall);
|
||||
|
||||
bool
|
||||
ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1765,7 +1767,7 @@ DoNewObject(JSContext *cx, ICNewObject_Fallback *stub, MutableHandleValue res)
|
|||
}
|
||||
|
||||
typedef bool(*DoNewObjectFn)(JSContext *, ICNewObject_Fallback *, MutableHandleValue);
|
||||
static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject);
|
||||
static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject, TailCall);
|
||||
|
||||
bool
|
||||
ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -1977,7 +1979,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub_
|
|||
typedef bool (*DoCompareFallbackFn)(JSContext *, BaselineFrame *, ICCompare_Fallback *,
|
||||
HandleValue, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoCompareFallbackInfo =
|
||||
FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, PopValues(2));
|
||||
FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICCompare_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -2312,7 +2314,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H
|
|||
|
||||
typedef bool (*pf)(JSContext *, BaselineFrame *, ICToBool_Fallback *, HandleValue,
|
||||
MutableHandleValue);
|
||||
static const VMFunction fun = FunctionInfo<pf>(DoToBoolFallback);
|
||||
static const VMFunction fun = FunctionInfo<pf>(DoToBoolFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICToBool_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -2479,7 +2481,7 @@ DoToNumberFallback(JSContext *cx, ICToNumber_Fallback *stub, HandleValue arg, Mu
|
|||
|
||||
typedef bool (*DoToNumberFallbackFn)(JSContext *, ICToNumber_Fallback *, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoToNumberFallbackInfo =
|
||||
FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, PopValues(1));
|
||||
FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, TailCall, PopValues(1));
|
||||
|
||||
bool
|
||||
ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -2722,7 +2724,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac
|
|||
typedef bool (*DoBinaryArithFallbackFn)(JSContext *, BaselineFrame *, ICBinaryArith_Fallback *,
|
||||
HandleValue, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoBinaryArithFallbackInfo =
|
||||
FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, PopValues(2));
|
||||
FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICBinaryArith_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -2768,7 +2770,7 @@ DoConcatStrings(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleVa
|
|||
}
|
||||
|
||||
typedef bool (*DoConcatStringsFn)(JSContext *, HandleValue, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoConcatStringsInfo = FunctionInfo<DoConcatStringsFn>(DoConcatStrings);
|
||||
static const VMFunction DoConcatStringsInfo = FunctionInfo<DoConcatStringsFn>(DoConcatStrings, TailCall);
|
||||
|
||||
bool
|
||||
ICBinaryArith_StringConcat::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -2845,7 +2847,7 @@ DoConcatStringObject(JSContext *cx, bool lhsIsString, HandleValue lhs, HandleVal
|
|||
typedef bool (*DoConcatStringObjectFn)(JSContext *, bool lhsIsString, HandleValue, HandleValue,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoConcatStringObjectInfo =
|
||||
FunctionInfo<DoConcatStringObjectFn>(DoConcatStringObject, PopValues(2));
|
||||
FunctionInfo<DoConcatStringObjectFn>(DoConcatStringObject, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICBinaryArith_StringObjectConcat::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -3132,7 +3134,7 @@ DoUnaryArithFallback(JSContext *cx, BaselineFrame *frame, ICUnaryArith_Fallback
|
|||
typedef bool (*DoUnaryArithFallbackFn)(JSContext *, BaselineFrame *, ICUnaryArith_Fallback *,
|
||||
HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoUnaryArithFallbackInfo =
|
||||
FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, PopValues(1));
|
||||
FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, TailCall, PopValues(1));
|
||||
|
||||
bool
|
||||
ICUnaryArith_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -4087,7 +4089,7 @@ DoGetElemFallback(JSContext *cx, BaselineFrame *frame, ICGetElem_Fallback *stub_
|
|||
typedef bool (*DoGetElemFallbackFn)(JSContext *, BaselineFrame *, ICGetElem_Fallback *,
|
||||
HandleValue, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoGetElemFallbackInfo =
|
||||
FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, PopValues(2));
|
||||
FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -5150,7 +5152,7 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_
|
|||
typedef bool (*DoSetElemFallbackFn)(JSContext *, BaselineFrame *, ICSetElem_Fallback *, Value *,
|
||||
HandleValue, HandleValue, HandleValue);
|
||||
static const VMFunction DoSetElemFallbackInfo =
|
||||
FunctionInfo<DoSetElemFallbackFn>(DoSetElemFallback, PopValues(2));
|
||||
FunctionInfo<DoSetElemFallbackFn>(DoSetElemFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICSetElem_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -5674,7 +5676,7 @@ DoInFallback(JSContext *cx, ICIn_Fallback *stub, HandleValue key, HandleValue ob
|
|||
typedef bool (*DoInFallbackFn)(JSContext *, ICIn_Fallback *, HandleValue, HandleValue,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoInFallbackInfo =
|
||||
FunctionInfo<DoInFallbackFn>(DoInFallback, PopValues(2));
|
||||
FunctionInfo<DoInFallbackFn>(DoInFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICIn_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -6038,7 +6040,7 @@ DoGetNameFallback(JSContext *cx, BaselineFrame *frame, ICGetName_Fallback *stub_
|
|||
|
||||
typedef bool (*DoGetNameFallbackFn)(JSContext *, BaselineFrame *, ICGetName_Fallback *,
|
||||
HandleObject, MutableHandleValue);
|
||||
static const VMFunction DoGetNameFallbackInfo = FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback);
|
||||
static const VMFunction DoGetNameFallbackInfo = FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICGetName_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -6149,7 +6151,7 @@ DoBindNameFallback(JSContext *cx, BaselineFrame *frame, ICBindName_Fallback *stu
|
|||
typedef bool (*DoBindNameFallbackFn)(JSContext *, BaselineFrame *, ICBindName_Fallback *,
|
||||
HandleObject, MutableHandleValue);
|
||||
static const VMFunction DoBindNameFallbackInfo =
|
||||
FunctionInfo<DoBindNameFallbackFn>(DoBindNameFallback);
|
||||
FunctionInfo<DoBindNameFallbackFn>(DoBindNameFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICBindName_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -6209,7 +6211,7 @@ DoGetIntrinsicFallback(JSContext *cx, BaselineFrame *frame, ICGetIntrinsic_Fallb
|
|||
typedef bool (*DoGetIntrinsicFallbackFn)(JSContext *, BaselineFrame *, ICGetIntrinsic_Fallback *,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoGetIntrinsicFallbackInfo =
|
||||
FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback);
|
||||
FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICGetIntrinsic_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -6833,7 +6835,7 @@ DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub_
|
|||
typedef bool (*DoGetPropFallbackFn)(JSContext *, BaselineFrame *, ICGetProp_Fallback *,
|
||||
MutableHandleValue, MutableHandleValue);
|
||||
static const VMFunction DoGetPropFallbackInfo =
|
||||
FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, PopValues(1));
|
||||
FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, TailCall, PopValues(1));
|
||||
|
||||
bool
|
||||
ICGetProp_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -7985,7 +7987,7 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_
|
|||
typedef bool (*DoSetPropFallbackFn)(JSContext *, BaselineFrame *, ICSetProp_Fallback *,
|
||||
HandleValue, HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoSetPropFallbackInfo =
|
||||
FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, PopValues(2));
|
||||
FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICSetProp_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10484,7 +10486,7 @@ DoIteratorNewFallback(JSContext *cx, BaselineFrame *frame, ICIteratorNew_Fallbac
|
|||
typedef bool (*DoIteratorNewFallbackFn)(JSContext *, BaselineFrame *, ICIteratorNew_Fallback *,
|
||||
HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoIteratorNewFallbackInfo =
|
||||
FunctionInfo<DoIteratorNewFallbackFn>(DoIteratorNewFallback, PopValues(1));
|
||||
FunctionInfo<DoIteratorNewFallbackFn>(DoIteratorNewFallback, TailCall, PopValues(1));
|
||||
|
||||
bool
|
||||
ICIteratorNew_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10540,7 +10542,7 @@ DoIteratorMoreFallback(JSContext *cx, BaselineFrame *frame, ICIteratorMore_Fallb
|
|||
typedef bool (*DoIteratorMoreFallbackFn)(JSContext *, BaselineFrame *, ICIteratorMore_Fallback *,
|
||||
HandleObject, MutableHandleValue);
|
||||
static const VMFunction DoIteratorMoreFallbackInfo =
|
||||
FunctionInfo<DoIteratorMoreFallbackFn>(DoIteratorMoreFallback);
|
||||
FunctionInfo<DoIteratorMoreFallbackFn>(DoIteratorMoreFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICIteratorMore_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10619,7 +10621,7 @@ DoIteratorCloseFallback(JSContext *cx, ICIteratorClose_Fallback *stub, HandleVal
|
|||
|
||||
typedef bool (*DoIteratorCloseFallbackFn)(JSContext *, ICIteratorClose_Fallback *, HandleValue);
|
||||
static const VMFunction DoIteratorCloseFallbackInfo =
|
||||
FunctionInfo<DoIteratorCloseFallbackFn>(DoIteratorCloseFallback);
|
||||
FunctionInfo<DoIteratorCloseFallbackFn>(DoIteratorCloseFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICIteratorClose_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10666,7 +10668,7 @@ DoInstanceOfFallback(JSContext *cx, ICInstanceOf_Fallback *stub,
|
|||
typedef bool (*DoInstanceOfFallbackFn)(JSContext *, ICInstanceOf_Fallback *, HandleValue, HandleValue,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoInstanceOfFallbackInfo =
|
||||
FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, PopValues(2));
|
||||
FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, TailCall, PopValues(2));
|
||||
|
||||
bool
|
||||
ICInstanceOf_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10715,7 +10717,7 @@ DoTypeOfFallback(JSContext *cx, BaselineFrame *frame, ICTypeOf_Fallback *stub, H
|
|||
typedef bool (*DoTypeOfFallbackFn)(JSContext *, BaselineFrame *frame, ICTypeOf_Fallback *,
|
||||
HandleValue, MutableHandleValue);
|
||||
static const VMFunction DoTypeOfFallbackInfo =
|
||||
FunctionInfo<DoTypeOfFallbackFn>(DoTypeOfFallback);
|
||||
FunctionInfo<DoTypeOfFallbackFn>(DoTypeOfFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICTypeOf_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -10806,7 +10808,7 @@ typedef bool(*DoRetSubFallbackFn)(JSContext *cx, BaselineFrame *, ICRetSub_Fallb
|
|||
static const VMFunction DoRetSubFallbackInfo = FunctionInfo<DoRetSubFallbackFn>(DoRetSubFallback);
|
||||
|
||||
typedef bool (*ThrowFn)(JSContext *, HandleValue);
|
||||
static const VMFunction ThrowInfoBaseline = FunctionInfo<ThrowFn>(js::Throw);
|
||||
static const VMFunction ThrowInfoBaseline = FunctionInfo<ThrowFn>(js::Throw, TailCall);
|
||||
|
||||
bool
|
||||
ICRetSub_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
@ -11529,7 +11531,7 @@ static bool DoRestFallback(JSContext *cx, ICRest_Fallback *stub,
|
|||
typedef bool (*DoRestFallbackFn)(JSContext *, ICRest_Fallback *, BaselineFrame *,
|
||||
MutableHandleValue);
|
||||
static const VMFunction DoRestFallbackInfo =
|
||||
FunctionInfo<DoRestFallbackFn>(DoRestFallback);
|
||||
FunctionInfo<DoRestFallbackFn>(DoRestFallback, TailCall);
|
||||
|
||||
bool
|
||||
ICRest_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
|
|
|
@ -1095,7 +1095,10 @@ PrepareAndExecuteRegExp(JSContext *cx, MacroAssembler &masm, Register regexp, Re
|
|||
RegExpStatics *res = cx->global()->getRegExpStatics(cx);
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
if (mode != RegExpShared::MatchOnly)
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
if (mode == RegExpShared::Normal) {
|
||||
// First, fill in a skeletal MatchPairs instance on the stack. This will be
|
||||
// passed to the OOL stub in the caller if we aren't able to execute the
|
||||
|
@ -1573,6 +1576,10 @@ JitCompartment::generateRegExpTestStub(JSContext *cx)
|
|||
|
||||
MacroAssembler masm(cx);
|
||||
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
|
||||
masm.reserveStack(sizeof(irregexp::InputOutputData));
|
||||
|
||||
Label notFound, oolEntry;
|
||||
|
@ -6173,7 +6180,9 @@ JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode)
|
|||
Register forkJoinContext = CallTempReg4;
|
||||
|
||||
Label failure, failurePopTemps;
|
||||
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
// If lhs is empty, return rhs.
|
||||
Label leftEmpty;
|
||||
masm.loadStringLength(lhs, temp1);
|
||||
|
@ -6284,6 +6293,9 @@ JitRuntime::generateMallocStub(JSContext *cx)
|
|||
MacroAssembler masm(cx);
|
||||
|
||||
RegisterSet regs = RegisterSet::Volatile();
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
regs.takeUnchecked(regNBytes);
|
||||
masm.PushRegsInMask(regs);
|
||||
|
||||
|
@ -6319,7 +6331,9 @@ JitRuntime::generateFreeStub(JSContext *cx)
|
|||
const Register regSlots = CallTempReg0;
|
||||
|
||||
MacroAssembler masm(cx);
|
||||
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
RegisterSet regs = RegisterSet::Volatile();
|
||||
regs.takeUnchecked(regSlots);
|
||||
masm.PushRegsInMask(regs);
|
||||
|
@ -6352,15 +6366,26 @@ JitCode *
|
|||
JitRuntime::generateLazyLinkStub(JSContext *cx)
|
||||
{
|
||||
MacroAssembler masm(cx);
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.push(lr);
|
||||
#endif
|
||||
|
||||
Label call;
|
||||
GeneralRegisterSet regs = GeneralRegisterSet::Volatile();
|
||||
Register temp0 = regs.takeAny();
|
||||
|
||||
masm.callWithExitFrame(&call);
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
// sigh, this should probably attempt to bypass the push lr that starts off the block
|
||||
// but oh well.
|
||||
masm.pop(lr);
|
||||
#endif
|
||||
masm.jump(ReturnReg);
|
||||
|
||||
masm.bind(&call);
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.push(lr);
|
||||
#endif
|
||||
masm.enterExitFrame();
|
||||
masm.setupUnalignedABICall(1, temp0);
|
||||
masm.loadJSContext(temp0);
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "jit/PerfSpewer.h"
|
||||
#include "jit/RangeAnalysis.h"
|
||||
#include "jit/ScalarReplacement.h"
|
||||
#include "jit/Sink.h"
|
||||
#include "jit/StupidAllocator.h"
|
||||
#include "jit/ValueNumbering.h"
|
||||
#include "vm/ForkJoin.h"
|
||||
|
@ -1562,6 +1563,17 @@ OptimizeMIR(MIRGenerator *mir)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mir->optimizationInfo().sinkEnabled()) {
|
||||
AutoTraceLog log(logger, TraceLogger::EliminateDeadCode);
|
||||
if (!Sink(mir, graph))
|
||||
return false;
|
||||
IonSpewPass("Sink");
|
||||
AssertExtendedGraphCoherency(graph);
|
||||
|
||||
if (mir->shouldCancel("Sink"))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make loops contiguous. We do this after GVN/UCE and range analysis,
|
||||
// which can remove CFG edges, exposing more blocks that can be moved.
|
||||
{
|
||||
|
|
|
@ -569,10 +569,6 @@ jit::EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph)
|
|||
if (js::jit::IsDiscardable(inst))
|
||||
{
|
||||
block->discard(inst);
|
||||
} else if (!inst->isRecoveredOnBailout() && !inst->isGuard() &&
|
||||
!inst->hasLiveDefUses() && inst->canRecoverOnBailout())
|
||||
{
|
||||
inst->setRecoveredOnBailout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8204,8 +8204,12 @@ IonBuilder::jsop_setelem()
|
|||
if (!setElemTryArguments(&emitted, object, index, value) || emitted)
|
||||
return emitted;
|
||||
|
||||
if (script()->argumentsHasVarBinding() && object->mightBeType(MIRType_MagicOptimizedArguments))
|
||||
if (script()->argumentsHasVarBinding() &&
|
||||
object->mightBeType(MIRType_MagicOptimizedArguments) &&
|
||||
info().executionMode() != ArgumentsUsageAnalysis)
|
||||
{
|
||||
return abort("Type is not definitely lazy arguments.");
|
||||
}
|
||||
|
||||
if (!setElemTryCache(&emitted, object, index, value) || emitted)
|
||||
return emitted;
|
||||
|
|
|
@ -33,6 +33,7 @@ OptimizationInfo::initNormalOptimizationInfo()
|
|||
rangeAnalysis_ = true;
|
||||
loopUnrolling_ = true;
|
||||
autoTruncate_ = true;
|
||||
sink_ = true;
|
||||
registerAllocator_ = RegisterAllocator_LSRA;
|
||||
|
||||
inlineMaxTotalBytecodeLength_ = 1000;
|
||||
|
@ -58,6 +59,7 @@ OptimizationInfo::initAsmjsOptimizationInfo()
|
|||
edgeCaseAnalysis_ = false;
|
||||
eliminateRedundantChecks_ = false;
|
||||
autoTruncate_ = false;
|
||||
sink_ = false;
|
||||
registerAllocator_ = RegisterAllocator_Backtracking;
|
||||
scalarReplacement_ = false; // AsmJS has no objects.
|
||||
}
|
||||
|
|
|
@ -76,6 +76,9 @@ class OptimizationInfo
|
|||
// Toggles whether Truncation based on Range Analysis is used.
|
||||
bool autoTruncate_;
|
||||
|
||||
// Toggles whether sink is used.
|
||||
bool sink_;
|
||||
|
||||
// Describes which register allocator to use.
|
||||
IonRegisterAllocator registerAllocator_;
|
||||
|
||||
|
@ -153,6 +156,10 @@ class OptimizationInfo
|
|||
return autoTruncate_ && rangeAnalysisEnabled();
|
||||
}
|
||||
|
||||
bool sinkEnabled() const {
|
||||
return sink_ && !js_JitOptions.disableSink;
|
||||
}
|
||||
|
||||
bool eaaEnabled() const {
|
||||
return eaa_ && !js_JitOptions.disableEaa;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,9 @@ JitOptions::JitOptions()
|
|||
// Toggles whether Range Analysis is globally disabled.
|
||||
SET_DEFAULT(disableRangeAnalysis, false);
|
||||
|
||||
// Toggles whether sink code motion is globally disabled.
|
||||
SET_DEFAULT(disableSink, false);
|
||||
|
||||
// Toggles whether Loop Unrolling is globally disabled.
|
||||
SET_DEFAULT(disableLoopUnrolling, true);
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ struct JitOptions
|
|||
bool disableInlining;
|
||||
bool disableEdgeCaseAnalysis;
|
||||
bool disableRangeAnalysis;
|
||||
bool disableSink;
|
||||
bool disableLoopUnrolling;
|
||||
bool disableEaa;
|
||||
bool eagerCompilation;
|
||||
|
|
|
@ -240,6 +240,7 @@ jit::CheckLogging()
|
|||
" alias Alias analysis\n"
|
||||
" gvn Global Value Numbering\n"
|
||||
" licm Loop invariant code motion\n"
|
||||
" sink Sink transformation\n"
|
||||
" regalloc Register allocation\n"
|
||||
" inline Inlining\n"
|
||||
" snapshots Snapshot information\n"
|
||||
|
@ -288,6 +289,8 @@ jit::CheckLogging()
|
|||
EnableChannel(JitSpew_Unrolling);
|
||||
if (ContainsFlag(env, "licm"))
|
||||
EnableChannel(JitSpew_LICM);
|
||||
if (ContainsFlag(env, "sink"))
|
||||
EnableChannel(JitSpew_Sink);
|
||||
if (ContainsFlag(env, "regalloc"))
|
||||
EnableChannel(JitSpew_RegAlloc);
|
||||
if (ContainsFlag(env, "inline"))
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace jit {
|
|||
_(Alias) \
|
||||
/* Information during GVN */ \
|
||||
_(GVN) \
|
||||
/* Information during sinking */ \
|
||||
_(Sink) \
|
||||
/* Information during Range analysis */ \
|
||||
_(Range) \
|
||||
/* Information during loop unrolling */ \
|
||||
|
|
|
@ -795,6 +795,27 @@ MBasicBlock::moveBefore(MInstruction *at, MInstruction *ins)
|
|||
ins->setTrackedSite(at->trackedSite());
|
||||
}
|
||||
|
||||
MInstruction *
|
||||
MBasicBlock::safeInsertTop(MDefinition *ins, IgnoreTop ignore)
|
||||
{
|
||||
// Beta nodes and interrupt checks are required to be located at the
|
||||
// beginnings of basic blocks, so we must insert new instructions after any
|
||||
// such instructions.
|
||||
MInstructionIterator insertIter = !ins || ins->isPhi()
|
||||
? begin()
|
||||
: begin(ins->toInstruction());
|
||||
while (insertIter->isBeta() ||
|
||||
insertIter->isInterruptCheck() ||
|
||||
insertIter->isInterruptCheckPar() ||
|
||||
insertIter->isConstant() ||
|
||||
(!(ignore & IgnoreRecover) && insertIter->isRecoveredOnBailout()))
|
||||
{
|
||||
insertIter++;
|
||||
}
|
||||
|
||||
return *insertIter;
|
||||
}
|
||||
|
||||
void
|
||||
MBasicBlock::discardResumePoint(MResumePoint *rp, ReferencesType refType /* = RefType_Default */)
|
||||
{
|
||||
|
|
|
@ -286,6 +286,15 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
|
|||
// Move an instruction. Movement may cross block boundaries.
|
||||
void moveBefore(MInstruction *at, MInstruction *ins);
|
||||
|
||||
enum IgnoreTop {
|
||||
IgnoreNone = 0,
|
||||
IgnoreRecover = 1 << 0
|
||||
};
|
||||
|
||||
// Locate the top of the |block|, where it is safe to insert a new
|
||||
// instruction.
|
||||
MInstruction *safeInsertTop(MDefinition *ins = nullptr, IgnoreTop ignore = IgnoreNone);
|
||||
|
||||
// Removes an instruction with the intention to discard it.
|
||||
void discard(MInstruction *ins);
|
||||
void discardLastIns();
|
||||
|
|
|
@ -2263,22 +2263,12 @@ RangeAnalysis::addRangeAssertions()
|
|||
// Beta nodes and interrupt checks are required to be located at the
|
||||
// beginnings of basic blocks, so we must insert range assertions
|
||||
// after any such instructions.
|
||||
MInstructionIterator insertIter = ins->isPhi()
|
||||
? block->begin()
|
||||
: block->begin(ins->toInstruction());
|
||||
while (insertIter->isBeta() ||
|
||||
insertIter->isInterruptCheck() ||
|
||||
insertIter->isInterruptCheckPar() ||
|
||||
insertIter->isConstant() ||
|
||||
insertIter->isRecoveredOnBailout())
|
||||
{
|
||||
insertIter++;
|
||||
}
|
||||
MInstruction *insertAt = block->safeInsertTop(ins);
|
||||
|
||||
if (*insertIter == *iter)
|
||||
block->insertAfter(*insertIter, guard);
|
||||
if (insertAt == *iter)
|
||||
block->insertAfter(insertAt, guard);
|
||||
else
|
||||
block->insertBefore(*insertIter, guard);
|
||||
block->insertBefore(insertAt, guard);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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/. */
|
||||
|
||||
#include "jit/Sink.h"
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "jit/IonAnalysis.h"
|
||||
#include "jit/JitSpewer.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGenerator.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
// Given the last found common dominator and a new definition to dominate, the
|
||||
// CommonDominator function returns the basic block which dominate the last
|
||||
// common dominator and the definition. If no such block exists, then this
|
||||
// functions return null.
|
||||
static MBasicBlock *
|
||||
CommonDominator(MBasicBlock *commonDominator, MBasicBlock *defBlock)
|
||||
{
|
||||
// This is the first instruction visited, record its basic block as being
|
||||
// the only interesting one.
|
||||
if (!commonDominator)
|
||||
return defBlock;
|
||||
|
||||
// Iterate on immediate dominators of the known common dominator to find a
|
||||
// block which dominates all previous uses as well as this instruction.
|
||||
while (!commonDominator->dominates(defBlock)) {
|
||||
MBasicBlock *nextBlock = commonDominator->immediateDominator();
|
||||
// All uses are dominated, so, this cannot happen unless the graph
|
||||
// coherency is not respected.
|
||||
MOZ_ASSERT(commonDominator != nextBlock);
|
||||
commonDominator = nextBlock;
|
||||
}
|
||||
|
||||
return commonDominator;
|
||||
}
|
||||
|
||||
bool
|
||||
Sink(MIRGenerator *mir, MIRGraph &graph)
|
||||
{
|
||||
TempAllocator &alloc = graph.alloc();
|
||||
|
||||
for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
|
||||
if (mir->shouldCancel("Sink"))
|
||||
return false;
|
||||
|
||||
for (MInstructionReverseIterator iter = block->rbegin(); iter != block->rend(); ) {
|
||||
MInstruction *ins = *iter++;
|
||||
|
||||
// Only instructions which can be recovered on bailout can be moved
|
||||
// into the bailout paths.
|
||||
if (ins->isGuard() || ins->isRecoveredOnBailout() || !ins->canRecoverOnBailout())
|
||||
continue;
|
||||
|
||||
// Compute a common dominator for all uses of the current
|
||||
// instruction.
|
||||
bool hasLiveUses = false;
|
||||
bool hasUses = false;
|
||||
MBasicBlock *usesDominator = nullptr;
|
||||
for (MUseIterator i(ins->usesBegin()), e(ins->usesEnd()); i != e; i++) {
|
||||
hasUses = true;
|
||||
MNode *consumerNode = (*i)->consumer();
|
||||
if (consumerNode->isResumePoint())
|
||||
continue;
|
||||
|
||||
MDefinition *consumer = consumerNode->toDefinition();
|
||||
if (consumer->isRecoveredOnBailout())
|
||||
continue;
|
||||
|
||||
hasLiveUses = true;
|
||||
|
||||
// If the instruction is a Phi, then we should dominate the
|
||||
// predecessor from which the value is coming from.
|
||||
MBasicBlock *consumerBlock = consumer->block();
|
||||
if (consumer->isPhi())
|
||||
consumerBlock = consumerBlock->getPredecessor(consumer->indexOf(*i));
|
||||
|
||||
usesDominator = CommonDominator(usesDominator, consumerBlock);
|
||||
if (usesDominator == *block)
|
||||
break;
|
||||
}
|
||||
|
||||
// Leave this instruction for DCE.
|
||||
if (!hasUses)
|
||||
continue;
|
||||
|
||||
// We have no uses, so sink this instruction in all the bailout
|
||||
// paths.
|
||||
if (!hasLiveUses) {
|
||||
MOZ_ASSERT(!usesDominator);
|
||||
ins->setRecoveredOnBailout();
|
||||
JitSpewDef(JitSpew_Sink, " No live uses, recover the instruction on bailout\n", ins);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If all the uses are under a loop, we might not want to work
|
||||
// against LICM by moving everything back into the loop, but if the
|
||||
// loop is it-self inside an if, then we still want to move the
|
||||
// computation under this if statement.
|
||||
while (block->loopDepth() < usesDominator->loopDepth()) {
|
||||
MOZ_ASSERT(usesDominator != usesDominator->immediateDominator());
|
||||
usesDominator = usesDominator->immediateDominator();
|
||||
}
|
||||
|
||||
// Only move instructions if there is a branch between the dominator
|
||||
// of the uses and the original instruction. This prevent moving the
|
||||
// computation of the arguments into an inline function if there is
|
||||
// no major win.
|
||||
MBasicBlock *lastJoin = usesDominator;
|
||||
while (*block != lastJoin && lastJoin->numPredecessors() == 1) {
|
||||
MOZ_ASSERT(lastJoin != lastJoin->immediateDominator());
|
||||
MBasicBlock *next = lastJoin->immediateDominator();
|
||||
if (next->numSuccessors() > 1)
|
||||
break;
|
||||
lastJoin = next;
|
||||
}
|
||||
if (*block == lastJoin)
|
||||
continue;
|
||||
|
||||
// Skip to the next instruction if we cannot find a common dominator
|
||||
// for all the uses of this instruction, or if the common dominator
|
||||
// correspond to the block of the current instruction.
|
||||
if (!usesDominator || usesDominator == *block)
|
||||
continue;
|
||||
|
||||
// Only instruction which can be recovered on bailout and which are
|
||||
// sinkable can be moved into blocks which are below while filling
|
||||
// the resume points with a clone which is recovered on bailout.
|
||||
|
||||
// If the instruction has live uses and if it is clonable, then we
|
||||
// can clone the instruction for all non-dominated uses and move the
|
||||
// instruction into the block which is dominating all live uses.
|
||||
if (!ins->canClone())
|
||||
continue;
|
||||
|
||||
JitSpewDef(JitSpew_Sink, " Can Clone & Recover, sink instruction\n", ins);
|
||||
JitSpew(JitSpew_Sink, " into Block %u", usesDominator->id());
|
||||
|
||||
// Copy the arguments and clone the instruction.
|
||||
MDefinitionVector operands(alloc);
|
||||
for (size_t i = 0, end = ins->numOperands(); i < end; i++) {
|
||||
if (!operands.append(ins->getOperand(i)))
|
||||
return false;
|
||||
}
|
||||
|
||||
MInstruction *clone = ins->clone(alloc, operands);
|
||||
ins->block()->insertBefore(ins, clone);
|
||||
clone->setRecoveredOnBailout();
|
||||
|
||||
// We should not update the producer of the entry resume point, as
|
||||
// it cannot refer to any instruction within the basic block excepts
|
||||
// for Phi nodes.
|
||||
MResumePoint *entry = usesDominator->entryResumePoint();
|
||||
|
||||
// Replace the instruction by its clone in all the resume points /
|
||||
// recovered-on-bailout instructions which are not in blocks which
|
||||
// are dominated by the usesDominator block.
|
||||
for (MUseIterator i(ins->usesBegin()), e(ins->usesEnd()); i != e; ) {
|
||||
MUse *use = *i++;
|
||||
MNode *consumer = use->consumer();
|
||||
|
||||
// If the consumer is a Phi, then we look for the index of the
|
||||
// use to find the corresponding predecessor block, which is
|
||||
// then used as the consumer block.
|
||||
MBasicBlock *consumerBlock = consumer->block();
|
||||
if (consumer->isDefinition() && consumer->toDefinition()->isPhi()) {
|
||||
consumerBlock = consumerBlock->getPredecessor(
|
||||
consumer->toDefinition()->toPhi()->indexOf(use));
|
||||
}
|
||||
|
||||
// Keep the current instruction for all dominated uses, except
|
||||
// for the entry resume point of the block in which the
|
||||
// instruction would be moved into.
|
||||
if (usesDominator->dominates(consumerBlock) &&
|
||||
(!consumer->isResumePoint() || consumer->toResumePoint() != entry))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
use->replaceProducer(clone);
|
||||
}
|
||||
|
||||
// Now, that all uses which are not dominated by usesDominator are
|
||||
// using the cloned instruction, we can safely move the instruction
|
||||
// into the usesDominator block.
|
||||
MInstruction *at = usesDominator->safeInsertTop(nullptr, MBasicBlock::IgnoreRecover);
|
||||
block->moveBefore(at, ins);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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/. */
|
||||
|
||||
// This file declares sink transformation.
|
||||
#ifndef jit_Sink_h
|
||||
#define jit_Sink_h
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
class MIRGenerator;
|
||||
class MIRGraph;
|
||||
|
||||
bool
|
||||
Sink(MIRGenerator *mir, MIRGraph &graph);
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif /* jit_Sink_h */
|
|
@ -41,6 +41,11 @@ struct PopValues
|
|||
{ }
|
||||
};
|
||||
|
||||
enum MaybeTailCall {
|
||||
TailCall,
|
||||
NonTailCall
|
||||
};
|
||||
|
||||
// Contains information about a virtual machine function that can be called
|
||||
// from JIT code. Functions described in this manner must conform to a simple
|
||||
// protocol: the return type must have a special "failure" value (for example,
|
||||
|
@ -123,6 +128,11 @@ struct VMFunction
|
|||
// wrapper.
|
||||
uint32_t extraValuesToPop;
|
||||
|
||||
// On some architectures, called functions need to explicitly push their
|
||||
// return address, for a tail call, there is nothing to push, so tail-callness
|
||||
// needs to be known at compile time.
|
||||
MaybeTailCall expectTailCall;
|
||||
|
||||
uint32_t argc() const {
|
||||
// JSContext * + args + (OutParam? *)
|
||||
return 1 + explicitArgc() + ((outParam == Type_Void) ? 0 : 1);
|
||||
|
@ -227,7 +237,8 @@ struct VMFunction
|
|||
VMFunction(void *wrapped, uint32_t explicitArgs, uint32_t argumentProperties,
|
||||
uint32_t argumentPassedInFloatRegs, uint64_t argRootTypes,
|
||||
DataType outParam, RootType outParamRootType, DataType returnType,
|
||||
ExecutionMode executionMode, uint32_t extraValuesToPop = 0)
|
||||
ExecutionMode executionMode, uint32_t extraValuesToPop = 0,
|
||||
MaybeTailCall expectTailCall = NonTailCall)
|
||||
: wrapped(wrapped),
|
||||
explicitArgs(explicitArgs),
|
||||
argumentProperties(argumentProperties),
|
||||
|
@ -237,7 +248,8 @@ struct VMFunction
|
|||
argumentRootTypes(argRootTypes),
|
||||
outParamRootType(outParamRootType),
|
||||
executionMode(executionMode),
|
||||
extraValuesToPop(extraValuesToPop)
|
||||
extraValuesToPop(extraValuesToPop),
|
||||
expectTailCall(expectTailCall)
|
||||
{
|
||||
// Check for valid failure/return type.
|
||||
MOZ_ASSERT_IF(outParam != Type_Void && executionMode == SequentialExecution,
|
||||
|
@ -503,12 +515,20 @@ template <> struct MatchContext<ThreadSafeContext *> {
|
|||
static inline uint64_t argumentRootTypes() { \
|
||||
return ForEachNb(COMPUTE_ARG_ROOT, SEP_OR, NOTHING); \
|
||||
} \
|
||||
explicit FunctionInfo(pf fun, MaybeTailCall expectTailCall, \
|
||||
PopValues extraValuesToPop = PopValues(0)) \
|
||||
: VMFunction(JS_FUNC_TO_DATA_PTR(void *, fun), explicitArgs(), \
|
||||
argumentProperties(), argumentPassedInFloatRegs(), \
|
||||
argumentRootTypes(), outParam(), outParamRootType(), \
|
||||
returnType(), executionMode(), \
|
||||
extraValuesToPop.numValues, expectTailCall) \
|
||||
{ } \
|
||||
explicit FunctionInfo(pf fun, PopValues extraValuesToPop = PopValues(0)) \
|
||||
: VMFunction(JS_FUNC_TO_DATA_PTR(void *, fun), explicitArgs(), \
|
||||
argumentProperties(), argumentPassedInFloatRegs(), \
|
||||
argumentRootTypes(), outParam(), outParamRootType(), \
|
||||
returnType(), executionMode(), \
|
||||
extraValuesToPop.numValues) \
|
||||
extraValuesToPop.numValues, NonTailCall) \
|
||||
{ }
|
||||
|
||||
template <typename Fun>
|
||||
|
@ -548,7 +568,13 @@ struct FunctionInfo<R (*)(Context)> : public VMFunction {
|
|||
: VMFunction(JS_FUNC_TO_DATA_PTR(void *, fun), explicitArgs(),
|
||||
argumentProperties(), argumentPassedInFloatRegs(),
|
||||
argumentRootTypes(), outParam(), outParamRootType(),
|
||||
returnType(), executionMode())
|
||||
returnType(), executionMode(), 0, NonTailCall)
|
||||
{ }
|
||||
explicit FunctionInfo(pf fun, MaybeTailCall expectTailCall)
|
||||
: VMFunction(JS_FUNC_TO_DATA_PTR(void *, fun), explicitArgs(),
|
||||
argumentProperties(), argumentPassedInFloatRegs(),
|
||||
argumentRootTypes(), outParam(), outParamRootType(),
|
||||
returnType(), executionMode(), 0, expectTailCall)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,9 @@ CodeGeneratorARM::generatePrologue()
|
|||
{
|
||||
MOZ_ASSERT(masm.framePushed() == 0);
|
||||
MOZ_ASSERT(!gen->compilingAsmJS());
|
||||
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
masm.pushReturnAddress();
|
||||
#endif
|
||||
// Note that this automatically sets MacroAssembler::framePushed().
|
||||
masm.reserveStack(frameSize());
|
||||
masm.checkStackAlignment();
|
||||
|
|
|
@ -3715,8 +3715,7 @@ MacroAssemblerARM::ma_callIon(const Register r)
|
|||
// When the stack is 8 byte aligned, we want to decrement sp by 8, and write
|
||||
// pc + 8 into the new sp. When we return from this call, sp will be its
|
||||
// present value minus 4.
|
||||
AutoForbidPools afp(this, 2);
|
||||
as_dtr(IsStore, 32, PreIndex, pc, DTRAddr(sp, DtrOffImm(-8)));
|
||||
as_sub(sp, sp, Imm8(4));
|
||||
as_blx(r);
|
||||
}
|
||||
void
|
||||
|
@ -3724,8 +3723,9 @@ MacroAssemblerARM::ma_callIonNoPush(const Register r)
|
|||
{
|
||||
// Since we just write the return address into the stack, which is popped on
|
||||
// return, the net effect is removing 4 bytes from the stack.
|
||||
AutoForbidPools afp(this, 2);
|
||||
as_dtr(IsStore, 32, Offset, pc, DTRAddr(sp, DtrOffImm(0)));
|
||||
|
||||
// Bug 1103108: remove this function, and refactor all uses.
|
||||
as_add(sp, sp, Imm8(4));
|
||||
as_blx(r);
|
||||
}
|
||||
|
||||
|
@ -3735,19 +3735,18 @@ MacroAssemblerARM::ma_callIonHalfPush(const Register r)
|
|||
// The stack is unaligned by 4 bytes. We push the pc to the stack to align
|
||||
// the stack before the call, when we return the pc is poped and the stack
|
||||
// is restored to its unaligned state.
|
||||
AutoForbidPools afp(this, 2);
|
||||
ma_push(pc);
|
||||
as_blx(r);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssemblerARM::ma_callIonHalfPush(Label *label)
|
||||
{
|
||||
// The stack is unaligned by 4 bytes. We push the pc to the stack to align
|
||||
// the stack before the call, when we return the pc is poped and the stack
|
||||
// The stack is unaligned by 4 bytes. The callee will push the lr to the stack to align
|
||||
// the stack after the call, when we return the pc is poped and the stack
|
||||
// is restored to its unaligned state.
|
||||
AutoForbidPools afp(this, 2);
|
||||
ma_push(pc);
|
||||
|
||||
// leave the stack as-is so the callee-side can push when necessary.
|
||||
|
||||
as_bl(label, Always);
|
||||
}
|
||||
|
||||
|
|
|
@ -1833,6 +1833,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
|
|||
void loadAsmJSHeapRegisterFromGlobalData() {
|
||||
loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
|
||||
}
|
||||
void pushReturnAddress() {
|
||||
push(lr);
|
||||
}
|
||||
};
|
||||
|
||||
typedef MacroAssemblerARMCompat MacroAssemblerSpecific;
|
||||
|
|
|
@ -419,6 +419,8 @@ JitCode *
|
|||
JitRuntime::generateArgumentsRectifier(JSContext *cx, ExecutionMode mode, void **returnAddrOut)
|
||||
{
|
||||
MacroAssembler masm(cx);
|
||||
masm.pushReturnAddress();
|
||||
|
||||
// ArgumentsRectifierReg contains the |nargs| pushed onto the current frame.
|
||||
// Including |this|, there are (|nargs| + 1) arguments to copy.
|
||||
MOZ_ASSERT(ArgumentsRectifierReg == r8);
|
||||
|
@ -747,6 +749,10 @@ JitRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
|
|||
// +0 ExitFrame
|
||||
//
|
||||
// We're aligned to an exit frame, so link it up.
|
||||
// If it isn't a tail call, then the return address needs to be saved
|
||||
if (f.expectTailCall == NonTailCall)
|
||||
masm.pushReturnAddress();
|
||||
|
||||
masm.enterExitFrameAndLoadContext(&f, cxreg, regs.getAny(), f.executionMode);
|
||||
|
||||
// Save the base of the argument set stored on the stack.
|
||||
|
@ -914,6 +920,7 @@ JitRuntime::generatePreBarrier(JSContext *cx, MIRType type)
|
|||
save = RegisterSet(GeneralRegisterSet(Registers::VolatileMask),
|
||||
FloatRegisterSet());
|
||||
}
|
||||
save.add(lr);
|
||||
masm.PushRegsInMask(save);
|
||||
|
||||
MOZ_ASSERT(PreBarrierReg == r1);
|
||||
|
@ -923,9 +930,9 @@ JitRuntime::generatePreBarrier(JSContext *cx, MIRType type)
|
|||
masm.passABIArg(r0);
|
||||
masm.passABIArg(r1);
|
||||
masm.callWithABI(IonMarkFunction(type));
|
||||
|
||||
save.take(AnyRegister(lr));
|
||||
save.add(pc);
|
||||
masm.PopRegsInMask(save);
|
||||
masm.ret();
|
||||
|
||||
Linker linker(masm);
|
||||
AutoFlushICache afc("PreBarrier");
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
#include "jit/RegisterSets.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
|
||||
#if defined(JS_CODEGEN_ARM)
|
||||
#define JS_USE_LINK_REGISTER
|
||||
#endif
|
||||
|
||||
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
|
||||
// JS_SMALL_BRANCH means the range on a branch instruction
|
||||
// is smaller than the whole address space
|
||||
|
|
|
@ -90,7 +90,7 @@ BaselineCompilerShared::callVM(const VMFunction &fun, CallVMPhase phase)
|
|||
masm.makeFrameDescriptor(BaselineTailCallReg, JitFrame_BaselineJS);
|
||||
masm.push(BaselineTailCallReg);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(fun.expectTailCall == NonTailCall);
|
||||
// Perform the call.
|
||||
masm.call(code);
|
||||
uint32_t callOffset = masm.currentOffset();
|
||||
|
|
|
@ -2677,6 +2677,8 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins)
|
|||
bool
|
||||
CodeGeneratorX86Shared::visitSimdBinaryCompIx4(LSimdBinaryCompIx4 *ins)
|
||||
{
|
||||
static const SimdConstant allOnes = SimdConstant::SplatX4(-1);
|
||||
|
||||
FloatRegister lhs = ToFloatRegister(ins->lhs());
|
||||
Operand rhs = ToOperand(ins->rhs());
|
||||
MOZ_ASSERT(ToFloatRegister(ins->output()) == lhs);
|
||||
|
@ -2690,22 +2692,41 @@ CodeGeneratorX86Shared::visitSimdBinaryCompIx4(LSimdBinaryCompIx4 *ins)
|
|||
masm.packedEqualInt32x4(rhs, lhs);
|
||||
return true;
|
||||
case MSimdBinaryComp::lessThan:
|
||||
// scr := rhs
|
||||
// src := rhs
|
||||
if (rhs.kind() == Operand::FPREG)
|
||||
masm.moveAlignedInt32x4(ToFloatRegister(ins->rhs()), ScratchSimdReg);
|
||||
else
|
||||
masm.loadAlignedInt32x4(rhs, ScratchSimdReg);
|
||||
|
||||
// scr := scr > lhs (i.e. lhs < rhs)
|
||||
// src := src > lhs (i.e. lhs < rhs)
|
||||
// Improve by doing custom lowering (rhs is tied to the output register)
|
||||
masm.packedGreaterThanInt32x4(ToOperand(ins->lhs()), ScratchSimdReg);
|
||||
masm.moveAlignedInt32x4(ScratchSimdReg, lhs);
|
||||
return true;
|
||||
case MSimdBinaryComp::notEqual:
|
||||
// Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we
|
||||
// should invert the comparison by, e.g. swapping the arms of a select
|
||||
// if that's what it's used in.
|
||||
masm.loadConstantInt32x4(allOnes, ScratchSimdReg);
|
||||
masm.packedEqualInt32x4(rhs, lhs);
|
||||
masm.bitwiseXorX4(Operand(ScratchSimdReg), lhs);
|
||||
return true;
|
||||
case MSimdBinaryComp::greaterThanOrEqual:
|
||||
// src := rhs
|
||||
if (rhs.kind() == Operand::FPREG)
|
||||
masm.moveAlignedInt32x4(ToFloatRegister(ins->rhs()), ScratchSimdReg);
|
||||
else
|
||||
masm.loadAlignedInt32x4(rhs, ScratchSimdReg);
|
||||
masm.packedGreaterThanInt32x4(ToOperand(ins->lhs()), ScratchSimdReg);
|
||||
masm.loadConstantInt32x4(allOnes, lhs);
|
||||
masm.bitwiseXorX4(Operand(ScratchSimdReg), lhs);
|
||||
return true;
|
||||
case MSimdBinaryComp::lessThanOrEqual:
|
||||
// These operations are not part of the spec. so are not implemented.
|
||||
break;
|
||||
// lhs <= rhs is equivalent to !(rhs < lhs), which we compute here.
|
||||
masm.loadConstantInt32x4(allOnes, ScratchSimdReg);
|
||||
masm.packedGreaterThanInt32x4(rhs, lhs);
|
||||
masm.bitwiseXorX4(Operand(ScratchSimdReg), lhs);
|
||||
return true;
|
||||
}
|
||||
MOZ_CRASH("unexpected SIMD op");
|
||||
}
|
||||
|
|
|
@ -2732,9 +2732,15 @@ GCRuntime::unprotectRelocatedArenas(ArenaHeader *relocatedList)
|
|||
void
|
||||
GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList)
|
||||
{
|
||||
// Release the relocated arenas, now containing only forwarding pointers
|
||||
AutoLockGC lock(rt);
|
||||
releaseRelocatedArenasWithoutUnlocking(relocatedList, lock);
|
||||
expireChunksAndArenas(true, lock);
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::releaseRelocatedArenasWithoutUnlocking(ArenaHeader *relocatedList, const AutoLockGC &lock)
|
||||
{
|
||||
// Release the relocated arenas, now containing only forwarding pointers
|
||||
unsigned count = 0;
|
||||
while (relocatedList) {
|
||||
ArenaHeader *aheader = relocatedList;
|
||||
|
@ -2759,8 +2765,6 @@ GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList)
|
|||
releaseArena(aheader, lock);
|
||||
++count;
|
||||
}
|
||||
|
||||
expireChunksAndArenas(true, lock);
|
||||
}
|
||||
|
||||
#endif // JSGC_COMPACTING
|
||||
|
@ -6406,6 +6410,13 @@ GCRuntime::onOutOfMallocMemory(const AutoLockGC &lock)
|
|||
// Throw away any excess chunks we have lying around.
|
||||
freeEmptyChunks(rt, lock);
|
||||
|
||||
// Release any relocated areans we may be holding on to.
|
||||
#if defined(JSGC_COMPACTING) && defined(DEBUG)
|
||||
unprotectRelocatedArenas(relocatedArenasToRelease);
|
||||
releaseRelocatedArenasWithoutUnlocking(relocatedArenasToRelease, lock);
|
||||
relocatedArenasToRelease = nullptr;
|
||||
#endif
|
||||
|
||||
// Immediately decommit as many arenas as possible in the hopes that this
|
||||
// might let the OS scrape together enough pages to satisfy the failing
|
||||
// malloc request.
|
||||
|
|
|
@ -190,6 +190,7 @@ UNIFIED_SOURCES += [
|
|||
'jit/shared/BaselineCompiler-shared.cpp',
|
||||
'jit/shared/CodeGenerator-shared.cpp',
|
||||
'jit/shared/Lowering-shared.cpp',
|
||||
'jit/Sink.cpp',
|
||||
'jit/Snapshots.cpp',
|
||||
'jit/StupidAllocator.cpp',
|
||||
'jit/TypedObjectPrediction.cpp',
|
||||
|
|
|
@ -5500,6 +5500,15 @@ SetRuntimeOptions(JSRuntime *rt, const OptionParser &op)
|
|||
return OptionFailure("ion-range-analysis", str);
|
||||
}
|
||||
|
||||
if (const char *str = op.getStringOption("ion-sink")) {
|
||||
if (strcmp(str, "on") == 0)
|
||||
jit::js_JitOptions.disableSink = false;
|
||||
else if (strcmp(str, "off") == 0)
|
||||
jit::js_JitOptions.disableSink = true;
|
||||
else
|
||||
return OptionFailure("ion-sink", str);
|
||||
}
|
||||
|
||||
if (const char *str = op.getStringOption("ion-loop-unrolling")) {
|
||||
if (strcmp(str, "on") == 0)
|
||||
jit::js_JitOptions.disableLoopUnrolling = false;
|
||||
|
@ -5797,6 +5806,8 @@ main(int argc, char **argv, char **envp)
|
|||
"Find edge cases where Ion can avoid bailouts (default: on, off to disable)")
|
||||
|| !op.addStringOption('\0', "ion-range-analysis", "on/off",
|
||||
"Range analysis (default: on, off to disable)")
|
||||
|| !op.addStringOption('\0', "ion-sink", "on/off",
|
||||
"Sink code motion (default: on, off to disable)")
|
||||
|| !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
|
||||
"Loop unrolling (default: off, on to enable)")
|
||||
|| !op.addBoolOption('\0', "ion-check-range-analysis",
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче