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

#import <memory>

#import "base/test/ios/wait_util.h"
#import "ios/web/common/crw_input_view_provider.h"
#import "ios/web/common/uikit_ui_util.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_client.h"
#import "ios/web_view/internal/browser_state_keyed_service_factories.h"
#import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
#import "ios/web_view/internal/cwv_web_view_internal.h"
#import "ios/web_view/internal/web_view_browser_state.h"
#import "ios/web_view/test/test_with_locale_and_resources.h"
#import "ios/web_view/test/web_view_test_util.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"

using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace ios_web_view {

class CWVWebViewTest : public TestWithLocaleAndResources {
 public:
  void SetUp() override {
    TestWithLocaleAndResources::SetUp();

    configuration_ = [[CWVWebViewConfiguration alloc]
        initWithBrowserState:std::make_unique<WebViewBrowserState>(
                                 /*off_the_record=*/false)];
  }

  void TearDown() override {
    // The BrowserState owned by `configuration_` cannot outlive the
    // `task_environment_` or it will DCHECK.
    [configuration_ shutDown];
    configuration_ = nil;

    TestWithLocaleAndResources::TearDown();
  }

  CWVWebView* CreateWebView() {
    CGRect frame = CGRectMake(0, 0, 1, 1);
    CWVWebView* web_view = [[CWVWebView alloc] initWithFrame:frame
                                               configuration:configuration_];

    // The WKWebView must be present in the view hierarchy in order to prevent
    // WebKit optimizations which may pause internal parts of the web view
    // without notice. Work around this by adding the view directly.
    UIViewController* view_controller = [GetAnyKeyWindow() rootViewController];
    [view_controller.view addSubview:web_view];

    return web_view;
  }

 protected:
  CWVWebViewTest() : web_client_(std::make_unique<web::WebClient>()) {
    EnsureBrowserStateKeyedServiceFactoriesBuilt();
  }

  web::WebTaskEnvironment task_environment_;
  web::ScopedTestingWebClient web_client_;
  CWVWebViewConfiguration* configuration_;
};

// Test CWVWebView's inputAccessoryView controls whether or not the overriding
// behavior is enabled.
TEST_F(CWVWebViewTest, InputAccessoryView) {
  CWVWebView* web_view = CreateWebView();

  EXPECT_FALSE(web_view.inputAccessoryView);
  EXPECT_FALSE([web_view webStateInputViewProvider:nil]);

  UIView* input_accessory_view = [[UIView alloc] initWithFrame:web_view.frame];
  web_view.inputAccessoryView = input_accessory_view;
  EXPECT_EQ(web_view, [web_view webStateInputViewProvider:nil]);
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (using only legacy code).
TEST_F(CWVWebViewTest, EncodeDecodeLegacy) {
  // Force the use of legacy storage.
  CWVWebView.useOptimizedSessionStorage = NO;

  NSData* serialized_state = nil;
  {
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);

    [web_view shutDown];
  }

  {
    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (using only optimized code).
TEST_F(CWVWebViewTest, EncodeDecodeOptimized) {
  // Force the use of optimized storage.
  CWVWebView.useOptimizedSessionStorage = YES;

  NSData* serialized_state = nil;
  {
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);

    [web_view shutDown];
  }

  {
    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (migrate legacy to optimised).
TEST_F(CWVWebViewTest, EncodeDecodeMigrateLegacyToOptimized) {
  NSData* serialized_state = nil;
  {
    // Force the use of legacy storage.
    CWVWebView.useOptimizedSessionStorage = NO;
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);

    [web_view shutDown];
  }

  {
    // Force the use of optimized storage.
    CWVWebView.useOptimizedSessionStorage = YES;

    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (migrate optimised to legacy).
TEST_F(CWVWebViewTest, EncodeDecodeMigrateOptimizedToLegacy) {
  NSData* serialized_state = nil;
  {
    // Force the use of optimized storage.
    CWVWebView.useOptimizedSessionStorage = YES;
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);

    [web_view shutDown];
  }

  {
    // Force the use of legacy storage.
    CWVWebView.useOptimizedSessionStorage = NO;

    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (using only legacy code, after shutdown).
TEST_F(CWVWebViewTest, EncodeDecodeLegacyAfterShutdown) {
  // Force the use of legacy storage.
  CWVWebView.useOptimizedSessionStorage = NO;

  NSData* serialized_state = nil;
  {
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    // Shutdown before serialization.
    [web_view shutDown];

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);
  }

  {
    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

// Tests CWVWebView's session serialization logic by saving and then
// restoring the WebView state (using only optimized code, after shutdown).
TEST_F(CWVWebViewTest, EncodeDecodeOptimizedAfterShutdown) {
  // Force the use of optimized storage.
  CWVWebView.useOptimizedSessionStorage = YES;

  NSData* serialized_state = nil;
  {
    CWVWebView* web_view = CreateWebView();

    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:newtab"]));
    ASSERT_TRUE(test::LoadUrl(web_view, [NSURL URLWithString:@"about:blank"]));
    ASSERT_TRUE([web_view canGoBack]);

    // Shutdown before serialization.
    [web_view shutDown];

    NSKeyedArchiver* archiver =
        [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO];
    [web_view encodeRestorableStateWithCoder:archiver];
    serialized_state = [archiver encodedData];
    ASSERT_TRUE(serialized_state);
  }

  {
    // Create a new CWVWebView to restore the state into. Check that there is
    // no navigation history in it and then restore the state.
    CWVWebView* web_view = CreateWebView();
    ASSERT_FALSE([web_view canGoBack]);

    NSKeyedUnarchiver* unarchiver =
        [[NSKeyedUnarchiver alloc] initForReadingFromData:serialized_state
                                                    error:nil];
    unarchiver.requiresSecureCoding = NO;

    // Verify that the state can been restored correctly.
    [web_view decodeRestorableStateWithCoder:unarchiver];
    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return !web_view.loading;
    }));

    // Verify that the state has been restored correctly.
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"],
                web_view.lastCommittedURL);
    EXPECT_NSEQ([NSURL URLWithString:@"about:blank"], web_view.visibleURL);
    EXPECT_TRUE([web_view canGoBack]);

    [web_view shutDown];
  }
}

}  // namespace ios_web_view
