// 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 "components/autofill/ios/form_util/form_activity_tab_helper.h"

#import "base/logging.h"
#import "base/memory/raw_ptr.h"
#import "base/rand_util.h"
#import "base/test/ios/wait_util.h"
#import "ios/web/public/js_messaging/fuzzer_support/fuzzer_util.h"
#import "ios/web/public/js_messaging/fuzzer_support/js_message.pb.h"
#import "ios/web/public/js_messaging/script_message.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/fuzzer_env_with_web_state.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/web_state.h"
#import "testing/libfuzzer/proto/lpm_interface.h"

using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace {

web::WebFrame* WaitForMainFrame(web::WebState* web_state) {
  __block web::WebFrame* main_frame = nullptr;
  DCHECK(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    main_frame = web_state->GetPageWorldWebFramesManager()->GetMainWebFrame();
    return main_frame != nullptr;
  }));
  return main_frame;
}

class Env : public web::FuzzerEnvWithWebState {
 public:
  Env() {
    // To create a main web frame.
    web::test::LoadHtml(@"<p>", web_state());
    web::WebFrame* main_frame = WaitForMainFrame(web_state());
    if (!main_frame) {
      LOG(ERROR) << "No main frame was created!";
    }
    main_frame_id_ = main_frame->GetFrameId();
    tab_helper_ =
        autofill::FormActivityTabHelper::GetOrCreateForWebState(web_state());
  }
  // The object will be deconstructed at deconstructing the WebState.
  raw_ptr<autofill::FormActivityTabHelper> tab_helper_;
  std::string main_frame_id_;
};

}  // namespace

DEFINE_PROTO_FUZZER(const web::ScriptMessageProto& proto_js_message) {
  static Env env;

  std::unique_ptr<web::ScriptMessage> script_message =
      web::fuzzer::ProtoToScriptMessage(proto_js_message);

  // Insert the |frameID| of main frame in the |WebState| as initialized at
  // creating |Env|. This is because if the |frameID| in |ScriptMessage| is
  // invalid, the fuzzed API will return early and skip most of the interesting
  // logic.
  if (script_message->legacy_body() &&
      script_message->legacy_body()->is_dict()) {
    // Insert the |frameID| at 98% probability. We still want to check how API
    // behaves at an invalid |frameID|.
    if (base::RandDouble() < 0.98) {
      script_message->legacy_body()->GetDict().Set("frameID",
                                                   env.main_frame_id_);
    }
  }

  // The actual API being fuzzed.
  env.tab_helper_->OnFormMessageReceived(env.web_state(), *script_message);
}
