text: /*
   * Copyright (C) 2013 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
   * DAMAGE.
   */
  
  #include "third_party/blink/renderer/core/css/font_face_set_document.h"
  
  #include "base/metrics/histogram_functions.h"
  #include "third_party/blink/public/common/features.h"
  #include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
  #include "third_party/blink/renderer/core/css/css_font_face.h"
  #include "third_party/blink/renderer/core/css/css_font_selector.h"
  #include "third_party/blink/renderer/core/css/css_property_value_set.h"
  #include "third_party/blink/renderer/core/css/css_segmented_font_face.h"
  #include "third_party/blink/renderer/core/css/font_face_cache.h"
  #include "third_party/blink/renderer/core/css/font_face_set_load_event.h"
  #include "third_party/blink/renderer/core/css/parser/css_parser.h"
  #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
  #include "third_party/blink/renderer/core/css/resolver/font_style_resolver.h"
  #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
  #include "third_party/blink/renderer/core/css/style_engine.h"
  #include "third_party/blink/renderer/core/dom/document.h"
  #include "third_party/blink/renderer/core/frame/local_dom_window.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/frame/local_frame_view.h"
  #include "third_party/blink/renderer/core/loader/document_loader.h"
  #include "third_party/blink/renderer/core/style/computed_style.h"
  #include "third_party/blink/renderer/platform/bindings/script_state.h"
  #include "third_party/blink/renderer/platform/heap/heap.h"
  
  namespace blink {
  
  // static
  const char FontFaceSetDocument::kSupplementName[] = "FontFaceSetDocument";
  
  FontFaceSetDocument::FontFaceSetDocument(Document& document)
      : FontFaceSet(*document.GetExecutionContext()),
        Supplement<Document>(document),
        lcp_limit_timer_(document.GetTaskRunner(TaskType::kInternalLoading),
                         this,
                         &FontFaceSetDocument::LCPLimitReached) {}
  
  FontFaceSetDocument::~FontFaceSetDocument() = default;
  
  bool FontFaceSetDocument::InActiveContext() const {
    ExecutionContext* context = GetExecutionContext();
    return context && To<LocalDOMWindow>(context)->document()->IsActive();
  }
  
  
  AtomicString FontFaceSetDocument::status() const {
    DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading"));
    DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded"));
    return is_loading_ ? loading : loaded;
  }
  
  void FontFaceSetDocument::DidLayout() {
    if (!GetExecutionContext())
      return;
    if (GetDocument()->GetFrame()->IsMainFrame() && loading_fonts_.IsEmpty())
      font_load_histogram_.Record();
    if (!ShouldSignalReady())
      return;
    HandlePendingEventsAndPromisesSoon();
  }
  
  void FontFaceSetDocument::StartLCPLimitTimerIfNeeded() {
    // Make sure the timer is started at most once for each document, and only
    // when the feature is enabled
    if (!base::FeatureList::IsEnabled(
            features::kAlignFontDisplayAutoTimeoutWithLCPGoal) ||
        has_reached_lcp_limit_ || lcp_limit_timer_.IsActive() ||
        !GetDocument()->Loader()) {
      return;
    }
  
    lcp_limit_timer_.StartOneShot(
        GetDocument()->Loader()->RemainingTimeToLCPLimit(), FROM_HERE);
  }
  
  void FontFaceSetDocument::BeginFontLoading(FontFace* font_face) {
    AddToLoadingFonts(font_face);
    StartLCPLimitTimerIfNeeded();
  }
  
  void FontFaceSetDocument::NotifyLoaded(FontFace* font_face) {
    font_load_histogram_.UpdateStatus(font_face);
    loaded_fonts_.push_back(font_face);
    RemoveFromLoadingFonts(font_face);
  }
  
  void FontFaceSetDocument::NotifyError(FontFace* font_face) {
    font_load_histogram_.UpdateStatus(font_face);
    failed_fonts_.push_back(font_face);
    RemoveFromLoadingFonts(font_face);
  }
  
  size_t FontFaceSetDocument::ApproximateBlankCharacterCount() const {
    size_t count = 0;
    for (auto& font_face : loading_fonts_)
      count += font_face->ApproximateBlankCharacterCount();
    return count;
  }
  
  ScriptPromise FontFaceSetDocument::ready(ScriptState* script_state) {
    if (ready_->GetState() != ReadyProperty::kPending && InActiveContext()) {
      // |ready_| is already resolved, but there may be pending stylesheet
      // changes and/or layout operations that may cause another font loads.
      // So synchronously update style and layout here.
      // This may trigger font loads, and replace |ready_| with a new Promise.
      GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
    }
    return ready_->Promise(script_state->World());
  }
  
  const HeapLinkedHashSet<Member<FontFace>>&
  FontFaceSetDocument::CSSConnectedFontFaceList() const {
    Document* document = GetDocument();
    document->GetStyleEngine().UpdateActiveStyle();
    return GetFontSelector()->GetFontFaceCache()->CssConnectedFontFaces();
  }
  
  void FontFaceSetDocument::FireDoneEventIfPossible() {
    if (should_fire_loading_event_)
      return;
    if (!ShouldSignalReady())
      return;
    Document* d = GetDocument();
    if (!d)
      return;
  
    // If the layout was invalidated in between when we thought layout
    // was updated and when we're ready to fire the event, just wait
    // until after the next layout before firing events.
    if (!d->View() || d->View()->NeedsLayout())
      return;
  
    FireDoneEvent();
  }
  
  
  bool FontFaceSetDocument::ResolveFontStyle(const String& font_string,
                                             Font& font) {
    if (font_string.IsEmpty())
      return false;
  
    // Interpret fontString in the same way as the 'font' attribute of
    // CanvasRenderingContext2D.
    auto* parsed_style = CSSParser::ParseFont(font_string, GetExecutionContext());
    if (!parsed_style)
      return false;
  
    if (!GetDocument()->documentElement()) {
      auto* font_selector = GetDocument()->GetStyleEngine().GetFontSelector();
      FontDescription description =
          FontStyleResolver::ComputeFont(*parsed_style, font_selector);
      font = Font(description, font_selector);
      return true;
    }
  
    scoped_refptr<ComputedStyle> style =
        GetDocument()->GetStyleResolver().CreateComputedStyle();
  
    FontFamily font_family;
    font_family.SetFamily(
        FontFaceSet::kDefaultFontFamily,
        FontFamily::InferredTypeFor(FontFaceSet::kDefaultFontFamily));
  
    FontDescription default_font_description;
    default_font_description.SetFamily(font_family);
    default_font_description.SetSpecifiedSize(FontFaceSet::kDefaultFontSize);
    default_font_description.SetComputedSize(FontFaceSet::kDefaultFontSize);
  
    style->SetFontDescription(default_font_description);
  
    GetDocument()->GetStyleEngine().ComputeFont(*GetDocument()->documentElement(),
                                                style.get(), *parsed_style);
  
    font = style->GetFont();
  
    // StyleResolver::ComputeFont() should have set the document's FontSelector
    // to |style|.
    DCHECK_EQ(font.GetFontSelector(), GetFontSelector());
  
    return true;
  }
  
  Document* FontFaceSetDocument::GetDocument() const {
    if (auto* window = To<LocalDOMWindow>(GetExecutionContext()))
      return window->document();
    return nullptr;
  }
  
  FontFaceSetDocument* FontFaceSetDocument::From(Document& document) {
    FontFaceSetDocument* fonts =
        Supplement<Document>::From<FontFaceSetDocument>(document);
    if (!fonts) {
      fonts = MakeGarbageCollected<FontFaceSetDocument>(document);
      Supplement<Document>::ProvideTo(document, fonts);
    }
  
    return fonts;
  }
  
  void FontFaceSetDocument::DidLayout(Document& document) {
    if (!document.LoadEventFinished()) {
      // https://www.w3.org/TR/2014/WD-css-font-loading-3-20140522/#font-face-set-ready
      // doesn't say when document.fonts.ready should actually fire, but the
      // existing tests depend on it firing after onload.
      return;
    }
    if (FontFaceSetDocument* fonts =
            Supplement<Document>::From<FontFaceSetDocument>(document))
      fonts->DidLayout();
  }
  
  size_t FontFaceSetDocument::ApproximateBlankCharacterCount(Document& document) {
    if (FontFaceSetDocument* fonts =
            Supplement<Document>::From<FontFaceSetDocument>(document))
      return fonts->ApproximateBlankCharacterCount();
    return 0;
  }
  
  void FontFaceSetDocument::AlignTimeoutWithLCPGoal(FontFace* font_face) {
    bool is_loading = font_face->LoadStatus() == FontFace::kLoading;
    bool affected = font_face->CssFontFace()->UpdatePeriod();
    // We only count loading font faces, so that unused fonts are excluded. This
    // is especially useful when the page uses a font library, where most of the
    // fonts are unused.
    if (is_loading && font_face->display() == "auto") {
      font_display_auto_align_histogram_.SetHasFontDisplayAuto();
      if (affected)
        font_display_auto_align_histogram_.CountAffected();
    }
  }
  
  void FontFaceSetDocument::LCPLimitReached(TimerBase*) {
    DCHECK(base::FeatureList::IsEnabled(
        features::kAlignFontDisplayAutoTimeoutWithLCPGoal));
    if (!GetDocument() || !GetDocument()->IsActive())
      return;
    has_reached_lcp_limit_ = true;
    for (FontFace* font_face : CSSConnectedFontFaceList())
      AlignTimeoutWithLCPGoal(font_face);
    for (FontFace* font_face : non_css_connected_faces_)
      AlignTimeoutWithLCPGoal(font_face);
    font_display_auto_align_histogram_.Record();
  }
  
  void FontFaceSetDocument::Trace(Visitor* visitor) const {
    visitor->Trace(lcp_limit_timer_);
    Supplement<Document>::Trace(visitor);
    FontFaceSet::Trace(visitor);
  }
  
  void FontFaceSetDocument::FontLoadHistogram::UpdateStatus(FontFace* font_face) {
    if (status_ == kReported)
      return;
    if (font_face->HadBlankText())
      status_ = kHadBlankText;
    else if (status_ == kNoWebFonts)
      status_ = kDidNotHaveBlankText;
  }
  
  void FontFaceSetDocument::FontLoadHistogram::Record() {
    if (status_ == kHadBlankText || status_ == kDidNotHaveBlankText) {
      base::UmaHistogramBoolean("WebFont.HadBlankText", status_ == kHadBlankText);
      status_ = kReported;
    }
  }
  
  void FontFaceSetDocument::FontDisplayAutoAlignHistogram::Record() {
    if (!base::FeatureList::IsEnabled(
            features::kAlignFontDisplayAutoTimeoutWithLCPGoal)) {
      return;
    }
    if (!has_font_display_auto_ || reported_)
      return;
    base::UmaHistogramCounts100(
        "WebFont.Clients.AlignFontDisplayAuto.FontFacesAffected",
        affected_count_);
    reported_ = true;
  }
  
  }  // namespace blink
  