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