gfxFontInfoLoader.cpp (9512B)
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 "mozilla/AppShutdown.h" 9 #include "nsCRT.h" 10 #include "nsIObserverService.h" 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 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: 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: 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 // runs on main thread after async font info loading is done 77 nsresult FontInfoLoadCompleteEvent::Run() { 78 gfxFontInfoLoader* loader = 79 static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList()); 80 81 loader->FinalizeLoader(mFontInfo); 82 83 return NS_OK; 84 } 85 86 // runs on separate thread 87 nsresult AsyncFontInfoLoader::Run() { 88 // load platform-specific font info 89 mFontInfo->Load(); 90 91 // post a completion event that transfer the data to the fontlist 92 NS_DispatchToMainThread(mCompleteEvent); 93 94 return NS_OK; 95 } 96 97 NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver) 98 99 NS_IMETHODIMP 100 gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports* aSubject, 101 const char* aTopic, 102 const char16_t* someData) { 103 if (!nsCRT::strcmp(aTopic, "quit-application") || 104 !nsCRT::strcmp(aTopic, "xpcom-shutdown")) { 105 mLoader->CancelLoader(); 106 } else { 107 MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); 108 } 109 return NS_OK; 110 } 111 112 // StartLoader is usually called at startup with a (prefs-derived) delay value, 113 // so that the async loader runs shortly after startup, to avoid competing for 114 // disk i/o etc with other more critical operations. 115 // However, it may be called with aDelay=0 if we find that the font info (e.g. 116 // localized names) is needed for layout. In this case we start the loader 117 // immediately; however, it is still an async process and we may use fallback 118 // fonts to satisfy layout until it completes. 119 void gfxFontInfoLoader::StartLoader(uint32_t aDelay) { 120 if (aDelay == 0 && (mState == stateTimerOff || mState == stateAsyncLoad)) { 121 // We were asked to load (async) without delay, but have already started, 122 // so just return and let the loader proceed. 123 return; 124 } 125 126 // We observe for "quit-application" above, so avoid initialization after it. 127 if (NS_WARN_IF(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) { 128 MOZ_ASSERT(!aDelay, "Delayed gfxFontInfoLoader startup after AppShutdown?"); 129 return; 130 } 131 132 // sanity check 133 if (mState != stateInitial && mState != stateTimerOff && 134 mState != stateTimerOnDelay) { 135 CancelLoader(); 136 } 137 138 // Create mFontInfo when we're initially called to set up the delay, rather 139 // than when called by the DelayedStartCallback, because on the initial call 140 // we know we'll be holding the gfxPlatformFontList lock. 141 if (!mFontInfo) { 142 mFontInfo = CreateFontInfoData(); 143 if (!mFontInfo) { 144 // The platform doesn't want anything loaded, so just bail out. 145 mState = stateTimerOff; 146 return; 147 } 148 } 149 150 AddShutdownObserver(); 151 152 // Caller asked for a delay? ==> start async thread after a delay 153 if (aDelay) { 154 // Set up delay timer, or if there is already a timer in place, just 155 // leave it to do its thing. (This can happen if a StartLoader runnable 156 // was posted to the main thread from the InitFontList thread, but then 157 // before it had a chance to run and call StartLoader, the main thread 158 // re-initialized the list due to a platform notification and called 159 // StartLoader directly.) 160 if (mTimer) { 161 return; 162 } 163 mTimer = NS_NewTimer(); 164 mTimer->InitWithNamedFuncCallback(DelayedStartCallback, this, aDelay, 165 nsITimer::TYPE_ONE_SHOT, 166 "gfxFontInfoLoader::StartLoader"_ns); 167 mState = stateTimerOnDelay; 168 return; 169 } 170 171 // Either we've been called back by the DelayedStartCallback when its timer 172 // fired, or a layout caller has passed aDelay=0 to ask the loader to run 173 // without further delay. 174 175 // Cancel the delay timer, if any. 176 if (mTimer) { 177 mTimer->Cancel(); 178 mTimer = nullptr; 179 } 180 181 // initialize 182 InitLoader(); 183 184 // start async load 185 nsresult rv = NS_NewNamedThread("Font Loader", 186 getter_AddRefs(mFontLoaderThread), nullptr); 187 if (NS_WARN_IF(NS_FAILED(rv))) { 188 return; 189 } 190 191 PRThread* prThread; 192 if (NS_SUCCEEDED(mFontLoaderThread->GetPRThread(&prThread))) { 193 PR_SetThreadPriority(prThread, PR_PRIORITY_LOW); 194 } 195 196 mState = stateAsyncLoad; 197 198 nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo); 199 200 mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL); 201 202 if (LOG_FONTINIT_ENABLED()) { 203 LOG_FONTINIT( 204 ("(fontinit) fontloader started (fontinfo: %p)\n", mFontInfo.get())); 205 } 206 } 207 208 class FinalizeLoaderRunnable : public Runnable { 209 virtual ~FinalizeLoaderRunnable() = default; 210 211 public: 212 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable, Runnable) 213 214 explicit FinalizeLoaderRunnable(gfxFontInfoLoader* aLoader) 215 : mozilla::Runnable("FinalizeLoaderRunnable"), mLoader(aLoader) {} 216 217 NS_IMETHOD Run() override { 218 nsresult rv; 219 if (mLoader->LoadFontInfo()) { 220 mLoader->CancelLoader(); 221 rv = NS_OK; 222 } else { 223 nsCOMPtr<nsIRunnable> runnable = this; 224 rv = NS_DispatchToCurrentThreadQueue( 225 runnable.forget(), PR_INTERVAL_NO_TIMEOUT, EventQueuePriority::Idle); 226 } 227 return rv; 228 } 229 230 private: 231 gfxFontInfoLoader* mLoader; 232 }; 233 234 void gfxFontInfoLoader::FinalizeLoader(FontInfoData* aFontInfo) { 235 // Avoid loading data if loader has already been canceled. 236 // This should mean that CancelLoader() ran and the Load 237 // thread has already Shutdown(), and likely before processing 238 // the Shutdown event it handled the load event and sent back 239 // our Completion event, thus we end up here. 240 if (mState != stateAsyncLoad || mFontInfo != aFontInfo) { 241 return; 242 } 243 244 mLoadTime = mFontInfo->mLoadTime; 245 246 MOZ_ASSERT(NS_IsMainThread()); 247 nsCOMPtr<nsIRunnable> runnable = new FinalizeLoaderRunnable(this); 248 if (NS_FAILED(NS_DispatchToCurrentThreadQueue(runnable.forget(), 249 PR_INTERVAL_NO_TIMEOUT, 250 EventQueuePriority::Idle))) { 251 NS_WARNING("Failed to finalize async font info"); 252 } 253 } 254 255 void gfxFontInfoLoader::CancelLoader() { 256 if (mState == stateInitial) { 257 return; 258 } 259 mState = stateTimerOff; 260 if (mTimer) { 261 mTimer->Cancel(); 262 mTimer = nullptr; 263 } 264 if (mFontInfo) // null during any initial delay 265 mFontInfo->mCanceled = true; 266 if (mFontLoaderThread) { 267 NS_DispatchToMainThread(NS_NewRunnableFunction( 268 __func__, 269 [thread = std::move(mFontLoaderThread)]() { thread->Shutdown(); })); 270 } 271 RemoveShutdownObserver(); 272 CleanupLoader(); 273 } 274 275 gfxFontInfoLoader::~gfxFontInfoLoader() { 276 RemoveShutdownObserver(); 277 MOZ_COUNT_DTOR(gfxFontInfoLoader); 278 } 279 280 void gfxFontInfoLoader::AddShutdownObserver() { 281 if (mObserver) { 282 return; 283 } 284 285 nsCOMPtr<nsIObserverService> obs = GetObserverService(); 286 if (obs) { 287 mObserver = new ShutdownObserver(this); 288 obs->AddObserver(mObserver, "quit-application", false); 289 obs->AddObserver(mObserver, "xpcom-shutdown", false); 290 } 291 } 292 293 void gfxFontInfoLoader::RemoveShutdownObserver() { 294 if (mObserver) { 295 nsCOMPtr<nsIObserverService> obs = GetObserverService(); 296 if (obs) { 297 obs->RemoveObserver(mObserver, "quit-application"); 298 obs->RemoveObserver(mObserver, "xpcom-shutdown"); 299 mObserver = nullptr; 300 } 301 } 302 }