diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp index b3981384948..a56d9ccdde1 100644 --- a/browser/app/nsBrowserApp.cpp +++ b/browser/app/nsBrowserApp.cpp @@ -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 diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 82fc33fc4d0..137d8c92721 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -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/"); diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js index b367be251ec..d12a6c16afb 100644 --- a/browser/base/content/aboutTabCrashed.js +++ b/browser/base/content/aboutTabCrashed.js @@ -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); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 9951f9bb875..6b6fa937460 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -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 diff --git a/browser/components/loop/content/conversation.html b/browser/components/loop/content/conversation.html index 3f9145c9971..9e2255f799b 100644 --- a/browser/components/loop/content/conversation.html +++ b/browser/components/loop/content/conversation.html @@ -38,6 +38,8 @@ + + diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index 120f07a3610..ecc956c3555 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -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, diff --git a/browser/components/loop/content/js/conversation.jsx b/browser/components/loop/content/js/conversation.jsx index 42fa265669b..ba48d1a3dc8 100644 --- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -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 ( ); @@ -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 (); } case "room": { return (); } case "failed": { - return (); + return ; } 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( ); diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index c1ec7f1a65e..49ea8297ada 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -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"}) ) ) ); diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index f4748d847bf..27f1b338beb 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -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"} /> + ); diff --git a/browser/components/loop/content/shared/css/conversation.css b/browser/components/loop/content/shared/css/conversation.css index f48f534fd58..ab1790bfdd8 100644 --- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -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 { diff --git a/browser/components/loop/content/shared/css/panel.css b/browser/components/loop/content/shared/css/panel.css index 342ae74e3c5..a613d71c6eb 100644 --- a/browser/components/loop/content/shared/css/panel.css +++ b/browser/components/loop/content/shared/css/panel.css @@ -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 { diff --git a/browser/components/loop/content/shared/img/svg/glyph-help-16x16.svg b/browser/components/loop/content/shared/img/svg/glyph-help-16x16.svg new file mode 100644 index 00000000000..ae99e22b42f --- /dev/null +++ b/browser/components/loop/content/shared/img/svg/glyph-help-16x16.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/browser/components/loop/content/shared/js/actions.js b/browser/components/loop/content/shared/js/actions.js index c4d5234d57a..59361266685 100644 --- a/browser/components/loop/content/shared/js/actions.js +++ b/browser/components/loop/content/shared/js/actions.js @@ -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 }) }; })(); diff --git a/browser/components/loop/content/shared/js/feedbackApiClient.js b/browser/components/loop/content/shared/js/feedbackApiClient.js index e2aca70546b..4742cb4f9cc 100644 --- a/browser/components/loop/content/shared/js/feedbackApiClient.js +++ b/browser/components/loop/content/shared/js/feedbackApiClient.js @@ -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 || ""))); }); } }; diff --git a/browser/components/loop/content/shared/js/feedbackStore.js b/browser/components/loop/content/shared/js/feedbackStore.js new file mode 100644 index 00000000000..a5b78e390de --- /dev/null +++ b/browser/components/loop/content/shared/js/feedbackStore.js @@ -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; +})(); diff --git a/browser/components/loop/content/shared/js/feedbackViews.js b/browser/components/loop/content/shared/js/feedbackViews.js new file mode 100644 index 00000000000..77859812e8d --- /dev/null +++ b/browser/components/loop/content/shared/js/feedbackViews.js @@ -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); diff --git a/browser/components/loop/content/shared/js/feedbackViews.jsx b/browser/components/loop/content/shared/js/feedbackViews.jsx new file mode 100644 index 00000000000..534126b56d8 --- /dev/null +++ b/browser/components/loop/content/shared/js/feedbackViews.jsx @@ -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 =
; + if (this.props.reset) { + backButton = ( + + ); + } + return ( +
+ {backButton} +

{this.props.title}

+ {this.props.children} +
+ ); + } + }); + + /** + * 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 ( + + ); + }, 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 ( + +
+ {this._getCategoryFields()} +

+ +

+ +
+
+ ); + } + }); + + /** + * 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 ( + +

{ + l10n.get("feedback_window_will_close_in2", { + countdown: this.state.countdown, + num: this.state.countdown + })}

+
+ ); + } + }); + + /** + * 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 ( + +
+ + +
+
+ ); + } + case FEEDBACK_STATES.DETAILS: { + return ( + + ); + } + 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 ( + + ); + } + } + } + }); + + return FeedbackView; +})(navigator.mozL10n || document.mozL10n); diff --git a/browser/components/loop/content/shared/js/views.js b/browser/components/loop/content/shared/js/views.js index 4f19fda89ea..1b7bcc81f1e 100644 --- a/browser/components/loop/content/shared/js/views.js +++ b/browser/components/loop/content/shared/js/views.js @@ -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 }; diff --git a/browser/components/loop/content/shared/js/views.jsx b/browser/components/loop/content/shared/js/views.jsx index ec8d400cf75..62a0324c504 100644 --- a/browser/components/loop/content/shared/js/views.jsx +++ b/browser/components/loop/content/shared/js/views.jsx @@ -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 =
; - if (this.props.reset) { - backButton = ( - - ); - } - return ( -
- {backButton} -

{this.props.title}

- {this.props.children} -
- ); - } - }); - - /** - * 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 ( - - ); - }, 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 ( - -
- {this._getCategoryFields()} -

- -

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

{ - l10n.get("feedback_window_will_close_in2", { - countdown: this.state.countdown, - num: this.state.countdown - })}

-
- ); - } - }); - - /** - * 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 ( - - ); - case "form": - return ; - default: - return ( - -
- - -
-
- ); - } - } - }); - /** * Notification view. */ @@ -743,7 +460,7 @@ loop.shared.views = (function(_, OT, l10n) { {this.props.caption} {this.props.children} - ) + ); } }); @@ -768,7 +485,7 @@ loop.shared.views = (function(_, OT, l10n) {
{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 }; diff --git a/browser/components/loop/jar.mn b/browser/components/loop/jar.mn index 989e1415efc..01425e94c65 100644 --- a/browser/components/loop/jar.mn +++ b/browser/components/loop/jar.mn @@ -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) diff --git a/browser/components/loop/standalone/Makefile b/browser/components/loop/standalone/Makefile index 4889798ac57..e9e6fdc72e8 100644 --- a/browser/components/loop/standalone/Makefile +++ b/browser/components/loop/standalone/Makefile @@ -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 diff --git a/browser/components/loop/standalone/content/css/webapp.css b/browser/components/loop/standalone/content/css/webapp.css index 26954f70d39..1de24b8a7c2 100644 --- a/browser/components/loop/standalone/content/css/webapp.css +++ b/browser/components/loop/standalone/content/css/webapp.css @@ -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; diff --git a/browser/components/loop/standalone/content/index.html b/browser/components/loop/standalone/content/index.html index f162ed07a3c..c9f8aaf0746 100644 --- a/browser/components/loop/standalone/content/index.html +++ b/browser/components/loop/standalone/content/index.html @@ -99,6 +99,8 @@ + + diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.js b/browser/components/loop/standalone/content/js/standaloneRoomViews.js index 1972fbfa529..58e72afc1a3 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js @@ -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"}) + ) ) ); } diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx index 98586371ec2..ef4b4293bbe 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx @@ -108,6 +108,9 @@ loop.standaloneRoomViews = (function(mozL10n) { return (

{mozL10n.get("clientShortname2")}

+ + +
); } diff --git a/browser/components/loop/standalone/content/js/webapp.js b/browser/components/loop/standalone/content/js/webapp.js index ada6950aeaa..e7da48238a8 100644 --- a/browser/components/loop/standalone/content/js/webapp.js +++ b/browser/components/loop/standalone/content/js/webapp.js @@ -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} diff --git a/browser/components/loop/standalone/content/js/webapp.jsx b/browser/components/loop/standalone/content/js/webapp.jsx index aa4908db64b..4756e49289e 100644 --- a/browser/components/loop/standalone/content/js/webapp.jsx +++ b/browser/components/loop/standalone/content/js/webapp.jsx @@ -260,6 +260,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
+
); } @@ -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 (
); @@ -887,14 +892,14 @@ loop.webapp = (function($, _, OT, mozL10n) { notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection) .isRequired, sdk: React.PropTypes.object.isRequired, - feedbackApiClient: React.PropTypes.object.isRequired, // XXX New types for flux style standaloneAppStore: React.PropTypes.instanceOf( loop.store.StandaloneAppStore).isRequired, activeRoomStore: React.PropTypes.instanceOf( loop.store.ActiveRoomStore).isRequired, - dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired + dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, + feedbackStore: React.PropTypes.instanceOf(loop.store.FeedbackStore) }, getInitialState: function() { @@ -931,7 +936,7 @@ loop.webapp = (function($, _, OT, mozL10n) { helper={this.props.helper} notifications={this.props.notifications} sdk={this.props.sdk} - feedbackApiClient={this.props.feedbackApiClient} + feedbackStore={this.props.feedbackStore} /> ); } @@ -992,7 +997,14 @@ loop.webapp = (function($, _, OT, mozL10n) { dispatcher: dispatcher, sdk: OT }); + var feedbackClient = new loop.FeedbackAPIClient( + loop.config.feedbackApiUrl, { + product: loop.config.feedbackProductName, + user_agent: navigator.userAgent, + url: document.location.origin + }); + // Stores var standaloneAppStore = new loop.store.StandaloneAppStore({ conversation: conversation, dispatcher: dispatcher, @@ -1003,6 +1015,9 @@ loop.webapp = (function($, _, OT, mozL10n) { mozLoop: standaloneMozLoop, sdkDriver: sdkDriver }); + var feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: feedbackClient + }); window.addEventListener("unload", function() { dispatcher.dispatch(new sharedActions.WindowUnload()); @@ -1014,7 +1029,7 @@ loop.webapp = (function($, _, OT, mozL10n) { helper={helper} notifications={notifications} sdk={OT} - feedbackApiClient={feedbackApiClient} + feedbackStore={feedbackStore} standaloneAppStore={standaloneAppStore} activeRoomStore={activeRoomStore} dispatcher={dispatcher} diff --git a/browser/components/loop/standalone/content/l10n/en-US/loop.properties b/browser/components/loop/standalone/content/l10n/en-US/loop.properties index 86ed08c1a43..47f98f1c616 100644 --- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties +++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties @@ -124,4 +124,4 @@ standalone_title_with_status={{clientShortname}} — {{currentStatus}} status_in_conversation=In conversation status_conversation_ended=Conversation ended status_error=Something went wrong - +support_link=Get Help diff --git a/browser/components/loop/standalone/server.js b/browser/components/loop/standalone/server.js index ac19b4f2e39..0893f3c5e94 100644 --- a/browser/components/loop/standalone/server.js +++ b/browser/components/loop/standalone/server.js @@ -30,7 +30,9 @@ function getConfigFile(req, res) { "loop.config.legalWebsiteUrl = '/legal/terms';", "loop.config.fxosApp = loop.config.fxosApp || {};", "loop.config.fxosApp.name = 'Loop';", - "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" + "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';", + "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';", + "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" ].join("\n")); } diff --git a/browser/components/loop/test/desktop-local/conversationViews_test.js b/browser/components/loop/test/desktop-local/conversationViews_test.js index 90d431c94b2..c8b190ebe27 100644 --- a/browser/components/loop/test/desktop-local/conversationViews_test.js +++ b/browser/components/loop/test/desktop-local/conversationViews_test.js @@ -445,13 +445,14 @@ describe("loop.conversationViews", function () { }); describe("OutgoingConversationView", function() { - var store; + var store, feedbackStore; function mountTestComponent() { return TestUtils.renderIntoDocument( loop.conversationViews.OutgoingConversationView({ dispatcher: dispatcher, - store: store + store: store, + feedbackStore: feedbackStore })); } @@ -461,6 +462,9 @@ describe("loop.conversationViews", function () { client: {}, sdkDriver: {} }); + feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: {} + }); }); it("should render the CallFailedView when the call state is 'terminated'", diff --git a/browser/components/loop/test/desktop-local/conversation_test.js b/browser/components/loop/test/desktop-local/conversation_test.js index 7bbc8d63a16..524a657afc2 100644 --- a/browser/components/loop/test/desktop-local/conversation_test.js +++ b/browser/components/loop/test/desktop-local/conversation_test.js @@ -233,7 +233,8 @@ describe("loop.conversation", function() { }); describe("IncomingConversationView", function() { - var conversationAppStore, conversation, client, icView, oldTitle; + var conversationAppStore, conversation, client, icView, oldTitle, + feedbackStore; function mountTestComponent() { return TestUtils.renderIntoDocument( @@ -241,7 +242,8 @@ describe("loop.conversation", function() { client: client, conversation: conversation, sdk: {}, - conversationAppStore: conversationAppStore + conversationAppStore: conversationAppStore, + feedbackStore: feedbackStore })); } @@ -257,6 +259,9 @@ describe("loop.conversation", function() { dispatcher: dispatcher, mozLoop: navigator.mozLoop }); + feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: {} + }); sandbox.stub(conversation, "setOutgoingSessionData"); }); diff --git a/browser/components/loop/test/desktop-local/index.html b/browser/components/loop/test/desktop-local/index.html index ec206b20fa3..5c64e30752c 100644 --- a/browser/components/loop/test/desktop-local/index.html +++ b/browser/components/loop/test/desktop-local/index.html @@ -46,6 +46,8 @@ + + diff --git a/browser/components/loop/test/desktop-local/panel_test.js b/browser/components/loop/test/desktop-local/panel_test.js index 7e051a7e172..e678c4a9405 100644 --- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -356,6 +356,31 @@ describe("loop.panel", function() { }); }); + describe("Help", function() { + var supportUrl = "https://example.com"; + + beforeEach(function() { + navigator.mozLoop.getLoopPref = function(pref) { + if (pref === "support_url") + return supportUrl; + return "unseen"; + }; + + sandbox.stub(window, "open"); + sandbox.stub(window, "close"); + }); + + it("should open a tab to the support page", function() { + var view = TestUtils.renderIntoDocument(loop.panel.SettingsDropdown()); + + TestUtils.Simulate + .click(view.getDOMNode().querySelector(".icon-help")); + + sinon.assert.calledOnce(window.open); + sinon.assert.calledWithExactly(window.open, supportUrl); + }); + }); + describe("#render", function() { it("should render a ToSView", function() { var view = createTestPanelView(); diff --git a/browser/components/loop/test/shared/feedbackStore_test.js b/browser/components/loop/test/shared/feedbackStore_test.js new file mode 100644 index 00000000000..8213189842a --- /dev/null +++ b/browser/components/loop/test/shared/feedbackStore_test.js @@ -0,0 +1,108 @@ +/* global chai, loop */ + +var expect = chai.expect; +var sharedActions = loop.shared.actions; + +describe("loop.store.FeedbackStore", function () { + "use strict"; + + var FEEDBACK_STATES = loop.store.FEEDBACK_STATES; + var sandbox, dispatcher, store, feedbackClient; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + dispatcher = new loop.Dispatcher(); + + feedbackClient = new loop.FeedbackAPIClient("http://invalid", { + product: "Loop" + }); + + store = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: feedbackClient + }); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe("#constructor", function() { + it("should throw an error if feedbackClient is missing", function() { + expect(function() { + new loop.store.FeedbackStore(dispatcher); + }).to.Throw(/feedbackClient/); + }); + + it("should set the store to the INIT feedback state", function() { + var store = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: feedbackClient + }); + + expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.INIT); + }); + }); + + describe("#requireFeedbackDetails", function() { + it("should transition to DETAILS state", function() { + store.requireFeedbackDetails(new sharedActions.RequireFeedbackDetails()); + + expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.DETAILS); + }); + }); + + describe("#sendFeedback", function() { + var sadFeedbackData = { + happy: false, + category: "fakeCategory", + description: "fakeDescription" + }; + + beforeEach(function() { + store.requireFeedbackDetails(); + }); + + it("should send feedback data over the feedback client", function() { + sandbox.stub(feedbackClient, "send"); + + store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData)); + + sinon.assert.calledOnce(feedbackClient.send); + sinon.assert.calledWithMatch(feedbackClient.send, sadFeedbackData); + }); + + it("should transition to PENDING state", function() { + sandbox.stub(feedbackClient, "send"); + + store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData)); + + expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.PENDING); + }); + + it("should transition to SENT state on successful submission", function(done) { + sandbox.stub(feedbackClient, "send", function(data, cb) { + cb(null); + }); + + store.once("change:feedbackState", function() { + expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.SENT); + done(); + }); + + store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData)); + }); + + it("should transition to FAILED state on failed submission", function(done) { + sandbox.stub(feedbackClient, "send", function(data, cb) { + cb(new Error("failed")); + }); + + store.once("change:feedbackState", function() { + expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.FAILED); + done(); + }); + + store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData)); + }); + }); +}); diff --git a/browser/components/loop/test/shared/feedbackViews_test.js b/browser/components/loop/test/shared/feedbackViews_test.js new file mode 100644 index 00000000000..052149bfdef --- /dev/null +++ b/browser/components/loop/test/shared/feedbackViews_test.js @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*global loop, sinon, React */ +/* jshint newcap:false */ + +var expect = chai.expect; +var l10n = navigator.mozL10n || document.mozL10n; +var TestUtils = React.addons.TestUtils; +var sharedActions = loop.shared.actions; +var sharedViews = loop.shared.views; + +var FEEDBACK_STATES = loop.store.FEEDBACK_STATES; + +describe("loop.shared.views.FeedbackView", function() { + "use strict"; + + var sandbox, comp, dispatcher, feedbackStore, fakeAudioXHR, fakeFeedbackClient; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + fakeAudioXHR = { + open: sinon.spy(), + send: function() {}, + abort: function() {}, + getResponseHeader: function(header) { + if (header === "Content-Type") + return "audio/ogg"; + }, + responseType: null, + response: new ArrayBuffer(10), + onload: null + }; + dispatcher = new loop.Dispatcher(); + fakeFeedbackClient = {send: sandbox.stub()}; + feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: fakeFeedbackClient + }); + sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR); + comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({ + feedbackStore: feedbackStore + })); + }); + + afterEach(function() { + sandbox.restore(); + }); + + // local test helpers + function clickHappyFace(comp) { + var happyFace = comp.getDOMNode().querySelector(".face-happy"); + TestUtils.Simulate.click(happyFace); + } + + function clickSadFace(comp) { + var sadFace = comp.getDOMNode().querySelector(".face-sad"); + TestUtils.Simulate.click(sadFace); + } + + function fillSadFeedbackForm(comp, category, text) { + TestUtils.Simulate.change( + comp.getDOMNode().querySelector("[value='" + category + "']")); + + if (text) { + TestUtils.Simulate.change( + comp.getDOMNode().querySelector("[name='description']"), { + target: {value: "fake reason"} + }); + } + } + + function submitSadFeedbackForm(comp, category, text) { + TestUtils.Simulate.submit(comp.getDOMNode().querySelector("form")); + } + + describe("Happy feedback", function() { + it("should dispatch a SendFeedback action", function() { + var dispatch = sandbox.stub(dispatcher, "dispatch"); + + clickHappyFace(comp); + + sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({ + happy: true, + category: "", + description: "" + })); + }); + + it("should thank the user once feedback data is sent", function() { + feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT}); + + expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null); + expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back")) + .eql(null); + }); + }); + + describe("Sad feedback", function() { + it("should bring the user to feedback form when clicking on the sad face", + function() { + clickSadFace(comp); + + expect(comp.getDOMNode().querySelectorAll("form")).not.eql(null); + }); + + it("should render a back button", function() { + feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS}); + + expect(comp.getDOMNode().querySelector("button.fx-embedded-btn-back")) + .not.eql(null); + }); + + it("should reset the view when clicking the back button", function() { + feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS}); + + TestUtils.Simulate.click( + comp.getDOMNode().querySelector("button.fx-embedded-btn-back")); + + expect(comp.getDOMNode().querySelector(".faces")).not.eql(null); + }); + + it("should disable the form submit button when no category is chosen", + function() { + clickSadFace(comp); + + expect(comp.getDOMNode().querySelector("form button").disabled).eql(true); + }); + + it("should disable the form submit button when the 'other' category is " + + "chosen but no description has been entered yet", + function() { + clickSadFace(comp); + fillSadFeedbackForm(comp, "other"); + + expect(comp.getDOMNode().querySelector("form button").disabled).eql(true); + }); + + it("should enable the form submit button when the 'other' category is " + + "chosen and a description is entered", + function() { + clickSadFace(comp); + fillSadFeedbackForm(comp, "other", "fake"); + + expect(comp.getDOMNode().querySelector("form button").disabled).eql(false); + }); + + it("should empty the description field when a predefined category is " + + "chosen", + function() { + clickSadFace(comp); + + fillSadFeedbackForm(comp, "confusing"); + + expect(comp.getDOMNode().querySelector(".feedback-description").value).eql(""); + }); + + it("should enable the form submit button once a predefined category is " + + "chosen", + function() { + clickSadFace(comp); + + fillSadFeedbackForm(comp, "confusing"); + + expect(comp.getDOMNode().querySelector("form button").disabled).eql(false); + }); + + it("should send feedback data when the form is submitted", function() { + var dispatch = sandbox.stub(dispatcher, "dispatch"); + feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.DETAILS}); + fillSadFeedbackForm(comp, "confusing"); + + submitSadFeedbackForm(comp); + + sinon.assert.calledOnce(dispatch); + sinon.assert.calledWithMatch(dispatch, new sharedActions.SendFeedback({ + happy: false, + category: "confusing", + description: "" + })); + }); + + it("should send feedback data when user has entered a custom description", + function() { + clickSadFace(comp); + + fillSadFeedbackForm(comp, "other", "fake reason"); + submitSadFeedbackForm(comp); + + sinon.assert.calledOnce(fakeFeedbackClient.send); + sinon.assert.calledWith(fakeFeedbackClient.send, { + happy: false, + category: "other", + description: "fake reason" + }); + }); + + it("should thank the user when feedback data has been sent", function() { + fakeFeedbackClient.send = function(data, cb) { + cb(); + }; + clickSadFace(comp); + fillSadFeedbackForm(comp, "confusing"); + submitSadFeedbackForm(comp); + + expect(comp.getDOMNode().querySelectorAll(".thank-you")).not.eql(null); + }); + }); +}); diff --git a/browser/components/loop/test/shared/index.html b/browser/components/loop/test/shared/index.html index 8d697a75cb4..2e1b8cb11ed 100644 --- a/browser/components/loop/test/shared/index.html +++ b/browser/components/loop/test/shared/index.html @@ -47,6 +47,8 @@ + + @@ -55,10 +57,12 @@ + + diff --git a/browser/components/loop/test/shared/views_test.js b/browser/components/loop/test/shared/views_test.js index fb72e059cf3..ede4b97e75d 100644 --- a/browser/components/loop/test/shared/views_test.js +++ b/browser/components/loop/test/shared/views_test.js @@ -526,177 +526,6 @@ describe("loop.shared.views", function() { }); }); - describe("FeedbackView", function() { - var comp, fakeFeedbackApiClient; - - beforeEach(function() { - fakeFeedbackApiClient = {send: sandbox.stub()}; - sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR); - comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({ - feedbackApiClient: fakeFeedbackApiClient - })); - }); - - // local test helpers - function clickHappyFace(comp) { - var happyFace = comp.getDOMNode().querySelector(".face-happy"); - TestUtils.Simulate.click(happyFace); - } - - function clickSadFace(comp) { - var sadFace = comp.getDOMNode().querySelector(".face-sad"); - TestUtils.Simulate.click(sadFace); - } - - function fillSadFeedbackForm(comp, category, text) { - TestUtils.Simulate.change( - comp.getDOMNode().querySelector("[value='" + category + "']")); - - if (text) { - TestUtils.Simulate.change( - comp.getDOMNode().querySelector("[name='description']"), { - target: {value: "fake reason"} - }); - } - } - - function submitSadFeedbackForm(comp, category, text) { - TestUtils.Simulate.submit(comp.getDOMNode().querySelector("form")); - } - - describe("Happy feedback", function() { - it("should send feedback data when clicking on the happy face", - function() { - clickHappyFace(comp); - - sinon.assert.calledOnce(fakeFeedbackApiClient.send); - sinon.assert.calledWith(fakeFeedbackApiClient.send, {happy: true}); - }); - - it("should thank the user once happy feedback data is sent", function() { - fakeFeedbackApiClient.send = function(data, cb) { - cb(); - }; - - clickHappyFace(comp); - - expect(comp.getDOMNode() - .querySelectorAll(".feedback .thank-you").length).eql(1); - expect(comp.getDOMNode().querySelector("button.back")).to.be.a("null"); - }); - }); - - describe("Sad feedback", function() { - it("should bring the user to feedback form when clicking on the sad face", - function() { - clickSadFace(comp); - - expect(comp.getDOMNode().querySelectorAll("form").length).eql(1); - }); - - it("should disable the form submit button when no category is chosen", - function() { - clickSadFace(comp); - - expect(comp.getDOMNode() - .querySelector("form button").disabled).eql(true); - }); - - it("should disable the form submit button when the 'other' category is " + - "chosen but no description has been entered yet", - function() { - clickSadFace(comp); - fillSadFeedbackForm(comp, "other"); - - expect(comp.getDOMNode() - .querySelector("form button").disabled).eql(true); - }); - - it("should enable the form submit button when the 'other' category is " + - "chosen and a description is entered", - function() { - clickSadFace(comp); - fillSadFeedbackForm(comp, "other", "fake"); - - expect(comp.getDOMNode() - .querySelector("form button").disabled).eql(false); - }); - - it("should empty the description field when a predefined category is " + - "chosen", - function() { - clickSadFace(comp); - - fillSadFeedbackForm(comp, "confusing"); - - expect(comp.getDOMNode() - .querySelector(".feedback-description").value).eql(""); - }); - - it("should enable the form submit button once a predefined category is " + - "chosen", - function() { - clickSadFace(comp); - - fillSadFeedbackForm(comp, "confusing"); - - expect(comp.getDOMNode() - .querySelector("form button").disabled).eql(false); - }); - - it("should disable the form submit button once the form is submitted", - function() { - clickSadFace(comp); - fillSadFeedbackForm(comp, "confusing"); - - submitSadFeedbackForm(comp); - - expect(comp.getDOMNode() - .querySelector("form button").disabled).eql(true); - }); - - it("should send feedback data when the form is submitted", function() { - clickSadFace(comp); - fillSadFeedbackForm(comp, "confusing"); - - submitSadFeedbackForm(comp); - - sinon.assert.calledOnce(fakeFeedbackApiClient.send); - sinon.assert.calledWithMatch(fakeFeedbackApiClient.send, { - happy: false, - category: "confusing" - }); - }); - - it("should send feedback data when user has entered a custom description", - function() { - clickSadFace(comp); - - fillSadFeedbackForm(comp, "other", "fake reason"); - submitSadFeedbackForm(comp); - - sinon.assert.calledOnce(fakeFeedbackApiClient.send); - sinon.assert.calledWith(fakeFeedbackApiClient.send, { - happy: false, - category: "other", - description: "fake reason" - }); - }); - - it("should thank the user when feedback data has been sent", function() { - fakeFeedbackApiClient.send = function(data, cb) { - cb(); - }; - clickSadFace(comp); - fillSadFeedbackForm(comp, "confusing"); - submitSadFeedbackForm(comp); - - expect(comp.getDOMNode() - .querySelectorAll(".feedback .thank-you").length).eql(1); - }); - }); - }); - describe("NotificationListView", function() { var coll, view, testNotif; diff --git a/browser/components/loop/test/standalone/index.html b/browser/components/loop/test/standalone/index.html index b5f6054d634..f26016dd322 100644 --- a/browser/components/loop/test/standalone/index.html +++ b/browser/components/loop/test/standalone/index.html @@ -42,6 +42,8 @@ + + diff --git a/browser/components/loop/test/standalone/webapp_test.js b/browser/components/loop/test/standalone/webapp_test.js index 21ce7f1690a..7b989db3d04 100644 --- a/browser/components/loop/test/standalone/webapp_test.js +++ b/browser/components/loop/test/standalone/webapp_test.js @@ -19,14 +19,20 @@ describe("loop.webapp", function() { notifications, feedbackApiClient, stubGetPermsAndCacheMedia, - fakeAudioXHR; + fakeAudioXHR, + dispatcher, + feedbackStore; beforeEach(function() { sandbox = sinon.sandbox.create(); + dispatcher = new loop.Dispatcher(); notifications = new sharedModels.NotificationCollection(); feedbackApiClient = new loop.FeedbackAPIClient("http://invalid", { product: "Loop" }); + feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: {} + }); stubGetPermsAndCacheMedia = sandbox.stub( loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia"); @@ -123,7 +129,7 @@ describe("loop.webapp", function() { conversation: conversation, notifications: notifications, sdk: {}, - feedbackApiClient: feedbackApiClient + feedbackStore: feedbackStore }); }); @@ -582,7 +588,7 @@ describe("loop.webapp", function() { describe("WebappRootView", function() { var helper, sdk, conversationModel, client, props, standaloneAppStore; - var dispatcher, activeRoomStore; + var activeRoomStore; function mountTestComponent() { return TestUtils.renderIntoDocument( @@ -609,7 +615,6 @@ describe("loop.webapp", function() { client = new loop.StandaloneClient({ baseServerUrl: "fakeUrl" }); - dispatcher = new loop.Dispatcher(); activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, { mozLoop: {}, sdkDriver: {} @@ -1039,7 +1044,7 @@ describe("loop.webapp", function() { loop.webapp.EndedConversationView({ conversation: conversation, sdk: {}, - feedbackApiClient: feedbackApiClient, + feedbackStore: feedbackStore, onAfterFeedbackReceived: function(){} }) ); diff --git a/browser/components/loop/ui/index.html b/browser/components/loop/ui/index.html index 85f8740774d..f5822eaff53 100644 --- a/browser/components/loop/ui/index.html +++ b/browser/components/loop/ui/index.html @@ -45,6 +45,8 @@ + + diff --git a/browser/components/loop/ui/ui-showcase.js b/browser/components/loop/ui/ui-showcase.js index cc74e6afb60..d1d8f7f2706 100644 --- a/browser/components/loop/ui/ui-showcase.js +++ b/browser/components/loop/ui/ui-showcase.js @@ -39,8 +39,9 @@ var ConversationView = loop.shared.views.ConversationView; var FeedbackView = loop.shared.views.FeedbackView; - // Room constants + // Store constants var ROOM_STATES = loop.store.ROOM_STATES; + var FEEDBACK_STATES = loop.store.FEEDBACK_STATES; // Local helpers function returnTrue() { @@ -69,6 +70,9 @@ var roomStore = new loop.store.RoomStore(dispatcher, { mozLoop: navigator.mozLoop }); + var feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: stageFeedbackApiClient + }); // Local mocks @@ -460,13 +464,13 @@ React.DOM.a({href: "https://input.allizom.org/"}, "input.allizom.org"), "." ), Example({summary: "Default (useable demo)", dashed: "true", style: {width: "260px"}}, - FeedbackView({feedbackApiClient: stageFeedbackApiClient}) + FeedbackView({feedbackStore: feedbackStore}) ), Example({summary: "Detailed form", dashed: "true", style: {width: "260px"}}, - FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "form"}) + FeedbackView({feedbackStore: feedbackStore, feedbackState: FEEDBACK_STATES.DETAILS}) ), Example({summary: "Thank you!", dashed: "true", style: {width: "260px"}}, - FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "finished"}) + FeedbackView({feedbackStore: feedbackStore, feedbackState: FEEDBACK_STATES.SENT}) ) ), @@ -486,7 +490,7 @@ video: {enabled: true}, audio: {enabled: true}, conversation: mockConversationModel, - feedbackApiClient: stageFeedbackApiClient, + feedbackStore: feedbackStore, onAfterFeedbackReceived: noop}) ) ) diff --git a/browser/components/loop/ui/ui-showcase.jsx b/browser/components/loop/ui/ui-showcase.jsx index 43668b50018..281ccff64cd 100644 --- a/browser/components/loop/ui/ui-showcase.jsx +++ b/browser/components/loop/ui/ui-showcase.jsx @@ -39,8 +39,9 @@ var ConversationView = loop.shared.views.ConversationView; var FeedbackView = loop.shared.views.FeedbackView; - // Room constants + // Store constants var ROOM_STATES = loop.store.ROOM_STATES; + var FEEDBACK_STATES = loop.store.FEEDBACK_STATES; // Local helpers function returnTrue() { @@ -69,6 +70,9 @@ var roomStore = new loop.store.RoomStore(dispatcher, { mozLoop: navigator.mozLoop }); + var feedbackStore = new loop.store.FeedbackStore(dispatcher, { + feedbackClient: stageFeedbackApiClient + }); // Local mocks @@ -460,13 +464,13 @@ input.allizom.org.

- + - + - + @@ -486,7 +490,7 @@ video={{enabled: true}} audio={{enabled: true}} conversation={mockConversationModel} - feedbackApiClient={stageFeedbackApiClient} + feedbackStore={feedbackStore} onAfterFeedbackReceived={noop} />
diff --git a/browser/devtools/app-manager/app-projects.js b/browser/devtools/app-manager/app-projects.js index 0fe287975ce..eb3c2ab32c6 100644 --- a/browser/devtools/app-manager/app-projects.js +++ b/browser/devtools/app-manager/app-projects.js @@ -16,14 +16,15 @@ const { indexedDB } = require("sdk/indexed-db"); const IDB = { _db: null, + databaseName: "AppProjects", open: function () { let deferred = promise.defer(); - let request = indexedDB.open("AppProjects", 5); + let request = indexedDB.open(IDB.databaseName, 5); request.onerror = function(event) { - deferred.reject("Unable to open AppProjects indexedDB. " + - "Error code: " + event.target.errorCode); + deferred.reject("Unable to open AppProjects indexedDB: " + + this.error.name + " - " + this.error.message ); }; request.onupgradeneeded = function(event) { let db = event.target.result; @@ -147,11 +148,10 @@ const store = new ObservableObject({ projects:[] }); let loadDeferred = promise.defer(); -IDB.open().then(function (projects) { +loadDeferred.resolve(IDB.open().then(function (projects) { store.object.projects = projects; AppProjects.emit("ready", store.object.projects); - loadDeferred.resolve(); -}); +})); const AppProjects = { load: function() { diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js index 95a0e33bf11..f3618742aca 100644 --- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -72,6 +72,9 @@ let UI = { AppProjects.load().then(() => { this.autoSelectProject(); + }, e => { + console.error(e); + this.reportError("error_appProjectsLoadFailed"); }); // Auto install the ADB Addon Helper and Tools Adapters. Only once. @@ -256,7 +259,7 @@ let UI = { this._busyTimeout = setTimeout(() => { this.unbusy(); UI.reportError("error_operationTimeout", this._busyOperationDescription); - }, 6000); + }, Services.prefs.getIntPref("devtools.webide.busyTimeout")); }, cancelBusyTimeout: function() { diff --git a/browser/devtools/webide/webide-prefs.js b/browser/devtools/webide/webide-prefs.js index 8b3c64c7440..15fae4dac3b 100644 --- a/browser/devtools/webide/webide-prefs.js +++ b/browser/devtools/webide/webide-prefs.js @@ -32,3 +32,4 @@ pref("devtools.webide.widget.enabled", false); pref("devtools.webide.widget.inNavbarByDefault", false); #endif pref("devtools.webide.zoom", "1"); +pref("devtools.webide.busyTimeout", 10000); diff --git a/browser/locales/en-US/chrome/browser/devtools/webide.properties b/browser/locales/en-US/chrome/browser/devtools/webide.properties index 314cdf31eb5..c658c439920 100644 --- a/browser/locales/en-US/chrome/browser/devtools/webide.properties +++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties @@ -20,7 +20,7 @@ importHostedApp_title=Open Hosted App importHostedApp_header=Enter Manifest URL notification_showTroubleShooting_label=Troubleshooting -notification_showTroubleShooting_accesskey=t +notification_showTroubleShooting_accesskey=T # LOCALIZATION NOTE (project_tab_loading): This is shown as a temporary tab # title for browser tab projects when the tab is still loading. @@ -42,6 +42,8 @@ error_cantConnectToApp=Can't connect to app: %1$S # Variable: error message (in english) error_cantFetchAddonsJSON=Can't fetch the add-on list: %S +error_appProjectsLoadFailed=Unable to load project list. This can occur if you've used this profile with a newer version of Firefox. + addons_stable=stable addons_unstable=unstable # LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of diff --git a/configure.in b/configure.in index 2b8db8d4a4f..1aae34cf00f 100644 --- a/configure.in +++ b/configure.in @@ -2212,7 +2212,7 @@ ia64*-hpux*) if test "$CPU_ARCH" = "x86"; then WIN32_SUBSYSTEM_VERSION=5.01 else - WIN32_SUBSYSTEM_VERSION=5.02 + WIN32_SUBSYSTEM_VERSION=6.01 fi WIN32_CONSOLE_EXE_LDFLAGS=-SUBSYSTEM:CONSOLE,$WIN32_SUBSYSTEM_VERSION WIN32_GUI_EXE_LDFLAGS=-SUBSYSTEM:WINDOWS,$WIN32_SUBSYSTEM_VERSION @@ -5935,53 +5935,57 @@ if test -n "$MOZ_ANGLE_RENDERER"; then ###################################### # Find _43 for use by XP. - # Get the SDK path from the registry. - # First try to get the June 2010 SDK - MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK (June 2010)' | head -n 1` - if test -z "$MOZ_DIRECTX_SDK_REG_KEY" ; then - # Otherwise just take whatever comes first - MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK' | head -n 1` - fi - MOZ_DIRECTX_SDK_PATH=`reg query "$MOZ_DIRECTX_SDK_REG_KEY" //v InstallPath | grep REG_SZ | sed 's/.*\([[a-zA-Z]]\)\\:\\\\/\\1\\:\\\\/' | sed 's,\\\\,/,g'` - - if test -n "$MOZ_DIRECTX_SDK_PATH" && - test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/dxguid.lib ; then - AC_MSG_RESULT([Found DirectX SDK via registry, using $MOZ_DIRECTX_SDK_PATH]) + if test "$HAVE_64BIT_BUILD"; then + AC_MSG_RESULT([We are building a 64-bit binary, skip checking d3dcompiler_43.]) else - AC_MSG_RESULT([DirectX SDK not found.]) - MOZ_DIRECTX_SDK_PATH= - fi + # Get the SDK path from the registry. + # First try to get the June 2010 SDK + MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK (June 2010)' | head -n 1` + if test -z "$MOZ_DIRECTX_SDK_REG_KEY" ; then + # Otherwise just take whatever comes first + MOZ_DIRECTX_SDK_REG_KEY=`reg query 'HKLM\Software\Microsoft\DirectX' //s | grep 'Microsoft DirectX SDK' | head -n 1` + fi + MOZ_DIRECTX_SDK_PATH=`reg query "$MOZ_DIRECTX_SDK_REG_KEY" //v InstallPath | grep REG_SZ | sed 's/.*\([[a-zA-Z]]\)\\:\\\\/\\1\\:\\\\/' | sed 's,\\\\,/,g'` - # Check that our DirectX SDK is acceptable. - if test -n "$MOZ_DIRECTX_SDK_PATH"; then - if test -n "`echo $MOZ_DIRECTX_SDK_REG_KEY | grep 'February 2010'`" ; then - AC_MSG_RESULT([Found the February 2010 DirectX SDK, which is unacceptable to ANGLE.]) + if test -n "$MOZ_DIRECTX_SDK_PATH" && + test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/dxguid.lib ; then + AC_MSG_RESULT([Found DirectX SDK via registry, using $MOZ_DIRECTX_SDK_PATH]) + else + AC_MSG_RESULT([DirectX SDK not found.]) MOZ_DIRECTX_SDK_PATH= fi - fi - if test -n "$MOZ_DIRECTX_SDK_PATH"; then - # Find a D3D compiler DLL in the DirectX SDK, if we didn't find one already. - # Get the SDK numeric version (e.g. 43) by looking at the dependencies of d3dx9.lib - MOZ_D3DX9_VERSION=`dumpbin //headers "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/d3dx9.lib | egrep d3dx9_[[0-9]][[0-9]]\.dll | head -n1 | sed 's/.*\([[0-9]][[0-9]]\).*/\\1/g'` + # Check that our DirectX SDK is acceptable. + if test -n "$MOZ_DIRECTX_SDK_PATH"; then + if test -n "`echo $MOZ_DIRECTX_SDK_REG_KEY | grep 'February 2010'`" ; then + AC_MSG_RESULT([Found the February 2010 DirectX SDK, which is unacceptable to ANGLE.]) + MOZ_DIRECTX_SDK_PATH= + fi + fi - if test -n "$MOZ_D3DX9_VERSION" ; then - MOZ_D3DCOMPILER_XP_CAB=`find "$MOZ_DIRECTX_SDK_PATH"/Redist -name *D3DCompiler_${MOZ_D3DX9_VERSION}_${MOZ_D3D_CPU_SUFFIX}.cab | head -n1` + if test -n "$MOZ_DIRECTX_SDK_PATH"; then + # Find a D3D compiler DLL in the DirectX SDK, if we didn't find one already. + # Get the SDK numeric version (e.g. 43) by looking at the dependencies of d3dx9.lib + MOZ_D3DX9_VERSION=`dumpbin //headers "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_D3D_CPU_SUFFIX/d3dx9.lib | egrep d3dx9_[[0-9]][[0-9]]\.dll | head -n1 | sed 's/.*\([[0-9]][[0-9]]\).*/\\1/g'` - if test -n "$MOZ_D3DCOMPILER_XP_CAB"; then - MOZ_D3DCOMPILER_XP_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll + if test -n "$MOZ_D3DX9_VERSION" ; then + MOZ_D3DCOMPILER_XP_CAB=`find "$MOZ_DIRECTX_SDK_PATH"/Redist -name *D3DCompiler_${MOZ_D3DX9_VERSION}_${MOZ_D3D_CPU_SUFFIX}.cab | head -n1` + + if test -n "$MOZ_D3DCOMPILER_XP_CAB"; then + MOZ_D3DCOMPILER_XP_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll + else + AC_MSG_RESULT([Couldn't find a CAB containing the D3D compiler DLL.]) + AC_MSG_ERROR([DirectX SDK at "$MOZ_DIRECTX_SDK_PATH" appears broken.]) + MOZ_DIRECTX_SDK_PATH= + fi else - AC_MSG_RESULT([Couldn't find a CAB containing the D3D compiler DLL.]) - AC_MSG_ERROR([DirectX SDK at "$MOZ_DIRECTX_SDK_PATH" appears broken.]) + AC_MSG_RESULT([Couldn't determine the D3DX9 version for the DirectX SDK.]) MOZ_DIRECTX_SDK_PATH= fi else - AC_MSG_RESULT([Couldn't determine the D3DX9 version for the DirectX SDK.]) - MOZ_DIRECTX_SDK_PATH= + AC_MSG_RESULT([Couldn't find an acceptable DirectX SDK for ANGLE, needed for d3dcompiler_43.]) + AC_MSG_RESULT([ Either ignore, install DirectX SDK (June 2010 version or newer), or reconfigure with --disable-webgl.]) fi - else - AC_MSG_RESULT([Couldn't find an acceptable DirectX SDK for ANGLE, needed for d3dcompiler_43.]) - AC_MSG_RESULT([ Either ignore, install DirectX SDK (June 2010 version or newer), or reconfigure with --disable-webgl.]) fi ###################################### diff --git a/content/base/crashtests/894137.html b/content/base/crashtests/894137.html deleted file mode 100644 index 41e7f98d9dd..00000000000 --- a/content/base/crashtests/894137.html +++ /dev/null @@ -1,46 +0,0 @@ - - - -
-
- - -
-
-

-

- - - - - - - - - - - - - - - - - - -
- -
- - -
- - - - - - diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 20b5a858079..1e1d1387c9b 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -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 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); + } } } diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index acb45155fae..93ce1d255f5 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -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 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 mPreloadingImages; nsRefPtr mDOMImplementation; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index c737f1235ca..83fad42fdaf 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -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 diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index b59e0f44f34..f053616725c 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -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(); diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index c454f6c0394..2a52a7a46f3 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -459,7 +459,7 @@ CreateInterfaceObject(JSContext* cx, JS::Handle 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 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; } diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 21dfc1e1cd1..d757c9bb12d 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -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) diff --git a/dom/imptests/idlharness.js b/dom/imptests/idlharness.js index 25e1117b4ab..613da0cb4c0 100644 --- a/dom/imptests/idlharness.js +++ b/dom/imptests/idlharness.js @@ -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"; }); diff --git a/dom/svg/nsSVGPolyElement.cpp b/dom/svg/nsSVGPolyElement.cpp index bd1aa99077b..211d8015d89 100644 --- a/dom/svg/nsSVGPolyElement.cpp +++ b/dom/svg/nsSVGPolyElement.cpp @@ -119,3 +119,38 @@ nsSVGPolyElement::GetMarkPoints(nsTArray *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; +} + diff --git a/dom/svg/nsSVGPolyElement.h b/dom/svg/nsSVGPolyElement.h index cee7047a931..e3efca6f8e7 100644 --- a/dom/svg/nsSVGPolyElement.h +++ b/dom/svg/nsSVGPolyElement.h @@ -45,6 +45,8 @@ public: virtual bool AttributeDefinesGeometry(const nsIAtom *aName) MOZ_OVERRIDE; virtual bool IsMarkable() MOZ_OVERRIDE { return true; } virtual void GetMarkPoints(nsTArray *aMarks) MOZ_OVERRIDE; + virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth, + const Matrix& aTransform) MOZ_OVERRIDE; // WebIDL already_AddRefed Points(); diff --git a/dom/webidl/URLSearchParams.webidl b/dom/webidl/URLSearchParams.webidl index dceb277a5d8..39aa200aadd 100644 --- a/dom/webidl/URLSearchParams.webidl +++ b/dom/webidl/URLSearchParams.webidl @@ -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); diff --git a/dom/xbl/test/test_bug1086996.xhtml b/dom/xbl/test/test_bug1086996.xhtml index 7f755df91cc..c60855a0054 100644 --- a/dom/xbl/test/test_bug1086996.xhtml +++ b/dom/xbl/test/test_bug1086996.xhtml @@ -34,7 +34,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1086996 Mozilla Bug 1086996 -
@@ -56,5 +55,8 @@ function gotEvent() {
 ]]>
 
 
+ +
diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h index 01c0bac0f38..0b6810cfddd 100644 --- a/gfx/2d/BaseRect.h +++ b/gfx/2d/BaseRect.h @@ -177,6 +177,23 @@ struct BaseRect { *static_cast(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; diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h index 9599705c9bd..9004ff2c74a 100644 --- a/gfx/2d/HelpersD2D.h +++ b/gfx/2d/HelpersD2D.h @@ -396,7 +396,7 @@ DWriteGlyphRunFromGlyphs(const GlyphBuffer &aGlyphs, ScaledFontDWrite *aFont, Au run->isSideways = FALSE; } -static TemporaryRef +static inline TemporaryRef ConvertRectToGeometry(const D2D1_RECT_F& aRect) { RefPtr rectGeom; @@ -404,7 +404,7 @@ ConvertRectToGeometry(const D2D1_RECT_F& aRect) return rectGeom.forget(); } -static TemporaryRef +static inline TemporaryRef GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform) { RefPtr tmpGeometry; @@ -417,7 +417,7 @@ GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTrans return tmpGeometry.forget(); } -static TemporaryRef +static inline TemporaryRef IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB) { RefPtr pathGeom; @@ -430,7 +430,7 @@ IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB) return pathGeom.forget(); } -static TemporaryRef +static inline TemporaryRef CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions) { RefPtr 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 +static inline TemporaryRef CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestinationTransform, const IntSize &aDestinationSize, ExtendMode aExtendMode, Matrix &aSourceTransform, ID2D1RenderTarget *aRT, diff --git a/gfx/layers/apz/testutil/APZTestData.h b/gfx/layers/apz/testutil/APZTestData.h index f547717e4c5..e31d7301794 100644 --- a/gfx/layers/apz/testutil/APZTestData.h +++ b/gfx/layers/apz/testutil/APZTestData.h @@ -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). } }; diff --git a/gfx/thebes/gfxDWriteCommon.h b/gfx/thebes/gfxDWriteCommon.h index 46146808cdd..fbf7005a6d4 100644 --- a/gfx/thebes/gfxDWriteCommon.h +++ b/gfx/thebes/gfxDWriteCommon.h @@ -20,7 +20,7 @@ #include #include -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) { diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index 275d06005c2..4ed0394efc1 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -1266,7 +1266,6 @@ CallTraceCallbackOnNonHeap(T *v, const TraceCallbacks &aCallbacks, const char *a MOZ_ASSERT(!IsInsideNursery(cell)); JS::Heap *asHeapT = reinterpret_cast*>(v); aCallbacks.Trace(asHeapT, aName, aClosure); - MOZ_ASSERT(GCMethods::asGCThingOrNull(*v) == cell); } } /* namespace gc */ diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp index 472831232e4..f25678c3439 100644 --- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -559,10 +559,6 @@ struct Or { static inline T apply(T l, T r) { return l | r; } }; template -struct Scale { - static inline T apply(int32_t lane, T scalar, T x) { return scalar * x; } -}; -template struct WithX { static inline T apply(int32_t lane, T scalar, T x) { return lane == 0 ? scalar : x; } }; @@ -578,22 +574,6 @@ template struct WithW { static inline T apply(int32_t lane, T scalar, T x) { return lane == 3 ? scalar : x; } }; -template -struct WithFlagX { - static inline T apply(T l, T f, T x) { return l == 0 ? (f ? 0xFFFFFFFF : 0x0) : x; } -}; -template -struct WithFlagY { - static inline T apply(T l, T f, T x) { return l == 1 ? (f ? 0xFFFFFFFF : 0x0) : x; } -}; -template -struct WithFlagZ { - static inline T apply(T l, T f, T x) { return l == 2 ? (f ? 0xFFFFFFFF : 0x0) : x; } -}; -template -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; } }; diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h index a398e0c6128..8e2fc7b2a47 100644 --- a/js/src/builtin/SIMD.h +++ b/js/src/builtin/SIMD.h @@ -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), 0, 0) - #define FLOAT32X4_UNARY_FUNCTION_LIST(V) \ V(abs, (UnaryFunc), 1, 0) \ V(fromInt32x4, (FuncConvert ), 1, 0) \ @@ -52,7 +49,6 @@ V(mul, (BinaryFunc), 2, 0) \ V(notEqual, (CompareFunc), 2, 0) \ V(or, (CoercedBinaryFunc), 2, 0) \ - V(scale, (FuncWith), 2, 0) \ V(store, (Store), 3, 0) \ V(storeXYZ, (Store), 3, 0) \ V(storeXY, (Store), 3, 0) \ @@ -73,15 +69,11 @@ V(shuffle, Shuffle, 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), 0, 0) - #define INT32X4_UNARY_FUNCTION_LIST(V) \ V(fromFloat32x4, (FuncConvert), 1, 0) \ V(fromFloat32x4Bits, (FuncConvertBits), 1, 0) \ @@ -94,12 +86,15 @@ V(and, (BinaryFunc), 2, 0) \ V(equal, (CompareFunc), 2, 0) \ V(greaterThan, (CompareFunc), 2, 0) \ + V(greaterThanOrEqual, (CompareFunc), 2, 0) \ V(lessThan, (CompareFunc), 2, 0) \ + V(lessThanOrEqual, (CompareFunc), 2, 0) \ V(load, (Load), 2, 0) \ V(loadXYZ, (Load), 2, 0) \ V(loadXY, (Load), 2, 0) \ V(loadX, (Load), 2, 0) \ V(mul, (BinaryFunc), 2, 0) \ + V(notEqual, (CompareFunc), 2, 0) \ V(or, (BinaryFunc), 2, 0) \ V(sub, (BinaryFunc), 2, 0) \ V(shiftLeft, (Int32x4BinaryScalar), 2, 0) \ @@ -109,10 +104,6 @@ V(storeXYZ, (Store), 3, 0) \ V(storeXY, (Store), 3, 0) \ V(storeX, (Store), 3, 0) \ - V(withFlagX, (FuncWith), 2, 0) \ - V(withFlagY, (FuncWith), 2, 0) \ - V(withFlagZ, (FuncWith), 2, 0) \ - V(withFlagW, (FuncWith), 2, 0) \ V(withX, (FuncWith), 2, 0) \ V(withY, (FuncWith), 2, 0) \ V(withZ, (FuncWith), 2, 0) \ @@ -130,7 +121,6 @@ V(shuffle, Shuffle, 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) \ diff --git a/js/src/configure.in b/js/src/configure.in index 8f1d72317b3..c270df1b60b 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -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 diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index cbd36fa7006..c85871fe2f8 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -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); diff --git a/js/src/jit-test/tests/asm.js/testSIMD.js b/js/src/jit-test/tests/asm.js/testSIMD.js index e67a4d30f67..611f3893d6d 100644 --- a/js/src/jit-test/tests/asm.js/testSIMD.js +++ b/js/src/jit-test/tests/asm.js/testSIMD.js @@ -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;'; diff --git a/js/src/jit-test/tests/ion/sink-in-recovered-object.js b/js/src/jit-test/tests/ion/sink-in-recovered-object.js new file mode 100644 index 00000000000..fd1b9791750 --- /dev/null +++ b/js/src/jit-test/tests/ion/sink-in-recovered-object.js @@ -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); +} diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 862954b8092..b89c0f6fa0c 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -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(jit::InterpretResume); typedef bool (*GeneratorThrowFn)(JSContext *, BaselineFrame *, HandleObject, HandleValue, uint32_t); -static const VMFunction GeneratorThrowInfo = FunctionInfo(jit::GeneratorThrowOrClose); +static const VMFunction GeneratorThrowInfo = FunctionInfo(jit::GeneratorThrowOrClose, TailCall); bool BaselineCompiler::emit_JSOP_RESUME() diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index a113e98b755..67f8559e2f8 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -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(DoProfilerFallback); + FunctionInfo(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(DoTypeMonitorFallback); + FunctionInfo(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(DoTypeUpdateFallback); + FunctionInfo(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(DoThisFallback); +static const VMFunction DoThisFallbackInfo = FunctionInfo(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(DoNewArray); +static const VMFunction DoNewArrayInfo = FunctionInfo(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(DoNewObject); +static const VMFunction DoNewObjectInfo = FunctionInfo(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(DoCompareFallback, PopValues(2)); + FunctionInfo(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(DoToBoolFallback); +static const VMFunction fun = FunctionInfo(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(DoToNumberFallback, PopValues(1)); + FunctionInfo(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(DoBinaryArithFallback, PopValues(2)); + FunctionInfo(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(DoConcatStrings); +static const VMFunction DoConcatStringsInfo = FunctionInfo(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(DoConcatStringObject, PopValues(2)); + FunctionInfo(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(DoUnaryArithFallback, PopValues(1)); + FunctionInfo(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(DoGetElemFallback, PopValues(2)); + FunctionInfo(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(DoSetElemFallback, PopValues(2)); + FunctionInfo(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(DoInFallback, PopValues(2)); + FunctionInfo(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(DoGetNameFallback); +static const VMFunction DoGetNameFallbackInfo = FunctionInfo(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(DoBindNameFallback); + FunctionInfo(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(DoGetIntrinsicFallback); + FunctionInfo(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(DoGetPropFallback, PopValues(1)); + FunctionInfo(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(DoSetPropFallback, PopValues(2)); + FunctionInfo(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(DoIteratorNewFallback, PopValues(1)); + FunctionInfo(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(DoIteratorMoreFallback); + FunctionInfo(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(DoIteratorCloseFallback); + FunctionInfo(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(DoInstanceOfFallback, PopValues(2)); + FunctionInfo(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(DoTypeOfFallback); + FunctionInfo(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(DoRetSubFallback); typedef bool (*ThrowFn)(JSContext *, HandleValue); -static const VMFunction ThrowInfoBaseline = FunctionInfo(js::Throw); +static const VMFunction ThrowInfoBaseline = FunctionInfo(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(DoRestFallback); + FunctionInfo(DoRestFallback, TailCall); bool ICRest_Fallback::Compiler::generateStubCode(MacroAssembler &masm) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index b014d230841..79386df0ac1 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -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); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 502c6e8bc06..7317cec4010 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -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. { diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 9bf8ff4b2db..461555fd662 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -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(); } } } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index c7ae2720398..7451ebf2b14 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -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; diff --git a/js/src/jit/IonOptimizationLevels.cpp b/js/src/jit/IonOptimizationLevels.cpp index 6b55ba77e0f..6d520988c98 100644 --- a/js/src/jit/IonOptimizationLevels.cpp +++ b/js/src/jit/IonOptimizationLevels.cpp @@ -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. } diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h index 213dfe2ff03..46a4d38d607 100644 --- a/js/src/jit/IonOptimizationLevels.h +++ b/js/src/jit/IonOptimizationLevels.h @@ -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; } diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index 1c3ce9870ad..7c96b73be23 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -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); diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h index 9158dab9b7d..67a73cf4f88 100644 --- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -38,6 +38,7 @@ struct JitOptions bool disableInlining; bool disableEdgeCaseAnalysis; bool disableRangeAnalysis; + bool disableSink; bool disableLoopUnrolling; bool disableEaa; bool eagerCompilation; diff --git a/js/src/jit/JitSpewer.cpp b/js/src/jit/JitSpewer.cpp index 92195a33be5..6b9f0e74717 100644 --- a/js/src/jit/JitSpewer.cpp +++ b/js/src/jit/JitSpewer.cpp @@ -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")) diff --git a/js/src/jit/JitSpewer.h b/js/src/jit/JitSpewer.h index 584e7745c2f..102273097a3 100644 --- a/js/src/jit/JitSpewer.h +++ b/js/src/jit/JitSpewer.h @@ -26,6 +26,8 @@ namespace jit { _(Alias) \ /* Information during GVN */ \ _(GVN) \ + /* Information during sinking */ \ + _(Sink) \ /* Information during Range analysis */ \ _(Range) \ /* Information during loop unrolling */ \ diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 30a98310d0f..6273d51843e 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -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 */) { diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index 04f404cbf1d..0a906fe75f1 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -286,6 +286,15 @@ class MBasicBlock : public TempObject, public InlineListNode // 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(); diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index fd8407bea3e..253ba7bd34f 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -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); } } diff --git a/js/src/jit/Sink.cpp b/js/src/jit/Sink.cpp new file mode 100644 index 00000000000..da496380315 --- /dev/null +++ b/js/src/jit/Sink.cpp @@ -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; +} + +} +} diff --git a/js/src/jit/Sink.h b/js/src/jit/Sink.h new file mode 100644 index 00000000000..ad74bb91808 --- /dev/null +++ b/js/src/jit/Sink.h @@ -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 */ diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index ee1fc437269..92503b51be6 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -41,6 +41,11 @@ struct PopValues { } }; +enum MaybeTailCall { + TailCall, + NonTailCall +}; + // Contains information about a virtual machine function that can be called // from JIT code. Functions described in this manner must conform to a simple // protocol: the return type must have a special "failure" value (for example, @@ -123,6 +128,11 @@ struct VMFunction // wrapper. uint32_t extraValuesToPop; + // On some architectures, called functions need to explicitly push their + // return address, for a tail call, there is nothing to push, so tail-callness + // needs to be known at compile time. + MaybeTailCall expectTailCall; + uint32_t argc() const { // JSContext * + args + (OutParam? *) return 1 + explicitArgc() + ((outParam == Type_Void) ? 0 : 1); @@ -227,7 +237,8 @@ struct VMFunction VMFunction(void *wrapped, uint32_t explicitArgs, uint32_t argumentProperties, uint32_t argumentPassedInFloatRegs, uint64_t argRootTypes, DataType outParam, RootType outParamRootType, DataType returnType, - ExecutionMode executionMode, uint32_t extraValuesToPop = 0) + ExecutionMode executionMode, uint32_t extraValuesToPop = 0, + MaybeTailCall expectTailCall = NonTailCall) : wrapped(wrapped), explicitArgs(explicitArgs), argumentProperties(argumentProperties), @@ -237,7 +248,8 @@ struct VMFunction argumentRootTypes(argRootTypes), outParamRootType(outParamRootType), executionMode(executionMode), - extraValuesToPop(extraValuesToPop) + extraValuesToPop(extraValuesToPop), + expectTailCall(expectTailCall) { // Check for valid failure/return type. MOZ_ASSERT_IF(outParam != Type_Void && executionMode == SequentialExecution, @@ -503,12 +515,20 @@ template <> struct MatchContext { 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 @@ -548,7 +568,13 @@ struct FunctionInfo : 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) { } }; diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index 13a8404e3f5..0e04cf390ef 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -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(); diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index d53fe8f49eb..c9b5d0e39b8 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -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); } diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index b7bbe165d3b..3646ae0d7b2 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1833,6 +1833,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void loadAsmJSHeapRegisterFromGlobalData() { loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg); } + void pushReturnAddress() { + push(lr); + } }; typedef MacroAssemblerARMCompat MacroAssemblerSpecific; diff --git a/js/src/jit/arm/Trampoline-arm.cpp b/js/src/jit/arm/Trampoline-arm.cpp index 8c2cb24fb51..2c3337e8e34 100644 --- a/js/src/jit/arm/Trampoline-arm.cpp +++ b/js/src/jit/arm/Trampoline-arm.cpp @@ -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"); diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index b470e32f77f..ec66226e168 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -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 diff --git a/js/src/jit/shared/BaselineCompiler-shared.cpp b/js/src/jit/shared/BaselineCompiler-shared.cpp index 77d8593263f..2e5bba563f3 100644 --- a/js/src/jit/shared/BaselineCompiler-shared.cpp +++ b/js/src/jit/shared/BaselineCompiler-shared.cpp @@ -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(); diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index f28a9d6c81a..c49015589f8 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -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"); } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 8bc7a333f03..c5bf7545182 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -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. diff --git a/js/src/moz.build b/js/src/moz.build index bc3a2a39a88..6956808d7f1 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -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', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8240ae80206..c220217f4ce 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5500,6 +5500,15 @@ SetRuntimeOptions(JSRuntime *rt, const OptionParser &op) return OptionFailure("ion-range-analysis", str); } + if (const char *str = op.getStringOption("ion-sink")) { + if (strcmp(str, "on") == 0) + jit::js_JitOptions.disableSink = false; + else if (strcmp(str, "off") == 0) + jit::js_JitOptions.disableSink = true; + else + return OptionFailure("ion-sink", str); + } + if (const char *str = op.getStringOption("ion-loop-unrolling")) { if (strcmp(str, "on") == 0) jit::js_JitOptions.disableLoopUnrolling = false; @@ -5797,6 +5806,8 @@ main(int argc, char **argv, char **envp) "Find edge cases where Ion can avoid bailouts (default: on, off to disable)") || !op.addStringOption('\0', "ion-range-analysis", "on/off", "Range analysis (default: on, off to disable)") + || !op.addStringOption('\0', "ion-sink", "on/off", + "Sink code motion (default: on, off to disable)") || !op.addStringOption('\0', "ion-loop-unrolling", "on/off", "Loop unrolling (default: off, on to enable)") || !op.addBoolOption('\0', "ion-check-range-analysis", diff --git a/js/src/tests/ecma_6/TypedObject/simd/comparisons.js b/js/src/tests/ecma_6/TypedObject/simd/comparisons.js new file mode 100644 index 00000000000..c97b5ec301a --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/comparisons.js @@ -0,0 +1,102 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var float32x4 = SIMD.float32x4; +var int32x4 = SIMD.int32x4; + +var fround = Math.fround; + +function boolToSimdLogical(b) { + return b ? 0xFFFFFFFF | 0 : 0x0; +} + +function testEqualFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.equal, (x, y) => boolToSimdLogical(fround(x) == fround(y))); +} +function testNotEqualFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.notEqual, (x, y) => boolToSimdLogical(fround(x) != fround(y))); +} +function testLessThanFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.lessThan, (x, y) => boolToSimdLogical(fround(x) < fround(y))); +} +function testLessThanOrEqualFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.lessThanOrEqual, (x, y) => boolToSimdLogical(fround(x) <= fround(y))); +} +function testGreaterThanFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.greaterThan, (x, y) => boolToSimdLogical(fround(x) > fround(y))); +} +function testGreaterThanOrEqualFloat32x4(v, w) { + testBinaryFunc(v, w, float32x4.greaterThanOrEqual, (x, y) => boolToSimdLogical(fround(x) >= fround(y))); +} + +function testEqualInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.equal, (x, y) => boolToSimdLogical(x == y)); +} +function testNotEqualInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.notEqual, (x, y) => boolToSimdLogical(x != y)); +} +function testLessThanInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.lessThan, (x, y) => boolToSimdLogical(x < y)); +} +function testLessThanOrEqualInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.lessThanOrEqual, (x, y) => boolToSimdLogical(x <= y)); +} +function testGreaterThanInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.greaterThan, (x, y) => boolToSimdLogical(x > y)); +} +function testGreaterThanOrEqualInt32x4(v, w) { + testBinaryFunc(v, w, int32x4.greaterThanOrEqual, (x, y) => boolToSimdLogical(x >= y)); +} + +function test() { + var float32x4val = [ + float32x4(1, 20, 30, 4), + float32x4(10, 2, 3, 40), + float32x4(9.999, 2.1234, 30.4443, 4), + float32x4(10, 2.1233, 30.4444, 4.0001), + float32x4(NaN, -Infinity, +Infinity, -0), + float32x4(+Infinity, NaN, -0, -Infinity), + float32x4(13.37, 42.42, NaN, 0) + ]; + + var v, w; + for (v of float32x4val) { + for (w of float32x4val) { + testEqualFloat32x4(v, w); + testNotEqualFloat32x4(v, w); + testLessThanFloat32x4(v, w); + testLessThanOrEqualFloat32x4(v, w); + testGreaterThanFloat32x4(v, w); + testGreaterThanOrEqualFloat32x4(v, w); + } + } + + var int32x4val = [ + int32x4(1, 2, 3, 4), + int32x4(-1, -2, -3, -4), + int32x4(-1, 2, -3, 4), + int32x4(1, -2, 3, -4), + int32x4(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), + int32x4(INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN) + ]; + + for (v of int32x4val) { + for (w of int32x4val) { + testEqualInt32x4(v, w); + testNotEqualInt32x4(v, w); + testLessThanInt32x4(v, w); + testLessThanOrEqualInt32x4(v, w); + testGreaterThanInt32x4(v, w); + testGreaterThanOrEqualInt32x4(v, w); + } + } + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4equal.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4equal.js deleted file mode 100644 index 9ad89e46adb..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4equal.js +++ /dev/null @@ -1,34 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 equal'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of NaN/-0/Infinity/-Infinity border cases. - - var a = float32x4(1, 20, 30, 40); - var b = float32x4(10, 20, 30, 4); - var c = SIMD.float32x4.equal(a, b); - assertEq(c.x, 0); - assertEq(c.y, -1); - assertEq(c.z, -1); - assertEq(c.w, 0); - - var d = float32x4(1.89, 20.51, 30.46, 40.12); - var e = float32x4(10.89, 20.51, Math.fround(30.46), 4.12); - var f = SIMD.float32x4.equal(d, e); - assertEq(c.x, 0); - assertEq(c.y, -1); - assertEq(c.z, -1); - assertEq(c.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthan.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthan.js deleted file mode 100644 index 6fe4e243108..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthan.js +++ /dev/null @@ -1,35 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 greaterThan'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases. - // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined. - - var a = float32x4(1, 20, 3, 40); - var b = float32x4(10, 2, 30, 4); - var c = SIMD.float32x4.greaterThan(b, a); - assertEq(c.x, -1); - assertEq(c.y, 0); - assertEq(c.z, -1); - assertEq(c.w, 0); - - var d = float32x4(10.8399, 20.37, 3.07, 4.6802); - var e = float32x4(10.8401, 20.367, 3.1, 4.6801); - var f = float32x4.greaterThan(e, d); - assertEq(f.x, -1); - assertEq(f.y, 0); - assertEq(f.z, -1); - assertEq(f.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthanorequal.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthanorequal.js deleted file mode 100644 index e1963c3a00c..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthanorequal.js +++ /dev/null @@ -1,35 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 greaterThanOrEqual'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases. - // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined. - - var a = float32x4(1, 20, 30, 40); - var b = float32x4(10, 20, 30, 4); - var c = SIMD.float32x4.greaterThanOrEqual(b, a); - assertEq(c.x, -1); - assertEq(c.y, -1); - assertEq(c.z, -1); - assertEq(c.w, 0); - - var d = float32x4(10.029, 20.87, 30.56, 4.7); - var e = float32x4(10.03, 20.87, 30.56, 4.698); - var f = float32x4.greaterThanOrEqual(e, d); - assertEq(f.x, -1); - assertEq(f.y, -1); - assertEq(f.z, -1); - assertEq(f.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthan.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthan.js deleted file mode 100644 index 199babf0e66..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthan.js +++ /dev/null @@ -1,35 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 lessThan'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases. - // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined. - - var a = float32x4(1, 20, 3, 40); - var b = float32x4(10, 2, 30, 4); - var c = SIMD.float32x4.lessThan(a, b); - assertEq(c.x, -1); - assertEq(c.y, 0); - assertEq(c.z, -1); - assertEq(c.w, 0); - - var d = float32x4(1.5399, 20.001, 30.045, 4.74); - var e = float32x4(1.54, 19.999, 30.05, 4.72); - var f = float32x4.lessThan(a, b); - assertEq(f.x, -1); - assertEq(f.y, 0); - assertEq(f.z, -1); - assertEq(f.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthanorequal.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthanorequal.js deleted file mode 100644 index 7c9d9d3003e..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthanorequal.js +++ /dev/null @@ -1,35 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 lessThanOrEqual'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases. - // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined. - - var a = float32x4(1, 20, 30, 40); - var b = float32x4(10, 20, 30, 4); - var c = SIMD.float32x4.lessThanOrEqual(a, b); - assertEq(c.x, -1); - assertEq(c.y, -1); - assertEq(c.z, -1); - assertEq(c.w, 0); - - var d = float32x4(9.999, 20.78, 30.14, 40.1235); - var e = float32x4(10, 20.78, 30.14, 40.123); - var f = float32x4.lessThanOrEqual(d, e); - assertEq(f.x, -1); - assertEq(f.y, -1); - assertEq(f.z, -1); - assertEq(f.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4notequal.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4notequal.js deleted file mode 100644 index c46bcba524d..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4notequal.js +++ /dev/null @@ -1,34 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 notEqual'; - -function test() { - print(BUGNUMBER + ": " + summary); - - // FIXME -- Bug 1081697: Amend to check for correctness of NaN/-0/Infinity/-Infinity border cases. - - var a = float32x4(1, 20, 30, 40); - var b = float32x4(10, 20, 30, 4); - var c = SIMD.float32x4.notEqual(a, b); - assertEq(c.x, -1); - assertEq(c.y, 0); - assertEq(c.z, 0); - assertEq(c.w, -1); - - var d = float32x4(9.98, 20.65, 30.14, 4.235); - var e = float32x4(9.99, 20.65, Math.fround(30.14), 4.23); - var f = SIMD.float32x4.notEqual(d, e); - assertEq(f.x, -1); - assertEq(f.y, 0); - assertEq(f.z, 0); - assertEq(f.w, -1); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4scale.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4scale.js deleted file mode 100644 index 923c878f1d9..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/float32x4scale.js +++ /dev/null @@ -1,41 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'float32x4 scale'; - -function mulf(a, b) { - return Math.fround(Math.fround(a) * Math.fround(b)); -} - -function test() { - print(BUGNUMBER + ": " + summary); - - var a = float32x4(1, 2, 3, 4); - var c = SIMD.float32x4.scale(a, 2); - assertEq(c.x, 2); - assertEq(c.y, 4); - assertEq(c.z, 6); - assertEq(c.w, 8); - - var d = float32x4(1.34, 2.76, 3.21, 4.09); - var f = float32x4.scale(d, 2.54); - assertEq(f.x, mulf(1.34, 2.54)); - assertEq(f.y, mulf(2.76, 2.54)); - assertEq(f.z, mulf(3.21, 2.54)); - assertEq(f.w, mulf(4.09, 2.54)); - - var g = float32x4(NaN, -0, Infinity, -Infinity); - var i = float32x4.scale(g, 2.54); - assertEq(i.x, NaN); - assertEq(i.y, -0); - assertEq(i.z, Infinity); - assertEq(i.w, -Infinity); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4equal.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4equal.js deleted file mode 100644 index 693e7a2cbdd..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/int32x4equal.js +++ /dev/null @@ -1,24 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 996076; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'int32x4 equal'; - -function test() { - print(BUGNUMBER + ": " + summary); - - var a = int32x4(1, 20, 30, 40); - var b = int32x4(10, 20, 30, 4); - var c = SIMD.int32x4.equal(a, b); - assertEq(c.x, 0); - assertEq(c.y, -1); - assertEq(c.z, -1); - assertEq(c.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4greaterthan.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4greaterthan.js deleted file mode 100644 index 74c82a056a5..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/int32x4greaterthan.js +++ /dev/null @@ -1,24 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 996076; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'int32x4 greaterThan'; - -function test() { - print(BUGNUMBER + ": " + summary); - - var a = int32x4(1, 20, 3, 40); - var b = int32x4(10, 2, 30, 4); - var c = SIMD.int32x4.greaterThan(b,a); - assertEq(c.x, -1); - assertEq(c.y, 0); - assertEq(c.z, -1); - assertEq(c.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4lessthan.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4lessthan.js deleted file mode 100644 index 1f2a2a63f8d..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/int32x4lessthan.js +++ /dev/null @@ -1,24 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 996076; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'int32x4 lessThan'; - -function test() { - print(BUGNUMBER + ": " + summary); - - var a = int32x4(1, 20, 3, 40); - var b = int32x4(10, 2, 30, 4); - var c = SIMD.int32x4.lessThan(a, b); - assertEq(c.x, -1); - assertEq(c.y, 0); - assertEq(c.z, -1); - assertEq(c.w, 0); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4withflag.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4withflag.js deleted file mode 100644 index 7263dc5f751..00000000000 --- a/js/src/tests/ecma_6/TypedObject/simd/int32x4withflag.js +++ /dev/null @@ -1,26 +0,0 @@ -// |reftest| skip-if(!this.hasOwnProperty("SIMD")) -var BUGNUMBER = 946042; -var float32x4 = SIMD.float32x4; -var int32x4 = SIMD.int32x4; - -var summary = 'int32x4 with'; - -function test() { - print(BUGNUMBER + ": " + summary); - - var a = int32x4(1, 2, 3, 4); - var x = SIMD.int32x4.withFlagX(a, true); - var y = SIMD.int32x4.withFlagY(a, false); - var z = SIMD.int32x4.withFlagZ(a, false); - var w = SIMD.int32x4.withFlagW(a, true); - assertEq(x.x, -1); - assertEq(y.y, 0); - assertEq(z.z, 0); - assertEq(w.w, -1); - - if (typeof reportCompare === "function") - reportCompare(true, true); -} - -test(); - diff --git a/layout/forms/crashtests/1102791.html b/layout/forms/crashtests/1102791.html new file mode 100644 index 00000000000..5bcdd80a660 --- /dev/null +++ b/layout/forms/crashtests/1102791.html @@ -0,0 +1,33 @@ + + + + Testcase for bug 1102791 + + + + + + + + diff --git a/layout/forms/crashtests/crashtests.list b/layout/forms/crashtests/crashtests.list index 1a12f83e532..87cfa6df53b 100644 --- a/layout/forms/crashtests/crashtests.list +++ b/layout/forms/crashtests/crashtests.list @@ -59,3 +59,4 @@ load 944198.html load 949891.xhtml load 959311.html load 960277-2.html +load 1102791.html diff --git a/layout/forms/nsHTMLButtonControlFrame.cpp b/layout/forms/nsHTMLButtonControlFrame.cpp index 400ba73d317..e0b3bcbdd0e 100644 --- a/layout/forms/nsHTMLButtonControlFrame.cpp +++ b/layout/forms/nsHTMLButtonControlFrame.cpp @@ -203,6 +203,11 @@ nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext, FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus); + // We're always complete and we don't support overflow containers + // so we shouldn't have a next-in-flow ever. + aStatus = NS_FRAME_COMPLETE; + MOZ_ASSERT(!GetNextInFlow()); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); } diff --git a/layout/generic/nsAbsoluteContainingBlock.cpp b/layout/generic/nsAbsoluteContainingBlock.cpp index 5f9ca5e8c06..316ed330960 100644 --- a/layout/generic/nsAbsoluteContainingBlock.cpp +++ b/layout/generic/nsAbsoluteContainingBlock.cpp @@ -132,7 +132,8 @@ nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, aConstrainHeight, kidFrame, kidStatus, aOverflowAreas); nsIFrame* nextFrame = kidFrame->GetNextInFlow(); - if (!NS_FRAME_IS_FULLY_COMPLETE(kidStatus)) { + if (!NS_FRAME_IS_FULLY_COMPLETE(kidStatus) && + aDelegatingFrame->IsFrameOfType(nsIFrame::eCanContainOverflowContainers)) { // Need a continuation if (!nextFrame) { nextFrame = diff --git a/mfbt/WindowsVersion.h b/mfbt/WindowsVersion.h index fcf24acc2e9..2e9621cb5d6 100644 --- a/mfbt/WindowsVersion.h +++ b/mfbt/WindowsVersion.h @@ -84,6 +84,44 @@ IsWindowsBuildOrLater(uint32_t aBuild) return false; } +#if defined(_M_X64) || defined(_M_AMD64) +// We support only Win7 or later on Win64. +MOZ_ALWAYS_INLINE bool +IsXPSP3OrLater() +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +IsWin2003OrLater() +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +IsWin2003SP2OrLater() +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +IsVistaOrLater() +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +IsVistaSP1OrLater() +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +IsWin7OrLater() +{ + return true; +} +#else MOZ_ALWAYS_INLINE bool IsXPSP3OrLater() { @@ -119,6 +157,7 @@ IsWin7OrLater() { return IsWindowsVersionOrLater(0x06010000ul); } +#endif MOZ_ALWAYS_INLINE bool IsWin7SP1OrLater() diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 2593bde3eab..f936467ab5b 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -7,6 +7,7 @@ package org.mozilla.gecko; import java.io.File; import java.io.FileNotFoundException; +import java.lang.Override; import java.lang.reflect.Method; import java.net.URLEncoder; import java.util.EnumSet; @@ -24,6 +25,7 @@ import org.mozilla.gecko.DynamicToolbar.VisibilityTransition; import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.Tabs.TabEvents; import org.mozilla.gecko.animation.PropertyAnimator; +import org.mozilla.gecko.animation.TransitionsTracker; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserContract.SearchHistory; @@ -1971,6 +1973,8 @@ public class BrowserApp extends GeckoApp final PropertyAnimator animator = new PropertyAnimator(250); animator.setUseHardwareLayer(false); + TransitionsTracker.track(animator); + mBrowserToolbar.startEditing(url, animator); final String panelId = selectedTab.getMostRecentHomePanel(); diff --git a/mobile/android/base/animation/TransitionsTracker.java b/mobile/android/base/animation/TransitionsTracker.java new file mode 100644 index 00000000000..78672b9a18e --- /dev/null +++ b/mobile/android/base/animation/TransitionsTracker.java @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.animation; + +import com.nineoldandroids.animation.Animator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mozilla.gecko.animation.PropertyAnimator; +import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener; +import org.mozilla.gecko.util.ThreadUtils; + + +/** + * {@link TransitionsTracker} provides a simple API to avoid running layout code + * during UI transitions. You should use it whenever you need to time-shift code + * that will likely trigger a layout traversal during an animation. + */ +public class TransitionsTracker { + private static final ArrayList pendingActions = new ArrayList<>(); + private static int transitionCount; + + private static final PropertyAnimationListener propertyAnimatorListener = + new PropertyAnimationListener() { + @Override + public void onPropertyAnimationStart() { + pushTransition(); + } + + @Override + public void onPropertyAnimationEnd() { + popTransition(); + } + }; + + private static final Animator.AnimatorListener animatorListener = + new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + pushTransition(); + } + + @Override + public void onAnimationEnd(Animator animation) { + popTransition(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }; + + private static void runPendingActions() { + ThreadUtils.assertOnUiThread(); + + final int size = pendingActions.size(); + for (int i = 0; i < size; i++) { + pendingActions.get(i).run(); + } + + pendingActions.clear(); + } + + public static void pushTransition() { + ThreadUtils.assertOnUiThread(); + transitionCount++; + } + + public static void popTransition() { + ThreadUtils.assertOnUiThread(); + transitionCount--; + + if (transitionCount < 0) { + throw new IllegalStateException("Invalid transition stack update"); + } + + if (transitionCount == 0) { + runPendingActions(); + } + } + + public static boolean areTransitionsRunning() { + ThreadUtils.assertOnUiThread(); + return (transitionCount > 0); + } + + public static void track(PropertyAnimator animator) { + ThreadUtils.assertOnUiThread(); + animator.addPropertyAnimationListener(propertyAnimatorListener); + } + + public static void track(Animator animator) { + ThreadUtils.assertOnUiThread(); + animator.addListener(animatorListener); + } + + public static boolean cancelPendingAction(Runnable action) { + ThreadUtils.assertOnUiThread(); + return pendingActions.removeAll(Collections.singleton(action)); + } + + public static void runAfterTransitions(Runnable action) { + ThreadUtils.assertOnUiThread(); + + if (transitionCount == 0) { + action.run(); + } else { + pendingActions.add(action); + } + } +} \ No newline at end of file diff --git a/mobile/android/base/home/BookmarksPanel.java b/mobile/android/base/home/BookmarksPanel.java index 73fc480d6ad..077114a41e6 100644 --- a/mobile/android/base/home/BookmarksPanel.java +++ b/mobile/android/base/home/BookmarksPanel.java @@ -216,7 +216,7 @@ public class BookmarksPanel extends HomeFragment { /** * Loader callbacks for the LoaderManager of this fragment. */ - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { if (args == null) { @@ -229,7 +229,7 @@ public class BookmarksPanel extends HomeFragment { } @Override - public void onLoadFinished(Loader loader, Cursor c) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { BookmarksLoader bl = (BookmarksLoader) loader; mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType()); updateUiFromCursor(c); @@ -237,6 +237,8 @@ public class BookmarksPanel extends HomeFragment { @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); + if (mList != null) { mListAdapter.swapCursor(null); } diff --git a/mobile/android/base/home/DynamicPanel.java b/mobile/android/base/home/DynamicPanel.java index 812518c52fe..62e7738ea07 100644 --- a/mobile/android/base/home/DynamicPanel.java +++ b/mobile/android/base/home/DynamicPanel.java @@ -262,12 +262,6 @@ public class DynamicPanel extends HomeFragment { public void requestDataset(DatasetRequest request) { Log.d(LOGTAG, "Requesting request: " + request); - // Ignore dataset requests while the fragment is not - // allowed to load its content. - if (!getCanLoadHint()) { - return; - } - final Bundle bundle = new Bundle(); bundle.putParcelable(DATASET_REQUEST, request); @@ -352,7 +346,7 @@ public class DynamicPanel extends HomeFragment { /** * LoaderCallbacks implementation that interacts with the LoaderManager. */ - private class PanelLoaderCallbacks implements LoaderCallbacks { + private class PanelLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { final DatasetRequest request = (DatasetRequest) args.getParcelable(DATASET_REQUEST); @@ -362,7 +356,7 @@ public class DynamicPanel extends HomeFragment { } @Override - public void onLoadFinished(Loader loader, Cursor cursor) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor cursor) { final DatasetRequest request = getRequestFromLoader(loader); Log.d(LOGTAG, "Finished loader for request: " + request); @@ -373,6 +367,8 @@ public class DynamicPanel extends HomeFragment { @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); + final DatasetRequest request = getRequestFromLoader(loader); Log.d(LOGTAG, "Resetting loader for request: " + request); diff --git a/mobile/android/base/home/HistoryPanel.java b/mobile/android/base/home/HistoryPanel.java index 4d5d6d954fa..8d1003cf910 100644 --- a/mobile/android/base/home/HistoryPanel.java +++ b/mobile/android/base/home/HistoryPanel.java @@ -471,20 +471,21 @@ public class HistoryPanel extends HomeFragment { } } - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new HistoryCursorLoader(getActivity()); } @Override - public void onLoadFinished(Loader loader, Cursor c) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { mAdapter.swapCursor(c); updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); mAdapter.swapCursor(null); } } diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 826d334ecf1..5d16c8f05c1 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -223,7 +223,7 @@ public class HomePager extends ViewPager { final HomeAdapter adapter = new HomeAdapter(mContext, fm); adapter.setOnAddPanelListener(mAddPanelListener); - adapter.setCanLoadHint(!shouldAnimate); + adapter.setCanLoadHint(true); setAdapter(adapter); // Don't show the tabs strip until we have the @@ -243,7 +243,6 @@ public class HomePager extends ViewPager { @Override public void onPropertyAnimationEnd() { setLayerType(View.LAYER_TYPE_NONE, null); - adapter.setCanLoadHint(true); } }); @@ -373,9 +372,7 @@ public class HomePager extends ViewPager { final HomeAdapter adapter = (HomeAdapter) getAdapter(); // Disable any fragment loading until we have the initial - // panel selection done. Store previous value to restore - // it if necessary once the UI is fully updated. - final boolean canLoadHint = adapter.getCanLoadHint(); + // panel selection done. adapter.setCanLoadHint(false); // Destroy any existing panels currently loaded @@ -436,19 +433,15 @@ public class HomePager extends ViewPager { } } - // If the load hint was originally true, this means the pager - // is not animating and it's fine to restore the load hint back. - if (canLoadHint) { - // The selection is updated asynchronously so we need to post to - // UI thread to give the pager time to commit the new page selection - // internally and load the right initial panel. - ThreadUtils.getUiHandler().post(new Runnable() { - @Override - public void run() { - adapter.setCanLoadHint(true); - } - }); - } + // The selection is updated asynchronously so we need to post to + // UI thread to give the pager time to commit the new page selection + // internally and load the right initial panel. + ThreadUtils.getUiHandler().post(new Runnable() { + @Override + public void run() { + adapter.setCanLoadHint(true); + } + }); } public void setOnPanelChangeListener(OnPanelChangeListener listener) { diff --git a/mobile/android/base/home/HomePagerTabStrip.java b/mobile/android/base/home/HomePagerTabStrip.java index 88b8596ed61..b603b60a25b 100644 --- a/mobile/android/base/home/HomePagerTabStrip.java +++ b/mobile/android/base/home/HomePagerTabStrip.java @@ -8,6 +8,7 @@ package org.mozilla.gecko.home; import org.mozilla.gecko.R; import org.mozilla.gecko.animation.BounceAnimator; import org.mozilla.gecko.animation.BounceAnimator.Attributes; +import org.mozilla.gecko.animation.TransitionsTracker; import android.content.Context; import android.content.res.Resources; @@ -120,6 +121,8 @@ class HomePagerTabStrip extends PagerTabStrip { nextBounceAnimator.queue(new Attributes(0, BOUNCE4_MS)); nextBounceAnimator.setStartDelay(ANIMATION_DELAY_MS); + TransitionsTracker.track(nextBounceAnimator); + // Start animations. alphaAnimatorSet.start(); prevBounceAnimator.start(); diff --git a/mobile/android/base/home/ReadingListPanel.java b/mobile/android/base/home/ReadingListPanel.java index 5eb8666b30f..365e6309f3f 100644 --- a/mobile/android/base/home/ReadingListPanel.java +++ b/mobile/android/base/home/ReadingListPanel.java @@ -197,20 +197,21 @@ public class ReadingListPanel extends HomeFragment { /** * LoaderCallbacks implementation that interacts with the LoaderManager. */ - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new ReadingListLoader(getActivity()); } @Override - public void onLoadFinished(Loader loader, Cursor c) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { mAdapter.swapCursor(c); updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); mAdapter.swapCursor(null); } } diff --git a/mobile/android/base/home/RecentTabsPanel.java b/mobile/android/base/home/RecentTabsPanel.java index d62013aafd6..9e55ccd2fe3 100644 --- a/mobile/android/base/home/RecentTabsPanel.java +++ b/mobile/android/base/home/RecentTabsPanel.java @@ -404,20 +404,21 @@ public class RecentTabsPanel extends HomeFragment } } - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new RecentTabsCursorLoader(getActivity(), mClosedTabs); } @Override - public void onLoadFinished(Loader loader, Cursor c) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { mAdapter.swapCursor(c); updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); mAdapter.swapCursor(null); } } diff --git a/mobile/android/base/home/RemoteTabsExpandableListFragment.java b/mobile/android/base/home/RemoteTabsExpandableListFragment.java index 1f263b6408e..8a093e3fd4f 100644 --- a/mobile/android/base/home/RemoteTabsExpandableListFragment.java +++ b/mobile/android/base/home/RemoteTabsExpandableListFragment.java @@ -392,14 +392,14 @@ public class RemoteTabsExpandableListFragment extends HomeFragment implements Re } } - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new RemoteTabsCursorLoader(getActivity()); } @Override - public void onLoadFinished(Loader loader, Cursor c) { + public void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { final List clients = TabsAccessor.getClientsFromCursor(c); // Filter the hidden clients out of the clients list. The clients @@ -421,6 +421,7 @@ public class RemoteTabsExpandableListFragment extends HomeFragment implements Re @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); mAdapter.replaceClients(null); } } diff --git a/mobile/android/base/home/TopSitesPanel.java b/mobile/android/base/home/TopSitesPanel.java index c0f0cf1c7d8..8c95d4626c0 100644 --- a/mobile/android/base/home/TopSitesPanel.java +++ b/mobile/android/base/home/TopSitesPanel.java @@ -689,7 +689,7 @@ public class TopSitesPanel extends HomeFragment { } } - private class CursorLoaderCallbacks implements LoaderCallbacks { + private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { trace("Creating TopSitesLoader: " + id); @@ -707,7 +707,7 @@ public class TopSitesPanel extends HomeFragment { * Why that is... dunno. */ @Override - public void onLoadFinished(Loader loader, Cursor c) { + protected void onLoadFinishedAfterTransitions(Loader loader, Cursor c) { debug("onLoadFinished: " + c.getCount() + " rows."); mListAdapter.swapCursor(c); @@ -752,6 +752,8 @@ public class TopSitesPanel extends HomeFragment { @Override public void onLoaderReset(Loader loader) { + super.onLoaderReset(loader); + if (mListAdapter != null) { mListAdapter.swapCursor(null); } diff --git a/mobile/android/base/home/TransitionAwareCursorLoaderCallbacks.java b/mobile/android/base/home/TransitionAwareCursorLoaderCallbacks.java new file mode 100644 index 00000000000..acf514cc697 --- /dev/null +++ b/mobile/android/base/home/TransitionAwareCursorLoaderCallbacks.java @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.home; + +import android.database.Cursor; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.Loader; + +import org.mozilla.gecko.animation.TransitionsTracker; + +/** + * A {@link LoaderCallbacks} implementation that avoids running its + * {@link #onLoadFinished(Loader, Cursor)} method during animations as it's + * likely to trigger a layout traversal as a result of a cursor swap in the + * target adapter. + */ +public abstract class TransitionAwareCursorLoaderCallbacks implements LoaderCallbacks { + private OnLoadFinishedRunnable onLoadFinishedRunnable; + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + if (onLoadFinishedRunnable != null) { + TransitionsTracker.cancelPendingAction(onLoadFinishedRunnable); + } + + onLoadFinishedRunnable = new OnLoadFinishedRunnable(loader, c); + TransitionsTracker.runAfterTransitions(onLoadFinishedRunnable); + } + + protected abstract void onLoadFinishedAfterTransitions(Loader loade, Cursor c); + + @Override + public void onLoaderReset(Loader loader) { + if (onLoadFinishedRunnable != null) { + TransitionsTracker.cancelPendingAction(onLoadFinishedRunnable); + onLoadFinishedRunnable = null; + } + } + + private class OnLoadFinishedRunnable implements Runnable { + private final Loader loader; + private final Cursor cursor; + + public OnLoadFinishedRunnable(Loader loader, Cursor cursor) { + this.loader = loader; + this.cursor = cursor; + } + + @Override + public void run() { + onLoadFinishedAfterTransitions(loader, cursor); + onLoadFinishedRunnable = null; + } + } +} \ No newline at end of file diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 24f278eb0be..ea2b81f3861 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -124,6 +124,7 @@ gbjar.sources += [ 'animation/HeightChangeAnimation.java', 'animation/PropertyAnimator.java', 'animation/Rotate3DAnimation.java', + 'animation/TransitionsTracker.java', 'animation/ViewHelper.java', 'ANRReporter.java', 'AppNotificationClient.java', @@ -310,6 +311,7 @@ gbjar.sources += [ 'home/TopSitesGridView.java', 'home/TopSitesPanel.java', 'home/TopSitesThumbnailView.java', + 'home/TransitionAwareCursorLoaderCallbacks.java', 'home/TwoLinePageRow.java', 'InputMethods.java', 'IntentHelper.java', diff --git a/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml b/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml index 7e77abe050e..e2e047b1d7b 100644 --- a/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml +++ b/mobile/android/base/newtablet/res/layout-large-v11/tab_strip.xml @@ -12,6 +12,8 @@ android:layout_weight="1" android:paddingTop="4dp"/> + cert); + } +} + +// There are various things that we want to measure about certificate +// chains that we accept. This is a single entry point for all of them. +void +GatherSuccessfulValidationTelemetry(const ScopedCERTCertList& certList) +{ + GatherBaselineRequirementsTelemetry(certList); + GatherRootCATelemetry(certList); +} + SECStatus AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject, @@ -1003,7 +1032,8 @@ AuthCertificate(CertVerifier& certVerifier, } if (rv == SECSuccess) { - GatherBaselineRequirementsTelemetry(certList); + GatherSuccessfulValidationTelemetry(certList); + // The connection may get terminated, for example, if the server requires // a client cert. Let's provide a minimal SSLStatus // to the caller that contains at least the cert and its status. diff --git a/security/manager/ssl/src/moz.build b/security/manager/ssl/src/moz.build index cd8371d08fb..ee3cfa13957 100644 --- a/security/manager/ssl/src/moz.build +++ b/security/manager/ssl/src/moz.build @@ -77,6 +77,10 @@ SOURCES += [ 'PSMContentListener.cpp', ] +LOCAL_INCLUDES += [ + '/security/manager/boot/src', +] + if not CONFIG['MOZ_NO_SMART_CARDS']: UNIFIED_SOURCES += [ 'nsSmartCardMonitor.cpp', diff --git a/testing/marionette/client/marionette/errors.py b/testing/marionette/client/marionette/errors.py index aee6aa24c52..cc9722c9e37 100644 --- a/testing/marionette/client/marionette/errors.py +++ b/testing/marionette/client/marionette/errors.py @@ -31,6 +31,7 @@ class ErrorCodes(object): INVALID_RESPONSE = 53 FRAME_SEND_NOT_INITIALIZED_ERROR = 54 FRAME_SEND_FAILURE_ERROR = 55 + FRAME_NOT_RESPONDING = 56 UNSUPPORTED_OPERATION = 405 MARIONETTE_ERROR = 500 diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index 010d83e9c14..93099901631 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -1584,3 +1584,13 @@ class Marionette(object): """ return self._send_message("maximizeWindow", "ok") + + def set_frame_timeout(self, timeout): + """ Set the OOP frame timeout value in ms. When focus is on a + remote frame, if the heartbeat pong is not received within this + specified value, the frame will timeout. + + :param timeout: The frame timeout value in ms. + """ + + return self._send_message("setFrameTimeout", "ok", ms=timeout) diff --git a/testing/marionette/client/marionette/tests/unit/test_set_frame_timeout.py b/testing/marionette/client/marionette/tests/unit/test_set_frame_timeout.py new file mode 100644 index 00000000000..c798508094a --- /dev/null +++ b/testing/marionette/client/marionette/tests/unit/test_set_frame_timeout.py @@ -0,0 +1,15 @@ +# 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/. + +from errors import MarionetteException +from marionette_test import MarionetteTestCase + +class TestSetFrameTimeout(MarionetteTestCase): + + def test_set_valid_frame_timeout(self): + self.marionette.set_frame_timeout(10000) + + def test_set_invalid_frame_timeout(self): + with self.assertRaisesRegexp(MarionetteException, "Not a number"): + self.marionette.set_frame_timeout("timeout") diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/client/marionette/tests/unit/unit-tests.ini index fea2b46eae1..5f18c92a7e5 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -133,4 +133,5 @@ b2g = false [test_set_window_size.py] b2g = false skip-if = os == "linux" # Bug 1085717 +[test_set_frame_timeout.py] [test_with_using_context.py] diff --git a/testing/marionette/marionette-frame-manager.js b/testing/marionette/marionette-frame-manager.js index 8243e92b7a1..6ba28ef35dc 100644 --- a/testing/marionette/marionette-frame-manager.js +++ b/testing/marionette/marionette-frame-manager.js @@ -108,6 +108,16 @@ FrameManager.prototype = { let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm + // Grab the app name + let appName = null; + try { + appName = oopFrame.getAttribute("mozapp"); + } + catch(e) { + appName = "mozapp name unavailable"; + logger.info("Error getting mozapp: " + e.result) + } + // See if this frame already has our frame script loaded in it; if so, // just wake it up. for (let i = 0; i < remoteFrames.length; i++) { @@ -133,7 +143,7 @@ FrameManager.prototype = { } mm.sendAsyncMessage("Marionette:restart", {}); - return oopFrame.id; + return [oopFrame.id, appName]; } } @@ -150,7 +160,7 @@ FrameManager.prototype = { aFrame.specialPowersObserver = new specialpowers.SpecialPowersObserver(); aFrame.specialPowersObserver.init(mm); - return oopFrame.id; + return [oopFrame.id, appName]; }, /* @@ -166,6 +176,22 @@ FrameManager.prototype = { this.handledModal = false; }, + /* + * Remove specified frame from the remote frames list + */ + removeRemoteFrame: function FM_removeRemoteFrame(frameId) { + logger.info("Deleting frame from remote frames list: " + frameId); + startLen = remoteFrames.length; + for (let i = 0; i < remoteFrames.length; i++) { + if (remoteFrames[i].frameId == frameId) { + remoteFrames.splice(i, 1); + } + } + if (remoteFrames.length == startLen) { + logger.info("Frame not found in remote frames list"); + } + }, + /** * This function removes any SpecialPowersObservers from OOP frames. */ @@ -205,9 +231,11 @@ FrameManager.prototype = { messageManager.addWeakMessageListener("Marionette:addCookie", this.server); messageManager.addWeakMessageListener("Marionette:getVisibleCookies", this.server); messageManager.addWeakMessageListener("Marionette:deleteCookie", this.server); + messageManager.addWeakMessageListener("Marionette:pong", this.server); messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this); messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this); + messageManager.addWeakMessageListener("Marionette:startHeartbeat", this.server); }, /** @@ -236,8 +264,10 @@ FrameManager.prototype = { messageManager.removeWeakMessageListener("Marionette:addCookie", this.server); messageManager.removeWeakMessageListener("Marionette:getVisibleCookies", this.server); messageManager.removeWeakMessageListener("Marionette:deleteCookie", this.server); + messageManager.removeWeakMessageListener("Marionette:pong", this.server); messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this); messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); + messageManager.removeWeakMessageListener("Marionette:startHeartbeat", this.server); }, }; diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index c6f39a38565..9be0c1d618d 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -188,6 +188,7 @@ function startListeners() { addMessageListenerId("Marionette:getCookies", getCookies); addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies); addMessageListenerId("Marionette:deleteCookie", deleteCookie); + addMessageListenerId("Marionette:ping", ping); } /** @@ -290,6 +291,7 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:getCookies", getCookies); removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies); removeMessageListenerId("Marionette:deleteCookie", deleteCookie); + removeMessageListenerId("Marionette:ping", ping); if (isB2G) { content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false); } @@ -1287,6 +1289,8 @@ function get(msg) { if (curFrame.document.readyState == "complete") { removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); sendOk(command_id); + // Restart the OOP frame heartbeat now that the URL is loaded + sendToServer("Marionette:startHeartbeat"); } else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) { @@ -1918,6 +1922,13 @@ function getAppCacheStatus(msg) { msg.json.command_id); } +/** + * Received heartbeat ping + */ +function ping(msg) { + sendToServer("Marionette:pong", {}, msg.json.command_id); +} + // emulator callbacks let _emu_cb_id = 0; let _emu_cbs = {}; diff --git a/testing/marionette/marionette-server.js b/testing/marionette/marionette-server.js index 5ca1188335a..4fe12e2456e 100644 --- a/testing/marionette/marionette-server.js +++ b/testing/marionette/marionette-server.js @@ -98,18 +98,14 @@ function FrameSendNotInitializedError(frame) { this.code = 54; this.frame = frame; this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)"; - this.toString = function() { - return this.message + " " + this.frame + "; frame has closed."; - } + this.errMsg = this.message + " " + this.frame + "; frame has closed."; } function FrameSendFailureError(frame) { this.code = 55; this.frame = frame; this.message = "Error sending message to frame (NS_ERROR_FAILURE)"; - this.toString = function() { - return this.message + " " + this.frame + "; frame not responding."; - } + this.errMsg = this.message + " " + this.frame + "; frame not responding."; } /** @@ -155,6 +151,11 @@ function MarionetteServerConnection(aPrefix, aTransport, aServer) this.currentFrameElement = null; this.testName = null; this.mozBrowserClose = null; + this.frameHeartbeatTimer = null; + this.frameHeartbeatLastPong = null; + this.frameHeartbeatLastApp = null; + this.frameHeartbeatExceptionPending = false; + this.frameTimeout = 5000; // default, set with setFrameTimeout this.oopFrameId = null; // frame ID of current remote frame, used for mozbrowserclose events this.sessionCapabilities = { // Mandated capabilities @@ -243,11 +244,21 @@ MarionetteServerConnection.prototype = { * @param object values * Object to send to the listener */ - sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure) { + sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure, throwError) { let success = true; if (commandId) { values.command_id = commandId; } + if (typeof(throwError) !== "boolean") { + throwError = false; + } + if (this.frameHeartbeatExceptionPending) { + // Previous frame was not responding; send exception indicating have switched to system frame + this.frameHeartbeatExceptionPending = false; + let errorTxt = "Frame not responding (" + this.frameHeartbeatLastApp + "), switching to root frame"; + this.sendError(errorTxt, 56, null, this.command_id); + return false; + } if (this.curBrowser.frameManager.currentRemoteFrame !== null) { try { this.messageManager.sendAsyncMessage( @@ -268,7 +279,12 @@ MarionetteServerConnection.prototype = { break; } let code = error.hasOwnProperty('code') ? e.code : 500; - this.sendError(error.toString(), code, error.stack, commandId); + if (throwError == false) { + this.sendError(error.toString(), code, error.stack, commandId); + } + else { + throw {message:"sendAsync failed: " + error.hasOwnProperty('type'), code, stack:null}; + } } } } @@ -724,6 +740,10 @@ MarionetteServerConnection.prototype = { else { this.context = context; this.sendOk(this.command_id); + // Stop the OOP frame heartbeat if switched into chrome + if (context == "chrome") { + this.stopHeartbeat(); + } } }, @@ -1179,6 +1199,8 @@ MarionetteServerConnection.prototype = { if (this.context != "chrome") { aRequest.command_id = command_id; aRequest.parameters.pageTimeout = this.pageTimeout; + // stop OOP frame heartbeat if it's running, so it won't timeout during URL load + this.stopHeartbeat(); this.sendAsync("get", aRequest.parameters, command_id); return; } @@ -1471,7 +1493,6 @@ MarionetteServerConnection.prototype = { this.sendError("Error loading page", 13, null, command_id); return; } - checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); } if (this.context == "chrome") { @@ -2327,6 +2348,7 @@ MarionetteServerConnection.prototype = { */ deleteSession: function MDA_deleteSession() { let command_id = this.command_id = this.getCommandId(); + this.stopHeartbeat(); try { this.sessionTearDown(); } @@ -2641,6 +2663,22 @@ MarionetteServerConnection.prototype = { this.sendOk(this.command_id); }, + /** + * Sets the OOP frame timeout value (ms) + */ + setFrameTimeout: function MDA_setFrameTimeout(aRequest) { + this.command_id = this.getCommandId(); + let timeout = parseInt(aRequest.parameters.ms); + if (isNaN(timeout)) { + this.sendError("Not a number", 500, null, this.command_id); + return; + } + else { + this.frameTimeout = timeout; + } + this.sendOk(this.command_id); + }, + /** * Helper function to convert an outerWindowID into a UID that Marionette * tracks. @@ -2650,6 +2688,54 @@ MarionetteServerConnection.prototype = { return uid; }, + /** + * Start the OOP frame heartbeat + */ + startHeartbeat: function MDA_startHeartbeat() { + this.frameHeartbeatLastPong = new Date().getTime(); + function pulse() { + let noResponse = false; + let now = new Date().getTime(); + let elapsed = now - this.frameHeartbeatLastPong; + try { + if (elapsed > this.frameTimeout) { + throw {message:null, code:56, stack:null}; + } + let result = this.sendAsync("ping", {}, this.command_id, false, true); + if (result == false) { + throw {message:null, code:56, stack:null}; + } + } + catch (e) { + let lastApp = this.frameHeartbeatLastApp ? this.frameHeartbeatLastApp : "undefined"; + this.stopHeartbeat(); + this.curBrowser.frameManager.removeRemoteFrame(this.curBrowser.frameManager.currentRemoteFrame.frameId); + this.switchToGlobalMessageManager(); + // If there is an active request, send back an exception now, otherwise wait until next request + if (this.command_id) { + let errorTxt = "Frame not responding (" + lastApp + "), switching to root frame"; + this.sendError(errorTxt, e.code, e.stack, this.command_id); + } + else { + this.frameHeartbeatExceptionPending = true; + } + return; + } + } + this.frameHeartbeatTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this.frameHeartbeatTimer.initWithCallback(pulse.bind(this), 500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP); + }, + + /** + * Stop the OOP frame heartbeat + */ + stopHeartbeat: function MDA_stopHeartbeat() { + if (this.frameHeartbeatTimer !== null) { + this.frameHeartbeatTimer.cancel(); + this.frameHeartbeatTimer = null; + } + }, + /** * Receives all messages from content messageManager */ @@ -2686,7 +2772,7 @@ MarionetteServerConnection.prototype = { this.sendToClient(message.json, -1); break; case "Marionette:switchToFrame": - this.oopFrameId = this.curBrowser.frameManager.switchToFrame(message); + [this.oopFrameId, this.frameHeartbeatLastApp] = this.curBrowser.frameManager.switchToFrame(message); this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get(); break; case "Marionette:switchToModalOrigin": @@ -2707,6 +2793,7 @@ MarionetteServerConnection.prototype = { } this.currentFrameElement = message.json.frameValue; } + this.stopHeartbeat(); break; case "Marionette:getVisibleCookies": let [currentPath, host] = message.json.value; @@ -2770,6 +2857,7 @@ MarionetteServerConnection.prototype = { // is from a remote frame. this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value); this.sendOk(this.command_id); + this.startHeartbeat(); } let browserType; @@ -2820,6 +2908,12 @@ MarionetteServerConnection.prototype = { globalMessageManager.broadcastAsyncMessage( "MarionetteMainListener:emitTouchEvent", message.json); return; + case "Marionette:pong": + this.frameHeartbeatLastPong = new Date().getTime(); + break; + case "Marionette:startHeartbeat": + this.startHeartbeat(); + break; } } }; @@ -2903,7 +2997,8 @@ MarionetteServerConnection.prototype.requestTypes = { "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation, "getWindowSize": MarionetteServerConnection.prototype.getWindowSize, "setWindowSize": MarionetteServerConnection.prototype.setWindowSize, - "maximizeWindow": MarionetteServerConnection.prototype.maximizeWindow + "maximizeWindow": MarionetteServerConnection.prototype.maximizeWindow, + "setFrameTimeout": MarionetteServerConnection.prototype.setFrameTimeout }; /** diff --git a/testing/mozbase/mozlog/setup.py b/testing/mozbase/mozlog/setup.py index 65991cfad42..7463b9016af 100644 --- a/testing/mozbase/mozlog/setup.py +++ b/testing/mozbase/mozlog/setup.py @@ -5,7 +5,7 @@ from setuptools import setup, find_packages PACKAGE_NAME = 'mozlog' -PACKAGE_VERSION = '2.7' +PACKAGE_VERSION = '2.8' setup(name=PACKAGE_NAME, version=PACKAGE_VERSION, diff --git a/testing/web-platform/harness/setup.py b/testing/web-platform/harness/setup.py index 54c3c5e9ce2..be5b560a5e6 100644 --- a/testing/web-platform/harness/setup.py +++ b/testing/web-platform/harness/setup.py @@ -12,7 +12,7 @@ from setuptools import setup, find_packages here = os.path.split(__file__)[0] PACKAGE_NAME = 'wptrunner' -PACKAGE_VERSION = '1.5' +PACKAGE_VERSION = '1.7' # Dependencies with open(os.path.join(here, "requirements.txt")) as f: diff --git a/testing/web-platform/harness/wptrunner/browsers/b2g_setup/certtest_app.zip b/testing/web-platform/harness/wptrunner/browsers/b2g_setup/certtest_app.zip index e69de29bb2d..ade8879b212 100644 Binary files a/testing/web-platform/harness/wptrunner/browsers/b2g_setup/certtest_app.zip and b/testing/web-platform/harness/wptrunner/browsers/b2g_setup/certtest_app.zip differ diff --git a/testing/web-platform/harness/wptrunner/executors/executorservo.py b/testing/web-platform/harness/wptrunner/executors/executorservo.py index c7703903356..e4393d731a0 100644 --- a/testing/web-platform/harness/wptrunner/executors/executorservo.py +++ b/testing/web-platform/harness/wptrunner/executors/executorservo.py @@ -42,8 +42,7 @@ class ServoTestharnessExecutor(ProcessTestExecutor): # Now wait to get the output we expect, or until we reach the timeout self.result_flag.wait(timeout + 5) - if self.result_flag.is_set(): - assert self.result_data is not None + if self.result_flag.is_set() and self.result_data is not None: self.result_data["test"] = test.url result = self.convert_result(test, self.result_data) self.proc.kill() diff --git a/testing/web-platform/harness/wptrunner/testrunner.py b/testing/web-platform/harness/wptrunner/testrunner.py index 6637d534982..8a88a73c2b5 100644 --- a/testing/web-platform/harness/wptrunner/testrunner.py +++ b/testing/web-platform/harness/wptrunner/testrunner.py @@ -359,7 +359,7 @@ class TestRunnerManager(threading.Thread): """Callback when we can't connect to the browser via marionette for some reason""" self.init_fail_count += 1 - self.logger.error("Init failed %i" % self.init_fail_count) + self.logger.warning("Init failed %i" % self.init_fail_count) self.init_timer.cancel() if self.init_fail_count < self.max_init_fails: self.restart_runner() diff --git a/testing/web-platform/harness/wptrunner/update.py b/testing/web-platform/harness/wptrunner/update.py index b0a2f9aea65..e0c9fba41f8 100644 --- a/testing/web-platform/harness/wptrunner/update.py +++ b/testing/web-platform/harness/wptrunner/update.py @@ -9,6 +9,8 @@ import sys import traceback import uuid +from mozlog.structured import commandline + import vcs from vcs import git, hg manifest = None @@ -16,6 +18,8 @@ import metadata import testloader import wptcommandline +logger = None + base_path = os.path.abspath(os.path.split(__file__)[0]) bsd_license = """W3C 3-clause BSD License @@ -49,12 +53,23 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ + def do_delayed_imports(serve_root): global manifest sys.path.insert(0, os.path.join(serve_root, "tools", "scripts")) import manifest +def setup_logging(args, defaults): + global logger + logger = commandline.setup_logging("web-platform-tests-update", args, defaults) + + for name in args.keys(): + if name.startswith("log_"): + args.pop(name) + + return logger + class RepositoryError(Exception): pass diff --git a/testing/web-platform/harness/wptrunner/vcs.py b/testing/web-platform/harness/wptrunner/vcs.py index fb49d471a7f..e24acc6d810 100644 --- a/testing/web-platform/harness/wptrunner/vcs.py +++ b/testing/web-platform/harness/wptrunner/vcs.py @@ -5,9 +5,15 @@ import subprocess from functools import partial +from mozlog.structured import get_default_logger + +logger = None def vcs(bin_name): def inner(command, *args, **kwargs): + global logger + if logger is None: + logger = get_default_logger("vcs") repo = kwargs.pop("repo", None) if kwargs: raise TypeError, kwargs @@ -19,12 +25,11 @@ def vcs(bin_name): proc_kwargs["cwd"] = repo command_line = [bin_name, command] + args - print " ".join(command_line) + logger.debug(" ".join(command_line)) try: return subprocess.check_output(command_line, **proc_kwargs) except subprocess.CalledProcessError as e: - print proc_kwargs - print e.output + logger.error(e.output) raise return inner @@ -41,5 +46,4 @@ def is_git_root(path): rv = git("rev-parse", "--show-cdup", repo=path) except subprocess.CalledProcessError: return False - print rv return rv == "\n" diff --git a/testing/web-platform/harness/wptrunner/wptcommandline.py b/testing/web-platform/harness/wptrunner/wptcommandline.py index cec95d39b45..659cd2a96cb 100644 --- a/testing/web-platform/harness/wptrunner/wptcommandline.py +++ b/testing/web-platform/harness/wptrunner/wptcommandline.py @@ -135,7 +135,6 @@ def set_from_config(kwargs): kwargs["config_path"] = config_path kwargs["config"] = config.read(kwargs["config_path"]) - kwargs["test_paths"] = OrderedDict() keys = {"paths": [("serve", "serve_root", True), ("prefs", "prefs_root", True), @@ -153,15 +152,7 @@ def set_from_config(kwargs): new_value = kwargs["config"].get(section, {}).get_path(config_value) kwargs[kw_value] = new_value - # Set up test_paths - - for section in kwargs["config"].iterkeys(): - if section.startswith("manifest:"): - manifest_opts = kwargs["config"].get(section) - url_base = manifest_opts.get("url_base", "/") - kwargs["test_paths"][url_base] = { - "tests_path": manifest_opts.get_path("tests"), - "metadata_path": manifest_opts.get_path("metadata")} + kwargs["test_paths"] = get_test_paths(kwargs["config"]) if kwargs["tests_root"]: if "/" not in kwargs["test_paths"]: @@ -173,9 +164,24 @@ def set_from_config(kwargs): kwargs["test_paths"]["/"] = {} kwargs["test_paths"]["/"]["metadata_path"] = kwargs["metadata_root"] +def get_test_paths(config): + # Set up test_paths + test_paths = OrderedDict() + + for section in config.iterkeys(): + if section.startswith("manifest:"): + manifest_opts = config.get(section) + url_base = manifest_opts.get("url_base", "/") + test_paths[url_base] = { + "tests_path": manifest_opts.get_path("tests"), + "metadata_path": manifest_opts.get_path("metadata")} + + return test_paths + + def check_args(kwargs): - from mozrunner import cli + from mozrunner import debugger_arguments set_from_config(kwargs) @@ -215,8 +221,8 @@ def check_args(kwargs): kwargs["chunk_type"] = "none" if kwargs["debugger"] is not None: - debug_args, interactive = cli.debugger_arguments(kwargs["debugger"], - kwargs["debugger_args"]) + debug_args, interactive = debugger_arguments(kwargs["debugger"], + kwargs["debugger_args"]) if interactive: require_arg(kwargs, "processes", lambda x: x == 1) kwargs["no_capture_stdio"] = True diff --git a/testing/web-platform/harness/wptrunner/wptrunner.py b/testing/web-platform/harness/wptrunner/wptrunner.py index 6896eb58ddb..3883e00b5b6 100644 --- a/testing/web-platform/harness/wptrunner/wptrunner.py +++ b/testing/web-platform/harness/wptrunner/wptrunner.py @@ -18,7 +18,8 @@ from StringIO import StringIO from multiprocessing import Queue -from mozlog.structured import commandline, stdadapter +from mozlog.structured import (commandline, stdadapter, get_default_logger, + structuredlog, handlers, formatters) import products import testloader @@ -92,6 +93,29 @@ class TestEnvironmentError(Exception): pass +class LogLevelRewriter(object): + """Filter that replaces log messages at specified levels with messages + at a different level. + + This can be used to e.g. downgrade log messages from ERROR to WARNING + in some component where ERRORs are not critical. + + :param inner: Handler to use for messages that pass this filter + :param from_levels: List of levels which should be affected + :param to_level: Log level to set for the affected messages + """ + def __init__(self, inner, from_levels, to_level): + self.inner = inner + self.from_levels = [item.upper() for item in from_levels] + self.to_level = to_level.upper() + + def __call__(self, data): + if data["action"] == "log" and data["level"].upper() in self.from_levels: + data = data.copy() + data["level"] = self.to_level + return self.inner(data) + + class TestEnvironment(object): def __init__(self, serve_path, test_paths, options): """Context manager that owns the test environment i.e. the http and @@ -108,11 +132,10 @@ class TestEnvironment(object): def __enter__(self): self.copy_required_files() + self.setup_server_logging() self.setup_routes() self.config = self.load_config() serve.set_computed_defaults(self.config) - - serve.logger = serve.default_logger("info") self.external_config, self.servers = serve.start(self.config) return self @@ -140,6 +163,18 @@ class TestEnvironment(object): return config + def setup_server_logging(self): + server_logger = get_default_logger(component="wptserve") + assert server_logger is not None + log_filter = handlers.LogLevelFilter(lambda x:x, "info") + # Downgrade errors to warnings for the server + log_filter = LogLevelRewriter(log_filter, ["error"], "warning") + server_logger.component_filter = log_filter + + serve.logger = server_logger + #Set as the default logger for wptserve + serve.set_logger(server_logger) + def setup_routes(self): for url, paths in self.test_paths.iteritems(): if url == "/": diff --git a/testing/web-platform/mach_commands.py b/testing/web-platform/mach_commands.py index 14c39003c4e..a327c16c858 100644 --- a/testing/web-platform/mach_commands.py +++ b/testing/web-platform/mach_commands.py @@ -74,6 +74,7 @@ class WebPlatformTestsUpdater(MozbuildObject): kwargs["config"] = os.path.join(self.topsrcdir, 'testing', 'web-platform', 'wptrunner.ini') wptcommandline.set_from_config(kwargs) + update.setup_logging(kwargs, {"mach": sys.stdout}) update.run_update(**kwargs) diff --git a/testing/web-platform/meta/DOMEvents/tests/approved/addEventListener.optional.useCapture.html.ini b/testing/web-platform/meta/DOMEvents/tests/approved/addEventListener.optional.useCapture.html.ini new file mode 100644 index 00000000000..76170cdc0e4 --- /dev/null +++ b/testing/web-platform/meta/DOMEvents/tests/approved/addEventListener.optional.useCapture.html.ini @@ -0,0 +1,3 @@ +[addEventListener.optional.useCapture.html] + type: testharness + expected: TIMEOUT diff --git a/testing/web-platform/meta/DOMEvents/tests/approved/dispatchEvent.return.value.html.ini b/testing/web-platform/meta/DOMEvents/tests/approved/dispatchEvent.return.value.html.ini new file mode 100644 index 00000000000..61d71362004 --- /dev/null +++ b/testing/web-platform/meta/DOMEvents/tests/approved/dispatchEvent.return.value.html.ini @@ -0,0 +1,3 @@ +[dispatchEvent.return.value.html] + type: testharness + expected: TIMEOUT diff --git a/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html.ini b/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html.ini new file mode 100644 index 00000000000..76170cdc0e4 --- /dev/null +++ b/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html.ini @@ -0,0 +1,3 @@ +[addEventListener.optional.useCapture.html] + type: testharness + expected: TIMEOUT diff --git a/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html.ini b/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html.ini new file mode 100644 index 00000000000..61d71362004 --- /dev/null +++ b/testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html.ini @@ -0,0 +1,3 @@ +[dispatchEvent.return.value.html] + type: testharness + expected: TIMEOUT diff --git a/testing/web-platform/meta/FileAPI/idlharness.html.ini b/testing/web-platform/meta/FileAPI/idlharness.html.ini index a7f9c19cd91..f00916f83b3 100644 --- a/testing/web-platform/meta/FileAPI/idlharness.html.ini +++ b/testing/web-platform/meta/FileAPI/idlharness.html.ini @@ -194,12 +194,3 @@ [FileReaderSync interface: operation readAsDataURL(Blob)] expected: FAIL - [Blob interface object length] - expected: FAIL - - [FileList interface object length] - expected: FAIL - - [FileReader interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/IndexedDB/interfaces.html.ini b/testing/web-platform/meta/IndexedDB/interfaces.html.ini index cb47cf31854..f06ae89f5d5 100644 --- a/testing/web-platform/meta/IndexedDB/interfaces.html.ini +++ b/testing/web-platform/meta/IndexedDB/interfaces.html.ini @@ -30,36 +30,3 @@ [WorkerUtils interface: attribute indexedDB] expected: FAIL - [IDBKeyRange interface object length] - expected: FAIL - - [IDBRequest interface object length] - expected: FAIL - - [IDBOpenDBRequest interface object length] - expected: FAIL - - [IDBVersionChangeEvent interface object length] - expected: FAIL - - [IDBFactory interface object length] - expected: FAIL - - [IDBDatabase interface object length] - expected: FAIL - - [IDBObjectStore interface object length] - expected: FAIL - - [IDBIndex interface object length] - expected: FAIL - - [IDBCursor interface object length] - expected: FAIL - - [IDBCursorWithValue interface object length] - expected: FAIL - - [IDBTransaction interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/XMLHttpRequest/interfaces.html.ini b/testing/web-platform/meta/XMLHttpRequest/interfaces.html.ini index f07e38a27bb..d6613ba36e6 100644 --- a/testing/web-platform/meta/XMLHttpRequest/interfaces.html.ini +++ b/testing/web-platform/meta/XMLHttpRequest/interfaces.html.ini @@ -15,9 +15,6 @@ [XMLHttpRequest interface: new XMLHttpRequest() must inherit property "statusText" with the proper type (16)] expected: FAIL - [FormData interface: existence and properties of interface object] - expected: FAIL - [FormData interface: operation delete(DOMString)] expected: FAIL @@ -108,12 +105,66 @@ [FormData interface: calling set(DOMString,DOMString) on new FormData(form) with too few arguments must throw TypeError] expected: FAIL - [XMLHttpRequestUpload interface object length] + [XMLHttpRequest interface: operation open(ByteString,USVString,boolean,USVString,USVString)] expected: FAIL - [XMLHttpRequest interface object length] + [XMLHttpRequest interface: calling open(ByteString,USVString,boolean,USVString,USVString) on new XMLHttpRequest() with too few arguments must throw TypeError] expected: FAIL - [FormData interface object length] + [FormData interface: operation delete(USVString)] + expected: FAIL + + [FormData interface: operation get(USVString)] + expected: FAIL + + [FormData interface: operation getAll(USVString)] + expected: FAIL + + [FormData interface: operation has(USVString)] + expected: FAIL + + [FormData interface: operation set(USVString,Blob,USVString)] + expected: FAIL + + [FormData interface: operation set(USVString,USVString)] + expected: FAIL + + [FormData interface: calling delete(USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling get(USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling getAll(USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling has(USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling set(USVString,Blob,USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling set(USVString,USVString) on new FormData() with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling delete(USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling get(USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling getAll(USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling has(USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling set(USVString,Blob,USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [FormData interface: calling set(USVString,USVString) on new FormData(form) with too few arguments must throw TypeError] + expected: FAIL + + [ProgressEvent interface: existence and properties of interface object] expected: FAIL diff --git a/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-repeat-no-args.htm.ini b/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-repeat-no-args.htm.ini deleted file mode 100644 index da77d3cb4a9..00000000000 --- a/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-repeat-no-args.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[send-authentication-basic-repeat-no-args.htm] - type: testharness - [XMLHttpRequest: send() - "Basic" authenticated requests with user name and password passed to open() in first request, without in second] - expected: FAIL - diff --git a/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-setrequestheader.htm.ini b/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-setrequestheader.htm.ini deleted file mode 100644 index 52962976252..00000000000 --- a/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-setrequestheader.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[send-authentication-basic-setrequestheader.htm] - type: testharness - [XMLHttpRequest: send() - "Basic" authenticated request using setRequestHeader()] - expected: FAIL - diff --git a/testing/web-platform/meta/ambient-light/idlharness.html.ini b/testing/web-platform/meta/ambient-light/idlharness.html.ini index bb14764dbbf..f2a803d6e07 100644 --- a/testing/web-platform/meta/ambient-light/idlharness.html.ini +++ b/testing/web-platform/meta/ambient-light/idlharness.html.ini @@ -9,6 +9,3 @@ [DeviceLightEvent interface: existence and properties of interface object] expected: FAIL - [DeviceLightEvent interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/battery-status/battery-interface-idlharness.html.ini b/testing/web-platform/meta/battery-status/battery-interface-idlharness.html.ini index 0380b7a2539..2f3e49d69af 100644 --- a/testing/web-platform/meta/battery-status/battery-interface-idlharness.html.ini +++ b/testing/web-platform/meta/battery-status/battery-interface-idlharness.html.ini @@ -3,6 +3,3 @@ [BatteryManager interface: existence and properties of interface object] expected: FAIL - [BatteryManager interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/cors/response-headers.htm.ini b/testing/web-platform/meta/cors/response-headers.htm.ini index d9523203aa6..4f607ec7c34 100644 --- a/testing/web-platform/meta/cors/response-headers.htm.ini +++ b/testing/web-platform/meta/cors/response-headers.htm.ini @@ -6,42 +6,3 @@ [getResponse: don\'t expose x-nonexposed] expected: FAIL - [getResponseHeader: Expose Access-Control-Expose-Headers (x-custom-header-comma)] - expected: FAIL - - [getResponseHeader: Expose second Access-Control-Expose-Headers (x-second-expose)] - expected: FAIL - - [getResponseHeader: Don\'t trim whitespace] - expected: FAIL - - [getResponseHeader: x-custom-header bytes] - expected: FAIL - - [getResponseHeader: Exposed server field readable (Date)] - expected: FAIL - - [getResponseHeader: Cache-Control: readable by default] - expected: FAIL - - [getResponseHeader: Content-Language: readable by default] - expected: FAIL - - [getResponseHeader: Expires: readable by default] - expected: FAIL - - [getResponseHeader: Last-Modified: readable by default] - expected: FAIL - - [getResponseHeader: Pragma: readable by default] - expected: FAIL - - [getResponseHeader: Server: unreadable by default] - expected: FAIL - - [getResponseHeader: X-Powered-By: unreadable by default] - expected: FAIL - - [getAllResponseHeaders: don\'t expose x-nonexposed] - expected: FAIL - diff --git a/testing/web-platform/meta/dom/interfaces.html.ini b/testing/web-platform/meta/dom/interfaces.html.ini index d20715a6511..8d86e61cd03 100644 --- a/testing/web-platform/meta/dom/interfaces.html.ini +++ b/testing/web-platform/meta/dom/interfaces.html.ini @@ -351,81 +351,6 @@ [Attr interface: attribute textContent] expected: FAIL - [DOMError interface object length] - expected: FAIL - - [Event interface object length] - expected: FAIL - - [CustomEvent interface object length] - expected: FAIL - - [EventTarget interface object length] - expected: FAIL - - [NodeList interface object length] - expected: FAIL - - [HTMLCollection interface object length] - expected: FAIL - - [MutationObserver interface object length] - expected: FAIL - - [MutationRecord interface object length] - expected: FAIL - - [Node interface object length] - expected: FAIL - - [Document interface object length] - expected: FAIL - - [XMLDocument interface object length] - expected: FAIL - - [DOMImplementation interface object length] - expected: FAIL - - [DocumentFragment interface object length] - expected: FAIL - - [DocumentType interface object length] - expected: FAIL - - [Element interface object length] - expected: FAIL - - [NamedNodeMap interface object length] - expected: FAIL - - [Attr interface object length] - expected: FAIL - - [CharacterData interface object length] - expected: FAIL - - [Text interface object length] - expected: FAIL - - [ProcessingInstruction interface object length] - expected: FAIL - - [Comment interface object length] - expected: FAIL - - [Range interface object length] - expected: FAIL - - [NodeIterator interface object length] - expected: FAIL - - [TreeWalker interface object length] - expected: FAIL - - [DOMTokenList interface object length] - expected: FAIL - - [DOMSettableTokenList interface object length] + [Attr interface: attribute nodeValue] expected: FAIL diff --git a/testing/web-platform/meta/dom/nodes/Document-getElementById.html.ini b/testing/web-platform/meta/dom/nodes/Document-getElementById.html.ini deleted file mode 100644 index 5ad19dff4e4..00000000000 --- a/testing/web-platform/meta/dom/nodes/Document-getElementById.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[Document-getElementById.html] - type: testharness - expected: ERROR diff --git a/testing/web-platform/meta/eventsource/interfaces.html.ini b/testing/web-platform/meta/eventsource/interfaces.html.ini index 07f6e7763ad..66e64261789 100644 --- a/testing/web-platform/meta/eventsource/interfaces.html.ini +++ b/testing/web-platform/meta/eventsource/interfaces.html.ini @@ -9,6 +9,3 @@ [Stringification of new EventSource("http://foo")] expected: FAIL - [EventSource interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-07.html.ini b/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-07.html.ini index 58df84b3173..a0eab9fe499 100644 --- a/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-07.html.ini +++ b/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-07.html.ini @@ -3,3 +3,6 @@ [Document.title and DOMImplementation.createHTMLDocument 6] expected: FAIL + [createHTMLDocument test 6: "foo\\f\\fbar baz","foo\\f\\fbar baz","foo bar baz"] + expected: FAIL + diff --git a/testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini b/testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini index 5c40910e6bb..74249d5a63d 100644 --- a/testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini +++ b/testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini @@ -2,3 +2,4 @@ type: testharness [getting cookie for a cookie-averse document returns empty string, setting does nothing] expected: FAIL + diff --git a/testing/web-platform/meta/html/dom/interfaces.html.ini b/testing/web-platform/meta/html/dom/interfaces.html.ini index 9c19b55059e..85be110382f 100644 --- a/testing/web-platform/meta/html/dom/interfaces.html.ini +++ b/testing/web-platform/meta/html/dom/interfaces.html.ini @@ -3876,336 +3876,3 @@ [HTMLFontElement interface: existence and properties of interface object] expected: FAIL - [HTMLAllCollection interface object length] - expected: FAIL - - [HTMLFormControlsCollection interface object length] - expected: FAIL - - [RadioNodeList interface object length] - expected: FAIL - - [HTMLOptionsCollection interface object length] - expected: FAIL - - [HTMLPropertiesCollection interface object length] - expected: FAIL - - [PropertyNodeList interface object length] - expected: FAIL - - [DOMStringMap interface object length] - expected: FAIL - - [HTMLElement interface object length] - expected: FAIL - - [HTMLUnknownElement interface object length] - expected: FAIL - - [HTMLHtmlElement interface object length] - expected: FAIL - - [HTMLHeadElement interface object length] - expected: FAIL - - [HTMLTitleElement interface object length] - expected: FAIL - - [HTMLBaseElement interface object length] - expected: FAIL - - [HTMLLinkElement interface object length] - expected: FAIL - - [HTMLMetaElement interface object length] - expected: FAIL - - [HTMLStyleElement interface object length] - expected: FAIL - - [HTMLBodyElement interface object length] - expected: FAIL - - [HTMLHeadingElement interface object length] - expected: FAIL - - [HTMLParagraphElement interface object length] - expected: FAIL - - [HTMLHRElement interface object length] - expected: FAIL - - [HTMLPreElement interface object length] - expected: FAIL - - [HTMLQuoteElement interface object length] - expected: FAIL - - [HTMLOListElement interface object length] - expected: FAIL - - [HTMLUListElement interface object length] - expected: FAIL - - [HTMLLIElement interface object length] - expected: FAIL - - [HTMLDListElement interface object length] - expected: FAIL - - [HTMLDivElement interface object length] - expected: FAIL - - [HTMLAnchorElement interface object length] - expected: FAIL - - [HTMLDataElement interface object length] - expected: FAIL - - [HTMLTimeElement interface object length] - expected: FAIL - - [HTMLSpanElement interface object length] - expected: FAIL - - [HTMLBRElement interface object length] - expected: FAIL - - [HTMLModElement interface object length] - expected: FAIL - - [HTMLImageElement interface object length] - expected: FAIL - - [HTMLIFrameElement interface object length] - expected: FAIL - - [HTMLEmbedElement interface object length] - expected: FAIL - - [HTMLObjectElement interface object length] - expected: FAIL - - [HTMLParamElement interface object length] - expected: FAIL - - [HTMLVideoElement interface object length] - expected: FAIL - - [HTMLAudioElement interface object length] - expected: FAIL - - [HTMLSourceElement interface object length] - expected: FAIL - - [HTMLTrackElement interface object length] - expected: FAIL - - [HTMLMediaElement interface object length] - expected: FAIL - - [MediaError interface object length] - expected: FAIL - - [TextTrackList interface object length] - expected: FAIL - - [TextTrack interface object length] - expected: FAIL - - [TextTrackCueList interface object length] - expected: FAIL - - [TimeRanges interface object length] - expected: FAIL - - [TrackEvent interface object length] - expected: FAIL - - [HTMLMapElement interface object length] - expected: FAIL - - [HTMLAreaElement interface object length] - expected: FAIL - - [HTMLTableElement interface object length] - expected: FAIL - - [HTMLTableCaptionElement interface object length] - expected: FAIL - - [HTMLTableColElement interface object length] - expected: FAIL - - [HTMLTableSectionElement interface object length] - expected: FAIL - - [HTMLTableRowElement interface object length] - expected: FAIL - - [HTMLTableCellElement interface object length] - expected: FAIL - - [HTMLFormElement interface object length] - expected: FAIL - - [HTMLLabelElement interface object length] - expected: FAIL - - [HTMLInputElement interface object length] - expected: FAIL - - [HTMLButtonElement interface object length] - expected: FAIL - - [HTMLSelectElement interface object length] - expected: FAIL - - [HTMLDataListElement interface object length] - expected: FAIL - - [HTMLOptGroupElement interface object length] - expected: FAIL - - [HTMLOptionElement interface object length] - expected: FAIL - - [HTMLTextAreaElement interface object length] - expected: FAIL - - [HTMLOutputElement interface object length] - expected: FAIL - - [HTMLProgressElement interface object length] - expected: FAIL - - [HTMLMeterElement interface object length] - expected: FAIL - - [HTMLFieldSetElement interface object length] - expected: FAIL - - [HTMLLegendElement interface object length] - expected: FAIL - - [ValidityState interface object length] - expected: FAIL - - [HTMLMenuElement interface object length] - expected: FAIL - - [HTMLMenuItemElement interface object length] - expected: FAIL - - [HTMLScriptElement interface object length] - expected: FAIL - - [HTMLTemplateElement interface object length] - expected: FAIL - - [HTMLCanvasElement interface object length] - expected: FAIL - - [CanvasGradient interface object length] - expected: FAIL - - [CanvasPattern interface object length] - expected: FAIL - - [TextMetrics interface object length] - expected: FAIL - - [ImageData interface object length] - expected: FAIL - - [Path2D interface object length] - expected: FAIL - - [Window interface object length] - expected: FAIL - - [BarProp interface object length] - expected: FAIL - - [History interface object length] - expected: FAIL - - [Location interface object length] - expected: FAIL - - [PopStateEvent interface object length] - expected: FAIL - - [HashChangeEvent interface object length] - expected: FAIL - - [PageTransitionEvent interface object length] - expected: FAIL - - [BeforeUnloadEvent interface object length] - expected: FAIL - - [ErrorEvent interface object length] - expected: FAIL - - [Navigator interface object length] - expected: FAIL - - [PluginArray interface object length] - expected: FAIL - - [MimeTypeArray interface object length] - expected: FAIL - - [Plugin interface object length] - expected: FAIL - - [MimeType interface object length] - expected: FAIL - - [External interface object length] - expected: FAIL - - [MessageEvent interface object length] - expected: FAIL - - [EventSource interface object length] - expected: FAIL - - [WebSocket interface object length] - expected: FAIL - - [CloseEvent interface object length] - expected: FAIL - - [MessagePort interface object length] - expected: FAIL - - [Worker interface object length] - expected: FAIL - - [SharedWorker interface object length] - expected: FAIL - - [Storage interface object length] - expected: FAIL - - [StorageEvent interface object length] - expected: FAIL - - [HTMLAppletElement interface object length] - expected: FAIL - - [HTMLFrameSetElement interface object length] - expected: FAIL - - [HTMLFrameElement interface object length] - expected: FAIL - - [HTMLDirectoryElement interface object length] - expected: FAIL - - [HTMLFontElement interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/media-source/SourceBuffer-abort-readyState.html.ini b/testing/web-platform/meta/media-source/SourceBuffer-abort-readyState.html.ini index b978bc9d15c..a46a221ca97 100644 --- a/testing/web-platform/meta/media-source/SourceBuffer-abort-readyState.html.ini +++ b/testing/web-platform/meta/media-source/SourceBuffer-abort-readyState.html.ini @@ -1,9 +1,5 @@ [SourceBuffer-abort-readyState.html] type: testharness - expected: TIMEOUT - [SourceBuffer#abort() (video/webm; codecs="vorbis,vp8") : If the readyState attribute of the parent media source is not in the "open" state then throw an INVALID_STATE_ERR exception and abort these steps.] - expected: TIMEOUT - [SourceBuffer#abort() (video/mp4) : If the readyState attribute of the parent media source is not in the "open" state then throw an INVALID_STATE_ERR exception and abort these steps.] expected: FAIL diff --git a/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/audiostreamtrack.html.ini b/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/audiostreamtrack.html.ini index 4ec1377f0e2..3833a718085 100644 --- a/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/audiostreamtrack.html.ini +++ b/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/audiostreamtrack.html.ini @@ -9,6 +9,3 @@ [AudioStreamTrack interface: operation getSourceIds()] expected: FAIL - [AudioStreamTrack interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/videostreamtrack.html.ini b/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/videostreamtrack.html.ini index 02bdbd1de55..ef10b06668d 100644 --- a/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/videostreamtrack.html.ini +++ b/testing/web-platform/meta/mediacapture-streams/stream-api/video-and-audio-tracks/videostreamtrack.html.ini @@ -9,6 +9,3 @@ [VideoStreamTrack interface: operation getSourceIds()] expected: FAIL - [VideoStreamTrack interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/navigation-timing/idlharness.html.ini b/testing/web-platform/meta/navigation-timing/idlharness.html.ini index c7c0d6c7042..fc125159716 100644 --- a/testing/web-platform/meta/navigation-timing/idlharness.html.ini +++ b/testing/web-platform/meta/navigation-timing/idlharness.html.ini @@ -33,12 +33,3 @@ [Window interface: attribute performance] expected: FAIL - [PerformanceTiming interface object length] - expected: FAIL - - [PerformanceNavigation interface object length] - expected: FAIL - - [Performance interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/notifications/interfaces.html.ini b/testing/web-platform/meta/notifications/interfaces.html.ini index ff84b2bd132..382b421cb1c 100644 --- a/testing/web-platform/meta/notifications/interfaces.html.ini +++ b/testing/web-platform/meta/notifications/interfaces.html.ini @@ -3,6 +3,3 @@ [Notification interface: existence and properties of interface object] expected: FAIL - [Notification interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/performance-timeline/idlharness.html.ini b/testing/web-platform/meta/performance-timeline/idlharness.html.ini deleted file mode 100644 index e7ec4e9715b..00000000000 --- a/testing/web-platform/meta/performance-timeline/idlharness.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[idlharness.html] - type: testharness - [PerformanceEntry interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/progress-events/interface.html.ini b/testing/web-platform/meta/progress-events/interface.html.ini new file mode 100644 index 00000000000..d9290c50a1b --- /dev/null +++ b/testing/web-platform/meta/progress-events/interface.html.ini @@ -0,0 +1,5 @@ +[interface.html] + type: testharness + [The ProgressEvent interface] + expected: FAIL + diff --git a/testing/web-platform/meta/proximity/idlharness.html.ini b/testing/web-platform/meta/proximity/idlharness.html.ini index 1853e0e33f5..094ebf05b53 100644 --- a/testing/web-platform/meta/proximity/idlharness.html.ini +++ b/testing/web-platform/meta/proximity/idlharness.html.ini @@ -18,9 +18,3 @@ [UserProximityEvent interface: existence and properties of interface object] expected: FAIL - [DeviceProximityEvent interface object length] - expected: FAIL - - [UserProximityEvent interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini b/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini index 9424f4554c6..69c9ea7d1bb 100644 --- a/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini +++ b/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini @@ -2,3 +2,4 @@ type: testharness [PerformanceEntry has correct name, initiatorType, startTime, and duration (link)] disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1079837 + diff --git a/testing/web-platform/meta/selectors-api/tests/submissions/Opera/level2-baseline.html.ini b/testing/web-platform/meta/selectors-api/tests/submissions/Opera/level2-baseline.html.ini index 36faa80ad2f..2bdf5698fb3 100644 --- a/testing/web-platform/meta/selectors-api/tests/submissions/Opera/level2-baseline.html.ini +++ b/testing/web-platform/meta/selectors-api/tests/submissions/Opera/level2-baseline.html.ini @@ -1,5 +1,6 @@ [level2-baseline.html] type: testharness + expected: TIMEOUT [Document supports find] expected: FAIL diff --git a/testing/web-platform/meta/url/a-element.html.ini b/testing/web-platform/meta/url/a-element.html.ini index b6d363df78c..8fa645f16cc 100644 --- a/testing/web-platform/meta/url/a-element.html.ini +++ b/testing/web-platform/meta/url/a-element.html.ini @@ -336,3 +336,15 @@ [Parsing: <#\xce\xb2> against ] expected: FAIL + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + diff --git a/testing/web-platform/meta/url/a-element.xhtml.ini b/testing/web-platform/meta/url/a-element.xhtml.ini index 34ec372b672..733f5cd899b 100644 --- a/testing/web-platform/meta/url/a-element.xhtml.ini +++ b/testing/web-platform/meta/url/a-element.xhtml.ini @@ -360,3 +360,15 @@ [Parsing: <#\xce\xb2> against ] expected: FAIL + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + + [Parsing: against ] + expected: FAIL + diff --git a/testing/web-platform/meta/url/interfaces.html.ini b/testing/web-platform/meta/url/interfaces.html.ini index 6c62320dc4b..0ddc90c980d 100644 --- a/testing/web-platform/meta/url/interfaces.html.ini +++ b/testing/web-platform/meta/url/interfaces.html.ini @@ -9,9 +9,9 @@ [URLSearchParams interface: existence and properties of interface object] expected: FAIL - [URL interface object length] + [URL interface: operation domainToASCII(ScalarValueString)] expected: FAIL - [URLSearchParams interface object length] + [URL interface: operation domainToUnicode(ScalarValueString)] expected: FAIL diff --git a/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html.ini b/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html.ini deleted file mode 100644 index e28dd2b3c2d..00000000000 --- a/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffer-interface/idl-test.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[idl-test.html] - type: testharness - [AudioBuffer interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html.ini b/testing/web-platform/meta/webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html.ini index e8064a2bfba..6b940b0d9cf 100644 --- a/testing/web-platform/meta/webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html.ini +++ b/testing/web-platform/meta/webaudio/the-audio-api/the-audiodestinationnode-interface/idl-test.html.ini @@ -3,6 +3,3 @@ [AudioDestinationNode interface: existence and properties of interface object] expected: FAIL - [AudioDestinationNode interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/webaudio/the-audio-api/the-delaynode-interface/idl-test.html.ini b/testing/web-platform/meta/webaudio/the-audio-api/the-delaynode-interface/idl-test.html.ini index 25030edeace..79e51800538 100644 --- a/testing/web-platform/meta/webaudio/the-audio-api/the-delaynode-interface/idl-test.html.ini +++ b/testing/web-platform/meta/webaudio/the-audio-api/the-delaynode-interface/idl-test.html.ini @@ -3,6 +3,3 @@ [DelayNode interface: existence and properties of interface object] expected: FAIL - [DelayNode interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/webaudio/the-audio-api/the-gainnode-interface/idl-test.html.ini b/testing/web-platform/meta/webaudio/the-audio-api/the-gainnode-interface/idl-test.html.ini index 5f132203b9d..27886aa6e26 100644 --- a/testing/web-platform/meta/webaudio/the-audio-api/the-gainnode-interface/idl-test.html.ini +++ b/testing/web-platform/meta/webaudio/the-audio-api/the-gainnode-interface/idl-test.html.ini @@ -3,6 +3,3 @@ [GainNode interface: existence and properties of interface object] expected: FAIL - [GainNode interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/websockets/interfaces.html.ini b/testing/web-platform/meta/websockets/interfaces.html.ini index cf1aef8343f..ddb18eb71eb 100644 --- a/testing/web-platform/meta/websockets/interfaces.html.ini +++ b/testing/web-platform/meta/websockets/interfaces.html.ini @@ -15,9 +15,3 @@ [CloseEvent interface: existence and properties of interface prototype object] expected: FAIL - [WebSocket interface object length] - expected: FAIL - - [CloseEvent interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/webstorage/idlharness.html.ini b/testing/web-platform/meta/webstorage/idlharness.html.ini index 3d2f1d56825..8d0db152f0e 100644 --- a/testing/web-platform/meta/webstorage/idlharness.html.ini +++ b/testing/web-platform/meta/webstorage/idlharness.html.ini @@ -9,9 +9,3 @@ [StorageEvent interface: existence and properties of interface object] expected: FAIL - [Storage interface object length] - expected: FAIL - - [StorageEvent interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/webvtt/interfaces.html.ini b/testing/web-platform/meta/webvtt/interfaces.html.ini index ffe33d269de..3cee139e2ec 100644 --- a/testing/web-platform/meta/webvtt/interfaces.html.ini +++ b/testing/web-platform/meta/webvtt/interfaces.html.ini @@ -78,6 +78,3 @@ [VTTRegion interface: new VTTRegion() must inherit property "scroll" with the proper type (6)] expected: FAIL - [VTTCue interface object length] - expected: FAIL - diff --git a/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini b/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini new file mode 100644 index 00000000000..6dfbdc124e0 --- /dev/null +++ b/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini @@ -0,0 +1,5 @@ +[event-ports-dedicated.html] + type: testharness + [e.ports in dedicated worker] + expected: FAIL + diff --git a/testing/web-platform/tests/DOMEvents/init-event-while-dispatching.html b/testing/web-platform/tests/DOMEvents/init-event-while-dispatching.html new file mode 100644 index 00000000000..2aa1f6701c4 --- /dev/null +++ b/testing/web-platform/tests/DOMEvents/init-event-while-dispatching.html @@ -0,0 +1,83 @@ + + +Re-initializing events while dispatching them + + + +
+ diff --git a/testing/web-platform/tests/DOMEvents/tests/approved/addEventListener.optional.useCapture.html b/testing/web-platform/tests/DOMEvents/tests/approved/addEventListener.optional.useCapture.html deleted file mode 100644 index 02ee847828f..00000000000 --- a/testing/web-platform/tests/DOMEvents/tests/approved/addEventListener.optional.useCapture.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - addEventListener() optional parameter: useCapture - - - - -
- - - - - - - - - - - - - - - - - diff --git a/testing/web-platform/tests/DOMEvents/tests/approved/dispatchEvent.return.value.html b/testing/web-platform/tests/DOMEvents/tests/approved/dispatchEvent.return.value.html deleted file mode 100644 index fcbedb04d96..00000000000 --- a/testing/web-platform/tests/DOMEvents/tests/approved/dispatchEvent.return.value.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - dispatchEvent() return value and Event.preventDefault() - - - - -
- - - - - - - - - - - - - - - - - diff --git a/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html b/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html deleted file mode 100644 index 779435a5e79..00000000000 --- a/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/addEventListener.optional.useCapture.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - addEventListener() optional parameter: useCapture - - - - -
- - - - - - - - - - - - - - - - - diff --git a/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html b/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html deleted file mode 100644 index 28edc3089f6..00000000000 --- a/testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.return.value.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - dispatchEvent() return value and Event.preventDefault() - - - - -
- - - - - - - - - - - - - - - - - diff --git a/testing/web-platform/tests/XMLHttpRequest/interfaces.html b/testing/web-platform/tests/XMLHttpRequest/interfaces.html index fbea8bef1f8..e987b49695a 100644 --- a/testing/web-platform/tests/XMLHttpRequest/interfaces.html +++ b/testing/web-platform/tests/XMLHttpRequest/interfaces.html @@ -10,9 +10,42 @@
@@ -22,7 +55,8 @@ callback EventHandlerNonNull = any (Event event); typedef EventHandlerNonNull? EventHandler; + + + + +
+ + diff --git a/testing/web-platform/tests/XMLHttpRequest/send-data-unexpected-tostring.htm b/testing/web-platform/tests/XMLHttpRequest/send-data-unexpected-tostring.htm new file mode 100644 index 00000000000..357a9cff9d9 --- /dev/null +++ b/testing/web-platform/tests/XMLHttpRequest/send-data-unexpected-tostring.htm @@ -0,0 +1,57 @@ + + +XMLHttpRequest: passing objects that interfere with the XHR instance to send() + + + + + + +
+ + diff --git a/testing/web-platform/tests/docs/running_tests.md b/testing/web-platform/tests/docs/running_tests.md index a45223f0811..affbc90059d 100644 --- a/testing/web-platform/tests/docs/running_tests.md +++ b/testing/web-platform/tests/docs/running_tests.md @@ -22,7 +22,7 @@ edge-cases like tests that cause the browser to crash or hang. ## By Automating the Browser For automated test running designed to be robust enough to use in a CI -environment, the [wptrunner](http://github.com/wptrunner) test runner +environment, the [wptrunner](http://github.com/w3c/wptrunner) test runner can be used. This is a test runner written in Python and designed to control the browser from the outside using some remote control protocol such as WebDriver. This allows it to handle cases such as the diff --git a/testing/web-platform/tests/dom/common.js b/testing/web-platform/tests/dom/common.js index 91ccb4425d7..2cc36bf6a07 100644 --- a/testing/web-platform/tests/dom/common.js +++ b/testing/web-platform/tests/dom/common.js @@ -5,7 +5,6 @@ // Everything is done in functions in this test harness, so we have to declare // all the variables before use to make sure they can be reused. -var selection; var testDiv, paras, detachedDiv, detachedPara1, detachedPara2, foreignDoc, foreignPara1, foreignPara2, xmlDoc, xmlElement, detachedXmlElement, detachedTextNode, foreignTextNode, @@ -17,7 +16,6 @@ var testDiv, paras, detachedDiv, detachedPara1, detachedPara2, var testRangesShort, testRanges, testPoints, testNodesShort, testNodes; function setupRangeTests() { - selection = getSelection(); testDiv = document.querySelector("#test"); if (testDiv) { testDiv.parentNode.removeChild(testDiv); diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html b/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html new file mode 100644 index 00000000000..77074d9a3ec --- /dev/null +++ b/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html @@ -0,0 +1,70 @@ + + +EventTarget.addEventListener: capture argument omitted + + + + +
+ + + + + + + + + + + + + diff --git a/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html new file mode 100644 index 00000000000..8804c38a53b --- /dev/null +++ b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html @@ -0,0 +1,43 @@ + + +EventTarget.dispatchEvent: return value + + + + + +
+ + + + + + + + + + + + + diff --git a/testing/web-platform/tests/dom/interfaces.html b/testing/web-platform/tests/dom/interfaces.html index 60c7c83e514..b5c10eec19d 100644 --- a/testing/web-platform/tests/dom/interfaces.html +++ b/testing/web-platform/tests/dom/interfaces.html @@ -360,7 +360,8 @@ interface Attr { readonly attribute DOMString localName; readonly attribute DOMString name; attribute DOMString value; - attribute DOMString textContent; // alias of .value + attribute DOMString nodeValue; // legacy alias of .value + attribute DOMString textContent; // legacy alias of .value readonly attribute Element? ownerElement; diff --git a/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js b/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js new file mode 100644 index 00000000000..360b9760e3e --- /dev/null +++ b/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js @@ -0,0 +1,77 @@ +function test_constructor(ctor) { + test(function() { + var object = new window[ctor](); + assert_equals(Object.getPrototypeOf(object), + window[ctor].prototype, "Prototype chain: " + ctor); + assert_equals(Object.getPrototypeOf(Object.getPrototypeOf(object)), + CharacterData.prototype, "Prototype chain: CharacterData"); + assert_equals(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(object))), + Node.prototype, "Prototype chain: Node"); + }, "new " + ctor + "(): prototype chain"); + + test(function() { + var object = new window[ctor](); + assert_true(object instanceof Node, "Should be a Node"); + assert_true(object instanceof CharacterData, "Should be a CharacterData"); + assert_true(object instanceof window[ctor], "Should be a " + ctor); + }, "new " + ctor + "(): instanceof"); + + test(function() { + var object = new window[ctor](); + assert_equals(object.data, ""); + assert_equals(object.nodeValue, ""); + assert_equals(object.ownerDocument, document); + }, "new " + ctor + "(): no arguments"); + + var arguments = [ + [undefined, ""], + [null, "null"], + [42, "42"], + ["", ""], + ["-", "-"], + ["--", "--"], + ["-->", "-->"], + ["", "-->"], - [" + + +
+ diff --git a/testing/web-platform/tests/encoding/gbk-encoder.html b/testing/web-platform/tests/encoding/gbk-encoder.html new file mode 100644 index 00000000000..a6074f975d3 --- /dev/null +++ b/testing/web-platform/tests/encoding/gbk-encoder.html @@ -0,0 +1,21 @@ + + + + +
+ diff --git a/testing/web-platform/tests/encoding/iso-2022-jp-decoder.html b/testing/web-platform/tests/encoding/iso-2022-jp-decoder.html new file mode 100644 index 00000000000..c86ffc15899 --- /dev/null +++ b/testing/web-platform/tests/encoding/iso-2022-jp-decoder.html @@ -0,0 +1,57 @@ + + + + +
+ diff --git a/testing/web-platform/tests/encoding/iso-2022-jp-encoder.html b/testing/web-platform/tests/encoding/iso-2022-jp-encoder.html new file mode 100644 index 00000000000..d3124e5f54b --- /dev/null +++ b/testing/web-platform/tests/encoding/iso-2022-jp-encoder.html @@ -0,0 +1,18 @@ + + + + +
+ diff --git a/testing/web-platform/tests/encoding/resources/single-byte-raw.py b/testing/web-platform/tests/encoding/resources/single-byte-raw.py new file mode 100644 index 00000000000..b4a6c90405e --- /dev/null +++ b/testing/web-platform/tests/encoding/resources/single-byte-raw.py @@ -0,0 +1,3 @@ +def main(request, response): + response.headers.set("Content-Type", "text/plain;charset=" + request.GET.first("label")) + response.content = "".join(chr(byte) for byte in xrange(255)) diff --git a/testing/web-platform/tests/encoding/single-byte-decoder.html b/testing/web-platform/tests/encoding/single-byte-decoder.html new file mode 100644 index 00000000000..e92af46b2bf --- /dev/null +++ b/testing/web-platform/tests/encoding/single-byte-decoder.html @@ -0,0 +1,176 @@ + + + +
+ diff --git a/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-07.html b/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-07.html index eea97c9ede1..9723d3f8115 100644 --- a/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-07.html +++ b/testing/web-platform/tests/html/dom/documents/dom-tree-accessors/document.title-07.html @@ -2,25 +2,10 @@ Document.title and DOMImplementation.createHTMLDocument +
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html index 128a62746df..efd563f51ac 100644 --- a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html +++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html @@ -9,12 +9,12 @@ onload = function() { var ifr = document.getElementsByTagName('iframe')[0]; ifr.contentDocument.body.appendChild(ifr.contentDocument.createElement('p')).textContent = 'Modified document'; - setTimeout(function () { - document.getElementById('target').appendChild(ifr); - setTimeout(function() { + setTimeout(function() { + ifr.onload = function() { assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1); done(); - }, 100); + }; + document.getElementById('target').appendChild(ifr); }, 100); } diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html index e93bbb3b5ff..dbe266b2930 100644 --- a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html +++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html @@ -12,11 +12,11 @@ onload = function() { ifr.contentDocument.write('Modified document'); ifr.contentDocument.close(); setTimeout(function() { - document.getElementById('target').appendChild(ifr); - setTimeout(function() { + ifr.onload = function() { assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1); done(); - }, 100); + }; + document.getElementById('target').appendChild(ifr); }, 100); } diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html index 498ac771a1e..c07dd42dc81 100644 --- a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html +++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html @@ -10,11 +10,11 @@ onload = function() { var ifr = document.getElementsByTagName('iframe')[0]; ifr.contentDocument.body.appendChild(ifr.contentDocument.createElement('p')).textContent = 'Modified document'; setTimeout(function() { - document.getElementById('target').appendChild(ifr); - setTimeout(function() { + ifr.onload = function() { assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1); done(); - }, 100); + }; + document.getElementById('target').appendChild(ifr); }, 100); } \ No newline at end of file diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html index b16caf1e4b2..755cd45d34b 100644 --- a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html +++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html @@ -11,12 +11,12 @@ onload = function(){ ifr.contentDocument.open(); ifr.contentDocument.write('Modified document'); ifr.contentDocument.close(); - setTimeout(function () { - document.getElementById('target').appendChild(ifr); - setTimeout(function () { + setTimeout(function() { + ifr.onload = function () { assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1); done(); - }, 100); + }; + document.getElementById('target').appendChild(ifr); }, 100); } \ No newline at end of file diff --git a/testing/web-platform/tests/media-source/SourceBuffer-abort-readyState.html b/testing/web-platform/tests/media-source/SourceBuffer-abort-readyState.html index c29f7611f42..515031687ae 100644 --- a/testing/web-platform/tests/media-source/SourceBuffer-abort-readyState.html +++ b/testing/web-platform/tests/media-source/SourceBuffer-abort-readyState.html @@ -10,7 +10,6 @@
diff --git a/testing/web-platform/tests/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html b/testing/web-platform/tests/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html index e973c2101f3..45b67414b26 100644 --- a/testing/web-platform/tests/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html +++ b/testing/web-platform/tests/pointerevents/pointerevent_setpointercapture_relatedtarget-manual.html @@ -15,7 +15,7 @@
  1. Put your mouse over the lower rectangle. pointerover should be received for the purple rectangle
  2. Press and hold left mouse button over "Set Capture" button -
  3. Put your mouse over the lower rectangle. pointerover should be received for the black rectangle +
  4. Put your mouse over the upper rectangle. pointerover should be received for the black rectangle
  5. Release left mouse button to complete the test.
diff --git a/testing/web-platform/tests/progress-events/tests/submissions/Samsung/resources/no-content-length.py b/testing/web-platform/tests/progress-events/tests/submissions/Samsung/resources/no-content-length.py index bf73dc70715..0b47ff146cc 100644 --- a/testing/web-platform/tests/progress-events/tests/submissions/Samsung/resources/no-content-length.py +++ b/testing/web-platform/tests/progress-events/tests/submissions/Samsung/resources/no-content-length.py @@ -1,5 +1,5 @@ def main(request, response): - response.headers.extend([('Transfer-Encoding', 'chunked'), + response.headers.update([('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/html'), ('Connection', 'keep-alive')]) response.write_status_headers() diff --git a/testing/web-platform/tests/resources/idlharness.js b/testing/web-platform/tests/resources/idlharness.js index e1aeae378f3..b6c02a3f8dd 100644 --- a/testing/web-platform/tests/resources/idlharness.js +++ b/testing/web-platform/tests/resources/idlharness.js @@ -426,6 +426,9 @@ IdlArray.prototype.assert_type_is = function(value, type) return; case "DOMString": + case "ByteString": + case "USVString": + // TODO: https://github.com/w3c/testharness.js/issues/92 assert_equals(typeof value, "string"); return; @@ -1798,6 +1801,8 @@ function create_suitable_object(type) return 7; case "DOMString": + case "ByteString": + case "USVString": return "foo"; case "object": diff --git a/testing/web-platform/tests/resources/readme.md b/testing/web-platform/tests/resources/readme.md index 7e54cf6cc2c..5c5b36c2869 100644 --- a/testing/web-platform/tests/resources/readme.md +++ b/testing/web-platform/tests/resources/readme.md @@ -16,7 +16,8 @@ To use testharness.js you must include two scripts, in the order given: ## Full documentation ## -Full documentation of the API is kept in the source of testharness.js. +Full user documentation for the API is in the +[docs/api.md](https://github.com/w3c/testharness.js/blob/master/docs/api.md) file. You can also read a tutorial on [Using testharness.js](http://darobin.github.com/test-harness-tutorial/docs/using-testharness.html). diff --git a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.html b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.html new file mode 100644 index 00000000000..db73e4012c3 --- /dev/null +++ b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.html @@ -0,0 +1,87 @@ + + +Selectors-API Level 2 Test Suite: HTML with Selectors Level 3 + + + + + + + + +
This test requires JavaScript.
+ + diff --git a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.js b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.js new file mode 100644 index 00000000000..1bc0f3d0887 --- /dev/null +++ b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/Element-matches.js @@ -0,0 +1,127 @@ +/* + * Check that the matches() method exists on the given Node + */ +function interfaceCheckMatches(type, obj) { + if (obj.nodeType === obj.ELEMENT_NODE) { + test(function() { + assert_idl_attribute(obj, "matches", type + " supports matches"); + }, type + " supports matches") + } +} + +function runSpecialMatchesTests(type, element) { + test(function() { // 1 + if (element.tagName.toLowerCase() === "null") { + assert_true(element.matches(null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match."); + } else { + assert_false(element.matches(null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match."); + } + }, type + ".matches(null)") + + test(function() { // 2 + if (element.tagName.toLowerCase() === "undefined") { + assert_true(element.matches(undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match."); + } else { + assert_false(element.matches(undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match."); + } + }, type + ".matches(undefined)") + + test(function() { // 3 + assert_throws(TypeError(), function() { + element.matches(); + }, "This should throw a TypeError.") + }, type + ".matches no parameter") +} + +/* + * Execute queries with the specified invalid selectors for matches() + * Only run these tests when errors are expected. Don't run for valid selector tests. + */ +function runInvalidSelectorTestMatches(type, root, selectors) { + if (root.nodeType === root.ELEMENT_NODE) { + for (var i = 0; i < selectors.length; i++) { + var s = selectors[i]; + var n = s["name"]; + var q = s["selector"]; + + test(function() { + assert_throws("SyntaxError", function() { + root.matches(q) + }) + }, type + ".matches: " + n + ": " + q); + } + } +} + +function runMatchesTest(type, root, selectors, docType) { + var nodeType = getNodeType(root); + + for (var i = 0; i < selectors.length; i++) { + var s = selectors[i]; + var n = s["name"]; + var q = s["selector"]; + var e = s["expect"]; + var u = s["unexpected"]; + + var ctx = s["ctx"]; + var ref = s["ref"]; + + if ((!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1)) + && (s["testType"] & TEST_MATCH) ) { + + if (ctx && !ref) { + test(function() { + var j, element, refNode; + for (j = 0; j < e.length; j++) { + element = root.querySelector("#" + e[j]); + refNode = root.querySelector(ctx); + assert_true(element.matches(q, refNode), "The element #" + e[j] + " should match the selector.") + } + + if (u) { + for (j = 0; j < u.length; j++) { + element = root.querySelector("#" + u[j]); + refNode = root.querySelector(ctx); + assert_false(element.matches(q, refNode), "The element #" + u[j] + " should not match the selector.") + } + } + }, type + " Element.matches: " + n + " (with refNode Element): " + q); + } + + if (ref) { + test(function() { + var j, element, refNodes; + for (j = 0; j < e.length; j++) { + element = root.querySelector("#" + e[j]); + refNodes = root.querySelectorAll(ref); + assert_true(element.matches(q, refNodes), "The element #" + e[j] + " should match the selector.") + } + + if (u) { + for (j = 0; j < u.length; j++) { + element = root.querySelector("#" + u[j]); + refNodes = root.querySelectorAll(ref); + assert_false(element.matches(q, refNodes), "The element #" + u[j] + " should not match the selector.") + } + } + }, type + " Element.matches: " + n + " (with refNodes NodeList): " + q); + } + + if (!ctx && !ref) { + test(function() { + for (var j = 0; j < e.length; j++) { + var element = root.querySelector("#" + e[j]); + assert_true(element.matches(q), "The element #" + e[j] + " should match the selector.") + } + + if (u) { + for (j = 0; j < u.length; j++) { + element = root.querySelector("#" + u[j]); + assert_false(element.matches(q), "The element #" + u[j] + " should not match the selector.") + } + } + }, type + " Element.matches: " + n + " (with no refNodes): " + q); + } + } + } +} diff --git a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-baseline.html b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.html similarity index 72% rename from testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-baseline.html rename to testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.html index 9d1e61d9b0d..e319f60cef5 100644 --- a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-baseline.html +++ b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.html @@ -6,12 +6,12 @@ +
This test requires JavaScript.
diff --git a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.js b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.js new file mode 100644 index 00000000000..b27bfd91c26 --- /dev/null +++ b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/ParentNode-find-findAll.js @@ -0,0 +1,278 @@ +/* + * Check that the find and findAll methods exist on the given Node + */ +function interfaceCheckFind(type, obj) { + test(function() { + var q = typeof obj.find === "function"; + assert_true(q, type + " supports find."); + }, type + " supports find") + + test(function() { + var qa = typeof obj.findAll === "function"; + assert_true( qa, type + " supports findAll."); + }, type + " supports findAll") +} + +/* + * Verify that the NodeList returned by findAll is static and and that a new list is created after + * each call. A static list should not be affected by subsequent changes to the DOM. + */ +function verifyStaticList(type, root) { + var pre, post, preLength; + + test(function() { + pre = root.findAll("div"); + preLength = pre.length; + + var div = doc.createElement("div"); + (root.body || root).appendChild(div); + + assert_equals(pre.length, preLength, "The length of the NodeList should not change.") + }, type + ": static NodeList") + + test(function() { + post = root.findAll("div"), + assert_equals(post.length, preLength + 1, "The length of the new NodeList should be 1 more than the previous list.") + }, type + ": new NodeList") +} + +/* + * Verify handling of special values for the selector parameter, including stringification of + * null and undefined, and the handling of the empty string. + */ +function runSpecialSelectorTests(type, root) { + test(function() { // 1 + assert_equals(root.findAll(null).length, 1, "This should find one element with the tag name 'NULL'."); + }, type + ".findAll null") + + test(function() { // 2 + assert_equals(root.findAll(undefined).length, 1, "This should find one elements with the tag name 'UNDEFINED'."); + }, type + ".findAll undefined") + + test(function() { // 3 + assert_throws(TypeError(), function() { + root.findAll(); + }, "This should throw a TypeError.") + }, type + ".findAll no parameter") + + test(function() { // 4 + var elm = root.find(null) + assert_not_equals(elm, null, "This should find an element."); + assert_equals(elm.tagName.toUpperCase(), "NULL", "The tag name should be 'NULL'.") + }, type + ".find null") + + test(function() { // 5 + var elm = root.find(undefined) + assert_not_equals(elm, undefined, "This should find an element."); + assert_equals(elm.tagName.toUpperCase(), "UNDEFINED", "The tag name should be 'UNDEFINED'.") + }, type + ".find undefined") + + test(function() { // 6 + assert_throws(TypeError(), function() { + root.find(); + }, "This should throw a TypeError.") + }, type + ".find no parameter.") + + test(function() { // 7 + result = root.findAll("*"); + var i = 0; + traverse(root, function(elem) { + if (elem !== root) { + assert_equals(elem, result[i++], "The result in index " + i + " should be in tree order.") + } + }) + }, type + ".findAll tree order"); +} + +/* + * Execute queries with the specified valid selectors for both find() and findAll() + * Only run these tests when results are expected. Don't run for syntax error tests. + * + * context.findAll(selector, refNodes) + * context.findAll(selector) // Only if refNodes is not specified + * root.findAll(selector, context) // Only if refNodes is not specified + * root.findAll(selector, refNodes) // Only if context is not specified + * root.findAll(selector) // Only if neither context nor refNodes is specified + * + * Equivalent tests will be run for .find() as well. + */ +function runValidSelectorTest(type, root, selectors, docType) { + var nodeType = getNodeType(root); + + for (var i = 0; i < selectors.length; i++) { + var s = selectors[i]; + var n = s["name"]; + var q = s["selector"]; + var e = s["expect"]; + + var ctx = s["ctx"]; + var ref = s["ref"]; + + if (!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1)) { + //console.log("Running tests " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) + var foundall, found, context, refNodes, refArray; + + if (s["testType"] & TEST_FIND) { + + + /* + * If ctx and ref are specified: + * context.findAll(selector, refNodes) + * context.find(selector, refNodes) + */ + if (ctx && ref) { + context = root.querySelector(ctx); + refNodes = root.querySelectorAll(ref); + refArray = Array.prototype.slice.call(refNodes, 0); + + test(function() { + foundall = context.findAll(q, refNodes); + verifyNodeList(foundall, expect); + }, type + " [Context Element].findAll: " + n + " (with refNodes NodeList): " + q); + + test(function() { + foundall = context.findAll(q, refArray); + verifyNodeList(foundall, expect); + }, type + " [Context Element].findAll: " + n + " (with refNodes Array): " + q); + + test(function() { + found = context.find(q, refNodes); + verifyElement(found, foundall, expect) + }, type + " [Context Element].find: " + n + " (with refNodes NodeList): " + q); + + test(function() { + found = context.find(q, refArray); + verifyElement(found, foundall, expect) + }, type + " [Context Element].find: " + n + " (with refNodes Array): " + q); + } + + + /* + * If ctx is specified, ref is not: + * context.findAll(selector) + * context.find(selector) + * root.findAll(selector, context) + * root.find(selector, context) + */ + if (ctx && !ref) { + context = root.querySelector(ctx); + + test(function() { + foundall = context.findAll(q); + verifyNodeList(foundall, expect); + }, type + " [Context Element].findAll: " + n + " (with no refNodes): " + q); + + test(function() { + found = context.find(q); + verifyElement(found, foundall, expect) + }, type + " [Context Element].find: " + n + " (with no refNodes): " + q); + + test(function() { + foundall = root.findAll(q, context); + verifyNodeList(foundall, expect); + }, type + " [Root Node].findAll: " + n + " (with refNode Element): " + q); + + test(function() { + foundall = root.find(q, context); + verifyElement(found, foundall, expect); + }, type + " [Root Node].find: " + n + " (with refNode Element): " + q); + } + + /* + * If ref is specified, ctx is not: + * root.findAll(selector, refNodes) + * root.find(selector, refNodes) + */ + if (!ctx && ref) { + refNodes = root.querySelectorAll(ref); + refArray = Array.prototype.slice.call(refNodes, 0); + + test(function() { + foundall = root.findAll(q, refNodes); + verifyNodeList(foundall, expect); + }, type + " [Root Node].findAll: " + n + " (with refNodes NodeList): " + q); + + test(function() { + foundall = root.findAll(q, refArray); + verifyNodeList(foundall, expect); + }, type + " [Root Node].findAll: " + n + " (with refNodes Array): " + q); + + test(function() { + found = root.find(q, refNodes); + verifyElement(found, foundall, expect); + }, type + " [Root Node].find: " + n + " (with refNodes NodeList): " + q); + + test(function() { + found = root.find(q, refArray); + verifyElement(found, foundall, expect); + }, type + " [Root Node].find: " + n + " (with refNodes Array): " + q); + } + + /* + * If neither ctx nor ref is specified: + * root.findAll(selector) + * root.find(selector) + */ + if (!ctx && !ref) { + test(function() { + foundall = root.findAll(q); + verifyNodeList(foundall, expect); + }, type + ".findAll: " + n + " (with no refNodes): " + q); + + test(function() { + found = root.find(q); + verifyElement(found, foundall, expect); + }, type + ".find: " + n + " (with no refNodes): " + q); + } + } + } else { + //console.log("Excluding for " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) + } + } +} + +/* + * Execute queries with the specified invalid selectors for both find() and findAll() + * Only run these tests when errors are expected. Don't run for valid selector tests. + */ +function runInvalidSelectorTestFind(type, root, selectors) { + for (var i = 0; i < selectors.length; i++) { + var s = selectors[i]; + var n = s["name"]; + var q = s["selector"]; + + test(function() { + assert_throws("SyntaxError", function() { + root.find(q) + }) + }, type + ".find: " + n + ": " + q); + + test(function() { + assert_throws("SyntaxError", function() { + root.findAll(q) + }) + }, type + ".findAll: " + n + ": " + q); + } +} + +function verifyNodeList(resultAll, expect) { + assert_not_equals(resultAll, null, "The method should not return null."); + assert_equals(resultAll.length, e.length, "The method should return the expected number of matches."); + + for (var i = 0; i < e.length; i++) { + assert_not_equals(resultAll[i], null, "The item in index " + i + " should not be null.") + assert_equals(resultAll[i].getAttribute("id"), e[i], "The item in index " + i + " should have the expected ID."); + assert_false(resultAll[i].hasAttribute("data-clone"), "This should not be a cloned element."); + } +} + +function verifyElement(result, resultAll, expect) { + if (expect.length > 0) { + assert_not_equals(result, null, "The method should return a match.") + assert_equals(found.getAttribute("id"), e[0], "The method should return the first match."); + assert_equals(result, resultAll[0], "The result should match the first item from querySelectorAll."); + assert_false(found.hasAttribute("data-clone"), "This should not be annotated as a cloned element."); + } else { + assert_equals(result, null, "The method should not match anything."); + } +} diff --git a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-lib.js b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-lib.js index eb2c0f4b9c0..afcdb972043 100644 --- a/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-lib.js +++ b/testing/web-platform/tests/selectors-api/tests/submissions/Opera/level2-lib.js @@ -47,403 +47,14 @@ function setupSpecialElements(parent) { parent.appendChild(noNS); } -/* - * Check that the find, findAll and matches() methods exist on the given Node - */ -function interfaceCheck(type, obj) { - test(function() { - var q = typeof obj.find === "function"; - assert_true(q, type + " supports find."); - }, type + " supports find") - - test(function() { - var qa = typeof obj.findAll === "function"; - assert_true( qa, type + " supports findAll."); - }, type + " supports findAll") - - if (obj.nodeType === obj.ELEMENT_NODE) { - test(function() { - assert_idl_attribute(obj, "matches", type + " supports matches"); - }, type + " supports matches") - } -} - -/* - * Verify that the NodeList returned by findAll is static and and that a new list is created after - * each call. A static list should not be affected by subsequent changes to the DOM. - */ -function verifyStaticList(type, root) { - var pre, post, preLength; - - test(function() { - pre = root.findAll("div"); - preLength = pre.length; - - var div = doc.createElement("div"); - (root.body || root).appendChild(div); - - assert_equals(pre.length, preLength, "The length of the NodeList should not change.") - }, type + ": static NodeList") - - test(function() { - post = root.findAll("div"), - assert_equals(post.length, preLength + 1, "The length of the new NodeList should be 1 more than the previous list.") - }, type + ": new NodeList") -} - -/* - * Verify handling of special values for the selector parameter, including stringification of - * null and undefined, and the handling of the empty string. - */ -function runSpecialSelectorTests(type, root) { - test(function() { // 1 - assert_equals(root.findAll(null).length, 1, "This should find one element with the tag name 'NULL'."); - }, type + ".findAll null") - - test(function() { // 2 - assert_equals(root.findAll(undefined).length, 1, "This should find one elements with the tag name 'UNDEFINED'."); - }, type + ".findAll undefined") - - test(function() { // 3 - assert_throws(TypeError(), function() { - root.findAll(); - }, "This should throw a TypeError.") - }, type + ".findAll no parameter") - - test(function() { // 4 - var elm = root.find(null) - assert_not_equals(elm, null, "This should find an element."); - assert_equals(elm.tagName.toUpperCase(), "NULL", "The tag name should be 'NULL'.") - }, type + ".find null") - - test(function() { // 5 - var elm = root.find(undefined) - assert_not_equals(elm, undefined, "This should find an element."); - assert_equals(elm.tagName.toUpperCase(), "UNDEFINED", "The tag name should be 'UNDEFINED'.") - }, type + ".find undefined") - - test(function() { // 6 - assert_throws(TypeError(), function() { - root.find(); - }, "This should throw a TypeError.") - }, type + ".find no parameter.") - - test(function() { // 7 - result = root.findAll("*"); - var i = 0; - traverse(root, function(elem) { - if (elem !== root) { - assert_equals(elem, result[i++], "The result in index " + i + " should be in tree order.") - } - }) - }, type + ".findAll tree order"); -} - -function runSpecialMatchesTests(type, element) { - test(function() { // 1 - if (element.tagName.toLowerCase() === "null") { - assert_true(element.matches(null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match."); - } else { - assert_false(element.matches(null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match."); - } - }, type + ".matches(null)") - - test(function() { // 2 - if (element.tagName.toLowerCase() === "undefined") { - assert_true(element.matches(undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match."); - } else { - assert_false(element.matches(undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match."); - } - }, type + ".matches(undefined)") - - test(function() { // 3 - assert_throws(TypeError(), function() { - element.matches(); - }, "This should throw a TypeError.") - }, type + ".matches no parameter") -} - -/* - * Execute queries with the specified valid selectors for both find() and findAll() - * Only run these tests when results are expected. Don't run for syntax error tests. - * - * Where testType is TEST_FIND_BASELINE or TEST_FIND_ADDITIONAL: - * - * context.findAll(selector, refNodes) - * context.findAll(selector) // Only if refNodes is not specified - * root.findAll(selector, context) // Only if refNodes is not specified - * root.findAll(selector, refNodes) // Only if context is not specified - * root.findAll(selector) // Only if neither context nor refNodes is specified - * - * Where testType is TEST_QSA_BASELINE or TEST_QSA_ADDITIONAL - * - * context.querySelectorAll(selector) // Only if refNodes is not specified - * root.querySelectorAll(selector) // Only if neither context nor refNodes is specified - * - * Equivalent tests will be run for .find() as well. - * Note: Do not specify a testType of TEST_QSA_* where either implied :scope or explicit refNodes - * are required. - */ -function runValidSelectorTest(type, root, selectors, testType, docType) { - var nodeType = getNodeType(root); - - for (var i = 0; i < selectors.length; i++) { - var s = selectors[i]; - var n = s["name"]; - var q = s["selector"]; - var e = s["expect"]; - - var ctx = s["ctx"]; - var ref = s["ref"]; - - if (!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1)) { - //console.log("Running tests " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) - var foundall, found, context, refNodes, refArray; - - if (s["testType"] & testType & (TEST_FIND)) { - - - /* - * If ctx and ref are specified: - * context.findAll(selector, refNodes) - * context.find(selector, refNodes) - */ - if (ctx && ref) { - context = root.querySelector(ctx); - refNodes = root.querySelectorAll(ref); - refArray = Array.prototype.slice.call(refNodes, 0); - - test(function() { - foundall = context.findAll(q, refNodes); - verifyNodeList(foundall, expect); - }, type + " [Context Element].findAll: " + n + " (with refNodes NodeList): " + q); - - test(function() { - foundall = context.findAll(q, refArray); - verifyNodeList(foundall, expect); - }, type + " [Context Element].findAll: " + n + " (with refNodes Array): " + q); - - test(function() { - found = context.find(q, refNodes); - verifyElement(found, foundall, expect) - }, type + " [Context Element].find: " + n + " (with refNodes NodeList): " + q); - - test(function() { - found = context.find(q, refArray); - verifyElement(found, foundall, expect) - }, type + " [Context Element].find: " + n + " (with refNodes Array): " + q); - } - - - /* - * If ctx is specified, ref is not: - * context.findAll(selector) - * context.find(selector) - * root.findAll(selector, context) - * root.find(selector, context) - */ - if (ctx && !ref) { - context = root.querySelector(ctx); - - test(function() { - foundall = context.findAll(q); - verifyNodeList(foundall, expect); - }, type + " [Context Element].findAll: " + n + " (with no refNodes): " + q); - - test(function() { - found = context.find(q); - verifyElement(found, foundall, expect) - }, type + " [Context Element].find: " + n + " (with no refNodes): " + q); - - test(function() { - foundall = root.findAll(q, context); - verifyNodeList(foundall, expect); - }, type + " [Root Node].findAll: " + n + " (with refNode Element): " + q); - - test(function() { - foundall = root.find(q, context); - verifyElement(found, foundall, expect); - }, type + " [Root Node].find: " + n + " (with refNode Element): " + q); - } - - /* - * If ref is specified, ctx is not: - * root.findAll(selector, refNodes) - * root.find(selector, refNodes) - */ - if (!ctx && ref) { - refNodes = root.querySelectorAll(ref); - refArray = Array.prototype.slice.call(refNodes, 0); - - test(function() { - foundall = root.findAll(q, refNodes); - verifyNodeList(foundall, expect); - }, type + " [Root Node].findAll: " + n + " (with refNodes NodeList): " + q); - - test(function() { - foundall = root.findAll(q, refArray); - verifyNodeList(foundall, expect); - }, type + " [Root Node].findAll: " + n + " (with refNodes Array): " + q); - - test(function() { - found = root.find(q, refNodes); - verifyElement(found, foundall, expect); - }, type + " [Root Node].find: " + n + " (with refNodes NodeList): " + q); - - test(function() { - found = root.find(q, refArray); - verifyElement(found, foundall, expect); - }, type + " [Root Node].find: " + n + " (with refNodes Array): " + q); - } - - /* - * If neither ctx nor ref is specified: - * root.findAll(selector) - * root.find(selector) - */ - if (!ctx && !ref) { - test(function() { - foundall = root.findAll(q); - verifyNodeList(foundall, expect); - }, type + ".findAll: " + n + " (with no refNodes): " + q); - - test(function() { - found = root.find(q); - verifyElement(found, foundall, expect); - }, type + ".find: " + n + " (with no refNodes): " + q); - } - } - - if (s["testType"] & testType & (TEST_QSA)) { - if (ctx && !ref) { - // context.querySelectorAll(selector) // Only if refNodes is not specified - } - - if (!ctx && !ref) { - // root.querySelectorAll(selector) // Only if neither context nor refNodes is specified - } - } - } else { - //console.log("Excluding for " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) - } - } -} - -/* - * Execute queries with the specified invalid selectors for both find() and findAll() - * Only run these tests when errors are expected. Don't run for valid selector tests. - */ -function runInvalidSelectorTest(type, root, selectors) { - for (var i = 0; i < selectors.length; i++) { - var s = selectors[i]; - var n = s["name"]; - var q = s["selector"]; - - test(function() { - assert_throws("SyntaxError", function() { - root.find(q) - }) - }, type + ".find: " + n + ": " + q); - - test(function() { - assert_throws("SyntaxError", function() { - root.findAll(q) - }) - }, type + ".findAll: " + n + ": " + q); - - if (root.nodeType === root.ELEMENT_NODE) { - test(function() { - assert_throws("SyntaxError", function() { - root.matches(q) - }) - }, type + ".matches: " + n + ": " + q); - } - } -} - -function runMatchesTest(type, root, selectors, testType, docType) { - var nodeType = getNodeType(root); - - for (var i = 0; i < selectors.length; i++) { - var s = selectors[i]; - var n = s["name"]; - var q = s["selector"]; - var e = s["expect"]; - var u = s["unexpected"]; - - var ctx = s["ctx"]; - var ref = s["ref"]; - - if ((!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1)) - && (s["testType"] & testType & (TEST_MATCH)) ) { - - if (ctx && !ref) { - test(function() { - var j, element, refNode; - for (j = 0; j < e.length; j++) { - element = root.querySelector("#" + e[j]); - refNode = root.querySelector(ctx); - assert_true(element.matches(q, refNode), "The element #" + e[j] + " should match the selector.") - } - - if (u) { - for (j = 0; j < u.length; j++) { - element = root.querySelector("#" + u[j]); - refNode = root.querySelector(ctx); - assert_false(element.matches(q, refNode), "The element #" + u[j] + " should not match the selector.") - } - } - }, type + " Element.matches: " + n + " (with refNode Element): " + q); - } - - if (ref) { - test(function() { - var j, element, refNodes; - for (j = 0; j < e.length; j++) { - element = root.querySelector("#" + e[j]); - refNodes = root.querySelectorAll(ref); - assert_true(element.matches(q, refNodes), "The element #" + e[j] + " should match the selector.") - } - - if (u) { - for (j = 0; j < u.length; j++) { - element = root.querySelector("#" + u[j]); - refNodes = root.querySelectorAll(ref); - assert_false(element.matches(q, refNodes), "The element #" + u[j] + " should not match the selector.") - } - } - }, type + " Element.matches: " + n + " (with refNodes NodeList): " + q); - } - - if (!ctx && !ref) { - test(function() { - for (var j = 0; j < e.length; j++) { - var element = root.querySelector("#" + e[j]); - assert_true(element.matches(q), "The element #" + e[j] + " should match the selector.") - } - - if (u) { - for (j = 0; j < u.length; j++) { - element = root.querySelector("#" + u[j]); - assert_false(element.matches(q), "The element #" + u[j] + " should not match the selector.") - } - } - }, type + " Element.matches: " + n + " (with no refNodes): " + q); - } - } - } -} - - function traverse(elem, fn) { if (elem.nodeType === elem.ELEMENT_NODE) { fn(elem); - - elem = elem.firstChild; - while (elem) { - traverse(elem, fn); - elem = elem.nextSibling; - } + } + elem = elem.firstChild; + while (elem) { + traverse(elem, fn); + elem = elem.nextSibling; } } @@ -460,25 +71,3 @@ function getNodeType(node) { return "unknown"; // This should never happen. } } - -function verifyNodeList(resultAll, expect) { - assert_not_equals(resultAll, null, "The method should not return null."); - assert_equals(resultAll.length, e.length, "The method should return the expected number of matches."); - - for (var i = 0; i < e.length; i++) { - assert_not_equals(resultAll[i], null, "The item in index " + i + " should not be null.") - assert_equals(resultAll[i].getAttribute("id"), e[i], "The item in index " + i + " should have the expected ID."); - assert_false(resultAll[i].hasAttribute("data-clone"), "This should not be a cloned element."); - } -} - -function verifyElement(result, resultAll, expect) { - if (expect.length > 0) { - assert_not_equals(result, null, "The method should return a match.") - assert_equals(found.getAttribute("id"), e[0], "The method should return the first match."); - assert_equals(result, resultAll[0], "The result should match the first item from querySelectorAll."); - assert_false(found.hasAttribute("data-clone"), "This should not be annotated as a cloned element."); - } else { - assert_equals(result, null, "The method should not match anything."); - } -} diff --git a/testing/web-platform/tests/serve.py b/testing/web-platform/tests/serve.py index 4122f4853d9..d0f16537a76 100644 --- a/testing/web-platform/tests/serve.py +++ b/testing/web-platform/tests/serve.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import argparse import json -import logging import os import signal import socket @@ -19,6 +18,8 @@ repo_root = os.path.abspath(os.path.split(__file__)[0]) sys.path.insert(1, os.path.join(repo_root, "tools", "wptserve")) from wptserve import server as wptserve, handlers from wptserve.router import any_method +from wptserve.logger import set_logger + sys.path.insert(1, os.path.join(repo_root, "tools", "pywebsocket", "src")) from mod_pywebsocket import standalone as pywebsocket @@ -39,12 +40,12 @@ subdomains = [u"www", u"天気の良い日", u"élève"] -logger = None - -def default_logger(level): +def setup_logger(level): + import logging + global logger logger = logging.getLogger("web-platform-tests") logging.basicConfig(level=getattr(logging, level.upper())) - return logger + set_logger(logger) def open_socket(port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -323,12 +324,10 @@ def load_config(default_path, override_path=None): return rv def main(): - global logger - config = load_config("config.default.json", "config.json") - logger = default_logger(config["log_level"]) + setup_logger(config["log_level"]) config_, servers = start(config) diff --git a/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html index 10501de21e6..ce6a045e25e 100644 --- a/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html +++ b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html @@ -21,7 +21,7 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope { readonly attribute ServiceWorkerClients clients; [Unforgeable] readonly attribute DOMString scope; - Promise fetch((Request or USVString) request); + Promise fetch((Request or ScalarValueString) request); void update(); void unregister(); @@ -55,7 +55,7 @@ synchronous requests MUST NOT be initiated inside of a Service Worker. interface CacheStorage {}; interface ServiceWorkerClients {}; interface Request {}; - interface USVString {}; + interface ScalarValueString {}; interface EventHandler {}; interface WorkerGlobalScope {}; diff --git a/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html index b899ae8d794..5f0c6f07a93 100644 --- a/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html +++ b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html @@ -15,11 +15,11 @@ - diff --git a/testing/web-platform/tests/webdriver/webdriver.cfg b/testing/web-platform/tests/webdriver/webdriver.cfg index 06ac7e9e5ba..98ecde7bd7c 100644 --- a/testing/web-platform/tests/webdriver/webdriver.cfg +++ b/testing/web-platform/tests/webdriver/webdriver.cfg @@ -24,3 +24,8 @@ mode: compatibility [ios-driver] capabilities: {"browserName": "iphone"} mode: compatibility + +[blackberry] +url: http://169.254.0.1:1338 +capabilities: {"browserName": "blackberry"} +mode: compatibility diff --git a/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html b/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html index f28f4d3043a..7ae4b070474 100644 --- a/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html +++ b/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html @@ -1,6 +1,6 @@ diff --git a/testing/web-platform/tests/workers/semantics/structured-clone/common.js b/testing/web-platform/tests/workers/semantics/structured-clone/common.js index bcf52b61438..56c2a300401 100644 --- a/testing/web-platform/tests/workers/semantics/structured-clone/common.js +++ b/testing/web-platform/tests/workers/semantics/structured-clone/common.js @@ -25,7 +25,7 @@ function compare_primitive(actual, input, test_obj) { if (test_obj) test_obj.done(); } -function compare_Array(callback) { +function compare_Array(callback, callback_is_async) { return function(actual, input, test_obj) { if (typeof actual === 'string') assert_unreached(actual); @@ -33,12 +33,12 @@ function compare_Array(callback) { assert_not_equals(actual, input); assert_equals(actual.length, input.length, 'length'); callback(actual, input); - if (test_obj) + if (test_obj && !callback_is_async) test_obj.done(); } } -function compare_Object(callback) { +function compare_Object(callback, callback_is_async) { return function(actual, input, test_obj) { if (typeof actual === 'string') assert_unreached(actual); @@ -46,15 +46,15 @@ function compare_Object(callback) { assert_false(actual instanceof Array, 'instanceof Array'); assert_not_equals(actual, input); callback(actual, input); - if (test_obj) + if (test_obj && !callback_is_async) test_obj.done(); } } -function enumerate_props(compare_func) { +function enumerate_props(compare_func, test_obj) { return function(actual, input) { for (var x in input) { - compare_func(actual[x], input[x]); + compare_func(actual[x], input[x], test_obj); } }; } @@ -339,41 +339,41 @@ function func_Blob_NUL() { check('Blob NUL', func_Blob_NUL, compare_Blob); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_basic)], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_basic)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob basic'); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800]))], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob unpaired high surrogate (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_bytes([0xDC00]))], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_bytes([0xDC00]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob unpaired low surrogate (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob paired surrogates (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_empty)], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_empty)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob empty'); async_test(function(test_obj) { - check(test_obj.name, [test_obj.step(func_Blob_NUL)], compare_Array(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, [test_obj.step(func_Blob_NUL)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Array Blob object, Blob NUL'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_basic)}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_basic)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob basic'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800]))}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob unpaired high surrogate (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xDC00]))}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xDC00]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob unpaired low surrogate (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob paired surrogates (invalid utf-8)'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_empty)}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_empty)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob empty'); async_test(function(test_obj) { - check(test_obj.name, {'x':test_obj.step(func_Blob_NUL)}, compare_Object(enumerate_props(compare_Blob)), test_obj); + check(test_obj.name, {'x':test_obj.step(func_Blob_NUL)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj); }, 'Object Blob object, Blob NUL'); function compare_File(actual, input, test_obj) { diff --git a/testing/web-platform/tests/workers/semantics/structured-clone/worker-common.js b/testing/web-platform/tests/workers/semantics/structured-clone/worker-common.js index f46df825730..fd63ff5a5d7 100644 --- a/testing/web-platform/tests/workers/semantics/structured-clone/worker-common.js +++ b/testing/web-platform/tests/workers/semantics/structured-clone/worker-common.js @@ -9,8 +9,9 @@ function check_true(actual, msg) { return true; } -function check_Blob(msg, input, port, expect_File) { +function check_Blob(msg, input, port, expect_File, orig_input) { expect_File = !!expect_File; + orig_input = orig_input || input; try { var expected; switch (msg) { @@ -64,7 +65,7 @@ function check_Blob(msg, input, port, expect_File) { check_true(view.getUint8(i) === expected[i], 'view.getUint8('+i+') === expected['+i+']') } if (log.length === 0) { - port.postMessage(input); + port.postMessage(orig_input); } else { port.postMessage('FAIL '+log); } @@ -724,7 +725,7 @@ function check(input, port) { case 'Array Blob object, Blob NUL': if (check_true(input instanceof Array, 'input instanceof Array') && check_true(input.length === 1, 'input.length === 1')) { - check_Blob(msg.substr('Array Blob object, '.length), input[0], port); + check_Blob(msg.substr('Array Blob object, '.length), input[0], port, false, input); // no postMessage or close here, check_Blob takes care of that } break; @@ -742,7 +743,7 @@ function check(input, port) { i++; } if (check_true(i === 1, 'i === 1')) { - check_Blob(msg.substr('Object Blob object, '.length), input['x'], port); + check_Blob(msg.substr('Object Blob object, '.length), input['x'], port, false, input); // no postMessage or close here, check_Blob takes care of that } } diff --git a/toolkit/components/places/PlacesUtils.jsm b/toolkit/components/places/PlacesUtils.jsm index 7ec5b61369c..05ba61260c9 100644 --- a/toolkit/components/places/PlacesUtils.jsm +++ b/toolkit/components/places/PlacesUtils.jsm @@ -834,6 +834,17 @@ this.PlacesUtils = { return null; }, + /** + * Gets the href and the post data for a given keyword, if any. + * + * @param keyword + * The string keyword to look for. + * @return {Promise} + * @resolves to a { href, postData } object. Both properties evaluate to null + * if no keyword is found. + */ + promiseHrefAndPostDataForKeyword(keyword) KeywordsCache.promiseEntry(keyword), + /** * Get the URI (and any associated POST data) for a given keyword. * @param aKeyword string keyword @@ -1965,6 +1976,114 @@ let GuidHelper = { } }; +// Cache of bookmarks keywords, used to quickly resolve keyword => URL requests. +let KeywordsCache = { + /** + * Initializes the cache. + * Every method should check _initialized and, if false, yield _initialize(). + */ + _initialized: false, + _initialize: Task.async(function* () { + // First populate the cache... + yield this._reloadCache(); + + // ...then observe changes to keep the cache up-to-date. + PlacesUtils.bookmarks.addObserver(this, false); + PlacesUtils.registerShutdownFunction(() => { + PlacesUtils.bookmarks.removeObserver(this); + }); + + this._initialized = true; + }), + + // nsINavBookmarkObserver + // Manually updating the cache would be tricky because some notifications + // don't report the original bookmark url and we also keep urls sorted by + // last modified. Since changing a keyword-ed bookmark is a rare event, + // it's easier to reload the cache. + onItemChanged(itemId, property, isAnno, val, lastModified, type, + parentId, guid, parentGuid) { + if (property == "keyword" || property == this.POST_DATA_ANNO || + this._keywordedGuids.has(guid)) { + // Since this cache is used in hot paths, it should be readily available + // as fast as possible. + this._reloadCache().catch(Cu.reportError); + } + }, + onItemRemoved(itemId, parentId, index, type, uri, guid, parentGuid) { + if (this._keywordedGuids.has(guid)) { + // Since this cache is used in hot paths, it should be readily available + // as fast as possible. + this._reloadCache().catch(Cu.reportError); + } + }, + QueryInterface: XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]), + __noSuchMethod__() {}, // Catch all remaining onItem* methods. + + // Maps an { href, postData } object to each keyword. + // Even if a keyword may be associated to multiple URLs, only the last + // modified bookmark href is retained here. + _urlDataForKeyword: null, + // Tracks GUIDs having a keyword. + _keywordedGuids: null, + + /** + * Reloads the cache. + */ + _reloadPromise: null, + _reloadCache() { + return this._reloadPromise = Task.spawn(function* () { + let db = yield PlacesUtils.promiseDBConnection(); + let rows = yield db.execute( + `/* do not warn (bug no) - there is no index on keyword_id */ + SELECT b.id, b.guid, h.url, k.keyword FROM moz_bookmarks b + JOIN moz_places h ON h.id = b.fk + JOIN moz_keywords k ON k.id = b.keyword_id + ORDER BY b.lastModified DESC + `); + + this._urlDataForKeyword = new Map(); + this._keywordedGuids = new Set(); + + for (let row of rows) { + let guid = row.getResultByName("guid"); + this._keywordedGuids.add(guid); + + let keyword = row.getResultByName("keyword"); + // Only keep the most recent href. + let urlData = this._urlDataForKeyword.get(keyword); + if (urlData) + continue; + + let id = row.getResultByName("id"); + let href = row.getResultByName("url"); + let postData = PlacesUtils.getPostDataForBookmark(id); + this._urlDataForKeyword.set(keyword, { href, postData }); + } + }.bind(this)).then(() => { + this._reloadPromise = null; + }); + }, + + /** + * Fetches a { href, postData } entry for the given keyword. + * + * @param keyword + * The keyword to look for. + * @return {promise} + * @resolves when the fetching is complete. + */ + promiseEntry: Task.async(function* (keyword) { + // We could yield regardless and do the checks internally, but that would + // waste at least a couple ticks and this can be used on hot paths. + if (!this._initialized) + yield this._initialize(); + if (this._reloadPromise) + yield this._reloadPromise; + return this._urlDataForKeyword.get(keyword) || { href: null, postData: null }; + }), +}; + //////////////////////////////////////////////////////////////////////////////// //// Transactions handlers. diff --git a/toolkit/components/places/UnifiedComplete.js b/toolkit/components/places/UnifiedComplete.js index 7d71c401fc7..5bbf760e7b8 100644 --- a/toolkit/components/places/UnifiedComplete.js +++ b/toolkit/components/places/UnifiedComplete.js @@ -768,7 +768,7 @@ Search.prototype = { let hasFirstResult = false; if (this._searchTokens.length > 0 && - PlacesUtils.bookmarks.getURIForKeyword(this._searchTokens[0])) { + (yield PlacesUtils.promiseHrefAndPostDataForKeyword(this._searchTokens[0])).href) { // This may be a keyword of a bookmark. queries.unshift(this._keywordQuery); hasFirstResult = true; diff --git a/toolkit/components/places/nsNavBookmarks.cpp b/toolkit/components/places/nsNavBookmarks.cpp index 09ba9024949..bb2eaee5516 100644 --- a/toolkit/components/places/nsNavBookmarks.cpp +++ b/toolkit/components/places/nsNavBookmarks.cpp @@ -19,8 +19,6 @@ #include "GeckoProfiler.h" -#define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH 32 - using namespace mozilla; // These columns sit to the right of the kGetInfoIndex_* columns. @@ -40,25 +38,6 @@ PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService) namespace { -struct keywordSearchData -{ - int64_t itemId; - nsString keyword; -}; - -PLDHashOperator -SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey, - const nsString aValue, - void* aUserArg) -{ - keywordSearchData* data = reinterpret_cast(aUserArg); - if (data->keyword.Equals(aValue)) { - data->itemId = aKey; - return PL_DHASH_STOP; - } - return PL_DHASH_NEXT; -} - template class AsyncGetBookmarksForURI : public AsyncStatementCallback { @@ -143,8 +122,6 @@ nsNavBookmarks::nsNavBookmarks() , mCanNotify(false) , mCacheObservers("bookmark-observers") , mBatching(false) - , mBookmarkToKeywordHash(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH) - , mBookmarkToKeywordHashInitialized(false) { NS_ASSERTION(!gBookmarksService, "Attempting to create two instances of the service!"); @@ -646,7 +623,7 @@ nsNavBookmarks::RemoveItem(int64_t aItemId) NS_ENSURE_SUCCESS(rv, rv); } - rv = UpdateKeywordsHashForRemovedBookmark(aItemId); + rv = removeOrphanKeywords(); NS_ENSURE_SUCCESS(rv, rv); // A broken url should not interrupt the removal process. @@ -1119,7 +1096,7 @@ nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId) NS_ENSURE_SUCCESS(rv, rv); } - rv = UpdateKeywordsHashForRemovedBookmark(child.id); + rv = removeOrphanKeywords(); NS_ENSURE_SUCCESS(rv, rv); } } @@ -2255,39 +2232,23 @@ nsNavBookmarks::SetItemIndex(int64_t aItemId, int32_t aNewIndex) nsresult -nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(int64_t aItemId) +nsNavBookmarks::removeOrphanKeywords() { - nsAutoString keyword; - if (NS_SUCCEEDED(GetKeywordForBookmark(aItemId, keyword)) && - !keyword.IsEmpty()) { - nsresult rv = EnsureKeywordsHash(); - NS_ENSURE_SUCCESS(rv, rv); - mBookmarkToKeywordHash.Remove(aItemId); + // If the keyword is unused, remove it from the database. + nsCOMPtr stmt = mDB->GetAsyncStatement( + "DELETE FROM moz_keywords " + "WHERE NOT EXISTS ( " + "SELECT id " + "FROM moz_bookmarks " + "WHERE keyword_id = moz_keywords.id " + ")" + ); + NS_ENSURE_STATE(stmt); - // If the keyword is unused, remove it from the database. - keywordSearchData searchData; - searchData.keyword.Assign(keyword); - searchData.itemId = -1; - mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData); - if (searchData.itemId == -1) { - nsCOMPtr stmt = mDB->GetAsyncStatement( - "DELETE FROM moz_keywords " - "WHERE keyword = :keyword " - "AND NOT EXISTS ( " - "SELECT id " - "FROM moz_bookmarks " - "WHERE keyword_id = moz_keywords.id " - ")" - ); - NS_ENSURE_STATE(stmt); + nsCOMPtr pendingStmt; + nsresult rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt)); + NS_ENSURE_SUCCESS(rv, rv); - rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr pendingStmt; - rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt)); - NS_ENSURE_SUCCESS(rv, rv); - } - } return NS_OK; } @@ -2303,9 +2264,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId, nsresult rv = FetchItemInfo(aBookmarkId, bookmark); NS_ENSURE_SUCCESS(rv, rv); - rv = EnsureKeywordsHash(); - NS_ENSURE_SUCCESS(rv, rv); - // Shortcuts are always lowercased internally. nsAutoString keyword(aUserCasedKeyword); ToLowerCase(keyword); @@ -2331,8 +2289,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId, mozStorageStatementScoper updateBookmarkScoper(updateBookmarkStmt); if (keyword.IsEmpty()) { - // Remove keyword association from the hash. - mBookmarkToKeywordHash.Remove(bookmark.id); rv = updateBookmarkStmt->BindNullByName(NS_LITERAL_CSTRING("keyword")); } else { @@ -2350,10 +2306,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId, rv = newKeywordStmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); - // Add new keyword association to the hash, removing the old one if needed. - if (!oldKeyword.IsEmpty()) - mBookmarkToKeywordHash.Remove(bookmark.id); - mBookmarkToKeywordHash.Put(bookmark.id, keyword); rv = updateBookmarkStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword); } NS_ENSURE_SUCCESS(rv, rv); @@ -2411,12 +2363,12 @@ nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword) rv = stmt->ExecuteStep(&hasMore); if (NS_FAILED(rv) || !hasMore) { aKeyword.SetIsVoid(true); - return NS_OK; // not found: return void keyword string + return NS_OK; } - // found, get the keyword rv = stmt->GetString(0, aKeyword); NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -2427,16 +2379,28 @@ nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword) NS_ENSURE_ARG_MIN(aBookmarkId, 1); aKeyword.Truncate(0); - nsresult rv = EnsureKeywordsHash(); + nsCOMPtr stmt = mDB->GetStatement( + "/* do not warn (bug no) - there is no index on keyword_id) */ " + "SELECT k.keyword " + "FROM moz_bookmarks b " + "JOIN moz_keywords k ON k.id = b.keyword_id " + "WHERE b.id = :id " + ); + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aBookmarkId); NS_ENSURE_SUCCESS(rv, rv); - nsAutoString keyword; - if (!mBookmarkToKeywordHash.Get(aBookmarkId, &keyword)) { + bool hasMore = false; + rv = stmt->ExecuteStep(&hasMore); + if (NS_FAILED(rv) || !hasMore) { aKeyword.SetIsVoid(true); + return NS_OK; } - else { - aKeyword.Assign(keyword); - } + + rv = stmt->GetString(0, aKeyword); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } @@ -2454,53 +2418,33 @@ nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword, nsAutoString keyword(aUserCasedKeyword); ToLowerCase(keyword); - nsresult rv = EnsureKeywordsHash(); + nsCOMPtr stmt = mDB->GetStatement( + "/* do not warn (bug no) - there is no index on keyword_id) */ " + "SELECT url FROM moz_keywords k " + "JOIN moz_bookmarks b ON b.keyword_id = k.id " + "JOIN moz_places h ON b.fk = h.id " + "WHERE k.keyword = :keyword " + "ORDER BY b.dateAdded DESC" + ); + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword); NS_ENSURE_SUCCESS(rv, rv); - keywordSearchData searchData; - searchData.keyword.Assign(keyword); - searchData.itemId = -1; - mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData); - - if (searchData.itemId == -1) { - // Not found. + bool hasMore = false; + rv = stmt->ExecuteStep(&hasMore); + if (NS_FAILED(rv) || !hasMore) { return NS_OK; } - rv = GetBookmarkURI(searchData.itemId, aURI); + nsCString url; + rv = stmt->GetUTF8String(0, url); NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - - -nsresult -nsNavBookmarks::EnsureKeywordsHash() { - if (mBookmarkToKeywordHashInitialized) { - return NS_OK; - } - mBookmarkToKeywordHashInitialized = true; - - nsCOMPtr stmt; - nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING( - "SELECT b.id, k.keyword " - "FROM moz_bookmarks b " - "JOIN moz_keywords k ON k.id = b.keyword_id " - ), getter_AddRefs(stmt)); + rv = NS_NewURI(aURI, url); NS_ENSURE_SUCCESS(rv, rv); - bool hasMore; - while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) { - int64_t itemId; - rv = stmt->GetInt64(0, &itemId); - NS_ENSURE_SUCCESS(rv, rv); - nsAutoString keyword; - rv = stmt->GetString(1, keyword); - NS_ENSURE_SUCCESS(rv, rv); - - mBookmarkToKeywordHash.Put(itemId, keyword); - } - return NS_OK; } diff --git a/toolkit/components/places/nsNavBookmarks.h b/toolkit/components/places/nsNavBookmarks.h index 02bc210bbb3..a1a87f2e26e 100644 --- a/toolkit/components/places/nsNavBookmarks.h +++ b/toolkit/components/places/nsNavBookmarks.h @@ -422,20 +422,9 @@ private: bool mBatching; /** - * Always call EnsureKeywordsHash() and check it for errors before actually - * using the hash. Internal keyword methods are already doing that. + * Removes orphan keywords. */ - nsresult EnsureKeywordsHash(); - nsDataHashtable mBookmarkToKeywordHash; - bool mBookmarkToKeywordHashInitialized; - - /** - * This function must be called every time a bookmark is removed. - * - * @param aURI - * Uri to test. - */ - nsresult UpdateKeywordsHashForRemovedBookmark(int64_t aItemId); + nsresult removeOrphanKeywords(); }; #endif // nsNavBookmarks_h_ diff --git a/toolkit/components/places/nsPlacesAutoComplete.js b/toolkit/components/places/nsPlacesAutoComplete.js index b4bae7e9edc..30e31da63d2 100644 --- a/toolkit/components/places/nsPlacesAutoComplete.js +++ b/toolkit/components/places/nsPlacesAutoComplete.js @@ -12,6 +12,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); //////////////////////////////////////////////////////////////////////////////// //// Constants @@ -1447,74 +1449,76 @@ urlInlineComplete.prototype = { this._listener = aListener; - // Don't autoFill if the search term is recognized as a keyword, otherwise - // it will override default keywords behavior. Note that keywords are - // hashed on first use, so while the first query may delay a little bit, - // next ones will just hit the memory hash. - if (this._currentSearchString.length == 0 || !this._db || - PlacesUtils.bookmarks.getURIForKeyword(this._currentSearchString)) { - this._finishSearch(); - return; - } - - // Don't try to autofill if the search term includes any whitespace. - // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH - // tokenizer ends up trimming the search string and returning a value - // that doesn't match it, or is even shorter. - if (/\s/.test(this._currentSearchString)) { - this._finishSearch(); - return; - } - - // Hosts have no "/" in them. - let lastSlashIndex = this._currentSearchString.lastIndexOf("/"); - - // Search only URLs if there's a slash in the search string... - if (lastSlashIndex != -1) { - // ...but not if it's exactly at the end of the search string. - if (lastSlashIndex < this._currentSearchString.length - 1) - this._queryURL(); - else + Task.spawn(function* () { + // Don't autoFill if the search term is recognized as a keyword, otherwise + // it will override default keywords behavior. Note that keywords are + // hashed on first use, so while the first query may delay a little bit, + // next ones will just hit the memory hash. + if (this._currentSearchString.length == 0 || !this._db || + (yield PlacesUtils.promiseHrefAndPostDataForKeyword(this._currentSearchString)).href) { this._finishSearch(); - return; - } - - // Do a synchronous search on the table of hosts. - let query = this._hostQuery; - query.params.search_string = this._currentSearchString.toLowerCase(); - // This is just to measure the delay to reach the UI, not the query time. - TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY); - let ac = this; - let wrapper = new AutoCompleteStatementCallbackWrapper(this, { - handleResult: function (aResultSet) { - let row = aResultSet.getNextRow(); - let trimmedHost = row.getResultByIndex(0); - let untrimmedHost = row.getResultByIndex(1); - // If the untrimmed value doesn't preserve the user's input just - // ignore it and complete to the found host. - if (untrimmedHost && - !untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) { - untrimmedHost = null; - } - - ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost); - - // handleCompletion() will cause the result listener to be called, and - // will display the result in the UI. - }, - - handleError: function (aError) { - Components.utils.reportError( - "URL Inline Complete: An async statement encountered an " + - "error: " + aError.result + ", '" + aError.message + "'"); - }, - - handleCompletion: function (aReason) { - TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY); - ac._finishSearch(); + return; } - }, this._db); - this._pendingQuery = wrapper.executeAsync([query]); + + // Don't try to autofill if the search term includes any whitespace. + // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH + // tokenizer ends up trimming the search string and returning a value + // that doesn't match it, or is even shorter. + if (/\s/.test(this._currentSearchString)) { + this._finishSearch(); + return; + } + + // Hosts have no "/" in them. + let lastSlashIndex = this._currentSearchString.lastIndexOf("/"); + + // Search only URLs if there's a slash in the search string... + if (lastSlashIndex != -1) { + // ...but not if it's exactly at the end of the search string. + if (lastSlashIndex < this._currentSearchString.length - 1) + this._queryURL(); + else + this._finishSearch(); + return; + } + + // Do a synchronous search on the table of hosts. + let query = this._hostQuery; + query.params.search_string = this._currentSearchString.toLowerCase(); + // This is just to measure the delay to reach the UI, not the query time. + TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY); + let ac = this; + let wrapper = new AutoCompleteStatementCallbackWrapper(this, { + handleResult: function (aResultSet) { + let row = aResultSet.getNextRow(); + let trimmedHost = row.getResultByIndex(0); + let untrimmedHost = row.getResultByIndex(1); + // If the untrimmed value doesn't preserve the user's input just + // ignore it and complete to the found host. + if (untrimmedHost && + !untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) { + untrimmedHost = null; + } + + ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost); + + // handleCompletion() will cause the result listener to be called, and + // will display the result in the UI. + }, + + handleError: function (aError) { + Components.utils.reportError( + "URL Inline Complete: An async statement encountered an " + + "error: " + aError.result + ", '" + aError.message + "'"); + }, + + handleCompletion: function (aReason) { + TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY); + ac._finishSearch(); + } + }, this._db); + this._pendingQuery = wrapper.executeAsync([query]); + }.bind(this)); }, /** diff --git a/toolkit/components/places/tests/unit/test_PlacesUtils_promiseHrefAndPostDataForKeyword.js b/toolkit/components/places/tests/unit/test_PlacesUtils_promiseHrefAndPostDataForKeyword.js new file mode 100644 index 00000000000..e854daabc3e --- /dev/null +++ b/toolkit/components/places/tests/unit/test_PlacesUtils_promiseHrefAndPostDataForKeyword.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* test_no_keyword() { + Assert.deepEqual({ href: null, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should not exist"); +}); + +add_task(function* test_add_remove() { + let item1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example1.com/", + keyword: "test" }); + Assert.deepEqual({ href: item1.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item1.url.href); + + // Add a second url for the same keyword, since it's newer it should be + // returned. + let item2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example2.com/", + keyword: "test" }); + Assert.deepEqual({ href: item2.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item2.url.href); + + // Now remove item2, should return item1 again. + yield PlacesUtils.bookmarks.remove(item2); + Assert.deepEqual({ href: item1.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item1.url.href); + + // Now remove item1, should return null again. + yield PlacesUtils.bookmarks.remove(item1); + Assert.deepEqual({ href: null, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should not exist"); +}); + +add_task(function* test_change_url() { + let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/", + keyword: "test" }); + Assert.deepEqual({ href: item.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item.url.href); + + // Change the bookmark url. + let updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid, + url: "http://example2.com" }); + Assert.deepEqual({ href: updatedItem.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + updatedItem.url.href); + yield PlacesUtils.bookmarks.remove(updatedItem); +}); + +add_task(function* test_change_keyword() { + let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/", + keyword: "test" }); + Assert.deepEqual({ href: item.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item.url.href); + + // Change the bookmark keywprd. + let updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid, + keyword: "test2" }); + Assert.deepEqual({ href: null, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should not exist"); + Assert.deepEqual({ href: updatedItem.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test2")), + "Keyword 'test' should point to " + updatedItem.url.href); + + // Remove the bookmark keyword. + updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid, + keyword: "" }); + Assert.deepEqual({ href: null, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should not exist"); + Assert.deepEqual({ href: null, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test2")), + "Keyword 'test' should not exist"); + yield PlacesUtils.bookmarks.remove(updatedItem); +}); + +add_task(function* test_postData() { + let item1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example1.com/", + keyword: "test" }); + let itemId1 = yield PlacesUtils.promiseItemId(item1.guid); + PlacesUtils.setPostDataForBookmark(itemId1, "testData"); + Assert.deepEqual({ href: item1.url.href, postData: "testData" }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item1.url.href); + + // Add a second url for the same keyword, but without postData. + let item2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example2.com/", + keyword: "test" }); + Assert.deepEqual({ href: item2.url.href, postData: null }, + (yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")), + "Keyword 'test' should point to " + item2.url.href); + + yield PlacesUtils.bookmarks.remove(item1); + yield PlacesUtils.bookmarks.remove(item2); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/places/tests/unit/xpcshell.ini b/toolkit/components/places/tests/unit/xpcshell.ini index bb6ec863bef..a887da9f184 100644 --- a/toolkit/components/places/tests/unit/xpcshell.ini +++ b/toolkit/components/places/tests/unit/xpcshell.ini @@ -119,6 +119,7 @@ skip-if = true [test_PlacesSearchAutocompleteProvider.js] [test_PlacesUtils_asyncGetBookmarkIds.js] [test_PlacesUtils_lazyobservers.js] +[test_PlacesUtils_promiseHrefAndPostDataForKeyword.js] [test_placesTxn.js] [test_preventive_maintenance.js] # Bug 676989: test hangs consistently on Android diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index af51df0e199..5fe5ab39fba 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6601,6 +6601,12 @@ "n_buckets": 10, "extended_statistics_ok": true }, + "CERT_VALIDATION_SUCCESS_BY_CA": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 256, + "description": "Successful SSL server cert validations by CA (see RootHashes.inc for names of CAs)" + }, "CERT_PINNING_FAILURES_BY_CA": { "alert_emails": ["pinning@mozilla.org"], "expires_in_version": "never", diff --git a/toolkit/content/globalOverlay.js b/toolkit/content/globalOverlay.js index 0c964a2811c..c42dbb71ba4 100644 --- a/toolkit/content/globalOverlay.js +++ b/toolkit/content/globalOverlay.js @@ -148,34 +148,6 @@ function goOnEvent(aNode, aEvent) } } -function visitLink(aEvent) { - var node = aEvent.target; - while (node.nodeType != Node.ELEMENT_NODE) - node = node.parentNode; - var url = node.getAttribute("link"); - if (!url) - return; - - var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"] - .getService(Components.interfaces.nsIExternalProtocolService); - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - var uri = ioService.newURI(url, null, null); - - // if the scheme is not an exposed protocol, then opening this link - // should be deferred to the system's external protocol handler - if (protocolSvc.isExposedProtocol(uri.scheme)) { - var win = window.top; - if (win instanceof Components.interfaces.nsIDOMChromeWindow) { - while (win.opener && !win.opener.closed) - win = win.opener; - } - win.open(uri.spec); - } - else - protocolSvc.loadUrl(uri); -} - function setTooltipText(aID, aTooltipText) { var element = document.getElementById(aID); diff --git a/toolkit/content/tests/chrome/window_panel.xul b/toolkit/content/tests/chrome/window_panel.xul index 7e1ab80800e..b99b52dfafe 100644 --- a/toolkit/content/tests/chrome/window_panel.xul +++ b/toolkit/content/tests/chrome/window_panel.xul @@ -196,7 +196,7 @@ var tests = [ if (navigator.platform.indexOf("Linux") < 0) { ok(panelrect.top >= 210 - mozInnerScreenY + 10, testname + "top greater"); } - ok(panelrect.top <= 210 - mozInnerScreenY + 30, testname + "top less"); + ok(panelrect.top <= 210 - mozInnerScreenY + 32, testname + "top less"); is(panelrect.width, 120, testname + "width"); is(panelrect.height, 40, testname + "height"); diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h index 6a351290959..266a47fd7aa 100644 --- a/toolkit/mozapps/update/common/updatedefines.h +++ b/toolkit/mozapps/update/common/updatedefines.h @@ -51,7 +51,7 @@ // multiple nulls in a string is fine and this approach is simpler (possibly // faster) than calculating the string length to place the null terminator and // truncates the string as _snprintf and _snwprintf do on other platforms. -static int mysnprintf(char* dest, size_t count, const char* fmt, ...) +static inline int mysnprintf(char* dest, size_t count, const char* fmt, ...) { size_t _count = count - 1; va_list varargs; @@ -62,7 +62,7 @@ static int mysnprintf(char* dest, size_t count, const char* fmt, ...) return result; } #define snprintf mysnprintf -static int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) +static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) { size_t _count = count - 1; va_list varargs; diff --git a/tools/mercurial/hgsetup/update.py b/tools/mercurial/hgsetup/update.py new file mode 100644 index 00000000000..f73c20dfea8 --- /dev/null +++ b/tools/mercurial/hgsetup/update.py @@ -0,0 +1,93 @@ +# 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/. + +from __future__ import unicode_literals + +import errno +import os +import which + +from configobj import ConfigObjError + +from mozversioncontrol.repoupdate import update_mercurial_repo + +from .config import ( + HgIncludeException, + MercurialConfig, + HOST_FINGERPRINTS, +) + +FINISHED = ''' +Your Mercurial recommended extensions are now up to date! +'''.lstrip() + + +class MercurialUpdater(object): + + def __init__(self, state_dir): + self.state_dir = os.path.normpath(state_dir) + self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions') + self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools') + + def update_all(self, config_paths): + try: + os.makedirs(self.ext_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + try: + hg = which.which('hg') + except which.WhichError as e: + print(e) + print('Try running |mach bootstrap| to ensure your environment is ' + 'up to date.') + return 1 + + try: + c = MercurialConfig(config_paths) + except ConfigObjError as e: + print('Error importing existing Mercurial config!\n') + for error in e.errors: + print(error.message) + + return 1 + except HgIncludeException as e: + print(e.message) + + return 1 + + if 'mqext' in c.extensions: + self.update_mercurial_repo( + hg, + 'https://bitbucket.org/sfink/mqext', + os.path.join(self.ext_dir, 'mqext'), + 'default', + 'Ensuring mqext is up to date...') + + if os.path.isdir(self.vcs_tools_dir): + self.update_mercurial_repo( + hg, + 'https://hg.mozilla.org/hgcustom/version-control-tools', + self.vcs_tools_dir, + 'default', + 'Ensuring version-control-tools is up to date...') + print(FINISHED) + return 0 + + def update_mercurial_repo(self, hg, url, dest, branch, msg): + # We always pass the host fingerprints that we "know" to be canonical + # because the existing config may have outdated fingerprints and this + # may cause Mercurial to abort. + return self._update_repo(hg, url, dest, branch, msg, + update_mercurial_repo, hostfingerprints=HOST_FINGERPRINTS) + + def _update_repo(self, binary, url, dest, branch, msg, fn, *args, **kwargs): + print('=' * 80) + print(msg) + try: + fn(binary, url, dest, branch, *args, **kwargs) + finally: + print('=' * 80) + print('') diff --git a/tools/mercurial/hgsetup/wizard.py b/tools/mercurial/hgsetup/wizard.py index 27a91e69f84..c666d5b443e 100644 --- a/tools/mercurial/hgsetup/wizard.py +++ b/tools/mercurial/hgsetup/wizard.py @@ -17,14 +17,10 @@ from configobj import ConfigObjError from StringIO import StringIO from mozversioncontrol import get_hg_version -from mozversioncontrol.repoupdate import ( - update_mercurial_repo, - update_git_repo, -) +from .update import MercurialUpdater from .config import ( HgIncludeException, - HOST_FINGERPRINTS, MercurialConfig, ) @@ -172,6 +168,7 @@ class MercurialSetupWizard(object): self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions') self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools') self.update_vcs_tools = False + self.updater = MercurialUpdater(state_dir) def run(self, config_paths): try: @@ -282,7 +279,7 @@ class MercurialSetupWizard(object): os.path.join(self.ext_dir, 'mqext')) if 'mqext' in c.extensions: - self.update_mercurial_repo( + self.updater.update_mercurial_repo( hg, 'https://bitbucket.org/sfink/mqext', os.path.join(self.ext_dir, 'mqext'), @@ -323,7 +320,7 @@ class MercurialSetupWizard(object): c.set_bugzilla_credentials(bzuser, bzpass) if self.update_vcs_tools: - self.update_mercurial_repo( + self.updater.update_mercurial_repo( hg, 'https://hg.mozilla.org/hgcustom/version-control-tools', self.vcs_tools_dir, @@ -402,25 +399,6 @@ class MercurialSetupWizard(object): self.update_vcs_tools = True c.activate_extension(name, path) - def update_mercurial_repo(self, hg, url, dest, branch, msg): - # We always pass the host fingerprints that we "know" to be canonical - # because the existing config may have outdated fingerprints and this - # may cause Mercurial to abort. - return self._update_repo(hg, url, dest, branch, msg, - update_mercurial_repo, hostfingerprints=HOST_FINGERPRINTS) - - def update_git_repo(self, git, url, dest, ref, msg): - return self._update_repo(git, url, dest, ref, msg, update_git_repo) - - def _update_repo(self, binary, url, dest, branch, msg, fn, *args, **kwargs): - print('=' * 80) - print(msg) - try: - fn(binary, url, dest, branch, *args, **kwargs) - finally: - print('=' * 80) - print('') - def _prompt(self, msg, allow_empty=False): print(msg) diff --git a/tools/mercurial/mach_commands.py b/tools/mercurial/mach_commands.py index 6c5e2a4d471..9fbd00aa992 100644 --- a/tools/mercurial/mach_commands.py +++ b/tools/mercurial/mach_commands.py @@ -9,6 +9,7 @@ import sys from mach.decorators import ( CommandProvider, + CommandArgument, Command, ) @@ -20,16 +21,25 @@ class VersionControlCommands(object): @Command('mercurial-setup', category='devenv', description='Help configure Mercurial for optimal development.') - def mercurial_bootstrap(self): + @CommandArgument('-u', '--update-only', action='store_true', + help='Only update recommended extensions, don\'t run the wizard.') + def mercurial_bootstrap(self, update_only=False): sys.path.append(os.path.dirname(__file__)) - from hgsetup.wizard import MercurialSetupWizard - wizard = MercurialSetupWizard(self._context.state_dir) config_paths = ['~/.hgrc'] if sys.platform in ('win32', 'cygwin'): - config_paths.insert(0, '~/mercurial.ini') - result = wizard.run(map(os.path.expanduser, config_paths)) + config_paths.insert(0, '~/mercurial.ini') + config_paths = map(os.path.expanduser, config_paths) + + if update_only: + from hgsetup.update import MercurialUpdater + updater = MercurialUpdater(self._context.state_dir) + result = updater.update_all(map(os.path.expanduser, config_paths)) + else: + from hgsetup.wizard import MercurialSetupWizard + wizard = MercurialSetupWizard(self._context.state_dir) + result = wizard.run(map(os.path.expanduser, config_paths)) # Touch a file so we can periodically prompt to update extensions. state_path = os.path.join(self._context.state_dir, diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 868596360b3..ee215fe2f0d 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -194,17 +194,6 @@ static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, nsAS return retval; } -// The driver ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD, possibly -// followed by &REV_XXXX. We uppercase the string, and strip the &REV_ part -// from it, if found. -static void normalizeDriverId(nsString& driverid) { - ToUpperCase(driverid); - int32_t rev = driverid.Find(NS_LITERAL_CSTRING("&REV_")); - if (rev != -1) { - driverid.Cut(rev, driverid.Length()); - } -} - // The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD // this function is used to extract the id's out of it uint32_t diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h index 5e28c7b0c93..5f611aa3789 100644 --- a/xpcom/base/nsWindowsHelpers.h +++ b/xpcom/base/nsWindowsHelpers.h @@ -127,7 +127,7 @@ typedef nsAutoRef nsModuleHandle; namespace { -bool +bool inline IsRunningInWindowsMetro() { static bool alreadyChecked = false; @@ -157,7 +157,7 @@ IsRunningInWindowsMetro() return isMetro; } -HMODULE +HMODULE inline LoadLibrarySystem32(LPCWSTR aModule) { WCHAR systemPath[MAX_PATH + 1] = { L'\0' };