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 "mozilla/Unused.h"
12 #include "nsIChannel.h"
13 #include "mozilla/dom/Document.h"
14 #include "nsThreadUtils.h"
15
16 namespace mozilla {
17 namespace net {
18
TakeEvent()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
FlushQueue()33 void ChannelEventQueue::FlushQueue() {
34 // Events flushed could include destruction of channel (and our own
35 // destructor) unless we make sure its refcount doesn't drop to 0 while this
36 // method is running.
37 nsCOMPtr<nsISupports> kungFuDeathGrip(mOwner);
38 mozilla::Unused << kungFuDeathGrip; // Not used in this function
39
40 #ifdef DEBUG
41 {
42 MutexAutoLock lock(mMutex);
43 MOZ_ASSERT(mFlushing);
44 }
45 #endif // DEBUG
46
47 bool needResumeOnOtherThread = false;
48
49 while (true) {
50 UniquePtr<ChannelEvent> event;
51 {
52 MutexAutoLock lock(mMutex);
53 event.reset(TakeEvent());
54 if (!event) {
55 MOZ_ASSERT(mFlushing);
56 mFlushing = false;
57 MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount));
58 break;
59 }
60 }
61
62 nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
63 MOZ_ASSERT(target);
64
65 bool isCurrentThread = false;
66 nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
67 if (NS_WARN_IF(NS_FAILED(rv))) {
68 // Simply run this event on current thread if we are not sure about it
69 // in release channel, or assert in Aurora/Nightly channel.
70 MOZ_DIAGNOSTIC_ASSERT(false);
71 isCurrentThread = true;
72 }
73
74 if (!isCurrentThread) {
75 // Next event needs to run on another thread. Put it back to
76 // the front of the queue can try resume on that thread.
77 Suspend();
78 PrependEvent(std::move(event));
79
80 needResumeOnOtherThread = true;
81 {
82 MutexAutoLock lock(mMutex);
83 MOZ_ASSERT(mFlushing);
84 mFlushing = false;
85 MOZ_ASSERT(!mEventQueue.IsEmpty());
86 }
87 break;
88 }
89
90 event->Run();
91 } // end of while(true)
92
93 // The flush procedure is aborted because next event cannot be run on current
94 // thread. We need to resume the event processing right after flush procedure
95 // is finished.
96 // Note: we cannot call Resume() while "mFlushing == true" because
97 // CompleteResume will not trigger FlushQueue while there is an ongoing flush.
98 if (needResumeOnOtherThread) {
99 Resume();
100 }
101 }
102
Suspend()103 void ChannelEventQueue::Suspend() {
104 MutexAutoLock lock(mMutex);
105 SuspendInternal();
106 }
107
SuspendInternal()108 void ChannelEventQueue::SuspendInternal() {
109 mMutex.AssertCurrentThreadOwns();
110
111 mSuspended = true;
112 mSuspendCount++;
113 }
114
Resume()115 void ChannelEventQueue::Resume() {
116 MutexAutoLock lock(mMutex);
117 ResumeInternal();
118 }
119
ResumeInternal()120 void ChannelEventQueue::ResumeInternal() {
121 mMutex.AssertCurrentThreadOwns();
122
123 // Resuming w/o suspend: error in debug mode, ignore in build
124 MOZ_ASSERT(mSuspendCount > 0);
125 if (mSuspendCount <= 0) {
126 return;
127 }
128
129 if (!--mSuspendCount) {
130 if (mEventQueue.IsEmpty() || !!mForcedCount) {
131 // Nothing in queue to flush or waiting for AutoEventEnqueuer to
132 // finish the force enqueue period, simply clear the flag.
133 mSuspended = false;
134 return;
135 }
136
137 // Hold a strong reference of mOwner to avoid the channel release
138 // before CompleteResume was executed.
139 class CompleteResumeRunnable : public CancelableRunnable {
140 public:
141 explicit CompleteResumeRunnable(ChannelEventQueue* aQueue,
142 nsISupports* aOwner)
143 : CancelableRunnable("CompleteResumeRunnable"),
144 mQueue(aQueue),
145 mOwner(aOwner) {}
146
147 NS_IMETHOD Run() override {
148 mQueue->CompleteResume();
149 return NS_OK;
150 }
151
152 private:
153 virtual ~CompleteResumeRunnable() = default;
154
155 RefPtr<ChannelEventQueue> mQueue;
156 nsCOMPtr<nsISupports> mOwner;
157 };
158
159 // Worker thread requires a CancelableRunnable.
160 RefPtr<Runnable> event = new CompleteResumeRunnable(this, mOwner);
161
162 nsCOMPtr<nsIEventTarget> target;
163 target = mEventQueue[0]->GetEventTarget();
164 MOZ_ASSERT(target);
165
166 Unused << NS_WARN_IF(
167 NS_FAILED(target->Dispatch(event.forget(), NS_DISPATCH_NORMAL)));
168 }
169 }
170
MaybeSuspendIfEventsAreSuppressed()171 bool ChannelEventQueue::MaybeSuspendIfEventsAreSuppressed() {
172 // We only ever need to suppress events on the main thread, since this is
173 // where content scripts can run.
174 if (!NS_IsMainThread()) {
175 return false;
176 }
177
178 // Only suppress events for queues associated with XHRs, as these can cause
179 // content scripts to run.
180 if (mHasCheckedForXMLHttpRequest && !mForXMLHttpRequest) {
181 return false;
182 }
183
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 XHR, if we haven't done so already.
191 if (!mHasCheckedForXMLHttpRequest) {
192 nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
193 mForXMLHttpRequest =
194 (contentType == nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST);
195 mHasCheckedForXMLHttpRequest = true;
196
197 if (!mForXMLHttpRequest) {
198 return false;
199 }
200 }
201
202 // Suspend the queue if the associated document has suppressed event handling,
203 // *and* it is not in the middle of a synchronous operation that might require
204 // XHR events to be processed (such as a synchronous XHR).
205 RefPtr<dom::Document> document;
206 loadInfo->GetLoadingDocument(getter_AddRefs(document));
207 if (document && document->EventHandlingSuppressed() &&
208 !document->IsInSyncOperation()) {
209 document->AddSuspendedChannelEventQueue(this);
210 SuspendInternal();
211 return true;
212 }
213
214 return false;
215 }
216
217 } // namespace net
218 } // namespace mozilla
219