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/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"
28 #include "mozilla/gfx/2D.h"
30 using namespace mozilla
;
31 using namespace mozilla::dom
;
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",
47 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry
* aUserFontEntry
,
49 FontFaceSetImpl
* aFontFaceSet
,
51 : mUserFontEntry(aUserFontEntry
),
52 mFontFaceSet(aFontFaceSet
),
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();
74 nsFontFaceLoader::~nsFontFaceLoader() {
75 MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback
);
76 MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete
);
78 mUserFontEntry
->mLoader
= nullptr;
85 mFontFaceSet
->RemoveLoader(this);
86 auto* doc
= mFontFaceSet
->GetDocument();
88 doc
->UnblockOnload(false);
93 void nsFontFaceLoader::StartedLoading(nsIStreamLoader
* aStreamLoader
) {
95 StyleFontDisplay fontDisplay
= GetFontDisplay();
96 if (fontDisplay
== StyleFontDisplay::Auto
||
97 fontDisplay
== StyleFontDisplay::Block
) {
98 loadTimeout
= GetFallbackDelay();
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());
109 mUserFontEntry
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
111 mStreamLoader
= aStreamLoader
;
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
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
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
&&
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
155 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_ALMOST_DONE
;
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
;
169 case StyleFontDisplay::Swap
:
170 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
172 case StyleFontDisplay::Fallback
: {
173 if (ufe
->mFontDataLoadingState
== gfxUserFontEntry::LOADING_STARTED
) {
174 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_SLOWLY
;
176 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
177 updateUserFontSet
= false;
181 case StyleFontDisplay::Optional
:
182 ufe
->mFontDataLoadingState
= gfxUserFontEntry::LOADING_TIMED_OUT
;
186 MOZ_ASSERT_UNREACHABLE("strange font-display value");
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
);
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
212 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader
* aLoader
,
213 nsISupports
* aContext
, nsresult aStatus
,
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;
226 mLoadTimer
->Cancel();
227 mLoadTimer
= nullptr;
231 // We've been canceled
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
;
251 if (NS_SUCCEEDED(aStatus
)) {
252 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
253 this, mFontURI
->GetSpecOrDefault().get(), downloadTimeMS
));
255 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
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
);
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
,
294 return NS_SUCCESS_ADOPTED_DATA
;
297 nsresult
nsFontFaceLoader::FontLoadComplete() {
298 MOZ_ASSERT(NS_IsMainThread());
301 // We've been canceled
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
);
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();
322 doc
->UnblockOnload(false);
324 mFontFaceSet
= nullptr;
329 // nsIRequestObserver
331 nsFontFaceLoader::OnStartRequest(nsIRequest
* aRequest
) {
332 MOZ_ASSERT(NS_IsMainThread());
334 nsCOMPtr
<nsIThreadRetargetableRequest
> req
= do_QueryInterface(aRequest
);
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
)));
346 nsFontFaceLoader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
347 MOZ_ASSERT(NS_IsMainThread());
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();
361 doc
->UnblockOnload(false);
363 mFontFaceSet
= nullptr;
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 return mUserFontEntry
->GetFontDisplay();