Bug 1698786: part 2) Change some compile-time dependent `printf`s to `MOZ_LOG` in...
[gecko.git] / layout / style / nsFontFaceLoader.cpp
blobbfce96cf827088d096240f9ac4861140c5d2428a
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/. */
7 /* code for loading in @font-face defined font data */
9 #include "mozilla/IntegerPrintfMacros.h"
10 #include "mozilla/Logging.h"
12 #include "nsFontFaceLoader.h"
14 #include "nsError.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/StaticPrefs_layout.h"
18 #include "mozilla/Telemetry.h"
19 #include "mozilla/Unused.h"
20 #include "FontFaceSet.h"
21 #include "nsPresContext.h"
22 #include "nsIHttpChannel.h"
23 #include "nsIThreadRetargetableRequest.h"
24 #include "nsContentPolicyUtils.h"
25 #include "nsNetCID.h"
27 #include "mozilla/gfx/2D.h"
29 using namespace mozilla;
30 using namespace mozilla::dom;
32 #define LOG(args) \
33 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
34 #define LOG_ENABLED() \
35 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
37 static uint32_t GetFallbackDelay() {
38 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
41 static uint32_t GetShortFallbackDelay() {
42 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short",
43 100);
46 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
47 nsIURI* aFontURI, FontFaceSet* aFontFaceSet,
48 nsIChannel* aChannel)
49 : mUserFontEntry(aUserFontEntry),
50 mFontURI(aFontURI),
51 mFontFaceSet(aFontFaceSet),
52 mChannel(aChannel),
53 mStreamLoader(nullptr) {
54 MOZ_ASSERT(mFontFaceSet,
55 "We should get a valid FontFaceSet from the caller!");
56 mStartTime = TimeStamp::Now();
58 // We add an explicit load block rather than just rely on the network
59 // request's block, since we need to do some OMT work after the load
60 // is finished before we unblock load.
61 mFontFaceSet->Document()->BlockOnload();
64 nsFontFaceLoader::~nsFontFaceLoader() {
65 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
66 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
67 if (mUserFontEntry) {
68 mUserFontEntry->mLoader = nullptr;
70 if (mLoadTimer) {
71 mLoadTimer->Cancel();
72 mLoadTimer = nullptr;
74 if (mFontFaceSet) {
75 mFontFaceSet->RemoveLoader(this);
76 mFontFaceSet->Document()->UnblockOnload(false);
80 void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) {
81 int32_t loadTimeout;
82 StyleFontDisplay fontDisplay = GetFontDisplay();
83 if (fontDisplay == StyleFontDisplay::Auto ||
84 fontDisplay == StyleFontDisplay::Block) {
85 loadTimeout = GetFallbackDelay();
86 } else {
87 loadTimeout = GetShortFallbackDelay();
90 if (loadTimeout > 0) {
91 NS_NewTimerWithFuncCallback(
92 getter_AddRefs(mLoadTimer), LoadTimerCallback, static_cast<void*>(this),
93 loadTimeout, nsITimer::TYPE_ONE_SHOT, "LoadTimerCallback",
94 mFontFaceSet->Document()->EventTargetFor(TaskCategory::Other));
95 } else {
96 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
98 mStreamLoader = aStreamLoader;
101 /* static */
102 void nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) {
103 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
105 MOZ_DIAGNOSTIC_ASSERT(!loader->mInLoadTimerCallback);
106 MOZ_DIAGNOSTIC_ASSERT(!loader->mInStreamComplete);
107 AutoRestore<bool> scope{loader->mInLoadTimerCallback};
108 loader->mInLoadTimerCallback = true;
110 if (!loader->mFontFaceSet) {
111 // We've been canceled
112 return;
115 gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
116 StyleFontDisplay fontDisplay = loader->GetFontDisplay();
118 // Depending upon the value of the font-display descriptor for the font,
119 // their may be one or two timeouts associated with each font. The
120 // LOADING_SLOWLY state indicates that the fallback font is shown. The
121 // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the
122 // downloaded font resource will not replace the fallback font when the load
123 // completes.
125 bool updateUserFontSet = true;
126 switch (fontDisplay) {
127 case StyleFontDisplay::Auto:
128 case StyleFontDisplay::Block:
129 // If the entry is loading, check whether it's >75% done; if so,
130 // we allow another timeout period before showing a fallback font.
131 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
132 int64_t contentLength;
133 uint32_t numBytesRead;
134 if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
135 contentLength > 0 && contentLength < UINT32_MAX &&
136 NS_SUCCEEDED(
137 loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
138 numBytesRead > 3 * (uint32_t(contentLength) >> 2)) {
139 // More than 3/4 the data has been downloaded, so allow 50% extra
140 // time and hope the remainder will arrive before the additional
141 // time expires.
142 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
143 uint32_t delay;
144 loader->mLoadTimer->GetDelay(&delay);
145 loader->mLoadTimer->InitWithNamedFuncCallback(
146 LoadTimerCallback, static_cast<void*>(loader), delay >> 1,
147 nsITimer::TYPE_ONE_SHOT, "nsFontFaceLoader::LoadTimerCallback");
148 updateUserFontSet = false;
149 LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
152 if (updateUserFontSet) {
153 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
155 break;
156 case StyleFontDisplay::Swap:
157 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
158 break;
159 case StyleFontDisplay::Fallback: {
160 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
161 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
162 } else {
163 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
164 updateUserFontSet = false;
166 break;
168 case StyleFontDisplay::Optional:
169 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
170 break;
172 default:
173 MOZ_ASSERT_UNREACHABLE("strange font-display value");
174 break;
177 // If the font is not 75% loaded, or if we've already timed out once
178 // before, we mark this entry as "loading slowly", so the fallback
179 // font will be used in the meantime, and tell the context to refresh.
180 if (updateUserFontSet) {
181 nsTArray<gfxUserFontSet*> fontSets;
182 ufe->GetUserFontSets(fontSets);
183 for (gfxUserFontSet* fontSet : fontSets) {
184 nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
185 if (ctx) {
186 fontSet->IncrementGeneration();
187 ctx->UserFontSetUpdated(ufe);
188 LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
189 loader, ctx, static_cast<int>(fontDisplay)));
195 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver, nsIRequestObserver)
197 // nsIStreamLoaderObserver
198 NS_IMETHODIMP
199 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
200 nsISupports* aContext, nsresult aStatus,
201 uint32_t aStringLen,
202 const uint8_t* aString) {
203 MOZ_ASSERT(NS_IsMainThread());
204 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
205 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
207 AutoRestore<bool> scope{mInStreamComplete};
208 mInStreamComplete = true;
210 DropChannel();
212 if (mLoadTimer) {
213 mLoadTimer->Cancel();
214 mLoadTimer = nullptr;
217 if (!mFontFaceSet) {
218 // We've been canceled
219 return aStatus;
222 TimeStamp doneTime = TimeStamp::Now();
223 TimeDuration downloadTime = doneTime - mStartTime;
224 uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
225 Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
227 if (GetFontDisplay() == StyleFontDisplay::Fallback) {
228 uint32_t loadTimeout = GetFallbackDelay();
229 if (downloadTimeMS > loadTimeout &&
230 (mUserFontEntry->mFontDataLoadingState ==
231 gfxUserFontEntry::LOADING_SLOWLY)) {
232 mUserFontEntry->mFontDataLoadingState =
233 gfxUserFontEntry::LOADING_TIMED_OUT;
237 if (LOG_ENABLED()) {
238 if (NS_SUCCEEDED(aStatus)) {
239 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
240 this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS));
241 } else {
242 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
243 "\n",
244 this, mFontURI->GetSpecOrDefault().get(),
245 static_cast<uint32_t>(aStatus)));
249 if (NS_SUCCEEDED(aStatus)) {
250 // for HTTP requests, check whether the request _actually_ succeeded;
251 // the "request status" in aStatus does not necessarily indicate this,
252 // because HTTP responses such as 404 (Not Found) will still result in
253 // a success code and potentially an HTML error page from the server
254 // as the resulting data. We don't want to use that as a font.
255 nsCOMPtr<nsIRequest> request;
256 nsCOMPtr<nsIHttpChannel> httpChannel;
257 aLoader->GetRequest(getter_AddRefs(request));
258 httpChannel = do_QueryInterface(request);
259 if (httpChannel) {
260 bool succeeded;
261 nsresult rv = httpChannel->GetRequestSucceeded(&succeeded);
262 if (NS_SUCCEEDED(rv) && !succeeded) {
263 aStatus = NS_ERROR_NOT_AVAILABLE;
268 mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime);
270 // The userFontEntry is responsible for freeing the downloaded data
271 // (aString) when finished with it; the pointer is no longer valid
272 // after FontDataDownloadComplete returns.
273 // This is called even in the case of a failed download (HTTP 404, etc),
274 // as there may still be data to be freed (e.g. an error page),
275 // and we need to load the next source.
277 // FontDataDownloadComplete will load the platform font on a worker thread,
278 // and will call FontLoadComplete when it has finished its work.
279 mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus, this);
280 return NS_SUCCESS_ADOPTED_DATA;
283 nsresult nsFontFaceLoader::FontLoadComplete() {
284 MOZ_ASSERT(NS_IsMainThread());
286 if (!mFontFaceSet) {
287 // We've been canceled
288 return NS_OK;
291 // when new font loaded, need to reflow
292 nsTArray<gfxUserFontSet*> fontSets;
293 mUserFontEntry->GetUserFontSets(fontSets);
294 for (gfxUserFontSet* fontSet : fontSets) {
295 nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
296 if (ctx) {
297 // Update layout for the presence of the new font. Since this is
298 // asynchronous, reflows will coalesce.
299 ctx->UserFontSetUpdated(mUserFontEntry);
300 LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx));
304 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
305 mFontFaceSet->RemoveLoader(this);
306 mFontFaceSet->Document()->UnblockOnload(false);
307 mFontFaceSet = nullptr;
309 return NS_OK;
312 // nsIRequestObserver
313 NS_IMETHODIMP
314 nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) {
315 MOZ_ASSERT(NS_IsMainThread());
317 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest);
318 if (req) {
319 nsCOMPtr<nsIEventTarget> sts =
320 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
321 Unused << NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(sts)));
323 return NS_OK;
326 NS_IMETHODIMP
327 nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
328 MOZ_ASSERT(NS_IsMainThread());
329 DropChannel();
330 return NS_OK;
333 void nsFontFaceLoader::Cancel() {
334 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
335 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
336 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
338 mUserFontEntry->LoadCanceled();
339 mUserFontEntry = nullptr;
340 mFontFaceSet->Document()->UnblockOnload(false);
341 mFontFaceSet = nullptr;
342 if (mLoadTimer) {
343 mLoadTimer->Cancel();
344 mLoadTimer = nullptr;
346 if (nsCOMPtr<nsIChannel> channel = std::move(mChannel)) {
347 channel->Cancel(NS_BINDING_ABORTED);
351 StyleFontDisplay nsFontFaceLoader::GetFontDisplay() {
352 if (!StaticPrefs::layout_css_font_display_enabled()) {
353 return StyleFontDisplay::Auto;
355 return mUserFontEntry->GetFontDisplay();
358 #undef LOG
359 #undef LOG_ENABLED