// 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 "ios/web/public/js_messaging/java_script_feature.h"

#import <WebKit/WebKit.h>

#import "base/strings/sys_string_conversions.h"
#import "ios/web/js_messaging/page_script_util.h"
#import "ios/web/public/js_messaging/content_world.h"
#import "ios/web/public/test/js_test_util.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

typedef PlatformTest JavaScriptFeatureTest;

// Tests the creation of FeatureScripts.
TEST_F(JavaScriptFeatureTest, CreateFeatureScript) {
  auto document_start_injection_time =
      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
  auto target_frames_all =
      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
  auto feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb", document_start_injection_time, target_frames_all);

  EXPECT_EQ(document_start_injection_time, feature_script.GetInjectionTime());
  EXPECT_EQ(target_frames_all, feature_script.GetTargetFrames());
  EXPECT_TRUE([feature_script.GetScriptString() containsString:@"__gCrWeb"]);
}

// Tests the creation of FeatureScripts with a script string.
TEST_F(JavaScriptFeatureTest, CreateFeatureScriptWithScriptString) {
  auto document_start_injection_time =
      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
  auto target_frames_all =
      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
  auto feature_script = web::JavaScriptFeature::FeatureScript::CreateWithString(
      "window.__scriptStringObject = {};", document_start_injection_time,
      target_frames_all);

  EXPECT_EQ(document_start_injection_time, feature_script.GetInjectionTime());
  EXPECT_EQ(target_frames_all, feature_script.GetTargetFrames());
  EXPECT_TRUE([feature_script.GetScriptString()
      containsString:@"__scriptStringObject"]);
}

// Tests the creation of FeatureScripts with different reinjection behaviors.
TEST_F(JavaScriptFeatureTest, FeatureScriptReinjectionBehavior) {
  auto once_feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb",
          web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart,
          web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames,
          web::JavaScriptFeature::FeatureScript::ReinjectionBehavior::
              kInjectOncePerWindow);

  auto reinject_feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb",
          web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart,
          web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames,
          web::JavaScriptFeature::FeatureScript::ReinjectionBehavior::
              kReinjectOnDocumentRecreation);

  EXPECT_NSNE(once_feature_script.GetScriptString(),
              reinject_feature_script.GetScriptString());
}

// Tests that FeatureScripts are only injected once when created with
// `ReinjectionBehavior::kInjectOncePerWindow`.
TEST_F(JavaScriptFeatureTest, ReinjectionBehaviorOnce) {
  auto feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb",
          web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart,
          web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames,
          web::JavaScriptFeature::FeatureScript::ReinjectionBehavior::
              kInjectOncePerWindow);

  WKWebView* web_view = [[WKWebView alloc] init];
  web::test::ExecuteJavaScriptInWebView(web_view,
                                        feature_script.GetScriptString());

  // Ensure __gCrWeb was injected.
  ASSERT_NSEQ(@(TRUE), web::test::ExecuteJavaScript(
                           web_view,
                           @"try { !!window.__gCrWeb; } catch (err) {false;}"));

  // Store a value within `window.__gCrWeb`.
  web::test::ExecuteJavaScriptInWebView(web_view,
                                        @"window.__gCrWeb.someData = 1;");
  ASSERT_NSEQ(@(1), web::test::ExecuteJavaScript(web_view,
                                                 @"window.__gCrWeb.someData"));

  // Execute feature script again, which should not overwrite window state.
  web::test::ExecuteJavaScriptInWebView(web_view,
                                        feature_script.GetScriptString());
  // The `someData` value should still exist.
  EXPECT_NSEQ(@(1), web::test::ExecuteJavaScript(web_view,
                                                 @"window.__gCrWeb.someData"));
}

// Tests creating a JavaScriptFeature.
TEST_F(JavaScriptFeatureTest, CreateFeature) {
  auto document_start_injection_time =
      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
  auto target_frames_all =
      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
  const web::JavaScriptFeature::FeatureScript feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb", document_start_injection_time, target_frames_all);

  auto any_content_world = web::ContentWorld::kPageContentWorld;
  web::JavaScriptFeature feature(any_content_world, {feature_script});

  EXPECT_EQ(any_content_world, feature.GetSupportedContentWorld());
  EXPECT_EQ(0ul, feature.GetDependentFeatures().size());

  auto feature_scripts = feature.GetScripts();
  ASSERT_EQ(1ul, feature_scripts.size());
  EXPECT_NSEQ(feature_script.GetScriptString(),
              feature_scripts[0].GetScriptString());
}

// Tests creating a JavaScriptFeature which relies on a dependent feature.
TEST_F(JavaScriptFeatureTest, CreateFeatureWithDependentFeature) {
  auto document_start_injection_time =
      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
  auto target_frames_all =
      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
  const web::JavaScriptFeature::FeatureScript dependent_feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb", document_start_injection_time, target_frames_all);

  auto document_end_injection_time =
      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd;
  auto target_frames_main =
      web::JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame;
  const web::JavaScriptFeature::FeatureScript feature_script =
      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
          "gcrweb", document_end_injection_time, target_frames_main);

  auto page_content_world = web::ContentWorld::kPageContentWorld;
  web::JavaScriptFeature dependent_feature(page_content_world,
                                           {dependent_feature_script});
  web::JavaScriptFeature feature(page_content_world, {feature_script},
                                 {&dependent_feature});
  EXPECT_EQ(page_content_world, feature.GetSupportedContentWorld());

  auto feature_scripts = feature.GetScripts();
  ASSERT_EQ(1ul, feature_scripts.size());
  auto dependent_features = feature.GetDependentFeatures();
  ASSERT_EQ(1ul, dependent_features.size());
  auto dependent_feature_scripts = dependent_features[0]->GetScripts();
  ASSERT_EQ(1ul, dependent_feature_scripts.size());
  EXPECT_NSEQ(feature_script.GetScriptString(),
              feature_scripts[0].GetScriptString());
  EXPECT_NSEQ(dependent_feature_script.GetScriptString(),
              dependent_feature_scripts[0].GetScriptString());
}
