|
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 #ifdef MOZ_LOGGING |
|
10 #define FORCE_PR_LOG /* Allow logging in the release build */ |
|
11 #endif /* MOZ_LOGGING */ |
|
12 #include "prlog.h" |
|
13 |
|
14 #include "nsFontFaceLoader.h" |
|
15 |
|
16 #include "nsError.h" |
|
17 #include "nsNetUtil.h" |
|
18 #include "nsContentUtils.h" |
|
19 #include "mozilla/Preferences.h" |
|
20 |
|
21 #include "nsPresContext.h" |
|
22 #include "nsIPresShell.h" |
|
23 #include "nsIPrincipal.h" |
|
24 #include "nsIScriptSecurityManager.h" |
|
25 |
|
26 #include "nsIContentPolicy.h" |
|
27 #include "nsContentPolicyUtils.h" |
|
28 #include "nsCrossSiteListenerProxy.h" |
|
29 #include "nsIContentSecurityPolicy.h" |
|
30 #include "nsIDocShell.h" |
|
31 #include "nsIWebNavigation.h" |
|
32 #include "nsISupportsPriority.h" |
|
33 #include "nsINetworkSeer.h" |
|
34 |
|
35 #include "nsIConsoleService.h" |
|
36 |
|
37 #include "nsStyleSet.h" |
|
38 #include "nsPrintfCString.h" |
|
39 #include "mozilla/gfx/2D.h" |
|
40 |
|
41 using namespace mozilla; |
|
42 |
|
43 #ifdef PR_LOGGING |
|
44 static PRLogModuleInfo* |
|
45 GetFontDownloaderLog() |
|
46 { |
|
47 static PRLogModuleInfo* sLog; |
|
48 if (!sLog) |
|
49 sLog = PR_NewLogModule("fontdownloader"); |
|
50 return sLog; |
|
51 } |
|
52 #endif /* PR_LOGGING */ |
|
53 |
|
54 #define LOG(args) PR_LOG(GetFontDownloaderLog(), PR_LOG_DEBUG, args) |
|
55 #define LOG_ENABLED() PR_LOG_TEST(GetFontDownloaderLog(), PR_LOG_DEBUG) |
|
56 |
|
57 |
|
58 nsFontFaceLoader::nsFontFaceLoader(gfxMixedFontFamily* aFontFamily, |
|
59 gfxProxyFontEntry* aProxy, |
|
60 nsIURI* aFontURI, |
|
61 nsUserFontSet* aFontSet, |
|
62 nsIChannel* aChannel) |
|
63 : mFontFamily(aFontFamily), |
|
64 mFontEntry(aProxy), |
|
65 mFontURI(aFontURI), |
|
66 mFontSet(aFontSet), |
|
67 mChannel(aChannel) |
|
68 { |
|
69 } |
|
70 |
|
71 nsFontFaceLoader::~nsFontFaceLoader() |
|
72 { |
|
73 if (mFontEntry) { |
|
74 mFontEntry->mLoader = nullptr; |
|
75 } |
|
76 if (mLoadTimer) { |
|
77 mLoadTimer->Cancel(); |
|
78 mLoadTimer = nullptr; |
|
79 } |
|
80 if (mFontSet) { |
|
81 mFontSet->RemoveLoader(this); |
|
82 } |
|
83 } |
|
84 |
|
85 void |
|
86 nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) |
|
87 { |
|
88 int32_t loadTimeout = |
|
89 Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000); |
|
90 if (loadTimeout > 0) { |
|
91 mLoadTimer = do_CreateInstance("@mozilla.org/timer;1"); |
|
92 if (mLoadTimer) { |
|
93 mLoadTimer->InitWithFuncCallback(LoadTimerCallback, |
|
94 static_cast<void*>(this), |
|
95 loadTimeout, |
|
96 nsITimer::TYPE_ONE_SHOT); |
|
97 } |
|
98 } else if (loadTimeout == 0) { |
|
99 mFontEntry->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY; |
|
100 } // -1 disables fallback |
|
101 mStreamLoader = aStreamLoader; |
|
102 } |
|
103 |
|
104 void |
|
105 nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) |
|
106 { |
|
107 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure); |
|
108 |
|
109 if (!loader->mFontSet) { |
|
110 // We've been canceled |
|
111 return; |
|
112 } |
|
113 |
|
114 gfxProxyFontEntry* pe = loader->mFontEntry.get(); |
|
115 bool updateUserFontSet = true; |
|
116 |
|
117 // If the entry is loading, check whether it's >75% done; if so, |
|
118 // we allow another timeout period before showing a fallback font. |
|
119 if (pe->mLoadingState == gfxProxyFontEntry::LOADING_STARTED) { |
|
120 int64_t contentLength; |
|
121 uint32_t numBytesRead; |
|
122 if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) && |
|
123 contentLength > 0 && |
|
124 contentLength < UINT32_MAX && |
|
125 NS_SUCCEEDED(loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) && |
|
126 numBytesRead > 3 * (uint32_t(contentLength) >> 2)) |
|
127 { |
|
128 // More than 3/4 the data has been downloaded, so allow 50% extra |
|
129 // time and hope the remainder will arrive before the additional |
|
130 // time expires. |
|
131 pe->mLoadingState = gfxProxyFontEntry::LOADING_ALMOST_DONE; |
|
132 uint32_t delay; |
|
133 loader->mLoadTimer->GetDelay(&delay); |
|
134 loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback, |
|
135 static_cast<void*>(loader), |
|
136 delay >> 1, |
|
137 nsITimer::TYPE_ONE_SHOT); |
|
138 updateUserFontSet = false; |
|
139 LOG(("fontdownloader (%p) 75%% done, resetting timer\n", loader)); |
|
140 } |
|
141 } |
|
142 |
|
143 // If the font is not 75% loaded, or if we've already timed out once |
|
144 // before, we mark this entry as "loading slowly", so the fallback |
|
145 // font will be used in the meantime, and tell the context to refresh. |
|
146 if (updateUserFontSet) { |
|
147 pe->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY; |
|
148 gfxUserFontSet* fontSet = loader->mFontSet; |
|
149 nsPresContext* ctx = loader->mFontSet->GetPresContext(); |
|
150 NS_ASSERTION(ctx, "userfontset doesn't have a presContext?"); |
|
151 if (ctx) { |
|
152 fontSet->IncrementGeneration(); |
|
153 ctx->UserFontSetUpdated(); |
|
154 LOG(("fontdownloader (%p) timeout reflow\n", loader)); |
|
155 } |
|
156 } |
|
157 } |
|
158 |
|
159 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver) |
|
160 |
|
161 NS_IMETHODIMP |
|
162 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader, |
|
163 nsISupports* aContext, |
|
164 nsresult aStatus, |
|
165 uint32_t aStringLen, |
|
166 const uint8_t* aString) |
|
167 { |
|
168 if (!mFontSet) { |
|
169 // We've been canceled |
|
170 return aStatus; |
|
171 } |
|
172 |
|
173 mFontSet->RemoveLoader(this); |
|
174 |
|
175 #ifdef PR_LOGGING |
|
176 if (LOG_ENABLED()) { |
|
177 nsAutoCString fontURI; |
|
178 mFontURI->GetSpec(fontURI); |
|
179 if (NS_SUCCEEDED(aStatus)) { |
|
180 LOG(("fontdownloader (%p) download completed - font uri: (%s)\n", |
|
181 this, fontURI.get())); |
|
182 } else { |
|
183 LOG(("fontdownloader (%p) download failed - font uri: (%s) error: %8.8x\n", |
|
184 this, fontURI.get(), aStatus)); |
|
185 } |
|
186 } |
|
187 #endif |
|
188 |
|
189 nsPresContext* ctx = mFontSet->GetPresContext(); |
|
190 NS_ASSERTION(ctx && !ctx->PresShell()->IsDestroying(), |
|
191 "We should have been canceled already"); |
|
192 |
|
193 if (NS_SUCCEEDED(aStatus)) { |
|
194 // for HTTP requests, check whether the request _actually_ succeeded; |
|
195 // the "request status" in aStatus does not necessarily indicate this, |
|
196 // because HTTP responses such as 404 (Not Found) will still result in |
|
197 // a success code and potentially an HTML error page from the server |
|
198 // as the resulting data. We don't want to use that as a font. |
|
199 nsCOMPtr<nsIRequest> request; |
|
200 nsCOMPtr<nsIHttpChannel> httpChannel; |
|
201 aLoader->GetRequest(getter_AddRefs(request)); |
|
202 httpChannel = do_QueryInterface(request); |
|
203 if (httpChannel) { |
|
204 bool succeeded; |
|
205 nsresult rv = httpChannel->GetRequestSucceeded(&succeeded); |
|
206 if (NS_SUCCEEDED(rv) && !succeeded) { |
|
207 aStatus = NS_ERROR_NOT_AVAILABLE; |
|
208 } |
|
209 } |
|
210 } |
|
211 |
|
212 // The userFontSet is responsible for freeing the downloaded data |
|
213 // (aString) when finished with it; the pointer is no longer valid |
|
214 // after OnLoadComplete returns. |
|
215 // This is called even in the case of a failed download (HTTP 404, etc), |
|
216 // as there may still be data to be freed (e.g. an error page), |
|
217 // and we need the fontSet to initiate loading the next source. |
|
218 bool fontUpdate = mFontSet->OnLoadComplete(mFontFamily, mFontEntry, aString, |
|
219 aStringLen, aStatus); |
|
220 |
|
221 // when new font loaded, need to reflow |
|
222 if (fontUpdate) { |
|
223 // Update layout for the presence of the new font. Since this is |
|
224 // asynchronous, reflows will coalesce. |
|
225 ctx->UserFontSetUpdated(); |
|
226 LOG(("fontdownloader (%p) reflow\n", this)); |
|
227 } |
|
228 |
|
229 // done with font set |
|
230 mFontSet = nullptr; |
|
231 if (mLoadTimer) { |
|
232 mLoadTimer->Cancel(); |
|
233 mLoadTimer = nullptr; |
|
234 } |
|
235 |
|
236 return NS_SUCCESS_ADOPTED_DATA; |
|
237 } |
|
238 |
|
239 void |
|
240 nsFontFaceLoader::Cancel() |
|
241 { |
|
242 mFontEntry->mLoadingState = gfxProxyFontEntry::NOT_LOADING; |
|
243 mFontEntry->mLoader = nullptr; |
|
244 mFontSet = nullptr; |
|
245 if (mLoadTimer) { |
|
246 mLoadTimer->Cancel(); |
|
247 mLoadTimer = nullptr; |
|
248 } |
|
249 mChannel->Cancel(NS_BINDING_ABORTED); |
|
250 } |
|
251 |
|
252 nsresult |
|
253 nsFontFaceLoader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal, |
|
254 nsIURI* aTargetURI, |
|
255 nsISupports* aContext) |
|
256 { |
|
257 nsresult rv; |
|
258 |
|
259 if (!aSourcePrincipal) |
|
260 return NS_OK; |
|
261 |
|
262 // check with the security manager |
|
263 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); |
|
264 rv = secMan->CheckLoadURIWithPrincipal(aSourcePrincipal, aTargetURI, |
|
265 nsIScriptSecurityManager::STANDARD); |
|
266 if (NS_FAILED(rv)) { |
|
267 return rv; |
|
268 } |
|
269 |
|
270 // check content policy |
|
271 int16_t shouldLoad = nsIContentPolicy::ACCEPT; |
|
272 rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_FONT, |
|
273 aTargetURI, |
|
274 aSourcePrincipal, |
|
275 aContext, |
|
276 EmptyCString(), // mime type |
|
277 nullptr, |
|
278 &shouldLoad, |
|
279 nsContentUtils::GetContentPolicy(), |
|
280 nsContentUtils::GetSecurityManager()); |
|
281 |
|
282 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { |
|
283 return NS_ERROR_CONTENT_BLOCKED; |
|
284 } |
|
285 |
|
286 return NS_OK; |
|
287 } |
|
288 |
|
289 nsUserFontSet::nsUserFontSet(nsPresContext* aContext) |
|
290 : mPresContext(aContext) |
|
291 { |
|
292 NS_ASSERTION(mPresContext, "null context passed to nsUserFontSet"); |
|
293 } |
|
294 |
|
295 nsUserFontSet::~nsUserFontSet() |
|
296 { |
|
297 NS_ASSERTION(mLoaders.Count() == 0, "mLoaders should have been emptied"); |
|
298 } |
|
299 |
|
300 static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey, |
|
301 void* aUserArg) |
|
302 { |
|
303 aKey->GetKey()->Cancel(); |
|
304 return PL_DHASH_REMOVE; |
|
305 } |
|
306 |
|
307 void |
|
308 nsUserFontSet::Destroy() |
|
309 { |
|
310 mPresContext = nullptr; |
|
311 mLoaders.EnumerateEntries(DestroyIterator, nullptr); |
|
312 mRules.Clear(); |
|
313 } |
|
314 |
|
315 void |
|
316 nsUserFontSet::RemoveLoader(nsFontFaceLoader* aLoader) |
|
317 { |
|
318 mLoaders.RemoveEntry(aLoader); |
|
319 } |
|
320 |
|
321 nsresult |
|
322 nsUserFontSet::StartLoad(gfxMixedFontFamily* aFamily, |
|
323 gfxProxyFontEntry* aProxy, |
|
324 const gfxFontFaceSrc* aFontFaceSrc) |
|
325 { |
|
326 nsresult rv; |
|
327 |
|
328 nsIPresShell* ps = mPresContext->PresShell(); |
|
329 if (!ps) |
|
330 return NS_ERROR_FAILURE; |
|
331 |
|
332 nsCOMPtr<nsIStreamLoader> streamLoader; |
|
333 nsCOMPtr<nsILoadGroup> loadGroup(ps->GetDocument()->GetDocumentLoadGroup()); |
|
334 |
|
335 nsCOMPtr<nsIChannel> channel; |
|
336 // get Content Security Policy from principal to pass into channel |
|
337 nsCOMPtr<nsIChannelPolicy> channelPolicy; |
|
338 nsCOMPtr<nsIContentSecurityPolicy> csp; |
|
339 rv = aProxy->mPrincipal->GetCsp(getter_AddRefs(csp)); |
|
340 NS_ENSURE_SUCCESS(rv, rv); |
|
341 if (csp) { |
|
342 channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); |
|
343 channelPolicy->SetContentSecurityPolicy(csp); |
|
344 channelPolicy->SetLoadType(nsIContentPolicy::TYPE_FONT); |
|
345 } |
|
346 rv = NS_NewChannel(getter_AddRefs(channel), |
|
347 aFontFaceSrc->mURI, |
|
348 nullptr, |
|
349 loadGroup, |
|
350 nullptr, |
|
351 nsIRequest::LOAD_NORMAL, |
|
352 channelPolicy); |
|
353 |
|
354 NS_ENSURE_SUCCESS(rv, rv); |
|
355 |
|
356 nsRefPtr<nsFontFaceLoader> fontLoader = |
|
357 new nsFontFaceLoader(aFamily, aProxy, aFontFaceSrc->mURI, this, channel); |
|
358 |
|
359 if (!fontLoader) |
|
360 return NS_ERROR_OUT_OF_MEMORY; |
|
361 |
|
362 #ifdef PR_LOGGING |
|
363 if (LOG_ENABLED()) { |
|
364 nsAutoCString fontURI, referrerURI; |
|
365 aFontFaceSrc->mURI->GetSpec(fontURI); |
|
366 if (aFontFaceSrc->mReferrer) |
|
367 aFontFaceSrc->mReferrer->GetSpec(referrerURI); |
|
368 LOG(("fontdownloader (%p) download start - font uri: (%s) " |
|
369 "referrer uri: (%s)\n", |
|
370 fontLoader.get(), fontURI.get(), referrerURI.get())); |
|
371 } |
|
372 #endif |
|
373 |
|
374 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); |
|
375 if (httpChannel) |
|
376 httpChannel->SetReferrer(aFontFaceSrc->mReferrer); |
|
377 nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(channel)); |
|
378 if (priorityChannel) { |
|
379 priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGH); |
|
380 } |
|
381 |
|
382 rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader); |
|
383 NS_ENSURE_SUCCESS(rv, rv); |
|
384 |
|
385 nsIDocument *document = ps->GetDocument(); |
|
386 mozilla::net::SeerLearn(aFontFaceSrc->mURI, document->GetDocumentURI(), |
|
387 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadGroup); |
|
388 |
|
389 bool inherits = false; |
|
390 rv = NS_URIChainHasFlags(aFontFaceSrc->mURI, |
|
391 nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, |
|
392 &inherits); |
|
393 if (NS_SUCCEEDED(rv) && inherits) { |
|
394 // allow data, javascript, etc URI's |
|
395 rv = channel->AsyncOpen(streamLoader, nullptr); |
|
396 } else { |
|
397 nsRefPtr<nsCORSListenerProxy> listener = |
|
398 new nsCORSListenerProxy(streamLoader, aProxy->mPrincipal, false); |
|
399 rv = listener->Init(channel); |
|
400 if (NS_SUCCEEDED(rv)) { |
|
401 rv = channel->AsyncOpen(listener, nullptr); |
|
402 } |
|
403 if (NS_FAILED(rv)) { |
|
404 fontLoader->DropChannel(); // explicitly need to break ref cycle |
|
405 } |
|
406 } |
|
407 |
|
408 if (NS_SUCCEEDED(rv)) { |
|
409 mLoaders.PutEntry(fontLoader); |
|
410 fontLoader->StartedLoading(streamLoader); |
|
411 aProxy->mLoader = fontLoader; // let the font entry remember the loader, |
|
412 // in case we need to cancel it |
|
413 } |
|
414 |
|
415 return rv; |
|
416 } |
|
417 |
|
418 static PLDHashOperator DetachFontEntries(const nsAString& aKey, |
|
419 nsRefPtr<gfxMixedFontFamily>& aFamily, |
|
420 void* aUserArg) |
|
421 { |
|
422 aFamily->DetachFontEntries(); |
|
423 return PL_DHASH_NEXT; |
|
424 } |
|
425 |
|
426 static PLDHashOperator RemoveIfEmpty(const nsAString& aKey, |
|
427 nsRefPtr<gfxMixedFontFamily>& aFamily, |
|
428 void* aUserArg) |
|
429 { |
|
430 return aFamily->GetFontList().Length() ? PL_DHASH_NEXT : PL_DHASH_REMOVE; |
|
431 } |
|
432 |
|
433 bool |
|
434 nsUserFontSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) |
|
435 { |
|
436 bool modified = false; |
|
437 |
|
438 // The @font-face rules that make up the user font set have changed, |
|
439 // so we need to update the set. However, we want to preserve existing |
|
440 // font entries wherever possible, so that we don't discard and then |
|
441 // re-download resources in the (common) case where at least some of the |
|
442 // same rules are still present. |
|
443 |
|
444 nsTArray<FontFaceRuleRecord> oldRules; |
|
445 mRules.SwapElements(oldRules); |
|
446 |
|
447 // Remove faces from the font family records; we need to re-insert them |
|
448 // because we might end up with faces in a different order even if they're |
|
449 // the same font entries as before. (The order can affect font selection |
|
450 // where multiple faces match the requested style, perhaps with overlapping |
|
451 // unicode-range coverage.) |
|
452 mFontFamilies.Enumerate(DetachFontEntries, nullptr); |
|
453 |
|
454 for (uint32_t i = 0, i_end = aRules.Length(); i < i_end; ++i) { |
|
455 // Insert each rule into our list, migrating old font entries if possible |
|
456 // rather than creating new ones; set modified to true if we detect |
|
457 // that rule ordering has changed, or if a new entry is created. |
|
458 InsertRule(aRules[i].mRule, aRules[i].mSheetType, oldRules, modified); |
|
459 } |
|
460 |
|
461 // Remove any residual families that have no font entries (i.e., they were |
|
462 // not defined at all by the updated set of @font-face rules). |
|
463 mFontFamilies.Enumerate(RemoveIfEmpty, nullptr); |
|
464 |
|
465 // If any rules are left in the old list, note that the set has changed |
|
466 // (even if the new set was built entirely by migrating old font entries). |
|
467 if (oldRules.Length() > 0) { |
|
468 modified = true; |
|
469 // Any in-progress loaders for obsolete rules should be cancelled, |
|
470 // as the resource being downloaded will no longer be required. |
|
471 // We need to explicitly remove any loaders here, otherwise the loaders |
|
472 // will keep their "orphaned" font entries alive until they complete, |
|
473 // even after the oldRules array is deleted. |
|
474 size_t count = oldRules.Length(); |
|
475 for (size_t i = 0; i < count; ++i) { |
|
476 gfxFontEntry* fe = oldRules[i].mFontEntry; |
|
477 if (!fe->mIsProxy) { |
|
478 continue; |
|
479 } |
|
480 gfxProxyFontEntry* proxy = static_cast<gfxProxyFontEntry*>(fe); |
|
481 nsFontFaceLoader* loader = proxy->mLoader; |
|
482 if (loader) { |
|
483 loader->Cancel(); |
|
484 RemoveLoader(loader); |
|
485 } |
|
486 } |
|
487 } |
|
488 |
|
489 if (modified) { |
|
490 IncrementGeneration(); |
|
491 } |
|
492 |
|
493 // local rules have been rebuilt, so clear the flag |
|
494 mLocalRulesUsed = false; |
|
495 |
|
496 return modified; |
|
497 } |
|
498 |
|
499 static bool |
|
500 HasLocalSrc(const nsCSSValue::Array *aSrcArr) |
|
501 { |
|
502 size_t numSrc = aSrcArr->Count(); |
|
503 for (size_t i = 0; i < numSrc; i++) { |
|
504 if (aSrcArr->Item(i).GetUnit() == eCSSUnit_Local_Font) { |
|
505 return true; |
|
506 } |
|
507 } |
|
508 return false; |
|
509 } |
|
510 |
|
511 void |
|
512 nsUserFontSet::InsertRule(nsCSSFontFaceRule* aRule, uint8_t aSheetType, |
|
513 nsTArray<FontFaceRuleRecord>& aOldRules, |
|
514 bool& aFontSetModified) |
|
515 { |
|
516 NS_ABORT_IF_FALSE(aRule->GetType() == mozilla::css::Rule::FONT_FACE_RULE, |
|
517 "InsertRule passed a non-fontface CSS rule"); |
|
518 |
|
519 // set up family name |
|
520 nsAutoString fontfamily; |
|
521 nsCSSValue val; |
|
522 uint32_t unit; |
|
523 |
|
524 aRule->GetDesc(eCSSFontDesc_Family, val); |
|
525 unit = val.GetUnit(); |
|
526 if (unit == eCSSUnit_String) { |
|
527 val.GetStringValue(fontfamily); |
|
528 } else { |
|
529 NS_ASSERTION(unit == eCSSUnit_Null, |
|
530 "@font-face family name has unexpected unit"); |
|
531 } |
|
532 if (fontfamily.IsEmpty()) { |
|
533 // If there is no family name, this rule cannot contribute a |
|
534 // usable font, so there is no point in processing it further. |
|
535 return; |
|
536 } |
|
537 |
|
538 // first, we check in oldRules; if the rule exists there, just move it |
|
539 // to the new rule list, and put the entry into the appropriate family |
|
540 for (uint32_t i = 0; i < aOldRules.Length(); ++i) { |
|
541 const FontFaceRuleRecord& ruleRec = aOldRules[i]; |
|
542 |
|
543 if (ruleRec.mContainer.mRule == aRule && |
|
544 ruleRec.mContainer.mSheetType == aSheetType) { |
|
545 |
|
546 // if local rules were used, don't use the old font entry |
|
547 // for rules containing src local usage |
|
548 if (mLocalRulesUsed) { |
|
549 aRule->GetDesc(eCSSFontDesc_Src, val); |
|
550 unit = val.GetUnit(); |
|
551 if (unit == eCSSUnit_Array && HasLocalSrc(val.GetArrayValue())) { |
|
552 break; |
|
553 } |
|
554 } |
|
555 |
|
556 AddFontFace(fontfamily, ruleRec.mFontEntry); |
|
557 mRules.AppendElement(ruleRec); |
|
558 aOldRules.RemoveElementAt(i); |
|
559 // note the set has been modified if an old rule was skipped to find |
|
560 // this one - something has been dropped, or ordering changed |
|
561 if (i > 0) { |
|
562 aFontSetModified = true; |
|
563 } |
|
564 return; |
|
565 } |
|
566 } |
|
567 |
|
568 // this is a new rule: |
|
569 |
|
570 uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL; |
|
571 int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL; |
|
572 uint32_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL; |
|
573 nsString languageOverride; |
|
574 |
|
575 // set up weight |
|
576 aRule->GetDesc(eCSSFontDesc_Weight, val); |
|
577 unit = val.GetUnit(); |
|
578 if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) { |
|
579 weight = val.GetIntValue(); |
|
580 } else if (unit == eCSSUnit_Normal) { |
|
581 weight = NS_STYLE_FONT_WEIGHT_NORMAL; |
|
582 } else { |
|
583 NS_ASSERTION(unit == eCSSUnit_Null, |
|
584 "@font-face weight has unexpected unit"); |
|
585 } |
|
586 |
|
587 // set up stretch |
|
588 aRule->GetDesc(eCSSFontDesc_Stretch, val); |
|
589 unit = val.GetUnit(); |
|
590 if (unit == eCSSUnit_Enumerated) { |
|
591 stretch = val.GetIntValue(); |
|
592 } else if (unit == eCSSUnit_Normal) { |
|
593 stretch = NS_STYLE_FONT_STRETCH_NORMAL; |
|
594 } else { |
|
595 NS_ASSERTION(unit == eCSSUnit_Null, |
|
596 "@font-face stretch has unexpected unit"); |
|
597 } |
|
598 |
|
599 // set up font style |
|
600 aRule->GetDesc(eCSSFontDesc_Style, val); |
|
601 unit = val.GetUnit(); |
|
602 if (unit == eCSSUnit_Enumerated) { |
|
603 italicStyle = val.GetIntValue(); |
|
604 } else if (unit == eCSSUnit_Normal) { |
|
605 italicStyle = NS_STYLE_FONT_STYLE_NORMAL; |
|
606 } else { |
|
607 NS_ASSERTION(unit == eCSSUnit_Null, |
|
608 "@font-face style has unexpected unit"); |
|
609 } |
|
610 |
|
611 // set up font features |
|
612 nsTArray<gfxFontFeature> featureSettings; |
|
613 aRule->GetDesc(eCSSFontDesc_FontFeatureSettings, val); |
|
614 unit = val.GetUnit(); |
|
615 if (unit == eCSSUnit_Normal) { |
|
616 // empty list of features |
|
617 } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) { |
|
618 nsRuleNode::ComputeFontFeatures(val.GetPairListValue(), featureSettings); |
|
619 } else { |
|
620 NS_ASSERTION(unit == eCSSUnit_Null, |
|
621 "@font-face font-feature-settings has unexpected unit"); |
|
622 } |
|
623 |
|
624 // set up font language override |
|
625 aRule->GetDesc(eCSSFontDesc_FontLanguageOverride, val); |
|
626 unit = val.GetUnit(); |
|
627 if (unit == eCSSUnit_Normal) { |
|
628 // empty feature string |
|
629 } else if (unit == eCSSUnit_String) { |
|
630 val.GetStringValue(languageOverride); |
|
631 } else { |
|
632 NS_ASSERTION(unit == eCSSUnit_Null, |
|
633 "@font-face font-language-override has unexpected unit"); |
|
634 } |
|
635 |
|
636 // set up src array |
|
637 nsTArray<gfxFontFaceSrc> srcArray; |
|
638 |
|
639 aRule->GetDesc(eCSSFontDesc_Src, val); |
|
640 unit = val.GetUnit(); |
|
641 if (unit == eCSSUnit_Array) { |
|
642 nsCSSValue::Array* srcArr = val.GetArrayValue(); |
|
643 size_t numSrc = srcArr->Count(); |
|
644 |
|
645 for (size_t i = 0; i < numSrc; i++) { |
|
646 val = srcArr->Item(i); |
|
647 unit = val.GetUnit(); |
|
648 gfxFontFaceSrc* face = srcArray.AppendElements(1); |
|
649 if (!face) |
|
650 return; |
|
651 |
|
652 switch (unit) { |
|
653 |
|
654 case eCSSUnit_Local_Font: |
|
655 val.GetStringValue(face->mLocalName); |
|
656 face->mIsLocal = true; |
|
657 face->mURI = nullptr; |
|
658 face->mFormatFlags = 0; |
|
659 break; |
|
660 case eCSSUnit_URL: |
|
661 face->mIsLocal = false; |
|
662 face->mURI = val.GetURLValue(); |
|
663 face->mReferrer = val.GetURLStructValue()->mReferrer; |
|
664 face->mOriginPrincipal = val.GetURLStructValue()->mOriginPrincipal; |
|
665 NS_ASSERTION(face->mOriginPrincipal, "null origin principal in @font-face rule"); |
|
666 |
|
667 // agent and user stylesheets are treated slightly differently, |
|
668 // the same-site origin check and access control headers are |
|
669 // enforced against the sheet principal rather than the document |
|
670 // principal to allow user stylesheets to include @font-face rules |
|
671 face->mUseOriginPrincipal = (aSheetType == nsStyleSet::eUserSheet || |
|
672 aSheetType == nsStyleSet::eAgentSheet); |
|
673 |
|
674 face->mLocalName.Truncate(); |
|
675 face->mFormatFlags = 0; |
|
676 while (i + 1 < numSrc && (val = srcArr->Item(i+1), |
|
677 val.GetUnit() == eCSSUnit_Font_Format)) { |
|
678 nsDependentString valueString(val.GetStringBufferValue()); |
|
679 if (valueString.LowerCaseEqualsASCII("woff")) { |
|
680 face->mFormatFlags |= FLAG_FORMAT_WOFF; |
|
681 } else if (valueString.LowerCaseEqualsASCII("opentype")) { |
|
682 face->mFormatFlags |= FLAG_FORMAT_OPENTYPE; |
|
683 } else if (valueString.LowerCaseEqualsASCII("truetype")) { |
|
684 face->mFormatFlags |= FLAG_FORMAT_TRUETYPE; |
|
685 } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) { |
|
686 face->mFormatFlags |= FLAG_FORMAT_TRUETYPE_AAT; |
|
687 } else if (valueString.LowerCaseEqualsASCII("embedded-opentype")) { |
|
688 face->mFormatFlags |= FLAG_FORMAT_EOT; |
|
689 } else if (valueString.LowerCaseEqualsASCII("svg")) { |
|
690 face->mFormatFlags |= FLAG_FORMAT_SVG; |
|
691 } else { |
|
692 // unknown format specified, mark to distinguish from the |
|
693 // case where no format hints are specified |
|
694 face->mFormatFlags |= FLAG_FORMAT_UNKNOWN; |
|
695 } |
|
696 i++; |
|
697 } |
|
698 if (!face->mURI) { |
|
699 // if URI not valid, omit from src array |
|
700 srcArray.RemoveElementAt(srcArray.Length() - 1); |
|
701 NS_WARNING("null url in @font-face rule"); |
|
702 continue; |
|
703 } |
|
704 break; |
|
705 default: |
|
706 NS_ASSERTION(unit == eCSSUnit_Local_Font || unit == eCSSUnit_URL, |
|
707 "strange unit type in font-face src array"); |
|
708 break; |
|
709 } |
|
710 } |
|
711 } else { |
|
712 NS_ASSERTION(unit == eCSSUnit_Null, "@font-face src has unexpected unit"); |
|
713 } |
|
714 |
|
715 if (srcArray.Length() > 0) { |
|
716 FontFaceRuleRecord ruleRec; |
|
717 ruleRec.mContainer.mRule = aRule; |
|
718 ruleRec.mContainer.mSheetType = aSheetType; |
|
719 ruleRec.mFontEntry = AddFontFace(fontfamily, srcArray, |
|
720 weight, stretch, italicStyle, |
|
721 featureSettings, languageOverride); |
|
722 if (ruleRec.mFontEntry) { |
|
723 mRules.AppendElement(ruleRec); |
|
724 } |
|
725 // this was a new rule and fontEntry, so note that the set was modified |
|
726 aFontSetModified = true; |
|
727 } |
|
728 } |
|
729 |
|
730 void |
|
731 nsUserFontSet::ReplaceFontEntry(gfxMixedFontFamily* aFamily, |
|
732 gfxProxyFontEntry* aProxy, |
|
733 gfxFontEntry* aFontEntry) |
|
734 { |
|
735 // aProxy is being supplanted by the "real" font aFontEntry, so we need to |
|
736 // update any rules that refer to it. Note that there may be multiple rules |
|
737 // that refer to the same proxy - e.g. if a stylesheet was loaded multiple |
|
738 // times, so that several identical @font-face rules are present. |
|
739 for (uint32_t i = 0; i < mRules.Length(); ++i) { |
|
740 if (mRules[i].mFontEntry == aProxy) { |
|
741 mRules[i].mFontEntry = aFontEntry; |
|
742 } |
|
743 } |
|
744 aFamily->ReplaceFontEntry(aProxy, aFontEntry); |
|
745 } |
|
746 |
|
747 nsCSSFontFaceRule* |
|
748 nsUserFontSet::FindRuleForEntry(gfxFontEntry* aFontEntry) |
|
749 { |
|
750 for (uint32_t i = 0; i < mRules.Length(); ++i) { |
|
751 if (mRules[i].mFontEntry == aFontEntry) { |
|
752 return mRules[i].mContainer.mRule; |
|
753 } |
|
754 } |
|
755 return nullptr; |
|
756 } |
|
757 |
|
758 nsresult |
|
759 nsUserFontSet::LogMessage(gfxMixedFontFamily* aFamily, |
|
760 gfxProxyFontEntry* aProxy, |
|
761 const char* aMessage, |
|
762 uint32_t aFlags, |
|
763 nsresult aStatus) |
|
764 { |
|
765 nsCOMPtr<nsIConsoleService> |
|
766 console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); |
|
767 if (!console) { |
|
768 return NS_ERROR_NOT_AVAILABLE; |
|
769 } |
|
770 |
|
771 NS_ConvertUTF16toUTF8 familyName(aFamily->Name()); |
|
772 nsAutoCString fontURI; |
|
773 if (aProxy->mSrcIndex == aProxy->mSrcList.Length()) { |
|
774 fontURI.AppendLiteral("(end of source list)"); |
|
775 } else { |
|
776 if (aProxy->mSrcList[aProxy->mSrcIndex].mURI) { |
|
777 aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); |
|
778 } else { |
|
779 fontURI.AppendLiteral("(invalid URI)"); |
|
780 } |
|
781 } |
|
782 |
|
783 char weightKeywordBuf[8]; // plenty to sprintf() a uint16_t |
|
784 const char* weightKeyword; |
|
785 const nsAFlatCString& weightKeywordString = |
|
786 nsCSSProps::ValueToKeyword(aProxy->Weight(), |
|
787 nsCSSProps::kFontWeightKTable); |
|
788 if (weightKeywordString.Length() > 0) { |
|
789 weightKeyword = weightKeywordString.get(); |
|
790 } else { |
|
791 sprintf(weightKeywordBuf, "%u", aProxy->Weight()); |
|
792 weightKeyword = weightKeywordBuf; |
|
793 } |
|
794 |
|
795 nsPrintfCString message |
|
796 ("downloadable font: %s " |
|
797 "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)", |
|
798 aMessage, |
|
799 familyName.get(), |
|
800 aProxy->IsItalic() ? "italic" : "normal", |
|
801 weightKeyword, |
|
802 nsCSSProps::ValueToKeyword(aProxy->Stretch(), |
|
803 nsCSSProps::kFontStretchKTable).get(), |
|
804 aProxy->mSrcIndex); |
|
805 |
|
806 if (NS_FAILED(aStatus)) { |
|
807 message.Append(": "); |
|
808 switch (aStatus) { |
|
809 case NS_ERROR_DOM_BAD_URI: |
|
810 message.Append("bad URI or cross-site access not allowed"); |
|
811 break; |
|
812 case NS_ERROR_CONTENT_BLOCKED: |
|
813 message.Append("content blocked"); |
|
814 break; |
|
815 default: |
|
816 message.Append("status="); |
|
817 message.AppendInt(static_cast<uint32_t>(aStatus)); |
|
818 break; |
|
819 } |
|
820 } |
|
821 message.Append("\nsource: "); |
|
822 message.Append(fontURI); |
|
823 |
|
824 #ifdef PR_LOGGING |
|
825 if (PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG)) { |
|
826 PR_LOG(GetUserFontsLog(), PR_LOG_DEBUG, |
|
827 ("userfonts (%p) %s", this, message.get())); |
|
828 } |
|
829 #endif |
|
830 |
|
831 // try to give the user an indication of where the rule came from |
|
832 nsCSSFontFaceRule* rule = FindRuleForEntry(aProxy); |
|
833 nsString href; |
|
834 nsString text; |
|
835 nsresult rv; |
|
836 if (rule) { |
|
837 rv = rule->GetCssText(text); |
|
838 NS_ENSURE_SUCCESS(rv, rv); |
|
839 nsCOMPtr<nsIDOMCSSStyleSheet> sheet; |
|
840 rv = rule->GetParentStyleSheet(getter_AddRefs(sheet)); |
|
841 NS_ENSURE_SUCCESS(rv, rv); |
|
842 // if the style sheet is removed while the font is loading can be null |
|
843 if (sheet) { |
|
844 rv = sheet->GetHref(href); |
|
845 NS_ENSURE_SUCCESS(rv, rv); |
|
846 } else { |
|
847 NS_WARNING("null parent stylesheet for @font-face rule"); |
|
848 href.AssignLiteral("unknown"); |
|
849 } |
|
850 } |
|
851 |
|
852 nsCOMPtr<nsIScriptError> scriptError = |
|
853 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); |
|
854 NS_ENSURE_SUCCESS(rv, rv); |
|
855 |
|
856 uint64_t innerWindowID = GetPresContext()->Document()->InnerWindowID(); |
|
857 rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message), |
|
858 href, // file |
|
859 text, // src line |
|
860 0, 0, // line & column number |
|
861 aFlags, // flags |
|
862 "CSS Loader", // category (make separate?) |
|
863 innerWindowID); |
|
864 if (NS_SUCCEEDED(rv)) { |
|
865 console->LogMessage(scriptError); |
|
866 } |
|
867 |
|
868 return NS_OK; |
|
869 } |
|
870 |
|
871 nsresult |
|
872 nsUserFontSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc, |
|
873 nsIPrincipal** aPrincipal, |
|
874 bool* aBypassCache) |
|
875 { |
|
876 // check same-site origin |
|
877 nsIPresShell* ps = mPresContext->PresShell(); |
|
878 if (!ps) |
|
879 return NS_ERROR_FAILURE; |
|
880 |
|
881 NS_ASSERTION(aFontFaceSrc && !aFontFaceSrc->mIsLocal, |
|
882 "bad font face url passed to fontloader"); |
|
883 NS_ASSERTION(aFontFaceSrc->mURI, "null font uri"); |
|
884 if (!aFontFaceSrc->mURI) |
|
885 return NS_ERROR_FAILURE; |
|
886 |
|
887 // use document principal, original principal if flag set |
|
888 // this enables user stylesheets to load font files via |
|
889 // @font-face rules |
|
890 *aPrincipal = ps->GetDocument()->NodePrincipal(); |
|
891 |
|
892 NS_ASSERTION(aFontFaceSrc->mOriginPrincipal, |
|
893 "null origin principal in @font-face rule"); |
|
894 if (aFontFaceSrc->mUseOriginPrincipal) { |
|
895 *aPrincipal = aFontFaceSrc->mOriginPrincipal; |
|
896 } |
|
897 |
|
898 nsresult rv = nsFontFaceLoader::CheckLoadAllowed(*aPrincipal, |
|
899 aFontFaceSrc->mURI, |
|
900 ps->GetDocument()); |
|
901 if (NS_FAILED(rv)) { |
|
902 return rv; |
|
903 } |
|
904 |
|
905 *aBypassCache = false; |
|
906 |
|
907 nsCOMPtr<nsIDocShell> docShell = ps->GetDocument()->GetDocShell(); |
|
908 if (docShell) { |
|
909 uint32_t loadType; |
|
910 if (NS_SUCCEEDED(docShell->GetLoadType(&loadType))) { |
|
911 if ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { |
|
912 *aBypassCache = true; |
|
913 } |
|
914 } |
|
915 } |
|
916 |
|
917 return rv; |
|
918 } |
|
919 |
|
920 nsresult |
|
921 nsUserFontSet::SyncLoadFontData(gfxProxyFontEntry* aFontToLoad, |
|
922 const gfxFontFaceSrc* aFontFaceSrc, |
|
923 uint8_t*& aBuffer, |
|
924 uint32_t& aBufferLength) |
|
925 { |
|
926 nsresult rv; |
|
927 |
|
928 nsCOMPtr<nsIChannel> channel; |
|
929 // get Content Security Policy from principal to pass into channel |
|
930 nsCOMPtr<nsIChannelPolicy> channelPolicy; |
|
931 nsCOMPtr<nsIContentSecurityPolicy> csp; |
|
932 rv = aFontToLoad->mPrincipal->GetCsp(getter_AddRefs(csp)); |
|
933 NS_ENSURE_SUCCESS(rv, rv); |
|
934 if (csp) { |
|
935 channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); |
|
936 channelPolicy->SetContentSecurityPolicy(csp); |
|
937 channelPolicy->SetLoadType(nsIContentPolicy::TYPE_FONT); |
|
938 } |
|
939 rv = NS_NewChannel(getter_AddRefs(channel), |
|
940 aFontFaceSrc->mURI, |
|
941 nullptr, |
|
942 nullptr, |
|
943 nullptr, |
|
944 nsIRequest::LOAD_NORMAL, |
|
945 channelPolicy); |
|
946 |
|
947 NS_ENSURE_SUCCESS(rv, rv); |
|
948 |
|
949 // blocking stream is OK for data URIs |
|
950 nsCOMPtr<nsIInputStream> stream; |
|
951 rv = channel->Open(getter_AddRefs(stream)); |
|
952 NS_ENSURE_SUCCESS(rv, rv); |
|
953 |
|
954 uint64_t bufferLength64; |
|
955 rv = stream->Available(&bufferLength64); |
|
956 NS_ENSURE_SUCCESS(rv, rv); |
|
957 if (bufferLength64 == 0) { |
|
958 return NS_ERROR_FAILURE; |
|
959 } |
|
960 if (bufferLength64 > UINT32_MAX) { |
|
961 return NS_ERROR_FILE_TOO_BIG; |
|
962 } |
|
963 aBufferLength = static_cast<uint32_t>(bufferLength64); |
|
964 |
|
965 // read all the decoded data |
|
966 aBuffer = static_cast<uint8_t*> (NS_Alloc(sizeof(uint8_t) * aBufferLength)); |
|
967 if (!aBuffer) { |
|
968 aBufferLength = 0; |
|
969 return NS_ERROR_OUT_OF_MEMORY; |
|
970 } |
|
971 |
|
972 uint32_t numRead, totalRead = 0; |
|
973 while (NS_SUCCEEDED(rv = |
|
974 stream->Read(reinterpret_cast<char*>(aBuffer + totalRead), |
|
975 aBufferLength - totalRead, &numRead)) && |
|
976 numRead != 0) |
|
977 { |
|
978 totalRead += numRead; |
|
979 if (totalRead > aBufferLength) { |
|
980 rv = NS_ERROR_FAILURE; |
|
981 break; |
|
982 } |
|
983 } |
|
984 |
|
985 // make sure there's a mime type |
|
986 if (NS_SUCCEEDED(rv)) { |
|
987 nsAutoCString mimeType; |
|
988 rv = channel->GetContentType(mimeType); |
|
989 aBufferLength = totalRead; |
|
990 } |
|
991 |
|
992 if (NS_FAILED(rv)) { |
|
993 NS_Free(aBuffer); |
|
994 aBuffer = nullptr; |
|
995 aBufferLength = 0; |
|
996 return rv; |
|
997 } |
|
998 |
|
999 return NS_OK; |
|
1000 } |
|
1001 |
|
1002 bool |
|
1003 nsUserFontSet::GetPrivateBrowsing() |
|
1004 { |
|
1005 nsIPresShell* ps = mPresContext->PresShell(); |
|
1006 if (!ps) { |
|
1007 return false; |
|
1008 } |
|
1009 |
|
1010 nsCOMPtr<nsILoadContext> loadContext = ps->GetDocument()->GetLoadContext(); |
|
1011 return loadContext && loadContext->UsePrivateBrowsing(); |
|
1012 } |
|
1013 |
|
1014 void |
|
1015 nsUserFontSet::DoRebuildUserFontSet() |
|
1016 { |
|
1017 if (!mPresContext) { |
|
1018 // AFAICS, this can only happen if someone has already called Destroy() on |
|
1019 // this font-set, which means it is in the process of being torn down -- |
|
1020 // so there's no point trying to update its rules. |
|
1021 return; |
|
1022 } |
|
1023 |
|
1024 mPresContext->RebuildUserFontSet(); |
|
1025 } |