From e50c2b1ebc6235905838827cc0f0319984c980a2 Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <stomlinson@mozilla.com>
Date: Wed, 26 Oct 2011 09:37:34 -0600
Subject: [PATCH] improved messaging for unsupported browsers - closes #273,
 closes #484

---
 browserid/app.js                              |   7 +-
 .../dialog/controllers/dialog_controller.js   |  14 +-
 .../dialog/controllers/page_controller.js     |  16 ++-
 browserid/static/dialog/css/m.css             |  15 +-
 browserid/static/dialog/css/popup.css         |  37 ++++-
 browserid/static/dialog/qunit.html            |  27 ++--
 .../dialog/resources/browser-support.js       | 118 ++++++++++++++++
 .../controllers/page_controller_unit_test.js  |  34 ++++-
 .../dialog/test/qunit/include_unit_test.js    |  52 +++++++
 browserid/static/dialog/test/qunit/qunit.js   |   8 +-
 .../resources/browser-support_unit_test.js    | 102 ++++++++++++++
 browserid/static/i/firefox_logo.png           | Bin 0 -> 26648 bytes
 browserid/static/include.js                   | 131 ++++++++++++------
 browserid/tests/page-requests-test.js         | 116 ++++++++++++++++
 browserid/views/dialog.ejs                    | 113 ++++-----------
 browserid/views/dialog_layout.ejs             |  58 ++++++++
 browserid/views/unsupported_dialog.ejs        |  28 ++++
 17 files changed, 708 insertions(+), 168 deletions(-)
 create mode 100644 browserid/static/dialog/resources/browser-support.js
 create mode 100644 browserid/static/dialog/test/qunit/include_unit_test.js
 create mode 100644 browserid/static/dialog/test/qunit/resources/browser-support_unit_test.js
 create mode 100644 browserid/static/i/firefox_logo.png
 create mode 100755 browserid/tests/page-requests-test.js
 create mode 100644 browserid/views/dialog_layout.ejs
 create mode 100644 browserid/views/unsupported_dialog.ejs

diff --git a/browserid/app.js b/browserid/app.js
index f637f884a..6f661ee7c 100644
--- a/browserid/app.js
+++ b/browserid/app.js
@@ -80,11 +80,16 @@ function router(app) {
     metrics.userEntry(req);
     res.render('dialog.ejs', {
       title: 'A Better Way to Sign In',
-      layout: false,
+      layout: 'dialog_layout.ejs',
+      useJavascript: true,
       production: configuration.get('use_minified_resources')
     });
   });
 
+  app.get("/unsupported_dialog", function(req,res) {
+    res.render('unsupported_dialog.ejs', {layout: 'dialog_layout.ejs', useJavascript: false});
+  });
+
   // simple redirects (internal for now)
   app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true));
 
diff --git a/browserid/static/dialog/controllers/dialog_controller.js b/browserid/static/dialog/controllers/dialog_controller.js
index 771c31770..49560e08d 100644
--- a/browserid/static/dialog/controllers/dialog_controller.js
+++ b/browserid/static/dialog/controllers/dialog_controller.js
@@ -50,18 +50,17 @@
   PageController.extend("Dialog", {}, {
       init: function(el) {
         var self=this;
-        //this.element.show();
 
         // keep track of where we are and what we do on success and error
         self.onsuccess = null;
         self.onerror = null;
         setupChannel(self);
         self.stateMachine();
+       
       },
         
       getVerifiedEmail: function(origin_url, onsuccess, onerror) {
         var self=this;
-
         self.onsuccess = onsuccess;
         self.onerror = onerror;
 
@@ -71,8 +70,6 @@
         }
 
         user.setOrigin(origin_url);
-        
-        // get the cleaned origin.
         $("#sitename").text(user.getHostname());
 
         self.doCheckAuth();
@@ -88,7 +85,6 @@
         var self=this, 
             hub = OpenAjax.hub, 
             el = this.element;
-       
 
         hub.subscribe("offline", function(msg, info) {
           self.doOffline();
@@ -119,7 +115,7 @@
         });
 
         hub.subscribe("assertion_generated", function(msg, info) {
-          if(info.assertion !== null) {
+          if (info.assertion !== null) {
             self.doAssertionGenerated(info.assertion);
           }
           else {
@@ -158,12 +154,12 @@
       },
 
       doOffline: function() {
-        this.renderError(errors.offline);
+        this.renderError("wait.ejs", errors.offline);
         offline = true;
       },
 
       doXHRError: function(info) {
-        if (!offline) this.renderError(errors.offline);  
+        if (!offline) this.renderError("wait.ejs", errors.offline);  
       },
 
       doConfirmUser: function(email) {
@@ -178,7 +174,7 @@
 
       doCancel: function() {
         var self=this;
-        if(self.onsuccess) {
+        if (self.onsuccess) {
           self.onsuccess(null);
         }
       },
diff --git a/browserid/static/dialog/controllers/page_controller.js b/browserid/static/dialog/controllers/page_controller.js
index ba80f9bbf..01ddbb26a 100644
--- a/browserid/static/dialog/controllers/page_controller.js
+++ b/browserid/static/dialog/controllers/page_controller.js
@@ -48,6 +48,8 @@
       var me=this,
           bodyTemplate = options.bodyTemplate,
           bodyVars = options.bodyVars,
+          errorTemplate = options.errorTemplate,
+          errorVars = options.errorVars,
           waitTemplate = options.waitTemplate,
           waitVars = options.waitVars;
 
@@ -60,6 +62,10 @@
         me.renderWait(waitTemplate, waitVars);
       }
 
+      if(errorTemplate) {
+        me.renderError(errorTemplate, errorVars);
+      }
+
       // XXX move all of these, bleck.
       $("form").bind("submit", me.onSubmit.bind(me));
       $("#cancel").click(me.onCancel.bind(me));
@@ -99,10 +105,10 @@
       $("#wait").stop().hide().fadeIn(ANIMATION_TIME);
     },
 
-    renderError: function(error_vars) {
-      this.renderTemplates("#error", "wait.ejs", error_vars);
-      $("body").removeClass("waiting").removeClass("form").addClass("error").css('opacity', 1);
-      $("#error").stop().hide().fadeIn(ANIMATION_TIME);
+    renderError: function(body, body_vars) {
+      this.renderTemplates("#error", body, body_vars);
+      $("body").removeClass("waiting").removeClass("form").addClass("error");
+      $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME);
     },
 
     onSubmit: function(event) {
@@ -144,7 +150,7 @@
      */
     getErrorDialog: function(info) {
       var self=this;
-      return self.renderError.bind(self, info);
+      return self.renderError.bind(self, "wait.ejs", info);
     },
 
     onCancel: function(event) {
diff --git a/browserid/static/dialog/css/m.css b/browserid/static/dialog/css/m.css
index 49f4a5613..9ddb605ea 100644
--- a/browserid/static/dialog/css/m.css
+++ b/browserid/static/dialog/css/m.css
@@ -111,6 +111,19 @@
       height: 250px;
   }
 
-}
+  #error .vertical {
+    width: auto;
+  }
+  #error .vertical > div {
+    display: block;
+    height: auto;
+    padding: 10px;
+  }
+
+  #error #borderbox {
+    border-left: none;
+    padding: 0;
+  }
+
 
 
diff --git a/browserid/static/dialog/css/popup.css b/browserid/static/dialog/css/popup.css
index b1b54243f..e5f4503f2 100644
--- a/browserid/static/dialog/css/popup.css
+++ b/browserid/static/dialog/css/popup.css
@@ -118,16 +118,17 @@ section > .contents {
 
 #wait, #error {
     text-align: center;
-    background-image: url("/i/bg.png");
 }
 
 #wait {
     z-index: 1;
+    background-image: url("/i/bg.png");
 }
 
 #error {
     display: none;
     z-index: 2;
+    background-color: #fff;
 }
 
 #wait strong, #error strong {
@@ -135,10 +136,40 @@ section > .contents {
     font-weight: bold;
 }
 
-#error {
-    z-index: 2;
+
+#error .vertical {
+    width: 630px;
+    margin: 0 auto;
+    display: block;
+}
+
+
+#error .vertical > div {
+    display: table-cell;
+    vertical-align: middle;
+    padding: 0 10px;
+    height: 250px;
+}
+
+#error #alternative a {
+    color: #549FDC;
+    text-decoration: underline;
+}
+
+#error #borderbox {
+    border-left: 1px solid #777;
+    padding: 20px 0;
 }
 
+#error #borderbox img {
+    border: none;
+}
+
+#error #alternative .lighter {
+    color: #777;
+}
+
+
 #formWrap {
     background-color: #fff;
     background-image: none;
diff --git a/browserid/static/dialog/qunit.html b/browserid/static/dialog/qunit.html
index 716ec5323..7e8189f82 100644
--- a/browserid/static/dialog/qunit.html
+++ b/browserid/static/dialog/qunit.html
@@ -12,28 +12,31 @@
 		<div id="qunit-testrunner-toolbar"></div>
 		<h2 id="qunit-userAgent"></h2>
 		<div id="test-content">
-      <div id="page_controller">
+    </div>
+		<ol id="qunit-tests"></ol>
+		<div id="qunit-test-area"></div>
 
-        <div id="formWrap">
-            <div class="contents"></div>
-        </div>
+    <h3>Content below here is test content that can be ignored</h3>
 
-        <div id="wait">
-            <div class="contents"></div>
-        </div>
+    <div id="controller_head">
 
-        <div id="error">
-            <div class="contents"></div>
-        </div>
+      <div id="formWrap">
+          <div class="contents"></div>
+      </div>
 
+      <div id="wait">
+          <div class="contents"></div>
       </div>
+
+      <div id="error">
+          <div class="contents"></div>
+      </div>
+
       <span id="email"></span>
       <span id="cannotconfirm" class="error">Cannot confirm</span>
       <span id="cannotcommunicate" class="error">Cannot communicate</span>
       <span id="siteinfo" class="error"><span class="website"></span></span>
       <span class=".hint">Hint</span>
     </div>
-		<ol id="qunit-tests"></ol>
-		<div id="qunit-test-area"></div>
 	</body>
 </html>
diff --git a/browserid/static/dialog/resources/browser-support.js b/browserid/static/dialog/resources/browser-support.js
new file mode 100644
index 000000000..6c2e34818
--- /dev/null
+++ b/browserid/static/dialog/resources/browser-support.js
@@ -0,0 +1,118 @@
+/*globals BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+BrowserID.BrowserSupport = (function() {
+  var bid = BrowserID,
+      win = window,
+      nav = navigator,
+      reason;
+
+  // For unit testing
+  function setTestEnv(newNav, newWindow) {
+    nav = newNav;
+    win = newWindow;
+  }
+
+  function getInternetExplorerVersion() {
+    var rv = -1; // Return value assumes failure.
+    if (nav.appName == 'Microsoft Internet Explorer') {
+      var ua = nav.userAgent;
+      var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
+      if (re.exec(ua) != null)
+        rv = parseFloat(RegExp.$1);
+    }
+
+    return rv;
+  }
+
+  function checkIE() {
+    var ieVersion = getInternetExplorerVersion(),
+        ieNosupport = ieVersion > -1 && ieVersion < 9;
+
+    if(ieNosupport) {
+      return "IE_VERSION";
+    }
+  }
+
+  function explicitNosupport() {
+    return checkIE();
+  }
+
+  function checkLocalStorage() {
+    var localStorage = 'localStorage' in win && win['localStorage'] !== null;
+    if(!localStorage) {
+      return "LOCALSTORAGE";
+    }
+  }
+
+  function checkPostMessage() {
+    if(!win.postMessage) {
+      return "POSTMESSAGE";
+    }
+  }
+
+  function isSupported() {
+    reason = checkLocalStorage() || checkPostMessage() || explicitNosupport();
+
+    return !reason;
+  }
+
+  function getNoSupportReason() {
+    return reason;
+  }
+
+  return {
+    /**
+     * Set the test environment.
+     * @method setTestEnv
+     */
+    setTestEnv: setTestEnv,
+    /**
+     * Check whether the current browser is supported
+     * @method isSupported
+     * @returns {boolean}
+     */
+    isSupported: isSupported,
+    /**
+     * Called after isSupported, if isSupported returns false.  Gets the reason 
+     * why browser is not supported.
+     * @method getNoSupportReason
+     * @returns {string}
+     */
+    getNoSupportReason: getNoSupportReason
+  };
+  
+}());
+
diff --git a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js b/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
index fca829d5c..e2df4dbb6 100644
--- a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
+++ b/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
@@ -41,16 +41,21 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
       bodyTemplate = "testBodyTemplate.ejs",
       waitTemplate = "wait.ejs";
 
+  function reset() {
+    el = $("#controller_head");
+    el.find("#formWrap .contents").html("");
+    el.find("#wait .contents").html("");
+    el.find("#error .contents").html("");
+  }
+
   module("PageController", {
     setup: function() {
-      el = $("#page_controller");
+      reset();
     },
 
     teardown: function() {
-      el.find("#formWrap .contents").html("");
-      el.find("#wait .contents").html("");
-      el.find("#error .contents").html("");
       controller.destroy();
+      reset();
     } 
   });
 
@@ -76,10 +81,11 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
     var html = el.find("#formWrap .contents").html();
     ok(html.length, "with template specified, form text is loaded");
 
+/*
 
     var input = el.find("input").eq(0);
     ok(input.is(":focus"), "make sure the first input is focused");
-
+*/
     html = el.find("#wait .contents").html();
     equal(html, "", "with body template specified, wait text is not loaded");
   });
@@ -100,6 +106,22 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
     ok(html.length, "with wait template specified, wait text is loaded");
   });
 
+  test("page controller with error template renders in #error .contents", function() {
+    controller = el.page({
+      errorTemplate: waitTemplate,
+      errorVars: {
+        title: "Test title",
+        message: "Test message"
+      }
+    }).controller();
+
+    var html = el.find("#formWrap .contents").html();
+    equal(html, "", "with error template specified, form is ignored");
+
+    html = el.find("#error .contents").html();
+    ok(html.length, "with error template specified, error text is loaded");
+  });
+
   test("renderError renders an error message", function() {
     controller = el.page({
       waitTemplate: waitTemplate,
@@ -109,7 +131,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
       }
     }).controller();
    
-    controller.renderError({
+    controller.renderError("wait.ejs", {
       title: "error title",
       message: "error message"
     });
diff --git a/browserid/static/dialog/test/qunit/include_unit_test.js b/browserid/static/dialog/test/qunit/include_unit_test.js
new file mode 100644
index 000000000..815ecf821
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/include_unit_test.js
@@ -0,0 +1,52 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery", "funcunit/qunit").then("/include.js", function() {
+  "use strict";
+
+  module("include.js");
+  
+  test("navigator.id is available", function() {
+    equal(typeof navigator.id, "object", "navigator.id namespace is available");
+  });
+
+  test("navigator.id.getVerifiedEmail is available", function() {
+    equal(typeof navigator.id.getVerifiedEmail, "function", "navigator.id.getVerifiedEmail is available");
+  });
+
+
+});
+
diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js
index 1733ac554..450add4ea 100644
--- a/browserid/static/dialog/test/qunit/qunit.js
+++ b/browserid/static/dialog/test/qunit/qunit.js
@@ -1,4 +1,5 @@
 steal("/dialog/resources/browserid.js",
+      "/dialog/resources/browser-support.js",
       "/dialog/resources/storage.js",
       "/dialog/resources/tooltip.js",
       "/dialog/resources/validation.js",
@@ -12,10 +13,15 @@ steal("/dialog/resources/browserid.js",
     "funcunit/qunit")
 	.views('testBodyTemplate.ejs')
 	.views('wait.ejs')
+  .then("/dialog/controllers/page_controller.js")
   .then("browserid_unit_test")
+  .then("include_unit_test")
   .then("pages/add_email_address_test")
-  .then("controllers/page_controller_unit_test")
+  .then("resources/browser-support_unit_test")
   .then("resources/validation_unit_test")
   .then("resources/storage_unit_test")
   .then("resources/network_unit_test")
   .then("resources/user_unit_test")
+  .then("controllers/page_controller_unit_test")
+  .then("controllers/page_controller_unit_test")
+
diff --git a/browserid/static/dialog/test/qunit/resources/browser-support_unit_test.js b/browserid/static/dialog/test/qunit/resources/browser-support_unit_test.js
new file mode 100644
index 000000000..a26857f01
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/resources/browser-support_unit_test.js
@@ -0,0 +1,102 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery", "funcunit/qunit").then(function() {
+  "use strict";
+
+  var bid = BrowserID,
+      support = bid.BrowserSupport,
+      stubWindow,
+      stubNavigator;
+
+  module("browser-support", {
+    setup: function() {
+      // Hard coded goodness for testing purposes
+      stubNavigator = {
+        appName: "Netscape",
+        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"
+      };
+
+      stubWindow = {
+        localStorage: {},
+        postMessage: function() {}
+      };
+
+      support.setTestEnv(stubNavigator, stubWindow);
+    },
+
+    teardown: function() {
+    }
+  });
+  
+  test("browser without localStorage", function() {
+    delete stubWindow.localStorage;
+
+    equal(support.isSupported(), false, "window.localStorage is required");
+    equal(support.getNoSupportReason(), "LOCALSTORAGE", "correct reason");
+  });
+
+
+  test("browser without postMessage", function() {
+    delete stubWindow.postMessage;
+
+    equal(support.isSupported(), false, "window.postMessage is required");
+    equal(support.getNoSupportReason(), "POSTMESSAGE", "correct reason");
+  });
+
+  test("Fake being IE8 - unsupported intentionally", function() {
+    stubNavigator.appName = "Microsoft Internet Explorer";
+    stubNavigator.userAgent = "MSIE 8.0";
+
+    equal(support.isSupported(), false, "IE8 is not supported");
+    equal(support.getNoSupportReason(), "IE_VERSION", "correct reason");
+  });
+
+  test("Fake being IE9 - supported", function() {
+    stubNavigator.appName = "Microsoft Internet Explorer";
+    stubNavigator.userAgent = "MSIE 9.0";
+
+    equal(support.isSupported(), true, "IE9 is supported");
+    equal(typeof support.getNoSupportReason(), "undefined", "no reason, we are all good");
+  });
+
+  test("Firefox 7.01 with postMessage, localStorage", function() {
+    equal(support.isSupported(), true, "Firefox 7.01 is supported");
+    equal(typeof support.getNoSupportReason(), "undefined", "no reason, we are all good");
+  });
+});
+
+
diff --git a/browserid/static/i/firefox_logo.png b/browserid/static/i/firefox_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..c55a338081cc1e0f1d2e78fc50226695c0b37488
GIT binary patch
literal 26648
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
z%0D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

literal 0
HcmV?d00001

diff --git a/browserid/static/include.js b/browserid/static/include.js
index 9f16d5d47..cebeafc44 100644
--- a/browserid/static/include.js
+++ b/browserid/static/include.js
@@ -557,55 +557,88 @@
     };
   })();
 
-  function getInternetExplorerVersion() {
-    var rv = -1; // Return value assumes failure.
-    if (navigator.appName == 'Microsoft Internet Explorer') {
-      var ua = navigator.userAgent;
-      var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
-      if (re.exec(ua) != null)
-        rv = parseFloat(RegExp.$1);
+  var BrowserSupport = (function() {
+    var win = window,
+        nav = navigator,
+        reason;
+
+    // For unit testing
+    function setTestEnv(newNav, newWindow) {
+      nav = newNav;
+      win = newWindow;
     }
 
-    return rv;
-  }
-
-  function checkIE() {
-    var ieVersion = getInternetExplorerVersion(),
-        ieNosupport = ieVersion > -1 && ieVersion < 9,
-        message;
+    function getInternetExplorerVersion() {
+      var rv = -1; // Return value assumes failure.
+      if (nav.appName == 'Microsoft Internet Explorer') {
+        var ua = nav.userAgent;
+        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
+        if (re.exec(ua) != null)
+          rv = parseFloat(RegExp.$1);
+      }
 
-    if(ieNosupport) {
-      message = "Unfortunately, your version of Internet Explorer is not yet supported.\n" +
-            'If you are using Internet Explorer 9, turn off "Compatibility View".';
+      return rv;
     }
 
-    return message;
-  }
+    function checkIE() {
+      var ieVersion = getInternetExplorerVersion(),
+          ieNosupport = ieVersion > -1 && ieVersion < 9;
 
-  function explicitNosupport() {
-    var message = checkIE();
+      if(ieNosupport) {
+        return "IE_VERSION";
+      }
+    }
 
-    if (message) {
-       message += "\nWe are working hard to bring BrowserID support to your browser!";
-       alert(message);
+    function explicitNosupport() {
+      return checkIE();
     }
 
-    return message;
-  }
+    function checkLocalStorage() {
+      var localStorage = 'localStorage' in win && win['localStorage'] !== null;
+      if(!localStorage) {
+        return "LOCALSTORAGE";
+      }
+    }
 
-  function checkRequirements() {
-    var localStorage = 'localStorage' in window && window['localStorage'] !== null;
-    var postMessage = !!window.postMessage;
-    var json = true;
+    function checkPostMessage() {
+      if(!win.postMessage) {
+        return "POSTMESSAGE";
+      }
+    }
 
-    var explicitNo = explicitNosupport()
+    function isSupported() {
+      reason = checkLocalStorage() || checkPostMessage() || explicitNosupport();
 
-    if(!explicitNo && !(localStorage && postMessage && json)) {
-      alert("Unfortunately, your browser does not meet the minimum HTML5 support required for BrowserID.");
+      return !reason;
     }
 
-    return localStorage && postMessage && json && !(explicitNo);
-  }
+    function getNoSupportReason() {
+      return reason;
+    }
+
+    return {
+      /**
+       * Set the test environment.
+       * @method setTestEnv
+       */
+      setTestEnv: setTestEnv,
+      /**
+       * Check whether the current browser is supported
+       * @method isSupported
+       * @returns {boolean}
+       */
+      isSupported: isSupported,
+      /**
+       * Called after isSupported, if isSupported returns false.  Gets the reason 
+       * why browser is not supported.
+       * @method getNoSupportReason
+       * @returns {string}
+       */
+      getNoSupportReason: getNoSupportReason
+    };
+    
+  }());
+
 
   // this is for calls that are non-interactive
   function _open_hidden_iframe(doc) {
@@ -636,16 +669,20 @@
     return iframe;
   }
   
-  function _open_window() {
+  function _open_window(url) {
+    url = url || "about:blank";
     // we open the window initially blank, and only after our relay frame has
     // been constructed do we update the location.  This is done because we
     // must launch the window inside a click handler, but we should wait to
     // start loading it until our relay iframe is instantiated and ready.
     // see issue #287 & #286
-    return window.open(
-      "about:blank",
+    var dialog = window.open(
+      url,
       "_mozid_signin",
       isFennec ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=700,height=375");
+
+    dialog.focus();
+    return dialog;
   }
 
   function _attach_event(element, name, listener) {
@@ -684,16 +721,17 @@
 
     // keep track of these so that we can re-use/re-focus an already open window.
     navigator.id.getVerifiedEmail = function(callback) {
-      if(!checkRequirements()) {
-        return;
-      }
-
       if (w) {
         // if there is already a window open, just focus the old window.
         w.focus();
         return;
       }
 
+      if (!BrowserSupport.isSupported()) {
+        w = _open_window(ipServer + "/unsupported_dialog");
+        return;
+      }
+
       var frameid = _get_relayframe_id();
       var iframe = _open_relayframe("browserid_relay_" + frameid);
       w = _open_window();
@@ -712,8 +750,7 @@
           // has a problem re-attaching new iframes with the same name.  Code inside
           // of frames with the same name sometimes does not get run.
           // See https://bugzilla.mozilla.org/show_bug.cgi?id=350023
-          w.location = ipServer + "/sign_in#" + frameid;
-          w.focus();
+          w = _open_window(ipServer + "/sign_in#" + frameid);
         }
       });
 
@@ -721,8 +758,10 @@
         chan.destroy();
         chan = null;
 
-        w.close();
-        w = null;
+        if (w) {
+          w.close();
+          w = null;
+        }
 
         iframe.parentNode.removeChild(iframe);
         iframe = null;
diff --git a/browserid/tests/page-requests-test.js b/browserid/tests/page-requests-test.js
new file mode 100755
index 000000000..156a0611d
--- /dev/null
+++ b/browserid/tests/page-requests-test.js
@@ -0,0 +1,116 @@
+#!/usr/bin/env node
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+require('./lib/test_env.js');
+
+const assert = require('assert'),
+http = require('http'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js');
+
+var suite = vows.describe('page requests');
+
+// start up a pristine server
+start_stop.addStartupBatches(suite);
+
+// This set of tests check to make sure all of the expected pages are served 
+// up with the correct status codes.  We use Lloyd's wsapi client as our REST 
+// interface.
+
+
+
+// Taken from the vows page.
+function assertStatus(code) {
+  return function (res, err) {
+    assert.equal(res.code, code);
+  };
+}
+
+function respondsWith(status) {
+  var context = {
+    topic: function () {
+      // Get the current context's name, such as "POST /"
+      // and split it at the space.
+      var req    = this.context.name.split(/ +/), // ["POST", "/"]
+          method = req[0].toLowerCase(),         // "post"
+          path   = req[1];                       // "/"
+
+      // Perform the contextual client request,
+      // with the above method and path.
+      wsapi[method](path).call(this);
+    }
+  };
+
+  // Create and assign the vow to the context.
+  // The description is generated from the expected status code
+  // and the status name, from node's http module.
+  context['should respond with a ' + status + ' '
+         + http.STATUS_CODES[status]] = assertStatus(status);
+
+  return context;
+}
+
+suite.addBatch({
+  'GET /':                       respondsWith(200),
+  'GET /signup':                 respondsWith(200),
+  'GET /forgot':                 respondsWith(200),
+  'GET /signin':                 respondsWith(200),
+  'GET /about':                  respondsWith(200),
+  'GET /tos':                    respondsWith(200),
+  'GET /privacy':                respondsWith(200),
+  'GET /verify_email_address':   respondsWith(200),
+  'GET /add_email_address':      respondsWith(200),
+  'GET /pk':                     respondsWith(200),
+  'GET /vepbundle':              respondsWith(200),
+  'GET /signin':                 respondsWith(200),
+  'GET /unsupported_dialog':     respondsWith(200),
+  'GET /developers':             respondsWith(200),
+  'GET /manage':                 respondsWith(302),
+  'GET /users':                  respondsWith(302),
+  'GET /users/':                 respondsWith(302),
+  'GET /primaries':              respondsWith(302),
+  'GET /primaries/':             respondsWith(302),
+  'GET /developers':             respondsWith(302)
+});
+
+// shut the server down and cleanup
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/browserid/views/dialog.ejs b/browserid/views/dialog.ejs
index bf766dfda..d2b634a26 100644
--- a/browserid/views/dialog.ejs
+++ b/browserid/views/dialog.ejs
@@ -1,90 +1,35 @@
-<!doctype html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;">  
-  <!--[if lt IE 9]>
-    <script type="text/javascript" src="/js/html5shim.js"></script>
-  <![endif]-->
-  <% if (production) { %>
-      <link href="/dialog/css/production.min.css" rel="stylesheet" type="text/css">
-  <% } else { %>
-      <link href="/dialog/css/popup.css" rel="stylesheet" type="text/css">
-      <link href="/dialog/css/m.css" rel="stylesheet" type="text/css">
-  <% } %>
-  <link href="https://fonts.googleapis.com/css?family=Droid+Serif:400,400italic,700,700italic" rel="stylesheet" type="text/css">
-  <title>Browser ID</title>
-</head>
-  <body class="waiting">
-      <div id="wrapper">
-          <header id="header" class="cf">
-              <ul>
-                  <li><a class="home" target="_blank" href="/"></a></li>
-              </ul>
-          </header>
-
-          <div id="content">
-              <section id="formWrap">
-                <form novalidate> 
-                  <div id="favicon">
-                      <div class="vertical">
-                          <strong id="sitename"></strong>
-                      </div>
-                  </div>
-
-                  <div id="signIn">
-                      <div class="arrow"></div>
-                      <div class="table">
-                          <div class="vertical contents">
-                          </div>
-                      </div>
-                  </div>
-                </form>
-              </section>
-
-
-              <section id="wait">
-                  <div class="table">
-                      <div class="vertical contents">
-                          <h2>Communicating with server</h2>
-                          <p>Just a moment while we talk with the server.</p>
-                      </div>
-                  </div>
-              </section>
-
-
-              <section id="error">
-                  <div class="table">
-                      <div class="vertical contents">
-                      </div>
-                  </div>
-              </section>
-          </div>
-
-          <footer>
-                <ul class="cf">
-                    <li>By <a href="http://mozillalabs.com">Mozilla Labs</a></li>
-
-                    <li>&mdash;</li>           
-                    <li><a href="#">Privacy</a></li>
-                    <li><a href="#">TOS</a></li>
-                </ul>
+    <section id="formWrap">
+      <form novalidate> 
+        <div id="favicon">
+            <div class="vertical">
+                <strong id="sitename"></strong>
+            </div>
+        </div>
 
-                <div class="learn">
-                    BrowserID is the fast and secure way to sign in &mdash; <a target="_blank" href="/about">learn more</a>
+        <div id="signIn">
+            <div class="arrow"></div>
+            <div class="table">
+                <div class="vertical contents">
                 </div>
+            </div>
+        </div>
+      </form>
+    </section>
+
 
-                <a class="help" href="https://support.mozilla.com/en-US/kb/what-browserid-and-how-does-it-work" target="_blank">need help?</a>
-          </footer>
+    <section id="wait">
+        <div class="table">
+            <div class="vertical contents">
+                <h2>Communicating with server</h2>
+                <p>Just a moment while we talk with the server.</p>
+            </div>
+        </div>
+    </section>
 
-      </div>
 
-      <script type="text/html" id="templateTooltip">
-        <div class="tooltip">
-          {{ contents }}
+    <section id="error">
+        <div class="table">
+            <div class="vertical contents">
+            </div>
         </div>
-      </script>
-      <script type="text/javascript" src="/vepbundle"></script>
-      <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?dialog"></script>
-	</body>
-</html>
+    </section>
diff --git a/browserid/views/dialog_layout.ejs b/browserid/views/dialog_layout.ejs
new file mode 100644
index 000000000..13dd4d98f
--- /dev/null
+++ b/browserid/views/dialog_layout.ejs
@@ -0,0 +1,58 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;">  
+  <!--[if lt IE 9]>
+    <script type="text/javascript" src="/js/html5shim.js"></script>
+  <![endif]-->
+  <% if (production) { %>
+      <link href="/dialog/css/production.min.css" rel="stylesheet" type="text/css">
+  <% } else { %>
+      <link href="/dialog/css/popup.css" rel="stylesheet" type="text/css">
+      <link href="/dialog/css/m.css" rel="stylesheet" type="text/css">
+  <% } %>
+  <link href="https://fonts.googleapis.com/css?family=Droid+Serif:400,400italic,700,700italic" rel="stylesheet" type="text/css">
+  <title>Browser ID</title>
+</head>
+  <body class="waiting">
+      <div id="wrapper">
+          <header id="header" class="cf">
+              <ul>
+                  <li><a class="home" target="_blank" href="/"></a></li>
+              </ul>
+          </header>
+
+          <div id="content">
+            <%- body %>
+          </div>
+
+          <footer>
+                <ul class="cf">
+                    <li>By <a href="http://mozillalabs.com">Mozilla Labs</a></li>
+
+                    <li>&mdash;</li>           
+                    <li><a href="#">Privacy</a></li>
+                    <li><a href="#">TOS</a></li>
+                </ul>
+
+                <div class="learn">
+                    BrowserID is the fast and secure way to sign in &mdash; <a target="_blank" href="/about">learn more</a>
+                </div>
+
+                <a class="help" href="https://support.mozilla.com/en-US/kb/what-browserid-and-how-does-it-work" target="_blank">need help?</a>
+          </footer>
+
+      </div>
+
+      <% if (useJavascript !== false) { %>
+          <script type="text/html" id="templateTooltip">
+            <div class="tooltip">
+              {{ contents }}
+            </div>
+          </script>
+          <script type="text/javascript" src="/vepbundle"></script>
+          <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?dialog"></script>
+      <% } %>
+	</body>
+</html>
diff --git a/browserid/views/unsupported_dialog.ejs b/browserid/views/unsupported_dialog.ejs
new file mode 100644
index 000000000..af410a76e
--- /dev/null
+++ b/browserid/views/unsupported_dialog.ejs
@@ -0,0 +1,28 @@
+  <section id="error" style="display: block">
+      <div class="table">
+          <div class="vertical contents">
+              <div id="reason">
+                We're sorry, but currently your browser isn't supported.
+              </div>
+
+              <div id="alternative">
+
+                <div id="borderbox">
+                  <a href="http://getfirefox.com" target="_blank">
+                    <img src="/i/firefox_logo.png" width="250" height="88" alt="Firefox logo" />
+                  </a>
+
+                  <p>
+                    BrowserID requires <a href="http://getfirefox.com" target="_blank" title="Get Firefox">Firefox</a>
+                  </p>
+
+                  <p class="lighter">
+                    or other <a href="" target="_blank">modern browser.</a>
+                  </p>
+                </div>
+
+              </div>
+
+          </div>
+      </div>
+  </section>
-- 
GitLab