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