From 9191fc726417dfd90a268ec00e48220a08f695e0 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 8 May 2015 10:32:46 +0200 Subject: [PATCH 01/54] Bug 1156205: show a scrollbar in the social share dropdown list inside the Loop conversation window when the number of items exceeds the maximum height. r=Standard8 --- .../loop/content/js/conversation.js | 3 + .../loop/content/js/conversation.jsx | 3 + .../loop/content/js/conversationViews.js | 2 +- .../loop/content/js/conversationViews.jsx | 2 +- browser/components/loop/content/js/panel.js | 5 +- browser/components/loop/content/js/panel.jsx | 5 +- .../components/loop/content/js/roomViews.js | 54 ++-- .../components/loop/content/js/roomViews.jsx | 52 ++-- .../loop/content/shared/css/common.css | 4 +- .../loop/content/shared/css/conversation.css | 12 +- .../loop/content/shared/js/mixins.js | 253 +++++++++++------- .../loop/content/shared/js/utils.js | 20 ++ .../loop/content/shared/js/views.js | 5 +- .../loop/content/shared/js/views.jsx | 5 +- .../loop/standalone/content/js/webapp.js | 2 +- .../loop/standalone/content/js/webapp.jsx | 2 +- .../components/loop/test/shared/utils_test.js | 37 +++ 17 files changed, 299 insertions(+), 167 deletions(-) diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index 048559edc07..54a71b2a1f5 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -161,6 +161,9 @@ loop.conversation = (function(mozL10n) { mozLoop: navigator.mozLoop} ), document.querySelector('#main')); + document.body.setAttribute("dir", mozL10n.getDirection()); + document.body.setAttribute("platform", loop.shared.utils.getPlatform()); + dispatcher.dispatch(new sharedActions.GetWindowData({ windowId: windowId })); diff --git a/browser/components/loop/content/js/conversation.jsx b/browser/components/loop/content/js/conversation.jsx index fcea1b402f5..8b800e6e701 100644 --- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -161,6 +161,9 @@ loop.conversation = (function(mozL10n) { mozLoop={navigator.mozLoop} />, document.querySelector('#main')); + document.body.setAttribute("dir", mozL10n.getDirection()); + document.body.setAttribute("platform", loop.shared.utils.getPlatform()); + dispatcher.dispatch(new sharedActions.GetWindowData({ windowId: windowId })); diff --git a/browser/components/loop/content/js/conversationViews.js b/browser/components/loop/content/js/conversationViews.js index 524f9f67eb2..a20b5dbc04b 100644 --- a/browser/components/loop/content/js/conversationViews.js +++ b/browser/components/loop/content/js/conversationViews.js @@ -140,7 +140,7 @@ loop.conversationViews = (function(mozL10n) { var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/; var AcceptCallView = React.createClass({displayName: "AcceptCallView", - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { callType: React.PropTypes.string.isRequired, diff --git a/browser/components/loop/content/js/conversationViews.jsx b/browser/components/loop/content/js/conversationViews.jsx index e3fe0c015a5..355e27171ba 100644 --- a/browser/components/loop/content/js/conversationViews.jsx +++ b/browser/components/loop/content/js/conversationViews.jsx @@ -140,7 +140,7 @@ loop.conversationViews = (function(mozL10n) { var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/; var AcceptCallView = React.createClass({ - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { callType: React.PropTypes.string.isRequired, diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index edd1d0d478e..1bd38970c78 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -117,7 +117,7 @@ loop.panel = (function(_, mozL10n) { * Availability drop down menu subview. */ var AvailabilityDropdown = React.createClass({displayName: "AvailabilityDropdown", - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], getInitialState: function() { return { @@ -322,7 +322,7 @@ loop.panel = (function(_, mozL10n) { mozLoop: React.PropTypes.object.isRequired }, - mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.WindowCloseMixin], + mixins: [sharedMixins.DropdownMenuMixin(), sharedMixins.WindowCloseMixin], handleClickSettingsEntry: function() { // XXX to be implemented at the same time as unhiding the entry @@ -941,6 +941,7 @@ loop.panel = (function(_, mozL10n) { ), document.querySelector("#main")); document.body.setAttribute("dir", mozL10n.getDirection()); + document.body.setAttribute("platform", loop.shared.utils.getPlatform()); // Notify the window that we've finished initalization and initial layout var evtObject = document.createEvent('Event'); diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index e1cb3e87428..7cb1b6feb26 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -117,7 +117,7 @@ loop.panel = (function(_, mozL10n) { * Availability drop down menu subview. */ var AvailabilityDropdown = React.createClass({ - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], getInitialState: function() { return { @@ -322,7 +322,7 @@ loop.panel = (function(_, mozL10n) { mozLoop: React.PropTypes.object.isRequired }, - mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.WindowCloseMixin], + mixins: [sharedMixins.DropdownMenuMixin(), sharedMixins.WindowCloseMixin], handleClickSettingsEntry: function() { // XXX to be implemented at the same time as unhiding the entry @@ -941,6 +941,7 @@ loop.panel = (function(_, mozL10n) { />, document.querySelector("#main")); document.body.setAttribute("dir", mozL10n.getDirection()); + document.body.setAttribute("platform", loop.shared.utils.getPlatform()); // Notify the window that we've finished initalization and initial layout var evtObject = document.createEvent('Event'); diff --git a/browser/components/loop/content/js/roomViews.js b/browser/components/loop/content/js/roomViews.js index 036f57194e5..c874cd5138e 100644 --- a/browser/components/loop/content/js/roomViews.js +++ b/browser/components/loop/content/js/roomViews.js @@ -113,6 +113,7 @@ loop.roomViews = (function(mozL10n) { var shareDropdown = cx({ "share-service-dropdown": true, "dropdown-menu": true, + "visually-hidden": true, "share-button-unavailable": !this.props.socialShareButtonAvailable, "hide": !this.props.show }); @@ -170,7 +171,7 @@ loop.roomViews = (function(mozL10n) { * Desktop room invitation view (overlay). */ var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView", - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")], propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, @@ -185,7 +186,8 @@ loop.roomViews = (function(mozL10n) { getInitialState: function() { return { copiedUrl: false, - editMode: false + editMode: false, + newRoomName: "" }; }, @@ -239,31 +241,31 @@ loop.roomViews = (function(mozL10n) { React.createElement("a", {className: cx({hide: !canAddContext, "room-invitation-addcontext": true}), onClick: this.handleAddContextClick}, mozL10n.get("context_add_some_label") - ), - React.createElement("div", {className: "btn-group call-action-group"}, - React.createElement("button", {className: "btn btn-info btn-email", - onClick: this.handleEmailButtonClick}, - mozL10n.get("email_link_button") - ), - React.createElement("button", {className: "btn btn-info btn-copy", - onClick: this.handleCopyButtonClick}, - this.state.copiedUrl ? mozL10n.get("copied_url_button") : - mozL10n.get("copy_url_button2") - ), - React.createElement("button", {className: "btn btn-info btn-share", - ref: "anchor", - onClick: this.handleShareButtonClick}, - mozL10n.get("share_button3") - ) - ), - React.createElement(SocialShareDropdown, { - dispatcher: this.props.dispatcher, - roomUrl: this.props.roomData.roomUrl, - show: this.state.showMenu, - socialShareButtonAvailable: this.props.socialShareButtonAvailable, - socialShareProviders: this.props.socialShareProviders, - ref: "menu"}) + ) ), + React.createElement("div", {className: "btn-group call-action-group"}, + React.createElement("button", {className: "btn btn-info btn-email", + onClick: this.handleEmailButtonClick}, + mozL10n.get("email_link_button") + ), + React.createElement("button", {className: "btn btn-info btn-copy", + onClick: this.handleCopyButtonClick}, + this.state.copiedUrl ? mozL10n.get("copied_url_button") : + mozL10n.get("copy_url_button2") + ), + React.createElement("button", {className: "btn btn-info btn-share", + ref: "anchor", + onClick: this.handleShareButtonClick}, + mozL10n.get("share_button3") + ) + ), + React.createElement(SocialShareDropdown, { + dispatcher: this.props.dispatcher, + roomUrl: this.props.roomData.roomUrl, + show: this.state.showMenu, + socialShareButtonAvailable: this.props.socialShareButtonAvailable, + socialShareProviders: this.props.socialShareProviders, + ref: "menu"}), React.createElement(DesktopRoomContextView, { dispatcher: this.props.dispatcher, editMode: this.state.editMode, diff --git a/browser/components/loop/content/js/roomViews.jsx b/browser/components/loop/content/js/roomViews.jsx index 8c532a3870e..5e5f94e575f 100644 --- a/browser/components/loop/content/js/roomViews.jsx +++ b/browser/components/loop/content/js/roomViews.jsx @@ -113,6 +113,7 @@ loop.roomViews = (function(mozL10n) { var shareDropdown = cx({ "share-service-dropdown": true, "dropdown-menu": true, + "visually-hidden": true, "share-button-unavailable": !this.props.socialShareButtonAvailable, "hide": !this.props.show }); @@ -170,7 +171,7 @@ loop.roomViews = (function(mozL10n) { * Desktop room invitation view (overlay). */ var DesktopRoomInvitationView = React.createClass({ - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")], propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, @@ -185,7 +186,8 @@ loop.roomViews = (function(mozL10n) { getInitialState: function() { return { copiedUrl: false, - editMode: false + editMode: false, + newRoomName: "" }; }, @@ -240,30 +242,30 @@ loop.roomViews = (function(mozL10n) { onClick={this.handleAddContextClick}> {mozL10n.get("context_add_some_label")} -
- - - -
- +
+ + + +
+ .dropdown-menu-item, +body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item { + -moz-padding-end: 20px; } .share-service-dropdown.share-button-unavailable { @@ -988,7 +995,6 @@ body[dir=rtl] .share-service-dropdown .share-panel-header { position: relative; left: auto; bottom: auto; - order: 2; flex: 0 1 auto; } diff --git a/browser/components/loop/content/shared/js/mixins.js b/browser/components/loop/content/shared/js/mixins.js index e204b7682fe..83d715b19f2 100644 --- a/browser/components/loop/content/shared/js/mixins.js +++ b/browser/components/loop/content/shared/js/mixins.js @@ -83,110 +83,165 @@ loop.shared.mixins = (function() { /** * Dropdown menu mixin. + * + * @param {Sring} [boundingBoxSelector] Selector that points to an element that + * defines the constraints this dropdown + * is shown within. If not provided, + * `document.body` is assumed to be the + * constraining element. * @type {Object} */ - var DropdownMenuMixin = { - get documentBody() { - return rootObject.document.body; - }, + var DropdownMenuMixin = function(boundingBoxSelector) { + return { + get documentBody() { + return rootObject.document.body; + }, - getInitialState: function() { - return {showMenu: false}; - }, + getInitialState: function() { + return { + showMenu: false + }; + }, - _onBodyClick: function(event) { - var menuButton = this.refs["menu-button"] && this.refs["menu-button"].getDOMNode(); - if (this.refs.anchor) { - menuButton = this.refs.anchor.getDOMNode(); + _onBodyClick: function(event) { + var menuButton = this.refs["menu-button"] && this.refs["menu-button"].getDOMNode(); + if (this.refs.anchor) { + menuButton = this.refs.anchor.getDOMNode(); + } + // If a menu button/ anchor is defined and clicked on, it will be in charge + // of hiding or showing the popup. + if (event.target !== menuButton) { + this.setState({ showMenu: false }); + } + }, + + _correctMenuPosition: function() { + var menu = this.refs.menu && this.refs.menu.getDOMNode(); + if (!menu) { + return; + } + if (menu.style.maxWidth) + menu.style.maxWidth = "none"; + if (menu.style.maxHeight) + menu.style.maxHeight = "none"; + + // Correct the position of the menu only if necessary. + var x, y, boundingBox, boundingRect; + // Amount of pixels that the dropdown needs to stay away from the edges of + // the page body. + var boundOffset = 4; + var menuNodeRect = menu.getBoundingClientRect(); + // If the menu dimensions are constrained to a bounding element, instead of + // the document body, find that element. + if (boundingBoxSelector) { + boundingBox = this.documentBody.querySelector(boundingBoxSelector); + if (boundingBox) { + boundingRect = boundingBox.getBoundingClientRect(); + } + } + if (!boundingRect) { + boundingRect = { + height: this.documentBody.offsetHeight, + left: 0, + top: 0, + width: this.documentBody.offsetWidth + }; + } + // Make sure the menu position will be a certain fixed amount of pixels away + // from the border of the bounding box. + boundingRect.width -= boundOffset; + boundingRect.height -= boundOffset; + + var x = menuNodeRect.left; + var y = menuNodeRect.top; + + // If there's an anchor present, position it relative to it first. + var anchor = this.refs.anchor && this.refs.anchor.getDOMNode(); + if (anchor) { + // XXXmikedeboer: at the moment we only support positioning centered above + // anchor node. Please add more modes as necessary. + var anchorNodeRect = anchor.getBoundingClientRect(); + // Because we're _correcting_ the position of the dropdown, we assume that + // the node is positioned absolute at 0,0 coordinates (top left). + x = Math.floor(anchorNodeRect.left - (menuNodeRect.width / 2) + (anchorNodeRect.width / 2)); + y = Math.floor(anchorNodeRect.top - menuNodeRect.height - anchorNodeRect.height); + } + + var overflowX = false; + var overflowY = false; + // Check the horizontal overflow. + if (x + menuNodeRect.width > boundingRect.width) { + // Anchor positioning is already relative, so don't subtract it again. + x = Math.floor(boundingRect.width - ((anchor ? 0 : x) + menuNodeRect.width)); + overflowX = true; + } + // Check the vertical overflow. + if (y + menuNodeRect.height > boundingRect.height) { + // Anchor positioning is already relative, so don't subtract it again. + y = Math.floor(boundingRect.height - ((anchor ? 0 : y) + menuNodeRect.height)); + overflowY = true; + } + + if (anchor || overflowX) { + // Set the maximum dimensions that the menu DOMNode may grow to. The + // content overflow style should be defined in CSS. + // Since we don't care much about horizontal overflow currently, this + // doesn't really do much for now. + if (menuNodeRect.width > boundingRect.width) { + menu.classList.add("overflow"); + menu.style.maxWidth = boundingRect.width + "px"; + } + menu.style.marginLeft = x + "px"; + } else if (!menu.style.marginLeft) { + menu.style.marginLeft = "auto"; + } + + if (anchor || overflowY) { + if (menuNodeRect.height > (boundingRect.height + y)) { + menu.classList.add("overflow"); + // Set the maximum dimensions that the menu DOMNode may grow to. The + // content overflow style should be defined in CSS. + menu.style.maxHeight = (boundingRect.height + y) + "px"; + // Since we just adjusted the max-height of the menu - thus its actual + // height as well - we need to adjust its vertical offset with the same + // amount. + y += menuNodeRect.height - (boundingRect.height + y); + } + menu.style.marginTop = y + "px"; + } else if (!menu.style.marginLeft) { + menu.style.marginTop = "auto"; + } + + menu.style.visibility = "visible"; + }, + + componentDidMount: function() { + this.documentBody.addEventListener("click", this._onBodyClick); + rootObject.addEventListener("blur", this.hideDropdownMenu); + }, + + componentWillUnmount: function() { + this.documentBody.removeEventListener("click", this._onBodyClick); + rootObject.removeEventListener("blur", this.hideDropdownMenu); + }, + + showDropdownMenu: function() { + this.setState({showMenu: true}, this._correctMenuPosition); + }, + + hideDropdownMenu: function() { + this.setState({showMenu: false}, function() { + var menu = this.refs.menu && this.refs.menu.getDOMNode(); + if (menu) { + menu.style.visibility = "hidden"; + } + }); + }, + + toggleDropdownMenu: function() { + this[this.state.showMenu ? "hideDropdownMenu" : "showDropdownMenu"](); } - // If a menu button/ anchor is defined and clicked on, it will be in charge - // of hiding or showing the popup. - if (event.target !== menuButton) { - this.setState({ showMenu: false }); - } - }, - - _correctMenuPosition: function() { - var menu = this.refs.menu && this.refs.menu.getDOMNode(); - if (!menu) { - return; - } - - // Correct the position of the menu only if necessary. - var x, y; - var menuNodeRect = menu.getBoundingClientRect(); - var x = menuNodeRect.left; - var y = menuNodeRect.top; - // Amount of pixels that the dropdown needs to stay away from the edges of - // the page body. - var bodyMargin = 10; - var bodyRect = { - height: this.documentBody.offsetHeight - bodyMargin, - width: this.documentBody.offsetWidth - bodyMargin - }; - - // If there's an anchor present, position it relative to it first. - var anchor = this.refs.anchor && this.refs.anchor.getDOMNode(); - if (anchor) { - // XXXmikedeboer: at the moment we only support positioning centered above - // anchor node. Please add more modes as necessary. - var anchorNodeRect = anchor.getBoundingClientRect(); - // Because we're _correcting_ the position of the dropdown, we assume that - // the node is positioned absolute at 0,0 coordinates (top left). - x = anchorNodeRect.left - (menuNodeRect.width / 2) + (anchorNodeRect.width / 2); - y = anchorNodeRect.top - menuNodeRect.height - anchorNodeRect.height; - } - - var overflowX = false; - var overflowY = false; - // Check the horizontal overflow. - if (x + menuNodeRect.width > bodyRect.width) { - // Anchor positioning is already relative, so don't subtract it again. - x = bodyRect.width - ((anchor ? 0 : x) + menuNodeRect.width); - overflowX = true; - } - // Check the vertical overflow. - if (y + menuNodeRect.height > bodyRect.height) { - // Anchor positioning is already relative, so don't subtract it again. - y = bodyRect.height - ((anchor ? 0 : y) + menuNodeRect.height); - overflowY = true; - } - - if (anchor || overflowX) { - menu.style.marginLeft = x + "px"; - } else if (!menu.style.marginLeft) { - menu.style.marginLeft = "auto"; - } - - if (anchor || overflowY) { - menu.style.marginTop = y + "px"; - } else if (!menu.style.marginLeft) { - menu.style.marginTop = "auto"; - } - }, - - componentDidMount: function() { - this.documentBody.addEventListener("click", this._onBodyClick); - rootObject.addEventListener("blur", this.hideDropdownMenu); - }, - - componentWillUnmount: function() { - this.documentBody.removeEventListener("click", this._onBodyClick); - rootObject.removeEventListener("blur", this.hideDropdownMenu); - }, - - showDropdownMenu: function() { - this.setState({showMenu: true}); - rootObject.setTimeout(this._correctMenuPosition, 0); - }, - - hideDropdownMenu: function() { - this.setState({showMenu: false}); - }, - - toggleDropdownMenu: function() { - this[this.state.showMenu ? "hideDropdownMenu" : "showDropdownMenu"](); - } + }; }; /** diff --git a/browser/components/loop/content/shared/js/utils.js b/browser/components/loop/content/shared/js/utils.js index 3e2e18c36db..469b43c96ff 100644 --- a/browser/components/loop/content/shared/js/utils.js +++ b/browser/components/loop/content/shared/js/utils.js @@ -260,6 +260,25 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; return { major: Infinity, minor: 0 }; }; + /** + * Helper to get the current short platform string, based on the return value + * of `getOS`. + * Possible return values are 'mac', 'win' or 'other'. + * + * @param {String} [os] Optional string for the OS, used in tests only. + * @return {String} 'mac', 'win' or 'other'. + */ + var getPlatform = function(os) { + os = getOS(os); + var platform = "other"; + if (os.indexOf("mac") > -1) { + platform = "mac"; + } else if (os.indexOf("win") > -1) { + platform = "win"; + } + return platform; + }; + /** * Helper to allow getting some of the location data in a way that's compatible * with stubbing for unit tests. @@ -632,6 +651,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; getBoolPreference: getBoolPreference, getOS: getOS, getOSVersion: getOSVersion, + getPlatform: getPlatform, isChrome: isChrome, isFirefox: isFirefox, isFirefoxOS: isFirefoxOS, diff --git a/browser/components/loop/content/shared/js/views.js b/browser/components/loop/content/shared/js/views.js index 0d66c22de14..c89a143ba4d 100644 --- a/browser/components/loop/content/shared/js/views.js +++ b/browser/components/loop/content/shared/js/views.js @@ -83,7 +83,7 @@ loop.shared.views = (function(_, l10n) { * loop.shared.utils.SCREEN_SHARE_STATES */ var ScreenShareControlButton = React.createClass({displayName: "ScreenShareControlButton", - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, @@ -151,7 +151,8 @@ loop.shared.views = (function(_, l10n) { var dropdownMenuClasses = cx({ "native-dropdown-menu": true, "conversation-window-dropdown": true, - "visually-hidden": !this.state.showMenu + "hide": !this.state.showMenu, + "visually-hidden": true }); var windowSharingClasses = cx({ "disabled": this.state.windowSharingDisabled diff --git a/browser/components/loop/content/shared/js/views.jsx b/browser/components/loop/content/shared/js/views.jsx index 198d84c5fff..6e1fffc972e 100644 --- a/browser/components/loop/content/shared/js/views.jsx +++ b/browser/components/loop/content/shared/js/views.jsx @@ -83,7 +83,7 @@ loop.shared.views = (function(_, l10n) { * loop.shared.utils.SCREEN_SHARE_STATES */ var ScreenShareControlButton = React.createClass({ - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, @@ -151,7 +151,8 @@ loop.shared.views = (function(_, l10n) { var dropdownMenuClasses = cx({ "native-dropdown-menu": true, "conversation-window-dropdown": true, - "visually-hidden": !this.state.showMenu + "hide": !this.state.showMenu, + "visually-hidden": true }); var windowSharingClasses = cx({ "disabled": this.state.windowSharingDisabled diff --git a/browser/components/loop/standalone/content/js/webapp.js b/browser/components/loop/standalone/content/js/webapp.js index 062f9d179dd..8c23fc9fa20 100644 --- a/browser/components/loop/standalone/content/js/webapp.js +++ b/browser/components/loop/standalone/content/js/webapp.js @@ -386,7 +386,7 @@ loop.webapp = (function($, _, OT, mozL10n) { }); var InitiateCallButton = React.createClass({displayName: "InitiateCallButton", - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { caption: React.PropTypes.string.isRequired, diff --git a/browser/components/loop/standalone/content/js/webapp.jsx b/browser/components/loop/standalone/content/js/webapp.jsx index 2e05a654f3c..9a2c206a09e 100644 --- a/browser/components/loop/standalone/content/js/webapp.jsx +++ b/browser/components/loop/standalone/content/js/webapp.jsx @@ -386,7 +386,7 @@ loop.webapp = (function($, _, OT, mozL10n) { }); var InitiateCallButton = React.createClass({ - mixins: [sharedMixins.DropdownMenuMixin], + mixins: [sharedMixins.DropdownMenuMixin()], propTypes: { caption: React.PropTypes.string.isRequired, diff --git a/browser/components/loop/test/shared/utils_test.js b/browser/components/loop/test/shared/utils_test.js index 466878e54c4..23d4a632139 100644 --- a/browser/components/loop/test/shared/utils_test.js +++ b/browser/components/loop/test/shared/utils_test.js @@ -319,6 +319,43 @@ describe("loop.shared.utils", function() { }); }); + describe("#getPlatform", function() { + it("should recognize the OSX userAgent string", function() { + var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0"; + var result = sharedUtils.getPlatform(UA); + + expect(result).eql("mac"); + }); + + it("should recognize the Windows userAgent string", function() { + var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0"; + var result = sharedUtils.getPlatform(UA); + + expect(result).eql("win"); + }); + + it("should recognize the Linux userAgent string", function() { + var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0"; + var result = sharedUtils.getPlatform(UA); + + expect(result).eql("other"); + }); + + it("should recognize the OSX oscpu string", function() { + var oscpu = "Intel Mac OS X 10.10"; + var result = sharedUtils.getPlatform(oscpu); + + expect(result).eql("mac"); + }); + + it("should recognize the Windows oscpu string", function() { + var oscpu = "Windows NT 5.3; Win64; x64"; + var result = sharedUtils.getPlatform(oscpu); + + expect(result).eql("win"); + }); + }); + describe("#objectDiff", function() { var a, b, diff; From 7740e246cfb0fbedb78e7843215977ca9ac50034 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Thu, 7 May 2015 20:27:52 +0200 Subject: [PATCH 02/54] Bug 961215 - Fix intermittent browser_tabview_bug625269.js failures by taking into account that window.resizeTo() can fail to change the window size sometimes r=MattN --- .../tabview/test/browser_tabview_bug625269.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/browser/components/tabview/test/browser_tabview_bug625269.js b/browser/components/tabview/test/browser_tabview_bug625269.js index 08a0636bbd8..47cc3c016df 100644 --- a/browser/components/tabview/test/browser_tabview_bug625269.js +++ b/browser/components/tabview/test/browser_tabview_bug625269.js @@ -66,11 +66,16 @@ function resizeWindow(win, diffX, diffY, callback) { (function tryResize() { let {outerWidth: width, outerHeight: height} = win; - if (width != targetWidth || height != targetHeight) { - win.resizeTo(targetWidth, targetHeight); - executeSoon(tryResize); - } else { - callback(); + if (width == targetWidth && height == targetHeight) { + executeSoon(callback); + return; } + + win.addEventListener("resize", function onResize() { + win.removeEventListener("resize", onResize); + executeSoon(tryResize); + }); + + win.resizeTo(targetWidth, targetHeight); })(); } From 469aaaa745f61585db1f8138932446f558581429 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 8 May 2015 13:46:52 +0100 Subject: [PATCH 03/54] Bug 1162495 - When using FxAccountsOAuthClient, unable to specify email parameter to go with action="force_auth". r=MattN --- services/fxaccounts/FxAccountsOAuthClient.jsm | 13 +++++++++++-- .../fxaccounts/tests/xpcshell/test_oauth_client.js | 9 +++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/services/fxaccounts/FxAccountsOAuthClient.jsm b/services/fxaccounts/FxAccountsOAuthClient.jsm index e91f796bc02..097d6eddd0a 100644 --- a/services/fxaccounts/FxAccountsOAuthClient.jsm +++ b/services/fxaccounts/FxAccountsOAuthClient.jsm @@ -36,7 +36,9 @@ Cu.importGlobalProperties(["URL"]); * @param {String} [options.parameters.scope] * Optional. A colon-separated list of scopes that the user has authorized * @param {String} [options.parameters.action] - * Optional. If provided, should be either signup or signin. + * Optional. If provided, should be either signup, signin or force_auth. + * @param {String} [options.parameters.email] + * Optional. Required if options.paramters.action is 'force_auth'. * @param {Boolean} [options.parameters.keys] * Optional. If true then relier-specific encryption keys will be * available in the second argument to onComplete. @@ -66,7 +68,10 @@ this.FxAccountsOAuthClient = function(options) { if (this.parameters.keys) { params.append("keys", "true"); } - + // Only append if we actually have a value. + if (this.parameters.email) { + params.append("email", this.parameters.email); + } }; this.FxAccountsOAuthClient.prototype = { @@ -256,5 +261,9 @@ this.FxAccountsOAuthClient.prototype = { throw new Error("Missing 'parameters." + option + "' parameter"); } }); + + if (options.parameters.action == "force_auth" && !options.parameters.email) { + throw new Error("parameters.email is required for action 'force_auth'"); + } }, }; diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_client.js index 74c7af28bb6..9bcb1b1ab18 100644 --- a/services/fxaccounts/tests/xpcshell/test_oauth_client.js +++ b/services/fxaccounts/tests/xpcshell/test_oauth_client.js @@ -33,6 +33,15 @@ function run_test() { }}, "Error: Missing 'parameters.state' parameter"); + validationHelper({ parameters: { + oauth_uri: "http://oauth.test/v1", + client_id: "client_id", + content_uri: "http://content.test", + state: "complete", + action: "force_auth" + }}, + "Error: parameters.email is required for action 'force_auth'"); + run_next_test(); } From e21617c5922db357c97fdb79a2f6f4f2bb0df423 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 8 May 2015 13:46:52 +0100 Subject: [PATCH 04/54] Bug 1153788 - Part 2. Ask the user to re-sign in to Loop if they don't have encryption keys for FxA. r=mikedeboer --- browser/components/loop/content/css/panel.css | 35 +++++++++++ browser/components/loop/content/js/panel.js | 59 +++++++++++++++++-- browser/components/loop/content/js/panel.jsx | 59 +++++++++++++++++-- .../components/loop/modules/MozLoopAPI.jsm | 25 +++++++- .../loop/modules/MozLoopService.jsm | 31 ++++++++-- .../loop/test/desktop-local/panel_test.js | 47 +++++++++++++++ .../test_loopservice_encryptionkey.js | 24 ++++++++ browser/components/loop/ui/fake-mozLoop.js | 1 + browser/components/loop/ui/ui-showcase.js | 4 ++ browser/components/loop/ui/ui-showcase.jsx | 4 ++ .../uitour/test/browser_UITour_loop.js | 2 + .../en-US/chrome/browser/loop/loop.properties | 9 +++ 12 files changed, 284 insertions(+), 16 deletions(-) diff --git a/browser/components/loop/content/css/panel.css b/browser/components/loop/content/css/panel.css index 184ea08c10e..4af9f3ae3fd 100644 --- a/browser/components/loop/content/css/panel.css +++ b/browser/components/loop/content/css/panel.css @@ -23,6 +23,41 @@ body { margin: 0; } +/* Sign-in request view */ + +.sign-in-request { + text-align: center; + vertical-align: middle; + margin: 2em 0; +} + +.sign-in-request > h1 { + font-size: 1.7em; + margin-bottom: .2em; +} + +.sign-in-request > h2, +.sign-in-request > a { + font-size: 1.2em; +} + +.sign-in-request > a { + cursor: pointer; + color: #0295df; +} + +.sign-in-request > a:hover:active { + text-decoration: underline; +} + +.sign-in-request-button { + font-size: 1rem; + margin: 1rem; + width: 80%; + padding: .5rem 1rem; + border-radius: 3px; +} + /* Tabs and tab selection buttons */ .tab-view-container { diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index 1bd38970c78..982bf9b9eb3 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -214,6 +214,45 @@ loop.panel = (function(_, mozL10n) { } }); + /** + * Displays a view requesting the user to sign-in again. + */ + var SignInRequestView = React.createClass({displayName: "SignInRequestView", + mixins: [sharedMixins.WindowCloseMixin], + + propTypes: { + mozLoop: React.PropTypes.object.isRequired + }, + + handleSignInClick: function(event) { + event.preventDefault(); + this.props.mozLoop.logInToFxA(true); + this.closeWindow(); + }, + + handleGuestClick: function(event) { + this.props.mozLoop.logOutFromFxA(); + }, + + render: function() { + return ( + React.createElement("div", {className: "sign-in-request"}, + React.createElement("h1", null, mozL10n.get("sign_in_again_title_line_one")), + React.createElement("h2", null, mozL10n.get("sign_in_again_title_line_two")), + React.createElement("div", null, + React.createElement("button", {className: "btn btn-info sign-in-request-button", + onClick: this.handleSignInClick}, + mozL10n.get("sign_in_again_button") + ) + ), + React.createElement("a", {onClick: this.handleGuestClick}, + mozL10n.get("sign_in_again_use_as_guest_button") + ) + ) + ); + } + }); + var ToSView = React.createClass({displayName: "ToSView", mixins: [sharedMixins.WindowCloseMixin], @@ -758,6 +797,7 @@ loop.panel = (function(_, mozL10n) { getInitialState: function() { return { + hasEncryptionKey: this.props.mozLoop.hasEncryptionKey, userProfile: this.props.userProfile || this.props.mozLoop.userProfile, gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen") }; @@ -796,7 +836,10 @@ loop.panel = (function(_, mozL10n) { var profile = this.props.mozLoop.userProfile; var currUid = this.state.userProfile ? this.state.userProfile.uid : null; var newUid = profile ? profile.uid : null; - if (currUid != newUid) { + if (currUid == newUid) { + // Update the state of hasEncryptionKey as this might have changed now. + this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey}); + } else { // On profile change (login, logout), switch back to the default tab. this.selectTab("rooms"); this.setState({userProfile: profile}); @@ -827,7 +870,11 @@ loop.panel = (function(_, mozL10n) { }, selectTab: function(name) { - this.refs.tabView.setState({ selectedTab: name }); + // The tab view might not be created yet (e.g. getting started or fxa + // re-sign in. + if (this.refs.tabView) { + this.refs.tabView.setState({ selectedTab: name }); + } }, componentWillMount: function() { @@ -865,6 +912,10 @@ loop.panel = (function(_, mozL10n) { ); } + if (!this.state.hasEncryptionKey) { + return React.createElement(SignInRequestView, {mozLoop: this.props.mozLoop}); + } + // Determine which buttons to NOT show. var hideButtons = []; if (!this.state.userProfile && !this.props.showTabButtons) { @@ -937,8 +988,7 @@ loop.panel = (function(_, mozL10n) { notifications: notifications, roomStore: roomStore, mozLoop: navigator.mozLoop, - dispatcher: dispatcher} - ), document.querySelector("#main")); + dispatcher: dispatcher}), document.querySelector("#main")); document.body.setAttribute("dir", mozL10n.getDirection()); document.body.setAttribute("platform", loop.shared.utils.getPlatform()); @@ -959,6 +1009,7 @@ loop.panel = (function(_, mozL10n) { RoomEntry: RoomEntry, RoomList: RoomList, SettingsDropdown: SettingsDropdown, + SignInRequestView: SignInRequestView, ToSView: ToSView, UserIdentity: UserIdentity }; diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index 7cb1b6feb26..2a97269fd74 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -214,6 +214,45 @@ loop.panel = (function(_, mozL10n) { } }); + /** + * Displays a view requesting the user to sign-in again. + */ + var SignInRequestView = React.createClass({ + mixins: [sharedMixins.WindowCloseMixin], + + propTypes: { + mozLoop: React.PropTypes.object.isRequired + }, + + handleSignInClick: function(event) { + event.preventDefault(); + this.props.mozLoop.logInToFxA(true); + this.closeWindow(); + }, + + handleGuestClick: function(event) { + this.props.mozLoop.logOutFromFxA(); + }, + + render: function() { + return ( +
+

{mozL10n.get("sign_in_again_title_line_one")}

+

{mozL10n.get("sign_in_again_title_line_two")}

+
+ +
+ + {mozL10n.get("sign_in_again_use_as_guest_button")} + +
+ ); + } + }); + var ToSView = React.createClass({ mixins: [sharedMixins.WindowCloseMixin], @@ -758,6 +797,7 @@ loop.panel = (function(_, mozL10n) { getInitialState: function() { return { + hasEncryptionKey: this.props.mozLoop.hasEncryptionKey, userProfile: this.props.userProfile || this.props.mozLoop.userProfile, gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen") }; @@ -796,7 +836,10 @@ loop.panel = (function(_, mozL10n) { var profile = this.props.mozLoop.userProfile; var currUid = this.state.userProfile ? this.state.userProfile.uid : null; var newUid = profile ? profile.uid : null; - if (currUid != newUid) { + if (currUid == newUid) { + // Update the state of hasEncryptionKey as this might have changed now. + this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey}); + } else { // On profile change (login, logout), switch back to the default tab. this.selectTab("rooms"); this.setState({userProfile: profile}); @@ -827,7 +870,11 @@ loop.panel = (function(_, mozL10n) { }, selectTab: function(name) { - this.refs.tabView.setState({ selectedTab: name }); + // The tab view might not be created yet (e.g. getting started or fxa + // re-sign in. + if (this.refs.tabView) { + this.refs.tabView.setState({ selectedTab: name }); + } }, componentWillMount: function() { @@ -865,6 +912,10 @@ loop.panel = (function(_, mozL10n) { ); } + if (!this.state.hasEncryptionKey) { + return ; + } + // Determine which buttons to NOT show. var hideButtons = []; if (!this.state.userProfile && !this.props.showTabButtons) { @@ -937,8 +988,7 @@ loop.panel = (function(_, mozL10n) { notifications={notifications} roomStore={roomStore} mozLoop={navigator.mozLoop} - dispatcher={dispatcher} - />, document.querySelector("#main")); + dispatcher={dispatcher} />, document.querySelector("#main")); document.body.setAttribute("dir", mozL10n.getDirection()); document.body.setAttribute("platform", loop.shared.utils.getPlatform()); @@ -959,6 +1009,7 @@ loop.panel = (function(_, mozL10n) { RoomEntry: RoomEntry, RoomList: RoomList, SettingsDropdown: SettingsDropdown, + SignInRequestView: SignInRequestView, ToSView: ToSView, UserIdentity: UserIdentity }; diff --git a/browser/components/loop/modules/MozLoopAPI.jsm b/browser/components/loop/modules/MozLoopAPI.jsm index e0c7b5ef773..fa2ab7daaf6 100644 --- a/browser/components/loop/modules/MozLoopAPI.jsm +++ b/browser/components/loop/modules/MozLoopAPI.jsm @@ -669,11 +669,20 @@ function injectLoopAPI(targetWindow) { }, }, + /** + * Start the FxA login flow using the OAuth client and params from the Loop + * server. + * + * @param {Boolean} forceReAuth Set to true to force FxA into a re-auth even + * if the user is already logged in. + * @return {Promise} Returns a promise that is resolved on successful + * completion, or rejected otherwise. + */ logInToFxA: { enumerable: true, writable: true, - value: function() { - return MozLoopService.logInToFxA(); + value: function(forceReAuth) { + return MozLoopService.logInToFxA(forceReAuth); } }, @@ -693,6 +702,18 @@ function injectLoopAPI(targetWindow) { }, }, + /** + * Returns true if this profile has an encryption key. + * + * @return {Boolean} True if the profile has an encryption key. + */ + hasEncryptionKey: { + enumerable: true, + get: function() { + return MozLoopService.hasEncryptionKey; + } + }, + /** * Opens the Getting Started tour in the browser. * diff --git a/browser/components/loop/modules/MozLoopService.jsm b/browser/components/loop/modules/MozLoopService.jsm index dfef7dc6b5d..cb362724fd2 100644 --- a/browser/components/loop/modules/MozLoopService.jsm +++ b/browser/components/loop/modules/MozLoopService.jsm @@ -948,9 +948,10 @@ let MozLoopServiceInternal = { /** * Get the OAuth client constructed with Loop OAauth parameters. * + * @param {Boolean} forceReAuth Set to true to force the user to reauthenticate. * @return {Promise} */ - promiseFxAOAuthClient: Task.async(function* () { + promiseFxAOAuthClient: Task.async(function* (forceReAuth) { // We must make sure to have only a single client otherwise they will have different states and // multiple channels. This would happen if the user clicks the Login button more than once. if (gFxAOAuthClientPromise) { @@ -961,6 +962,10 @@ let MozLoopServiceInternal = { parameters => { // Add the fact that we want keys to the parameters. parameters.keys = true; + if (forceReAuth) { + parameters.action = "force_auth"; + parameters.email = MozLoopService.userProfile.email; + } try { gFxAOAuthClient = new FxAccountsOAuthClient({ @@ -984,11 +989,12 @@ let MozLoopServiceInternal = { /** * Get the OAuth client and do the authorization web flow to get an OAuth code. * + * @param {Boolean} forceReAuth Set to true to force the user to reauthenticate. * @return {Promise} */ - promiseFxAOAuthAuthorization: function() { + promiseFxAOAuthAuthorization: function(forceReAuth) { let deferred = Promise.defer(); - this.promiseFxAOAuthClient().then( + this.promiseFxAOAuthClient(forceReAuth).then( client => { client.onComplete = this._fxAOAuthComplete.bind(this, deferred); client.onError = this._fxAOAuthError.bind(this, deferred); @@ -1366,6 +1372,18 @@ this.MozLoopService = { }); }, + /** + * Returns true if this profile has an encryption key. For guest profiles + * this is always true, since we can generate a new one if needed. For FxA + * profiles, we need to check the preference. + * + * @return {Boolean} True if the profile has an encryption key. + */ + get hasEncryptionKey() { + return !this.userProfile || + Services.prefs.prefHasUserValue("loop.key.fxa"); + }, + get errors() { return MozLoopServiceInternal.errors; }, @@ -1468,14 +1486,15 @@ this.MozLoopService = { * * The caller should be prepared to handle rejections related to network, server or login errors. * + * @param {Boolean} forceReAuth Set to true to force the user to reauthenticate. * @return {Promise} that resolves when the FxA login flow is complete. */ - logInToFxA: function() { + logInToFxA: function(forceReAuth) { log.debug("logInToFxA with fxAOAuthTokenData:", !!MozLoopServiceInternal.fxAOAuthTokenData); - if (MozLoopServiceInternal.fxAOAuthTokenData) { + if (!forceReAuth && MozLoopServiceInternal.fxAOAuthTokenData) { return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData); } - return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => { + return MozLoopServiceInternal.promiseFxAOAuthAuthorization(forceReAuth).then(response => { return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state); }).then(tokenData => { MozLoopServiceInternal.fxAOAuthTokenData = tokenData; diff --git a/browser/components/loop/test/desktop-local/panel_test.js b/browser/components/loop/test/desktop-local/panel_test.js index b8385b62b3c..bc61a5858ca 100644 --- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -70,6 +70,9 @@ describe("loop.panel", function() { on: sandbox.stub() }, confirm: sandbox.stub(), + hasEncryptionKey: true, + logInToFxA: sandbox.stub(), + logOutFromFxA: sandbox.stub(), notifyUITour: sandbox.stub(), openURL: sandbox.stub(), getSelectedTabMetadata: sandbox.stub() @@ -453,6 +456,22 @@ describe("loop.panel", function() { } catch (ex) {} }); + it("should render a SignInRequestView when mozLoop.hasEncryptionKey is false", function() { + fakeMozLoop.hasEncryptionKey = false; + + var view = createTestPanelView(); + + TestUtils.findRenderedComponentWithType(view, loop.panel.SignInRequestView); + }); + + it("should render a SignInRequestView when mozLoop.hasEncryptionKey is true", function() { + var view = createTestPanelView(); + + try { + TestUtils.findRenderedComponentWithType(view, loop.panel.SignInRequestView); + sinon.assert.fail("Should not find the GettingStartedView if it has been seen"); + } catch (ex) {} + }); }); }); @@ -930,4 +949,32 @@ describe("loop.panel", function() { }); }); + + describe("loop.panel.SignInRequestView", function() { + var view; + + function mountTestComponent() { + return TestUtils.renderIntoDocument( + React.createElement(loop.panel.SignInRequestView, { + mozLoop: fakeMozLoop + })); + } + + it("should call login with forced re-authentication when sign-in is clicked", function() { + view = mountTestComponent(); + + TestUtils.Simulate.click(view.getDOMNode().querySelector("button")); + + sinon.assert.calledOnce(fakeMozLoop.logInToFxA); + sinon.assert.calledWithExactly(fakeMozLoop.logInToFxA, true); + }); + + it("should logout when use as guest is clicked", function() { + view = mountTestComponent(); + + TestUtils.Simulate.click(view.getDOMNode().querySelector("a")); + + sinon.assert.calledOnce(fakeMozLoop.logOutFromFxA); + }); + }); }); diff --git a/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js b/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js index 20edf68c5b8..f3f4f00af10 100644 --- a/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js +++ b/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js @@ -9,6 +9,7 @@ const kFxAKeyPref = "loop.key.fxa"; do_register_cleanup(function() { Services.prefs.clearUserPref(kGuestKeyPref); + Services.prefs.clearUserPref(kFxAKeyPref); MozLoopServiceInternal.fxAOAuthTokenData = null; MozLoopServiceInternal.fxAOAuthProfile = null; }); @@ -58,3 +59,26 @@ add_task(function* test_fxaGetKey() { yield Assert.rejects(MozLoopService.promiseProfileEncryptionKey(), /not implemented/, "should reject as unimplemented"); }); + +add_task(function test_hasEncryptionKey() { + MozLoopServiceInternal.fxAOAuthTokenData = null; + MozLoopServiceInternal.fxAOAuthProfile = null; + + Services.prefs.clearUserPref(kGuestKeyPref); + Services.prefs.clearUserPref(kFxAKeyPref); + + Assert.ok(MozLoopService.hasEncryptionKey, "should return true in guest mode without a key"); + + Services.prefs.setCharPref(kGuestKeyPref, "123456"); + + Assert.ok(MozLoopService.hasEncryptionKey, "should return true in guest mode with a key"); + + MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" }; + MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" }; + + Assert.ok(!MozLoopService.hasEncryptionKey, "should return false in fxa mode without a key"); + + Services.prefs.setCharPref(kFxAKeyPref, "12345678"); + + Assert.ok(MozLoopService.hasEncryptionKey, "should return true in fxa mode with a key"); +}); diff --git a/browser/components/loop/ui/fake-mozLoop.js b/browser/components/loop/ui/fake-mozLoop.js index 7d2205f4755..2ab28954a5c 100644 --- a/browser/components/loop/ui/fake-mozLoop.js +++ b/browser/components/loop/ui/fake-mozLoop.js @@ -132,6 +132,7 @@ navigator.mozLoop = { return false; } }, + hasEncryptionKey: true, setLoopPref: function(){}, releaseCallData: function() {}, copyString: function() {}, diff --git a/browser/components/loop/ui/ui-showcase.js b/browser/components/loop/ui/ui-showcase.js index 9ca389f90d3..ae0e313ca74 100644 --- a/browser/components/loop/ui/ui-showcase.js +++ b/browser/components/loop/ui/ui-showcase.js @@ -17,6 +17,7 @@ // 1. Desktop components // 1.1 Panel var PanelView = loop.panel.PanelView; + var SignInRequestView = loop.panel.SignInRequestView; // 1.2. Conversation Window var AcceptCallView = loop.conversationViews.AcceptCallView; var DesktopPendingConversationView = loop.conversationViews.PendingConversationView; @@ -265,6 +266,9 @@ React.createElement("p", {className: "note"}, React.createElement("strong", null, "Note:"), " 332px wide." ), + React.createElement(Example, {summary: "Re-sign-in view", dashed: "true", style: {width: "332px"}}, + React.createElement(SignInRequestView, {mozLoop: mockMozLoopRooms}) + ), React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}}, React.createElement(PanelView, {client: mockClient, notifications: notifications, userProfile: {email: "test@example.com"}, diff --git a/browser/components/loop/ui/ui-showcase.jsx b/browser/components/loop/ui/ui-showcase.jsx index 38f373d8209..8c2da2fc948 100644 --- a/browser/components/loop/ui/ui-showcase.jsx +++ b/browser/components/loop/ui/ui-showcase.jsx @@ -17,6 +17,7 @@ // 1. Desktop components // 1.1 Panel var PanelView = loop.panel.PanelView; + var SignInRequestView = loop.panel.SignInRequestView; // 1.2. Conversation Window var AcceptCallView = loop.conversationViews.AcceptCallView; var DesktopPendingConversationView = loop.conversationViews.PendingConversationView; @@ -265,6 +266,9 @@

Note: 332px wide.

+ + + Date: Fri, 8 May 2015 13:46:53 +0100 Subject: [PATCH 05/54] Bug 1162570 - Enable context in conversations for Loop. r=mikedeboer --- browser/app/profile/firefox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index b3e2f916e3a..3f399cd9d45 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1719,7 +1719,7 @@ pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-fir pref("loop.contacts.gravatars.show", false); pref("loop.contacts.gravatars.promo", true); pref("loop.browserSharing.showInfoBar", true); -pref("loop.contextInConversations.enabled", false); +pref("loop.contextInConversations.enabled", true); pref("social.sidebar.unload_timeout_ms", 10000); From 254ccacd8b35fa0f8a2829015f8512cf338c60e0 Mon Sep 17 00:00:00 2001 From: Will Bamberg Date: Wed, 6 May 2015 20:59:00 +0200 Subject: [PATCH 06/54] Bug 1159109 - The MDN tooltip should be controlled by a pref. r=pbrosset --- browser/app/profile/firefox.js | 2 + browser/devtools/styleinspector/rule-view.js | 11 +- .../devtools/styleinspector/test/browser.ini | 1 + ..._ruleview_context-menu-show-mdn-docs-03.js | 116 ++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 browser/devtools/styleinspector/test/browser_ruleview_context-menu-show-mdn-docs-03.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 3f399cd9d45..72838760684 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1396,6 +1396,8 @@ pref("devtools.inspector.imagePreviewTooltipSize", 300); pref("devtools.inspector.showUserAgentStyles", false); // Show all native anonymous content (like controls in