Bug 1168848 - Make TextChat style correctly when focussed. r=Standard8
authorDan Mosedale <dmose@meer.net>
Sat, 20 Jun 2015 17:32:52 +0100
changeset 249955 1fab1b2a759bcd2556b9215f0ea806cd169590d0
parent 249954 0879159622522cf3127b1908610654e56428fbd9
child 249956 6a653debc815a97008251d87e88063109305f965
push id61393
push usercbook@mozilla.com
push dateMon, 22 Jun 2015 12:44:45 +0000
treeherdermozilla-inbound@4b47c3f074a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1168848
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>