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