// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/loader/progress_tracker.h"

#include "base/auto_reset.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/testing/fake_local_frame_host.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"

namespace blink {

class ProgressTrackerTest : public testing::Test, public FakeLocalFrameHost {
 public:
  ProgressTrackerTest()
      : response_(KURL("http://example.com")), last_progress_(0.0) {
    response_.SetMimeType(AtomicString("text/html"));
    response_.SetExpectedContentLength(1024);
  }

  void SetUp() override {
    FakeLocalFrameHost::Init(
        web_frame_client_.GetRemoteNavigationAssociatedInterfaces());
    web_view_helper_.Initialize(&web_frame_client_);
  }

  void TearDown() override {
    // The WebViewHelper will crash when being reset if the TestWebFrameClient
    // is still reporting that some loads are in progress, so let's make sure
    // that's not the case via a call to ProgressTracker::ProgressCompleted().
    if (web_frame_client_.IsLoading())
      Progress().ProgressCompleted();
    web_view_helper_.Reset();
  }

  LocalFrame* GetFrame() const {
    return web_view_helper_.GetWebView()->MainFrameImpl()->GetFrame();
  }

  ProgressTracker& Progress() const { return GetFrame()->Loader().Progress(); }

  double LastProgress() const { return last_progress_; }

  const ResourceResponse& ResponseHeaders() const { return response_; }

  // Reports a 1024-byte "main resource" (VeryHigh priority) request/response
  // to ProgressTracker with identifier 1, but tests are responsible for
  // emulating payload and load completion.
  void EmulateMainResourceRequestAndResponse() const {
    Progress().ProgressStarted();
    Progress().WillStartLoading(1ul, ResourceLoadPriority::kVeryHigh);
    EXPECT_EQ(0.0, LastProgress());
    Progress().IncrementProgress(1ul, ResponseHeaders());
    EXPECT_EQ(0.0, LastProgress());
  }

  double WaitForNextProgressChange() const {
    base::RunLoop run_loop;
    base::AutoReset<base::RunLoop*> current_loop(&current_run_loop_, &run_loop);
    run_loop.Run();
    return last_progress_;
  }

  // FakeLocalFrameHost:
  void DidChangeLoadProgress(double progress) override {
    last_progress_ = progress;
    current_run_loop_->Quit();
  }

 private:
  test::TaskEnvironment task_environment_;
  mutable base::RunLoop* current_run_loop_ = nullptr;
  frame_test_helpers::TestWebFrameClient web_frame_client_;
  frame_test_helpers::WebViewHelper web_view_helper_;
  ResourceResponse response_;
  double last_progress_;
};

TEST_F(ProgressTrackerTest, Static) {
  Progress().ProgressStarted();
  EXPECT_EQ(0.0, LastProgress());
  Progress().ProgressCompleted();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, MainResourceOnly) {
  EmulateMainResourceRequestAndResponse();

  // .2 for committing, .25 out of .5 possible for bytes received.
  Progress().IncrementProgress(1ul, 512);
  EXPECT_EQ(0.45, WaitForNextProgressChange());

  // .2 for committing, .5 for all bytes received.
  Progress().CompleteProgress(1ul);
  EXPECT_EQ(0.7, WaitForNextProgressChange());

  Progress().FinishedParsing();
  EXPECT_EQ(0.8, WaitForNextProgressChange());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, WithHighPriorirySubresource) {
  EmulateMainResourceRequestAndResponse();

  Progress().WillStartLoading(2ul, ResourceLoadPriority::kHigh);
  Progress().IncrementProgress(2ul, ResponseHeaders());
  EXPECT_EQ(0.0, LastProgress());

  // .2 for committing, .25 out of .5 possible for bytes received.
  Progress().IncrementProgress(1ul, 1024);
  Progress().CompleteProgress(1ul);
  EXPECT_EQ(0.45, WaitForNextProgressChange());

  // .4 for finishing parsing/painting,
  // .25 out of .5 possible for bytes received.
  Progress().FinishedParsing();
  EXPECT_EQ(0.55, WaitForNextProgressChange());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(0.65, WaitForNextProgressChange());

  Progress().CompleteProgress(2ul);
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, WithMediumPrioritySubresource) {
  EmulateMainResourceRequestAndResponse();

  Progress().WillStartLoading(2ul, ResourceLoadPriority::kMedium);
  Progress().IncrementProgress(2ul, ResponseHeaders());
  EXPECT_EQ(0.0, LastProgress());

  // .2 for committing, .5 for all bytes received.
  // Medium priority resource is ignored.
  Progress().CompleteProgress(1ul);
  EXPECT_EQ(0.7, WaitForNextProgressChange());

  Progress().FinishedParsing();
  EXPECT_EQ(0.8, WaitForNextProgressChange());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, FinishParsingBeforeContentfulPaint) {
  EmulateMainResourceRequestAndResponse();

  // .2 for committing, .5 for all bytes received.
  Progress().CompleteProgress(1ul);
  EXPECT_EQ(0.7, WaitForNextProgressChange());

  Progress().FinishedParsing();
  EXPECT_EQ(0.8, WaitForNextProgressChange());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, ContentfulPaintBeforeFinishParsing) {
  EmulateMainResourceRequestAndResponse();

  // .2 for committing, .5 for all bytes received.
  Progress().CompleteProgress(1ul);
  EXPECT_EQ(0.7, WaitForNextProgressChange());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(0.8, WaitForNextProgressChange());

  Progress().FinishedParsing();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, NavigationApiInterceptBeforeCompletion) {
  Progress().ProgressStarted();
  EXPECT_EQ(0.0, LastProgress());

  Progress().DidFirstContentfulPaint();
  EXPECT_EQ(0.8, WaitForNextProgressChange());

  Progress().DidNavigationApiIntercept();
  EXPECT_EQ(0.8, LastProgress());

  Progress().ProgressCompleted();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

TEST_F(ProgressTrackerTest, NavigationApiInterceptAfterCompletion) {
  Progress().ProgressStarted();
  EXPECT_EQ(0.0, LastProgress());
  Progress().ProgressCompleted();
  EXPECT_EQ(1.0, WaitForNextProgressChange());

  Progress().ProgressStarted();
  Progress().DidNavigationApiIntercept();
  EXPECT_EQ(0.7, WaitForNextProgressChange());
  Progress().ProgressCompleted();
  EXPECT_EQ(1.0, WaitForNextProgressChange());
}

}  // namespace blink
