Bug 820170 - Wrapping nodes into documents compartment. r=bholley, r=enn
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1844,16 +1844,23 @@ nsDocument::Init()
return NS_ERROR_ALREADY_INITIALIZED;
}
mIdentifierMap.Init();
mStyledLinks.Init();
mRadioGroups.Init();
mCustomPrototypes.Init();
+ // If after creation the owner js global is not set for a document
+ // we use the default compartment for this document, instead of creating
+ // wrapper in some random compartment when the document is exposed to js
+ // via some events.
+ mScopeObject = do_GetWeakReference(xpc::GetNativeForGlobal(xpc::GetJunkScope()));
+ MOZ_ASSERT(mScopeObject);
+
// Force initialization.
nsINode::nsSlots* slots = Slots();
// Prepend self as mutation-observer whether we need it or not (some
// subclasses currently do, other don't). This is because the code in
// nsNodeUtils always notifies the first observer first, expecting the
// first observer to be the document.
NS_ENSURE_TRUE(slots->mMutationObservers.PrependElementUnlessExists(static_cast<nsIMutationObserver*>(this)),
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -1515,16 +1515,35 @@ XULDocument::GetHeight(int32_t* aHeight)
int32_t
XULDocument::GetHeight(ErrorResult& aRv)
{
int32_t height;
aRv = GetHeight(&height);
return height;
}
+nsIGlobalObject*
+GetScopeObjectOfNode(nsIDOMNode* node)
+{
+ // Window root occasionally keeps alive a node of a document whose
+ // window is already dead. If in this brief period someone calls
+ // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
+ // because it will not know which scope this node belongs to. Returning
+ // an orphan node like that to JS would be a bug anyway, so to avoid
+ // this, let's do the same check as nsNodeSH::PreCreate does to
+ // determine the scope and if it fails let's just return null in
+ // XULDocument::GetPopupNode.
+ nsIDocument* doc = nullptr;
+ for (nsCOMPtr<nsINode> inode = do_QueryInterface(node);
+ !doc && inode; inode = inode->GetParent()) {
+ doc = inode->OwnerDoc();
+ }
+ return doc ? doc->GetScopeObject() : nullptr;
+}
+
//----------------------------------------------------------------------
//
// nsIDOMXULDocument interface
//
NS_IMETHODIMP
XULDocument::GetPopupNode(nsIDOMNode** aNode)
{
@@ -1537,18 +1556,20 @@ XULDocument::GetPopupNode(nsIDOMNode** a
if (!node) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
node = pm->GetLastTriggerPopupNode(this);
}
}
- if (node && nsContentUtils::CanCallerAccess(node))
- node.swap(*aNode);
+ if (node && nsContentUtils::CanCallerAccess(node)
+ && GetScopeObjectOfNode(node)) {
+ node.swap(*aNode);
+ }
return NS_OK;
}
already_AddRefed<nsINode>
XULDocument::GetPopupNode()
{
nsCOMPtr<nsIDOMNode> node;
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -5716,28 +5716,16 @@ nsNodeSH::PreCreate(nsISupports *nativeO
// Make sure that we get the owner document of the content node, in case
// we're in document teardown. If we are, it's important to *not* use
// globalObj as the nodes parent since that would give the node the
// principal of globalObj (i.e. the principal of the document that's being
// loaded) and not the principal of the document that's being unloaded.
// See http://bugzilla.mozilla.org/show_bug.cgi?id=227417
nsIDocument* doc = node->OwnerDoc();
- // If we have a document, make sure one of these is true
- // (1) it has a script handling object,
- // (2) has had one, or has been marked to have had one,
- // (3) we are running a privileged script.
- // Event handling is possible only if (1). If (2) event handling is prevented.
- // If document has never had a script handling object,
- // untrusted scripts (3) shouldn't touch it!
- bool hasHadScriptHandlingObject = false;
- NS_ENSURE_STATE(doc->GetScriptHandlingObject(hasHadScriptHandlingObject) ||
- hasHadScriptHandlingObject ||
- nsContentUtils::IsCallerChrome());
-
nsINode *native_parent;
bool nodeIsElement = node->IsElement();
if (nodeIsElement && node->AsElement()->IsXUL()) {
// For XUL elements, use the parent, if any.
native_parent = node->GetParent();
if (!native_parent) {
@@ -5776,36 +5764,24 @@ nsNodeSH::PreCreate(nsISupports *nativeO
if (form) {
native_parent = form;
}
}
}
}
} else {
// We're called for a document object; set the parent to be the
- // document's global object, if there is one
-
- // Get the scope object from the document.
- nsISupports *scope = doc->GetScopeObject();
-
- if (scope) {
- jsval v;
- nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
- nsresult rv = WrapNative(cx, globalObj, scope, false, &v,
- getter_AddRefs(holder));
- NS_ENSURE_SUCCESS(rv, rv);
-
- holder->GetJSObject(parentObj);
- }
- else {
- // No global object reachable from this document, use the
- // global object that was passed to this method.
-
- *parentObj = globalObj;
- }
+ // document's global object
+
+ // Document should know its global but if the owner window of the
+ // document is already dead at this point, then just throw.
+ nsIGlobalObject* scope = doc->GetScopeObject();
+ NS_ENSURE_TRUE(scope, NS_ERROR_UNEXPECTED);
+
+ *parentObj = scope->GetGlobalJSObject();
// No slim wrappers for a document's scope object.
return node->ChromeOnlyAccess() ?
NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK;
}
// XXXjst: Maybe we need to find the global to use from the
// nsIScriptGlobalObject that's reachable from the node we're about
--- a/js/xpconnect/tests/unit/test_allowedDomainsXHR.js
+++ b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js
@@ -27,46 +27,67 @@ function checkResults(xhr)
do_check_eq(xhr.status, 200);
do_check_eq(xhr.responseText, httpbody);
var root_node = xhr.responseXML.getElementsByTagName('root').item(0);
do_check_eq(root_node.firstChild.data, "0123456789");
return true;
}
+var httpServersClosed = 0;
+function finishIfDone()
+{
+ if (++httpServersClosed == 2)
+ do_test_finished();
+}
+
function run_test()
{
+ do_test_pending();
+
httpserver.registerPathHandler(testpath, serverHandler);
httpserver.start(4444);
httpserver2.registerPathHandler(negativetestpath, serverHandler);
httpserver2.start(4445);
// Test sync XHR sending
cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb);
var res = cu.evalInSandbox('var sync = createXHR("4444/simple"); sync.send(null); sync', sb);
checkResults(res);
- // Test async XHR sending
- var async = cu.evalInSandbox('var async = createXHR("4444/simple", true); async', sb);
- async.addEventListener("readystatechange", function(event) {
- if (checkResults(async))
- httpserver.stop(do_test_finished);
- }, false);
- async.send(null);
-
// negative test sync XHR sending (to ensure that the xhr do not have chrome caps, see bug 779821)
try {
cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb);
var res = cu.evalInSandbox('var sync = createXHR("4445/negative"); sync.send(null); sync', sb);
do_check_false(true, "XHR created from sandbox should not have chrome caps");
} catch (e) {
do_check_true(true);
}
+
+ httpserver2.stop(finishIfDone);
+
+ // Test async XHR sending
+ sb.finish = function(){
+ httpserver.stop(finishIfDone);
+ }
+
+ sb.checkResults = checkResults;
- do_test_pending();
+ sb.do_check_eq = do_check_eq;
+
+ function changeListener(event) {
+ if (checkResults(async))
+ finish();
+ }
+
+ var async = cu.evalInSandbox('var async = createXHR("4444/simple", true);' +
+ 'async.addEventListener("readystatechange", ' +
+ changeListener.toString() + ', false);' +
+ 'async', sb);
+ async.send(null);
}
function serverHandler(metadata, response)
{
response.setHeader("Content-Type", "text/xml", false);
response.bodyOutputStream.write(httpbody, httpbody.length);
}