diff --git a/browserid/app.js b/browserid/app.js
index f637f884a8bc9ecbde0e1a5e682b6618fb20a6da..6f661ee7c23063c93e6e44bfcfaaafda91e8e68f 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 771c3177067bcf5432d58d20bb40fc4eba72a81a..49560e08de033003c50a668bb273d72ffd141d02 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 ba80f9bbff4ee3569571b92843c422b76aa2c0e7..01ddbb26a0671476402fb333de998bdb32998db5 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 49f4a561375d7648adab479f5d104dab69b0c9bb..9ddb605ea69a40de8225cbc81420ceecf12ddcb4 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 b1b54243f2d023bc6e2a46f27d7a621ad3b1ba01..e5f4503f22515f34345a6993ba6c885a472fd836 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 716ec5323d770965ab866d14c51cb36583920fce..7e8189f829a5ff161198b6c2aa4251a536757786 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 0000000000000000000000000000000000000000..6c2e34818d0e67ec6b20ed76ce0f03ce3404593a
--- /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 fca829d5c917981be1272ff7ee2148d1f089fa5c..e2df4dbb6a159fc4aa78c73b6a2e9c5842885996 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 0000000000000000000000000000000000000000..815ecf82138ce58855bffdae94d8639568269a97
--- /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 1733ac5548a0e89a31aa0f86527d287d7fc4d2ca..450add4ea320c385bc56072036d2781843ffaccb 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 0000000000000000000000000000000000000000..a26857f01eef422ad03c0cc49fbb4d9257957cef
--- /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
Binary files /dev/null and b/browserid/static/i/firefox_logo.png differ
diff --git a/browserid/static/include.js b/browserid/static/include.js
index 9f16d5d47007e557a9c7987df3a98f23471be0fb..cebeafc44cb225688d5ed9b36a062a33f9492e5f 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 0000000000000000000000000000000000000000..156a0611df558716b6c3d25edbc8e8fd13c3679d
--- /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 bf766dfdae7a176d976d3fca573e38a5638efab0..d2b634a26338b2e79f40b510c8351d1aba641e22 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 0000000000000000000000000000000000000000..13dd4d98f92ddc60880e28d7105492ba153837ae
--- /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 0000000000000000000000000000000000000000..af410a76e1883f574edd61aba028f7e06ae7ae72
--- /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>