Coverage Report

Created: 2018-09-25 14:53

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