tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFontFaceLoader.cpp (12744B)


      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 "nsFontFaceLoader.h"
     10 
     11 #include "FontFaceSet.h"
     12 #include "mozilla/AutoRestore.h"
     13 #include "mozilla/IntegerPrintfMacros.h"
     14 #include "mozilla/Logging.h"
     15 #include "mozilla/Preferences.h"
     16 #include "mozilla/StaticPrefs_layout.h"
     17 #include "mozilla/TaskQueue.h"
     18 #include "mozilla/gfx/2D.h"
     19 #include "mozilla/glean/GfxMetrics.h"
     20 #include "nsContentPolicyUtils.h"
     21 #include "nsError.h"
     22 #include "nsIHttpChannel.h"
     23 #include "nsIThreadRetargetableRequest.h"
     24 #include "nsNetCID.h"
     25 #include "nsPresContext.h"
     26 
     27 using namespace mozilla;
     28 using namespace mozilla::dom;
     29 
     30 #define LOG(args) \
     31  MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
     32 #define LOG_ENABLED() \
     33  MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), LogLevel::Debug)
     34 
     35 static uint32_t GetFallbackDelay() {
     36  return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
     37 }
     38 
     39 static uint32_t GetShortFallbackDelay() {
     40  return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short",
     41                             100);
     42 }
     43 
     44 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
     45                                   uint32_t aSrcIndex,
     46                                   FontFaceSetImpl* aFontFaceSet,
     47                                   nsIChannel* aChannel)
     48    : mUserFontEntry(aUserFontEntry),
     49      mFontFaceSet(aFontFaceSet),
     50      mChannel(aChannel),
     51      mStreamLoader(nullptr),
     52      mSrcIndex(aSrcIndex) {
     53  MOZ_ASSERT(mFontFaceSet,
     54             "We should get a valid FontFaceSet from the caller!");
     55 
     56  const gfxFontFaceSrc& src = aUserFontEntry->SourceAt(mSrcIndex);
     57  MOZ_ASSERT(src.mSourceType == gfxFontFaceSrc::eSourceType_URL);
     58 
     59  mFontURI = src.mURI->get();
     60  mStartTime = TimeStamp::Now();
     61 
     62  // We add an explicit load block rather than just rely on the network
     63  // request's block, since we need to do some OMT work after the load
     64  // is finished before we unblock load.
     65  auto* doc = mFontFaceSet->GetDocument();
     66  if (doc) {
     67    doc->BlockOnload();
     68  }
     69 }
     70 
     71 nsFontFaceLoader::~nsFontFaceLoader() {
     72  MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
     73  MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
     74  if (mUserFontEntry) {
     75    mUserFontEntry->mLoader = nullptr;
     76  }
     77  if (mLoadTimer) {
     78    mLoadTimer->Cancel();
     79    mLoadTimer = nullptr;
     80  }
     81  if (mFontFaceSet) {
     82    mFontFaceSet->RemoveLoader(this);
     83    auto* doc = mFontFaceSet->GetDocument();
     84    if (doc) {
     85      doc->UnblockOnload(false);
     86    }
     87  }
     88 }
     89 
     90 void nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader) {
     91  int32_t loadTimeout;
     92  StyleFontDisplay fontDisplay = GetFontDisplay();
     93  if (fontDisplay == StyleFontDisplay::Auto ||
     94      fontDisplay == StyleFontDisplay::Block) {
     95    loadTimeout = GetFallbackDelay();
     96  } else {
     97    loadTimeout = GetShortFallbackDelay();
     98  }
     99 
    100  if (loadTimeout > 0) {
    101    NS_NewTimerWithFuncCallback(getter_AddRefs(mLoadTimer), LoadTimerCallback,
    102                                static_cast<void*>(this), loadTimeout,
    103                                nsITimer::TYPE_ONE_SHOT, "LoadTimerCallback"_ns,
    104                                GetMainThreadSerialEventTarget());
    105  } else {
    106    mUserFontEntry->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
    107  }
    108  mStreamLoader = aStreamLoader;
    109 }
    110 
    111 /* static */
    112 void nsFontFaceLoader::LoadTimerCallback(nsITimer* aTimer, void* aClosure) {
    113  nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
    114 
    115  MOZ_DIAGNOSTIC_ASSERT(!loader->mInLoadTimerCallback);
    116  MOZ_DIAGNOSTIC_ASSERT(!loader->mInStreamComplete);
    117  AutoRestore<bool> scope{loader->mInLoadTimerCallback};
    118  loader->mInLoadTimerCallback = true;
    119 
    120  if (!loader->mFontFaceSet) {
    121    // We've been canceled
    122    return;
    123  }
    124 
    125  gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
    126  StyleFontDisplay fontDisplay = loader->GetFontDisplay();
    127 
    128  // Depending upon the value of the font-display descriptor for the font,
    129  // their may be one or two timeouts associated with each font. The
    130  // LOADING_SLOWLY state indicates that the fallback font is shown. The
    131  // LOADING_TIMED_OUT state indicates that the fallback font is shown *and* the
    132  // downloaded font resource will not replace the fallback font when the load
    133  // completes.
    134 
    135  bool updateUserFontSet = true;
    136  switch (fontDisplay) {
    137    case StyleFontDisplay::Auto:
    138    case StyleFontDisplay::Block:
    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 (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
    142        int64_t contentLength;
    143        uint32_t numBytesRead;
    144        if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
    145            contentLength > 0 && contentLength < UINT32_MAX &&
    146            NS_SUCCEEDED(
    147                loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
    148            numBytesRead > 3 * (uint32_t(contentLength) >> 2)) {
    149          // More than 3/4 the data has been downloaded, so allow 50% extra
    150          // time and hope the remainder will arrive before the additional
    151          // time expires.
    152          ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
    153          uint32_t delay;
    154          loader->mLoadTimer->GetDelay(&delay);
    155          loader->mLoadTimer->InitWithNamedFuncCallback(
    156              LoadTimerCallback, static_cast<void*>(loader), delay >> 1,
    157              nsITimer::TYPE_ONE_SHOT,
    158              "nsFontFaceLoader::LoadTimerCallback"_ns);
    159          updateUserFontSet = false;
    160          LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
    161        }
    162      }
    163      if (updateUserFontSet) {
    164        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
    165      }
    166      break;
    167    case StyleFontDisplay::Swap:
    168      ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
    169      break;
    170    case StyleFontDisplay::Fallback: {
    171      if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
    172        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
    173      } else {
    174        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
    175        updateUserFontSet = false;
    176      }
    177      break;
    178    }
    179    case StyleFontDisplay::Optional:
    180      ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
    181      break;
    182 
    183    default:
    184      MOZ_ASSERT_UNREACHABLE("strange font-display value");
    185      break;
    186  }
    187 
    188  // If the font is not 75% loaded, or if we've already timed out once
    189  // before, we mark this entry as "loading slowly", so the fallback
    190  // font will be used in the meantime, and tell the context to refresh.
    191  if (updateUserFontSet) {
    192    // FIXME(emilio): This is basically the same as FontLoadComplete... Also
    193    // shouldn't this increment the generation for worker font sets?
    194    AutoTArray<RefPtr<gfxUserFontSet>, 4> fontSets;
    195    ufe->GetUserFontSets(fontSets);
    196    for (gfxUserFontSet* fontSet : fontSets) {
    197      if (FontVisibilityProvider* ctx =
    198              FontFaceSetImpl::GetFontVisibilityProviderFor(fontSet)) {
    199        fontSet->IncrementGeneration();
    200        ctx->UserFontSetUpdated(ufe);
    201        LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
    202             loader, ctx, static_cast<int>(fontDisplay)));
    203      }
    204    }
    205  }
    206 }
    207 
    208 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver, nsIRequestObserver)
    209 
    210 // nsIStreamLoaderObserver
    211 NS_IMETHODIMP
    212 nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader,
    213                                   nsISupports* aContext, nsresult aStatus,
    214                                   uint32_t aStringLen,
    215                                   const uint8_t* aString) {
    216  MOZ_ASSERT(NS_IsMainThread());
    217  MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
    218  MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
    219 
    220  AutoRestore<bool> scope{mInStreamComplete};
    221  mInStreamComplete = true;
    222 
    223  DropChannel();
    224 
    225  if (mLoadTimer) {
    226    mLoadTimer->Cancel();
    227    mLoadTimer = nullptr;
    228  }
    229 
    230  if (!mFontFaceSet) {
    231    // We've been canceled
    232    return aStatus;
    233  }
    234 
    235  TimeStamp doneTime = TimeStamp::Now();
    236  TimeDuration downloadTime = doneTime - mStartTime;
    237  glean::webfont::download_time.AccumulateRawDuration(downloadTime);
    238 
    239  uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
    240  if (GetFontDisplay() == StyleFontDisplay::Fallback) {
    241    uint32_t loadTimeout = GetFallbackDelay();
    242    if (downloadTimeMS > loadTimeout &&
    243        (mUserFontEntry->mFontDataLoadingState ==
    244         gfxUserFontEntry::LOADING_SLOWLY)) {
    245      mUserFontEntry->mFontDataLoadingState =
    246          gfxUserFontEntry::LOADING_TIMED_OUT;
    247    }
    248  }
    249 
    250  if (LOG_ENABLED()) {
    251    if (NS_SUCCEEDED(aStatus)) {
    252      LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
    253           this, mFontURI->GetSpecOrDefault().get(), downloadTimeMS));
    254    } else {
    255      LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8" PRIx32
    256           "\n",
    257           this, mFontURI->GetSpecOrDefault().get(),
    258           static_cast<uint32_t>(aStatus)));
    259    }
    260  }
    261 
    262  if (NS_SUCCEEDED(aStatus)) {
    263    // for HTTP requests, check whether the request _actually_ succeeded;
    264    // the "request status" in aStatus does not necessarily indicate this,
    265    // because HTTP responses such as 404 (Not Found) will still result in
    266    // a success code and potentially an HTML error page from the server
    267    // as the resulting data. We don't want to use that as a font.
    268    nsCOMPtr<nsIRequest> request;
    269    nsCOMPtr<nsIHttpChannel> httpChannel;
    270    aLoader->GetRequest(getter_AddRefs(request));
    271    httpChannel = do_QueryInterface(request);
    272    if (httpChannel) {
    273      bool succeeded;
    274      nsresult rv = httpChannel->GetRequestSucceeded(&succeeded);
    275      if (NS_SUCCEEDED(rv) && !succeeded) {
    276        aStatus = NS_ERROR_NOT_AVAILABLE;
    277      }
    278    }
    279  }
    280 
    281  mFontFaceSet->RecordFontLoadDone(aStringLen, doneTime);
    282 
    283  // The userFontEntry is responsible for freeing the downloaded data
    284  // (aString) when finished with it; the pointer is no longer valid
    285  // after FontDataDownloadComplete returns.
    286  // This is called even in the case of a failed download (HTTP 404, etc),
    287  // as there may still be data to be freed (e.g. an error page),
    288  // and we need to load the next source.
    289 
    290  // FontDataDownloadComplete will load the platform font on a worker thread,
    291  // and will call FontLoadComplete when it has finished its work.
    292  mUserFontEntry->FontDataDownloadComplete(mSrcIndex, aString, aStringLen,
    293                                           aStatus, this);
    294  return NS_SUCCESS_ADOPTED_DATA;
    295 }
    296 
    297 nsresult nsFontFaceLoader::FontLoadComplete() {
    298  MOZ_ASSERT(NS_IsMainThread());
    299 
    300  if (!mFontFaceSet) {
    301    // We've been canceled
    302    return NS_OK;
    303  }
    304 
    305  // when new font loaded, need to reflow
    306  mUserFontEntry->FontLoadComplete();
    307 
    308  MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
    309  mFontFaceSet->RemoveLoader(this);
    310  if (auto* doc = mFontFaceSet->GetDocument()) {
    311    doc->UnblockOnload(false);
    312  }
    313  mFontFaceSet = nullptr;
    314 
    315  return NS_OK;
    316 }
    317 
    318 // nsIRequestObserver
    319 NS_IMETHODIMP
    320 nsFontFaceLoader::OnStartRequest(nsIRequest* aRequest) {
    321  MOZ_ASSERT(NS_IsMainThread());
    322 
    323  nsCOMPtr<nsIThreadRetargetableRequest> req = do_QueryInterface(aRequest);
    324  if (req) {
    325    nsCOMPtr<nsIEventTarget> sts =
    326        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
    327    RefPtr<TaskQueue> queue =
    328        TaskQueue::Create(sts.forget(), "nsFontFaceLoader STS Delivery Queue");
    329    (void)NS_WARN_IF(NS_FAILED(req->RetargetDeliveryTo(queue)));
    330  }
    331  return NS_OK;
    332 }
    333 
    334 NS_IMETHODIMP
    335 nsFontFaceLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
    336  MOZ_ASSERT(NS_IsMainThread());
    337  DropChannel();
    338  return NS_OK;
    339 }
    340 
    341 void nsFontFaceLoader::Cancel() {
    342  MOZ_DIAGNOSTIC_ASSERT(!mInLoadTimerCallback);
    343  MOZ_DIAGNOSTIC_ASSERT(!mInStreamComplete);
    344  MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet);
    345 
    346  mUserFontEntry->LoadCanceled();
    347  mUserFontEntry = nullptr;
    348  auto* doc = mFontFaceSet->GetDocument();
    349  if (doc) {
    350    doc->UnblockOnload(false);
    351  }
    352  mFontFaceSet = nullptr;
    353  if (mLoadTimer) {
    354    mLoadTimer->Cancel();
    355    mLoadTimer = nullptr;
    356  }
    357  if (nsCOMPtr<nsIChannel> channel = std::move(mChannel)) {
    358    channel->CancelWithReason(NS_BINDING_ABORTED,
    359                              "nsFontFaceLoader::OnStopRequest"_ns);
    360  }
    361 }
    362 
    363 StyleFontDisplay nsFontFaceLoader::GetFontDisplay() {
    364  return mUserFontEntry->GetFontDisplay();
    365 }
    366 
    367 #undef LOG
    368 #undef LOG_ENABLED