Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/nsBaseAppShell.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "base/message_loop.h"
7
8
#include "nsBaseAppShell.h"
9
#include "nsExceptionHandler.h"
10
#include "nsThreadUtils.h"
11
#include "nsIObserverService.h"
12
#include "nsServiceManagerUtils.h"
13
#include "mozilla/Services.h"
14
#include "nsXULAppAPI.h"
15
16
// When processing the next thread event, the appshell may process native
17
// events (if not in performance mode), which can result in suppressing the
18
// next thread event for at most this many ticks:
19
0
#define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(10)
20
21
NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver)
22
23
nsBaseAppShell::nsBaseAppShell()
24
  : mSuspendNativeCount(0)
25
  , mEventloopNestingLevel(0)
26
  , mBlockedWait(nullptr)
27
  , mFavorPerf(0)
28
  , mNativeEventPending(false)
29
  , mStarvationDelay(0)
30
  , mSwitchTime(0)
31
  , mLastNativeEventTime(0)
32
  , mEventloopNestingState(eEventloopNone)
33
  , mRunning(false)
34
  , mExiting(false)
35
  , mBlockNativeEvent(false)
36
  , mProcessedGeckoEvents(false)
37
0
{
38
0
}
39
40
nsBaseAppShell::~nsBaseAppShell()
41
0
{
42
0
}
43
44
nsresult
45
nsBaseAppShell::Init()
46
0
{
47
0
  // Configure ourselves as an observer for the current thread:
48
0
49
0
  if (XRE_UseNativeEventProcessing()) {
50
0
    nsCOMPtr<nsIThreadInternal> threadInt =
51
0
      do_QueryInterface(NS_GetCurrentThread());
52
0
    NS_ENSURE_STATE(threadInt);
53
0
54
0
    threadInt->SetObserver(this);
55
0
  }
56
0
57
0
  nsCOMPtr<nsIObserverService> obsSvc =
58
0
    mozilla::services::GetObserverService();
59
0
  if (obsSvc)
60
0
    obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
61
0
  return NS_OK;
62
0
}
63
64
// Called by nsAppShell's native event callback
65
void
66
nsBaseAppShell::NativeEventCallback()
67
0
{
68
0
  if (!mNativeEventPending.exchange(false))
69
0
    return;
70
0
71
0
  // If DoProcessNextNativeEvent is on the stack, then we assume that we can
72
0
  // just unwind and let nsThread::ProcessNextEvent process the next event.
73
0
  // However, if we are called from a nested native event loop (maybe via some
74
0
  // plug-in or library function), then go ahead and process Gecko events now.
75
0
  if (mEventloopNestingState == eEventloopXPCOM) {
76
0
    mEventloopNestingState = eEventloopOther;
77
0
    // XXX there is a tiny risk we will never get a new NativeEventCallback,
78
0
    // XXX see discussion in bug 389931.
79
0
    return;
80
0
  }
81
0
82
0
  // nsBaseAppShell::Run is not being used to pump events, so this may be
83
0
  // our only opportunity to process pending gecko events.
84
0
85
0
  nsIThread *thread = NS_GetCurrentThread();
86
0
  bool prevBlockNativeEvent = mBlockNativeEvent;
87
0
  if (mEventloopNestingState == eEventloopOther) {
88
0
    if (!NS_HasPendingEvents(thread))
89
0
      return;
90
0
    // We're in a nested native event loop and have some gecko events to
91
0
    // process.  While doing that we block processing native events from the
92
0
    // appshell - instead, we want to get back to the nested native event
93
0
    // loop ASAP (bug 420148).
94
0
    mBlockNativeEvent = true;
95
0
  }
96
0
97
0
  IncrementEventloopNestingLevel();
98
0
  EventloopNestingState prevVal = mEventloopNestingState;
99
0
  NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
100
0
  mProcessedGeckoEvents = true;
101
0
  mEventloopNestingState = prevVal;
102
0
  mBlockNativeEvent = prevBlockNativeEvent;
103
0
104
0
  // Continue processing pending events later (we don't want to starve the
105
0
  // embedders event loop).
106
0
  if (NS_HasPendingEvents(thread))
107
0
    DoProcessMoreGeckoEvents();
108
0
109
0
  DecrementEventloopNestingLevel();
110
0
}
111
112
// Note, this is currently overidden on windows, see comments in nsAppShell for
113
// details.
114
void
115
nsBaseAppShell::DoProcessMoreGeckoEvents()
116
0
{
117
0
  OnDispatchedEvent();
118
0
}
119
120
121
// Main thread via OnProcessNextEvent below
122
bool
123
nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
124
0
{
125
0
  // The next native event to be processed may trigger our NativeEventCallback,
126
0
  // in which case we do not want it to process any thread events since we'll
127
0
  // do that when this function returns.
128
0
  //
129
0
  // If the next native event is not our NativeEventCallback, then we may end
130
0
  // up recursing into this function.
131
0
  //
132
0
  // However, if the next native event is not our NativeEventCallback, but it
133
0
  // results in another native event loop, then our NativeEventCallback could
134
0
  // fire and it will see mEventloopNestingState as eEventloopOther.
135
0
  //
136
0
  EventloopNestingState prevVal = mEventloopNestingState;
137
0
  mEventloopNestingState = eEventloopXPCOM;
138
0
139
0
  IncrementEventloopNestingLevel();
140
0
  bool result = ProcessNextNativeEvent(mayWait);
141
0
  DecrementEventloopNestingLevel();
142
0
143
0
  mEventloopNestingState = prevVal;
144
0
  return result;
145
0
}
146
147
//-------------------------------------------------------------------------
148
// nsIAppShell methods:
149
150
NS_IMETHODIMP
151
nsBaseAppShell::Run(void)
152
0
{
153
0
  NS_ENSURE_STATE(!mRunning);  // should not call Run twice
154
0
  mRunning = true;
155
0
156
0
  nsIThread *thread = NS_GetCurrentThread();
157
0
158
0
  MessageLoop::current()->Run();
159
0
160
0
  NS_ProcessPendingEvents(thread);
161
0
162
0
  mRunning = false;
163
0
  return NS_OK;
164
0
}
165
166
NS_IMETHODIMP
167
nsBaseAppShell::Exit(void)
168
0
{
169
0
  if (mRunning && !mExiting) {
170
0
    MessageLoop::current()->Quit();
171
0
  }
172
0
  mExiting = true;
173
0
  return NS_OK;
174
0
}
175
176
NS_IMETHODIMP
177
nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation,
178
                                     uint32_t starvationDelay)
179
0
{
180
0
  mStarvationDelay = PR_MillisecondsToInterval(starvationDelay);
181
0
  if (favorPerfOverStarvation) {
182
0
    ++mFavorPerf;
183
0
  } else {
184
0
    --mFavorPerf;
185
0
    mSwitchTime = PR_IntervalNow();
186
0
  }
187
0
  return NS_OK;
188
0
}
189
190
NS_IMETHODIMP
191
nsBaseAppShell::SuspendNative()
192
0
{
193
0
  ++mSuspendNativeCount;
194
0
  return NS_OK;
195
0
}
196
197
NS_IMETHODIMP
198
nsBaseAppShell::ResumeNative()
199
0
{
200
0
  --mSuspendNativeCount;
201
0
  NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
202
0
  return NS_OK;
203
0
}
204
205
NS_IMETHODIMP
206
nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult)
207
0
{
208
0
  NS_ENSURE_ARG_POINTER(aNestingLevelResult);
209
0
210
0
  *aNestingLevelResult = mEventloopNestingLevel;
211
0
212
0
  return NS_OK;
213
0
}
214
215
//-------------------------------------------------------------------------
216
// nsIThreadObserver methods:
217
218
// Called from any thread
219
NS_IMETHODIMP
220
nsBaseAppShell::OnDispatchedEvent()
221
0
{
222
0
  if (mBlockNativeEvent)
223
0
    return NS_OK;
224
0
225
0
  if (mNativeEventPending.exchange(true))
226
0
    return NS_OK;
227
0
228
0
  // Returns on the main thread in NativeEventCallback above
229
0
  ScheduleNativeEventCallback();
230
0
  return NS_OK;
231
0
}
232
233
// Called from the main thread
234
NS_IMETHODIMP
235
nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait)
236
0
{
237
0
  if (mBlockNativeEvent) {
238
0
    if (!mayWait)
239
0
      return NS_OK;
240
0
    // Hmm, we're in a nested native event loop and would like to get
241
0
    // back to it ASAP, but it seems a gecko event has caused us to
242
0
    // spin up a nested XPCOM event loop (eg. modal window), so we
243
0
    // really must start processing native events here again.
244
0
    mBlockNativeEvent = false;
245
0
    if (NS_HasPendingEvents(thr))
246
0
      OnDispatchedEvent(); // in case we blocked it earlier
247
0
  }
248
0
249
0
  PRIntervalTime start = PR_IntervalNow();
250
0
  PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
251
0
252
0
  // Unblock outer nested wait loop (below).
253
0
  if (mBlockedWait)
254
0
    *mBlockedWait = false;
255
0
256
0
  bool *oldBlockedWait = mBlockedWait;
257
0
  mBlockedWait = &mayWait;
258
0
259
0
  // When mayWait is true, we need to make sure that there is an event in the
260
0
  // thread's event queue before we return.  Otherwise, the thread will block
261
0
  // on its event queue waiting for an event.
262
0
  bool needEvent = mayWait;
263
0
  // Reset prior to invoking DoProcessNextNativeEvent which might cause
264
0
  // NativeEventCallback to process gecko events.
265
0
  mProcessedGeckoEvents = false;
266
0
267
0
  if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
268
0
    // Favor pending native events
269
0
    PRIntervalTime now = start;
270
0
    bool keepGoing;
271
0
    do {
272
0
      mLastNativeEventTime = now;
273
0
      keepGoing = DoProcessNextNativeEvent(false);
274
0
    } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
275
0
  } else {
276
0
    // Avoid starving native events completely when in performance mode
277
0
    if (start - mLastNativeEventTime > limit) {
278
0
      mLastNativeEventTime = start;
279
0
      DoProcessNextNativeEvent(false);
280
0
    }
281
0
  }
282
0
283
0
  while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
284
0
    // If we have been asked to exit from Run, then we should not wait for
285
0
    // events to process.  Note that an inner nested event loop causes
286
0
    // 'mayWait' to become false too, through 'mBlockedWait'.
287
0
    if (mExiting)
288
0
      mayWait = false;
289
0
290
0
    mLastNativeEventTime = PR_IntervalNow();
291
0
    if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
292
0
      break;
293
0
  }
294
0
295
0
  mBlockedWait = oldBlockedWait;
296
0
297
0
  // Make sure that the thread event queue does not block on its monitor, as
298
0
  // it normally would do if it did not have any pending events.  To avoid
299
0
  // that, we simply insert a dummy event into its queue during shutdown.
300
0
  if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
301
0
    DispatchDummyEvent(thr);
302
0
  }
303
0
304
0
  return NS_OK;
305
0
}
306
307
bool
308
nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
309
0
{
310
0
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
311
0
312
0
  if (!mDummyEvent)
313
0
    mDummyEvent = new mozilla::Runnable("DummyEvent");
314
0
315
0
  return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
316
0
}
317
318
void
319
nsBaseAppShell::IncrementEventloopNestingLevel()
320
0
{
321
0
  ++mEventloopNestingLevel;
322
0
  CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
323
0
}
324
325
void
326
nsBaseAppShell::DecrementEventloopNestingLevel()
327
0
{
328
0
  --mEventloopNestingLevel;
329
0
  CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
330
0
}
331
332
// Called from the main thread
333
NS_IMETHODIMP
334
nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
335
                                      bool eventWasProcessed)
336
0
{
337
0
  return NS_OK;
338
0
}
339
340
NS_IMETHODIMP
341
nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
342
                        const char16_t *data)
343
0
{
344
0
  NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
345
0
  Exit();
346
0
  return NS_OK;
347
0
}