CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / layout / style / nsFontFaceLoader.cpp
blobc2c8305aeafd66be5f8d327c3af259183f1b55fb
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla Foundation code.
18 * The Initial Developer of the Original Code is
19 * Mozilla Foundation.
20 * Portions created by the Initial Developer are Copyright (C) 2008
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * John Daggett <jdaggett@mozilla.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 /* code for loading in @font-face defined font data */
42 #ifdef MOZ_LOGGING
43 #define FORCE_PR_LOG /* Allow logging in the release build */
44 #endif /* MOZ_LOGGING */
45 #include "prlog.h"
47 #include "nsFontFaceLoader.h"
49 #include "nsError.h"
50 #include "nsIFile.h"
51 #include "nsILocalFile.h"
52 #include "nsIStreamListener.h"
53 #include "nsNetUtil.h"
54 #include "nsIChannelEventSink.h"
55 #include "nsIInterfaceRequestor.h"
56 #include "nsContentUtils.h"
57 #include "nsIPrefService.h"
59 #include "nsPresContext.h"
60 #include "nsIPresShell.h"
61 #include "nsIDocument.h"
62 #include "nsIFrame.h"
63 #include "nsIPrincipal.h"
64 #include "nsIScriptSecurityManager.h"
66 #include "nsDirectoryServiceUtils.h"
67 #include "nsDirectoryServiceDefs.h"
68 #include "nsIContentPolicy.h"
69 #include "nsContentPolicyUtils.h"
70 #include "nsContentErrors.h"
71 #include "nsCrossSiteListenerProxy.h"
72 #include "nsIContentSecurityPolicy.h"
73 #include "nsIChannelPolicy.h"
74 #include "nsChannelPolicy.h"
76 #ifdef PR_LOGGING
77 static PRLogModuleInfo *gFontDownloaderLog = PR_NewLogModule("fontdownloader");
78 #endif /* PR_LOGGING */
80 #define LOG(args) PR_LOG(gFontDownloaderLog, PR_LOG_DEBUG, args)
81 #define LOG_ENABLED() PR_LOG_TEST(gFontDownloaderLog, PR_LOG_DEBUG)
84 nsFontFaceLoader::nsFontFaceLoader(gfxFontEntry *aFontToLoad, nsIURI *aFontURI,
85 nsUserFontSet *aFontSet, nsIChannel *aChannel)
86 : mFontEntry(aFontToLoad), mFontURI(aFontURI), mFontSet(aFontSet),
87 mChannel(aChannel)
91 nsFontFaceLoader::~nsFontFaceLoader()
93 if (mLoadTimer) {
94 mLoadTimer->Cancel();
95 mLoadTimer = nsnull;
97 if (mFontSet) {
98 mFontSet->RemoveLoader(this);
102 void
103 nsFontFaceLoader::StartedLoading(nsIStreamLoader *aStreamLoader)
105 PRInt32 loadTimeout = 3000;
106 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
107 if (prefs) {
108 prefs->GetIntPref("gfx.downloadable_fonts.fallback_delay", &loadTimeout);
110 if (loadTimeout > 0) {
111 mLoadTimer = do_CreateInstance("@mozilla.org/timer;1");
112 if (mLoadTimer) {
113 mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
114 static_cast<void*>(this),
115 loadTimeout,
116 nsITimer::TYPE_ONE_SHOT);
118 } else {
119 gfxProxyFontEntry *pe =
120 static_cast<gfxProxyFontEntry*>(mFontEntry.get());
121 pe->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY;
123 mStreamLoader = aStreamLoader;
126 void
127 nsFontFaceLoader::LoadTimerCallback(nsITimer *aTimer, void *aClosure)
129 nsFontFaceLoader *loader = static_cast<nsFontFaceLoader*>(aClosure);
131 if (!loader->mFontEntry->mIsProxy) {
132 return;
135 gfxProxyFontEntry *pe =
136 static_cast<gfxProxyFontEntry*>(loader->mFontEntry.get());
137 bool updateUserFontSet = true;
139 // If the entry is loading, check whether it's >75% done; if so,
140 // we allow another timeout period before showing a fallback font.
141 if (pe->mLoadingState == gfxProxyFontEntry::LOADING_STARTED) {
142 PRInt32 contentLength;
143 loader->mChannel->GetContentLength(&contentLength);
144 PRUint32 numBytesRead;
145 loader->mStreamLoader->GetNumBytesRead(&numBytesRead);
147 if (contentLength > 0 &&
148 numBytesRead > 3 * (PRUint32(contentLength) >> 2))
150 // More than 3/4 the data has been downloaded, so allow 50% extra
151 // time and hope the remainder will arrive before the additional
152 // time expires.
153 pe->mLoadingState = gfxProxyFontEntry::LOADING_ALMOST_DONE;
154 PRUint32 delay;
155 loader->mLoadTimer->GetDelay(&delay);
156 loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
157 static_cast<void*>(loader),
158 delay >> 1,
159 nsITimer::TYPE_ONE_SHOT);
160 updateUserFontSet = false;
161 LOG(("fontdownloader (%p) 75%% done, resetting timer\n", loader));
165 // If the font is not 75% loaded, or if we've already timed out once
166 // before, we mark this entry as "loading slowly", so the fallback
167 // font will be used in the meantime, and tell the context to refresh.
168 if (updateUserFontSet) {
169 pe->mLoadingState = gfxProxyFontEntry::LOADING_SLOWLY;
170 nsPresContext *ctx = loader->mFontSet->GetPresContext();
171 NS_ASSERTION(ctx, "fontSet doesn't have a presContext?");
172 gfxUserFontSet *fontSet;
173 if (ctx && (fontSet = ctx->GetUserFontSet()) != nsnull) {
174 fontSet->IncrementGeneration();
175 ctx->UserFontSetUpdated();
176 LOG(("fontdownloader (%p) timeout reflow\n", loader));
181 NS_IMPL_ISUPPORTS1(nsFontFaceLoader, nsIStreamLoaderObserver)
183 NS_IMETHODIMP
184 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
185 nsISupports* aContext,
186 nsresult aStatus,
187 PRUint32 aStringLen,
188 const PRUint8* aString)
190 if (!mFontSet) {
191 // We've been canceled
192 return aStatus;
195 mFontSet->RemoveLoader(this);
197 #ifdef PR_LOGGING
198 if (LOG_ENABLED()) {
199 nsCAutoString fontURI;
200 mFontURI->GetSpec(fontURI);
201 if (NS_SUCCEEDED(aStatus)) {
202 LOG(("fontdownloader (%p) download completed - font uri: (%s)\n",
203 this, fontURI.get()));
204 } else {
205 LOG(("fontdownloader (%p) download failed - font uri: (%s) error: %8.8x\n",
206 this, fontURI.get(), aStatus));
209 #endif
211 nsPresContext *ctx = mFontSet->GetPresContext();
212 NS_ASSERTION(ctx && !ctx->PresShell()->IsDestroying(),
213 "We should have been canceled already");
215 // whether an error occurred or not, notify the user font set of the completion
216 gfxUserFontSet *userFontSet = ctx->GetUserFontSet();
217 if (!userFontSet) {
218 return aStatus;
221 // The userFontSet is responsible for freeing the downloaded data
222 // (aString) when finished with it; the pointer is no longer valid
223 // after OnLoadComplete returns.
224 PRBool fontUpdate = userFontSet->OnLoadComplete(mFontEntry,
225 aString, aStringLen,
226 aStatus);
228 // when new font loaded, need to reflow
229 if (fontUpdate) {
230 // Update layout for the presence of the new font. Since this is
231 // asynchronous, reflows will coalesce.
232 ctx->UserFontSetUpdated();
233 LOG(("fontdownloader (%p) reflow\n", this));
236 return NS_SUCCESS_ADOPTED_DATA;
239 void
240 nsFontFaceLoader::Cancel()
242 mFontSet = nsnull;
243 if (mLoadTimer) {
244 mLoadTimer->Cancel();
245 mLoadTimer = nsnull;
247 mChannel->Cancel(NS_BINDING_ABORTED);
250 nsresult
251 nsFontFaceLoader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
252 nsIURI* aTargetURI,
253 nsISupports* aContext)
255 nsresult rv;
257 if (!aSourcePrincipal)
258 return NS_OK;
260 // check with the security manager
261 nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
262 rv = secMan->CheckLoadURIWithPrincipal(aSourcePrincipal, aTargetURI,
263 nsIScriptSecurityManager::STANDARD);
264 if (NS_FAILED(rv)) {
265 return rv;
268 // check content policy
269 PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
270 rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_FONT,
271 aTargetURI,
272 aSourcePrincipal,
273 aContext,
274 EmptyCString(), // mime type
275 nsnull,
276 &shouldLoad,
277 nsContentUtils::GetContentPolicy(),
278 nsContentUtils::GetSecurityManager());
280 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
281 return NS_ERROR_CONTENT_BLOCKED;
284 return NS_OK;
287 nsUserFontSet::nsUserFontSet(nsPresContext *aContext)
288 : mPresContext(aContext)
290 NS_ASSERTION(mPresContext, "null context passed to nsUserFontSet");
291 mLoaders.Init();
294 nsUserFontSet::~nsUserFontSet()
296 NS_ASSERTION(mLoaders.Count() == 0, "mLoaders should have been emptied");
299 static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey,
300 void* aUserArg)
302 aKey->GetKey()->Cancel();
303 return PL_DHASH_REMOVE;
306 void
307 nsUserFontSet::Destroy()
309 mPresContext = nsnull;
310 mLoaders.EnumerateEntries(DestroyIterator, nsnull);
313 void
314 nsUserFontSet::RemoveLoader(nsFontFaceLoader *aLoader)
316 mLoaders.RemoveEntry(aLoader);
319 nsresult
320 nsUserFontSet::StartLoad(gfxFontEntry *aFontToLoad,
321 const gfxFontFaceSrc *aFontFaceSrc)
323 nsresult rv;
325 // check same-site origin
326 nsIPresShell *ps = mPresContext->PresShell();
327 if (!ps)
328 return NS_ERROR_FAILURE;
330 NS_ASSERTION(aFontFaceSrc && !aFontFaceSrc->mIsLocal,
331 "bad font face url passed to fontloader");
332 NS_ASSERTION(aFontFaceSrc->mURI, "null font uri");
333 if (!aFontFaceSrc->mURI)
334 return NS_ERROR_FAILURE;
336 // use document principal, original principal if flag set
337 // this enables user stylesheets to load font files via
338 // @font-face rules
339 nsCOMPtr<nsIPrincipal> principal = ps->GetDocument()->NodePrincipal();
341 NS_ASSERTION(aFontFaceSrc->mOriginPrincipal,
342 "null origin principal in @font-face rule");
343 if (aFontFaceSrc->mUseOriginPrincipal) {
344 principal = do_QueryInterface(aFontFaceSrc->mOriginPrincipal);
347 rv = nsFontFaceLoader::CheckLoadAllowed(principal, aFontFaceSrc->mURI,
348 ps->GetDocument());
349 if (NS_FAILED(rv)) {
350 #ifdef PR_LOGGING
351 if (LOG_ENABLED()) {
352 nsCAutoString fontURI, referrerURI;
353 aFontFaceSrc->mURI->GetSpec(fontURI);
354 if (aFontFaceSrc->mReferrer)
355 aFontFaceSrc->mReferrer->GetSpec(referrerURI);
356 LOG(("fontdownloader download blocked - font uri: (%s) "
357 "referrer uri: (%s) err: %8.8x\n",
358 fontURI.get(), referrerURI.get(), rv));
360 #endif
361 return rv;
364 nsCOMPtr<nsIStreamLoader> streamLoader;
365 nsCOMPtr<nsILoadGroup> loadGroup(ps->GetDocument()->GetDocumentLoadGroup());
367 nsCOMPtr<nsIChannel> channel;
368 // get Content Security Policy from principal to pass into channel
369 nsCOMPtr<nsIChannelPolicy> channelPolicy;
370 nsCOMPtr<nsIContentSecurityPolicy> csp;
371 rv = principal->GetCsp(getter_AddRefs(csp));
372 NS_ENSURE_SUCCESS(rv, rv);
373 if (csp) {
374 channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
375 channelPolicy->SetContentSecurityPolicy(csp);
376 channelPolicy->SetLoadType(nsIContentPolicy::TYPE_FONT);
378 rv = NS_NewChannel(getter_AddRefs(channel),
379 aFontFaceSrc->mURI,
380 nsnull,
381 loadGroup,
382 nsnull,
383 nsIRequest::LOAD_NORMAL,
384 channelPolicy);
386 NS_ENSURE_SUCCESS(rv, rv);
388 nsRefPtr<nsFontFaceLoader> fontLoader =
389 new nsFontFaceLoader(aFontToLoad, aFontFaceSrc->mURI, this, channel);
391 if (!fontLoader)
392 return NS_ERROR_OUT_OF_MEMORY;
394 #ifdef PR_LOGGING
395 if (LOG_ENABLED()) {
396 nsCAutoString fontURI, referrerURI;
397 aFontFaceSrc->mURI->GetSpec(fontURI);
398 if (aFontFaceSrc->mReferrer)
399 aFontFaceSrc->mReferrer->GetSpec(referrerURI);
400 LOG(("fontdownloader (%p) download start - font uri: (%s) "
401 "referrer uri: (%s)\n",
402 fontLoader.get(), fontURI.get(), referrerURI.get()));
404 #endif
406 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
407 if (httpChannel)
408 httpChannel->SetReferrer(aFontFaceSrc->mReferrer);
409 rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader);
410 NS_ENSURE_SUCCESS(rv, rv);
412 PRBool inherits = PR_FALSE;
413 rv = NS_URIChainHasFlags(aFontFaceSrc->mURI,
414 nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
415 &inherits);
416 if (NS_SUCCEEDED(rv) && inherits) {
417 // allow data, javascript, etc URI's
418 rv = channel->AsyncOpen(streamLoader, nsnull);
419 } else {
420 nsCOMPtr<nsIStreamListener> listener =
421 new nsCrossSiteListenerProxy(streamLoader, principal, channel,
422 PR_FALSE, &rv);
423 if (NS_FAILED(rv)) {
424 fontLoader->DropChannel(); // explicitly need to break ref cycle
426 NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
427 NS_ENSURE_SUCCESS(rv, rv);
429 rv = channel->AsyncOpen(listener, nsnull);
432 if (NS_SUCCEEDED(rv)) {
433 mLoaders.PutEntry(fontLoader);
434 fontLoader->StartedLoading(streamLoader);
437 return rv;