chromium/ios/web/js_features/error_page/error_page_java_script_feature.mm

// Copyright 2023 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_features/error_page/error_page_java_script_feature.h"

#import "base/values.h"
#import "ios/web/navigation/crw_error_page_helper.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"

namespace web {

namespace {
// The name of the message handler for messages from the error page. Must be
// kept in sync with components/neterror/resources/error_page_controller_ios.js.
const char kWebUIMessageHandlerName[] = "ErrorPageMessage";

// The NSUserDefault key to store the easter egg game on error page high score.
NSString* const kEasterEggHighScore = @"EasterEggHighScore";
}  // namespace

// static
ErrorPageJavaScriptFeature* ErrorPageJavaScriptFeature::GetInstance() {
  static base::NoDestructor<ErrorPageJavaScriptFeature> instance;
  return instance.get();
}

ErrorPageJavaScriptFeature::ErrorPageJavaScriptFeature()
    // This feature must be in the page content world in order to listen for
    // messages from the Error Page JavaScript.
    : JavaScriptFeature(ContentWorld::kPageContentWorld, {}) {}

ErrorPageJavaScriptFeature::~ErrorPageJavaScriptFeature() = default;

std::optional<std::string>
ErrorPageJavaScriptFeature::GetScriptMessageHandlerName() const {
  return kWebUIMessageHandlerName;
}

void ErrorPageJavaScriptFeature::ScriptMessageReceived(
    WebState* web_state,
    const ScriptMessage& script_message) {
  // WebUI messages are only handled if sent from the main frame.
  if (!script_message.is_main_frame()) {
    return;
  }

  std::optional<GURL> url = script_message.request_url();
  // Messages must be from an error page.
  if (!url || ![CRWErrorPageHelper isErrorPageFileURL:url.value()]) {
    return;
  }

  if (!script_message.body() || !script_message.body()->is_dict()) {
    return;
  }

  const base::Value::Dict& dict = script_message.body()->GetDict();

  const std::string* command = dict.FindString("command");
  if (!command) {
    return;
  }

  if (*command == "updateEasterEggHighScore") {
    const std::string* high_score_string = dict.FindString("highScore");
    if (!high_score_string) {
      return;
    }
    int high_score;
    if (!base::StringToInt(*high_score_string, &high_score)) {
      return;
    }
    [[NSUserDefaults standardUserDefaults] setInteger:high_score
                                               forKey:kEasterEggHighScore];
  } else if (*command == "resetEasterEggHighScore") {
    [[NSUserDefaults standardUserDefaults]
        removeObjectForKey:kEasterEggHighScore];
  } else if (*command == "trackEasterEgg") {
    WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame();
    if (!frame) {
      return;
    }

    int high_score = [[NSUserDefaults standardUserDefaults]
        integerForKey:kEasterEggHighScore];

    auto parameters = base::Value::List().Append(high_score);
    frame->CallJavaScriptFunction(
        "errorPageController.initializeEasterEggHighScore", parameters);
  }
}

}  // namespace web