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"
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"
27 #include "mozilla/gfx/2D.h"
29 using namespace mozilla
;
30 using namespace mozilla::dom
;
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",
46 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry
* aUserFontEntry
,
47 nsIURI
* aFontURI
, FontFaceSet
* aFontFaceSet
,
49 : mUserFontEntry(aUserFontEntry
),
51 mFontFaceSet(aFontFaceSet
),
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
);
68 mUserFontEntry
->mLoader
= nullptr;
75 mFontFaceSet
->RemoveLoader(this);
76 mFontFaceSet
->Document()->UnblockOnload(false);
80 void nsFontFaceLoader::StartedLoading(nsIStreamLoader
* aStreamLoader
) {
82 StyleFontDisplay fontDisplay
= GetFontDisplay();
83 if (fontDisplay
== StyleFontDisplay::Auto
||
84 fontDisplay
== StyleFontDisplay::Block
) {
85 loadTimeout
= GetFallbackDelay();
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
));
96 mUserFontEntry
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
98 mStreamLoader
= aStreamLoader
;
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
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
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
&&
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
142 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_ALMOST_DONE
;
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
;
156 case StyleFontDisplay::Swap
:
157 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
159 case StyleFontDisplay::Fallback
: {
160 if (ufe
->mFontDataLoadingState
== gfxUserFontEntry::LOADING_STARTED
) {
161 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
163 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
164 updateUserFontSet
= false;
168 case StyleFontDisplay::Optional
:
169 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
173 MOZ_ASSERT_UNREACHABLE("strange font-display value");
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
);
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
199 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader
* aLoader
,
200 nsISupports
* aContext
, nsresult aStatus
,
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;
213 mLoadTimer
->Cancel();
214 mLoadTimer
= nullptr;
218 // We've been canceled
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
;
238 if (NS_SUCCEEDED(aStatus
)) {
239 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
240 this, mFontURI
->GetSpecOrDefault().get(), downloadTimeMS
));
242 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
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
);
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());
287 // We've been canceled
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
);
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;
312 // nsIRequestObserver
314 nsFontFaceLoader::OnStartRequest(nsIRequest
* aRequest
) {
315 MOZ_ASSERT(NS_IsMainThread());
317 nsCOMPtr
<nsIThreadRetargetableRequest
> req
= do_QueryInterface(aRequest
);
319 nsCOMPtr
<nsIEventTarget
> sts
=
320 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
321 Unused
<< NS_WARN_IF(NS_FAILED(req
->RetargetDeliveryTo(sts
)));
327 nsFontFaceLoader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
328 MOZ_ASSERT(NS_IsMainThread());
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;
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();