1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/style/nsFontFaceLoader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1025 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +// vim:cindent:ts=2:et:sw=2: 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* code for loading in @font-face defined font data */ 1.11 + 1.12 +#ifdef MOZ_LOGGING 1.13 +#define FORCE_PR_LOG /* Allow logging in the release build */ 1.14 +#endif /* MOZ_LOGGING */ 1.15 +#include "prlog.h" 1.16 + 1.17 +#include "nsFontFaceLoader.h" 1.18 + 1.19 +#include "nsError.h" 1.20 +#include "nsNetUtil.h" 1.21 +#include "nsContentUtils.h" 1.22 +#include "mozilla/Preferences.h" 1.23 + 1.24 +#include "nsPresContext.h" 1.25 +#include "nsIPresShell.h" 1.26 +#include "nsIPrincipal.h" 1.27 +#include "nsIScriptSecurityManager.h" 1.28 + 1.29 +#include "nsIContentPolicy.h" 1.30 +#include "nsContentPolicyUtils.h" 1.31 +#include "nsCrossSiteListenerProxy.h" 1.32 +#include "nsIContentSecurityPolicy.h" 1.33 +#include "nsIDocShell.h" 1.34 +#include "nsIWebNavigation.h" 1.35 +#include "nsISupportsPriority.h" 1.36 +#include "nsINetworkSeer.h" 1.37 + 1.38 +#include "nsIConsoleService.h" 1.39 + 1.40 +#include "nsStyleSet.h" 1.41 +#include "nsPrintfCString.h" 1.42 +#include "mozilla/gfx/2D.h" 1.43 + 1.44 +using namespace mozilla; 1.45 + 1.46 +#ifdef PR_LOGGING 1.47 +static PRLogModuleInfo* 1.48 +GetFontDownloaderLog() 1.49 +{ 1.50 + static PRLogModuleInfo* sLog; 1.51 + if (!sLog) 1.52 + sLog = PR_NewLogModule("fontdownloader"); 1.53 + return sLog; 1.54 +} 1.55 +#endif /* PR_LOGGING */ 1.56 + 1.57 +#define LOG(args) PR_LOG(GetFontDownloaderLog(), PR_LOG_DEBUG, args) 1.58 +#define LOG_ENABLED() PR_LOG_TEST(GetFontDownloaderLog(), PR_LOG_DEBUG) 1.59 + 1.60 + 1.61 +nsFontFaceLoader::nsFontFaceLoader(gfxMixedFontFamily* aFontFamily, 1.62 + gfxProxyFontEntry* aProxy, 1.63 + nsIURI* aFontURI, 1.64 + nsUserFontSet* aFontSet, 1.65 + nsIChannel* aChannel) 1.66 + : mFontFamily(aFontFamily), 1.67 + mFontEntry(aProxy), 1.68 + mFontURI(aFontURI), 1.69 + mFontSet(aFontSet), 1.70 + mChannel(aChannel) 1.71 +{ 1.72 +} 1.73 + 1.74 +nsFontFaceLoader::~nsFontFaceLoader() 1.75 +{ 1.76 + if (mFontEntry) { 1.77 + mFontEntry->mLoader = nullptr; 1.78 + } 1.79 + if (mLoadTimer) { 1.80 + mLoadTimer->Cancel(); 1.81 + mLoadTimer = nullptr; 1.82 + } 1.83 + if (mFontSet) { 1.84 + mFontSet->RemoveLoader(this); 1.85 + } 1.86 +} 1.87 + 1.88 +void 1.89 +nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) 1.90 +{ 1.91 + int32_t loadTimeout = 1.92 + Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000); 1.93 + if (loadTimeout > 0) { 1.94 + mLoadTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.95 + if (mLoadTimer) { 1.96 + mLoadTimer->InitWithFuncCallback(LoadTimerCallback, 1.97 + static_cast<void*>(this), 1.98 + loadTimeout, 1.99 + nsITimer::TYPE_ONE_SHOT); 1.100 + } 1.101 + } else if (loadTimeout == 0) { 1.102 + mFontEntry->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY; 1.103 + } // -1 disables fallback 1.104 + mStreamLoader = aStreamLoader; 1.105 +} 1.106 + 1.107 +void 1.108 +nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) 1.109 +{ 1.110 + nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure); 1.111 + 1.112 + if (!loader->mFontSet) { 1.113 + // We've been canceled 1.114 + return; 1.115 + } 1.116 + 1.117 + gfxProxyFontEntry* pe = loader->mFontEntry.get(); 1.118 + bool updateUserFontSet = true; 1.119 + 1.120 + // If the entry is loading, check whether it's >75% done; if so, 1.121 + // we allow another timeout period before showing a fallback font. 1.122 + if (pe->mLoadingState == gfxProxyFontEntry::LOADING_STARTED) { 1.123 + int64_t contentLength; 1.124 + uint32_t numBytesRead; 1.125 + if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) && 1.126 + contentLength > 0 && 1.127 + contentLength < UINT32_MAX && 1.128 + NS_SUCCEEDED(loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) && 1.129 + numBytesRead > 3 * (uint32_t(contentLength) >> 2)) 1.130 + { 1.131 + // More than 3/4 the data has been downloaded, so allow 50% extra 1.132 + // time and hope the remainder will arrive before the additional 1.133 + // time expires. 1.134 + pe->mLoadingState = gfxProxyFontEntry::LOADING_ALMOST_DONE; 1.135 + uint32_t delay; 1.136 + loader->mLoadTimer->GetDelay(&delay); 1.137 + loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback, 1.138 + static_cast<void*>(loader), 1.139 + delay >> 1, 1.140 + nsITimer::TYPE_ONE_SHOT); 1.141 + updateUserFontSet = false; 1.142 + LOG(("fontdownloader (%p) 75%% done, resetting timer\n", loader)); 1.143 + } 1.144 + } 1.145 + 1.146 + // If the font is not 75% loaded, or if we've already timed out once 1.147 + // before, we mark this entry as "loading slowly", so the fallback 1.148 + // font will be used in the meantime, and tell the context to refresh. 1.149 + if (updateUserFontSet) { 1.150 + pe->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY; 1.151 + gfxUserFontSet* fontSet = loader->mFontSet; 1.152 + nsPresContext* ctx = loader->mFontSet->GetPresContext(); 1.153 + NS_ASSERTION(ctx, "userfontset doesn't have a presContext?"); 1.154 + if (ctx) { 1.155 + fontSet->IncrementGeneration(); 1.156 + ctx->UserFontSetUpdated(); 1.157 + LOG(("fontdownloader (%p) timeout reflow\n", loader)); 1.158 + } 1.159 + } 1.160 +} 1.161 + 1.162 +NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver) 1.163 + 1.164 +NS_IMETHODIMP 1.165 +nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader, 1.166 + nsISupports* aContext, 1.167 + nsresult aStatus, 1.168 + uint32_t aStringLen, 1.169 + const uint8_t* aString) 1.170 +{ 1.171 + if (!mFontSet) { 1.172 + // We've been canceled 1.173 + return aStatus; 1.174 + } 1.175 + 1.176 + mFontSet->RemoveLoader(this); 1.177 + 1.178 +#ifdef PR_LOGGING 1.179 + if (LOG_ENABLED()) { 1.180 + nsAutoCString fontURI; 1.181 + mFontURI->GetSpec(fontURI); 1.182 + if (NS_SUCCEEDED(aStatus)) { 1.183 + LOG(("fontdownloader (%p) download completed - font uri: (%s)\n", 1.184 + this, fontURI.get())); 1.185 + } else { 1.186 + LOG(("fontdownloader (%p) download failed - font uri: (%s) error: %8.8x\n", 1.187 + this, fontURI.get(), aStatus)); 1.188 + } 1.189 + } 1.190 +#endif 1.191 + 1.192 + nsPresContext* ctx = mFontSet->GetPresContext(); 1.193 + NS_ASSERTION(ctx && !ctx->PresShell()->IsDestroying(), 1.194 + "We should have been canceled already"); 1.195 + 1.196 + if (NS_SUCCEEDED(aStatus)) { 1.197 + // for HTTP requests, check whether the request _actually_ succeeded; 1.198 + // the "request status" in aStatus does not necessarily indicate this, 1.199 + // because HTTP responses such as 404 (Not Found) will still result in 1.200 + // a success code and potentially an HTML error page from the server 1.201 + // as the resulting data. We don't want to use that as a font. 1.202 + nsCOMPtr<nsIRequest> request; 1.203 + nsCOMPtr<nsIHttpChannel> httpChannel; 1.204 + aLoader->GetRequest(getter_AddRefs(request)); 1.205 + httpChannel = do_QueryInterface(request); 1.206 + if (httpChannel) { 1.207 + bool succeeded; 1.208 + nsresult rv = httpChannel->GetRequestSucceeded(&succeeded); 1.209 + if (NS_SUCCEEDED(rv) && !succeeded) { 1.210 + aStatus = NS_ERROR_NOT_AVAILABLE; 1.211 + } 1.212 + } 1.213 + } 1.214 + 1.215 + // The userFontSet is responsible for freeing the downloaded data 1.216 + // (aString) when finished with it; the pointer is no longer valid 1.217 + // after OnLoadComplete returns. 1.218 + // This is called even in the case of a failed download (HTTP 404, etc), 1.219 + // as there may still be data to be freed (e.g. an error page), 1.220 + // and we need the fontSet to initiate loading the next source. 1.221 + bool fontUpdate = mFontSet->OnLoadComplete(mFontFamily, mFontEntry, aString, 1.222 + aStringLen, aStatus); 1.223 + 1.224 + // when new font loaded, need to reflow 1.225 + if (fontUpdate) { 1.226 + // Update layout for the presence of the new font. Since this is 1.227 + // asynchronous, reflows will coalesce. 1.228 + ctx->UserFontSetUpdated(); 1.229 + LOG(("fontdownloader (%p) reflow\n", this)); 1.230 + } 1.231 + 1.232 + // done with font set 1.233 + mFontSet = nullptr; 1.234 + if (mLoadTimer) { 1.235 + mLoadTimer->Cancel(); 1.236 + mLoadTimer = nullptr; 1.237 + } 1.238 + 1.239 + return NS_SUCCESS_ADOPTED_DATA; 1.240 +} 1.241 + 1.242 +void 1.243 +nsFontFaceLoader::Cancel() 1.244 +{ 1.245 + mFontEntry->mLoadingState = gfxProxyFontEntry::NOT_LOADING; 1.246 + mFontEntry->mLoader = nullptr; 1.247 + mFontSet = nullptr; 1.248 + if (mLoadTimer) { 1.249 + mLoadTimer->Cancel(); 1.250 + mLoadTimer = nullptr; 1.251 + } 1.252 + mChannel->Cancel(NS_BINDING_ABORTED); 1.253 +} 1.254 + 1.255 +nsresult 1.256 +nsFontFaceLoader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal, 1.257 + nsIURI* aTargetURI, 1.258 + nsISupports* aContext) 1.259 +{ 1.260 + nsresult rv; 1.261 + 1.262 + if (!aSourcePrincipal) 1.263 + return NS_OK; 1.264 + 1.265 + // check with the security manager 1.266 + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); 1.267 + rv = secMan->CheckLoadURIWithPrincipal(aSourcePrincipal, aTargetURI, 1.268 + nsIScriptSecurityManager::STANDARD); 1.269 + if (NS_FAILED(rv)) { 1.270 + return rv; 1.271 + } 1.272 + 1.273 + // check content policy 1.274 + int16_t shouldLoad = nsIContentPolicy::ACCEPT; 1.275 + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_FONT, 1.276 + aTargetURI, 1.277 + aSourcePrincipal, 1.278 + aContext, 1.279 + EmptyCString(), // mime type 1.280 + nullptr, 1.281 + &shouldLoad, 1.282 + nsContentUtils::GetContentPolicy(), 1.283 + nsContentUtils::GetSecurityManager()); 1.284 + 1.285 + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { 1.286 + return NS_ERROR_CONTENT_BLOCKED; 1.287 + } 1.288 + 1.289 + return NS_OK; 1.290 +} 1.291 + 1.292 +nsUserFontSet::nsUserFontSet(nsPresContext* aContext) 1.293 + : mPresContext(aContext) 1.294 +{ 1.295 + NS_ASSERTION(mPresContext, "null context passed to nsUserFontSet"); 1.296 +} 1.297 + 1.298 +nsUserFontSet::~nsUserFontSet() 1.299 +{ 1.300 + NS_ASSERTION(mLoaders.Count() == 0, "mLoaders should have been emptied"); 1.301 +} 1.302 + 1.303 +static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey, 1.304 + void* aUserArg) 1.305 +{ 1.306 + aKey->GetKey()->Cancel(); 1.307 + return PL_DHASH_REMOVE; 1.308 +} 1.309 + 1.310 +void 1.311 +nsUserFontSet::Destroy() 1.312 +{ 1.313 + mPresContext = nullptr; 1.314 + mLoaders.EnumerateEntries(DestroyIterator, nullptr); 1.315 + mRules.Clear(); 1.316 +} 1.317 + 1.318 +void 1.319 +nsUserFontSet::RemoveLoader(nsFontFaceLoader* aLoader) 1.320 +{ 1.321 + mLoaders.RemoveEntry(aLoader); 1.322 +} 1.323 + 1.324 +nsresult 1.325 +nsUserFontSet::StartLoad(gfxMixedFontFamily* aFamily, 1.326 + gfxProxyFontEntry* aProxy, 1.327 + const gfxFontFaceSrc* aFontFaceSrc) 1.328 +{ 1.329 + nsresult rv; 1.330 + 1.331 + nsIPresShell* ps = mPresContext->PresShell(); 1.332 + if (!ps) 1.333 + return NS_ERROR_FAILURE; 1.334 + 1.335 + nsCOMPtr<nsIStreamLoader> streamLoader; 1.336 + nsCOMPtr<nsILoadGroup> loadGroup(ps->GetDocument()->GetDocumentLoadGroup()); 1.337 + 1.338 + nsCOMPtr<nsIChannel> channel; 1.339 + // get Content Security Policy from principal to pass into channel 1.340 + nsCOMPtr<nsIChannelPolicy> channelPolicy; 1.341 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.342 + rv = aProxy->mPrincipal->GetCsp(getter_AddRefs(csp)); 1.343 + NS_ENSURE_SUCCESS(rv, rv); 1.344 + if (csp) { 1.345 + channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); 1.346 + channelPolicy->SetContentSecurityPolicy(csp); 1.347 + channelPolicy->SetLoadType(nsIContentPolicy::TYPE_FONT); 1.348 + } 1.349 + rv = NS_NewChannel(getter_AddRefs(channel), 1.350 + aFontFaceSrc->mURI, 1.351 + nullptr, 1.352 + loadGroup, 1.353 + nullptr, 1.354 + nsIRequest::LOAD_NORMAL, 1.355 + channelPolicy); 1.356 + 1.357 + NS_ENSURE_SUCCESS(rv, rv); 1.358 + 1.359 + nsRefPtr<nsFontFaceLoader> fontLoader = 1.360 + new nsFontFaceLoader(aFamily, aProxy, aFontFaceSrc->mURI, this, channel); 1.361 + 1.362 + if (!fontLoader) 1.363 + return NS_ERROR_OUT_OF_MEMORY; 1.364 + 1.365 +#ifdef PR_LOGGING 1.366 + if (LOG_ENABLED()) { 1.367 + nsAutoCString fontURI, referrerURI; 1.368 + aFontFaceSrc->mURI->GetSpec(fontURI); 1.369 + if (aFontFaceSrc->mReferrer) 1.370 + aFontFaceSrc->mReferrer->GetSpec(referrerURI); 1.371 + LOG(("fontdownloader (%p) download start - font uri: (%s) " 1.372 + "referrer uri: (%s)\n", 1.373 + fontLoader.get(), fontURI.get(), referrerURI.get())); 1.374 + } 1.375 +#endif 1.376 + 1.377 + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); 1.378 + if (httpChannel) 1.379 + httpChannel->SetReferrer(aFontFaceSrc->mReferrer); 1.380 + nsCOMPtr<nsISupportsPriority> priorityChannel(do_QueryInterface(channel)); 1.381 + if (priorityChannel) { 1.382 + priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGH); 1.383 + } 1.384 + 1.385 + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader); 1.386 + NS_ENSURE_SUCCESS(rv, rv); 1.387 + 1.388 + nsIDocument *document = ps->GetDocument(); 1.389 + mozilla::net::SeerLearn(aFontFaceSrc->mURI, document->GetDocumentURI(), 1.390 + nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadGroup); 1.391 + 1.392 + bool inherits = false; 1.393 + rv = NS_URIChainHasFlags(aFontFaceSrc->mURI, 1.394 + nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, 1.395 + &inherits); 1.396 + if (NS_SUCCEEDED(rv) && inherits) { 1.397 + // allow data, javascript, etc URI's 1.398 + rv = channel->AsyncOpen(streamLoader, nullptr); 1.399 + } else { 1.400 + nsRefPtr<nsCORSListenerProxy> listener = 1.401 + new nsCORSListenerProxy(streamLoader, aProxy->mPrincipal, false); 1.402 + rv = listener->Init(channel); 1.403 + if (NS_SUCCEEDED(rv)) { 1.404 + rv = channel->AsyncOpen(listener, nullptr); 1.405 + } 1.406 + if (NS_FAILED(rv)) { 1.407 + fontLoader->DropChannel(); // explicitly need to break ref cycle 1.408 + } 1.409 + } 1.410 + 1.411 + if (NS_SUCCEEDED(rv)) { 1.412 + mLoaders.PutEntry(fontLoader); 1.413 + fontLoader->StartedLoading(streamLoader); 1.414 + aProxy->mLoader = fontLoader; // let the font entry remember the loader, 1.415 + // in case we need to cancel it 1.416 + } 1.417 + 1.418 + return rv; 1.419 +} 1.420 + 1.421 +static PLDHashOperator DetachFontEntries(const nsAString& aKey, 1.422 + nsRefPtr<gfxMixedFontFamily>& aFamily, 1.423 + void* aUserArg) 1.424 +{ 1.425 + aFamily->DetachFontEntries(); 1.426 + return PL_DHASH_NEXT; 1.427 +} 1.428 + 1.429 +static PLDHashOperator RemoveIfEmpty(const nsAString& aKey, 1.430 + nsRefPtr<gfxMixedFontFamily>& aFamily, 1.431 + void* aUserArg) 1.432 +{ 1.433 + return aFamily->GetFontList().Length() ? PL_DHASH_NEXT : PL_DHASH_REMOVE; 1.434 +} 1.435 + 1.436 +bool 1.437 +nsUserFontSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules) 1.438 +{ 1.439 + bool modified = false; 1.440 + 1.441 + // The @font-face rules that make up the user font set have changed, 1.442 + // so we need to update the set. However, we want to preserve existing 1.443 + // font entries wherever possible, so that we don't discard and then 1.444 + // re-download resources in the (common) case where at least some of the 1.445 + // same rules are still present. 1.446 + 1.447 + nsTArray<FontFaceRuleRecord> oldRules; 1.448 + mRules.SwapElements(oldRules); 1.449 + 1.450 + // Remove faces from the font family records; we need to re-insert them 1.451 + // because we might end up with faces in a different order even if they're 1.452 + // the same font entries as before. (The order can affect font selection 1.453 + // where multiple faces match the requested style, perhaps with overlapping 1.454 + // unicode-range coverage.) 1.455 + mFontFamilies.Enumerate(DetachFontEntries, nullptr); 1.456 + 1.457 + for (uint32_t i = 0, i_end = aRules.Length(); i < i_end; ++i) { 1.458 + // Insert each rule into our list, migrating old font entries if possible 1.459 + // rather than creating new ones; set modified to true if we detect 1.460 + // that rule ordering has changed, or if a new entry is created. 1.461 + InsertRule(aRules[i].mRule, aRules[i].mSheetType, oldRules, modified); 1.462 + } 1.463 + 1.464 + // Remove any residual families that have no font entries (i.e., they were 1.465 + // not defined at all by the updated set of @font-face rules). 1.466 + mFontFamilies.Enumerate(RemoveIfEmpty, nullptr); 1.467 + 1.468 + // If any rules are left in the old list, note that the set has changed 1.469 + // (even if the new set was built entirely by migrating old font entries). 1.470 + if (oldRules.Length() > 0) { 1.471 + modified = true; 1.472 + // Any in-progress loaders for obsolete rules should be cancelled, 1.473 + // as the resource being downloaded will no longer be required. 1.474 + // We need to explicitly remove any loaders here, otherwise the loaders 1.475 + // will keep their "orphaned" font entries alive until they complete, 1.476 + // even after the oldRules array is deleted. 1.477 + size_t count = oldRules.Length(); 1.478 + for (size_t i = 0; i < count; ++i) { 1.479 + gfxFontEntry* fe = oldRules[i].mFontEntry; 1.480 + if (!fe->mIsProxy) { 1.481 + continue; 1.482 + } 1.483 + gfxProxyFontEntry* proxy = static_cast<gfxProxyFontEntry*>(fe); 1.484 + nsFontFaceLoader* loader = proxy->mLoader; 1.485 + if (loader) { 1.486 + loader->Cancel(); 1.487 + RemoveLoader(loader); 1.488 + } 1.489 + } 1.490 + } 1.491 + 1.492 + if (modified) { 1.493 + IncrementGeneration(); 1.494 + } 1.495 + 1.496 + // local rules have been rebuilt, so clear the flag 1.497 + mLocalRulesUsed = false; 1.498 + 1.499 + return modified; 1.500 +} 1.501 + 1.502 +static bool 1.503 +HasLocalSrc(const nsCSSValue::Array *aSrcArr) 1.504 +{ 1.505 + size_t numSrc = aSrcArr->Count(); 1.506 + for (size_t i = 0; i < numSrc; i++) { 1.507 + if (aSrcArr->Item(i).GetUnit() == eCSSUnit_Local_Font) { 1.508 + return true; 1.509 + } 1.510 + } 1.511 + return false; 1.512 +} 1.513 + 1.514 +void 1.515 +nsUserFontSet::InsertRule(nsCSSFontFaceRule* aRule, uint8_t aSheetType, 1.516 + nsTArray<FontFaceRuleRecord>& aOldRules, 1.517 + bool& aFontSetModified) 1.518 +{ 1.519 + NS_ABORT_IF_FALSE(aRule->GetType() == mozilla::css::Rule::FONT_FACE_RULE, 1.520 + "InsertRule passed a non-fontface CSS rule"); 1.521 + 1.522 + // set up family name 1.523 + nsAutoString fontfamily; 1.524 + nsCSSValue val; 1.525 + uint32_t unit; 1.526 + 1.527 + aRule->GetDesc(eCSSFontDesc_Family, val); 1.528 + unit = val.GetUnit(); 1.529 + if (unit == eCSSUnit_String) { 1.530 + val.GetStringValue(fontfamily); 1.531 + } else { 1.532 + NS_ASSERTION(unit == eCSSUnit_Null, 1.533 + "@font-face family name has unexpected unit"); 1.534 + } 1.535 + if (fontfamily.IsEmpty()) { 1.536 + // If there is no family name, this rule cannot contribute a 1.537 + // usable font, so there is no point in processing it further. 1.538 + return; 1.539 + } 1.540 + 1.541 + // first, we check in oldRules; if the rule exists there, just move it 1.542 + // to the new rule list, and put the entry into the appropriate family 1.543 + for (uint32_t i = 0; i < aOldRules.Length(); ++i) { 1.544 + const FontFaceRuleRecord& ruleRec = aOldRules[i]; 1.545 + 1.546 + if (ruleRec.mContainer.mRule == aRule && 1.547 + ruleRec.mContainer.mSheetType == aSheetType) { 1.548 + 1.549 + // if local rules were used, don't use the old font entry 1.550 + // for rules containing src local usage 1.551 + if (mLocalRulesUsed) { 1.552 + aRule->GetDesc(eCSSFontDesc_Src, val); 1.553 + unit = val.GetUnit(); 1.554 + if (unit == eCSSUnit_Array && HasLocalSrc(val.GetArrayValue())) { 1.555 + break; 1.556 + } 1.557 + } 1.558 + 1.559 + AddFontFace(fontfamily, ruleRec.mFontEntry); 1.560 + mRules.AppendElement(ruleRec); 1.561 + aOldRules.RemoveElementAt(i); 1.562 + // note the set has been modified if an old rule was skipped to find 1.563 + // this one - something has been dropped, or ordering changed 1.564 + if (i > 0) { 1.565 + aFontSetModified = true; 1.566 + } 1.567 + return; 1.568 + } 1.569 + } 1.570 + 1.571 + // this is a new rule: 1.572 + 1.573 + uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL; 1.574 + int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL; 1.575 + uint32_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL; 1.576 + nsString languageOverride; 1.577 + 1.578 + // set up weight 1.579 + aRule->GetDesc(eCSSFontDesc_Weight, val); 1.580 + unit = val.GetUnit(); 1.581 + if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) { 1.582 + weight = val.GetIntValue(); 1.583 + } else if (unit == eCSSUnit_Normal) { 1.584 + weight = NS_STYLE_FONT_WEIGHT_NORMAL; 1.585 + } else { 1.586 + NS_ASSERTION(unit == eCSSUnit_Null, 1.587 + "@font-face weight has unexpected unit"); 1.588 + } 1.589 + 1.590 + // set up stretch 1.591 + aRule->GetDesc(eCSSFontDesc_Stretch, val); 1.592 + unit = val.GetUnit(); 1.593 + if (unit == eCSSUnit_Enumerated) { 1.594 + stretch = val.GetIntValue(); 1.595 + } else if (unit == eCSSUnit_Normal) { 1.596 + stretch = NS_STYLE_FONT_STRETCH_NORMAL; 1.597 + } else { 1.598 + NS_ASSERTION(unit == eCSSUnit_Null, 1.599 + "@font-face stretch has unexpected unit"); 1.600 + } 1.601 + 1.602 + // set up font style 1.603 + aRule->GetDesc(eCSSFontDesc_Style, val); 1.604 + unit = val.GetUnit(); 1.605 + if (unit == eCSSUnit_Enumerated) { 1.606 + italicStyle = val.GetIntValue(); 1.607 + } else if (unit == eCSSUnit_Normal) { 1.608 + italicStyle = NS_STYLE_FONT_STYLE_NORMAL; 1.609 + } else { 1.610 + NS_ASSERTION(unit == eCSSUnit_Null, 1.611 + "@font-face style has unexpected unit"); 1.612 + } 1.613 + 1.614 + // set up font features 1.615 + nsTArray<gfxFontFeature> featureSettings; 1.616 + aRule->GetDesc(eCSSFontDesc_FontFeatureSettings, val); 1.617 + unit = val.GetUnit(); 1.618 + if (unit == eCSSUnit_Normal) { 1.619 + // empty list of features 1.620 + } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) { 1.621 + nsRuleNode::ComputeFontFeatures(val.GetPairListValue(), featureSettings); 1.622 + } else { 1.623 + NS_ASSERTION(unit == eCSSUnit_Null, 1.624 + "@font-face font-feature-settings has unexpected unit"); 1.625 + } 1.626 + 1.627 + // set up font language override 1.628 + aRule->GetDesc(eCSSFontDesc_FontLanguageOverride, val); 1.629 + unit = val.GetUnit(); 1.630 + if (unit == eCSSUnit_Normal) { 1.631 + // empty feature string 1.632 + } else if (unit == eCSSUnit_String) { 1.633 + val.GetStringValue(languageOverride); 1.634 + } else { 1.635 + NS_ASSERTION(unit == eCSSUnit_Null, 1.636 + "@font-face font-language-override has unexpected unit"); 1.637 + } 1.638 + 1.639 + // set up src array 1.640 + nsTArray<gfxFontFaceSrc> srcArray; 1.641 + 1.642 + aRule->GetDesc(eCSSFontDesc_Src, val); 1.643 + unit = val.GetUnit(); 1.644 + if (unit == eCSSUnit_Array) { 1.645 + nsCSSValue::Array* srcArr = val.GetArrayValue(); 1.646 + size_t numSrc = srcArr->Count(); 1.647 + 1.648 + for (size_t i = 0; i < numSrc; i++) { 1.649 + val = srcArr->Item(i); 1.650 + unit = val.GetUnit(); 1.651 + gfxFontFaceSrc* face = srcArray.AppendElements(1); 1.652 + if (!face) 1.653 + return; 1.654 + 1.655 + switch (unit) { 1.656 + 1.657 + case eCSSUnit_Local_Font: 1.658 + val.GetStringValue(face->mLocalName); 1.659 + face->mIsLocal = true; 1.660 + face->mURI = nullptr; 1.661 + face->mFormatFlags = 0; 1.662 + break; 1.663 + case eCSSUnit_URL: 1.664 + face->mIsLocal = false; 1.665 + face->mURI = val.GetURLValue(); 1.666 + face->mReferrer = val.GetURLStructValue()->mReferrer; 1.667 + face->mOriginPrincipal = val.GetURLStructValue()->mOriginPrincipal; 1.668 + NS_ASSERTION(face->mOriginPrincipal, "null origin principal in @font-face rule"); 1.669 + 1.670 + // agent and user stylesheets are treated slightly differently, 1.671 + // the same-site origin check and access control headers are 1.672 + // enforced against the sheet principal rather than the document 1.673 + // principal to allow user stylesheets to include @font-face rules 1.674 + face->mUseOriginPrincipal = (aSheetType == nsStyleSet::eUserSheet || 1.675 + aSheetType == nsStyleSet::eAgentSheet); 1.676 + 1.677 + face->mLocalName.Truncate(); 1.678 + face->mFormatFlags = 0; 1.679 + while (i + 1 < numSrc && (val = srcArr->Item(i+1), 1.680 + val.GetUnit() == eCSSUnit_Font_Format)) { 1.681 + nsDependentString valueString(val.GetStringBufferValue()); 1.682 + if (valueString.LowerCaseEqualsASCII("woff")) { 1.683 + face->mFormatFlags |= FLAG_FORMAT_WOFF; 1.684 + } else if (valueString.LowerCaseEqualsASCII("opentype")) { 1.685 + face->mFormatFlags |= FLAG_FORMAT_OPENTYPE; 1.686 + } else if (valueString.LowerCaseEqualsASCII("truetype")) { 1.687 + face->mFormatFlags |= FLAG_FORMAT_TRUETYPE; 1.688 + } else if (valueString.LowerCaseEqualsASCII("truetype-aat")) { 1.689 + face->mFormatFlags |= FLAG_FORMAT_TRUETYPE_AAT; 1.690 + } else if (valueString.LowerCaseEqualsASCII("embedded-opentype")) { 1.691 + face->mFormatFlags |= FLAG_FORMAT_EOT; 1.692 + } else if (valueString.LowerCaseEqualsASCII("svg")) { 1.693 + face->mFormatFlags |= FLAG_FORMAT_SVG; 1.694 + } else { 1.695 + // unknown format specified, mark to distinguish from the 1.696 + // case where no format hints are specified 1.697 + face->mFormatFlags |= FLAG_FORMAT_UNKNOWN; 1.698 + } 1.699 + i++; 1.700 + } 1.701 + if (!face->mURI) { 1.702 + // if URI not valid, omit from src array 1.703 + srcArray.RemoveElementAt(srcArray.Length() - 1); 1.704 + NS_WARNING("null url in @font-face rule"); 1.705 + continue; 1.706 + } 1.707 + break; 1.708 + default: 1.709 + NS_ASSERTION(unit == eCSSUnit_Local_Font || unit == eCSSUnit_URL, 1.710 + "strange unit type in font-face src array"); 1.711 + break; 1.712 + } 1.713 + } 1.714 + } else { 1.715 + NS_ASSERTION(unit == eCSSUnit_Null, "@font-face src has unexpected unit"); 1.716 + } 1.717 + 1.718 + if (srcArray.Length() > 0) { 1.719 + FontFaceRuleRecord ruleRec; 1.720 + ruleRec.mContainer.mRule = aRule; 1.721 + ruleRec.mContainer.mSheetType = aSheetType; 1.722 + ruleRec.mFontEntry = AddFontFace(fontfamily, srcArray, 1.723 + weight, stretch, italicStyle, 1.724 + featureSettings, languageOverride); 1.725 + if (ruleRec.mFontEntry) { 1.726 + mRules.AppendElement(ruleRec); 1.727 + } 1.728 + // this was a new rule and fontEntry, so note that the set was modified 1.729 + aFontSetModified = true; 1.730 + } 1.731 +} 1.732 + 1.733 +void 1.734 +nsUserFontSet::ReplaceFontEntry(gfxMixedFontFamily* aFamily, 1.735 + gfxProxyFontEntry* aProxy, 1.736 + gfxFontEntry* aFontEntry) 1.737 +{ 1.738 + // aProxy is being supplanted by the "real" font aFontEntry, so we need to 1.739 + // update any rules that refer to it. Note that there may be multiple rules 1.740 + // that refer to the same proxy - e.g. if a stylesheet was loaded multiple 1.741 + // times, so that several identical @font-face rules are present. 1.742 + for (uint32_t i = 0; i < mRules.Length(); ++i) { 1.743 + if (mRules[i].mFontEntry == aProxy) { 1.744 + mRules[i].mFontEntry = aFontEntry; 1.745 + } 1.746 + } 1.747 + aFamily->ReplaceFontEntry(aProxy, aFontEntry); 1.748 +} 1.749 + 1.750 +nsCSSFontFaceRule* 1.751 +nsUserFontSet::FindRuleForEntry(gfxFontEntry* aFontEntry) 1.752 +{ 1.753 + for (uint32_t i = 0; i < mRules.Length(); ++i) { 1.754 + if (mRules[i].mFontEntry == aFontEntry) { 1.755 + return mRules[i].mContainer.mRule; 1.756 + } 1.757 + } 1.758 + return nullptr; 1.759 +} 1.760 + 1.761 +nsresult 1.762 +nsUserFontSet::LogMessage(gfxMixedFontFamily* aFamily, 1.763 + gfxProxyFontEntry* aProxy, 1.764 + const char* aMessage, 1.765 + uint32_t aFlags, 1.766 + nsresult aStatus) 1.767 +{ 1.768 + nsCOMPtr<nsIConsoleService> 1.769 + console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); 1.770 + if (!console) { 1.771 + return NS_ERROR_NOT_AVAILABLE; 1.772 + } 1.773 + 1.774 + NS_ConvertUTF16toUTF8 familyName(aFamily->Name()); 1.775 + nsAutoCString fontURI; 1.776 + if (aProxy->mSrcIndex == aProxy->mSrcList.Length()) { 1.777 + fontURI.AppendLiteral("(end of source list)"); 1.778 + } else { 1.779 + if (aProxy->mSrcList[aProxy->mSrcIndex].mURI) { 1.780 + aProxy->mSrcList[aProxy->mSrcIndex].mURI->GetSpec(fontURI); 1.781 + } else { 1.782 + fontURI.AppendLiteral("(invalid URI)"); 1.783 + } 1.784 + } 1.785 + 1.786 + char weightKeywordBuf[8]; // plenty to sprintf() a uint16_t 1.787 + const char* weightKeyword; 1.788 + const nsAFlatCString& weightKeywordString = 1.789 + nsCSSProps::ValueToKeyword(aProxy->Weight(), 1.790 + nsCSSProps::kFontWeightKTable); 1.791 + if (weightKeywordString.Length() > 0) { 1.792 + weightKeyword = weightKeywordString.get(); 1.793 + } else { 1.794 + sprintf(weightKeywordBuf, "%u", aProxy->Weight()); 1.795 + weightKeyword = weightKeywordBuf; 1.796 + } 1.797 + 1.798 + nsPrintfCString message 1.799 + ("downloadable font: %s " 1.800 + "(font-family: \"%s\" style:%s weight:%s stretch:%s src index:%d)", 1.801 + aMessage, 1.802 + familyName.get(), 1.803 + aProxy->IsItalic() ? "italic" : "normal", 1.804 + weightKeyword, 1.805 + nsCSSProps::ValueToKeyword(aProxy->Stretch(), 1.806 + nsCSSProps::kFontStretchKTable).get(), 1.807 + aProxy->mSrcIndex); 1.808 + 1.809 + if (NS_FAILED(aStatus)) { 1.810 + message.Append(": "); 1.811 + switch (aStatus) { 1.812 + case NS_ERROR_DOM_BAD_URI: 1.813 + message.Append("bad URI or cross-site access not allowed"); 1.814 + break; 1.815 + case NS_ERROR_CONTENT_BLOCKED: 1.816 + message.Append("content blocked"); 1.817 + break; 1.818 + default: 1.819 + message.Append("status="); 1.820 + message.AppendInt(static_cast<uint32_t>(aStatus)); 1.821 + break; 1.822 + } 1.823 + } 1.824 + message.Append("\nsource: "); 1.825 + message.Append(fontURI); 1.826 + 1.827 +#ifdef PR_LOGGING 1.828 + if (PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG)) { 1.829 + PR_LOG(GetUserFontsLog(), PR_LOG_DEBUG, 1.830 + ("userfonts (%p) %s", this, message.get())); 1.831 + } 1.832 +#endif 1.833 + 1.834 + // try to give the user an indication of where the rule came from 1.835 + nsCSSFontFaceRule* rule = FindRuleForEntry(aProxy); 1.836 + nsString href; 1.837 + nsString text; 1.838 + nsresult rv; 1.839 + if (rule) { 1.840 + rv = rule->GetCssText(text); 1.841 + NS_ENSURE_SUCCESS(rv, rv); 1.842 + nsCOMPtr<nsIDOMCSSStyleSheet> sheet; 1.843 + rv = rule->GetParentStyleSheet(getter_AddRefs(sheet)); 1.844 + NS_ENSURE_SUCCESS(rv, rv); 1.845 + // if the style sheet is removed while the font is loading can be null 1.846 + if (sheet) { 1.847 + rv = sheet->GetHref(href); 1.848 + NS_ENSURE_SUCCESS(rv, rv); 1.849 + } else { 1.850 + NS_WARNING("null parent stylesheet for @font-face rule"); 1.851 + href.AssignLiteral("unknown"); 1.852 + } 1.853 + } 1.854 + 1.855 + nsCOMPtr<nsIScriptError> scriptError = 1.856 + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); 1.857 + NS_ENSURE_SUCCESS(rv, rv); 1.858 + 1.859 + uint64_t innerWindowID = GetPresContext()->Document()->InnerWindowID(); 1.860 + rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(message), 1.861 + href, // file 1.862 + text, // src line 1.863 + 0, 0, // line & column number 1.864 + aFlags, // flags 1.865 + "CSS Loader", // category (make separate?) 1.866 + innerWindowID); 1.867 + if (NS_SUCCEEDED(rv)) { 1.868 + console->LogMessage(scriptError); 1.869 + } 1.870 + 1.871 + return NS_OK; 1.872 +} 1.873 + 1.874 +nsresult 1.875 +nsUserFontSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc, 1.876 + nsIPrincipal** aPrincipal, 1.877 + bool* aBypassCache) 1.878 +{ 1.879 + // check same-site origin 1.880 + nsIPresShell* ps = mPresContext->PresShell(); 1.881 + if (!ps) 1.882 + return NS_ERROR_FAILURE; 1.883 + 1.884 + NS_ASSERTION(aFontFaceSrc && !aFontFaceSrc->mIsLocal, 1.885 + "bad font face url passed to fontloader"); 1.886 + NS_ASSERTION(aFontFaceSrc->mURI, "null font uri"); 1.887 + if (!aFontFaceSrc->mURI) 1.888 + return NS_ERROR_FAILURE; 1.889 + 1.890 + // use document principal, original principal if flag set 1.891 + // this enables user stylesheets to load font files via 1.892 + // @font-face rules 1.893 + *aPrincipal = ps->GetDocument()->NodePrincipal(); 1.894 + 1.895 + NS_ASSERTION(aFontFaceSrc->mOriginPrincipal, 1.896 + "null origin principal in @font-face rule"); 1.897 + if (aFontFaceSrc->mUseOriginPrincipal) { 1.898 + *aPrincipal = aFontFaceSrc->mOriginPrincipal; 1.899 + } 1.900 + 1.901 + nsresult rv = nsFontFaceLoader::CheckLoadAllowed(*aPrincipal, 1.902 + aFontFaceSrc->mURI, 1.903 + ps->GetDocument()); 1.904 + if (NS_FAILED(rv)) { 1.905 + return rv; 1.906 + } 1.907 + 1.908 + *aBypassCache = false; 1.909 + 1.910 + nsCOMPtr<nsIDocShell> docShell = ps->GetDocument()->GetDocShell(); 1.911 + if (docShell) { 1.912 + uint32_t loadType; 1.913 + if (NS_SUCCEEDED(docShell->GetLoadType(&loadType))) { 1.914 + if ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { 1.915 + *aBypassCache = true; 1.916 + } 1.917 + } 1.918 + } 1.919 + 1.920 + return rv; 1.921 +} 1.922 + 1.923 +nsresult 1.924 +nsUserFontSet::SyncLoadFontData(gfxProxyFontEntry* aFontToLoad, 1.925 + const gfxFontFaceSrc* aFontFaceSrc, 1.926 + uint8_t*& aBuffer, 1.927 + uint32_t& aBufferLength) 1.928 +{ 1.929 + nsresult rv; 1.930 + 1.931 + nsCOMPtr<nsIChannel> channel; 1.932 + // get Content Security Policy from principal to pass into channel 1.933 + nsCOMPtr<nsIChannelPolicy> channelPolicy; 1.934 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.935 + rv = aFontToLoad->mPrincipal->GetCsp(getter_AddRefs(csp)); 1.936 + NS_ENSURE_SUCCESS(rv, rv); 1.937 + if (csp) { 1.938 + channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); 1.939 + channelPolicy->SetContentSecurityPolicy(csp); 1.940 + channelPolicy->SetLoadType(nsIContentPolicy::TYPE_FONT); 1.941 + } 1.942 + rv = NS_NewChannel(getter_AddRefs(channel), 1.943 + aFontFaceSrc->mURI, 1.944 + nullptr, 1.945 + nullptr, 1.946 + nullptr, 1.947 + nsIRequest::LOAD_NORMAL, 1.948 + channelPolicy); 1.949 + 1.950 + NS_ENSURE_SUCCESS(rv, rv); 1.951 + 1.952 + // blocking stream is OK for data URIs 1.953 + nsCOMPtr<nsIInputStream> stream; 1.954 + rv = channel->Open(getter_AddRefs(stream)); 1.955 + NS_ENSURE_SUCCESS(rv, rv); 1.956 + 1.957 + uint64_t bufferLength64; 1.958 + rv = stream->Available(&bufferLength64); 1.959 + NS_ENSURE_SUCCESS(rv, rv); 1.960 + if (bufferLength64 == 0) { 1.961 + return NS_ERROR_FAILURE; 1.962 + } 1.963 + if (bufferLength64 > UINT32_MAX) { 1.964 + return NS_ERROR_FILE_TOO_BIG; 1.965 + } 1.966 + aBufferLength = static_cast<uint32_t>(bufferLength64); 1.967 + 1.968 + // read all the decoded data 1.969 + aBuffer = static_cast<uint8_t*> (NS_Alloc(sizeof(uint8_t) * aBufferLength)); 1.970 + if (!aBuffer) { 1.971 + aBufferLength = 0; 1.972 + return NS_ERROR_OUT_OF_MEMORY; 1.973 + } 1.974 + 1.975 + uint32_t numRead, totalRead = 0; 1.976 + while (NS_SUCCEEDED(rv = 1.977 + stream->Read(reinterpret_cast<char*>(aBuffer + totalRead), 1.978 + aBufferLength - totalRead, &numRead)) && 1.979 + numRead != 0) 1.980 + { 1.981 + totalRead += numRead; 1.982 + if (totalRead > aBufferLength) { 1.983 + rv = NS_ERROR_FAILURE; 1.984 + break; 1.985 + } 1.986 + } 1.987 + 1.988 + // make sure there's a mime type 1.989 + if (NS_SUCCEEDED(rv)) { 1.990 + nsAutoCString mimeType; 1.991 + rv = channel->GetContentType(mimeType); 1.992 + aBufferLength = totalRead; 1.993 + } 1.994 + 1.995 + if (NS_FAILED(rv)) { 1.996 + NS_Free(aBuffer); 1.997 + aBuffer = nullptr; 1.998 + aBufferLength = 0; 1.999 + return rv; 1.1000 + } 1.1001 + 1.1002 + return NS_OK; 1.1003 +} 1.1004 + 1.1005 +bool 1.1006 +nsUserFontSet::GetPrivateBrowsing() 1.1007 +{ 1.1008 + nsIPresShell* ps = mPresContext->PresShell(); 1.1009 + if (!ps) { 1.1010 + return false; 1.1011 + } 1.1012 + 1.1013 + nsCOMPtr<nsILoadContext> loadContext = ps->GetDocument()->GetLoadContext(); 1.1014 + return loadContext && loadContext->UsePrivateBrowsing(); 1.1015 +} 1.1016 + 1.1017 +void 1.1018 +nsUserFontSet::DoRebuildUserFontSet() 1.1019 +{ 1.1020 + if (!mPresContext) { 1.1021 + // AFAICS, this can only happen if someone has already called Destroy() on 1.1022 + // this font-set, which means it is in the process of being torn down -- 1.1023 + // so there's no point trying to update its rules. 1.1024 + return; 1.1025 + } 1.1026 + 1.1027 + mPresContext->RebuildUserFontSet(); 1.1028 +}