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
,
48 FontFaceSet
* aFontFaceSet
,
50 : mUserFontEntry(aUserFontEntry
),
51 mFontFaceSet(aFontFaceSet
),
53 mStreamLoader(nullptr),
54 mSrcIndex(aSrcIndex
) {
55 MOZ_ASSERT(mFontFaceSet
,
56 "We should get a valid FontFaceSet from the caller!");
58 const gfxFontFaceSrc
& src
= aUserFontEntry
->SourceAt(mSrcIndex
);
59 MOZ_ASSERT(src
.mSourceType
== gfxFontFaceSrc::eSourceType_URL
);
61 mFontURI
= src
.mURI
->get();
62 mStartTime
= TimeStamp::Now();
64 // We add an explicit load block rather than just rely on the network
65 // request's block, since we need to do some OMT work after the load
66 // is finished before we unblock load.
67 mFontFaceSet
->Document()->BlockOnload();
70 nsFontFaceLoader::~nsFontFaceLoader() {
71 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback
);
72 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete
);
74 mUserFontEntry
->mLoader
= nullptr;
81 mFontFaceSet
->RemoveLoader(this);
82 mFontFaceSet
->Document()->UnblockOnload(false);
86 void nsFontFaceLoader::StartedLoading(nsIStreamLoader
* aStreamLoader
) {
88 StyleFontDisplay fontDisplay
= GetFontDisplay();
89 if (fontDisplay
== StyleFontDisplay::Auto
||
90 fontDisplay
== StyleFontDisplay::Block
) {
91 loadTimeout
= GetFallbackDelay();
93 loadTimeout
= GetShortFallbackDelay();
96 if (loadTimeout
> 0) {
97 NS_NewTimerWithFuncCallback(
98 getter_AddRefs(mLoadTimer
), LoadTimerCallback
, static_cast<void*>(this),
99 loadTimeout
, nsITimer::TYPE_ONE_SHOT
, "LoadTimerCallback",
100 mFontFaceSet
->Document()->EventTargetFor(TaskCategory::Other
));
102 mUserFontEntry
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
104 mStreamLoader
= aStreamLoader
;
108 void nsFontFaceLoader::LoadTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
109 nsFontFaceLoader
* loader
= static_cast<nsFontFaceLoader
*>(aClosure
);
111 MOZ_DIAGNOSTIC_ASSERT(!loader
->mInLoadTimerCallback
);
112 MOZ_DIAGNOSTIC_ASSERT(!loader
->mInStreamComplete
);
113 AutoRestore
<bool> scope
{loader
->mInLoadTimerCallback
};
114 loader
->mInLoadTimerCallback
= true;
116 if (!loader
->mFontFaceSet
) {
117 // We've been canceled
121 gfxUserFontEntry
* ufe
= loader
->mUserFontEntry
.get();
122 StyleFontDisplay fontDisplay
= loader
->GetFontDisplay();
124 // Depending upon the value of the font-display descriptor for the font,
125 // their may be one or two timeouts associated with each font. The
126 // LOADING_SLOWLY state indicates that the fallback font is shown. The
127 // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the
128 // downloaded font resource will not replace the fallback font when the load
131 bool updateUserFontSet
= true;
132 switch (fontDisplay
) {
133 case StyleFontDisplay::Auto
:
134 case StyleFontDisplay::Block
:
135 // If the entry is loading, check whether it's >75% done; if so,
136 // we allow another timeout period before showing a fallback font.
137 if (ufe
->mFontDataLoadingState
== gfxUserFontEntry::LOADING_STARTED
) {
138 int64_t contentLength
;
139 uint32_t numBytesRead
;
140 if (NS_SUCCEEDED(loader
->mChannel
->GetContentLength(&contentLength
)) &&
141 contentLength
> 0 && contentLength
< UINT32_MAX
&&
143 loader
->mStreamLoader
->GetNumBytesRead(&numBytesRead
)) &&
144 numBytesRead
> 3 * (uint32_t(contentLength
) >> 2)) {
145 // More than 3/4 the data has been downloaded, so allow 50% extra
146 // time and hope the remainder will arrive before the additional
148 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_ALMOST_DONE
;
150 loader
->mLoadTimer
->GetDelay(&delay
);
151 loader
->mLoadTimer
->InitWithNamedFuncCallback(
152 LoadTimerCallback
, static_cast<void*>(loader
), delay
>> 1,
153 nsITimer::TYPE_ONE_SHOT
, "nsFontFaceLoader::LoadTimerCallback");
154 updateUserFontSet
= false;
155 LOG(("userfonts (%p) 75%% done, resetting timer\n", loader
));
158 if (updateUserFontSet
) {
159 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
162 case StyleFontDisplay::Swap
:
163 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
165 case StyleFontDisplay::Fallback
: {
166 if (ufe
->mFontDataLoadingState
== gfxUserFontEntry::LOADING_STARTED
) {
167 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
169 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
170 updateUserFontSet
= false;
174 case StyleFontDisplay::Optional
:
175 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
179 MOZ_ASSERT_UNREACHABLE("strange font-display value");
183 // If the font is not 75% loaded, or if we've already timed out once
184 // before, we mark this entry as "loading slowly", so the fallback
185 // font will be used in the meantime, and tell the context to refresh.
186 if (updateUserFontSet
) {
187 nsTArray
<gfxUserFontSet
*> fontSets
;
188 ufe
->GetUserFontSets(fontSets
);
189 for (gfxUserFontSet
* fontSet
: fontSets
) {
190 nsPresContext
* ctx
= FontFaceSet::GetPresContextFor(fontSet
);
192 fontSet
->IncrementGeneration();
193 ctx
->UserFontSetUpdated(ufe
);
194 LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
195 loader
, ctx
, static_cast<int>(fontDisplay
)));
201 NS_IMPL_ISUPPORTS(nsFontFaceLoader
, nsIStreamLoaderObserver
, nsIRequestObserver
)
203 // nsIStreamLoaderObserver
205 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader
* aLoader
,
206 nsISupports
* aContext
, nsresult aStatus
,
208 const uint8_t* aString
) {
209 MOZ_ASSERT(NS_IsMainThread());
210 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback
);
211 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete
);
213 AutoRestore
<bool> scope
{mInStreamComplete
};
214 mInStreamComplete
= true;
219 mLoadTimer
->Cancel();
220 mLoadTimer
= nullptr;
224 // We've been canceled
228 TimeStamp doneTime
= TimeStamp::Now();
229 TimeDuration downloadTime
= doneTime
- mStartTime
;
230 uint32_t downloadTimeMS
= uint32_t(downloadTime
.ToMilliseconds());
231 Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME
, downloadTimeMS
);
233 if (GetFontDisplay() == StyleFontDisplay::Fallback
) {
234 uint32_t loadTimeout
= GetFallbackDelay();
235 if (downloadTimeMS
> loadTimeout
&&
236 (mUserFontEntry
->mFontDataLoadingState
==
237 gfxUserFontEntry::LOADING_SLOWLY
)) {
238 mUserFontEntry
->mFontDataLoadingState
=
239 gfxUserFontEntry::LOADING_TIMED_OUT
;
244 if (NS_SUCCEEDED(aStatus
)) {
245 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
246 this, mFontURI
->GetSpecOrDefault().get(), downloadTimeMS
));
248 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
250 this, mFontURI
->GetSpecOrDefault().get(),
251 static_cast<uint32_t>(aStatus
)));
255 if (NS_SUCCEEDED(aStatus
)) {
256 // for HTTP requests, check whether the request _actually_ succeeded;
257 // the "request status" in aStatus does not necessarily indicate this,
258 // because HTTP responses such as 404 (Not Found) will still result in
259 // a success code and potentially an HTML error page from the server
260 // as the resulting data. We don't want to use that as a font.
261 nsCOMPtr
<nsIRequest
> request
;
262 nsCOMPtr
<nsIHttpChannel
> httpChannel
;
263 aLoader
->GetRequest(getter_AddRefs(request
));
264 httpChannel
= do_QueryInterface(request
);
267 nsresult rv
= httpChannel
->GetRequestSucceeded(&succeeded
);
268 if (NS_SUCCEEDED(rv
) && !succeeded
) {
269 aStatus
= NS_ERROR_NOT_AVAILABLE
;
274 mFontFaceSet
->GetUserFontSet()->RecordFontLoadDone(aStringLen
, doneTime
);
276 // The userFontEntry is responsible for freeing the downloaded data
277 // (aString) when finished with it; the pointer is no longer valid
278 // after FontDataDownloadComplete returns.
279 // This is called even in the case of a failed download (HTTP 404, etc),
280 // as there may still be data to be freed (e.g. an error page),
281 // and we need to load the next source.
283 // FontDataDownloadComplete will load the platform font on a worker thread,
284 // and will call FontLoadComplete when it has finished its work.
285 mUserFontEntry
->FontDataDownloadComplete(mSrcIndex
, aString
, aStringLen
,
287 return NS_SUCCESS_ADOPTED_DATA
;
290 nsresult
nsFontFaceLoader::FontLoadComplete() {
291 MOZ_ASSERT(NS_IsMainThread());
294 // We've been canceled
298 // when new font loaded, need to reflow
299 nsTArray
<gfxUserFontSet
*> fontSets
;
300 mUserFontEntry
->GetUserFontSets(fontSets
);
301 for (gfxUserFontSet
* fontSet
: fontSets
) {
302 nsPresContext
* ctx
= FontFaceSet::GetPresContextFor(fontSet
);
304 // Update layout for the presence of the new font. Since this is
305 // asynchronous, reflows will coalesce.
306 ctx
->UserFontSetUpdated(mUserFontEntry
);
307 LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx
));
311 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet
);
312 mFontFaceSet
->RemoveLoader(this);
313 mFontFaceSet
->Document()->UnblockOnload(false);
314 mFontFaceSet
= nullptr;
319 // nsIRequestObserver
321 nsFontFaceLoader::OnStartRequest(nsIRequest
* aRequest
) {
322 MOZ_ASSERT(NS_IsMainThread());
324 nsCOMPtr
<nsIThreadRetargetableRequest
> req
= do_QueryInterface(aRequest
);
326 nsCOMPtr
<nsIEventTarget
> sts
=
327 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
328 Unused
<< NS_WARN_IF(NS_FAILED(req
->RetargetDeliveryTo(sts
)));
334 nsFontFaceLoader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
335 MOZ_ASSERT(NS_IsMainThread());
340 void nsFontFaceLoader::Cancel() {
341 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback
);
342 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete
);
343 MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet
);
345 mUserFontEntry
->LoadCanceled();
346 mUserFontEntry
= nullptr;
347 mFontFaceSet
->Document()->UnblockOnload(false);
348 mFontFaceSet
= nullptr;
350 mLoadTimer
->Cancel();
351 mLoadTimer
= nullptr;
353 if (nsCOMPtr
<nsIChannel
> channel
= std::move(mChannel
)) {
354 channel
->Cancel(NS_BINDING_ABORTED
);
358 StyleFontDisplay
nsFontFaceLoader::GetFontDisplay() {
359 if (!StaticPrefs::layout_css_font_display_enabled()) {
360 return StyleFontDisplay::Auto
;
362 return mUserFontEntry
->GetFontDisplay();