diff --git a/docs/changes/2252.rp b/docs/changes/2252.rp
new file mode 100644
index 0000000000000000000000000000000000000000..8b95f24a90c89f1a1eb06edf4c52d83013fba725
--- /dev/null
+++ b/docs/changes/2252.rp
@@ -0,0 +1,2 @@
+RPs can now call navigator.id.watch from the head of their document. 
+
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index 1df41c082075bbd6abed52e08d55423fbed8718b..eb2dab63ec9a097ba2ac3a0f17eeb05be9c5992d 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -966,6 +966,8 @@
       ready: null
     };
 
+    var loggedInUser;
+
     var compatMode = undefined;
     function checkCompat(requiredMode) {
       if (requiredMode === true) {
@@ -981,18 +983,48 @@
     }
 
     var commChan,
+        waitingForDOM = false,
         browserSupported = BrowserSupport.isSupported();
 
+    function domReady(callback) {
+      if (document.addEventListener) {
+        document.addEventListener('DOMContentLoaded', function contentLoaded() {
+          document.removeEventListener('DOMContentLoaded', contentLoaded);
+          callback();
+        }, false);
+      } else if (document.attachEvent && document.readyState) {
+        document.attachEvent('onreadystatechange', function ready() {
+          var state = document.readyState;
+          // 'interactive' is the same as DOMContentLoaded,
+          // but not all browsers use it, sadly.
+          if (state === 'loaded' || state === 'complete' || state === 'interactive') {
+            document.detachEvent('onreadystatechange', ready);
+            callback();
+          }
+        });
+      }
+    }
+
+
     // this is for calls that are non-interactive
     function _open_hidden_iframe() {
       // If this is an unsupported browser, do not even attempt to add the
       // IFRAME as doing so will cause an exception to be thrown in IE6 and IE7
       // from within the communication_iframe.
       if(!browserSupported) return;
+      var doc = window.document;
+
+      // can't attach iframe and make commChan without the body
+      if (!doc.body) {
+        if (!waitingForDOM) {
+          domReady(_open_hidden_iframe);
+          waitingForDOM = true;
+        }
+        return;
+      }
 
       try {
         if (!commChan) {
-          var doc = window.document;
           var iframe = doc.createElement("iframe");
           iframe.style.display = "none";
           doc.body.appendChild(iframe);
@@ -1022,6 +1054,13 @@
           commChan.bind('login', function(trans, params) {
             if (observers.login) observers.login(params);
           });
+
+          if (loggedInUser) {
+            commChan.notify({
+              method: 'loggedInUser',
+              params: loggedInUser
+            });
+          }
         }
       } catch(e) {
         // channel building failed!  let's ignore the error and allow higher
@@ -1077,20 +1116,11 @@
       observers.logout = options.onlogout || null;
       observers.ready = options.onready || null;
 
-      _open_hidden_iframe();
-
       // back compat support for loggedInEmail
       checkRenamed(options, "loggedInEmail", "loggedInUser");
+      loggedInUser = options.loggedInUser;
 
-      // check that the commChan was properly initialized before interacting with it.
-      // on unsupported browsers commChan might still be undefined, in which case
-      // we let the dialog display the "unsupported browser" message upon spawning.
-      if (typeof options.loggedInUser !== 'undefined' && commChan) {
-        commChan.notify({
-          method: 'loggedInUser',
-          params: options.loggedInUser
-        });
-      }
+      _open_hidden_iframe();
     }
 
     function internalRequest(options) {