1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 "gfxFontInfoLoader.h"
7 #include "mozilla/gfx/Logging.h"
8 #include "nsCRT.h"
9 #include "nsIObserverService.h"
10 #include "nsXPCOM.h"        // for gXPCOMThreadsShutDown
11 #include "nsThreadUtils.h"  // for nsRunnable
12 #include "gfxPlatformFontList.h"
13 
14 #ifdef XP_WIN
15 #  include <windows.h>
16 #endif
17 
18 using namespace mozilla;
19 using services::GetObserverService;
20 
21 #define LOG_FONTINIT(args) \
22   MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
23 #define LOG_FONTINIT_ENABLED() \
24   MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
25 
Load()26 void FontInfoData::Load() {
27   TimeStamp start = TimeStamp::Now();
28 
29   uint32_t i, n = mFontFamiliesToLoad.Length();
30   mLoadStats.families = n;
31   for (i = 0; i < n && !mCanceled; i++) {
32     // font file memory mapping sometimes causes exceptions - bug 1100949
33     MOZ_SEH_TRY { LoadFontFamilyData(mFontFamiliesToLoad[i]); }
34     MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
35       gfxCriticalError() << "Exception occurred reading font data for "
36                          << mFontFamiliesToLoad[i].get();
37     }
38   }
39 
40   mLoadTime = TimeStamp::Now() - start;
41 }
42 
43 class FontInfoLoadCompleteEvent : public Runnable {
44   virtual ~FontInfoLoadCompleteEvent() = default;
45 
46  public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(FontInfoLoadCompleteEvent,Runnable)47   NS_INLINE_DECL_REFCOUNTING_INHERITED(FontInfoLoadCompleteEvent, Runnable)
48 
49   explicit FontInfoLoadCompleteEvent(FontInfoData* aFontInfo)
50       : mozilla::Runnable("FontInfoLoadCompleteEvent"), mFontInfo(aFontInfo) {}
51 
52   NS_IMETHOD Run() override;
53 
54  private:
55   RefPtr<FontInfoData> mFontInfo;
56 };
57 
58 class AsyncFontInfoLoader : public Runnable {
59   virtual ~AsyncFontInfoLoader() = default;
60 
61  public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(AsyncFontInfoLoader,Runnable)62   NS_INLINE_DECL_REFCOUNTING_INHERITED(AsyncFontInfoLoader, Runnable)
63 
64   explicit AsyncFontInfoLoader(FontInfoData* aFontInfo)
65       : mozilla::Runnable("AsyncFontInfoLoader"), mFontInfo(aFontInfo) {
66     mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
67   }
68 
69   NS_IMETHOD Run() override;
70 
71  private:
72   RefPtr<FontInfoData> mFontInfo;
73   RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
74 };
75 
76 class ShutdownThreadEvent : public Runnable {
77   virtual ~ShutdownThreadEvent() = default;
78 
79  public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(ShutdownThreadEvent,Runnable)80   NS_INLINE_DECL_REFCOUNTING_INHERITED(ShutdownThreadEvent, Runnable)
81 
82   explicit ShutdownThreadEvent(nsIThread* aThread)
83       : mozilla::Runnable("ShutdownThreadEvent"), mThread(aThread) {}
Run()84   NS_IMETHOD Run() override {
85     mThread->Shutdown();
86     return NS_OK;
87   }
88 
89  private:
90   nsCOMPtr<nsIThread> mThread;
91 };
92 
93 // runs on main thread after async font info loading is done
Run()94 nsresult FontInfoLoadCompleteEvent::Run() {
95   gfxFontInfoLoader* loader =
96       static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
97 
98   loader->FinalizeLoader(mFontInfo);
99 
100   return NS_OK;
101 }
102 
103 // runs on separate thread
Run()104 nsresult AsyncFontInfoLoader::Run() {
105   // load platform-specific font info
106   mFontInfo->Load();
107 
108   // post a completion event that transfer the data to the fontlist
109   NS_DispatchToMainThread(mCompleteEvent);
110 
111   return NS_OK;
112 }
113 
114 NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
115 
116 static bool sFontLoaderShutdownObserved = false;
117 
118 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * someData)119 gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports* aSubject,
120                                              const char* aTopic,
121                                              const char16_t* someData) {
122   if (!nsCRT::strcmp(aTopic, "quit-application") ||
123       !nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
124     mLoader->CancelLoader();
125     sFontLoaderShutdownObserved = true;
126   } else {
127     MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
128   }
129   return NS_OK;
130 }
131 
132 // StartLoader is usually called at startup with a (prefs-derived) delay value,
133 // so that the async loader runs shortly after startup, to avoid competing for
134 // disk i/o etc with other more critical operations.
135 // However, it may be called with aDelay=0 if we find that the font info (e.g.
136 // localized names) is needed for layout. In this case we start the loader
137 // immediately; however, it is still an async process and we may use fallback
138 // fonts to satisfy layout until it completes.
StartLoader(uint32_t aDelay)139 void gfxFontInfoLoader::StartLoader(uint32_t aDelay) {
140   if (aDelay == 0 && (mState == stateTimerOff || mState == stateAsyncLoad)) {
141     // We were asked to load (async) without delay, but have already started,
142     // so just return and let the loader proceed.
143     return;
144   }
145 
146   NS_ASSERTION(!mFontInfo, "fontinfo should be null when starting font loader");
147 
148   // sanity check
149   if (mState != stateInitial && mState != stateTimerOff &&
150       mState != stateTimerOnDelay) {
151     CancelLoader();
152   }
153 
154   AddShutdownObserver();
155 
156   // Caller asked for a delay? ==> start async thread after a delay
157   if (aDelay) {
158     NS_ASSERTION(!sFontLoaderShutdownObserved,
159                  "Bug 1508626 - Setting delay timer for font loader after "
160                  "shutdown observed");
161     NS_ASSERTION(!gXPCOMThreadsShutDown,
162                  "Bug 1508626 - Setting delay timer for font loader after "
163                  "shutdown but before observer");
164     // Set up delay timer, or if there is already a timer in place, just
165     // leave it to do its thing. (This can happen if a StartLoader runnable
166     // was posted to the main thread from the InitFontList thread, but then
167     // before it had a chance to run and call StartLoader, the main thread
168     // re-initialized the list due to a platform notification and called
169     // StartLoader directly.)
170     if (mTimer) {
171       return;
172     }
173     mTimer = NS_NewTimer();
174     mTimer->InitWithNamedFuncCallback(DelayedStartCallback, this, aDelay,
175                                       nsITimer::TYPE_ONE_SHOT,
176                                       "gfxFontInfoLoader::StartLoader");
177     mState = stateTimerOnDelay;
178     return;
179   }
180 
181   // Either we've been called back by the DelayedStartCallback when its timer
182   // fired, or a layout caller has passed aDelay=0 to ask the loader to run
183   // without further delay.
184 
185   // Cancel the delay timer, if any.
186   if (mTimer) {
187     mTimer->Cancel();
188     mTimer = nullptr;
189   }
190 
191   NS_ASSERTION(
192       !sFontLoaderShutdownObserved,
193       "Bug 1508626 - Initializing font loader after shutdown observed");
194   NS_ASSERTION(!gXPCOMThreadsShutDown,
195                "Bug 1508626 - Initializing font loader after shutdown but "
196                "before observer");
197 
198   mFontInfo = CreateFontInfoData();
199   if (!mFontInfo) {
200     // The platform doesn't want anything loaded, so just bail out.
201     mState = stateTimerOff;
202     return;
203   }
204 
205   // initialize
206   InitLoader();
207 
208   // start async load
209   nsresult rv = NS_NewNamedThread("Font Loader",
210                                   getter_AddRefs(mFontLoaderThread), nullptr);
211   if (NS_WARN_IF(NS_FAILED(rv))) {
212     return;
213   }
214 
215   PRThread* prThread;
216   if (NS_SUCCEEDED(mFontLoaderThread->GetPRThread(&prThread))) {
217     PR_SetThreadPriority(prThread, PR_PRIORITY_LOW);
218   }
219 
220   mState = stateAsyncLoad;
221 
222   nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);
223 
224   mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL);
225 
226   if (LOG_FONTINIT_ENABLED()) {
227     LOG_FONTINIT(
228         ("(fontinit) fontloader started (fontinfo: %p)\n", mFontInfo.get()));
229   }
230 }
231 
232 class FinalizeLoaderRunnable : public Runnable {
233   virtual ~FinalizeLoaderRunnable() = default;
234 
235  public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable,Runnable)236   NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable, Runnable)
237 
238   explicit FinalizeLoaderRunnable(gfxFontInfoLoader* aLoader)
239       : mozilla::Runnable("FinalizeLoaderRunnable"), mLoader(aLoader) {}
240 
Run()241   NS_IMETHOD Run() override {
242     nsresult rv;
243     if (mLoader->LoadFontInfo()) {
244       mLoader->CancelLoader();
245       rv = NS_OK;
246     } else {
247       nsCOMPtr<nsIRunnable> runnable = this;
248       rv = NS_DispatchToCurrentThreadQueue(
249           runnable.forget(), PR_INTERVAL_NO_TIMEOUT, EventQueuePriority::Idle);
250     }
251     return rv;
252   }
253 
254  private:
255   gfxFontInfoLoader* mLoader;
256 };
257 
FinalizeLoader(FontInfoData * aFontInfo)258 void gfxFontInfoLoader::FinalizeLoader(FontInfoData* aFontInfo) {
259   // Avoid loading data if loader has already been canceled.
260   // This should mean that CancelLoader() ran and the Load
261   // thread has already Shutdown(), and likely before processing
262   // the Shutdown event it handled the load event and sent back
263   // our Completion event, thus we end up here.
264   if (mState != stateAsyncLoad || mFontInfo != aFontInfo) {
265     return;
266   }
267 
268   mLoadTime = mFontInfo->mLoadTime;
269 
270   MOZ_ASSERT(NS_IsMainThread());
271   nsCOMPtr<nsIRunnable> runnable = new FinalizeLoaderRunnable(this);
272   if (NS_FAILED(NS_DispatchToCurrentThreadQueue(runnable.forget(),
273                                                 PR_INTERVAL_NO_TIMEOUT,
274                                                 EventQueuePriority::Idle))) {
275     NS_WARNING("Failed to finalize async font info");
276   }
277 }
278 
CancelLoader()279 void gfxFontInfoLoader::CancelLoader() {
280   if (mState == stateInitial) {
281     return;
282   }
283   mState = stateTimerOff;
284   if (mTimer) {
285     mTimer->Cancel();
286     mTimer = nullptr;
287   }
288   if (mFontInfo)  // null during any initial delay
289     mFontInfo->mCanceled = true;
290   if (mFontLoaderThread) {
291     NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread));
292     mFontLoaderThread = nullptr;
293   }
294   RemoveShutdownObserver();
295   CleanupLoader();
296 }
297 
~gfxFontInfoLoader()298 gfxFontInfoLoader::~gfxFontInfoLoader() {
299   RemoveShutdownObserver();
300   MOZ_COUNT_DTOR(gfxFontInfoLoader);
301 }
302 
AddShutdownObserver()303 void gfxFontInfoLoader::AddShutdownObserver() {
304   if (mObserver) {
305     return;
306   }
307 
308   nsCOMPtr<nsIObserverService> obs = GetObserverService();
309   if (obs) {
310     mObserver = new ShutdownObserver(this);
311     obs->AddObserver(mObserver, "quit-application", false);
312     obs->AddObserver(mObserver, "xpcom-shutdown", false);
313   }
314 }
315 
RemoveShutdownObserver()316 void gfxFontInfoLoader::RemoveShutdownObserver() {
317   if (mObserver) {
318     nsCOMPtr<nsIObserverService> obs = GetObserverService();
319     if (obs) {
320       obs->RemoveObserver(mObserver, "quit-application");
321       obs->RemoveObserver(mObserver, "xpcom-shutdown");
322       mObserver = nullptr;
323     }
324   }
325 }
326