Bug 1495748: Make sure ChannelWrappers get cleaned up before JS engine shutdown. r=aswan,mccr8
authorKris Maglione <maglione.k@gmail.com>
Tue, 16 Oct 2018 15:05:24 -0700
changeset 490129 8bdbb72ad61b6272b3b7faed01383bb7c5fe7e72
parent 490128 8b037400fdbed6b9fd14c36a8b32acd78a742e1d
child 490130 de3d53ed28eed9a6f36aff370b4471703db700bc
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersaswan, mccr8
bugs1495748
milestone64.0a1
Bug 1495748: Make sure ChannelWrappers get cleaned up before JS engine shutdown. r=aswan,mccr8 The gross contortions here are required to deal with the deferred finalizers HTTP channels use for their property bags. The actual channels get destroyed relatively early during shutdown, but their property bag hashes which hold our ChannelWrapper reference end up being destroyed after JS engine shutdown, which gives us no good point to clear our reference. The stub holder class takes the place of our existing property bag entry, and behaves more or less the same, but allows us to cut the reference to the ChannelWrapper without having a strong reference to the channel. Differential Revision: https://phabricator.services.mozilla.com/D8923
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
toolkit/components/extensions/webrequest/ChannelWrapper.h
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -11,51 +11,122 @@
 
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/SystemPrincipal.h"
 
 #include "NSSErrorsService.h"
 #include "nsITransportSecurityInfo.h"
 
 #include "mozilla/AddonManagerWebAPI.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/TabParent.h"
 #include "nsIContentPolicy.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsILoadGroup.h"
 #include "nsIProxiedChannel.h"
 #include "nsIProxyInfo.h"
 #include "nsITraceableChannel.h"
+#include "nsIWritablePropertyBag.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsPrintfCString.h"
 
 using namespace mozilla::dom;
 using namespace JS;
 
 namespace mozilla {
 namespace extensions {
 
 #define CHANNELWRAPPER_PROP_KEY NS_LITERAL_STRING("ChannelWrapper::CachedInstance")
 
 /*****************************************************************************
+ * Lifetimes
+ *****************************************************************************/
+
+namespace {
+class ChannelListHolder : public LinkedList<ChannelWrapper>
+{
+public:
+  ChannelListHolder()
+    : LinkedList<ChannelWrapper>()
+  {}
+
+  ~ChannelListHolder();
+};
+
+} // anonymous namespace
+
+ChannelListHolder::~ChannelListHolder()
+{
+  while (ChannelWrapper* wrapper = popFirst()) {
+    wrapper->Die();
+  }
+}
+
+
+static LinkedList<ChannelWrapper>&
+ChannelList()
+{
+  static UniquePtr<ChannelListHolder> sChannelList;
+  if (!sChannelList) {
+    sChannelList.reset(new ChannelListHolder());
+    ClearOnShutdown(&sChannelList, ShutdownPhase::Shutdown);
+  }
+  return *sChannelList;
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ChannelWrapper::ChannelWrapperStub)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ChannelWrapper::ChannelWrapperStub)
+
+NS_IMPL_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub, mChannelWrapper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub)
+  NS_INTERFACE_MAP_ENTRY_TEAROFF(ChannelWrapper, mChannelWrapper)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/*****************************************************************************
  * Initialization
  *****************************************************************************/
 
+ChannelWrapper::ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel)
+  : ChannelHolder(aChannel)
+  , mParent(aParent)
+{
+  mStub = new ChannelWrapperStub(this);
+
+  ChannelList().insertBack(this);
+}
+
+ChannelWrapper::~ChannelWrapper()
+{
+  if (LinkedListElement<ChannelWrapper>::isInList()) {
+    LinkedListElement<ChannelWrapper>::remove();
+  }
+}
+
+void
+ChannelWrapper::Die()
+{
+  if (mStub) {
+    mStub->mChannelWrapper = nullptr;
+  }
+}
+
 /* static */
-
 already_AddRefed<ChannelWrapper>
 ChannelWrapper::Get(const GlobalObject& global, nsIChannel* channel)
 {
   RefPtr<ChannelWrapper> wrapper;
 
   nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel);
   if (props) {
     Unused << props->GetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY,
@@ -67,17 +138,17 @@ ChannelWrapper::Get(const GlobalObject& 
       wrapper->ClearCachedAttributes();
     }
   }
 
   if (!wrapper) {
     wrapper = new ChannelWrapper(global.GetAsSupports(), channel);
     if (props) {
       Unused << props->SetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY,
-                                              wrapper);
+                                              wrapper->mStub);
     }
   }
 
   return wrapper.forget();
 }
 
 already_AddRefed<ChannelWrapper>
 ChannelWrapper::GetRegisteredChannel(const GlobalObject& global, uint64_t aChannelId, const WebExtensionPolicy& aAddon, nsITabParent* aTabParent)
@@ -1050,20 +1121,22 @@ ChannelWrapper::WrapObject(JSContext* aC
 NS_IMPL_CYCLE_COLLECTION_CLASS(ChannelWrapper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper)
   NS_INTERFACE_MAP_ENTRY(ChannelWrapper)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStub)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStub)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_ADDREF_INHERITED(ChannelWrapper, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(ChannelWrapper, DOMEventTargetHelper)
 
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.h
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -10,16 +10,17 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ChannelWrapperBinding.h"
 
 #include "mozilla/WebRequestService.h"
 #include "mozilla/extensions/MatchPattern.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 
 #include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIChannel.h"
@@ -106,25 +107,28 @@ namespace detail {
     mutable Maybe<nsIHttpChannel*> MOZ_NON_OWNING_REF mWeakHttpChannel;
   };
 }
 
 class WebRequestChannelEntry;
 
 class ChannelWrapper final : public DOMEventTargetHelper
                            , public SupportsWeakPtr<ChannelWrapper>
+                           , public LinkedListElement<ChannelWrapper>
                            , private detail::ChannelHolder
 {
 public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ChannelWrapper)
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ChannelWrapper, DOMEventTargetHelper)
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CHANNELWRAPPER_IID)
 
+  void Die();
+
   static already_AddRefed<extensions::ChannelWrapper> Get(const dom::GlobalObject& global, nsIChannel* channel);
   static already_AddRefed<extensions::ChannelWrapper> GetRegisteredChannel(const dom::GlobalObject& global, uint64_t aChannelId, const WebExtensionPolicy& aAddon, nsITabParent* aTabParent);
 
   uint64_t Id() const { return mId; }
 
   already_AddRefed<nsIChannel> GetChannel() const { return MaybeChannel(); }
 
   void SetChannel(nsIChannel* aChannel);
@@ -236,23 +240,20 @@ public:
   virtual void EventListenerRemoved(nsAtom* aType) override;
 
 
   nsISupports* GetParentObject() const { return mParent; }
 
   JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
 
 protected:
-  ~ChannelWrapper() = default;
+  ~ChannelWrapper();
 
 private:
-  ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel)
-    : ChannelHolder(aChannel)
-    , mParent(aParent)
-  {}
+  ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel);
 
   void ClearCachedAttributes();
 
   bool CheckAlive(ErrorResult& aRv) const
   {
     if (!HaveChannel()) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return false;
@@ -274,16 +275,37 @@ private:
   static uint64_t GetNextId()
   {
     static uint64_t sNextId = 1;
     return ++sNextId;
   }
 
   void CheckEventListeners();
 
+  class ChannelWrapperStub final : public nsISupports
+  {
+  public:
+    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+    NS_DECL_CYCLE_COLLECTION_CLASS(ChannelWrapperStub)
+
+    explicit ChannelWrapperStub(ChannelWrapper* aChannelWrapper)
+      : mChannelWrapper(aChannelWrapper)
+    {}
+
+  private:
+    friend class ChannelWrapper;
+
+    RefPtr<ChannelWrapper> mChannelWrapper;
+
+  protected:
+    ~ChannelWrapperStub() = default;
+  };
+
+  RefPtr<ChannelWrapperStub> mStub;
+
   mutable Maybe<URLInfo> mFinalURLInfo;
   mutable Maybe<URLInfo> mDocumentURLInfo;
 
   UniquePtr<WebRequestChannelEntry> mChannelEntry;
 
   // The overridden Content-Type header value.
   nsCString mContentTypeHdr = VoidCString();