Fix for bug 637099 (Remove JS_ClearScope calls on windows). r=mrbkap.
authorPeter Van der Beken <peterv@propagandism.org>
Tue, 15 Mar 2011 12:06:39 +0100
changeset 90823 491ceed82be3404994c6f7d25a0c18b0b151344b
parent 90822 a77570b9a808672c89b856560f36b21fa98aa5c7
child 90824 7658ef219e8bc581d62a37fb8a89c9d781d5efd3
push idunknown
push userunknown
push dateunknown
reviewersmrbkap
bugs637099
milestone13.0a1
Fix for bug 637099 (Remove JS_ClearScope calls on windows). r=mrbkap.
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsIScriptContext.h
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/tests/mochitest/bugs/Makefile.in
dom/tests/mochitest/bugs/iframe_bug304459-1.html
dom/tests/mochitest/bugs/iframe_bug304459-2.html
dom/tests/mochitest/bugs/iframe_bug38959-1.html
dom/tests/mochitest/bugs/iframe_bug38959-2.html
dom/tests/mochitest/bugs/test_bug304459.html
dom/tests/mochitest/bugs/test_bug38959.html
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1222,58 +1222,18 @@ nsGlobalWindow::ClearControllers()
       if (context)
         context->SetCommandContext(nsnull);
     }
 
     mControllers = nsnull;
   }
 }
 
-// static
 void
-nsGlobalWindow::TryClearWindowScope(nsISupports *aWindow)
-{
-  nsGlobalWindow *window =
-          static_cast<nsGlobalWindow *>(static_cast<nsIDOMWindow*>(aWindow));
-
-  // This termination function might be called when any script evaluation in our
-  // context terminated, even if there are other scripts in the stack. Thus, we
-  // have to check again if a script is executing and post a new termination
-  // function if necessary.
-  window->ClearScopeWhenAllScriptsStop();
-}
-
-void
-nsGlobalWindow::ClearScopeWhenAllScriptsStop()
-{
-  NS_ASSERTION(IsInnerWindow(), "Must be an inner window");
-
-  // We cannot clear scope safely until all the scripts in our script context
-  // stopped. This might be a long wait, for example if one script is busy
-  // because it started a nested event loop for a modal dialog.
-  nsIScriptContext *jsscx = GetContextInternal();
-  if (jsscx && jsscx->GetExecutingScript()) {
-    // We ignore the return value because the only reason that we clear scope
-    // here is to try to prevent leaks. Failing to clear scope might mean that
-    // we'll leak more but if we don't have enough memory to allocate a
-    // termination function we probably don't have to worry about this anyway.
-    jsscx->SetTerminationFunction(TryClearWindowScope,
-                                  static_cast<nsIDOMWindow *>(this));
-    return;
-  }
-
-  NotifyWindowIDDestroyed("inner-window-destroyed");
-  nsIScriptContext *scx = GetContextInternal();
-  if (scx) {
-    scx->ClearScope(mJSObject, true);
-  }
-}
-
-void
-nsGlobalWindow::FreeInnerObjects(bool aClearScope)
+nsGlobalWindow::FreeInnerObjects()
 {
   NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window");
 
   // Make sure that this is called before we null out the document and
   // other members that the window destroyed observers could
   // re-create.
   NotifyDOMWindowDestroyed(this);
 
@@ -1325,19 +1285,17 @@ nsGlobalWindow::FreeInnerObjects(bool aC
 
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
     mApplicationCache = nsnull;
   }
 
   mIndexedDB = nsnull;
 
-  if (aClearScope) {
-    ClearScopeWhenAllScriptsStop();
-  }
+  NotifyWindowIDDestroyed("inner-window-destroyed");
 
   if (mDummyJavaPluginOwner) {
     // Tear down the dummy java plugin.
 
     // XXXjst: On a general note, should windows with java stuff in
     // them ever even make it into the fast-back cache?
 
     mDummyJavaPluginOwner->Destroy();
@@ -1661,17 +1619,16 @@ nsGlobalWindow::WouldReuseInnerWindow(ns
   }
   
   NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()),
                "How'd this happen?");
   
   // Great, we're the original document, check for one of the other
   // conditions.
   if (mDoc == aNewDocument) {
-    // aClearScopeHint is false.
     return true;
   }
 
   bool equal;
   if (NS_SUCCEEDED(mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal(),
                                                  &equal)) &&
       equal) {
     // The origin is the same.
@@ -1835,20 +1792,18 @@ WindowStateHolder::WindowStateHolder(nsG
 
 WindowStateHolder::~WindowStateHolder()
 {
   if (mInnerWindow) {
     // This window was left in the bfcache and is now going away. We need to
     // free it up.
     // Note that FreeInnerObjects may already have been called on the
     // inner window if its outer has already had SetDocShell(null)
-    // called.  In this case the contexts will all be null and the
-    // true for aClearScope won't do anything; this is OK since
-    // SetDocShell(null) already did it.
-    mInnerWindow->FreeInnerObjects(true);
+    // called.
+    mInnerWindow->FreeInnerObjects();
   }
 }
 
 NS_IMPL_ISUPPORTS1(WindowStateHolder, WindowStateHolder)
 
 
 struct ReparentWaiverClosure
 {
@@ -2001,22 +1956,16 @@ nsGlobalWindow::SetNewDocument(nsIDocume
     return NS_ERROR_FAILURE;
   }
 
   JSAutoRequest ar(cx);
 
   nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState);
   NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?");
 
-  // Make sure to clear scope on the outer window *before* we
-  // initialize the new inner window. If we don't, things
-  // (Object.prototype etc) could leak from the old outer to the new
-  // inner scope.
-  mContext->ClearScope(mJSObject, false);
-
   if (reUseInnerWindow) {
     // We're reusing the current inner window.
     NS_ASSERTION(!currentInner->IsFrozen(),
                  "We should never be reusing a shared inner window");
     newInnerWindow = currentInner;
 
     if (aDocument != oldDoc) {
       nsWindowSH::InvalidateGlobalScopePolluter(cx, currentInner->mJSObject);
@@ -2076,66 +2025,31 @@ nsGlobalWindow::SetNewDocument(nsIDocume
 
       mCreatingInnerWindow = false;
       Thaw();
 
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if (currentInner && currentInner->mJSObject) {
-      bool termFuncSet = false;
-
       if (oldDoc == aDocument) {
         // Move the navigator from the old inner window to the new one since
         // this is a document.write. This is safe from a same-origin point of
         // view because document.write can only be used by the same origin.
         newInnerWindow->mNavigator = currentInner->mNavigator;
         currentInner->mNavigator = nsnull;
         if (newInnerWindow->mNavigator) {
           newInnerWindow->mNavigator->SetWindow(newInnerWindow);
         }
-
-        // Suspend the current context's request before Pop() resumes the old
-        // context's request.
-        JSAutoSuspendRequest asr(cx);
-
-        // Pop our context here so that we get the correct one for the
-        // termination function.
-        cxPusher.Pop();
-
-        JSContext *oldCx = nsContentUtils::GetCurrentJSContext();
-
-        nsIScriptContext *callerScx;
-        if (oldCx && (callerScx = GetScriptContextFromJSContext(oldCx))) {
-          // We're called from document.open() (and document.open() is
-          // called from JS), clear the scope etc in a termination
-          // function on the calling context to prevent clearing the
-          // calling scope.
-          NS_ASSERTION(!currentInner->IsFrozen(),
-              "How does this opened window get into session history");
-
-          JSAutoRequest ar(oldCx);
-
-          callerScx->SetTerminationFunction(ClearWindowScope,
-                                            static_cast<nsIDOMWindow *>
-                                                       (currentInner));
-
-          termFuncSet = true;
-        }
-
-        // Re-push our context.
-        cxPusher.Push(cx);
       }
 
-      // Don't clear scope on our current inner window if it's going to be
+      // Don't free objects on our current inner window if it's going to be
       // held in the bfcache.
       if (!currentInner->IsFrozen()) {
-        // Skip the ClearScope if we set a termination function to do
-        // it ourselves, later.
-        currentInner->FreeInnerObjects(!termFuncSet);
+        currentInner->FreeInnerObjects();
       }
     }
 
     mInnerWindow = newInnerWindow;
 
     if (!mJSObject) {
       mContext->CreateOuterObject(this, newInnerWindow);
       mContext->DidInitializeContext();
@@ -2428,17 +2342,17 @@ nsGlobalWindow::SetDocShell(nsIDocShell*
     // Call FreeInnerObjects on all inner windows, not just the current
     // one, since some could be held by WindowStateHolder objects that
     // are GC-owned.
     for (nsRefPtr<nsGlobalWindow> inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
          inner != this;
          inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
       NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this,
                    "bad outer window pointer");
-      inner->FreeInnerObjects(true);
+      inner->FreeInnerObjects();
     }
 
     // Make sure that this is called before we null out the document.
     NotifyDOMWindowDestroyed(this);
 
     NotifyWindowIDDestroyed("outer-window-destroyed");
 
     nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
@@ -2450,20 +2364,16 @@ nsGlobalWindow::SetDocShell(nsIDocShell*
       mDocumentPrincipal = mDoc->NodePrincipal();
 
       // Release our document reference
       mDocument = nsnull;
       mDoc = nsnull;
       mFocusedNode = nsnull;
     }
 
-    if (mContext) {
-      mContext->ClearScope(mJSObject, true);
-    }
-
     ClearControllers();
 
     mChromeEventHandler = nsnull; // force release now
 
     if (mArguments) { 
       // We got no new document after someone called
       // SetArguments(), drop our reference to the arguments.
       mArguments = nsnull;
@@ -8980,27 +8890,16 @@ nsGlobalWindow::CloseWindow(nsISupports 
                (static_cast<nsPIDOMWindow*>(win));
 
   // Need to post an event for closing, otherwise window and 
   // presshell etc. may get destroyed while creating frames, bug 338897.
   nsCloseEvent::PostCloseEvent(globalWin);
   // else if OOM, better not to close. That might cause a crash.
 }
 
-// static
-void
-nsGlobalWindow::ClearWindowScope(nsISupports *aWindow)
-{
-  nsCOMPtr<nsIScriptGlobalObject> sgo(do_QueryInterface(aWindow));
-  nsIScriptContext *scx = sgo->GetContext();
-  if (scx) {
-    scx->ClearScope(sgo->GetGlobalJSObject(), true);
-  }
-}
-
 //*****************************************************************************
 // nsGlobalWindow: Timeout Functions
 //*****************************************************************************
 
 PRUint32 sNestingLevel;
 
 nsresult
 nsGlobalWindow::SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -590,21 +590,19 @@ private:
 protected:
   friend class HashchangeCallback;
   friend class nsBarProp;
 
   // Object Management
   virtual ~nsGlobalWindow();
   void CleanUp(bool aIgnoreModalDialog);
   void ClearControllers();
-  static void TryClearWindowScope(nsISupports* aWindow);
-  void ClearScopeWhenAllScriptsStop();
   nsresult FinalClose();
 
-  void FreeInnerObjects(bool aClearScope);
+  void FreeInnerObjects();
   nsGlobalWindow *CallerInnerWindow();
 
   nsresult InnerSetNewDocument(nsIDocument* aDocument);
 
   nsresult DefineArgumentsProperty(nsIArray *aArguments);
 
   // Get the parent, returns null if this is a toplevel window
   nsIDOMWindow* GetParentInternal();
@@ -672,17 +670,16 @@ protected:
                                     bool aDoJSFixups,
                                     nsIArray *argv,
                                     nsISupports *aExtraArgument,
                                     nsIPrincipal *aCalleePrincipal,
                                     JSContext *aJSCallerContext,
                                     nsIDOMWindow **aReturn);
 
   static void CloseWindow(nsISupports* aWindow);
-  static void ClearWindowScope(nsISupports* aWindow);
 
   // Timeout Functions
   // Language agnostic timeout function (all args passed).
   // |interval| is in milliseconds.
   nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
                                 PRInt32 interval,
                                 bool aIsInterval, PRInt32 *aReturn);
   nsresult ClearTimeoutOrInterval(PRInt32 aTimerID);
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -70,18 +70,18 @@ public:
 
   virtual nsIScriptObjectPrincipal* GetObjectPrincipal() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptContextPrincipal,
                               NS_ISCRIPTCONTEXTPRINCIPAL_IID)
 
 #define NS_ISCRIPTCONTEXT_IID \
-{ 0xf3840057, 0x4fe5, 0x4f92, \
- { 0xa3, 0xb8, 0x27, 0xd7, 0x44, 0x6f, 0x72, 0x4d } }
+{ 0x6d69fbee, 0x0723, 0x48f5, \
+ { 0x82, 0x48, 0xcd, 0xcf, 0x88, 0xac, 0x25, 0x74 } }
 
 /* This MUST match JSVERSION_DEFAULT.  This version stuff if we don't
    know what language we have is a little silly... */
 #define SCRIPTVERSION_DEFAULT JSVERSION_DEFAULT
 
 /**
  * It is used by the application to initialize a runtime and run scripts.
  * A script runtime would implement this interface.
@@ -423,30 +423,16 @@ public:
    * Initialize DOM classes on aGlobalObj, always call
    * WillInitializeContext() before calling InitContext(), and always
    * call DidInitializeContext() when a context is fully
    * (successfully) initialized.
    */
   virtual nsresult InitClasses(JSObject* aGlobalObj) = 0;
 
   /**
-   * Clear the scope object - may be called either as we are being torn down,
-   * or before we are attached to a different document.
-   *
-   * aClearFromProtoChain is probably somewhat JavaScript specific.  It
-   * indicates that the global scope polluter should be removed from the
-   * prototype chain and that the objects in the prototype chain should
-   * also have their scopes cleared.  We don't do this all the time
-   * because the prototype chain is shared between inner and outer
-   * windows, and needs to stay with inner windows that we're keeping
-   * around.
-   */
-  virtual void ClearScope(void* aGlobalObj, bool aClearFromProtoChain) = 0;
-
-  /**
    * Tell the context we're about to be reinitialize it.
    */
   virtual void WillInitializeContext() = 0;
 
   /**
    * Tell the context we're done reinitializing it.
    */
   virtual void DidInitializeContext() = 0;
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -3011,92 +3011,16 @@ nsJSContext::InitClasses(JSObject* aGlob
 #endif
 
   JSOptionChangedCallback(js_options_dot_str, this);
 
   return rv;
 }
 
 void
-nsJSContext::ClearScope(void *aGlobalObj, bool aClearFromProtoChain)
-{
-  // Push our JSContext on our thread's context stack.
-  nsCOMPtr<nsIJSContextStack> stack =
-    do_GetService("@mozilla.org/js/xpc/ContextStack;1");
-  if (stack && NS_FAILED(stack->Push(mContext))) {
-    stack = nsnull;
-  }
-
-  if (aGlobalObj) {
-    JSObject *obj = (JSObject *)aGlobalObj;
-    JSAutoRequest ar(mContext);
-
-    JSAutoEnterCompartment ac;
-    ac.enterAndIgnoreErrors(mContext, obj);
-
-    // Grab a reference to the window property, which is the outer
-    // window, so that we can re-define it once we've cleared
-    // scope. This is what keeps the outer window alive in cases where
-    // nothing else does.
-    jsval window;
-    if (!JS_GetProperty(mContext, obj, "window", &window)) {
-      window = JSVAL_VOID;
-
-      JS_ClearPendingException(mContext);
-    }
-
-    JS_ClearScope(mContext, obj);
-
-    NS_ABORT_IF_FALSE(!xpc::WrapperFactory::IsXrayWrapper(obj), "unexpected wrapper");
-
-    if (window != JSVAL_VOID) {
-      if (!JS_DefineProperty(mContext, obj, "window", window,
-                             JS_PropertyStub, JS_StrictPropertyStub,
-                             JSPROP_ENUMERATE | JSPROP_READONLY |
-                             JSPROP_PERMANENT)) {
-        JS_ClearPendingException(mContext);
-      }
-    }
-
-    if (!js::GetObjectParent(obj)) {
-      JS_ClearRegExpStatics(mContext, obj);
-    }
-
-    // Always clear watchpoints, to deal with two cases:
-    // 1.  The first document for this window is loading, and a miscreant has
-    //     preset watchpoints on the window object in order to attack the new
-    //     document's privileged information.
-    // 2.  A document loaded and used watchpoints on its own window, leaving
-    //     them set until the next document loads. We must clean up window
-    //     watchpoints here.
-    // Watchpoints set on document and subordinate objects are all cleared
-    // when those sub-window objects are finalized, after JS_ClearScope and
-    // a GC run that finds them to be garbage.
-    ::JS_ClearWatchPointsForObject(mContext, obj);
-
-    // Since the prototype chain is shared between inner and outer (and
-    // stays with the inner), we don't clear things from the prototype
-    // chain when we're clearing an outer window whose current inner we
-    // still want.
-    if (aClearFromProtoChain) {
-      nsWindowSH::InvalidateGlobalScopePolluter(mContext, obj);
-
-      // Clear up obj's prototype chain, but not Object.prototype.
-      for (JSObject *o = ::JS_GetPrototype(obj), *next;
-           o && (next = ::JS_GetPrototype(o)); o = next)
-        ::JS_ClearScope(mContext, o);
-    }
-  }
-
-  if (stack) {
-    stack->Pop(nsnull);
-  }
-}
-
-void
 nsJSContext::WillInitializeContext()
 {
   mIsInitialized = false;
 }
 
 void
 nsJSContext::DidInitializeContext()
 {
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -156,17 +156,16 @@ public:
   virtual bool GetProcessingScriptTag();
   virtual void SetProcessingScriptTag(bool aResult);
 
   virtual bool GetExecutingScript();
 
   virtual void SetGCOnDestruction(bool aGCOnDestruction);
 
   virtual nsresult InitClasses(JSObject* aGlobalObj);
-  virtual void ClearScope(void* aGlobalObj, bool bClearPolluters);
 
   virtual void WillInitializeContext();
   virtual void DidInitializeContext();
 
   virtual nsresult Serialize(nsIObjectOutputStream* aStream, JSScript* aScriptObject);
   virtual nsresult Deserialize(nsIObjectInputStream* aStream,
                                nsScriptObjectHolder<JSScript>& aResult);
 
--- a/dom/tests/mochitest/bugs/Makefile.in
+++ b/dom/tests/mochitest/bugs/Makefile.in
@@ -151,12 +151,18 @@ include $(topsrcdir)/config/rules.mk
 		file_window_bar.html \
 		test_resize_move_windows.html \
 		test_devicemotion_multiple_listeners.html \
 		devicemotion_outer.html \
 		devicemotion_inner.html \
 		test_bug698061.html \
 		test_bug707749.html \
 		test_bug691707.html \
+		test_bug304459.html \
+		iframe_bug304459-1.html \
+		iframe_bug304459-2.html \
+		test_bug38959.html \
+		iframe_bug38959-1.html \
+		iframe_bug38959-2.html \
 		$(NULL)
 
 libs:: 	$(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/iframe_bug304459-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+	<title>Iframe test for bug 304459</title>
+</head>
+<body">
+<script>
+
+Object.prototype.x = "oops";
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/iframe_bug304459-2.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+	<title>Iframe test for bug 304459</title>
+</head>
+<body">
+<script>
+
+var result;
+try {
+  x == undefined;
+} catch (e) {
+  result = true;
+}
+window.parent.postMessage(result, "http://mochi.test:8888");
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/iframe_bug38959-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+	<title>Iframe test for bug 38959</title>
+</head>
+<body">
+<script>
+
+x = false;
+window.opener.postMessage(1, "http://mochi.test:8888");
+window.close();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/iframe_bug38959-2.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+	<title>Iframe test for bug 38959</title>
+</head>
+<body">
+<script>
+
+x = true;
+window.opener.postMessage(2, "http://mochi.test:8888");
+window.close();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_bug304459.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=304459
+-->
+<head>
+  <title>Test for Bug 304459</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=304459">Mozilla Bug 304459</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <iframe id="frame"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 304459 **/
+
+function iframeLoaded()
+{
+  var frame = document.getElementById("frame");
+  frame.onload = undefined;
+  frame.src = "http://example.org/tests/dom/tests/mochitest/bugs/iframe_bug304459-2.html";
+}
+
+function receiveMessage(evt)
+{
+  ok(evt.data, "Prototype leaks across navigation");
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage, false);
+
+var frame = document.getElementById("frame");
+frame.onload = iframeLoaded;
+frame.src = "iframe_bug304459-1.html"
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_bug38959.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=38959
+-->
+<head>
+  <title>Test for Bug 38959</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=38959">Mozilla Bug 38959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <iframe id="frame"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 38959 **/
+
+var newValue;
+
+function watcher(id, ol, ne)
+{
+  newValue = ne;
+  return ne;
+}
+
+function openWindow(url, crossOrigin)
+{
+  newValue = true;
+  var w = window.open(url);
+  w.watch("x", watcher);
+}
+
+function receiveMessage(evt)
+{
+  ok(newValue, "Watchpoints only allowed same-origin.");
+  if (evt.data == 1) {
+    openWindow("/tests/dom/tests/mochitest/bugs/iframe_bug38959-2.html");
+  }
+  else {
+    SimpleTest.finish();
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage, false);
+
+openWindow("http://example.org/tests/dom/tests/mochitest/bugs/iframe_bug38959-1.html");
+
+</script>
+</pre>
+</body>
+</html>