Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing 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/. */
7 /* code for loading in @font-face defined font data */
9 #ifdef MOZ_LOGGING
10 #define FORCE_PR_LOG /* Allow logging in the release build */
11 #endif /* MOZ_LOGGING */
12 #include "prlog.h"
14 #include "nsFontFaceLoader.h"
16 #include "nsError.h"
17 #include "nsNetUtil.h"
18 #include "nsContentUtils.h"
19 #include "mozilla/Preferences.h"
21 #include "nsPresContext.h"
22 #include "nsIPresShell.h"
23 #include "nsIPrincipal.h"
24 #include "nsIScriptSecurityManager.h"
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"
35 #include "nsIConsoleService.h"
37 #include "nsStyleSet.h"
38 #include "nsPrintfCString.h"
39 #include "mozilla/gfx/2D.h"
41 using namespace mozilla;
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 */
54 #define LOG(args) PR_LOG(GetFontDownloaderLog(), PR_LOG_DEBUG, args)
55 #define LOG_ENABLED() PR_LOG_TEST(GetFontDownloaderLog(), PR_LOG_DEBUG)
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 }
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 }
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 }
104 void
105 nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure)
106 {
107 nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
109 if (!loader->mFontSet) {
110 // We've been canceled
111 return;
112 }
114 gfxProxyFontEntry* pe = loader->mFontEntry.get();
115 bool updateUserFontSet = true;
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 }
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 }
159 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
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 }
173 mFontSet->RemoveLoader(this);
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
189 nsPresContext* ctx = mFontSet->GetPresContext();
190 NS_ASSERTION(ctx && !ctx->PresShell()->IsDestroying(),
191 "We should have been canceled already");
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 }
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);
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 }
229 // done with font set
230 mFontSet = nullptr;
231 if (mLoadTimer) {
232 mLoadTimer->Cancel();
233 mLoadTimer = nullptr;
234 }
236 return NS_SUCCESS_ADOPTED_DATA;
237 }
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 }
252 nsresult
253 nsFontFaceLoader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
254 nsIURI* aTargetURI,
255 nsISupports* aContext)
256 {
257 nsresult rv;
259 if (!aSourcePrincipal)
260 return NS_OK;
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 }
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());
282 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
283 return NS_ERROR_CONTENT_BLOCKED;
284 }
286 return NS_OK;
287 }
289 nsUserFontSet::nsUserFontSet(nsPresContext* aContext)
290 : mPresContext(aContext)
291 {
292 NS_ASSERTION(mPresContext, "null context passed to nsUserFontSet");
293 }
295 nsUserFontSet::~nsUserFontSet()
296 {
297 NS_ASSERTION(mLoaders.Count() == 0, "mLoaders should have been emptied");
298 }
300 static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey,
301 void* aUserArg)
302 {
303 aKey->GetKey()->Cancel();
304 return PL_DHASH_REMOVE;
305 }
307 void
308 nsUserFontSet::Destroy()
309 {
310 mPresContext = nullptr;
311 mLoaders.EnumerateEntries(DestroyIterator, nullptr);
312 mRules.Clear();
313 }
315 void
316 nsUserFontSet::RemoveLoader(nsFontFaceLoader* aLoader)
317 {
318 mLoaders.RemoveEntry(aLoader);
319 }
321 nsresult
322 nsUserFontSet::StartLoad(gfxMixedFontFamily* aFamily,
323 gfxProxyFontEntry* aProxy,
324 const gfxFontFaceSrc* aFontFaceSrc)
325 {
326 nsresult rv;
328 nsIPresShell* ps = mPresContext->PresShell();
329 if (!ps)
330 return NS_ERROR_FAILURE;
332 nsCOMPtr<nsIStreamLoader> streamLoader;
333 nsCOMPtr<nsILoadGroup> loadGroup(ps->GetDocument()->GetDocumentLoadGroup());
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);
354 NS_ENSURE_SUCCESS(rv, rv);
356 nsRefPtr<nsFontFaceLoader> fontLoader =
357 new nsFontFaceLoader(aFamily, aProxy, aFontFaceSrc->mURI, this, channel);
359 if (!fontLoader)
360 return NS_ERROR_OUT_OF_MEMORY;
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
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 }
382 rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader);
383 NS_ENSURE_SUCCESS(rv, rv);
385 nsIDocument *document = ps->GetDocument();
386 mozilla::net::SeerLearn(aFontFaceSrc->mURI, document->GetDocumentURI(),
387 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadGroup);
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 }
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 }
415 return rv;
416 }
418 static PLDHashOperator DetachFontEntries(const nsAString& aKey,
419 nsRefPtr<gfxMixedFontFamily>& aFamily,
420 void* aUserArg)
421 {
422 aFamily->DetachFontEntries();
423 return PL_DHASH_NEXT;
424 }
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 }
433 bool
434 nsUserFontSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules)
435 {
436 bool modified = false;
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.
444 nsTArray<FontFaceRuleRecord> oldRules;
445 mRules.SwapElements(oldRules);
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);
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 }
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);
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 }
489 if (modified) {
490 IncrementGeneration();
491 }
493 // local rules have been rebuilt, so clear the flag
494 mLocalRulesUsed = false;
496 return modified;
497 }
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 }
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");
519 // set up family name
520 nsAutoString fontfamily;
521 nsCSSValue val;
522 uint32_t unit;
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 }
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];
543 if (ruleRec.mContainer.mRule == aRule &&
544 ruleRec.mContainer.mSheetType == aSheetType) {
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 }
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 }
568 // this is a new rule:
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;
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 }
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 }
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 }
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 }
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 }
636 // set up src array
637 nsTArray<gfxFontFaceSrc> srcArray;
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();
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;
652 switch (unit) {
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");
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);
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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);
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);
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
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 }
852 nsCOMPtr<nsIScriptError> scriptError =
853 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
854 NS_ENSURE_SUCCESS(rv, rv);
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 }
868 return NS_OK;
869 }
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;
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;
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();
892 NS_ASSERTION(aFontFaceSrc->mOriginPrincipal,
893 "null origin principal in @font-face rule");
894 if (aFontFaceSrc->mUseOriginPrincipal) {
895 *aPrincipal = aFontFaceSrc->mOriginPrincipal;
896 }
898 nsresult rv = nsFontFaceLoader::CheckLoadAllowed(*aPrincipal,
899 aFontFaceSrc->mURI,
900 ps->GetDocument());
901 if (NS_FAILED(rv)) {
902 return rv;
903 }
905 *aBypassCache = false;
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 }
917 return rv;
918 }
920 nsresult
921 nsUserFontSet::SyncLoadFontData(gfxProxyFontEntry* aFontToLoad,
922 const gfxFontFaceSrc* aFontFaceSrc,
923 uint8_t*& aBuffer,
924 uint32_t& aBufferLength)
925 {
926 nsresult rv;
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);
947 NS_ENSURE_SUCCESS(rv, rv);
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);
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);
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 }
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 }
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 }
992 if (NS_FAILED(rv)) {
993 NS_Free(aBuffer);
994 aBuffer = nullptr;
995 aBufferLength = 0;
996 return rv;
997 }
999 return NS_OK;
1000 }
1002 bool
1003 nsUserFontSet::GetPrivateBrowsing()
1004 {
1005 nsIPresShell* ps = mPresContext->PresShell();
1006 if (!ps) {
1007 return false;
1008 }
1010 nsCOMPtr<nsILoadContext> loadContext = ps->GetDocument()->GetLoadContext();
1011 return loadContext && loadContext->UsePrivateBrowsing();
1012 }
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 }
1024 mPresContext->RebuildUserFontSet();
1025 }