nsFontFaceLoader.cpp (12744B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* code for loading in @font-face defined font data */ 8 9 #include "nsFontFaceLoader.h" 10 11 #include "FontFaceSet.h" 12 #include "mozilla/AutoRestore.h" 13 #include "mozilla/IntegerPrintfMacros.h" 14 #include "mozilla/Logging.h" 15 #include "mozilla/Preferences.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "mozilla/TaskQueue.h" 18 #include "mozilla/gfx/2D.h" 19 #include "mozilla/glean/GfxMetrics.h" 20 #include "nsContentPolicyUtils.h" 21 #include "nsError.h" 22 #include "nsIHttpChannel.h" 23 #include "nsIThreadRetargetableRequest.h" 24 #include "nsNetCID.h" 25 #include "nsPresContext.h" 26 27 using namespace mozilla; 28 using namespace mozilla::dom; 29 30 #define LOG(args) \ 31 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) 32 #define LOG_ENABLED() \ 33 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug) 34 35 static uint32_t GetFallbackDelay() { 36 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000); 37 } 38 39 static uint32_t GetShortFallbackDelay() { 40 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short", 41 100); 42 } 43 44 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry, 45 uint32_t aSrcIndex, 46 FontFaceSetImpl* aFontFaceSet, 47 nsIChannel* aChannel) 48 : mUserFontEntry(aUserFontEntry), 49 mFontFaceSet(aFontFaceSet), 50 mChannel(aChannel), 51 mStreamLoader(nullptr), 52 mSrcIndex(aSrcIndex) { 53 MOZ_ASSERT(mFontFaceSet, 54 "We should get a valid FontFaceSet from the caller!"); 55 56 const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(mSrcIndex); 57 MOZ_ASSERT(src.mSourceType == gfxFontFaceSrc::eSourceType_URL); 58 59 mFontURI = src.mURI->get(); 60 mStartTime = TimeStamp::Now(); 61 62 // We add an explicit load block rather than just rely on the network 63 // request's block, since we need to do some OMT work after the load 64 // is finished before we unblock load. 65 auto* doc = mFontFaceSet->GetDocument(); 66 if (doc) { 67 doc->BlockOnload(); 68 } 69 } 70 71 nsFontFaceLoader::~nsFontFaceLoader() { 72 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback); 73 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete); 74 if (mUserFontEntry) { 75 mUserFontEntry->mLoader = nullptr; 76 } 77 if (mLoadTimer) { 78 mLoadTimer->Cancel(); 79 mLoadTimer = nullptr; 80 } 81 if (mFontFaceSet) { 82 mFontFaceSet->RemoveLoader(this); 83 auto* doc = mFontFaceSet->GetDocument(); 84 if (doc) { 85 doc->UnblockOnload(false); 86 } 87 } 88 } 89 90 void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) { 91 int32_t loadTimeout; 92 StyleFontDisplay fontDisplay = GetFontDisplay(); 93 if (fontDisplay == StyleFontDisplay::Auto || 94 fontDisplay == StyleFontDisplay::Block) { 95 loadTimeout = GetFallbackDelay(); 96 } else { 97 loadTimeout = GetShortFallbackDelay(); 98 } 99 100 if (loadTimeout > 0) { 101 NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadTimer), LoadTimerCallback, 102 static_cast<void*>(this), loadTimeout, 103 nsITimer::TYPE_ONE_SHOT, "LoadTimerCallback"_ns, 104 GetMainThreadSerialEventTarget()); 105 } else { 106 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY; 107 } 108 mStreamLoader = aStreamLoader; 109 } 110 111 /* static */ 112 void nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) { 113 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure); 114 115 MOZ_DIAGNOSTIC_ASSERT(!loader->mInLoadTimerCallback); 116 MOZ_DIAGNOSTIC_ASSERT(!loader->mInStreamComplete); 117 AutoRestore<bool> scope{loader->mInLoadTimerCallback}; 118 loader->mInLoadTimerCallback = true; 119 120 if (!loader->mFontFaceSet) { 121 // We've been canceled 122 return; 123 } 124 125 gfxUserFontEntry* ufe = loader->mUserFontEntry.get(); 126 StyleFontDisplay fontDisplay = loader->GetFontDisplay(); 127 128 // Depending upon the value of the font-display descriptor for the font, 129 // their may be one or two timeouts associated with each font. The 130 // LOADING_SLOWLY state indicates that the fallback font is shown. The 131 // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the 132 // downloaded font resource will not replace the fallback font when the load 133 // completes. 134 135 bool updateUserFontSet = true; 136 switch (fontDisplay) { 137 case StyleFontDisplay::Auto: 138 case StyleFontDisplay::Block: 139 // If the entry is loading, check whether it's >75% done; if so, 140 // we allow another timeout period before showing a fallback font. 141 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) { 142 int64_t contentLength; 143 uint32_t numBytesRead; 144 if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) && 145 contentLength > 0 && contentLength < UINT32_MAX && 146 NS_SUCCEEDED( 147 loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) && 148 numBytesRead > 3 * (uint32_t(contentLength) >> 2)) { 149 // More than 3/4 the data has been downloaded, so allow 50% extra 150 // time and hope the remainder will arrive before the additional 151 // time expires. 152 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE; 153 uint32_t delay; 154 loader->mLoadTimer->GetDelay(&delay); 155 loader->mLoadTimer->InitWithNamedFuncCallback( 156 LoadTimerCallback, static_cast<void*>(loader), delay >> 1, 157 nsITimer::TYPE_ONE_SHOT, 158 "nsFontFaceLoader::LoadTimerCallback"_ns); 159 updateUserFontSet = false; 160 LOG(("userfonts (%p) 75%% done, resetting timer\n", loader)); 161 } 162 } 163 if (updateUserFontSet) { 164 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY; 165 } 166 break; 167 case StyleFontDisplay::Swap: 168 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY; 169 break; 170 case StyleFontDisplay::Fallback: { 171 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) { 172 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY; 173 } else { 174 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT; 175 updateUserFontSet = false; 176 } 177 break; 178 } 179 case StyleFontDisplay::Optional: 180 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT; 181 break; 182 183 default: 184 MOZ_ASSERT_UNREACHABLE("strange font-display value"); 185 break; 186 } 187 188 // If the font is not 75% loaded, or if we've already timed out once 189 // before, we mark this entry as "loading slowly", so the fallback 190 // font will be used in the meantime, and tell the context to refresh. 191 if (updateUserFontSet) { 192 // FIXME(emilio): This is basically the same as FontLoadComplete... Also 193 // shouldn't this increment the generation for worker font sets? 194 AutoTArray<RefPtr<gfxUserFontSet>, 4> fontSets; 195 ufe->GetUserFontSets(fontSets); 196 for (gfxUserFontSet* fontSet : fontSets) { 197 if (FontVisibilityProvider* ctx = 198 FontFaceSetImpl::GetFontVisibilityProviderFor(fontSet)) { 199 fontSet->IncrementGeneration(); 200 ctx->UserFontSetUpdated(ufe); 201 LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n", 202 loader, ctx, static_cast<int>(fontDisplay))); 203 } 204 } 205 } 206 } 207 208 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver, nsIRequestObserver) 209 210 // nsIStreamLoaderObserver 211 NS_IMETHODIMP 212 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader, 213 nsISupports* aContext, nsresult aStatus, 214 uint32_t aStringLen, 215 const uint8_t* aString) { 216 MOZ_ASSERT(NS_IsMainThread()); 217 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback); 218 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete); 219 220 AutoRestore<bool> scope{mInStreamComplete}; 221 mInStreamComplete = true; 222 223 DropChannel(); 224 225 if (mLoadTimer) { 226 mLoadTimer->Cancel(); 227 mLoadTimer = nullptr; 228 } 229 230 if (!mFontFaceSet) { 231 // We've been canceled 232 return aStatus; 233 } 234 235 TimeStamp doneTime = TimeStamp::Now(); 236 TimeDuration downloadTime = doneTime - mStartTime; 237 glean::webfont::download_time.AccumulateRawDuration(downloadTime); 238 239 uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds()); 240 if (GetFontDisplay() == StyleFontDisplay::Fallback) { 241 uint32_t loadTimeout = GetFallbackDelay(); 242 if (downloadTimeMS > loadTimeout && 243 (mUserFontEntry->mFontDataLoadingState == 244 gfxUserFontEntry::LOADING_SLOWLY)) { 245 mUserFontEntry->mFontDataLoadingState = 246 gfxUserFontEntry::LOADING_TIMED_OUT; 247 } 248 } 249 250 if (LOG_ENABLED()) { 251 if (NS_SUCCEEDED(aStatus)) { 252 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n", 253 this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS)); 254 } else { 255 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32 256 "\n", 257 this, mFontURI->GetSpecOrDefault().get(), 258 static_cast<uint32_t>(aStatus))); 259 } 260 } 261 262 if (NS_SUCCEEDED(aStatus)) { 263 // for HTTP requests, check whether the request _actually_ succeeded; 264 // the "request status" in aStatus does not necessarily indicate this, 265 // because HTTP responses such as 404 (Not Found) will still result in 266 // a success code and potentially an HTML error page from the server 267 // as the resulting data. We don't want to use that as a font. 268 nsCOMPtr<nsIRequest> request; 269 nsCOMPtr<nsIHttpChannel> httpChannel; 270 aLoader->GetRequest(getter_AddRefs(request)); 271 httpChannel = do_QueryInterface(request); 272 if (httpChannel) { 273 bool succeeded; 274 nsresult rv = httpChannel->GetRequestSucceeded(&succeeded); 275 if (NS_SUCCEEDED(rv) && !succeeded) { 276 aStatus = NS_ERROR_NOT_AVAILABLE; 277 } 278 } 279 } 280 281 mFontFaceSet->RecordFontLoadDone(aStringLen, doneTime); 282 283 // The userFontEntry is responsible for freeing the downloaded data 284 // (aString) when finished with it; the pointer is no longer valid 285 // after FontDataDownloadComplete returns. 286 // This is called even in the case of a failed download (HTTP 404, etc), 287 // as there may still be data to be freed (e.g. an error page), 288 // and we need to load the next source. 289 290 // FontDataDownloadComplete will load the platform font on a worker thread, 291 // and will call FontLoadComplete when it has finished its work. 292 mUserFontEntry->FontDataDownloadComplete(mSrcIndex, aString, aStringLen, 293 aStatus, this); 294 return NS_SUCCESS_ADOPTED_DATA; 295 } 296 297 nsresult nsFontFaceLoader::FontLoadComplete() { 298 MOZ_ASSERT(NS_IsMainThread()); 299 300 if (!mFontFaceSet) { 301 // We've been canceled 302 return NS_OK; 303 } 304 305 // when new font loaded, need to reflow 306 mUserFontEntry->FontLoadComplete(); 307 308 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet); 309 mFontFaceSet->RemoveLoader(this); 310 if (auto* doc = mFontFaceSet->GetDocument()) { 311 doc->UnblockOnload(false); 312 } 313 mFontFaceSet = nullptr; 314 315 return NS_OK; 316 } 317 318 // nsIRequestObserver 319 NS_IMETHODIMP 320 nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) { 321 MOZ_ASSERT(NS_IsMainThread()); 322 323 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest); 324 if (req) { 325 nsCOMPtr<nsIEventTarget> sts = 326 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 327 RefPtr<TaskQueue> queue = 328 TaskQueue::Create(sts.forget(), "nsFontFaceLoader STS Delivery Queue"); 329 (void)NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(queue))); 330 } 331 return NS_OK; 332 } 333 334 NS_IMETHODIMP 335 nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { 336 MOZ_ASSERT(NS_IsMainThread()); 337 DropChannel(); 338 return NS_OK; 339 } 340 341 void nsFontFaceLoader::Cancel() { 342 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback); 343 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete); 344 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet); 345 346 mUserFontEntry->LoadCanceled(); 347 mUserFontEntry = nullptr; 348 auto* doc = mFontFaceSet->GetDocument(); 349 if (doc) { 350 doc->UnblockOnload(false); 351 } 352 mFontFaceSet = nullptr; 353 if (mLoadTimer) { 354 mLoadTimer->Cancel(); 355 mLoadTimer = nullptr; 356 } 357 if (nsCOMPtr<nsIChannel> channel = std::move(mChannel)) { 358 channel->CancelWithReason(NS_BINDING_ABORTED, 359 "nsFontFaceLoader::OnStopRequest"_ns); 360 } 361 } 362 363 StyleFontDisplay nsFontFaceLoader::GetFontDisplay() { 364 return mUserFontEntry->GetFontDisplay(); 365 } 366 367 #undef LOG 368 #undef LOG_ENABLED