summaryrefslogtreecommitdiffstats
path: root/netwerk/ipc/ChannelEventQueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/ipc/ChannelEventQueue.cpp218
1 files changed, 218 insertions, 0 deletions
diff --git a/netwerk/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp
new file mode 100644
index 0000000000..0aa4cf71f4
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set sw=2 ts=8 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 "ChannelEventQueue.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+#include "nsIChannel.h"
+#include "mozilla/dom/Document.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelEvent* ChannelEventQueue::TakeEvent() {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mFlushing);
+
+ if (mSuspended || mEventQueue.IsEmpty()) {
+ return nullptr;
+ }
+
+ UniquePtr<ChannelEvent> event(std::move(mEventQueue[0]));
+ mEventQueue.RemoveElementAt(0);
+
+ return event.release();
+}
+
+void ChannelEventQueue::FlushQueue() {
+ // Events flushed could include destruction of channel (and our own
+ // destructor) unless we make sure its refcount doesn't drop to 0 while this
+ // method is running.
+ nsCOMPtr<nsISupports> kungFuDeathGrip(mOwner);
+ mozilla::Unused << kungFuDeathGrip; // Not used in this function
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mFlushing);
+ }
+#endif // DEBUG
+
+ bool needResumeOnOtherThread = false;
+
+ while (true) {
+ UniquePtr<ChannelEvent> event;
+ {
+ MutexAutoLock lock(mMutex);
+ event.reset(TakeEvent());
+ if (!event) {
+ MOZ_ASSERT(mFlushing);
+ mFlushing = false;
+ MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount));
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+ MOZ_ASSERT(target);
+
+ bool isCurrentThread = false;
+ nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Simply run this event on current thread if we are not sure about it
+ // in release channel, or assert in Aurora/Nightly channel.
+ MOZ_DIAGNOSTIC_ASSERT(false);
+ isCurrentThread = true;
+ }
+
+ if (!isCurrentThread) {
+ // Next event needs to run on another thread. Put it back to
+ // the front of the queue can try resume on that thread.
+ Suspend();
+ PrependEvent(std::move(event));
+
+ needResumeOnOtherThread = true;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mFlushing);
+ mFlushing = false;
+ MOZ_ASSERT(!mEventQueue.IsEmpty());
+ }
+ break;
+ }
+
+ event->Run();
+ } // end of while(true)
+
+ // The flush procedure is aborted because next event cannot be run on current
+ // thread. We need to resume the event processing right after flush procedure
+ // is finished.
+ // Note: we cannot call Resume() while "mFlushing == true" because
+ // CompleteResume will not trigger FlushQueue while there is an ongoing flush.
+ if (needResumeOnOtherThread) {
+ Resume();
+ }
+}
+
+void ChannelEventQueue::Suspend() {
+ MutexAutoLock lock(mMutex);
+ SuspendInternal();
+}
+
+void ChannelEventQueue::SuspendInternal() {
+ mMutex.AssertCurrentThreadOwns();
+
+ mSuspended = true;
+ mSuspendCount++;
+}
+
+void ChannelEventQueue::Resume() {
+ MutexAutoLock lock(mMutex);
+ ResumeInternal();
+}
+
+void ChannelEventQueue::ResumeInternal() {
+ mMutex.AssertCurrentThreadOwns();
+
+ // Resuming w/o suspend: error in debug mode, ignore in build
+ MOZ_ASSERT(mSuspendCount > 0);
+ if (mSuspendCount <= 0) {
+ return;
+ }
+
+ if (!--mSuspendCount) {
+ if (mEventQueue.IsEmpty() || !!mForcedCount) {
+ // Nothing in queue to flush or waiting for AutoEventEnqueuer to
+ // finish the force enqueue period, simply clear the flag.
+ mSuspended = false;
+ return;
+ }
+
+ // Hold a strong reference of mOwner to avoid the channel release
+ // before CompleteResume was executed.
+ class CompleteResumeRunnable : public Runnable {
+ public:
+ explicit CompleteResumeRunnable(ChannelEventQueue* aQueue,
+ nsISupports* aOwner)
+ : Runnable("CompleteResumeRunnable"),
+ mQueue(aQueue),
+ mOwner(aOwner) {}
+
+ NS_IMETHOD Run() override {
+ mQueue->CompleteResume();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~CompleteResumeRunnable() = default;
+
+ RefPtr<ChannelEventQueue> mQueue;
+ nsCOMPtr<nsISupports> mOwner;
+ };
+
+ // Worker thread requires a CancelableRunnable.
+ RefPtr<Runnable> event = new CompleteResumeRunnable(this, mOwner);
+
+ nsCOMPtr<nsIEventTarget> target;
+ target = mEventQueue[0]->GetEventTarget();
+ MOZ_ASSERT(target);
+
+ Unused << NS_WARN_IF(
+ NS_FAILED(target->Dispatch(event.forget(), NS_DISPATCH_NORMAL)));
+ }
+}
+
+bool ChannelEventQueue::MaybeSuspendIfEventsAreSuppressed() {
+ // We only ever need to suppress events on the main thread, since this is
+ // where content scripts can run.
+ if (!NS_IsMainThread()) {
+ return false;
+ }
+
+ // Only suppress events for queues associated with XHRs, as these can cause
+ // content scripts to run.
+ if (mHasCheckedForXMLHttpRequest && !mForXMLHttpRequest) {
+ return false;
+ }
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mOwner));
+ if (!channel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ // Figure out if this is for an XHR, if we haven't done so already.
+ if (!mHasCheckedForXMLHttpRequest) {
+ nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
+ mForXMLHttpRequest =
+ (contentType == nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST);
+ mHasCheckedForXMLHttpRequest = true;
+
+ if (!mForXMLHttpRequest) {
+ return false;
+ }
+ }
+
+ // Suspend the queue if the associated document has suppressed event handling,
+ // *and* it is not in the middle of a synchronous operation that might require
+ // XHR events to be processed (such as a synchronous XHR).
+ RefPtr<dom::Document> document;
+ loadInfo->GetLoadingDocument(getter_AddRefs(document));
+ if (document && document->EventHandlingSuppressed() &&
+ !document->IsInSyncOperation()) {
+ document->AddSuspendedChannelEventQueue(this);
+ SuspendInternal();
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla