diff --git a/.gitignore b/.gitignore
index 5027b0300902b0cbc4968947fc9af206ff14557c..ff96dc53d3fffe2582853ee0c18a82dff9706204 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@
 /resources/static/production
 /resources/static/i18n
 .DS_Store
+Thumbs.db
 /locale
diff --git a/resources/static/common/js/provisioning.js b/resources/static/common/js/provisioning.js
index 53859547130ee17a4033c1ae060e879ebbbbf704..82aaa25c29b4a5b91dac6e5925621b8be4844e16 100644
--- a/resources/static/common/js/provisioning.js
+++ b/resources/static/common/js/provisioning.js
@@ -7,9 +7,13 @@ BrowserID.Provisioning = (function() {
   "use strict";
 
   var jwcrypto = require("./lib/jwcrypto");
+  var MAX_TIMEOUT = 20000; // 20s
 
   var Provisioning = function(args, successCB, failureCB) {
+    var timeoutID;
+
     function tearDown() {
+      if (timeoutID) timeoutID = clearTimeout(timeoutID);
       if (chan) chan.destroy();
       chan = undefined;
       if (iframe) document.body.removeChild(iframe);
@@ -35,8 +39,9 @@ BrowserID.Provisioning = (function() {
     // extract the expected origin from the provisioning url
     // (this may be a different domain than the email domain part, if the
     //  domain delates authority)
+    var origin;
     try {
-      var origin = /^(https?:\/\/[^\/]+)\//.exec(args.url)[1];
+      origin = /^(https?:\/\/[^\/]+)\//.exec(args.url)[1];
     } catch(e) { alert(e); }
     if (!origin) {
       return fail('internal', 'bad provisioning url, can\'t extract origin');
@@ -47,6 +52,29 @@ BrowserID.Provisioning = (function() {
     var iframe = document.createElement("iframe");
     iframe.setAttribute('src', args.url);
     iframe.style.display = "none";
+
+    // start the timeout once the iframe loads, so we don't get false
+    // positives if the user is on a slow connection.
+    // the timeout should only happen if the provisioning site doesn't
+    // want to provision for us.
+    // see https://github.com/mozilla/browserid/pull/1954
+    function iframeOnLoad() {
+      if (timeoutID) {
+        clearTimeout(timeoutID);
+      }
+      // a timeout for the amount of time that provisioning is allowed to take
+      timeoutID = setTimeout(function provisionTimedOut() {
+        fail('timeoutError', 'Provisioning timed out.');
+      }, MAX_TIMEOUT);
+    }
+
+    if (iframe.addEventListener) {
+      iframe.addEventListener('load', iframeOnLoad, false);
+    } else if (iframe.attachEvent) {
+      iframe.attachEvent('onload', iframeOnLoad);
+    }
+    // else ruh-roh?
+
     document.body.appendChild(iframe);
 
     var chan = Channel.build({
@@ -92,7 +120,6 @@ BrowserID.Provisioning = (function() {
       successCB(keypair, cert);
     });
 
-    // XXX: set a timeout for the amount of time that provisioning is allowed to take
   };
 
   return Provisioning;