Bug 1480206 - Move XULDocument popup attributes to Document. r?bz
Allows top level non-XUL documents to share this code. Three tests had to
be adjusted to account for the attributes being chrome only now and not
available to content privilege XUL. In two tests, the values attributes
are now simply undefined. The crashtest was converted to a chrome
mochitest to preserve what it was testing.
MozReview-Commit-ID: 99w9Ax4et3C
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -69,17 +69,16 @@ load 439206-1.html
load 443538-1.svg
load 448615-1.html
load 450383-1.html
load 450385-1.html
skip load 458637-1.html # sporadically times out (bug 473680)
load 462947.html
load 467392.html
load 472593-1.html
-load 473284.xul
load 474041-1.svg
load 476526.html
load 483818-1.html
load 490760-1.xhtml
load 493281-1.html
load 493281-2.html
load 494810-1.html
load 499006-1.html
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -254,16 +254,17 @@
#include "mozilla/dom/SVGDocument.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/TabGroup.h"
#ifdef MOZ_XUL
#include "mozilla/dom/MenuBoxObject.h"
#include "mozilla/dom/TreeBoxObject.h"
#include "nsIXULWindow.h"
+#include "nsXULPopupManager.h"
#include "nsIDocShellTreeOwner.h"
#endif
#include "nsIPresShellInlines.h"
#include "mozilla/DocLoadingTimelineMarker.h"
#include "nsISpeculativeConnect.h"
@@ -10169,16 +10170,122 @@ nsIDocument::MaybeResolveReadyForIdle()
{
IgnoredErrorResult rv;
Promise* readyPromise = GetDocumentReadyForIdle(rv);
if (readyPromise) {
readyPromise->MaybeResolve(this);
}
}
+static JSObject*
+GetScopeObjectOfNode(nsINode* node)
+{
+ MOZ_ASSERT(node, "Must not be called with null.");
+
+ // 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, we can end up creating a
+ // reflector for the node in the wrong global (the current global,
+ // not the document global, because we won't know what the document
+ // global is). Returning an orphan node like that to JS would be a
+ // bug anyway, so to avoid this, let's do the same check as fetching
+ // GetParentObjet() on the document does to determine the scope and
+ // if it returns null let's just return null in XULDocument::GetPopupNode.
+ nsIDocument* doc = node->OwnerDoc();
+ MOZ_ASSERT(doc, "This should never happen.");
+
+ nsIGlobalObject* global = doc->GetScopeObject();
+ return global ? global->GetGlobalJSObject() : nullptr;
+}
+
+
+already_AddRefed<nsPIWindowRoot>
+nsIDocument::GetWindowRoot()
+{
+ if (!mDocumentContainer) {
+ return nullptr;
+ }
+ // XXX It's unclear why this can't just use GetWindow().
+ nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
+ return piWin ? piWin->GetTopWindowRoot() : nullptr;
+}
+
+already_AddRefed<nsINode>
+nsIDocument::GetPopupNode()
+{
+ nsCOMPtr<nsINode> node;
+ nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
+ if (rootWin) {
+ node = rootWin->GetPopupNode(); // addref happens here
+ }
+
+ if (!node) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ node = pm->GetLastTriggerPopupNode(this);
+ }
+ }
+
+ if (node && GetScopeObjectOfNode(node)) {
+ return node.forget();
+ }
+
+ return nullptr;
+}
+
+void
+nsIDocument::SetPopupNode(nsINode* aNode)
+{
+ nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
+ if (rootWin) {
+ rootWin->SetPopupNode(aNode);
+ }
+}
+
+// Returns the rangeOffset element from the XUL Popup Manager. This is for
+// chrome callers only.
+nsINode*
+nsIDocument::GetPopupRangeParent(ErrorResult& aRv)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return pm->GetMouseLocationParent();
+}
+
+// Returns the rangeOffset element from the XUL Popup Manager.
+int32_t
+nsIDocument::GetPopupRangeOffset(ErrorResult& aRv)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ return pm->MouseLocationOffset();
+}
+
+already_AddRefed<nsINode>
+nsIDocument::GetTooltipNode()
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsCOMPtr<nsINode> node = pm->GetLastTriggerTooltipNode(this);
+ if (node) {
+ return node.forget();
+ }
+ }
+
+ return nullptr;
+}
+
nsIHTMLCollection*
nsIDocument::Children()
{
if (!mChildrenCollection) {
mChildrenCollection = new nsContentList(this, kNameSpaceID_Wildcard,
nsGkAtoms::_asterisk,
nsGkAtoms::_asterisk,
false);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1378,16 +1378,21 @@ protected:
mPageUnloadingEventTimeStamp = mozilla::TimeStamp();
}
/**
* Clears any Servo element data stored on Elements in the document.
*/
void ClearStaleServoData();
+ /**
+ * Returns the top window root from the outer window.
+ */
+ already_AddRefed<nsPIWindowRoot> GetWindowRoot();
+
private:
class SelectorCacheKey
{
public:
explicit SelectorCacheKey(const nsAString& aString) : mKey(aString)
{
MOZ_COUNT_CTOR(SelectorCacheKey);
}
@@ -3331,16 +3336,23 @@ public:
already_AddRefed<mozilla::dom::Promise> BlockParsing(mozilla::dom::Promise& aPromise,
const mozilla::dom::BlockParsingOptions& aOptions,
mozilla::ErrorResult& aRv);
already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
mozilla::dom::Promise* GetDocumentReadyForIdle(mozilla::ErrorResult& aRv);
+ already_AddRefed<nsINode> GetPopupNode();
+ void SetPopupNode(nsINode* aNode);
+ nsINode* GetPopupRangeParent(ErrorResult& aRv);
+ int32_t GetPopupRangeOffset(ErrorResult& aRv);
+ already_AddRefed<nsINode> GetTooltipNode();
+ void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
+
// ParentNode
nsIHTMLCollection* Children();
uint32_t ChildElementCount();
/**
* Asserts IsHTMLOrXHTML, and can't return null.
* Defined inline in nsHTMLDocument.h
*/
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -34,16 +34,17 @@ support-files = ../file_bug357450.js
[test_bug380418.html]
[test_bug380418.html^headers^]
[test_bug383430.html]
[test_bug418986-1.xul]
[test_bug421622.xul]
[test_bug429785.xul]
[test_bug430050.xul]
[test_bug467123.xul]
+[test_bug473284.xul]
[test_bug549682.xul]
skip-if = verify
[test_bug571390.xul]
[test_bug1098074_throw_from_ReceiveMessage.xul]
[test_bug616841.xul]
[test_bug635835.xul]
[test_bug682305.html]
[test_bug683852.xul]
rename from dom/base/crashtests/473284.xul
rename to dom/base/test/chrome/test_bug473284.xul
--- a/dom/base/crashtests/473284.xul
+++ b/dom/base/test/chrome/test_bug473284.xul
@@ -1,10 +1,16 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473284
+-->
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- class="reftest-wait"
onload="
var result = '';
try {
document.commandDispatcher.advanceFocusIntoSubtree({});
result += '1';
} catch (ex) {
result += '.';
}
@@ -81,13 +87,19 @@ try {
try {
document.commandDispatcher.focusedWindow = window;
result += 'c';
} catch (ex) {
result += '.';
}
-document.documentElement.textContent = result == '.23.56.89.bc' ? 'PASSED' : 'FAILED';
-if (document.documentElement.textContent == 'PASSED') {
- document.documentElement.removeAttribute('class');
-}
-"/>
+is(result, '.23.56.89.bc', 'The correct assignments throw.');
+">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=473284"
+ target="_blank">Mozilla Bug 473284</a>
+ </body>
+</window>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -401,16 +401,31 @@ partial interface Document {
// trying to load when we hit an error, rather than the error page's own URI.
[ChromeOnly] readonly attribute URI? mozDocumentURIIfNotForErrorPages;
// A promise that is resolved, with this document itself, when we have both
// fired DOMContentLoaded and are ready to start layout. This is used for the
// "document_idle" webextension script injection point.
[ChromeOnly, Throws]
readonly attribute Promise<Document> documentReadyForIdle;
+
+ [ChromeOnly]
+ attribute Node? popupNode;
+
+ /**
+ * These attributes correspond to rangeParent and rangeOffset. They will help
+ * you find where in the DOM the popup is happening. Can be accessed only
+ * during a popup event. Accessing any other time will be an error.
+ */
+ [Throws, ChromeOnly]
+ readonly attribute Node? popupRangeParent;
+ [Throws, ChromeOnly]
+ readonly attribute long popupRangeOffset;
+ [ChromeOnly]
+ attribute Node? tooltipNode;
};
dictionary BlockParsingOptions {
/**
* If true, blocks script-created parsers (created via document.open()) in
* addition to network-created parsers.
*/
boolean blockScriptCreated = true;
--- a/dom/webidl/XULDocument.webidl
+++ b/dom/webidl/XULDocument.webidl
@@ -4,30 +4,16 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
interface XULCommandDispatcher;
interface MozObserver;
[Func="IsChromeOrXBL"]
interface XULDocument : Document {
- attribute Node? popupNode;
-
- /**
- * These attributes correspond to trustedGetPopupNode().rangeOffset and
- * rangeParent. They will help you find where in the DOM the popup is
- * happening. Can be accessed only during a popup event. Accessing any other
- * time will be an error.
- */
- [Throws, ChromeOnly]
- readonly attribute Node? popupRangeParent;
- [Throws, ChromeOnly]
- readonly attribute long popupRangeOffset;
-
- attribute Node? tooltipNode;
readonly attribute XULCommandDispatcher? commandDispatcher;
[Throws]
void addBroadcastListenerFor(Element broadcaster, Element observer,
DOMString attr);
void removeBroadcastListenerFor(Element broadcaster, Element observer,
DOMString attr);
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -998,123 +998,16 @@ XULDocument::Persist(Element* aElement,
if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) {
return;
}
}
mLocalStore->SetValue(uri, id, attrstr, valuestr);
}
-static JSObject*
-GetScopeObjectOfNode(nsINode* node)
-{
- MOZ_ASSERT(node, "Must not be called with null.");
-
- // 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 = node->OwnerDoc();
- MOZ_ASSERT(doc, "This should never happen.");
-
- nsIGlobalObject* global = doc->GetScopeObject();
- return global ? global->GetGlobalJSObject() : nullptr;
-}
-
-already_AddRefed<nsINode>
-XULDocument::GetPopupNode()
-{
- nsCOMPtr<nsINode> node;
- nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
- if (rootWin) {
- node = rootWin->GetPopupNode(); // addref happens here
- }
-
- if (!node) {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm) {
- node = pm->GetLastTriggerPopupNode(this);
- }
- }
-
- if (node && nsContentUtils::CanCallerAccess(node)
- && GetScopeObjectOfNode(node)) {
- return node.forget();
- }
-
- return nullptr;
-}
-
-void
-XULDocument::SetPopupNode(nsINode* aNode)
-{
- nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
- if (rootWin) {
- rootWin->SetPopupNode(aNode);
- }
-}
-
-// Returns the rangeOffset element from the XUL Popup Manager. This is for
-// chrome callers only.
-nsINode*
-XULDocument::GetPopupRangeParent(ErrorResult& aRv)
-{
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (!pm) {
- aRv.Throw(NS_ERROR_FAILURE);
- return nullptr;
- }
-
- nsINode* rangeParent = pm->GetMouseLocationParent();
- if (rangeParent && !nsContentUtils::CanCallerAccess(rangeParent)) {
- aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
- return nullptr;
- }
-
- return rangeParent;
-}
-
-// Returns the rangeOffset element from the XUL Popup Manager. We check the
-// rangeParent to determine if the caller has rights to access to the data.
-int32_t
-XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
-{
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (!pm) {
- aRv.Throw(NS_ERROR_FAILURE);
- return 0;
- }
-
- nsINode* rangeParent = pm->GetMouseLocationParent();
- if (rangeParent && !nsContentUtils::CanCallerAccess(rangeParent)) {
- aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
- return 0;
- }
-
- return pm->MouseLocationOffset();
-}
-
-already_AddRefed<nsINode>
-XULDocument::GetTooltipNode()
-{
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm) {
- nsCOMPtr<nsINode> node = pm->GetLastTriggerTooltipNode(this);
- if (node && nsContentUtils::CanCallerAccess(node)) {
- return node.forget();
- }
- }
-
- return nullptr;
-}
-
nsresult
XULDocument::AddElementToDocumentPre(Element* aElement)
{
// Do a bunch of work that's necessary when an element gets added
// to the XUL Document.
nsresult rv;
// 1. Add the element to the id map, since it seems this can be
@@ -2712,27 +2605,16 @@ XULDocument::CachedChromeStreamListener:
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount)
{
MOZ_ASSERT_UNREACHABLE("CachedChromeStream doesn't receive data");
return NS_ERROR_UNEXPECTED;
}
-already_AddRefed<nsPIWindowRoot>
-XULDocument::GetWindowRoot()
-{
- if (!mDocumentContainer) {
- return nullptr;
- }
-
- nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
- return piWin ? piWin->GetTopWindowRoot() : nullptr;
-}
-
bool
XULDocument::IsDocumentRightToLeft()
{
// setting the localedir attribute on the root element forces a
// specific direction for the document.
Element* element = GetRootElement();
if (element) {
static Element::AttrValuesArray strings[] =
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -132,22 +132,16 @@ public:
NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) override;
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
void TraceProtos(JSTracer* aTrc);
// WebIDL API
- already_AddRefed<nsINode> GetPopupNode();
- void SetPopupNode(nsINode* aNode);
- nsINode* GetPopupRangeParent(ErrorResult& aRv);
- int32_t GetPopupRangeOffset(ErrorResult& aRv);
- already_AddRefed<nsINode> GetTooltipNode();
- void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
nsIDOMXULCommandDispatcher* GetCommandDispatcher() const
{
return mCommandDispatcher;
}
void AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
const nsAString& aAttr, ErrorResult& aRv);
void RemoveBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
const nsAString& aAttr);
@@ -185,18 +179,16 @@ protected:
nsresult
AddElementToDocumentPost(Element* aElement);
nsresult
ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
Element* aListener,
nsAtom* aAttr);
- already_AddRefed<nsPIWindowRoot> GetWindowRoot();
-
static void DirectionChanged(const char* aPrefName, XULDocument* aData);
// pseudo constants
static int32_t gRefCnt;
static LazyLogModule gXULLog;
void
--- a/toolkit/content/tests/chrome/popup_trigger.js
+++ b/toolkit/content/tests/chrome/popup_trigger.js
@@ -448,17 +448,17 @@ var popupTests = [
var childframe = document.getElementById("childframe");
if (childframe) {
for (var t = 0; t < 2; t++) {
var child = childframe.contentDocument;
var evt = child.createEvent("Event");
evt.initEvent("click", true, true);
child.documentElement.dispatchEvent(evt);
- is(child.documentElement.getAttribute("data"), "xnull",
+ is(child.documentElement.getAttribute("data"), "xundefined",
"cannot get popupNode from other document");
child.documentElement.setAttribute("data", "none");
// now try again with document.popupNode set explicitly
document.popupNode = gCachedEvent.target;
}
}
var openX = 8;
--- a/toolkit/content/tests/chrome/window_tooltip.xul
+++ b/toolkit/content/tests/chrome/window_tooltip.xul
@@ -108,17 +108,17 @@ var popupTests = [
is(tooltip.triggerNode, gButton, testname + " triggerNode");
is(document.popupNode, null, testname + " document.popupNode");
is(document.tooltipNode, gButton, testname + " document.tooltipNode");
var child = $("childframe").contentDocument;
var evt = child.createEvent("Event");
evt.initEvent("click", true, true);
child.documentElement.dispatchEvent(evt);
- is(child.documentElement.getAttribute("data"), "xnull",
+ is(child.documentElement.getAttribute("data"), "xundefined",
"cannot get tooltipNode from other document");
var buttonrect = document.getElementById("withtooltip").getBoundingClientRect();
var rect = tooltip.getBoundingClientRect();
var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"));
is(Math.round(rect.left),
Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6),