Source code

Revision control

Copy as Markdown

Other Tools

/* 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 nsMaybeWeakPtr_h_
#define nsMaybeWeakPtr_h_
#include "mozilla/Try.h"
#include "mozilla/Variant.h"
#include "nsCOMPtr.h"
#include "nsIWeakReferenceUtils.h"
#include "nsTArray.h"
#include "nsCycleCollectionNoteChild.h"
#include "xpcpublic.h"
// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference
// to the template class. It's pretty minimal, but sufficient.
template <class T>
class nsMaybeWeakPtr {
public:
nsMaybeWeakPtr() : mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, nullptr) {}
explicit nsMaybeWeakPtr(std::nullptr_t)
: mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, nullptr) {}
explicit nsMaybeWeakPtr(T* aRef)
: mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, aRef) {}
explicit nsMaybeWeakPtr(const nsWeakPtr& aRef)
: mPtr(mozilla::VariantType<nsWeakPtr>{}, aRef) {
MOZ_ASSERT(AsWeak(), "null nsWeakPtr pointer passed to nsMaybeWeakPtr");
}
bool IsStrong() const { return mPtr.template is<nsCOMPtr<T>>(); }
bool IsWeak() const { return mPtr.template is<nsWeakPtr>(); }
nsMaybeWeakPtr& operator=(std::nullptr_t) {
mPtr.template emplace<nsCOMPtr<T>>(nullptr);
return *this;
}
nsMaybeWeakPtr<T>& operator=(T* aRef) {
mPtr.template emplace<nsCOMPtr<T>>(aRef);
return *this;
}
nsMaybeWeakPtr<T>& operator=(const nsWeakPtr& aRef) {
mPtr.template emplace<nsWeakPtr>(aRef);
return *this;
}
bool operator==(const nsMaybeWeakPtr<T>& aOther) const {
return mPtr == aOther.mPtr;
}
bool operator==(T* const& aStrong) const {
return IsStrong() && AsStrong() == aStrong;
}
bool operator==(const nsWeakPtr& aWeak) const {
return IsWeak() && AsWeak() == aWeak;
}
already_AddRefed<T> GetValue() const {
return IsWeak() ? GetWeakReferent() : nsCOMPtr<T>{AsStrong()}.forget();
}
friend inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
const nsMaybeWeakPtr& aField, const char* aName, uint32_t aFlags = 0) {
if (aField.IsStrong()) {
CycleCollectionNoteChild(aCallback, aField.AsStrong().get(), aName,
aFlags);
}
}
protected:
const nsCOMPtr<T>& AsStrong() const {
return mPtr.template as<nsCOMPtr<T>>();
}
const nsWeakPtr& AsWeak() const { return mPtr.template as<nsWeakPtr>(); }
private:
already_AddRefed<T> GetWeakReferent() const {
MOZ_ASSERT(AsWeak());
nsresult rv;
nsCOMPtr<T> ref = do_QueryReferent(AsWeak(), &rv);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_NULL_POINTER,
"QueryReferent failed with non-null pointer");
return ref.forget();
}
mozilla::Variant<nsCOMPtr<T>, nsWeakPtr> mPtr;
};
// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to
// grab a weak reference to a given object if requested. It only allows a
// given object to appear in the array once.
template <class T>
class nsMaybeWeakPtrArray : public CopyableTArray<nsMaybeWeakPtr<T>> {
using MaybeWeakArray = nsTArray<nsMaybeWeakPtr<T>>;
nsresult SetMaybeWeakPtr(nsMaybeWeakPtr<T>& aRef, T* aElement,
bool aOwnsWeak) {
nsresult rv = NS_OK;
if (aOwnsWeak) {
aRef = do_GetWeakReference(aElement, &rv);
} else {
aRef = aElement;
}
return rv;
}
public:
nsresult AppendWeakElement(T* aElement, bool aOwnsWeak) {
nsMaybeWeakPtr<T> ref;
MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));
#if (defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(MOZ_THUNDERBIRD))
// Checking for duplicates is expensive, so we enforce callers to avoid
// this with a diagnostic assertion. See bug 2000788 for Thunderbird.
if (IsAssertOnDoubleAdd()) {
if (MaybeWeakArray::Contains(aElement)) {
xpc_DumpJSStack(true, true, false);
MOZ_DIAGNOSTIC_ASSERT(false, "Element already in array.");
}
}
#endif
MaybeWeakArray::AppendElement(ref);
return NS_OK;
}
nsresult AppendWeakElementUnlessExists(T* aElement, bool aOwnsWeak) {
nsMaybeWeakPtr<T> ref;
MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));
if (MaybeWeakArray::Contains(ref)) {
return NS_ERROR_INVALID_ARG;
}
MaybeWeakArray::AppendElement(ref);
return NS_OK;
}
nsresult RemoveWeakElement(T* aElement) {
if (MaybeWeakArray::RemoveElement(aElement)) {
return NS_OK;
}
// Don't use do_GetWeakReference; it should only be called if we know
// the object supports weak references.
nsCOMPtr<nsISupportsWeakReference> supWeakRef = do_QueryInterface(aElement);
if (!supWeakRef) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIWeakReference> weakRef;
nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef));
NS_ENSURE_SUCCESS(rv, rv);
if (MaybeWeakArray::RemoveElement(weakRef)) {
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
friend inline void ImplCycleCollectionUnlink(nsMaybeWeakPtrArray& aField) {
aField.Clear();
}
friend inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
nsMaybeWeakPtrArray& aField, const char* aName, uint32_t aFlags = 0) {
aFlags |= CycleCollectionEdgeNameArrayFlag;
for (auto& e : aField) {
ImplCycleCollectionTraverse(aCallback, e, aName, aFlags);
}
}
private:
#if (defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(MOZ_THUNDERBIRD))
// Until we have bug 2005466, a plain diagnostic assert would only yield us
// unactionable crash reports from official Nightly builds in case JS was
// adding an observer (which is common enough). But we want developers using
// non-DEBUG builds to catch this locally and on CI when testing.
static inline bool IsAssertOnDoubleAdd() {
# if defined(DEBUG) || defined(FUZZING)
return true;
# else
return xpc::IsInAutomation();
# endif
}
#endif
};
// Call a method on each element in the array, but only if the element is
// non-null.
#define ENUMERATE_WEAKARRAY(array, type, method) \
for (uint32_t array_idx = 0; array_idx < (array).Length(); ++array_idx) { \
const nsCOMPtr<type>& e = (array).ElementAt(array_idx).GetValue(); \
if (e) e->method; \
}
#endif