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