Bug 1730256 [wpt PR 30555] - Move getWindowSegments to visualViewport.segments, a...
[gecko.git] / layout / style / nsFontFaceLoader.cpp
blobd8a89ba5e277afae5e78170e41b8c69fd2330591
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 uint32_t aSrcIndex,
48 FontFaceSet* aFontFaceSet,
49 nsIChannel* aChannel)
50 : mUserFontEntry(aUserFontEntry),
51 mFontFaceSet(aFontFaceSet),
52 mChannel(aChannel),
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);
73 if (mUserFontEntry) {
74 mUserFontEntry->mLoader = nullptr;
76 if (mLoadTimer) {
77 mLoadTimer->Cancel();
78 mLoadTimer = nullptr;
80 if (mFontFaceSet) {
81 mFontFaceSet->RemoveLoader(this);
82 mFontFaceSet->Document()->UnblockOnload(false);
86 void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) {
87 int32_t loadTimeout;
88 StyleFontDisplay fontDisplay = GetFontDisplay();
89 if (fontDisplay == StyleFontDisplay::Auto ||
90 fontDisplay == StyleFontDisplay::Block) {
91 loadTimeout = GetFallbackDelay();
92 } else {
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));
101 } else {
102 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
104 mStreamLoader = aStreamLoader;
107 /* static */
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
118 return;
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
129 // completes.
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 &&
142 NS_SUCCEEDED(
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
147 // time expires.
148 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
149 uint32_t delay;
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;
161 break;
162 case StyleFontDisplay::Swap:
163 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
164 break;
165 case StyleFontDisplay::Fallback: {
166 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
167 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
168 } else {
169 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
170 updateUserFontSet = false;
172 break;
174 case StyleFontDisplay::Optional:
175 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
176 break;
178 default:
179 MOZ_ASSERT_UNREACHABLE("strange font-display value");
180 break;
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);
191 if (ctx) {
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
204 NS_IMETHODIMP
205 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
206 nsISupports* aContext, nsresult aStatus,
207 uint32_t aStringLen,
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;
216 DropChannel();
218 if (mLoadTimer) {
219 mLoadTimer->Cancel();
220 mLoadTimer = nullptr;
223 if (!mFontFaceSet) {
224 // We've been canceled
225 return aStatus;
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;
243 if (LOG_ENABLED()) {
244 if (NS_SUCCEEDED(aStatus)) {
245 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
246 this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS));
247 } else {
248 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
249 "\n",
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);
265 if (httpChannel) {
266 bool succeeded;
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,
286 aStatus, this);
287 return NS_SUCCESS_ADOPTED_DATA;
290 nsresult nsFontFaceLoader::FontLoadComplete() {
291 MOZ_ASSERT(NS_IsMainThread());
293 if (!mFontFaceSet) {
294 // We've been canceled
295 return NS_OK;
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);
303 if (ctx) {
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;
316 return NS_OK;
319 // nsIRequestObserver
320 NS_IMETHODIMP
321 nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) {
322 MOZ_ASSERT(NS_IsMainThread());
324 nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest);
325 if (req) {
326 nsCOMPtr<nsIEventTarget> sts =
327 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
328 Unused << NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(sts)));
330 return NS_OK;
333 NS_IMETHODIMP
334 nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
335 MOZ_ASSERT(NS_IsMainThread());
336 DropChannel();
337 return NS_OK;
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;
349 if (mLoadTimer) {
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();
365 #undef LOG
366 #undef LOG_ENABLED