Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / style / nsFontFaceLoader.cpp
blob4003e7e3ecc2b4d9e25e3be196485a63f96c6dc2
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/TaskQueue.h"
19 #include "mozilla/Telemetry.h"
20 #include "mozilla/Unused.h"
21 #include "FontFaceSet.h"
22 #include "nsPresContext.h"
23 #include "nsIHttpChannel.h"
24 #include "nsIThreadRetargetableRequest.h"
25 #include "nsContentPolicyUtils.h"
26 #include "nsNetCID.h"
28 #include "mozilla/gfx/2D.h"
30 using namespace mozilla;
31 using namespace mozilla::dom;
33 #define LOG(args) \
34 MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
35 #define LOG_ENABLED() \
36 MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
38 static uint32_t GetFallbackDelay() {
39 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
42 static uint32_t GetShortFallbackDelay() {
43 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short",
44 100);
47 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
48 uint32_t aSrcIndex,
49 FontFaceSetImpl* aFontFaceSet,
50 nsIChannel* aChannel)
51 : mUserFontEntry(aUserFontEntry),
52 mFontFaceSet(aFontFaceSet),
53 mChannel(aChannel),
54 mStreamLoader(nullptr),
55 mSrcIndex(aSrcIndex) {
56 MOZ_ASSERT(mFontFaceSet,
57 "We should get a valid FontFaceSet from the caller!");
59 const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(mSrcIndex);
60 MOZ_ASSERT(src.mSourceType == gfxFontFaceSrc::eSourceType_URL);
62 mFontURI = src.mURI->get();
63 mStartTime = TimeStamp::Now();
65 // We add an explicit load block rather than just rely on the network
66 // request's block, since we need to do some OMT work after the load
67 // is finished before we unblock load.
68 auto* doc = mFontFaceSet->GetDocument();
69 if (doc) {
70 doc->BlockOnload();
74 nsFontFaceLoader::~nsFontFaceLoader() {
75 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
76 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
77 if (mUserFontEntry) {
78 mUserFontEntry->mLoader = nullptr;
80 if (mLoadTimer) {
81 mLoadTimer->Cancel();
82 mLoadTimer = nullptr;
84 if (mFontFaceSet) {
85 mFontFaceSet->RemoveLoader(this);
86 auto* doc = mFontFaceSet->GetDocument();
87 if (doc) {
88 doc->UnblockOnload(false);
93 void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) {
94 int32_t loadTimeout;
95 StyleFontDisplay fontDisplay = GetFontDisplay();
96 if (fontDisplay == StyleFontDisplay::Auto ||
97 fontDisplay == StyleFontDisplay::Block) {
98 loadTimeout = GetFallbackDelay();
99 } else {
100 loadTimeout = GetShortFallbackDelay();
103 if (loadTimeout > 0) {
104 NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadTimer), LoadTimerCallback,
105 static_cast<void*>(this), loadTimeout,
106 nsITimer::TYPE_ONE_SHOT, "LoadTimerCallback",
107 GetMainThreadSerialEventTarget());
108 } else {
109 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
111 mStreamLoader = aStreamLoader;
114 /* static */
115 void nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) {
116 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
118 MOZ_DIAGNOSTIC_ASSERT(!loader->mInLoadTimerCallback);
119 MOZ_DIAGNOSTIC_ASSERT(!loader->mInStreamComplete);
120 AutoRestore<bool> scope{loader->mInLoadTimerCallback};
121 loader->mInLoadTimerCallback = true;
123 if (!loader->mFontFaceSet) {
124 // We've been canceled
125 return;
128 gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
129 StyleFontDisplay fontDisplay = loader->GetFontDisplay();
131 // Depending upon the value of the font-display descriptor for the font,
132 // their may be one or two timeouts associated with each font. The
133 // LOADING_SLOWLY state indicates that the fallback font is shown. The
134 // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the
135 // downloaded font resource will not replace the fallback font when the load
136 // completes.
138 bool updateUserFontSet = true;
139 switch (fontDisplay) {
140 case StyleFontDisplay::Auto:
141 case StyleFontDisplay::Block:
142 // If the entry is loading, check whether it's >75% done; if so,
143 // we allow another timeout period before showing a fallback font.
144 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
145 int64_t contentLength;
146 uint32_t numBytesRead;
147 if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
148 contentLength > 0 && contentLength < UINT32_MAX &&
149 NS_SUCCEEDED(
150 loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
151 numBytesRead > 3 * (uint32_t(contentLength) >> 2)) {
152 // More than 3/4 the data has been downloaded, so allow 50% extra
153 // time and hope the remainder will arrive before the additional
154 // time expires.
155 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
156 uint32_t delay;
157 loader->mLoadTimer->GetDelay(&delay);
158 loader->mLoadTimer->InitWithNamedFuncCallback(
159 LoadTimerCallback, static_cast<void*>(loader), delay >> 1,
160 nsITimer::TYPE_ONE_SHOT, "nsFontFaceLoader::LoadTimerCallback");
161 updateUserFontSet = false;
162 LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
165 if (updateUserFontSet) {
166 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
168 break;
169 case StyleFontDisplay::Swap:
170 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
171 break;
172 case StyleFontDisplay::Fallback: {
173 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
174 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
175 } else {
176 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
177 updateUserFontSet = false;
179 break;
181 case StyleFontDisplay::Optional:
182 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
183 break;
185 default:
186 MOZ_ASSERT_UNREACHABLE("strange font-display value");
187 break;
190 // If the font is not 75% loaded, or if we've already timed out once
191 // before, we mark this entry as "loading slowly", so the fallback
192 // font will be used in the meantime, and tell the context to refresh.
193 if (updateUserFontSet) {
194 nsTArray<RefPtr<gfxUserFontSet>> fontSets;
195 ufe->GetUserFontSets(fontSets);
196 for (gfxUserFontSet* fontSet : fontSets) {
197 nsPresContext* ctx = FontFaceSetImpl::GetPresContextFor(fontSet);
198 if (ctx) {
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)));
208 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver, nsIRequestObserver)
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);
220 AutoRestore<bool> scope{mInStreamComplete};
221 mInStreamComplete = true;
223 DropChannel();
225 if (mLoadTimer) {
226 mLoadTimer->Cancel();
227 mLoadTimer = nullptr;
230 if (!mFontFaceSet) {
231 // We've been canceled
232 return aStatus;
235 TimeStamp doneTime = TimeStamp::Now();
236 TimeDuration downloadTime = doneTime - mStartTime;
237 uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
238 Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
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;
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)));
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;
281 mFontFaceSet->RecordFontLoadDone(aStringLen, doneTime);
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.
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;
297 nsresult nsFontFaceLoader::FontLoadComplete() {
298 MOZ_ASSERT(NS_IsMainThread());
300 if (!mFontFaceSet) {
301 // We've been canceled
302 return NS_OK;
305 // when new font loaded, need to reflow
306 nsTArray<RefPtr<gfxUserFontSet>> fontSets;
307 mUserFontEntry->GetUserFontSets(fontSets);
308 for (gfxUserFontSet* fontSet : fontSets) {
309 nsPresContext* ctx = FontFaceSetImpl::GetPresContextFor(fontSet);
310 if (ctx) {
311 // Update layout for the presence of the new font. Since this is
312 // asynchronous, reflows will coalesce.
313 ctx->UserFontSetUpdated(mUserFontEntry);
314 LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx));
318 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
319 mFontFaceSet->RemoveLoader(this);
320 auto* doc = mFontFaceSet->GetDocument();
321 if (doc) {
322 doc->UnblockOnload(false);
324 mFontFaceSet = nullptr;
326 return NS_OK;
329 // nsIRequestObserver
330 NS_IMETHODIMP
331 nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) {
332 MOZ_ASSERT(NS_IsMainThread());
334 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest);
335 if (req) {
336 nsCOMPtr<nsIEventTarget> sts =
337 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
338 RefPtr<TaskQueue> queue =
339 TaskQueue::Create(sts.forget(), "nsFontFaceLoader STS Delivery Queue");
340 Unused << NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(queue)));
342 return NS_OK;
345 NS_IMETHODIMP
346 nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
347 MOZ_ASSERT(NS_IsMainThread());
348 DropChannel();
349 return NS_OK;
352 void nsFontFaceLoader::Cancel() {
353 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
354 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
355 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
357 mUserFontEntry->LoadCanceled();
358 mUserFontEntry = nullptr;
359 auto* doc = mFontFaceSet->GetDocument();
360 if (doc) {
361 doc->UnblockOnload(false);
363 mFontFaceSet = nullptr;
364 if (mLoadTimer) {
365 mLoadTimer->Cancel();
366 mLoadTimer = nullptr;
368 if (nsCOMPtr<nsIChannel> channel = std::move(mChannel)) {
369 channel->CancelWithReason(NS_BINDING_ABORTED,
370 "nsFontFaceLoader::OnStopRequest"_ns);
374 StyleFontDisplay nsFontFaceLoader::GetFontDisplay() {
375 if (!StaticPrefs::layout_css_font_display_enabled()) {
376 return StyleFontDisplay::Auto;
378 return mUserFontEntry->GetFontDisplay();
381 #undef LOG
382 #undef LOG_ENABLED