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

#import "ios/web/js_messaging/web_frames_manager_impl.h"

#import "base/strings/string_number_conversions.h"
#import "ios/web/js_messaging/web_frame_impl.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/web_state/web_state_impl.h"
#import "testing/gtest/include/gtest/gtest.h"

namespace web {

namespace {

constexpr char kLowercaseFrameId[] = "abba1234beef1234cafe1234deed1234";
constexpr char kUppercaseFrameId[] = "ABBA1234BEEF1234CAFE1234DEED1234";

class FakeWebFramesManagerObserver : public WebFramesManagerImpl::Observer {
 public:
  // The current available frames as tracked by the WebFramesManage Observer
  // calls.
  const std::map<std::string, WebFrame*> frames() const { return frames_; }

  // WebFramesManagerImpl::Observer
  void WebFrameBecameAvailable(WebFramesManager* web_frames_manager,
                               WebFrame* web_frame) override {
    frames_[web_frame->GetFrameId()] = web_frame;
  }

  void WebFrameBecameUnavailable(WebFramesManager* web_frames_manager,
                                 const std::string& frame_id) override {
    frames_.erase(frame_id);
  }

 private:
  std::map<std::string, WebFrame*> frames_;
};

}  // namespace

class WebFramesManagerImplTest : public WebTestWithWebState {
 protected:
  void SetUp() override {
    WebTestWithWebState::SetUp();

    GetPageWorldWebFramesManager().AddObserver(&observer_);
  }

  void TearDown() override {
    GetPageWorldWebFramesManager().RemoveObserver(&observer_);

    WebTestWithWebState::TearDown();
  }

  // Notifies `web_state()` of a newly available `web_frame`.
  void SendFrameBecameAvailableMessage(std::unique_ptr<WebFrame> web_frame) {
    GetPageWorldWebFramesManager().AddFrame(std::move(web_frame));
  }

  // Notifies `web_state()` that the web frame with `frame_id` will become
  // unavailable.
  void SendFrameBecameUnavailableMessage(const std::string& frame_id) {
    GetPageWorldWebFramesManager().RemoveFrameWithId(frame_id);
  }

  WebFramesManagerImpl& GetPageWorldWebFramesManager() {
    WebStateImpl* web_state_impl = WebStateImpl::FromWebState(web_state());
    return web_state_impl->GetWebFramesManagerImpl(
        ContentWorld::kPageContentWorld);
  }

 protected:
  FakeWebFramesManagerObserver observer_;
};

// Tests main web frame construction/destruction.
TEST_F(WebFramesManagerImplTest, MainWebFrame) {
  auto frame = FakeWebFrame::Create(kMainFakeFrameId,
                                    /*is_main_frame=*/true,
                                    GURL("https://www.main.test"));
  FakeWebFrame* main_frame_ptr = frame.get();

  SendFrameBecameAvailableMessage(std::move(frame));

  EXPECT_EQ(1ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());
  WebFrame* main_frame = GetPageWorldWebFramesManager().GetMainWebFrame();
  WebFrame* main_frame_by_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kMainFakeFrameId);
  ASSERT_TRUE(main_frame);
  ASSERT_TRUE(main_frame_by_id);
  EXPECT_EQ(main_frame, main_frame_by_id);
  EXPECT_EQ(main_frame_ptr, main_frame_by_id);
  EXPECT_TRUE(main_frame->IsMainFrame());
  EXPECT_EQ(main_frame_ptr->GetSecurityOrigin(),
            main_frame->GetSecurityOrigin());

  const std::map<std::string, WebFrame*> observed_frames = observer_.frames();
  ASSERT_EQ(1ul, observed_frames.size());
  auto main_frame_it = observed_frames.find(kMainFakeFrameId);
  ASSERT_NE(main_frame_it, observed_frames.end());
  WebFrame* observed_main_frame = main_frame_it->second;
  EXPECT_TRUE(observed_main_frame);
  EXPECT_EQ(main_frame, observed_main_frame);

  SendFrameBecameUnavailableMessage(kMainFakeFrameId);
  EXPECT_EQ(0ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());
  EXPECT_FALSE(GetPageWorldWebFramesManager().GetMainWebFrame());
  EXPECT_FALSE(GetPageWorldWebFramesManager().GetFrameWithId(kMainFakeFrameId));

  EXPECT_EQ(0ul, observer_.frames().size());
}

// Tests duplicate registration of the main web frame.
TEST_F(WebFramesManagerImplTest, DuplicateMainWebFrame) {
  auto frame = FakeWebFrame::Create(kMainFakeFrameId,
                                    /*is_main_frame=*/true,
                                    GURL("https://www.main.test"));
  FakeWebFrame* main_frame_ptr = frame.get();

  auto second_main_frame = FakeWebFrame::Create(kChildFakeFrameId,
                                                /*is_main_frame=*/true,
                                                GURL("https://www.main2.test"));

  SendFrameBecameAvailableMessage(std::move(frame));
  SendFrameBecameAvailableMessage(std::move(second_main_frame));

  // Validate that `frame` remains the main frame and `second_main_frame` is
  // ignored.
  EXPECT_EQ(1ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());
  WebFrame* main_frame = GetPageWorldWebFramesManager().GetMainWebFrame();
  WebFrame* main_frame_by_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kMainFakeFrameId);
  ASSERT_TRUE(main_frame);
  ASSERT_TRUE(main_frame_by_id);
  EXPECT_EQ(main_frame, main_frame_by_id);
  EXPECT_EQ(main_frame_ptr, main_frame_by_id);
  EXPECT_TRUE(main_frame->IsMainFrame());
  EXPECT_EQ(main_frame_ptr->GetSecurityOrigin(),
            main_frame->GetSecurityOrigin());

  const std::map<std::string, WebFrame*> observed_frames = observer_.frames();
  ASSERT_EQ(1ul, observed_frames.size());
  auto main_frame_it = observed_frames.find(kMainFakeFrameId);
  ASSERT_NE(main_frame_it, observed_frames.end());
  WebFrame* observed_main_frame = main_frame_it->second;
  EXPECT_TRUE(observed_main_frame);
  EXPECT_EQ(main_frame, observed_main_frame);
}

// Tests WebStateImpl::RemoveAllWebFrames. Removing all frames must go through
// the web state in order to notify observers.
TEST_F(WebFramesManagerImplTest, RemoveAllWebFrames) {
  SendFrameBecameAvailableMessage(FakeWebFrame::Create(
      kMainFakeFrameId,
      /*is_main_frame=*/true, GURL("https://www.main.test")));
  SendFrameBecameAvailableMessage(FakeWebFrame::Create(
      kChildFakeFrameId,
      /*is_main_frame=*/false, GURL("https://www.frame1.test")));
  SendFrameBecameAvailableMessage(FakeWebFrame::Create(
      kChildFakeFrameId2,
      /*is_main_frame=*/false, GURL("https://www.frame2.test")));
  EXPECT_EQ(3ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());

  WebStateImpl* web_state_impl = WebStateImpl::FromWebState(web_state());
  web_state_impl->RemoveAllWebFrames();
  EXPECT_EQ(0ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());
  // Check main frame.
  EXPECT_FALSE(GetPageWorldWebFramesManager().GetMainWebFrame());
  EXPECT_FALSE(GetPageWorldWebFramesManager().GetFrameWithId(kMainFakeFrameId));
  // Check frame 1.
  EXPECT_FALSE(
      GetPageWorldWebFramesManager().GetFrameWithId(kChildFakeFrameId));
  // Check frame 2.
  EXPECT_FALSE(
      GetPageWorldWebFramesManager().GetFrameWithId(kChildFakeFrameId2));

  const std::map<std::string, WebFrame*> observed_frames = observer_.frames();
  ASSERT_EQ(0ul, observed_frames.size());
  // Check main frame.
  auto main_frame_it = observed_frames.find(kMainFakeFrameId);
  ASSERT_EQ(main_frame_it, observed_frames.end());
  // Check frame 1.
  auto frame_1_it = observed_frames.find(kChildFakeFrameId);
  ASSERT_EQ(frame_1_it, observed_frames.end());
  // Check frame 2.
  auto frame_2_it = observed_frames.find(kChildFakeFrameId2);
  ASSERT_EQ(frame_2_it, observed_frames.end());
}

// Tests removing a frame which doesn't exist.
TEST_F(WebFramesManagerImplTest, RemoveNonexistantFrame) {
  auto frame = FakeWebFrame::Create(kMainFakeFrameId,
                                    /*is_main_frame=*/true,
                                    GURL("https://www.main.test"));
  FakeWebFrame* main_frame_ptr = frame.get();
  SendFrameBecameAvailableMessage(std::move(frame));

  SendFrameBecameUnavailableMessage(kChildFakeFrameId);
  EXPECT_EQ(1ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());
  WebFrame* main_frame = GetPageWorldWebFramesManager().GetMainWebFrame();
  WebFrame* main_frame_by_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kMainFakeFrameId);
  ASSERT_TRUE(main_frame);
  ASSERT_TRUE(main_frame_by_id);
  EXPECT_EQ(main_frame, main_frame_by_id);
  EXPECT_EQ(main_frame_ptr, main_frame_by_id);
  EXPECT_TRUE(main_frame->IsMainFrame());
  EXPECT_EQ(main_frame_ptr->GetSecurityOrigin(),
            main_frame->GetSecurityOrigin());

  const std::map<std::string, WebFrame*> observed_frames = observer_.frames();
  ASSERT_EQ(1ul, observed_frames.size());
  auto main_frame_it = observed_frames.find(kMainFakeFrameId);
  ASSERT_NE(main_frame_it, observed_frames.end());
  WebFrame* observed_main_frame = main_frame_it->second;
  EXPECT_TRUE(observed_main_frame);
  EXPECT_EQ(main_frame, observed_main_frame);
}

// Tests that frame lookup is not case-sensitive.
TEST_F(WebFramesManagerImplTest, CaseInsensitiveLookup) {
  auto frame = FakeWebFrame::Create(kLowercaseFrameId,
                                    /*is_main_frame=*/true,
                                    GURL("https://www.main.test"));
  SendFrameBecameAvailableMessage(std::move(frame));

  EXPECT_EQ(1ul, GetPageWorldWebFramesManager().GetAllWebFrames().size());

  WebFrame* frame_by_uppercase_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kUppercaseFrameId);
  EXPECT_TRUE(frame_by_uppercase_id);

  WebFrame* frame_by_lowercase_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kLowercaseFrameId);
  EXPECT_TRUE(frame_by_lowercase_id);

  EXPECT_EQ(frame_by_uppercase_id, frame_by_lowercase_id);
}

// By convention, the frame ID should be stored in lowercase internally, even if
// it was passed as uppercase at construct-time.
TEST_F(WebFramesManagerImplTest, CaseInsensitiveConstruct) {
  auto frame_with_uppercase_id = FakeWebFrame::Create(
      kUppercaseFrameId, /*is_main_frame=*/true, GURL("https://www.main.test"));

  SendFrameBecameAvailableMessage(std::move(frame_with_uppercase_id));

  WebFrame* frame_by_lowercase_id =
      GetPageWorldWebFramesManager().GetFrameWithId(kLowercaseFrameId);
  ASSERT_TRUE(frame_by_lowercase_id);

  EXPECT_EQ(frame_by_lowercase_id->GetFrameId(), kLowercaseFrameId);
}

}  // namespace web
