/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "FontVisibilityProvider.h"

#include "gfxTypes.h"
#include "gfxFontEntry.h"
#include "gfxPlatformFontList.h"

#include "SharedFontList.h"
#include "nsPresContext.h"
#include "nsRFPService.h"

#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/ContentBlockingAllowList.h"

#include "mozilla/dom/OffscreenCanvas.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/OffscreenCanvas.h"

using namespace mozilla;

void FontVisibilityProvider::ReportBlockedFontFamily(
    const gfxFontFamily& aFamily) const {
  nsCString msg;
  FormatBlockedFontFamilyMessage(msg, aFamily.Name(), aFamily.Visibility());
  ReportBlockedFontFamily(msg);
}

void FontVisibilityProvider::ReportBlockedFontFamily(
    const fontlist::Family& aFamily) const {
  auto* fontList = gfxPlatformFontList::PlatformFontList()->SharedFontList();
  const nsCString& name = aFamily.DisplayName().AsString(fontList);
  nsCString msg;
  FormatBlockedFontFamilyMessage(msg, name, aFamily.Visibility());
  ReportBlockedFontFamily(msg);
}

void FontVisibilityProvider::FormatBlockedFontFamilyMessage(
    nsCString& aMsg, const nsCString& aFamily,
    FontVisibility aVisibility) const {
  aMsg.AppendPrintf(
      "Request for font \"%s\" blocked at visibility level %d (requires "
      "%d)\n",
      aFamily.get(), int(GetFontVisibility()), int(aVisibility));
}

FontVisibility FontVisibilityProvider::ComputeFontVisibility() const {
  /*
   * Expected behavior in order of precedence:
   *  1  If offscreen canvas, attempt to inherit the visibility
   *  2  Chrome Rules give User Level (3)
   *  3  RFP gives Highest Level (1 aka Base)
   *  4  An RFPTarget of Base gives Base Level (1)
   *  5  An RFPTarget of LangPack gives LangPack Level (2)
   *  6  The value of the Standard Font Visibility Pref
   *
   * If the ETP toggle is disabled (aka
   * ContentBlockingAllowList::Check is true), it will only override 4-6,
   * not rules 2 or 3.
   */

  // Rule 1: If the visibility should be inherited, return that value.
  if (Maybe<FontVisibility> maybeVis = MaybeInheritFontVisibility()) {
    return *maybeVis;
  }

  // Rule 2: Allow all font access for privileged contexts, including
  // chrome and devtools contexts.
  if (IsChrome()) {
    return FontVisibility::User;
  }

  // Is this a private browsing context?
  bool isPrivate = IsPrivateBrowsing();

  int32_t level;
  // Rule 4
  if (ShouldResistFingerprinting(RFPTarget::FontVisibilityBaseSystem)) {
    // Rule 3: Check RFP pref
    // This is inside Rule 4 in case this document is exempted from RFP.
    // But if it is not exempted, and RFP is enabled, we return immediately
    // to prevent the override below from occurring.
    if (nsRFPService::IsRFPPrefEnabled(isPrivate)) {
      return FontVisibility::Base;
    }

    level = int32_t(FontVisibility::Base);
  }
  // Rule 5
  else if (ShouldResistFingerprinting(RFPTarget::FontVisibilityLangPack)) {
    level = int32_t(FontVisibility::LangPack);
  }
  // Rule 6
  else {
    level = StaticPrefs::layout_css_font_visibility();
  }

  nsICookieJarSettings* cookieJarSettings = GetCookieJarSettings();

  // Override Rules 4-6 Only: Determine if the user has exempted the
  // domain from tracking protections, if so, use the default value.
  if (level != StaticPrefs::layout_css_font_visibility() &&
      ContentBlockingAllowList::Check(cookieJarSettings)) {
    level = StaticPrefs::layout_css_font_visibility();
  }

  // Clamp result to the valid range of levels.
  level = std::clamp(level, int32_t(FontVisibility::Base),
                     int32_t(FontVisibility::User));

  return FontVisibility(level);
}
