Bug 1168848 - Make TextChat style correctly when focussed. r=Standard8
authorDan Mosedale <dmose@meer.net>
Sat, 20 Jun 2015 17:32:52 +0100
changeset 267928 1fab1b2a759bcd2556b9215f0ea806cd169590d0
parent 267927 0879159622522cf3127b1908610654e56428fbd9
child 267929 6a653debc815a97008251d87e88063109305f965
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1168848
milestone41.0a1
Bug 1168848 - Make TextChat style correctly when focussed. r=Standard8 Now with a crazy long URL in the text to exercise our line-breaking!
browser/components/loop/content/shared/css/conversation.css
browser/components/loop/content/shared/js/textChatView.js
browser/components/loop/content/shared/js/textChatView.jsx
browser/components/loop/ui/react-frame-component.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1150,18 +1150,19 @@ html[dir="rtl"] .room-context-btn-edit {
      https://code.google.com/p/chromium/issues/detail?id=400829 */
   overflow: hidden;
 }
 
 .media-wrapper > .remote > .remote-video {
   object-fit: cover;
 }
 
-/* Note: we can't use flex for the text-chat-view as this lets it overflow
-   the expected column heights, and we ca't fix its height. */
+/* Note: we can't use "display: flex;" for the text-chat-view itself,
+   as this lets it overflow the expected column heights, and we can't
+   fix its height. */
 .media-wrapper > .text-chat-view {
   flex: 0 0 auto;
   /* Text chat is a fixed 200px width for normal displays. */
   width: 200px;
   height: 100%;
 }
 
 .media-wrapper.showing-local-streams > .text-chat-view {
@@ -1457,17 +1458,17 @@ html[dir="rtl"] .standalone .room-conver
   flex-flow: column nowrap;
 }
 
 .fx-embedded .video-layout-wrapper {
   flex: 1 1 auto;
 }
 
 .text-chat-view {
-  background: #fff;
+  background: white;
 }
 
 .fx-embedded .text-chat-view {
   flex: 1 0 auto;
   display: flex;
   flex-flow: column nowrap;
 }
 
@@ -1531,21 +1532,23 @@ html[dir="rtl"] .standalone .room-conver
   margin: auto;
 }
 
 .text-chat-box > form > input {
   width: 100%;
   height: 40px;
   padding: 0 .5em .5em;
   font-size: 1.1em;
+  border: 0;
+  border-top: 1px solid #999;
 }
 
-.fx-embedded .text-chat-box > form > input {
-  border: 0;
-  border-top: 1px solid #999;
+/* turn the visible border blue as a visual indicator of focus */
+.text-chat-box > form > input:focus {
+  border-top: 1px solid #66c9f2;
 }
 
 @media screen and (max-width:640px) {
   .standalone-context-url {
     /* XXX We haven't got UX for standalone yet, so temporarily not displaying
        on narrow window widths. See bug 1153827. */
     display: none;
   }
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -76,18 +76,22 @@ loop.shared.views.TextChatView = (functi
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
-          var node = this.getDOMNode();
-          node.scrollTop = node.scrollHeight - node.clientHeight;
+          try {
+            var node = this.getDOMNode();
+            node.scrollTop = node.scrollHeight - node.clientHeight;
+          } catch (ex) {
+            console.error("TextChatEntriesView.componentDidUpdate exception", ex);
+          }
         }.bind(this));
       }
     },
 
     render: function() {
       if (!this.props.messageList.length) {
         return null;
       }
@@ -95,25 +99,24 @@ loop.shared.views.TextChatView = (functi
       return (
         React.createElement("div", {className: "text-chat-entries"}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
                   switch (entry.contentType) {
                     case CHAT_CONTENT_TYPES.ROOM_NAME:
-                      return React.createElement(TextChatRoomName, {message: entry.message});
+                      return React.createElement(TextChatRoomName, {key: i, message: entry.message});
                     case CHAT_CONTENT_TYPES.CONTEXT:
                       return (
-                        React.createElement("div", {className: "context-url-view-wrapper"}, 
+                        React.createElement("div", {key: i, className: "context-url-view-wrapper"}, 
                           React.createElement(sharedViews.ContextUrlView, {
                             allowClick: true, 
                             description: entry.message, 
                             dispatcher: this.props.dispatcher, 
-                            key: i, 
                             showContextTitle: true, 
                             thumbnail: entry.extraData.thumbnail, 
                             url: entry.extraData.location, 
                             useDesktopPaths: false})
                         )
                       );
                     default:
                       console.error("Unsupported contentType", entry.contentType);
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -76,18 +76,22 @@ loop.shared.views.TextChatView = (functi
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
-          var node = this.getDOMNode();
-          node.scrollTop = node.scrollHeight - node.clientHeight;
+          try {
+            var node = this.getDOMNode();
+            node.scrollTop = node.scrollHeight - node.clientHeight;
+          } catch (ex) {
+            console.error("TextChatEntriesView.componentDidUpdate exception", ex);
+          }
         }.bind(this));
       }
     },
 
     render: function() {
       if (!this.props.messageList.length) {
         return null;
       }
@@ -95,25 +99,24 @@ loop.shared.views.TextChatView = (functi
       return (
         <div className="text-chat-entries">
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
                   switch (entry.contentType) {
                     case CHAT_CONTENT_TYPES.ROOM_NAME:
-                      return <TextChatRoomName message={entry.message}/>;
+                      return <TextChatRoomName key={i} message={entry.message}/>;
                     case CHAT_CONTENT_TYPES.CONTEXT:
                       return (
-                        <div className="context-url-view-wrapper">
+                        <div key={i} className="context-url-view-wrapper">
                           <sharedViews.ContextUrlView
                             allowClick={true}
                             description={entry.message}
                             dispatcher={this.props.dispatcher}
-                            key={i}
                             showContextTitle={true}
                             thumbnail={entry.extraData.thumbnail}
                             url={entry.extraData.location}
                             useDesktopPaths={false} />
                         </div>
                       );
                     default:
                       console.error("Unsupported contentType", entry.contentType);
--- a/browser/components/loop/ui/react-frame-component.js
+++ b/browser/components/loop/ui/react-frame-component.js
@@ -64,19 +64,23 @@ window.Frame = React.createClass({
       var iframeHead = childDoc.querySelector("head");
       var parentHeadChildren = document.querySelector("head").children;
 
       [].forEach.call(parentHeadChildren, function(parentHeadNode) {
 
         // if this node is a CSS stylesheet...
         if (isStyleSheet(parentHeadNode)) {
           // and it has a class different from the one that this frame does,
-          // return immediately instead of appending it.
-          if (parentHeadNode.hasAttribute("class") && this.props.cssClass &&
-            parentHeadNode.getAttribute("class") !== this.props.cssClass) {
+          // return immediately instead of appending it.  Note that this
+          // explicitly does not check for cssClass existence, because
+          // non-existence of cssClass will be different from a style
+          // element that does have a class on it, and we want it to return
+          // in that case.
+          if (parentHeadNode.hasAttribute("class") &&
+              parentHeadNode.getAttribute("class") !== this.props.cssClass) {
             return;
           }
         }
 
         iframeHead.appendChild(parentHeadNode.cloneNode(true));
       }.bind(this));
 
       var contents = React.createElement("div",
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -288,33 +288,53 @@
     client: {},
     mozLoop: navigator.mozLoop,
     sdkDriver: mockSDK
   });
   var textChatStore = new loop.store.TextChatStore(dispatcher, {
     sdkDriver: mockSDK
   });
 
-  textChatStore.setStoreState({
-    // XXX Disabled until we start sorting out some of the layouts.
-    textChatEnabled: false
-  });
-
   // Update the text chat store with the room info.
   textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
     roomName: "A Very Long Conversation Name",
     roomOwner: "fake",
     roomUrl: "http://showcase",
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
       // use the fallback thumbnail
     }]
   }));
 
+  textChatStore.setStoreState({textChatEnabled: true});
+
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Rheet!"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Hi there"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Check out this menu from DNA Pizza:" +
+    " http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
+    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "That avocado monkey-brains pie sounds tasty!"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "What time should we meet?"
+  }));
+
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
 
   // Local mocks
@@ -816,28 +836,28 @@
               React.createElement("div", {className: "standalone"}, 
                 React.createElement(UnsupportedDeviceView, {platform: "ios"})
               )
             )
           ), 
 
           React.createElement(Section, {name: "DesktopRoomConversationView"}, 
             React.createElement(FramedExample, {width: 298, height: 254, 
-              summary: "Desktop room conversation (invitation)"}, 
+              summary: "Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   roomStore: invitationRoomStore, 
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   roomState: ROOM_STATES.INIT})
               )
             ), 
 
-            React.createElement(FramedExample, {width: 298, height: 254, 
+            React.createElement(FramedExample, {width: 298, height: 394, dashed: true, 
               summary: "Desktop room conversation (loading)"}, 
               /* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */
               React.createElement("div", {className: "fx-embedded overflow-hidden"}, 
                 React.createElement(DesktopRoomConversationView, {
                   roomStore: desktopRoomStoreLoading, 
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
@@ -855,28 +875,28 @@
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS})
               )
             ), 
 
-            React.createElement(FramedExample, {width: 298, height: 254, 
+            React.createElement(FramedExample, {width: 298, height: 394, dashed: true, 
                            summary: "Desktop room conversation local face-mute"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   roomStore: desktopLocalFaceMuteRoomStore, 
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   remotePosterUrl: "sample-img/video-screen-remote.png"})
               )
             ), 
 
-            React.createElement(FramedExample, {width: 298, height: 254, 
+            React.createElement(FramedExample, {width: 298, height: 394, dashed: true, 
                            summary: "Desktop room conversation remote face-mute"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   roomStore: desktopRemoteFaceMuteRoomStore, 
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   localPosterUrl: "sample-img/video-screen-local.png"})
               )
@@ -1063,16 +1083,17 @@
                   activeRoomStore: failedRoomStore, 
                   isFirefox: false})
               )
             )
           ), 
 
           React.createElement(Section, {name: "StandaloneRoomView (Mobile)"}, 
             React.createElement(FramedExample, {width: 600, height: 480, cssClass: "standalone", 
+                           dashed: true, 
                            onContentsRendered: updatingActiveRoomStore.forcedUpdate, 
                            summary: "Standalone room conversation (has-participants, 600x480)"}, 
                 React.createElement("div", {className: "standalone"}, 
                   React.createElement(StandaloneRoomView, {
                     dispatcher: dispatcher, 
                     activeRoomStore: updatingActiveRoomStore, 
                     roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                     isFirefox: true, 
@@ -1092,24 +1113,36 @@
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png", 
                     screenSharePosterUrl: "sample-img/video-screen-terminal.png"})
                 )
             )
           ), 
 
-          React.createElement(Section, {name: "TextChatView (standalone)"}, 
-            React.createElement(FramedExample, {width: 200, height: 400, cssClass: "standalone", 
-                          summary: "Standalone Text Chat conversation (200 x 400)"}, 
+          React.createElement(Section, {name: "TextChatView"}, 
+            React.createElement(FramedExample, {width: 298, height: 160, dashed: true, 
+                           summary: "TextChatView: desktop embedded"}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(TextChatView, {dispatcher: dispatcher, 
+                              showAlways: false, 
+                              showRoomName: false})
+              )
+            ), 
+
+            React.createElement(FramedExample, {width: 200, height: 400, dashed: true, 
+                           cssClass: "standalone", 
+                           summary: "Standalone Text Chat conversation (200x400)"}, 
               React.createElement("div", {className: "standalone text-chat-example"}, 
-                React.createElement(TextChatView, {
-                  dispatcher: dispatcher, 
-                  showAlways: true, 
-                  showRoomName: true})
+                React.createElement("div", {className: "media-wrapper"}, 
+                  React.createElement(TextChatView, {
+                    dispatcher: dispatcher, 
+                    showAlways: true, 
+                    showRoomName: true})
+                )
               )
             )
           ), 
 
           React.createElement(Section, {name: "SVG icons preview", className: "svg-icons"}, 
             React.createElement(Example, {summary: "10x10"}, 
               React.createElement(SVGIcons, {size: "10x10"})
             ), 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -288,33 +288,53 @@
     client: {},
     mozLoop: navigator.mozLoop,
     sdkDriver: mockSDK
   });
   var textChatStore = new loop.store.TextChatStore(dispatcher, {
     sdkDriver: mockSDK
   });
 
-  textChatStore.setStoreState({
-    // XXX Disabled until we start sorting out some of the layouts.
-    textChatEnabled: false
-  });
-
   // Update the text chat store with the room info.
   textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
     roomName: "A Very Long Conversation Name",
     roomOwner: "fake",
     roomUrl: "http://showcase",
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
       // use the fallback thumbnail
     }]
   }));
 
+  textChatStore.setStoreState({textChatEnabled: true});
+
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Rheet!"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Hi there"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Check out this menu from DNA Pizza:" +
+    " http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
+    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "That avocado monkey-brains pie sounds tasty!"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "What time should we meet?"
+  }));
+
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
 
   // Local mocks
@@ -816,28 +836,28 @@
               <div className="standalone">
                 <UnsupportedDeviceView platform="ios"/>
               </div>
             </Example>
           </Section>
 
           <Section name="DesktopRoomConversationView">
             <FramedExample width={298} height={254}
-              summary="Desktop room conversation (invitation)">
+              summary="Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)">
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   roomStore={invitationRoomStore}
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   localPosterUrl="sample-img/video-screen-local.png"
                   roomState={ROOM_STATES.INIT} />
               </div>
             </FramedExample>
 
-            <FramedExample width={298} height={254}
+            <FramedExample width={298} height={394} dashed={true}
               summary="Desktop room conversation (loading)">
               {/* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */}
               <div className="fx-embedded overflow-hidden">
                 <DesktopRoomConversationView
                   roomStore={desktopRoomStoreLoading}
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
@@ -855,28 +875,28 @@
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   localPosterUrl="sample-img/video-screen-local.png"
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS} />
               </div>
             </FramedExample>
 
-            <FramedExample width={298} height={254}
+            <FramedExample width={298} height={394} dashed={true}
                            summary="Desktop room conversation local face-mute">
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   roomStore={desktopLocalFaceMuteRoomStore}
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   remotePosterUrl="sample-img/video-screen-remote.png" />
               </div>
             </FramedExample>
 
-            <FramedExample width={298} height={254}
+            <FramedExample width={298} height={394} dashed={true}
                            summary="Desktop room conversation remote face-mute">
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   roomStore={desktopRemoteFaceMuteRoomStore}
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   localPosterUrl="sample-img/video-screen-local.png" />
               </div>
@@ -1063,16 +1083,17 @@
                   activeRoomStore={failedRoomStore}
                   isFirefox={false} />
               </div>
             </FramedExample>
           </Section>
 
           <Section name="StandaloneRoomView (Mobile)">
             <FramedExample width={600} height={480} cssClass="standalone"
+                           dashed={true}
                            onContentsRendered={updatingActiveRoomStore.forcedUpdate}
                            summary="Standalone room conversation (has-participants, 600x480)">
                 <div className="standalone">
                   <StandaloneRoomView
                     dispatcher={dispatcher}
                     activeRoomStore={updatingActiveRoomStore}
                     roomState={ROOM_STATES.HAS_PARTICIPANTS}
                     isFirefox={true}
@@ -1092,24 +1113,36 @@
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png"
                     screenSharePosterUrl="sample-img/video-screen-terminal.png" />
                 </div>
             </FramedExample>
           </Section>
 
-          <Section name="TextChatView (standalone)">
-            <FramedExample width={200} height={400} cssClass="standalone"
-                          summary="Standalone Text Chat conversation (200 x 400)">
+          <Section name="TextChatView">
+            <FramedExample width={298} height={160} dashed={true}
+                           summary="TextChatView: desktop embedded">
+              <div className="fx-embedded">
+                <TextChatView dispatcher={dispatcher}
+                              showAlways={false}
+                              showRoomName={false} />
+              </div>
+            </FramedExample>
+
+            <FramedExample width={200} height={400} dashed={true}
+                           cssClass="standalone"
+                           summary="Standalone Text Chat conversation (200x400)">
               <div className="standalone text-chat-example">
-                <TextChatView
-                  dispatcher={dispatcher}
-                  showAlways={true}
-                  showRoomName={true} />
+                <div className="media-wrapper">
+                  <TextChatView
+                    dispatcher={dispatcher}
+                    showAlways={true}
+                    showRoomName={true} />
+                </div>
               </div>
             </FramedExample>
           </Section>
 
           <Section name="SVG icons preview" className="svg-icons">
             <Example summary="10x10">
               <SVGIcons size="10x10"/>
             </Example>