--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -253,16 +253,17 @@
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#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/XULBroadcastManager.h"
#include "mozilla/dom/TreeBoxObject.h"
#include "nsIXULWindow.h"
#include "nsXULCommandDispatcher.h"
#include "nsXULPopupManager.h"
#include "nsIDocShellTreeOwner.h"
#endif
#include "nsIPresShellInlines.h"
@@ -1740,16 +1741,20 @@ nsDocument::~nsDocument()
// Could be null here if Init() failed or if we have been unlinked.
mCSSLoader->DropDocumentReference();
}
if (mStyleImageLoader) {
mStyleImageLoader->DropDocumentReference();
}
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->DropDocumentReference();
+ }
+
delete mHeaderData;
ClearAllBoxObjects();
mPendingTitleChangeEvent.Revoke();
mPlugins.Clear();
}
@@ -5108,16 +5113,19 @@ nsDocument::EndUpdate()
--mUpdateNestLevel;
// This set of updates may have created XBL bindings. Let the
// binding manager know we're done.
MaybeEndOutermostXBLUpdate();
MaybeInitializeFinalizeFrameLoaders();
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->MaybeBroadcast();
+ }
}
void
nsDocument::BeginLoad()
{
MOZ_ASSERT(!mDidCallBeginLoad);
mDidCallBeginLoad = true;
@@ -10242,16 +10250,25 @@ nsIDocument::GetCommandDispatcher()
}
if (!mCommandDispatcher) {
// Create our command dispatcher and hook it up.
mCommandDispatcher = new nsXULCommandDispatcher(this);
}
return mCommandDispatcher;
}
+void
+nsIDocument::InitializeXULBroadcastManager()
+{
+ if (mXULBroadcastManager) {
+ return;
+ }
+ mXULBroadcastManager = new XULBroadcastManager(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
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -143,16 +143,17 @@ class ImageLoader;
class Rule;
} // namespace css
namespace dom {
class Animation;
class AnonymousContent;
class Attr;
class BoxObject;
+class XULBroadcastManager;
class ClientInfo;
class ClientState;
class CDATASection;
class Comment;
struct CustomElementDefinition;
class DocGroup;
class DocumentL10n;
class DocumentFragment;
@@ -3437,16 +3438,25 @@ public:
const mozilla::dom::BlockParsingOptions& aOptions,
mozilla::ErrorResult& aRv);
already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
mozilla::dom::Promise* GetDocumentReadyForIdle(mozilla::ErrorResult& aRv);
nsIDOMXULCommandDispatcher* GetCommandDispatcher();
+ bool HasXULBroadcastManager() const
+ {
+ return mXULBroadcastManager;
+ };
+ void InitializeXULBroadcastManager();
+ mozilla::dom::XULBroadcastManager* GetXULBroadcastManager() const
+ {
+ return mXULBroadcastManager;
+ }
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
@@ -4734,16 +4744,18 @@ protected:
// document.close(), and document.write() when they are invoked by the parser.
uint32_t mThrowOnDynamicMarkupInsertionCounter;
// Count of unload/beforeunload/pagehide operations in progress.
uint32_t mIgnoreOpensDuringUnloadCounter;
nsCOMPtr<nsIDOMXULCommandDispatcher> mCommandDispatcher; // [OWNER] of the focus tracker
+ RefPtr<mozilla::dom::XULBroadcastManager> mXULBroadcastManager;
+
// At the moment, trackers might be blocked by Tracking Protection or FastBlock.
// In order to know the numbers of trackers detected and blocked, we add
// these two values here and those are shared by TP and FB.
uint32_t mNumTrackersFound;
uint32_t mNumTrackersBlocked;
mozilla::EnumSet<mozilla::Telemetry::LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED>
mTrackerBlockedReasons;
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.cpp
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XULBroadcastManager.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsXULElement.h"
+#include "mozilla/Logging.h"
+
+
+struct BroadcastListener {
+ nsWeakPtr mListener;
+ RefPtr<nsAtom> mAttribute;
+};
+
+struct BroadcasterMapEntry : public PLDHashEntryHdr
+{
+ Element* mBroadcaster; // [WEAK]
+ nsTArray<BroadcastListener*> mListeners; // [OWNING] of BroadcastListener objects
+};
+
+struct nsAttrNameInfo
+{
+ nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) :
+ mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
+ nsAttrNameInfo(const nsAttrNameInfo& aOther) :
+ mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
+ mPrefix(aOther.mPrefix) {}
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mName;
+ RefPtr<nsAtom> mPrefix;
+};
+
+static void
+ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ BroadcasterMapEntry* entry =
+ static_cast<BroadcasterMapEntry*>(aEntry);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ delete entry->mListeners[i];
+ }
+ entry->mListeners.Clear();
+
+ // N.B. that we need to manually run the dtor because we
+ // constructed the nsTArray object in-place.
+ entry->mListeners.~nsTArray<BroadcastListener*>();
+}
+
+static bool
+CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
+{
+ // Don't push changes to the |id|, |persist|, |command| or
+ // |observes| attribute.
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if ((aAttribute == nsGkAtoms::id) ||
+ (aAttribute == nsGkAtoms::persist) ||
+ (aAttribute == nsGkAtoms::command) ||
+ (aAttribute == nsGkAtoms::observes)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+namespace mozilla {
+namespace dom {
+static LazyLogModule sXULBroadCastManager("XULBroadcastManager");
+
+/* static */
+bool
+XULBroadcastManager::MayNeedListener(const Element& aElement) {
+ if (aElement.NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+ return true;
+ }
+ if (aElement.HasAttr(nsGkAtoms::observes)) {
+ return true;
+ }
+ if (aElement.HasAttr(nsGkAtoms::command) &&
+ !(aElement.NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+ aElement.NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL))) {
+ return true;
+ }
+ return false;
+}
+
+XULBroadcastManager::XULBroadcastManager(nsIDocument* aDocument)
+ : mDocument(aDocument),
+ mBroadcasterMap(nullptr),
+ mHandlingDelayedAttrChange(false),
+ mHandlingDelayedBroadcasters(false)
+{
+}
+
+XULBroadcastManager::~XULBroadcastManager()
+{
+ delete mBroadcasterMap;
+}
+
+void
+XULBroadcastManager::DropDocumentReference(void)
+{
+ mDocument = nullptr;
+}
+
+void
+XULBroadcastManager::SynchronizeBroadcastListener(Element *aBroadcaster,
+ Element *aListener,
+ const nsAString &aAttr)
+{
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
+ aAttr);
+ mDelayedBroadcasters.AppendElement(delayedUpdate);
+ MaybeBroadcast();
+ return;
+ }
+ bool notify = mHandlingDelayedBroadcasters;
+
+ if (aAttr.EqualsLiteral("*")) {
+ uint32_t count = aBroadcaster->GetAttrCount();
+ nsTArray<nsAttrNameInfo> attributes(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
+ int32_t nameSpaceID = attrName->NamespaceID();
+ nsAtom* name = attrName->LocalName();
+
+ // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
+ if (! CanBroadcast(nameSpaceID, name))
+ continue;
+
+ attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
+ attrName->GetPrefix()));
+ }
+
+ count = attributes.Length();
+ while (count-- > 0) {
+ int32_t nameSpaceID = attributes[count].mNamespaceID;
+ nsAtom* name = attributes[count].mName;
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
+ aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
+ value, notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during
+ // initial hookup: doing so would potentially run the
+ // |onbroadcast| handler before the |onload| handler,
+ // which could define JS properties that mask XBL
+ // properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+ }
+ else {
+ // Find out if the attribute is even present at all.
+ RefPtr<nsAtom> name = NS_Atomize(aAttr);
+
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
+ aListener->SetAttr(kNameSpaceID_None, name, value, notify);
+ } else {
+ aListener->UnsetAttr(kNameSpaceID_None, name, notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during initial
+ // hookup: doing so would potentially run the |onbroadcast|
+ // handler before the |onload| handler, which could define JS
+ // properties that mask XBL properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+}
+
+void
+XULBroadcastManager::AddListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr, ErrorResult& aRv)
+{
+ if (!mDocument) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsresult rv =
+ nsContentUtils::CheckSameOrigin(mDocument, &aBroadcaster);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = nsContentUtils::CheckSameOrigin(mDocument, &aListener);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ static const PLDHashTableOps gOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub,
+ ClearBroadcasterMapEntry,
+ nullptr
+ };
+
+ if (! mBroadcasterMap) {
+ mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
+ }
+
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(&aBroadcaster));
+ if (!entry) {
+ entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Add(&aBroadcaster, fallible));
+
+ if (! entry) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ entry->mBroadcaster = &aBroadcaster;
+
+ // N.B. placement new to construct the nsTArray object in-place
+ new (&entry->mListeners) nsTArray<BroadcastListener*>();
+ }
+
+ // Only add the listener if it's not there already!
+ RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr)
+ return;
+ }
+
+ BroadcastListener* bl = new BroadcastListener;
+ bl->mListener = do_GetWeakReference(&aListener);
+ bl->mAttribute = attr;
+
+ entry->mListeners.AppendElement(bl);
+
+ SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
+}
+
+void
+XULBroadcastManager::RemoveListenerFor(Element& aBroadcaster,
+ Element& aListener,
+ const nsAString& aAttr)
+{
+ // If we haven't added any broadcast listeners, then there sure
+ // aren't any to remove.
+ if (! mBroadcasterMap)
+ return;
+
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(&aBroadcaster));
+ if (entry) {
+ RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr) {
+ entry->mListeners.RemoveElementAt(i);
+ delete bl;
+
+ if (entry->mListeners.IsEmpty())
+ mBroadcasterMap->RemoveEntry(entry);
+
+ break;
+ }
+ }
+ }
+}
+
+nsresult
+XULBroadcastManager::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+ Element* aListener,
+ nsAtom* aAttr)
+{
+ if (!mDocument) {
+ return NS_OK;
+ }
+ // Now we execute the onchange handler in the context of the
+ // observer. We need to find the observer in order to
+ // execute the handler.
+
+ for (nsIContent* child = aListener->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ // Look for an <observes> element beneath the listener. This
+ // ought to have an |element| attribute that refers to
+ // aBroadcaster, and an |attribute| element that tells us what
+ // attriubtes we're listening for.
+ if (!child->IsXULElement(nsGkAtoms::observes))
+ continue;
+
+ // Is this the element that was listening to us?
+ nsAutoString listeningToID;
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
+
+ nsAutoString broadcasterID;
+ aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
+
+ if (listeningToID != broadcasterID)
+ continue;
+
+ // We are observing the broadcaster, but is this the right
+ // attribute?
+ nsAutoString listeningToAttribute;
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
+ listeningToAttribute);
+
+ if (!aAttr->Equals(listeningToAttribute) &&
+ !listeningToAttribute.EqualsLiteral("*")) {
+ continue;
+ }
+
+ // This is the right <observes> element. Execute the
+ // |onbroadcast| event handler
+ WidgetEvent event(true, eXULBroadcast);
+
+ RefPtr<nsPresContext> presContext = mDocument->GetPresContext();
+ if (presContext) {
+ // Handle the DOM event
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(child, presContext, &event, nullptr,
+ &status);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+XULBroadcastManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute)
+{
+ if (!mDocument) {
+ return;
+ }
+ NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
+
+ // Synchronize broadcast listeners
+ if (mBroadcasterMap &&
+ CanBroadcast(aNameSpaceID, aAttribute)) {
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(aElement));
+
+ if (entry) {
+ // We've got listeners: push the value.
+ nsAutoString value;
+ bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ if ((bl->mAttribute == aAttribute) ||
+ (bl->mAttribute == nsGkAtoms::_asterisk)) {
+ nsCOMPtr<Element> listenerEl
+ = do_QueryReferent(bl->mListener);
+ if (listenerEl) {
+ nsAutoString currentValue;
+ bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
+ aAttribute,
+ currentValue);
+ // We need to update listener only if we're
+ // (1) removing an existing attribute,
+ // (2) adding a new attribute or
+ // (3) changing the value of an attribute.
+ bool needsAttrChange =
+ attrSet != hasAttr || !value.Equals(currentValue);
+ nsDelayedBroadcastUpdate delayedUpdate(aElement,
+ listenerEl,
+ aAttribute,
+ value,
+ attrSet,
+ needsAttrChange);
+
+ size_t index =
+ mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
+ 0, nsDelayedBroadcastUpdate::Comparator());
+ if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
+ if (mHandlingDelayedAttrChange) {
+ NS_WARNING("Broadcasting loop!");
+ continue;
+ }
+ mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
+ }
+
+ mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+XULBroadcastManager::MaybeBroadcast()
+{
+ // Only broadcast when not in an update and when safe to run scripts.
+ if (mDocument && mDocument->UpdateNestingLevel() == 0 &&
+ (mDelayedAttrChangeBroadcasts.Length() ||
+ mDelayedBroadcasters.Length())) {
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ if (mDocument) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("dom::XULBroadcastManager::MaybeBroadcast",
+ this,
+ &XULBroadcastManager::MaybeBroadcast));
+ }
+ return;
+ }
+ if (!mHandlingDelayedAttrChange) {
+ mHandlingDelayedAttrChange = true;
+ for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
+ nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
+ if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
+ nsCOMPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
+ const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
+ if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
+ listener->SetAttr(kNameSpaceID_None, attrName, value,
+ true);
+ } else {
+ listener->UnsetAttr(kNameSpaceID_None, attrName,
+ true);
+ }
+ }
+ ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
+ mDelayedAttrChangeBroadcasts[i].mListener,
+ attrName);
+ }
+ mDelayedAttrChangeBroadcasts.Clear();
+ mHandlingDelayedAttrChange = false;
+ }
+
+ uint32_t length = mDelayedBroadcasters.Length();
+ if (length) {
+ bool oldValue = mHandlingDelayedBroadcasters;
+ mHandlingDelayedBroadcasters = true;
+ nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
+ mDelayedBroadcasters.SwapElements(delayedBroadcasters);
+ for (uint32_t i = 0; i < length; ++i) {
+ SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
+ delayedBroadcasters[i].mListener,
+ delayedBroadcasters[i].mAttr);
+ }
+ mHandlingDelayedBroadcasters = oldValue;
+ }
+ }
+}
+
+nsresult
+XULBroadcastManager::FindBroadcaster(Element* aElement,
+ Element** aListener,
+ nsString& aBroadcasterID,
+ nsString& aAttribute,
+ Element** aBroadcaster)
+{
+ NodeInfo *ni = aElement->NodeInfo();
+ *aListener = nullptr;
+ *aBroadcaster = nullptr;
+
+ if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+ // It's an <observes> element, which means that the actual
+ // listener is the _parent_ node. This element should have an
+ // 'element' attribute that specifies the ID of the
+ // broadcaster element, and an 'attribute' element, which
+ // specifies the name of the attribute to observe.
+ nsIContent* parent = aElement->GetParent();
+ if (!parent) {
+ // <observes> is the root element
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+
+ *aListener = Element::FromNode(parent);
+ NS_IF_ADDREF(*aListener);
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
+ if (aBroadcasterID.IsEmpty()) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
+ }
+ else {
+ // It's a generic element, which means that we'll use the
+ // value of the 'observes' attribute to determine the ID of
+ // the broadcaster element, and we'll watch _all_ of its
+ // values.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
+
+ // Bail if there's no aBroadcasterID
+ if (aBroadcasterID.IsEmpty()) {
+ // Try the command attribute next.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
+ if (!aBroadcasterID.IsEmpty()) {
+ // We've got something in the command attribute. We
+ // only treat this as a normal broadcaster if we are
+ // not a menuitem or a key.
+
+ if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ }
+ else {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ }
+
+ *aListener = aElement;
+ NS_ADDREF(*aListener);
+
+ aAttribute.Assign('*');
+ }
+
+ // Make sure we got a valid listener.
+ NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
+
+ // Try to find the broadcaster element in the document.
+ nsIDocument* doc = aElement->GetComposedDoc();
+ if (doc) {
+ *aBroadcaster = doc->GetElementById(aBroadcasterID);
+ }
+
+ // The broadcaster element is missing.
+ if (! *aBroadcaster) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+
+ NS_ADDREF(*aBroadcaster);
+
+ return NS_FINDBROADCASTER_FOUND;
+}
+
+nsresult
+XULBroadcastManager::UpdateListenerHookup(Element* aElement, HookupAction aAction)
+{
+ // Resolve a broadcaster hookup. Look at the element that we're
+ // trying to resolve: it could be an '<observes>' element, or just
+ // a vanilla element with an 'observes' attribute on it.
+ nsresult rv;
+
+ nsCOMPtr<Element> listener;
+ nsAutoString broadcasterID;
+ nsAutoString attribute;
+ nsCOMPtr<Element> broadcaster;
+
+ rv = FindBroadcaster(aElement, getter_AddRefs(listener),
+ broadcasterID, attribute, getter_AddRefs(broadcaster));
+ switch (rv) {
+ case NS_FINDBROADCASTER_NOT_FOUND:
+ return NS_OK;
+ case NS_FINDBROADCASTER_FOUND:
+ break;
+ default:
+ return rv;
+ }
+
+ NS_ENSURE_ARG(broadcaster && listener);
+ if (aAction == eHookupAdd) {
+ ErrorResult domRv;
+ AddListenerFor(*broadcaster, *listener, attribute, domRv);
+ if (domRv.Failed()) {
+ return domRv.StealNSResult();
+ }
+ } else {
+ RemoveListenerFor(*broadcaster, *listener, attribute);
+ }
+
+ // Tell the world we succeeded
+ if (MOZ_LOG_TEST(sXULBroadCastManager, LogLevel::Debug)) {
+ nsCOMPtr<nsIContent> content = listener;
+ NS_ASSERTION(content != nullptr, "not an nsIContent");
+ if (!content) {
+ return rv;
+ }
+
+ nsAutoCString attributeC,broadcasteridC;
+ LossyCopyUTF16toASCII(attribute, attributeC);
+ LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
+ MOZ_LOG(sXULBroadCastManager, LogLevel::Debug,
+ ("xul: broadcaster hookup <%s attribute='%s'> to %s",
+ nsAtomCString(content->NodeInfo()->NameAtom()).get(),
+ attributeC.get(),
+ broadcasteridC.get()));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULBroadcastManager::AddListener(Element* aElement)
+{
+ return UpdateListenerHookup(aElement, eHookupAdd);
+}
+
+nsresult
+XULBroadcastManager::RemoveListener(Element* aElement)
+{
+ return UpdateListenerHookup(aElement, eHookupRemove);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XULBroadcastManager_h
+#define mozilla_dom_XULBroadcastManager_h
+
+#include "mozilla/dom/Element.h"
+#include "nsAtom.h"
+
+class nsXULElement;
+
+namespace mozilla {
+namespace dom {
+
+class XULBroadcastManager final {
+
+public:
+ typedef mozilla::dom::Element Element;
+
+ explicit XULBroadcastManager(nsIDocument* aDocument);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(XULBroadcastManager)
+
+ /**
+ * Checks whether an element uses any of the special broadcaster attributes
+ * or is an observes element. This mimics the logic in FindBroadcaster, but
+ * is intended to be a lighter weight check and doesn't actually guarantee
+ * that the element will need a listener.
+ */
+ static bool MayNeedListener(const Element& aElement);
+
+ nsresult AddListener(Element* aElement);
+ nsresult RemoveListener(Element* aElement);
+ void AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+ void MaybeBroadcast();
+ void DropDocumentReference(); // notification that doc is going away
+protected:
+
+ enum HookupAction {
+ eHookupAdd = 0,
+ eHookupRemove
+ };
+
+ nsresult UpdateListenerHookup(Element* aElement, HookupAction aAction);
+
+ void RemoveListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr);
+ void AddListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr, ErrorResult& aRv);
+
+ nsresult
+ ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+ Element* aListener,
+ nsAtom* aAttr);
+ // The out params of FindBroadcaster only have values that make sense when
+ // the method returns NS_FINDBROADCASTER_FOUND. In all other cases, the
+ // values of the out params should not be relied on (though *aListener and
+ // *aBroadcaster do need to be released if non-null, of course).
+ nsresult
+ FindBroadcaster(Element* aElement,
+ Element** aListener,
+ nsString& aBroadcasterID,
+ nsString& aAttribute,
+ Element** aBroadcaster);
+
+ void
+ SynchronizeBroadcastListener(Element *aBroadcaster,
+ Element *aListener,
+ const nsAString &aAttr);
+
+
+ // This reference is nulled by the Document in it's destructor through
+ // DropDocumentReference().
+ nsIDocument* MOZ_NON_OWNING_REF mDocument;
+
+ /**
+ * A map from a broadcaster element to a list of listener elements.
+ */
+ PLDHashTable* mBroadcasterMap;
+
+ class nsDelayedBroadcastUpdate
+ {
+ public:
+ nsDelayedBroadcastUpdate(Element* aBroadcaster,
+ Element* aListener,
+ const nsAString &aAttr)
+ : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+ mSetAttr(false), mNeedsAttrChange(false) {}
+
+ nsDelayedBroadcastUpdate(Element* aBroadcaster,
+ Element* aListener,
+ nsAtom* aAttrName,
+ const nsAString &aAttr,
+ bool aSetAttr,
+ bool aNeedsAttrChange)
+ : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+ mAttrName(aAttrName), mSetAttr(aSetAttr),
+ mNeedsAttrChange(aNeedsAttrChange) {}
+
+ nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther)
+ : mBroadcaster(aOther.mBroadcaster), mListener(aOther.mListener),
+ mAttr(aOther.mAttr), mAttrName(aOther.mAttrName),
+ mSetAttr(aOther.mSetAttr), mNeedsAttrChange(aOther.mNeedsAttrChange) {}
+
+ nsCOMPtr<Element> mBroadcaster;
+ nsCOMPtr<Element> mListener;
+ // Note if mAttrName isn't used, this is the name of the attr, otherwise
+ // this is the value of the attribute.
+ nsString mAttr;
+ RefPtr<nsAtom> mAttrName;
+ bool mSetAttr;
+ bool mNeedsAttrChange;
+
+ class Comparator {
+ public:
+ static bool Equals(const nsDelayedBroadcastUpdate& a, const nsDelayedBroadcastUpdate& b) {
+ return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener && a.mAttrName == b.mAttrName;
+ }
+ };
+ };
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
+ bool mHandlingDelayedAttrChange;
+ bool mHandlingDelayedBroadcasters;
+
+private:
+ ~XULBroadcastManager();
+
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_XULBroadcastManager_h
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -112,29 +112,16 @@ static NS_DEFINE_CID(kParserCID,
// Statics
//
int32_t XULDocument::gRefCnt = 0;
LazyLogModule XULDocument::gXULLog("XULDocument");
//----------------------------------------------------------------------
-
-struct BroadcastListener {
- nsWeakPtr mListener;
- RefPtr<nsAtom> mAttribute;
-};
-
-struct BroadcasterMapEntry : public PLDHashEntryHdr
-{
- Element* mBroadcaster; // [WEAK]
- nsTArray<BroadcastListener*> mListeners; // [OWNING] of BroadcastListener objects
-};
-
-//----------------------------------------------------------------------
//
// ctors & dtors
//
namespace mozilla {
namespace dom {
XULDocument::XULDocument(void)
@@ -144,20 +131,17 @@ XULDocument::XULDocument(void)
mIsWritingFastLoad(false),
mDocumentLoaded(false),
mStillWalking(false),
mPendingSheets(0),
mCurrentScriptProto(nullptr),
mOffThreadCompiling(false),
mOffThreadCompileStringBuf(nullptr),
mOffThreadCompileStringLength(0),
- mBroadcasterMap(nullptr),
- mInitialLayoutComplete(false),
- mHandlingDelayedAttrChange(false),
- mHandlingDelayedBroadcasters(false)
+ mInitialLayoutComplete(false)
{
// Override the default in nsDocument
mCharacterSet = UTF_8_ENCODING;
mDefaultElementType = kNameSpaceID_XUL;
mType = eXUL;
mDelayFrameLoaderInitialization = true;
@@ -165,19 +149,16 @@ XULDocument::XULDocument(void)
mAllowXULXBL = eTriTrue;
}
XULDocument::~XULDocument()
{
NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
"unreferenced document still waiting for script source to load?");
- // Destroy our broadcaster map.
- delete mBroadcasterMap;
-
Preferences::UnregisterCallback(XULDocument::DirectionChanged,
"intl.uidirection", this);
if (mOffThreadCompileStringBuf) {
js_free(mOffThreadCompileStringBuf);
}
}
@@ -437,286 +418,16 @@ XULDocument::OnPrototypeLoadDone(bool aR
if (NS_FAILED(rv)) return rv;
if (aResumeWalk) {
rv = ResumeWalk();
}
return rv;
}
-static void
-ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
-{
- BroadcasterMapEntry* entry =
- static_cast<BroadcasterMapEntry*>(aEntry);
- for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
- delete entry->mListeners[i];
- }
- entry->mListeners.Clear();
-
- // N.B. that we need to manually run the dtor because we
- // constructed the nsTArray object in-place.
- entry->mListeners.~nsTArray<BroadcastListener*>();
-}
-
-static bool
-CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
-{
- // Don't push changes to the |id|, |persist|, |command| or
- // |observes| attribute.
- if (aNameSpaceID == kNameSpaceID_None) {
- if ((aAttribute == nsGkAtoms::id) ||
- (aAttribute == nsGkAtoms::persist) ||
- (aAttribute == nsGkAtoms::command) ||
- (aAttribute == nsGkAtoms::observes)) {
- return false;
- }
- }
- return true;
-}
-
-struct nsAttrNameInfo
-{
- nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) :
- mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
- nsAttrNameInfo(const nsAttrNameInfo& aOther) :
- mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
- mPrefix(aOther.mPrefix) {}
- int32_t mNamespaceID;
- RefPtr<nsAtom> mName;
- RefPtr<nsAtom> mPrefix;
-};
-
-void
-XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
- Element *aListener,
- const nsAString &aAttr)
-{
- if (!nsContentUtils::IsSafeToRunScript()) {
- nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
- aAttr);
- mDelayedBroadcasters.AppendElement(delayedUpdate);
- MaybeBroadcast();
- return;
- }
- bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
-
- if (aAttr.EqualsLiteral("*")) {
- uint32_t count = aBroadcaster->GetAttrCount();
- nsTArray<nsAttrNameInfo> attributes(count);
- for (uint32_t i = 0; i < count; ++i) {
- const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
- int32_t nameSpaceID = attrName->NamespaceID();
- nsAtom* name = attrName->LocalName();
-
- // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
- if (! CanBroadcast(nameSpaceID, name))
- continue;
-
- attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
- attrName->GetPrefix()));
- }
-
- count = attributes.Length();
- while (count-- > 0) {
- int32_t nameSpaceID = attributes[count].mNamespaceID;
- nsAtom* name = attributes[count].mName;
- nsAutoString value;
- if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
- aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
- value, notify);
- }
-
-#if 0
- // XXX we don't fire the |onbroadcast| handler during
- // initial hookup: doing so would potentially run the
- // |onbroadcast| handler before the |onload| handler,
- // which could define JS properties that mask XBL
- // properties, etc.
- ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
-#endif
- }
- }
- else {
- // Find out if the attribute is even present at all.
- RefPtr<nsAtom> name = NS_Atomize(aAttr);
-
- nsAutoString value;
- if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
- aListener->SetAttr(kNameSpaceID_None, name, value, notify);
- } else {
- aListener->UnsetAttr(kNameSpaceID_None, name, notify);
- }
-
-#if 0
- // XXX we don't fire the |onbroadcast| handler during initial
- // hookup: doing so would potentially run the |onbroadcast|
- // handler before the |onload| handler, which could define JS
- // properties that mask XBL properties, etc.
- ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
-#endif
- }
-}
-
-void
-XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
- const nsAString& aAttr, ErrorResult& aRv)
-{
- nsresult rv =
- nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
-
- if (NS_FAILED(rv)) {
- aRv.Throw(rv);
- return;
- }
-
- rv = nsContentUtils::CheckSameOrigin(this, &aListener);
-
- if (NS_FAILED(rv)) {
- aRv.Throw(rv);
- return;
- }
-
- static const PLDHashTableOps gOps = {
- PLDHashTable::HashVoidPtrKeyStub,
- PLDHashTable::MatchEntryStub,
- PLDHashTable::MoveEntryStub,
- ClearBroadcasterMapEntry,
- nullptr
- };
-
- if (! mBroadcasterMap) {
- mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
- }
-
- auto entry = static_cast<BroadcasterMapEntry*>
- (mBroadcasterMap->Search(&aBroadcaster));
- if (!entry) {
- entry = static_cast<BroadcasterMapEntry*>
- (mBroadcasterMap->Add(&aBroadcaster, fallible));
-
- if (! entry) {
- aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- entry->mBroadcaster = &aBroadcaster;
-
- // N.B. placement new to construct the nsTArray object in-place
- new (&entry->mListeners) nsTArray<BroadcastListener*>();
- }
-
- // Only add the listener if it's not there already!
- RefPtr<nsAtom> attr = NS_Atomize(aAttr);
-
- for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
- BroadcastListener* bl = entry->mListeners[i];
- nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
-
- if (blListener == &aListener && bl->mAttribute == attr)
- return;
- }
-
- BroadcastListener* bl = new BroadcastListener;
- bl->mListener = do_GetWeakReference(&aListener);
- bl->mAttribute = attr;
-
- entry->mListeners.AppendElement(bl);
-
- SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
-}
-
-void
-XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
- Element& aListener,
- const nsAString& aAttr)
-{
- // If we haven't added any broadcast listeners, then there sure
- // aren't any to remove.
- if (! mBroadcasterMap)
- return;
-
- auto entry = static_cast<BroadcasterMapEntry*>
- (mBroadcasterMap->Search(&aBroadcaster));
- if (entry) {
- RefPtr<nsAtom> attr = NS_Atomize(aAttr);
- for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
- BroadcastListener* bl = entry->mListeners[i];
- nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
-
- if (blListener == &aListener && bl->mAttribute == attr) {
- entry->mListeners.RemoveElementAt(i);
- delete bl;
-
- if (entry->mListeners.IsEmpty())
- mBroadcasterMap->RemoveEntry(entry);
-
- break;
- }
- }
- }
-}
-
-nsresult
-XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
- Element* aListener,
- nsAtom* aAttr)
-{
- // Now we execute the onchange handler in the context of the
- // observer. We need to find the observer in order to
- // execute the handler.
-
- for (nsIContent* child = aListener->GetFirstChild();
- child;
- child = child->GetNextSibling()) {
-
- // Look for an <observes> element beneath the listener. This
- // ought to have an |element| attribute that refers to
- // aBroadcaster, and an |attribute| element that tells us what
- // attriubtes we're listening for.
- if (!child->IsXULElement(nsGkAtoms::observes))
- continue;
-
- // Is this the element that was listening to us?
- nsAutoString listeningToID;
- child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
-
- nsAutoString broadcasterID;
- aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
-
- if (listeningToID != broadcasterID)
- continue;
-
- // We are observing the broadcaster, but is this the right
- // attribute?
- nsAutoString listeningToAttribute;
- child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
- listeningToAttribute);
-
- if (!aAttr->Equals(listeningToAttribute) &&
- !listeningToAttribute.EqualsLiteral("*")) {
- continue;
- }
-
- // This is the right <observes> element. Execute the
- // |onbroadcast| event handler
- WidgetEvent event(true, eXULBroadcast);
-
- RefPtr<nsPresContext> presContext = GetPresContext();
- if (presContext) {
- // Handle the DOM event
- nsEventStatus status = nsEventStatus_eIgnore;
- EventDispatcher::Dispatch(child, presContext, &event, nullptr,
- &status);
- }
- }
-
- return NS_OK;
-}
-
static bool
ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute)
{
if (aElement->IsXULElement(nsGkAtoms::window)) {
// This is not an element of the top document, its owner is
// not an nsXULWindow. Persist it.
if (aElement->OwnerDoc()->GetParentDocument()) {
return true;
@@ -739,72 +450,16 @@ XULDocument::AttributeChanged(Element* a
nsAtom* aAttribute, int32_t aModType,
const nsAttrValue* aOldValue)
{
NS_ASSERTION(aElement->OwnerDoc() == this, "unexpected doc");
// Might not need this, but be safe for now.
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
- // Synchronize broadcast listeners
- if (mBroadcasterMap &&
- CanBroadcast(aNameSpaceID, aAttribute)) {
- auto entry = static_cast<BroadcasterMapEntry*>
- (mBroadcasterMap->Search(aElement));
-
- if (entry) {
- // We've got listeners: push the value.
- nsAutoString value;
- bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
-
- for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
- BroadcastListener* bl = entry->mListeners[i];
- if ((bl->mAttribute == aAttribute) ||
- (bl->mAttribute == nsGkAtoms::_asterisk)) {
- nsCOMPtr<Element> listenerEl
- = do_QueryReferent(bl->mListener);
- if (listenerEl) {
- nsAutoString currentValue;
- bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
- aAttribute,
- currentValue);
- // We need to update listener only if we're
- // (1) removing an existing attribute,
- // (2) adding a new attribute or
- // (3) changing the value of an attribute.
- bool needsAttrChange =
- attrSet != hasAttr || !value.Equals(currentValue);
- nsDelayedBroadcastUpdate delayedUpdate(aElement,
- listenerEl,
- aAttribute,
- value,
- attrSet,
- needsAttrChange);
-
- size_t index =
- mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
- 0, nsDelayedBroadcastUpdate::Comparator());
- if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
- if (mHandlingDelayedAttrChange) {
- NS_WARNING("Broadcasting loop!");
- continue;
- }
- mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
- }
-
- mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
- }
- }
- }
- }
- }
-
- // checks for modifications in broadcasters
- CheckBroadcasterHookup(aElement);
-
// See if there is anything we need to persist in the localstore.
//
// XXX Namespace handling broken :-(
nsAutoString persist;
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
// Persistence of attributes of xul:window is handled in nsXULWindow.
if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
// XXXldb This should check that it's a token, not just a substring.
@@ -915,32 +570,26 @@ XULDocument::Persist(Element* aElement,
mLocalStore->SetValue(uri, id, attrstr, valuestr);
}
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
// called when creating elements from prototypes.
nsAtom* id = aElement->GetID();
if (id) {
// FIXME: Shouldn't BindToTree take care of this?
nsAutoScriptBlocker scriptBlocker;
AddToIdTable(aElement, id);
}
- // 2. Check for a broadcaster hookup attribute, in which case
- // we'll hook the node up as a listener on a broadcaster.
- rv = CheckBroadcasterHookup(aElement);
- if (NS_FAILED(rv)) return rv;
-
return NS_OK;
}
nsresult
XULDocument::AddElementToDocumentPost(Element* aElement)
{
if (aElement == GetRootElement()) {
ResetDocumentDirection();
@@ -1010,26 +659,16 @@ XULDocument::RemoveSubtreeFromDocument(n
// AddElementToDocumentPre().
nsAtom* id = aElement->GetID();
if (id) {
// FIXME: Shouldn't UnbindFromTree take care of this?
nsAutoScriptBlocker scriptBlocker;
RemoveFromIdTable(aElement, id);
}
- // Remove the element from our broadcaster map, since it is no longer
- // in the document.
- nsCOMPtr<Element> broadcaster, listener;
- nsAutoString attribute, broadcasterID;
- rv = FindBroadcaster(aElement, getter_AddRefs(listener),
- broadcasterID, attribute, getter_AddRefs(broadcaster));
- if (rv == NS_FINDBROADCASTER_FOUND) {
- RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
- }
-
return NS_OK;
}
//----------------------------------------------------------------------
//
// nsINode interface
//
@@ -1797,75 +1436,19 @@ XULDocument::StyleSheetLoaded(StyleSheet
return DoneWalking();
}
}
return NS_OK;
}
void
-XULDocument::MaybeBroadcast()
-{
- // Only broadcast when not in an update and when safe to run scripts.
- if (mUpdateNestLevel == 0 &&
- (mDelayedAttrChangeBroadcasts.Length() ||
- mDelayedBroadcasters.Length())) {
- if (!nsContentUtils::IsSafeToRunScript()) {
- if (!mInDestructor) {
- nsContentUtils::AddScriptRunner(
- NewRunnableMethod("dom::XULDocument::MaybeBroadcast",
- this,
- &XULDocument::MaybeBroadcast));
- }
- return;
- }
- if (!mHandlingDelayedAttrChange) {
- mHandlingDelayedAttrChange = true;
- for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
- nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
- if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
- nsCOMPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
- const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
- if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
- listener->SetAttr(kNameSpaceID_None, attrName, value,
- true);
- } else {
- listener->UnsetAttr(kNameSpaceID_None, attrName,
- true);
- }
- }
- ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
- mDelayedAttrChangeBroadcasts[i].mListener,
- attrName);
- }
- mDelayedAttrChangeBroadcasts.Clear();
- mHandlingDelayedAttrChange = false;
- }
-
- uint32_t length = mDelayedBroadcasters.Length();
- if (length) {
- bool oldValue = mHandlingDelayedBroadcasters;
- mHandlingDelayedBroadcasters = true;
- nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
- mDelayedBroadcasters.SwapElements(delayedBroadcasters);
- for (uint32_t i = 0; i < length; ++i) {
- SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
- delayedBroadcasters[i].mListener,
- delayedBroadcasters[i].mAttr);
- }
- mHandlingDelayedBroadcasters = oldValue;
- }
- }
-}
-
-void
XULDocument::EndUpdate()
{
XMLDocument::EndUpdate();
- MaybeBroadcast();
}
nsresult
XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
{
// Load a transcluded script
nsresult rv;
@@ -2243,151 +1826,16 @@ XULDocument::AddAttributes(nsXULPrototyp
valueStr,
false);
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
-
-//----------------------------------------------------------------------
-
-nsresult
-XULDocument::FindBroadcaster(Element* aElement,
- Element** aListener,
- nsString& aBroadcasterID,
- nsString& aAttribute,
- Element** aBroadcaster)
-{
- mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
- *aListener = nullptr;
- *aBroadcaster = nullptr;
-
- if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
- // It's an <observes> element, which means that the actual
- // listener is the _parent_ node. This element should have an
- // 'element' attribute that specifies the ID of the
- // broadcaster element, and an 'attribute' element, which
- // specifies the name of the attribute to observe.
- nsIContent* parent = aElement->GetParent();
- if (!parent) {
- // <observes> is the root element
- return NS_FINDBROADCASTER_NOT_FOUND;
- }
-
- *aListener = Element::FromNode(parent);
- NS_IF_ADDREF(*aListener);
-
- aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
- if (aBroadcasterID.IsEmpty()) {
- return NS_FINDBROADCASTER_NOT_FOUND;
- }
- aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
- }
- else {
- // It's a generic element, which means that we'll use the
- // value of the 'observes' attribute to determine the ID of
- // the broadcaster element, and we'll watch _all_ of its
- // values.
- aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
-
- // Bail if there's no aBroadcasterID
- if (aBroadcasterID.IsEmpty()) {
- // Try the command attribute next.
- aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
- if (!aBroadcasterID.IsEmpty()) {
- // We've got something in the command attribute. We
- // only treat this as a normal broadcaster if we are
- // not a menuitem or a key.
-
- if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
- ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
- return NS_FINDBROADCASTER_NOT_FOUND;
- }
- }
- else {
- return NS_FINDBROADCASTER_NOT_FOUND;
- }
- }
-
- *aListener = aElement;
- NS_ADDREF(*aListener);
-
- aAttribute.Assign('*');
- }
-
- // Make sure we got a valid listener.
- NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
-
- // Try to find the broadcaster element in the document.
- *aBroadcaster = GetElementById(aBroadcasterID);
-
- // The broadcaster element is missing.
- if (! *aBroadcaster) {
- return NS_FINDBROADCASTER_NOT_FOUND;
- }
-
- NS_ADDREF(*aBroadcaster);
-
- return NS_FINDBROADCASTER_FOUND;
-}
-
-nsresult
-XULDocument::CheckBroadcasterHookup(Element* aElement)
-{
- // Resolve a broadcaster hookup. Look at the element that we're
- // trying to resolve: it could be an '<observes>' element, or just
- // a vanilla element with an 'observes' attribute on it.
- nsresult rv;
-
- nsCOMPtr<Element> listener;
- nsAutoString broadcasterID;
- nsAutoString attribute;
- nsCOMPtr<Element> broadcaster;
-
- rv = FindBroadcaster(aElement, getter_AddRefs(listener),
- broadcasterID, attribute, getter_AddRefs(broadcaster));
- switch (rv) {
- case NS_FINDBROADCASTER_NOT_FOUND:
- return NS_OK;
- case NS_FINDBROADCASTER_FOUND:
- break;
- default:
- return rv;
- }
-
- NS_ENSURE_ARG(broadcaster && listener);
- ErrorResult domRv;
- AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
- if (domRv.Failed()) {
- return domRv.StealNSResult();
- }
-
- // Tell the world we succeeded
- if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
- nsCOMPtr<nsIContent> content = listener;
- NS_ASSERTION(content != nullptr, "not an nsIContent");
- if (!content) {
- return rv;
- }
-
- nsAutoCString attributeC,broadcasteridC;
- LossyCopyUTF16toASCII(attribute, attributeC);
- LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
- MOZ_LOG(gXULLog, LogLevel::Debug,
- ("xul: broadcaster hookup <%s attribute='%s'> to %s",
- nsAtomCString(content->NodeInfo()->NameAtom()).get(),
- attributeC.get(),
- broadcasteridC.get()));
- }
-
- return NS_OK;
-}
-
//----------------------------------------------------------------------
//
// CachedChromeStreamListener
//
XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
: mDocument(aDocument),
mProtoLoaded(aProtoLoaded)
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -123,19 +123,16 @@ public:
void ResetDocumentDirection();
NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) override;
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
void TraceProtos(JSTracer* aTrc);
- void RemoveBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
- const nsAString& aAttr);
-
protected:
virtual ~XULDocument();
// Implementation methods
friend nsresult
(::NS_NewXULDocument(nsIDocument** aResult));
nsresult Init(void) override;
@@ -159,24 +156,16 @@ protected:
nsCOMArray<Element>& aElements);
nsresult
AddElementToDocumentPre(Element* aElement);
nsresult
AddElementToDocumentPost(Element* aElement);
- void AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
- const nsAString& aAttr, ErrorResult& aRv);
-
- nsresult
- ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
- Element* aListener,
- nsAtom* aAttr);
-
static void DirectionChanged(const char* aPrefName, XULDocument* aData);
// pseudo constants
static int32_t gRefCnt;
static LazyLogModule gXULLog;
void
@@ -287,35 +276,16 @@ protected:
* If the current transcluded script is being compiled off thread, the
* source for that script.
*/
char16_t* mOffThreadCompileStringBuf;
size_t mOffThreadCompileStringLength;
protected:
- // The out params of FindBroadcaster only have values that make sense when
- // the method returns NS_FINDBROADCASTER_FOUND. In all other cases, the
- // values of the out params should not be relied on (though *aListener and
- // *aBroadcaster do need to be released if non-null, of course).
- nsresult
- FindBroadcaster(Element* aElement,
- Element** aListener,
- nsString& aBroadcasterID,
- nsString& aAttribute,
- Element** aBroadcaster);
-
- nsresult
- CheckBroadcasterHookup(Element* aElement);
-
- void
- SynchronizeBroadcastListener(Element *aBroadcaster,
- Element *aListener,
- const nsAString &aAttr);
-
/**
* The current prototype that we are walking to construct the
* content model.
*/
RefPtr<nsXULPrototypeDocument> mCurrentPrototype;
/**
* Owning references to all of the prototype documents that were
@@ -377,70 +347,18 @@ protected:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
};
friend class CachedChromeStreamListener;
- /**
- * A map from a broadcaster element to a list of listener elements.
- */
- PLDHashTable* mBroadcasterMap;
-
bool mInitialLayoutComplete;
- class nsDelayedBroadcastUpdate
- {
- public:
- nsDelayedBroadcastUpdate(Element* aBroadcaster,
- Element* aListener,
- const nsAString &aAttr)
- : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
- mSetAttr(false), mNeedsAttrChange(false) {}
-
- nsDelayedBroadcastUpdate(Element* aBroadcaster,
- Element* aListener,
- nsAtom* aAttrName,
- const nsAString &aAttr,
- bool aSetAttr,
- bool aNeedsAttrChange)
- : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
- mAttrName(aAttrName), mSetAttr(aSetAttr),
- mNeedsAttrChange(aNeedsAttrChange) {}
-
- nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther)
- : mBroadcaster(aOther.mBroadcaster), mListener(aOther.mListener),
- mAttr(aOther.mAttr), mAttrName(aOther.mAttrName),
- mSetAttr(aOther.mSetAttr), mNeedsAttrChange(aOther.mNeedsAttrChange) {}
-
- nsCOMPtr<Element> mBroadcaster;
- nsCOMPtr<Element> mListener;
- // Note if mAttrName isn't used, this is the name of the attr, otherwise
- // this is the value of the attribute.
- nsString mAttr;
- RefPtr<nsAtom> mAttrName;
- bool mSetAttr;
- bool mNeedsAttrChange;
-
- class Comparator {
- public:
- static bool Equals(const nsDelayedBroadcastUpdate& a, const nsDelayedBroadcastUpdate& b) {
- return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener && a.mAttrName == b.mAttrName;
- }
- };
- };
-
- nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
- nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
- bool mHandlingDelayedAttrChange;
- bool mHandlingDelayedBroadcasters;
-
- void MaybeBroadcast();
private:
// helpers
};
} // namespace dom
} // namespace mozilla
--- a/dom/xul/moz.build
+++ b/dom/xul/moz.build
@@ -17,16 +17,17 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
if CONFIG['MOZ_XUL']:
EXPORTS += [
'nsXULCommandDispatcher.h',
'nsXULElement.h',
'nsXULSortService.h',
]
EXPORTS.mozilla.dom += [
+ 'XULBroadcastManager.h',
'XULFrameElement.h',
'XULMenuElement.h',
'XULPopupElement.h',
'XULScrollElement.h',
'XULTextElement.h',
'XULTooltipElement.h',
]
@@ -34,16 +35,17 @@ if CONFIG['MOZ_XUL']:
'nsXULCommandDispatcher.cpp',
'nsXULContentSink.cpp',
'nsXULContentUtils.cpp',
'nsXULElement.cpp',
'nsXULPopupListener.cpp',
'nsXULPrototypeCache.cpp',
'nsXULPrototypeDocument.cpp',
'nsXULSortService.cpp',
+ 'XULBroadcastManager.cpp',
'XULDocument.cpp',
'XULFrameElement.cpp',
'XULMenuElement.cpp',
'XULPopupElement.cpp',
'XULScrollElement.cpp',
'XULTextElement.cpp',
'XULTooltipElement.cpp',
]
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -76,16 +76,17 @@
#include "nsLayoutUtils.h"
#include "XULFrameElement.h"
#include "XULMenuElement.h"
#include "XULPopupElement.h"
#include "XULScrollElement.h"
#include "mozilla/dom/XULElementBinding.h"
#include "mozilla/dom/BoxObject.h"
+#include "mozilla/dom/XULBroadcastManager.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/XULCommandEvent.h"
using namespace mozilla;
using namespace mozilla::dom;
#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
@@ -749,30 +750,45 @@ nsXULElement::BindToTree(nsIDocument* aD
// Create our XUL key listener and hook it up.
nsXBLService::AttachGlobalKeyHandler(this);
}
if (doc && NeedTooltipSupport(*this)) {
AddTooltipSupport();
}
+ if (doc && XULBroadcastManager::MayNeedListener(*this)) {
+ if (!doc->HasXULBroadcastManager()) {
+ doc->InitializeXULBroadcastManager();
+ }
+ XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
+ broadcastManager->AddListener(this);
+ }
+
return rv;
}
void
nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
nsXBLService::DetachGlobalKeyHandler(this);
}
if (NeedTooltipSupport(*this)) {
RemoveTooltipSupport();
}
+ nsIDocument* doc = GetComposedDoc();
+ if (doc && doc->HasXULBroadcastManager() &&
+ XULBroadcastManager::MayNeedListener(*this)) {
+ RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+ broadcastManager->RemoveListener(this);
+ }
+
// mControllers can own objects that are implemented
// in JavaScript (such as some implementations of
// nsIControllers. These objects prevent their global
// object's script object from being garbage collected,
// which means JS continues to hold an owning reference
// to the nsGlobalWindow, which owns the document,
// which owns this content. That's a cycle, so we break
// it here. (It might be better to break this by releasing
@@ -825,24 +841,28 @@ nsXULElement::BeforeSetAttr(int32_t aNam
nsAutoString oldValue;
if (GetAttr(aNamespaceID, aName, oldValue)) {
UnregisterAccessKey(oldValue);
}
} else if (aNamespaceID == kNameSpaceID_None &&
(aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
IsInUncomposedDoc()) {
// XXX sXBL/XBL2 issue! Owner or current document?
+ // XXX Why does this not also remove broadcast listeners if the
+ // "element" attribute was changed on an <observer>?
nsAutoString oldValue;
GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
if (oldValue.IsEmpty()) {
GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
}
- if (!oldValue.IsEmpty()) {
- RemoveBroadcaster(oldValue);
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
+ RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+ broadcastManager->RemoveListener(this);
}
} else if (aNamespaceID == kNameSpaceID_None &&
aValue &&
mNodeInfo->Equals(nsGkAtoms::window) &&
aName == nsGkAtoms::chromemargin) {
nsAttrValue attrValue;
// Make sure the margin format is valid first
if (!attrValue.ParseIntMarginValue(aValue->String())) {
@@ -960,16 +980,29 @@ nsXULElement::AfterSetAttr(int32_t aName
!NodeInfo()->Equals(nsGkAtoms::treechildren)) {
if (aValue) {
AddTooltipSupport();
} else {
RemoveTooltipSupport();
}
}
}
+ nsIDocument* doc = GetComposedDoc();
+ if (doc && doc->HasXULBroadcastManager()) {
+ RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+ broadcastManager->AttributeChanged(this, aNamespaceID, aName);
+ }
+ if (doc && XULBroadcastManager::MayNeedListener(*this)) {
+ if (!doc->HasXULBroadcastManager()) {
+ doc->InitializeXULBroadcastManager();
+ }
+ XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
+ broadcastManager->AddListener(this);
+ }
+
// XXX need to check if they're changing an event handler: if
// so, then we need to unhook the old one. Or something.
}
return nsStyledElement::AfterSetAttr(aNamespaceID, aName,
aValue, aOldValue, aSubjectPrincipal, aNotify);
}
@@ -1008,29 +1041,16 @@ nsXULElement::ParseAttribute(int32_t aNa
// Fall back to parsing as atom for short values
aResult.ParseStringOrAtom(aValue);
}
return true;
}
void
-nsXULElement::RemoveBroadcaster(const nsAString & broadcasterId)
-{
- nsIDocument* doc = OwnerDoc();
- if (!doc->IsXULDocument()) {
- return;
- }
- if (Element* broadcaster = doc->GetElementById(broadcasterId)) {
- doc->AsXULDocument()->RemoveBroadcastListenerFor(
- *broadcaster, *this, NS_LITERAL_STRING("*"));
- }
-}
-
-void
nsXULElement::DestroyContent()
{
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
if (slots) {
slots->mControllers = nullptr;
}
nsStyledElement::DestroyContent();
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -688,18 +688,16 @@ protected:
nsresult HideWindowChrome(bool aShouldHide);
void SetChromeMargins(const nsAttrValue* aValue);
void ResetChromeMargins();
void SetDrawsInTitlebar(bool aState);
void SetDrawsTitle(bool aState);
void UpdateBrightTitlebarForeground(nsIDocument* aDocument);
- void RemoveBroadcaster(const nsAString & broadcasterId);
-
protected:
void AddTooltipSupport();
void RemoveTooltipSupport();
// Internal accessor. This shadows the 'Slots', and returns
// appropriate value.
nsIControllers *Controllers() {
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
--- a/dom/xul/test/test_bug445177.xul
+++ b/dom/xul/test/test_bug445177.xul
@@ -11,18 +11,18 @@ https://bugzilla.mozilla.org/show_bug.cg
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<body id="body" xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=445177">Mozilla Bug 445177</a>
-<hbox id="b1" value="foo"/>
-<hbox id="o1" observes="b1"/>
+<xul:hbox id="b1" value="foo"/>
+<xul:hbox id="o1" observes="b1"/>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
function do_test() {
var b1 = document.getElementById("b1");
var o1 = document.getElementById("o1");