chromium/ios/web/navigation/navigation_java_script_feature.mm

// 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/navigation/navigation_java_script_feature.h"

#import "base/no_destructor.h"
#import "ios/web/public/js_messaging/java_script_feature_util.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/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"

namespace web {

namespace {

const char kScriptName[] = "navigation";
const char kListenersScriptName[] = "navigation_listeners";
const char kScriptHandlerName[] = "NavigationEventMessage";

}  // namespace

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

NavigationJavaScriptFeature::NavigationJavaScriptFeature()
    : JavaScriptFeature(
          ContentWorld::kPageContentWorld,
          {FeatureScript::CreateWithFilename(
               kScriptName,
               FeatureScript::InjectionTime::kDocumentStart,
               FeatureScript::TargetFrames::kMainFrame,
               FeatureScript::ReinjectionBehavior::kInjectOncePerWindow),
           FeatureScript::CreateWithFilename(
               kListenersScriptName,
               FeatureScript::InjectionTime::kDocumentStart,
               FeatureScript::TargetFrames::kMainFrame,
               FeatureScript::ReinjectionBehavior::
                   kReinjectOnDocumentRecreation)},
          {web::java_script_features::GetCommonJavaScriptFeature(),
           web::java_script_features::GetMessageJavaScriptFeature()}) {}

NavigationJavaScriptFeature::~NavigationJavaScriptFeature() = default;

std::optional<std::string>
NavigationJavaScriptFeature::GetScriptMessageHandlerName() const {
  return kScriptHandlerName;
}

void NavigationJavaScriptFeature::ScriptMessageReceived(
    web::WebState* web_state,
    const web::ScriptMessage& message) {
  if (!message.body()) {
    // Ignore malformed responses.
    return;
  }
  auto* dict = message.body()->GetIfDict();
  if (!dict) {
    // Ignore malformed responses.
    return;
  }

  if (!message.is_main_frame()) {
    return;
  }

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

  const std::string* frame_id = dict->FindString("frame_id");
  if (!frame_id) {
    return;
  }

  WebFrame* main_frame =
      web_state->GetPageWorldWebFramesManager()->GetMainWebFrame();
  std::string main_frame_id = main_frame ? main_frame->GetFrameId() : "";
  if (main_frame_id != *frame_id) {
    // Frame has changed, do not send message to the web controller as it would
    // update the incorrect navigation item.
    return;
  }

  CRWWebController* web_controller =
      WebStateImpl::FromWebState(web_state)->GetWebController();

  if (*command == "hashchange") {
    [web_controller handleNavigationHashChange];
  } else if (*command == "willChangeState") {
    [web_controller handleNavigationWillChangeState];
  } else if (*command == "didPushState") {
    [web_controller handleNavigationDidPushStateMessage:dict];
  } else if (*command == "didReplaceState") {
    [web_controller handleNavigationDidReplaceStateMessage:dict];
  }
}

}  // namespace web