1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsMessageLoop.h"
8 #include "mozilla/WeakPtr.h"
9 #include "base/message_loop.h"
10 #include "base/task.h"
11 #include "nsINamed.h"
12 #include "nsIRunnable.h"
13 #include "nsITimer.h"
14 #include "nsCOMPtr.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsThreadUtils.h"
17
18 using namespace mozilla;
19
20 namespace {
21
22 /**
23 * This Task runs its nsIRunnable when Run() is called, or after
24 * aEnsureRunsAfterMS milliseconds have elapsed since the object was
25 * constructed.
26 *
27 * Note that the MessageLoop owns this object and will delete it after it calls
28 * Run(). Tread lightly.
29 */
30 class MessageLoopIdleTask : public Runnable, public SupportsWeakPtr {
31 public:
32 MessageLoopIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS);
33 NS_IMETHOD Run() override;
34
35 private:
36 nsresult Init(uint32_t aEnsureRunsAfterMS);
37
38 nsCOMPtr<nsIRunnable> mTask;
39 nsCOMPtr<nsITimer> mTimer;
40
41 virtual ~MessageLoopIdleTask() = default;
42 };
43
44 /**
45 * This timer callback calls MessageLoopIdleTask::Run() when its timer fires.
46 * (The timer can't call back into MessageLoopIdleTask directly since that's
47 * not a refcounted object; it's owned by the MessageLoop.)
48 *
49 * We keep a weak reference to the MessageLoopIdleTask, although a raw pointer
50 * should in theory suffice: When the MessageLoopIdleTask runs (right before
51 * the MessageLoop deletes it), it cancels its timer. But the weak pointer
52 * saves us from worrying about an edge case somehow messing us up here.
53 */
54 class MessageLoopTimerCallback : public nsITimerCallback, public nsINamed {
55 public:
56 explicit MessageLoopTimerCallback(MessageLoopIdleTask* aTask);
57
58 NS_DECL_ISUPPORTS
59 NS_DECL_NSITIMERCALLBACK
60
GetName(nsACString & aName)61 NS_IMETHOD GetName(nsACString& aName) override {
62 aName.AssignLiteral("MessageLoopTimerCallback");
63 return NS_OK;
64 }
65
66 private:
67 WeakPtr<MessageLoopIdleTask> mTask;
68
69 virtual ~MessageLoopTimerCallback() = default;
70 };
71
MessageLoopIdleTask(nsIRunnable * aTask,uint32_t aEnsureRunsAfterMS)72 MessageLoopIdleTask::MessageLoopIdleTask(nsIRunnable* aTask,
73 uint32_t aEnsureRunsAfterMS)
74 : mozilla::Runnable("MessageLoopIdleTask"), mTask(aTask) {
75 // Init() really shouldn't fail, but if it does, we schedule our runnable
76 // immediately, because it's more important to guarantee that we run the task
77 // eventually than it is to run the task when we're idle.
78 nsresult rv = Init(aEnsureRunsAfterMS);
79 if (NS_FAILED(rv)) {
80 NS_WARNING(
81 "Running idle task early because we couldn't initialize our timer.");
82 NS_DispatchToCurrentThread(mTask);
83
84 mTask = nullptr;
85 mTimer = nullptr;
86 }
87 }
88
Init(uint32_t aEnsureRunsAfterMS)89 nsresult MessageLoopIdleTask::Init(uint32_t aEnsureRunsAfterMS) {
90 RefPtr<MessageLoopTimerCallback> callback =
91 new MessageLoopTimerCallback(this);
92 return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback,
93 aEnsureRunsAfterMS, nsITimer::TYPE_ONE_SHOT);
94 }
95
96 NS_IMETHODIMP
Run()97 MessageLoopIdleTask::Run() {
98 // Null out our pointers because if Run() was called by the timer, this
99 // object will be kept alive by the MessageLoop until the MessageLoop calls
100 // Run().
101
102 if (mTimer) {
103 mTimer->Cancel();
104 mTimer = nullptr;
105 }
106
107 if (mTask) {
108 mTask->Run();
109 mTask = nullptr;
110 }
111
112 return NS_OK;
113 }
114
MessageLoopTimerCallback(MessageLoopIdleTask * aTask)115 MessageLoopTimerCallback::MessageLoopTimerCallback(MessageLoopIdleTask* aTask)
116 : mTask(aTask) {}
117
118 NS_IMETHODIMP
Notify(nsITimer * aTimer)119 MessageLoopTimerCallback::Notify(nsITimer* aTimer) {
120 // We don't expect to hit the case when the timer fires but mTask has been
121 // deleted, because mTask should cancel the timer before the mTask is
122 // deleted. But you never know...
123 NS_WARNING_ASSERTION(mTask, "This timer shouldn't have fired.");
124
125 if (mTask) {
126 mTask->Run();
127 }
128 return NS_OK;
129 }
130
131 NS_IMPL_ISUPPORTS(MessageLoopTimerCallback, nsITimerCallback, nsINamed)
132
133 } // namespace
134
NS_IMPL_ISUPPORTS(nsMessageLoop,nsIMessageLoop)135 NS_IMPL_ISUPPORTS(nsMessageLoop, nsIMessageLoop)
136
137 NS_IMETHODIMP
138 nsMessageLoop::PostIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS) {
139 // The message loop owns MessageLoopIdleTask and deletes it after calling
140 // Run(). Be careful...
141 RefPtr<MessageLoopIdleTask> idle =
142 new MessageLoopIdleTask(aTask, aEnsureRunsAfterMS);
143 MessageLoop::current()->PostIdleTask(idle.forget());
144
145 return NS_OK;
146 }
147
nsMessageLoopConstructor(nsISupports * aOuter,const nsIID & aIID,void ** aInstancePtr)148 nsresult nsMessageLoopConstructor(nsISupports* aOuter, const nsIID& aIID,
149 void** aInstancePtr) {
150 if (NS_WARN_IF(aOuter)) {
151 return NS_ERROR_NO_AGGREGATION;
152 }
153 nsISupports* messageLoop = new nsMessageLoop();
154 return messageLoop->QueryInterface(aIID, aInstancePtr);
155 }
156