Bug 1146921 - Disable the window sharing dropdown item in Loop conversation windows on unsupported platforms. r=Standard8, a=sledru
authorMike de Boer <mdeboer@mozilla.com>
Thu, 02 Apr 2015 13:24:31 +0200
changeset 258322 d384bdaed2fd
parent 258321 8ff6cc64abe8
child 258323 4406ce9ace92
push id4645
push userryanvm@gmail.com
push date2015-04-07 15:05 +0000
treeherdermozilla-beta@8f0271f2c153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, sledru
bugs1146921
milestone38.0
Bug 1146921 - Disable the window sharing dropdown item in Loop conversation windows on unsupported platforms. r=Standard8, a=sledru
browser/components/loop/content/shared/js/utils.js
browser/components/loop/content/shared/js/views.js
browser/components/loop/content/shared/js/views.jsx
browser/components/loop/test/shared/utils_test.js
browser/components/loop/test/shared/views_test.js
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -116,16 +116,126 @@ loop.shared.utils = (function(mozL10n) {
     if (/BlackBerry/i.test(platform)) {
       return "blackberry";
     }
 
     return null;
   }
 
   /**
+   * Helper to get the Operating System name.
+   *
+   * @param {String}  [platform]    The platform this is running on, will fall
+   *                                back to navigator.oscpu and navigator.userAgent
+   *                                respectively if not supplied.
+   * @param {Boolean} [withVersion] Optional flag to keep the version number
+   *                                included in the resulting string. Defaults to
+   *                                `false`.
+   * @return {String} The platform we're currently running on, in lower-case.
+   */
+  var getOS = _.memoize(function(platform, withVersion) {
+    if (!platform) {
+      if ("oscpu" in window.navigator) {
+        // See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/oscpu
+        platform = window.navigator.oscpu.split(";")[0].trim();
+      } else {
+        // Fall back to navigator.userAgent as a last resort.
+        platform = window.navigator.userAgent;
+      }
+    }
+
+    if (!platform) {
+      return "unknown";
+    }
+
+    // Support passing in navigator.userAgent.
+    var platformPart = platform.match(/\((.*)\)/);
+    if (platformPart) {
+      platform = platformPart[1];
+    }
+    platform = platform.toLowerCase().split(";");
+    if (/macintosh/.test(platform[0]) || /x11/.test(platform[0])) {
+      platform = platform[1];
+    } else {
+      if (platform[0].indexOf("win") > -1 && platform.length > 4) {
+        // Skip the security notation.
+        platform = platform[2];
+      } else {
+        platform = platform[0];
+      }
+    }
+
+    if (!withVersion) {
+      platform = platform.replace(/\s[0-9.]+/g, "");
+    }
+
+    return platform.trim();
+  }, function(platform, withVersion) {
+    // Cache the return values with the following key.
+    return (platform + "") + (withVersion + "");
+  });
+
+  /**
+   * Helper to get the Operating System version.
+   * See http://en.wikipedia.org/wiki/Windows_NT for a table of Windows NT
+   * versions.
+   *
+   * @param {String} [platform] The platform this is running on, will fall back
+   *                            to navigator.oscpu and navigator.userAgent
+   *                            respectively if not supplied.
+   * @return {String} The current version of the platform we're currently running
+   *                  on.
+   */
+  var getOSVersion = _.memoize(function(platform) {
+    var os = getOS(platform, true);
+    var digitsRE = /\s([0-9.]+)/;
+
+    var version = os.match(digitsRE);
+    if (!version) {
+      if (os.indexOf("win") > -1) {
+        if (os.indexOf("xp")) {
+          return { major: 5, minor: 2 };
+        } else if (os.indexOf("vista") > -1) {
+          return { major: 6, minor: 0 };
+        }
+      }
+    } else {
+      version = version[1];
+      // Windows versions have an interesting scheme.
+      if (os.indexOf("win") > -1) {
+        switch (parseFloat(version)) {
+          case 98:
+            return { major: 4, minor: 1 };
+          case 2000:
+            return { major: 5, minor: 0 };
+          case 2003:
+            return { major: 5, minor: 2 };
+          case 7:
+          case 2008:
+          case 2011:
+            return { major: 6, minor: 1 };
+          case 8:
+            return { major: 6, minor: 2 };
+          case 8.1:
+          case 2012:
+            return { major: 6, minor: 3 };
+        }
+      }
+
+      version = version.split(".");
+      return {
+        major: parseInt(version[0].trim(), 10),
+        minor: parseInt(version[1] ? version[1].trim() : 0, 10)
+      };
+    }
+
+    return { major: Infinity, minor: 0 };
+  });
+
+  /**
    * Helper to allow getting some of the location data in a way that's compatible
    * with stubbing for unit tests.
    */
   function locationData() {
     return {
       hash: window.location.hash,
       pathname: window.location.pathname
     };
@@ -163,14 +273,16 @@ loop.shared.utils = (function(mozL10n) {
     FAILURE_DETAILS: FAILURE_DETAILS,
     REST_ERRNOS: REST_ERRNOS,
     WEBSOCKET_REASONS: WEBSOCKET_REASONS,
     STREAM_PROPERTIES: STREAM_PROPERTIES,
     SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
     composeCallUrlEmail: composeCallUrlEmail,
     formatDate: formatDate,
     getBoolPreference: getBoolPreference,
+    getOS: getOS,
+    getOSVersion: getOSVersion,
     isFirefox: isFirefox,
     isFirefoxOS: isFirefoxOS,
     getUnsupportedPlatform: getUnsupportedPlatform,
     locationData: locationData
   };
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -88,16 +88,27 @@ loop.shared.views = (function(_, l10n) {
     mixins: [sharedMixins.DropdownMenuMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       visible: React.PropTypes.bool.isRequired,
       state: React.PropTypes.string.isRequired,
     },
 
+    getInitialState: function() {
+      var os = loop.shared.utils.getOS();
+      var osVersion = loop.shared.utils.getOSVersion();
+      // Disable screensharing on older OSX and Windows versions.
+      if ((os.indexOf("mac") > -1 && osVersion.major <= 10 && osVersion.minor <= 6) ||
+          (os.indexOf("win") > -1 && osVersion.major <= 5 && osVersion.minor <= 2)) {
+        return { windowSharingDisabled: true };
+      }
+      return { windowSharingDisabled: false };
+    },
+
     handleClick: function() {
       if (this.props.state === SCREEN_SHARE_STATES.ACTIVE) {
         this.props.dispatcher.dispatch(
           new sharedActions.EndScreenShare({}));
       } else {
         this.toggleDropdownMenu();
       }
     },
@@ -139,29 +150,32 @@ loop.shared.views = (function(_, l10n) {
         "active": isActive,
         "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
       });
       var dropdownMenuClasses = cx({
         "native-dropdown-menu": true,
         "conversation-window-dropdown": true,
         "visually-hidden": !this.state.showMenu
       });
+      var windowSharingClasses = cx({
+        "disabled": this.state.windowSharingDisabled
+      });
 
       return (
         React.createElement("div", null, 
           React.createElement("button", {className: screenShareClasses, 
                   onClick: this.handleClick, 
                   title: this._getTitle()}, 
             isActive ? null : React.createElement("span", {className: "chevron"})
           ), 
           React.createElement("ul", {ref: "menu", className: dropdownMenuClasses}, 
             React.createElement("li", {onClick: this._handleShareTabs}, 
               l10n.get("share_tabs_button_title")
             ), 
-            React.createElement("li", {onClick: this._handleShareWindows}, 
+            React.createElement("li", {onClick: this._handleShareWindows, className: windowSharingClasses}, 
               l10n.get("share_windows_button_title")
             )
           )
         )
       );
     }
   });
 
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -88,16 +88,27 @@ loop.shared.views = (function(_, l10n) {
     mixins: [sharedMixins.DropdownMenuMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       visible: React.PropTypes.bool.isRequired,
       state: React.PropTypes.string.isRequired,
     },
 
+    getInitialState: function() {
+      var os = loop.shared.utils.getOS();
+      var osVersion = loop.shared.utils.getOSVersion();
+      // Disable screensharing on older OSX and Windows versions.
+      if ((os.indexOf("mac") > -1 && osVersion.major <= 10 && osVersion.minor <= 6) ||
+          (os.indexOf("win") > -1 && osVersion.major <= 5 && osVersion.minor <= 2)) {
+        return { windowSharingDisabled: true };
+      }
+      return { windowSharingDisabled: false };
+    },
+
     handleClick: function() {
       if (this.props.state === SCREEN_SHARE_STATES.ACTIVE) {
         this.props.dispatcher.dispatch(
           new sharedActions.EndScreenShare({}));
       } else {
         this.toggleDropdownMenu();
       }
     },
@@ -139,29 +150,32 @@ loop.shared.views = (function(_, l10n) {
         "active": isActive,
         "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
       });
       var dropdownMenuClasses = cx({
         "native-dropdown-menu": true,
         "conversation-window-dropdown": true,
         "visually-hidden": !this.state.showMenu
       });
+      var windowSharingClasses = cx({
+        "disabled": this.state.windowSharingDisabled
+      });
 
       return (
         <div>
           <button className={screenShareClasses}
                   onClick={this.handleClick}
                   title={this._getTitle()}>
             {isActive ? null : <span className="chevron"/>}
           </button>
           <ul ref="menu" className={dropdownMenuClasses}>
             <li onClick={this._handleShareTabs}>
               {l10n.get("share_tabs_button_title")}
             </li>
-            <li onClick={this._handleShareWindows}>
+            <li onClick={this._handleShareWindows} className={windowSharingClasses}>
               {l10n.get("share_windows_button_title")}
             </li>
           </ul>
         </div>
       );
     }
   });
 
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -166,9 +166,84 @@ describe("loop.shared.utils", function()
     it("should compose a call url email", function() {
       sharedUtils.composeCallUrlEmail("http://invalid", "fake@invalid.tld");
 
       sinon.assert.calledOnce(composeEmail);
       sinon.assert.calledWith(composeEmail,
                               "subject", "body", "fake@invalid.tld");
     });
   });
+
+  describe("#getOS", 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.getOS(UA);
+
+      expect(result).eql("intel mac os x");
+    });
+
+    it("should recognize the OSX userAgent string with version", function() {
+      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
+      var result = sharedUtils.getOS(UA, true);
+
+      expect(result).eql("intel mac os x 10.10");
+    });
+
+    it("should recognize the Windows userAgent string with version", function() {
+      var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
+      var result = sharedUtils.getOS(UA, true);
+
+      expect(result).eql("windows nt 6.1");
+    });
+
+    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.getOS(UA);
+
+      expect(result).eql("linux i686 on x86_64");
+    });
+
+    it("should recognize the OSX oscpu string", function() {
+      var oscpu = "Intel Mac OS X 10.10";
+      var result = sharedUtils.getOS(oscpu, true);
+
+      expect(result).eql("intel mac os x 10.10");
+    });
+
+    it("should recognize the Windows oscpu string", function() {
+      var oscpu = "Windows NT 5.3; Win64; x64";
+      var result = sharedUtils.getOS(oscpu, true);
+
+      expect(result).eql("windows nt 5.3");
+    });
+  });
+
+  describe("#getOSVersion", function() {
+    it("should fetch the correct version info for OSX", function() {
+      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
+      var result = sharedUtils.getOSVersion(UA);
+
+      expect(result).eql({ major: 10, minor: 10 });
+    });
+
+    it("should fetch the correct version info for Windows", function() {
+      var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
+      var result = sharedUtils.getOSVersion(UA);
+
+      expect(result).eql({ major: 6, minor: 1 });
+    });
+
+    it("should fetch the correct version info for Windows XP", function() {
+      var oscpu = "Windows XP";
+      var result = sharedUtils.getOSVersion(oscpu);
+
+      expect(result).eql({ major: 5, minor: 2 });
+    });
+
+    it("should fetch the correct version info for Linux", function() {
+      var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
+      var result = sharedUtils.getOSVersion(UA);
+
+      // Linux version can't be determined correctly.
+      expect(result).eql({ major: Infinity, minor: 0 });
+    });
+  });
 });
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -11,17 +11,17 @@ var TestUtils = React.addons.TestUtils;
 
 describe("loop.shared.views", function() {
   "use strict";
 
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass;
-  var sandbox, fakeAudioXHR, dispatcher;
+  var sandbox, fakeAudioXHR, dispatcher, OS, OSVersion;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
     sandbox.stub(l10n, "get", function(x) {
       return "translated:" + x;
     });
 
@@ -35,16 +35,25 @@ describe("loop.shared.views", function()
       getResponseHeader: function(header) {
         if (header === "Content-Type")
           return "audio/ogg";
       },
       responseType: null,
       response: new ArrayBuffer(10),
       onload: null
     };
+
+    OS = "mac";
+    OSVersion = { major: 10, minor: 10 };
+    sandbox.stub(loop.shared.utils, "getOS", function() {
+      return OS;
+    });
+    sandbox.stub(loop.shared.utils, "getOSVersion", function() {
+      return OSVersion;
+    });
   });
 
   afterEach(function() {
     $("#fixtures").empty();
     sandbox.restore();
   });
 
   describe("MediaControlButton", function() {
@@ -181,16 +190,58 @@ describe("loop.shared.views", function()
         TestUtils.Simulate.click(comp.getDOMNode().querySelector(
           ".conversation-window-dropdown > li:last-child"));
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.StartScreenShare({ type: "window" }));
       });
 
+    it("should have the 'window' option enabled", function() {
+      var comp = TestUtils.renderIntoDocument(
+        React.createElement(sharedViews.ScreenShareControlButton, {
+          dispatcher: dispatcher,
+          visible: true,
+          state: SCREEN_SHARE_STATES.INACTIVE
+        }));
+
+      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      expect(node.classList.contains("disabled")).eql(false);
+    });
+
+    it("should disable the 'window' option on Windows XP", function() {
+      OS = "win";
+      OSVersion = { major: 5, minor: 1 };
+
+      var comp = TestUtils.renderIntoDocument(
+        React.createElement(sharedViews.ScreenShareControlButton, {
+          dispatcher: dispatcher,
+          visible: true,
+          state: SCREEN_SHARE_STATES.INACTIVE
+        }));
+
+      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      expect(node.classList.contains("disabled")).eql(true);
+    });
+
+    it("should disable the 'window' option on OSX 10.6", function() {
+      OS = "mac";
+      OSVersion = { major: 10, minor: 6 };
+
+      var comp = TestUtils.renderIntoDocument(
+        React.createElement(sharedViews.ScreenShareControlButton, {
+          dispatcher: dispatcher,
+          visible: true,
+          state: SCREEN_SHARE_STATES.INACTIVE
+        }));
+
+      var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
+      expect(node.classList.contains("disabled")).eql(true);
+    });
+
     it("should dispatch a EndScreenShare action on click when the state is active",
       function() {
         var comp = TestUtils.renderIntoDocument(
           React.createElement(sharedViews.ScreenShareControlButton, {
             dispatcher: dispatcher,
             visible: true,
             state: SCREEN_SHARE_STATES.ACTIVE
           }));