1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "nsCRT.h"
8 #include "nsIObserverService.h"
9 #include "nsThreadUtils.h" // for nsRunnable
10 #include "gfxPlatformFontList.h"
11
12 using namespace mozilla;
13 using services::GetObserverService;
14
15 #define LOG_FONTINIT(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \
16 LogLevel::Debug, args)
17 #define LOG_FONTINIT_ENABLED() MOZ_LOG_TEST( \
18 gfxPlatform::GetLog(eGfxLog_fontinit), \
19 LogLevel::Debug)
20
21 void
Load()22 FontInfoData::Load()
23 {
24 TimeStamp start = TimeStamp::Now();
25
26 uint32_t i, n = mFontFamiliesToLoad.Length();
27 mLoadStats.families = n;
28 for (i = 0; i < n && !mCanceled; i++) {
29 // font file memory mapping sometimes causes exceptions - bug 1100949
30 MOZ_SEH_TRY {
31 LoadFontFamilyData(mFontFamiliesToLoad[i]);
32 } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
33 gfxCriticalError() <<
34 "Exception occurred reading font data for " <<
35 NS_ConvertUTF16toUTF8(mFontFamiliesToLoad[i]).get();
36 }
37 }
38
39 mLoadTime = TimeStamp::Now() - start;
40 }
41
42 class FontInfoLoadCompleteEvent : public Runnable {
~FontInfoLoadCompleteEvent()43 virtual ~FontInfoLoadCompleteEvent() {}
44
45 NS_DECL_ISUPPORTS_INHERITED
46
FontInfoLoadCompleteEvent(FontInfoData * aFontInfo)47 explicit FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
48 mFontInfo(aFontInfo)
49 {}
50
51 NS_IMETHOD Run() override;
52
53 RefPtr<FontInfoData> mFontInfo;
54 };
55
56 class AsyncFontInfoLoader : public Runnable {
~AsyncFontInfoLoader()57 virtual ~AsyncFontInfoLoader() {}
58
59 NS_DECL_ISUPPORTS_INHERITED
60
AsyncFontInfoLoader(FontInfoData * aFontInfo)61 explicit AsyncFontInfoLoader(FontInfoData *aFontInfo) :
62 mFontInfo(aFontInfo)
63 {
64 mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
65 }
66
67 NS_IMETHOD Run() override;
68
69 RefPtr<FontInfoData> mFontInfo;
70 RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
71 };
72
73 class ShutdownThreadEvent : public Runnable {
~ShutdownThreadEvent()74 virtual ~ShutdownThreadEvent() {}
75
76 NS_DECL_ISUPPORTS_INHERITED
77
ShutdownThreadEvent(nsIThread * aThread)78 explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
Run()79 NS_IMETHOD Run() override {
80 mThread->Shutdown();
81 return NS_OK;
82 }
83 nsCOMPtr<nsIThread> mThread;
84 };
85
86 NS_IMPL_ISUPPORTS_INHERITED0(ShutdownThreadEvent, Runnable);
87
88 // runs on main thread after async font info loading is done
89 nsresult
Run()90 FontInfoLoadCompleteEvent::Run()
91 {
92 gfxFontInfoLoader *loader =
93 static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
94
95 loader->FinalizeLoader(mFontInfo);
96
97 return NS_OK;
98 }
99
100 NS_IMPL_ISUPPORTS_INHERITED0(FontInfoLoadCompleteEvent, Runnable);
101
102 // runs on separate thread
103 nsresult
Run()104 AsyncFontInfoLoader::Run()
105 {
106 // load platform-specific font info
107 mFontInfo->Load();
108
109 // post a completion event that transfer the data to the fontlist
110 NS_DispatchToMainThread(mCompleteEvent);
111
112 return NS_OK;
113 }
114
115 NS_IMPL_ISUPPORTS_INHERITED0(AsyncFontInfoLoader, Runnable);
116
NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver,nsIObserver)117 NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
118
119 NS_IMETHODIMP
120 gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
121 const char *aTopic,
122 const char16_t *someData)
123 {
124 if (!nsCRT::strcmp(aTopic, "quit-application")) {
125 mLoader->CancelLoader();
126 } else {
127 NS_NOTREACHED("unexpected notification topic");
128 }
129 return NS_OK;
130 }
131
132 void
StartLoader(uint32_t aDelay,uint32_t aInterval)133 gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
134 {
135 mInterval = aInterval;
136
137 NS_ASSERTION(!mFontInfo,
138 "fontinfo should be null when starting font loader");
139
140 // sanity check
141 if (mState != stateInitial &&
142 mState != stateTimerOff &&
143 mState != stateTimerOnDelay) {
144 CancelLoader();
145 }
146
147 // set up timer
148 if (!mTimer) {
149 mTimer = do_CreateInstance("@mozilla.org/timer;1");
150 if (!mTimer) {
151 NS_WARNING("Failure to create font info loader timer");
152 return;
153 }
154 }
155
156 AddShutdownObserver();
157
158 // delay? ==> start async thread after a delay
159 if (aDelay) {
160 mState = stateTimerOnDelay;
161 mTimer->InitWithFuncCallback(DelayedStartCallback, this, aDelay,
162 nsITimer::TYPE_ONE_SHOT);
163 return;
164 }
165
166 mFontInfo = CreateFontInfoData();
167
168 // initialize
169 InitLoader();
170
171 // start async load
172 nsresult rv = NS_NewNamedThread("Font Loader",
173 getter_AddRefs(mFontLoaderThread),
174 nullptr);
175 if (NS_WARN_IF(NS_FAILED(rv))) {
176 return;
177 }
178 mState = stateAsyncLoad;
179
180 nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);
181
182 mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL);
183
184 if (LOG_FONTINIT_ENABLED()) {
185 LOG_FONTINIT(("(fontinit) fontloader started (fontinfo: %p)\n",
186 mFontInfo.get()));
187 }
188 }
189
190 void
FinalizeLoader(FontInfoData * aFontInfo)191 gfxFontInfoLoader::FinalizeLoader(FontInfoData *aFontInfo)
192 {
193 // Avoid loading data if loader has already been canceled.
194 // This should mean that CancelLoader() ran and the Load
195 // thread has already Shutdown(), and likely before processing
196 // the Shutdown event it handled the load event and sent back
197 // our Completion event, thus we end up here.
198 if (mState != stateAsyncLoad || mFontInfo != aFontInfo) {
199 return;
200 }
201
202 mLoadTime = mFontInfo->mLoadTime;
203
204 // try to load all font data immediately
205 if (LoadFontInfo()) {
206 CancelLoader();
207 return;
208 }
209
210 // not all work completed ==> run load on interval
211 mState = stateTimerOnInterval;
212 mTimer->InitWithFuncCallback(LoadFontInfoCallback, this, mInterval,
213 nsITimer::TYPE_REPEATING_SLACK);
214 }
215
216 void
CancelLoader()217 gfxFontInfoLoader::CancelLoader()
218 {
219 if (mState == stateInitial) {
220 return;
221 }
222 mState = stateTimerOff;
223 if (mTimer) {
224 mTimer->Cancel();
225 mTimer = nullptr;
226 }
227 if (mFontInfo) // null during any initial delay
228 mFontInfo->mCanceled = true;
229 if (mFontLoaderThread) {
230 NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread));
231 mFontLoaderThread = nullptr;
232 }
233 RemoveShutdownObserver();
234 CleanupLoader();
235 }
236
237 void
LoadFontInfoTimerFire()238 gfxFontInfoLoader::LoadFontInfoTimerFire()
239 {
240 if (mState == stateTimerOnDelay) {
241 mState = stateTimerOnInterval;
242 mTimer->SetDelay(mInterval);
243 }
244
245 bool done = LoadFontInfo();
246 if (done) {
247 CancelLoader();
248 }
249 }
250
~gfxFontInfoLoader()251 gfxFontInfoLoader::~gfxFontInfoLoader()
252 {
253 RemoveShutdownObserver();
254 MOZ_COUNT_DTOR(gfxFontInfoLoader);
255 }
256
257 void
AddShutdownObserver()258 gfxFontInfoLoader::AddShutdownObserver()
259 {
260 if (mObserver) {
261 return;
262 }
263
264 nsCOMPtr<nsIObserverService> obs = GetObserverService();
265 if (obs) {
266 mObserver = new ShutdownObserver(this);
267 obs->AddObserver(mObserver, "quit-application", false);
268 }
269 }
270
271 void
RemoveShutdownObserver()272 gfxFontInfoLoader::RemoveShutdownObserver()
273 {
274 if (mObserver) {
275 nsCOMPtr<nsIObserverService> obs = GetObserverService();
276 if (obs) {
277 obs->RemoveObserver(mObserver, "quit-application");
278 mObserver = nullptr;
279 }
280 }
281 }
282