ChannelEventQueue.cpp (6401B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set sw=2 ts=8 et tw=80 : 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "ChannelEventQueue.h" 9 10 #include "mozilla/Assertions.h" 11 #include "nsIChannel.h" 12 #include "mozilla/dom/Document.h" 13 #include "nsThreadUtils.h" 14 #include "mozilla/FlowMarkers.h" 15 16 namespace mozilla { 17 namespace net { 18 19 ChannelEvent* ChannelEventQueue::TakeEvent() { 20 mMutex.AssertCurrentThreadOwns(); 21 MOZ_ASSERT(mFlushing); 22 23 if (mSuspended || mEventQueue.IsEmpty()) { 24 return nullptr; 25 } 26 27 UniquePtr<ChannelEvent> event(std::move(mEventQueue[0])); 28 mEventQueue.RemoveElementAt(0); 29 30 return event.release(); 31 } 32 33 void ChannelEventQueue::FlushQueue() { 34 mMutex.AssertCurrentThreadOwns(); 35 // Events flushed could include destruction of channel (and our own 36 // destructor) unless we make sure its refcount doesn't drop to 0 while this 37 // method is running. 38 nsCOMPtr<nsISupports> kungFuDeathGrip; 39 kungFuDeathGrip = mOwner; 40 (void)kungFuDeathGrip; // Not used in this function 41 42 MOZ_ASSERT(mFlushing); 43 44 bool needResumeOnOtherThread = false; 45 46 while (true) { 47 UniquePtr<ChannelEvent> event; 48 event.reset(TakeEvent()); 49 if (!event) { 50 MOZ_ASSERT(mFlushing); 51 mFlushing = false; 52 MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount)); 53 break; 54 } 55 56 nsCOMPtr<nsIEventTarget> target = event->GetEventTarget(); 57 MOZ_ASSERT(target); 58 59 bool isCurrentThread = false; 60 nsresult rv = target->IsOnCurrentThread(&isCurrentThread); 61 if (NS_WARN_IF(NS_FAILED(rv))) { 62 // Simply run this event on current thread if we are not sure about it 63 // in release channel, or assert in Aurora/Nightly channel. 64 MOZ_DIAGNOSTIC_CRASH("IsOnCurrentThread failed"); 65 isCurrentThread = true; 66 } 67 68 if (!isCurrentThread) { 69 // Next event needs to run on another thread. Put it back to 70 // the front of the queue can try resume on that thread. 71 SuspendInternal(); 72 PrependEventInternal(std::move(event)); 73 74 needResumeOnOtherThread = true; 75 MOZ_ASSERT(mFlushing); 76 mFlushing = false; 77 MOZ_ASSERT(!mEventQueue.IsEmpty()); 78 break; 79 } 80 { 81 MutexAutoUnlock unlock(mMutex); 82 AUTO_PROFILER_TERMINATING_FLOW_MARKER("ChannelEvent", NETWORK, 83 Flow::FromPointer(event.get())); 84 event->Run(); 85 } 86 } // end of while(true) 87 88 // The flush procedure is aborted because next event cannot be run on current 89 // thread. We need to resume the event processing right after flush procedure 90 // is finished. 91 // Note: we cannot call Resume() while "mFlushing == true" because 92 // CompleteResume will not trigger FlushQueue while there is an ongoing flush. 93 if (needResumeOnOtherThread) { 94 ResumeInternal(); 95 } 96 } 97 98 void ChannelEventQueue::Suspend() { 99 MutexAutoLock lock(mMutex); 100 SuspendInternal(); 101 } 102 103 void ChannelEventQueue::SuspendInternal() { 104 mMutex.AssertCurrentThreadOwns(); 105 106 mSuspended = true; 107 mSuspendCount++; 108 } 109 110 void ChannelEventQueue::Resume() { 111 MutexAutoLock lock(mMutex); 112 ResumeInternal(); 113 } 114 115 void ChannelEventQueue::ResumeInternal() { 116 mMutex.AssertCurrentThreadOwns(); 117 118 // Resuming w/o suspend: error in debug mode, ignore in build 119 MOZ_ASSERT(mSuspendCount > 0); 120 if (mSuspendCount <= 0) { 121 return; 122 } 123 124 if (!--mSuspendCount) { 125 if (mEventQueue.IsEmpty() || !!mForcedCount) { 126 // Nothing in queue to flush or waiting for AutoEventEnqueuer to 127 // finish the force enqueue period, simply clear the flag. 128 mSuspended = false; 129 return; 130 } 131 132 // Hold a strong reference of mOwner to avoid the channel release 133 // before CompleteResume was executed. 134 class CompleteResumeRunnable : public Runnable { 135 public: 136 explicit CompleteResumeRunnable(ChannelEventQueue* aQueue, 137 nsISupports* aOwner) 138 : Runnable("CompleteResumeRunnable"), 139 mQueue(aQueue), 140 mOwner(aOwner) {} 141 142 NS_IMETHOD Run() override { 143 mQueue->CompleteResume(); 144 return NS_OK; 145 } 146 147 private: 148 virtual ~CompleteResumeRunnable() = default; 149 150 RefPtr<ChannelEventQueue> mQueue; 151 nsCOMPtr<nsISupports> mOwner; 152 }; 153 154 if (!mOwner) { 155 return; 156 } 157 158 // Worker thread requires a CancelableRunnable. 159 RefPtr<Runnable> event = new CompleteResumeRunnable(this, mOwner); 160 161 nsCOMPtr<nsIEventTarget> target; 162 target = mEventQueue[0]->GetEventTarget(); 163 MOZ_ASSERT(target); 164 165 (void)NS_WARN_IF( 166 NS_FAILED(target->Dispatch(event.forget(), NS_DISPATCH_NORMAL))); 167 } 168 } 169 170 bool ChannelEventQueue::MaybeSuspendIfEventsAreSuppressed() { 171 // We only ever need to suppress events on the main thread, since this is 172 // where content scripts can run. 173 if (!NS_IsMainThread()) { 174 return false; 175 } 176 177 // Only suppress events for queues associated with XHRs, as these can cause 178 // content scripts to run. 179 if (mHasCheckedForAsyncXMLHttpRequest && !mForAsyncXMLHttpRequest) { 180 return false; 181 } 182 183 mMutex.AssertCurrentThreadOwns(); 184 nsCOMPtr<nsIChannel> channel(do_QueryInterface(mOwner)); 185 if (!channel) { 186 return false; 187 } 188 189 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 190 // Figure out if this is for an Async XHR, if we haven't done so already. 191 // We don't want to suspend Sync XHRs, as they'll always suspend event 192 // handling on the document, but we still need to process events for them. 193 if (!mHasCheckedForAsyncXMLHttpRequest) { 194 nsContentPolicyType contentType = loadInfo->InternalContentPolicyType(); 195 mForAsyncXMLHttpRequest = 196 contentType == nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC; 197 mHasCheckedForAsyncXMLHttpRequest = true; 198 199 if (!mForAsyncXMLHttpRequest) { 200 return false; 201 } 202 } 203 204 // Suspend the queue if the associated document has suppressed event handling. 205 RefPtr<dom::Document> document; 206 loadInfo->GetLoadingDocument(getter_AddRefs(document)); 207 if (document && document->EventHandlingSuppressed()) { 208 document->AddSuspendedChannelEventQueue(this); 209 SuspendInternal(); 210 return true; 211 } 212 213 return false; 214 } 215 216 } // namespace net 217 } // namespace mozilla