Bug 1173036 - Change Loop's RTL attributes to be consistently set on the html element. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Wed, 10 Jun 2015 14:12:42 +0100
changeset 247996 b971fbbbe37e14d786e036758a1302eacadce226
parent 247995 04d1294656a1ed703e837991d1b487b563f85405
child 247997 b4f355ca322fb6ca04d795720209dd59addc8a14
push id28887
push userkwierso@gmail.com
push dateThu, 11 Jun 2015 01:10:53 +0000
treeherdermozilla-central@1fd19d8fc936 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1173036
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 1173036 - Change Loop's RTL attributes to be consistently set on the html element. r=mikedeboer
browser/components/loop/content/css/contacts.css
browser/components/loop/content/css/panel.css
browser/components/loop/content/js/conversation.js
browser/components/loop/content/js/conversation.jsx
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/content/shared/css/common.css
browser/components/loop/content/shared/css/conversation.css
browser/components/loop/ui/react-frame-component.js
browser/components/loop/ui/ui-showcase.css
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -129,17 +129,17 @@
   border-radius: 50%;
   background-image: url("../shared/img/icons-16x16.svg#google");
   background-position: center;
   background-size: 16px 16px;
   background-repeat: no-repeat;
   background-color: #fff;
 }
 
-body[dir="rtl"] .contact > .details > .username > i.icon-google {
+html[dir="rtl"] .contact > .details > .username > i.icon-google {
   left: 1rem;
   right: auto;
 }
 
 .contact > .details > .email {
   color: #999;
   font-size: 11px;
   line-height: 16px;
@@ -188,17 +188,17 @@ body[dir="rtl"] .contact > .details > .u
 .contact > .dropdown-menu {
   z-index: 2;
   top: 10px;
   bottom: auto;
   right: 3em;
   left: auto;
 }
 
-body[dir="rtl"] .contact > .dropdown-menu {
+html[dir="rtl"] .contact > .dropdown-menu {
   right: auto;
   left: 3em;
 }
 
 .contact > .dropdown-menu-up {
   bottom: 10px;
   top: auto;
 }
@@ -206,17 +206,17 @@ body[dir="rtl"] .contact > .dropdown-men
 .contact > .dropdown-menu > .dropdown-menu-item > .icon {
   width: 20px;
   height: 10px;
   background-position: center left;
   background-size: 10px 10px;
   margin-top: 3px;
 }
 
-body[dir="rtl"] .contact > .dropdown-menu > .dropdown-menu-item > .icon {
+html[dir="rtl"] .contact > .dropdown-menu > .dropdown-menu-item > .icon {
   background-position: center right;
 }
 
 .contact > .dropdown-menu > .dropdown-menu-item > .icon-audio-call {
   background-image: url("../shared/img/icons-16x16.svg#audio");
 }
 
 .contact > .dropdown-menu > .dropdown-menu-item > .icon-video-call {
@@ -254,17 +254,17 @@ body[dir="rtl"] .contact > .dropdown-men
 
 .contacts-gravatar-promo > p {
   margin-top: 2px;
   margin-bottom: 8px;
   margin-right: 4px;
   word-wrap: break-word;
 }
 
-body[dir=rtl] .contacts-gravatar-promo > p {
+html[dir="rtl"] .contacts-gravatar-promo > p {
   margin-right: 0;
   margin-left: 4px;
 }
 
 .contacts-gravatar-promo > p > a {
   color: #0295df;
   text-decoration: none;
 }
@@ -274,12 +274,12 @@ body[dir=rtl] .contacts-gravatar-promo >
 }
 
 .contacts-gravatar-promo > .button-close {
   position: absolute;
   top: 8px;
   right: 8px;
 }
 
-body[dir=rtl] .contacts-gravatar-promo > .button-close {
+html[dir="rtl"] .contacts-gravatar-promo > .button-close {
   right: auto;
   left: 8px;
 }
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -260,17 +260,17 @@ body {
 .new-room-view > .context > .context-content > .context-preview {
   float: left;
   width: 16px;
   max-height: 16px;
   -moz-margin-end: .8em;
   flex: 0 1 auto;
 }
 
-body[dir=rtl] .new-room-view > .context > .context-content > .context-preview {
+html[dir="rtl"] .new-room-view > .context > .context-content > .context-preview {
   float: left;
 }
 
 .new-room-view > .context > .context-content > .context-description {
   flex: 0 1 auto;
   display: block;
 }
 
@@ -558,17 +558,17 @@ body[dir=rtl] .new-room-view > .context 
   position: absolute;
   pointer-events: none;
   z-index: 1;
   top: 4px;
   left: auto;
   right: 4px;
 }
 
-body[dir=rtl] .generate-url-spinner {
+html[dir="rtl"] .generate-url-spinner {
   left: 4px;
   right: auto;
 }
 
 #fte-button,
 .generate-url .button {
   background-color: #0096dd;
   border-color: #0096dd;
@@ -744,17 +744,17 @@ body[dir=rtl] .generate-url-spinner {
      let's anchor it from the bottom-right, while resetting the top & left values
      set by .dropdown-menu */
   top: auto;
   left: auto;
   bottom: -8px;
   right: 14px;
 }
 
-body[dir="rtl"] .settings-menu .dropdown-menu {
+html[dir="rtl"] .settings-menu .dropdown-menu {
   /* This is specified separately rather than using -moz-margin-start etc, as
      we need to override .dropdown-menu's values which can't use the gecko
      specific extensions. */
   left: 14px;
   right: auto;
 }
 
 .settings-menu .icon {
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -158,17 +158,18 @@ loop.conversation = (function(mozL10n) {
       dispatcher.dispatch(new sharedActions.WindowUnload());
     });
 
     React.render(React.createElement(AppControllerView, {
       roomStore: roomStore, 
       dispatcher: dispatcher, 
       mozLoop: navigator.mozLoop}), document.querySelector("#main"));
 
-    document.body.setAttribute("dir", mozL10n.getDirection());
+    document.documentElement.setAttribute("lang", mozL10n.getLanguage());
+    document.documentElement.setAttribute("dir", mozL10n.getDirection());
     document.body.setAttribute("platform", loop.shared.utils.getPlatform());
 
     dispatcher.dispatch(new sharedActions.GetWindowData({
       windowId: windowId
     }));
   }
 
   return {
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -158,17 +158,18 @@ loop.conversation = (function(mozL10n) {
       dispatcher.dispatch(new sharedActions.WindowUnload());
     });
 
     React.render(<AppControllerView
       roomStore={roomStore}
       dispatcher={dispatcher}
       mozLoop={navigator.mozLoop} />, document.querySelector("#main"));
 
-    document.body.setAttribute("dir", mozL10n.getDirection());
+    document.documentElement.setAttribute("lang", mozL10n.getLanguage());
+    document.documentElement.setAttribute("dir", mozL10n.getDirection());
     document.body.setAttribute("platform", loop.shared.utils.getPlatform());
 
     dispatcher.dispatch(new sharedActions.GetWindowData({
       windowId: windowId
     }));
   }
 
   return {
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -997,17 +997,18 @@ loop.panel = (function(_, mozL10n) {
     });
 
     React.render(React.createElement(PanelView, {
       notifications: notifications, 
       roomStore: roomStore, 
       mozLoop: navigator.mozLoop, 
       dispatcher: dispatcher}), document.querySelector("#main"));
 
-    document.body.setAttribute("dir", mozL10n.getDirection());
+    document.documentElement.setAttribute("lang", mozL10n.getLanguage());
+    document.documentElement.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");
     evtObject.initEvent("loopPanelInitialized", true, false);
     window.dispatchEvent(evtObject);
   }
 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -997,17 +997,18 @@ loop.panel = (function(_, mozL10n) {
     });
 
     React.render(<PanelView
       notifications={notifications}
       roomStore={roomStore}
       mozLoop={navigator.mozLoop}
       dispatcher={dispatcher} />, document.querySelector("#main"));
 
-    document.body.setAttribute("dir", mozL10n.getDirection());
+    document.documentElement.setAttribute("lang", mozL10n.getLanguage());
+    document.documentElement.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");
     evtObject.initEvent("loopPanelInitialized", true, false);
     window.dispatchEvent(evtObject);
   }
 
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -421,17 +421,17 @@ p {
   left: 0;
   background-color: #fdfdfd;
   box-shadow: 0 1px 3px rgba(0,0,0,.3);
   list-style: none;
   padding: 5px;
   border-radius: 2px;
 }
 
-body[dir=rtl] .dropdown-menu {
+html[dir="rtl"] .dropdown-menu {
   left: auto;
   right: 0;
 }
 
 .dropdown-menu-item {
   text-align: start;
   margin: .3em 0;
   padding: .2em .5em;
@@ -476,17 +476,17 @@ body[dir=rtl] .dropdown-menu {
   border-radius: 3px;
   cursor: pointer;
   background-color: transparent;
   background-position: center center;
   background-repeat: no-repeat;
   background-size: 1em 1em;
 }
 
-body[dir="rtl"] .checkbox {
+html[dir="rtl"] .checkbox {
   float: right;
 }
 
 .checkbox.checked {
   background-image: url("../img/check.svg#check");
 }
 
 .checkbox.checked:hover,
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -874,17 +874,17 @@ html, .fx-embedded, #main,
   cursor: pointer;
 }
 
 .room-invitation-addcontext:hover,
 .room-invitation-addcontext:hover:active {
   text-decoration: underline;
 }
 
-body[dir="rtl"] .room-invitation-addcontext {
+html[dir="rtl"] .room-invitation-addcontext {
   padding-left: 0;
   padding-right: 1.5em;
   background-position: right top;
 }
 
 .share-service-dropdown {
   color: #000;
   text-align: start;
@@ -1099,23 +1099,23 @@ body[platform="win"] .share-service-drop
   background-image: url("../img/icons-10x10.svg#edit-active");
 }
 
 .room-context-btn-close:hover,
 .room-context-btn-close:hover:active {
   background-image: url("../img/icons-10x10.svg#close-active");
 }
 
-body[dir=rtl] .room-context-btn-close,
-body[dir=rtl] .room-context-btn-edit {
+html[dir="rtl"] .room-context-btn-close,
+html[dir="rtl"] .room-context-btn-edit {
   right: auto;
   left: 8px;
 }
 
-body[dir=rtl] .room-context-btn-edit {
+html[dir="rtl"] .room-context-btn-edit {
   left: 20px;
 }
 
 /* Standalone rooms */
 
 .standalone .room-conversation-wrapper {
   position: relative;
   height: 100%;
--- a/browser/components/loop/ui/react-frame-component.js
+++ b/browser/components/loop/ui/react-frame-component.js
@@ -57,16 +57,24 @@ window.Frame = React.createClass({
 
       var contents = React.createElement("div",
         undefined,
         this.props.head,
         this.props.children
       );
 
       React.render(contents, doc.body, this.fireOnContentsRendered.bind(this));
+
+      // Set the RTL mode. We assume for now that rtl is the only query parameter.
+      //
+      // See also "ShowCase" in ui-showcase.jsx
+      if (document.location.search === "?rtl=1") {
+        doc.documentElement.setAttribute("lang", "ar");
+        doc.documentElement.setAttribute("dir", "rtl");
+      }
     } else {
       // Queue it, only if it isn't already. We do need to set the timeout
       // regardless, as this function can get re-entered several times.
       if (window.queuedFrames.indexOf(this) === -1) {
         window.queuedFrames.push(this);
       }
       setTimeout(this.renderFrameContents.bind(this), 0);
     }
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -81,16 +81,24 @@ body {
   text-align: left;
 }
 
 .showcase > section .example > h3 a {
   text-decoration: none;
   color: #555;
 }
 
+.showcase .checkbox-wrapper label {
+  font-weight: bold;
+}
+
+.showcase .checkbox.checked {
+  background-image: url("../content/shared/img/check.svg#check-blue");
+}
+
 .showcase p.note {
   margin: 0;
   padding: 0;
   color: #666;
   font-style: italic;
 }
 
 .override-position * {
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -26,16 +26,17 @@
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var StandaloneRoomView      = loop.standaloneRoomViews.StandaloneRoomView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var FeedbackView = loop.shared.views.FeedbackView;
+  var Checkbox = loop.shared.views.Checkbox;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   // Local helpers
   function returnTrue() {
@@ -410,21 +411,52 @@
           React.createElement("h1", null, this.props.name), 
           this.props.children
         )
       );
     }
   });
 
   var ShowCase = React.createClass({displayName: "ShowCase",
+    getInitialState: function() {
+      // We assume for now that rtl is the only query parameter.
+      //
+      // Note: this check is repeated in react-frame-component to save passing
+      // rtlMode down the props tree.
+      var rtlMode = document.location.search === "?rtl=1";
+
+      return {
+        rtlMode: rtlMode
+      };
+    },
+
+    _handleCheckboxChange: function(newState) {
+      var newLocation = "";
+      if (newState.checked) {
+        newLocation = document.location.href.split("#")[0];
+        newLocation += "?rtl=1";
+      } else {
+        newLocation = document.location.href.split("?")[0];
+      }
+      newLocation += document.location.hash;
+      document.location = newLocation;
+    },
+
     render: function() {
+      if (this.state.rtlMode) {
+        document.documentElement.setAttribute("lang", "ar");
+        document.documentElement.setAttribute("dir", "rtl");
+      }
+
       return (
         React.createElement("div", {className: "showcase"}, 
           React.createElement("header", null, 
             React.createElement("h1", null, "Loop UI Components Showcase"), 
+            React.createElement(Checkbox, {label: "RTL mode?", checked: this.state.rtlMode, 
+              onChange: this._handleCheckboxChange}), 
             React.createElement("nav", {className: "showcase-menu"}, 
               React.Children.map(this.props.children, function(section) {
                 return (
                   React.createElement("a", {className: "btn btn-info", href: "#" + section.props.name}, 
                     section.props.name
                   )
                 );
               })
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -26,16 +26,17 @@
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var StandaloneRoomView      = loop.standaloneRoomViews.StandaloneRoomView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var FeedbackView = loop.shared.views.FeedbackView;
+  var Checkbox = loop.shared.views.Checkbox;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   // Local helpers
   function returnTrue() {
@@ -410,21 +411,52 @@
           <h1>{this.props.name}</h1>
           {this.props.children}
         </section>
       );
     }
   });
 
   var ShowCase = React.createClass({
+    getInitialState: function() {
+      // We assume for now that rtl is the only query parameter.
+      //
+      // Note: this check is repeated in react-frame-component to save passing
+      // rtlMode down the props tree.
+      var rtlMode = document.location.search === "?rtl=1";
+
+      return {
+        rtlMode: rtlMode
+      };
+    },
+
+    _handleCheckboxChange: function(newState) {
+      var newLocation = "";
+      if (newState.checked) {
+        newLocation = document.location.href.split("#")[0];
+        newLocation += "?rtl=1";
+      } else {
+        newLocation = document.location.href.split("?")[0];
+      }
+      newLocation += document.location.hash;
+      document.location = newLocation;
+    },
+
     render: function() {
+      if (this.state.rtlMode) {
+        document.documentElement.setAttribute("lang", "ar");
+        document.documentElement.setAttribute("dir", "rtl");
+      }
+
       return (
         <div className="showcase">
           <header>
             <h1>Loop UI Components Showcase</h1>
+            <Checkbox label="RTL mode?" checked={this.state.rtlMode}
+              onChange={this._handleCheckboxChange} />
             <nav className="showcase-menu">{
               React.Children.map(this.props.children, function(section) {
                 return (
                   <a className="btn btn-info" href={"#" + section.props.name}>
                     {section.props.name}
                   </a>
                 );
               })