1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
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/. */
6
7 /* code for loading in @font-face defined font data */
8
9 #include "mozilla/Logging.h"
10
11 #include "nsFontFaceLoader.h"
12
13 #include "nsError.h"
14 #include "nsContentUtils.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/Telemetry.h"
17 #include "FontFaceSet.h"
18 #include "nsPresContext.h"
19 #include "nsIPrincipal.h"
20 #include "nsIScriptSecurityManager.h"
21 #include "nsIHttpChannel.h"
22 #include "nsIContentPolicy.h"
23 #include "nsContentPolicyUtils.h"
24
25 #include "mozilla/gfx/2D.h"
26
27 using namespace mozilla;
28 using namespace mozilla::dom;
29
30 #define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
31 #define LOG_ENABLED() MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), \
32 LogLevel::Debug)
33
34 static uint32_t
GetFallbackDelay()35 GetFallbackDelay()
36 {
37 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
38 }
39
40 static uint32_t
GetShortFallbackDelay()41 GetShortFallbackDelay()
42 {
43 return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short", 100);
44 }
45
nsFontFaceLoader(gfxUserFontEntry * aUserFontEntry,nsIURI * aFontURI,FontFaceSet * aFontFaceSet,nsIChannel * aChannel)46 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
47 nsIURI* aFontURI,
48 FontFaceSet* aFontFaceSet,
49 nsIChannel* aChannel)
50 : mUserFontEntry(aUserFontEntry),
51 mFontURI(aFontURI),
52 mFontFaceSet(aFontFaceSet),
53 mChannel(aChannel)
54 {
55 mStartTime = TimeStamp::Now();
56 }
57
~nsFontFaceLoader()58 nsFontFaceLoader::~nsFontFaceLoader()
59 {
60 if (mUserFontEntry) {
61 mUserFontEntry->mLoader = nullptr;
62 }
63 if (mLoadTimer) {
64 mLoadTimer->Cancel();
65 mLoadTimer = nullptr;
66 }
67 if (mFontFaceSet) {
68 mFontFaceSet->RemoveLoader(this);
69 }
70 }
71
72 void
StartedLoading(nsIStreamLoader * aStreamLoader)73 nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader)
74 {
75 int32_t loadTimeout;
76 uint8_t fontDisplay = GetFontDisplay();
77 if (fontDisplay == NS_FONT_DISPLAY_AUTO ||
78 fontDisplay == NS_FONT_DISPLAY_BLOCK) {
79 loadTimeout = GetFallbackDelay();
80 } else {
81 loadTimeout = GetShortFallbackDelay();
82 }
83
84 if (loadTimeout > 0) {
85 mLoadTimer = do_CreateInstance("@mozilla.org/timer;1");
86 if (mLoadTimer) {
87 mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
88 static_cast<void*>(this),
89 loadTimeout,
90 nsITimer::TYPE_ONE_SHOT);
91 }
92 } else {
93 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
94 }
95 mStreamLoader = aStreamLoader;
96 }
97
98 /* static */ void
LoadTimerCallback(nsITimer * aTimer,void * aClosure)99 nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure)
100 {
101 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
102
103 if (!loader->mFontFaceSet) {
104 // We've been canceled
105 return;
106 }
107
108 gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
109 uint8_t fontDisplay = loader->GetFontDisplay();
110
111 // Depending upon the value of the font-display descriptor for the font,
112 // their may be one or two timeouts associated with each font. The LOADING_SLOWLY
113 // state indicates that the fallback font is shown. The LOADING_TIMED_OUT
114 // state indicates that the fallback font is shown *and* the downloaded font
115 // resource will not replace the fallback font when the load completes.
116
117 bool updateUserFontSet = true;
118 switch (fontDisplay) {
119 case NS_FONT_DISPLAY_AUTO:
120 case NS_FONT_DISPLAY_BLOCK:
121 // If the entry is loading, check whether it's >75% done; if so,
122 // we allow another timeout period before showing a fallback font.
123 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
124 int64_t contentLength;
125 uint32_t numBytesRead;
126 if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
127 contentLength > 0 &&
128 contentLength < UINT32_MAX &&
129 NS_SUCCEEDED(loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
130 numBytesRead > 3 * (uint32_t(contentLength) >> 2))
131 {
132 // More than 3/4 the data has been downloaded, so allow 50% extra
133 // time and hope the remainder will arrive before the additional
134 // time expires.
135 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
136 uint32_t delay;
137 loader->mLoadTimer->GetDelay(&delay);
138 loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
139 static_cast<void*>(loader),
140 delay >> 1,
141 nsITimer::TYPE_ONE_SHOT);
142 updateUserFontSet = false;
143 LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
144 }
145 }
146 if (updateUserFontSet) {
147 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
148 }
149 break;
150 case NS_FONT_DISPLAY_SWAP:
151 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
152 break;
153 case NS_FONT_DISPLAY_FALLBACK: {
154 if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
155 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
156 } else {
157 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
158 updateUserFontSet = false;
159 }
160 break;
161 }
162 case NS_FONT_DISPLAY_OPTIONAL:
163 ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
164 break;
165
166 default:
167 NS_NOTREACHED("strange font-display value");
168 break;
169 }
170
171 // If the font is not 75% loaded, or if we've already timed out once
172 // before, we mark this entry as "loading slowly", so the fallback
173 // font will be used in the meantime, and tell the context to refresh.
174 if (updateUserFontSet) {
175 nsTArray<gfxUserFontSet*> fontSets;
176 ufe->GetUserFontSets(fontSets);
177 for (gfxUserFontSet* fontSet : fontSets) {
178 nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
179 if (ctx) {
180 fontSet->IncrementGeneration();
181 ctx->UserFontSetUpdated(ufe);
182 LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
183 loader, ctx, fontDisplay));
184 }
185 }
186 }
187 }
188
NS_IMPL_ISUPPORTS(nsFontFaceLoader,nsIStreamLoaderObserver)189 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
190
191 NS_IMETHODIMP
192 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
193 nsISupports* aContext,
194 nsresult aStatus,
195 uint32_t aStringLen,
196 const uint8_t* aString)
197 {
198 if (!mFontFaceSet) {
199 // We've been canceled
200 return aStatus;
201 }
202
203 mFontFaceSet->RemoveLoader(this);
204
205 TimeStamp doneTime = TimeStamp::Now();
206 TimeDuration downloadTime = doneTime - mStartTime;
207 uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
208 Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
209
210 if (GetFontDisplay() == NS_FONT_DISPLAY_FALLBACK) {
211 uint32_t loadTimeout = GetFallbackDelay();
212 if (downloadTimeMS > loadTimeout &&
213 (mUserFontEntry->mFontDataLoadingState ==
214 gfxUserFontEntry::LOADING_SLOWLY)) {
215 mUserFontEntry->mFontDataLoadingState =
216 gfxUserFontEntry::LOADING_TIMED_OUT;
217 }
218 }
219
220 if (LOG_ENABLED()) {
221 if (NS_SUCCEEDED(aStatus)) {
222 LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
223 this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS));
224 } else {
225 LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8x\n",
226 this, mFontURI->GetSpecOrDefault().get(), aStatus));
227 }
228 }
229
230 if (NS_SUCCEEDED(aStatus)) {
231 // for HTTP requests, check whether the request _actually_ succeeded;
232 // the "request status" in aStatus does not necessarily indicate this,
233 // because HTTP responses such as 404 (Not Found) will still result in
234 // a success code and potentially an HTML error page from the server
235 // as the resulting data. We don't want to use that as a font.
236 nsCOMPtr<nsIRequest> request;
237 nsCOMPtr<nsIHttpChannel> httpChannel;
238 aLoader->GetRequest(getter_AddRefs(request));
239 httpChannel = do_QueryInterface(request);
240 if (httpChannel) {
241 bool succeeded;
242 nsresult rv = httpChannel->GetRequestSucceeded(&succeeded);
243 if (NS_SUCCEEDED(rv) && !succeeded) {
244 aStatus = NS_ERROR_NOT_AVAILABLE;
245 }
246 }
247 }
248
249 // The userFontEntry is responsible for freeing the downloaded data
250 // (aString) when finished with it; the pointer is no longer valid
251 // after FontDataDownloadComplete returns.
252 // This is called even in the case of a failed download (HTTP 404, etc),
253 // as there may still be data to be freed (e.g. an error page),
254 // and we need to load the next source.
255 bool fontUpdate =
256 mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus);
257
258 mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime);
259
260 // when new font loaded, need to reflow
261 if (fontUpdate) {
262 nsTArray<gfxUserFontSet*> fontSets;
263 mUserFontEntry->GetUserFontSets(fontSets);
264 for (gfxUserFontSet* fontSet : fontSets) {
265 nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
266 if (ctx) {
267 // Update layout for the presence of the new font. Since this is
268 // asynchronous, reflows will coalesce.
269 ctx->UserFontSetUpdated(mUserFontEntry);
270 LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx));
271 }
272 }
273 }
274
275 // done with font set
276 mFontFaceSet = nullptr;
277 if (mLoadTimer) {
278 mLoadTimer->Cancel();
279 mLoadTimer = nullptr;
280 }
281
282 return NS_SUCCESS_ADOPTED_DATA;
283 }
284
285 void
Cancel()286 nsFontFaceLoader::Cancel()
287 {
288 mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::NOT_LOADING;
289 mUserFontEntry->mLoader = nullptr;
290 mFontFaceSet = nullptr;
291 if (mLoadTimer) {
292 mLoadTimer->Cancel();
293 mLoadTimer = nullptr;
294 }
295 mChannel->Cancel(NS_BINDING_ABORTED);
296 }
297
298 uint8_t
GetFontDisplay()299 nsFontFaceLoader::GetFontDisplay()
300 {
301 uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
302 if (Preferences::GetBool("layout.css.font-display.enabled")) {
303 fontDisplay = mUserFontEntry->GetFontDisplay();
304 }
305 return fontDisplay;
306 }
307