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