Merge m-c to b2g-inbound a=merge

This commit is contained in:
Wes Kocher 2014-11-24 17:11:37 -08:00
commit 4b5a807a6e
280 changed files with 5097 additions and 3523 deletions

View File

@ -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

View File

@ -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/");

View File

@ -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);

View File

@ -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

View File

@ -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>

View File

@ -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,

View File

@ -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}

View File

@ -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)}
)
);

View File

@ -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)}
/>
);

View File

@ -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"})
)
)
);

View File

@ -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>
);

View File

@ -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 {

View File

@ -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 {

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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
})
};
})();

View File

@ -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 || "")));
});
}
};

View File

@ -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;
})();

View File

@ -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);

View File

@ -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}>
&laquo;&nbsp;{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);

View File

@ -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
};

View File

@ -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}>
&laquo;&nbsp;{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
};

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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"})
)
)
);
}

View File

@ -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>
);
}

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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"));
}

View File

@ -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'",

View File

@ -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");
});

View File

@ -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>

View File

@ -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();

View File

@ -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));
});
});
});

View File

@ -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);
});
});
});

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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(){}
})
);

View File

@ -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>

View File

@ -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})
)
)

View File

@ -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>

View File

@ -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() {

View File

@ -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() {

View File

@ -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);

View File

@ -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

View File

@ -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
######################################

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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)

View File

@ -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"; });

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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>

View File

@ -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;

View File

@ -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,

View File

@ -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).
}
};

View File

@ -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) {

View File

@ -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 */

View File

@ -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; }
};

View File

@ -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) \

View File

@ -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

View File

@ -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);

View File

@ -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;';

View File

@ -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);
}

View File

@ -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()

View File

@ -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)

View File

@ -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);

View File

@ -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.
{

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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.
}

View File

@ -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;
}

View File

@ -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);

View File

@ -38,6 +38,7 @@ struct JitOptions
bool disableInlining;
bool disableEdgeCaseAnalysis;
bool disableRangeAnalysis;
bool disableSink;
bool disableLoopUnrolling;
bool disableEaa;
bool eagerCompilation;

View File

@ -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"))

View File

@ -26,6 +26,8 @@ namespace jit {
_(Alias) \
/* Information during GVN */ \
_(GVN) \
/* Information during sinking */ \
_(Sink) \
/* Information during Range analysis */ \
_(Range) \
/* Information during loop unrolling */ \

View File

@ -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 */)
{

View File

@ -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();

View File

@ -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);
}
}

202
js/src/jit/Sink.cpp Normal file
View File

@ -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;
}
}
}

23
js/src/jit/Sink.h Normal file
View File

@ -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 */

View File

@ -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)
{ }
};

View File

@ -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();

View File

@ -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);
}

View File

@ -1833,6 +1833,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
void loadAsmJSHeapRegisterFromGlobalData() {
loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
}
void pushReturnAddress() {
push(lr);
}
};
typedef MacroAssemblerARMCompat MacroAssemblerSpecific;

View File

@ -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");

View File

@ -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

View File

@ -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();

View File

@ -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");
}

View File

@ -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.

View File

@ -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',

View File

@ -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",

Some files were not shown because too many files have changed in this diff Show More