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

#include "extensions/browser/api/messaging/message_service.h"

#include <stdint.h>

#include <algorithm>
#include <limits>
#include <memory>
#include <utility>
#include <variant>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/api/messaging/extension_message_port.h"
#include "extensions/browser/api/messaging/message_port.h"
#include "extensions/browser/api/messaging/messaging_delegate.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/message_service_api.h"
#include "extensions/browser/message_tracker.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/api/messaging/port_context.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/externally_connectable.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/mojom/message_port.mojom-shared.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ipc/constants.mojom.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "url/gurl.h"

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
#include "cef/ohos_cef_ext/libcef/browser/chrome/extensions/arkweb_chrome_extension_util_ext.h"
#endif // ARKWEB_ARKWEB_EXTENSIONS

#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#endif

using content::BrowserContext;
using content::BrowserThread;
using content::SiteInstance;
using content::WebContents;

namespace extensions {

namespace {

const char kReceivingEndDoesntExistError[] =
    "Could not establish connection. Receiving end does not exist.";
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_OHOS)
const char kMissingPermissionError[] =
    "Access to native messaging requires nativeMessaging permission.";
const char kProhibitedByPoliciesError[] =
    "Access to the native messaging host was disabled by the system "
    "administrator.";
#endif

LazyContextId LazyContextIdFor(content::BrowserContext* browser_context,
                               const Extension* extension) {
  return LazyContextId::ForExtension(browser_context, extension);
}

const Extension* GetExtensionForNativeAppChannel(
    const ChannelEndpoint& source) {
  DCHECK(!source.is_for_native_host());

  if (source.is_for_service_worker()) {
    const ExtensionId& extension_id =
        source.port_context().worker->extension_id;
    return ExtensionRegistry::Get(source.browser_context())
        ->enabled_extensions()
        .GetByID(extension_id);
  }

  DCHECK(source.is_for_render_frame());
  content::RenderFrameHost* source_render_frame_host =
      source.GetRenderFrameHost();
  if (!source_render_frame_host) {
    return nullptr;
  }
  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(source_render_frame_host);
  if (!web_contents)
    return nullptr;
  ExtensionWebContentsObserver* extension_web_contents_observer =
      ExtensionWebContentsObserver::GetForWebContents(web_contents);
  if (!extension_web_contents_observer)
    return nullptr;
  return extension_web_contents_observer->GetExtensionFromFrame(
      source_render_frame_host, true);
}

// Disables the back forward for `host` if the current configuration does not
// support extension messaging APIs.
void MaybeDisableBackForwardCacheForMessaging(content::RenderFrameHost* host) {
  if (!host || content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
    return;
  }
  content::BackForwardCache::DisableForRenderFrameHost(
      host, back_forward_cache::DisabledReason(
                back_forward_cache::DisabledReasonId::kExtensionMessaging));
}

// Default apps that grant extended lifetime when connecting to them with a
// persistent port connection, until the channel is closed. These values extend
// the values of the
// ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls policy. They
// are chosen as defaults because they are known apps that offer SDKs and do not
// offer the possibility to restart a closed connection to a previous state.
constexpr const char* kDefaultSWExtendedLifetimeList[] = {
    // Smart Card Connector
    "chrome-extension://khpfeaanjngmcnplbdlpegiifgpfgdco/",  // stable
    "chrome-extension://mockcojkppdndnhgonljagclgpkjbkek/",  // beta

    // Citrix Receiver
    "chrome-extension://haiffjcadagjlijoggckpgfnoeiflnem/",  // stable
    "chrome-extension://lbfgjakkeeccemhonnolnmglmfmccaag/",  // beta
    "chrome-extension://anjihnbmjbbpofafpmklejenkgnjfcdi/",  // back-up

    // VMware Horizon
    "chrome-extension://ppkfnjlimknmjoaemnpidmdlfchhehel/",  // stable
    "chrome-extension://kenkpdjcfppbccchillfdjkjnejjgand/",  // beta
};

std::vector<url::Origin> GetServiceWorkerExtendedLifetimeOrigins(
    content::BrowserContext* browser_context) {
  std::vector<url::Origin> origins;
  const base::Value::List& extended_lifetime_urls =
      ExtensionPrefs::Get(browser_context)
          ->pref_service()
          ->GetList(
              pref_names::kExtendedBackgroundLifetimeForPortConnectionsToUrls);
  origins.reserve(std::size(kDefaultSWExtendedLifetimeList) +
                  extended_lifetime_urls.size());

  // Add default values.
  for (const char* default_value : kDefaultSWExtendedLifetimeList) {
    url::Origin origin = url::Origin::Create(GURL(default_value));
    origins.push_back(std::move(origin));
  }

  // Add policy values.
  for (const base::Value& value : extended_lifetime_urls) {
    GURL url(value.GetString());
    if (!url.is_valid()) {
      continue;
    }
    url::Origin origin = url::Origin::Create(url);
    if (origin.opaque()) {
      continue;
    }
    origins.push_back(std::move(origin));
  }

  return origins;
}

// Verifies (via CHECK()) that the lazy context for the given `extension` is
// currently active and registered in the ProcessManager.
void VerifyLazyContextActive(
    const Extension* extension,
    const LazyContextTaskQueue::ContextInfo& context_info) {
  CHECK(extension);
  ProcessManager* process_manager =
      ProcessManager::Get(context_info.browser_context);
  if (BackgroundInfo::IsServiceWorkerBased(extension)) {
    std::vector<WorkerId> service_workers =
        process_manager->GetServiceWorkersForExtension(extension->id());
    CHECK(!service_workers.empty());
    CHECK(std::ranges::find_if(
              service_workers, [&context_info](const WorkerId& worker_id) {
                return worker_id.version_id ==
                           context_info.service_worker_version_id &&
                       worker_id.thread_id == context_info.worker_thread_id;
              }) != service_workers.end());
  } else {
    CHECK(BackgroundInfo::HasLazyBackgroundPage(extension));
    CHECK(process_manager->GetBackgroundHostForExtension(extension->id()));
  }
}

}  // namespace

struct MessageService::MessageChannel {
  std::unique_ptr<MessagePort> opener;
  std::unique_ptr<MessagePort> receiver;
};

struct MessageService::OpenChannelParams {
  ChannelEndpoint source;
  std::optional<base::Value::Dict> source_tab;
  ExtensionApiFrameIdMap::FrameData source_frame;
  std::unique_ptr<MessagePort> receiver;
  PortId receiver_port_id;
  MessagingEndpoint source_endpoint;
  std::unique_ptr<MessagePort> opener_port;
  std::string target_extension_id;
  GURL source_url;
  std::optional<url::Origin> source_origin;
  mojom::ChannelType channel_type;
  std::string channel_name;
  bool include_guest_process_info;
  std::set<base::UnguessableToken> open_channel_tracking_ids;

  // Takes ownership of receiver.
  OpenChannelParams(const ChannelEndpoint& source,
                    std::optional<base::Value::Dict> source_tab,
                    const ExtensionApiFrameIdMap::FrameData& source_frame,
                    MessagePort* receiver,
                    const PortId& receiver_port_id,
                    const MessagingEndpoint& source_endpoint,
                    std::unique_ptr<MessagePort> opener_port,
                    const std::string& target_extension_id,
                    const GURL& source_url,
                    std::optional<url::Origin> source_origin,
                    mojom::ChannelType channel_type,
                    const std::string& channel_name,
                    bool include_guest_process_info)
      : OpenChannelParams(source,
                          std::move(source_tab),
                          source_frame,
                          receiver,
                          receiver_port_id,
                          source_endpoint,
                          std::move(opener_port),
                          target_extension_id,
                          source_url,
                          source_origin,
                          channel_type,
                          channel_name,
                          include_guest_process_info,
                          /*open_channel_tracking_ids=*/{}) {}
  OpenChannelParams(const ChannelEndpoint& source,
                    std::optional<base::Value::Dict> source_tab,
                    const ExtensionApiFrameIdMap::FrameData& source_frame,
                    MessagePort* receiver,
                    const PortId& receiver_port_id,
                    const MessagingEndpoint& source_endpoint,
                    std::unique_ptr<MessagePort> opener_port,
                    const std::string& target_extension_id,
                    const GURL& source_url,
                    std::optional<url::Origin> source_origin,
                    mojom::ChannelType channel_type,
                    const std::string& channel_name,
                    bool include_guest_process_info,
                    std::set<base::UnguessableToken> open_channel_tracking_ids)
      : source(source),
        source_tab(std::move(source_tab)),
        source_frame(source_frame),
        receiver(receiver),
        receiver_port_id(receiver_port_id),
        source_endpoint(source_endpoint),
        opener_port(std::move(opener_port)),
        target_extension_id(target_extension_id),
        source_url(source_url),
        source_origin(source_origin),
        channel_type(channel_type),
        channel_name(channel_name),
        include_guest_process_info(include_guest_process_info),
        open_channel_tracking_ids(std::move(open_channel_tracking_ids)) {}

  OpenChannelParams(const OpenChannelParams&) = delete;
  OpenChannelParams& operator=(const OpenChannelParams&) = delete;

  bool is_onetime_channel() const {
    return channel_type == mojom::ChannelType::kSendMessage ||
           channel_type == mojom::ChannelType::kSendRequest;
  }
};

MessageService::MessageService(BrowserContext* context)
    : context_(context),
      messaging_delegate_(ExtensionsAPIClient::Get()->GetMessagingDelegate()) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_NE(nullptr, messaging_delegate_);
}

MessageService::~MessageService() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  channels_.clear();
}

class MessageServiceFactory
    : public BrowserContextKeyedAPIFactory<MessageService>,
      public MessageServiceApi {
 public:
  MessageServiceFactory() {
    DependsOn(ProcessManagerFactory::GetInstance());
    DependsOn(ExtensionPrefsFactory::GetInstance());
    DependsOn(ExtensionRegistryFactory::GetInstance());

    MessageServiceApi::SetMessageService(this);
  }
  ~MessageServiceFactory() override {
    MessageServiceApi::SetMessageService(nullptr);
  }

  void OpenChannelToExtension(
      content::BrowserContext* context,
      Source source,
      const PortId& source_port_id,
      const ExternalConnectionInfo& info,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
          port_host) override {
    MessageService::Get(context)->OpenChannelToExtension(
        GetEndpoint(context, source), source_port_id, info, channel_type,
        channel_name, std::move(port), std::move(port_host));
  }

  void OpenChannelToNativeApp(
      content::BrowserContext* context,
      Source source,
      const PortId& source_port_id,
      const std::string& native_app_name,
      mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
          port_host) override {
    MessageService::Get(context)->OpenChannelToNativeApp(
        GetEndpoint(context, source), source_port_id, native_app_name,
        std::move(port), std::move(port_host));
  }

  void OpenChannelToTab(
      content::BrowserContext* context,
      Source source,
      const PortId& source_port_id,
      int tab_id,
      int frame_id,
      const std::string& document_id,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
          port_host) override {
    MessageService::Get(context)->OpenChannelToTab(
        GetEndpoint(context, source), source_port_id, tab_id, frame_id,
        document_id, channel_type, channel_name, std::move(port),
        std::move(port_host));
  }

 private:
  ChannelEndpoint GetEndpoint(content::BrowserContext* context,
                              const Source& source) {
    return std::visit(
        absl::Overload{
            [&](const WorkerId& worker) {
              return ChannelEndpoint(
                  context, worker.render_process_id,
                  PortContext::ForWorker(worker.thread_id, worker.version_id,
                                         worker.render_process_id,
                                         worker.extension_id));
            },
            [&](const content::RenderFrameHost* render_frame_host) {
              return ChannelEndpoint(
                  context, render_frame_host->GetProcess()->GetDeprecatedID(),
                  PortContext::ForFrame(render_frame_host->GetRoutingID()));
            }},
        source);
  }
};

static base::LazyInstance<MessageServiceFactory>::DestructorAtExit g_factory =
    LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<MessageService>*
MessageService::GetFactoryInstance() {
  return g_factory.Pointer();
}

// static
MessageService* MessageService::Get(BrowserContext* context) {
  return BrowserContextKeyedAPIFactory<MessageService>::Get(context);
}

void MessageService::OpenChannelToExtension(
    const ChannelEndpoint& source,
    const PortId& source_port_id,
    const MessagingEndpoint& source_endpoint,
    std::unique_ptr<MessagePort> opener_port,
    const std::string& target_extension_id,
    const GURL& source_url,
    mojom::ChannelType channel_type,
    const std::string& channel_name) {
  // Overall open channel metrics tracking.
  std::set<base::UnguessableToken> open_channel_tracking_ids;
  base::UnguessableToken open_channel_tracking_id(
      base::UnguessableToken::Create());
  open_channel_tracking_ids.insert(open_channel_tracking_id);
  auto* message_tracker = MessageTracker::Get(context_);
  message_tracker->StartTrackingMessagingStage(
      open_channel_tracking_id, "Extensions.MessagePipeline.OpenChannelStatus",
      channel_type);

  BrowserContext* context = source.browser_context();
  ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(source_port_id.is_opener);
  DCHECK(!target_extension_id.empty());
  DCHECK(source_endpoint.extension_id.has_value() ||
         source_endpoint.type == MessagingEndpoint::Type::kWebPage ||
         source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
  DCHECK_EQ(source_endpoint.native_app_name.has_value(),
            source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
  int source_process_id = source.render_process_id();
  DCHECK_EQ(source_process_id == content::ChildProcessHost::kInvalidUniqueID,
            source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
  content::RenderFrameHost* source_render_frame_host =
      source.is_for_render_frame() ? source.GetRenderFrameHost() : nullptr;
  if (!source.IsValid()) {
    for (const auto& tracking_id : open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOpenChannelSourceEndpointInvalid);
    }
    return;
  }
  DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(context, context_));

  MaybeDisableBackForwardCacheForMessaging(source_render_frame_host);

  CHECK(opener_port);
  if (!opener_port->IsValidPort()) {
    for (const auto& tracking_id : open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOpenChannelOpenerPortInvalid);
    }
    return;
  }

  const Extension* target_extension =
      registry->enabled_extensions().GetByID(target_extension_id);

  if (!target_extension) {
    opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    for (const auto& tracking_id : open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOpenChannelToNonEnabledExtension);
    }
    return;
  }

  // Worker specific open channel metrics tracking.
  std::string worker_status_metric_suffix;
  if (BackgroundInfo::IsServiceWorkerBased(target_extension)) {
    if (!ProcessManager::Get(context)
             ->GetServiceWorkersForExtension(target_extension_id)
             .empty()) {
      worker_status_metric_suffix = "WithActiveWorker";
    } else {
      worker_status_metric_suffix = "WithIdleWorker";
    }
  } else {
    worker_status_metric_suffix = "WithNoWorker";
  }
  std::string worker_status_metric_name =
      base::StrCat({"Extensions.MessagePipeline.OpenChannelStatus",
                    worker_status_metric_suffix});
  base::UnguessableToken open_channel_worker_status_tracking_id(
      base::UnguessableToken::Create());
  open_channel_tracking_ids.insert(open_channel_worker_status_tracking_id);
  message_tracker->StartTrackingMessagingStage(
      open_channel_worker_status_tracking_id, worker_status_metric_name,
      channel_type);

  MessagingEndpoint::Relationship relationship =
      MessagingEndpoint::GetRelationship(source_endpoint, target_extension_id);

  if (relationship != MessagingEndpoint::Relationship::kInternal &&
      relationship != MessagingEndpoint::Relationship::kExternalNativeApp) {
    // It's an external connection (other than a native app). Check the
    // externally_connectable manifest key if it's present. If it's not, we
    // allow connection from any extension but not webpages.
    // TODO(devlin): We should just use ExternallyConnectableInfo::Get() here.
    // We don't currently because we don't synthesize externally-connectable
    // information (so that it's always present, even for extensions that don't
    // have an explicit key); we should.
    ExternallyConnectableInfo* externally_connectable =
        static_cast<ExternallyConnectableInfo*>(
            target_extension->GetManifestData(
                manifest_keys::kExternallyConnectable));
    bool is_externally_connectable = false;

    if (externally_connectable) {
      if (relationship == MessagingEndpoint::Relationship::kExternalExtension) {
        DCHECK(source_endpoint.extension_id);
        // The source was another extension or a content script. Check that the
        // extension ID matches.
        is_externally_connectable =
            externally_connectable->IdCanConnect(*source_endpoint.extension_id);
      } else {
        DCHECK(source_render_frame_host);
        DCHECK_EQ(MessagingEndpoint::Relationship::kExternalWebPage,
                  relationship);

        // Check that the web page URL matches.
        is_externally_connectable = externally_connectable->matches.MatchesURL(
            source_render_frame_host->GetLastCommittedURL());
      }
    } else {
      // Default behaviour. Any extension or content script, no webpages.
      is_externally_connectable =
          relationship == MessagingEndpoint::Relationship::kExternalExtension;
    }

    if (!is_externally_connectable) {
      // Important: use kReceivingEndDoesntExistError here so that we don't
      // leak information about this extension to callers. This way it's
      // indistinguishable from the extension just not existing.
      opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
      for (const auto& tracking_id : open_channel_tracking_ids) {
        message_tracker->StopTrackingMessagingStage(
            tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                             kNotExternallyConnectable);
      }
      return;
    }
  }

  WebContents* source_contents = nullptr;
  if (source_render_frame_host) {
    source_contents =
        WebContents::FromRenderFrameHost(source_render_frame_host);
  }

  ExtensionApiFrameIdMap::FrameData source_frame;
  bool include_guest_process_info = false;

  // Get information about the opener's tab, if applicable.
  std::optional<base::Value::Dict> source_tab =
      messaging_delegate_->MaybeGetTabInfo(source_contents);

  std::optional<url::Origin> source_origin;
  if (source_render_frame_host)
    source_origin = source_render_frame_host->GetLastCommittedOrigin();

  if (source_tab) {
    DCHECK(source_render_frame_host);
    source_frame = ExtensionApiFrameIdMap::Get()->GetFrameData(
        source_render_frame_host->GetGlobalId());
  } else {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
    // Check to see if it was a WebView making the request.
    // Sending messages from WebViews to extensions breaks webview isolation,
    // so only allow component extensions to receive messages from WebViews.
    bool is_web_view =
        !!WebViewGuest::FromRenderFrameHost(source_render_frame_host);
    if (is_web_view &&
        Manifest::IsComponentLocation(target_extension->location())) {
      include_guest_process_info = true;
    }
#endif
  }

  std::unique_ptr<OpenChannelParams> params =
      std::make_unique<OpenChannelParams>(
          source, std::move(source_tab), source_frame, nullptr,
          source_port_id.GetOppositePortId(), source_endpoint,
          std::move(opener_port), target_extension_id, source_url,
          std::move(source_origin), channel_type, channel_name,
          include_guest_process_info, std::move(open_channel_tracking_ids));
  pending_incognito_channels_[params->receiver_port_id.GetChannelId()] =
      PendingMessagesQueue();
  if (context->IsOffTheRecord() &&
      !util::IsIncognitoEnabled(target_extension_id, context)) {
    // Give the user a chance to accept an incognito connection from the web if
    // they haven't already, with the conditions:
    // - Only for non-split mode incognito. We don't want the complication of
    //   spinning up an additional process here which might need to do some
    //   setup that we're not expecting.
    // - Only for extensions that can't normally be enabled in incognito, since
    //   that surface (e.g. chrome://extensions) should be the only one for
    //   enabling in incognito. In practice this means platform apps only.
    if (relationship != MessagingEndpoint::Relationship::kExternalWebPage ||
        IncognitoInfo::IsSplitMode(target_extension) ||
        util::CanBeIncognitoEnabled(target_extension)) {
      OnOpenChannelAllowed(std::move(params), false);
      return;
    }

    // If the target extension isn't even listening for connect/message events,
    // there is no need to go any further and the connection should be
    // rejected without showing a prompt. See http://crbug.com/442497
    EventRouter* event_router = EventRouter::Get(context);
    const char* const events[] = {
        "runtime.onConnectExternal",
        "runtime.onConnectNative",
        "runtime.onMessageExternal",
        "extension.onRequestExternal",
    };
    const bool has_event_listener =
        std::ranges::any_of(events, [&](const char* event) {
          return event_router->ExtensionHasEventListener(target_extension_id,
                                                         event);
        });
    if (!has_event_listener) {
      OnOpenChannelAllowed(std::move(params), false);
      return;
    }

    // This check may show a dialog.
    messaging_delegate_->QueryIncognitoConnectability(
        context, target_extension, source_contents, source_url,
        base::BindOnce(&MessageService::OnOpenChannelAllowed,
                       weak_factory_.GetWeakPtr(), std::move(params)));
    return;
  }

  OnOpenChannelAllowed(std::move(params), true);
}

void MessageService::OpenChannelToNativeAppImpl(
    const ChannelEndpoint& source,
    const PortId& source_port_id,
    const std::string& native_app_name,
    mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
    mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
        port_host) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(source_port_id.is_opener);

  if (!source.IsValid())
    return;
  const Extension* extension = GetExtensionForNativeAppChannel(source);

  if (!extension)
    return;

  std::unique_ptr<ExtensionMessagePort> opener_port =
      ExtensionMessagePort::CreateForEndpoint(
          weak_factory_.GetWeakPtr(), source_port_id, extension->id(), source,
          std::move(port), std::move(port_host));
  if (!opener_port->IsValidPort())
    return;

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_OHOS)
  bool has_permission = extension->permissions_data()->HasAPIPermission(
      mojom::APIPermissionID::kNativeMessaging);
  if (!has_permission) {
    opener_port->DispatchOnDisconnect(kMissingPermissionError);
    return;
  }

  // Verify that the host is not blocked by policies.
  BrowserContext* source_context = source.browser_context();
  DCHECK(
      ExtensionsBrowserClient::Get()->IsSameContext(source_context, context_));
  MessagingDelegate::PolicyPermission policy_permission =
      messaging_delegate_->IsNativeMessagingHostAllowed(source_context,
                                                        native_app_name);
  if (policy_permission == MessagingDelegate::PolicyPermission::DISALLOW) {
    opener_port->DispatchOnDisconnect(kProhibitedByPoliciesError);
    return;
  }

  std::unique_ptr<MessageChannel> channel = std::make_unique<MessageChannel>();
  channel->opener = std::move(opener_port);
  channel->opener->OpenPort(source.render_process_id(), source.port_context());

  content::RenderFrameHost* source_render_frame_host =
      source.is_for_render_frame() ? source.GetRenderFrameHost() : nullptr;

  MaybeDisableBackForwardCacheForMessaging(source_render_frame_host);

  std::string error = kReceivingEndDoesntExistError;
  const PortId receiver_port_id = source_port_id.GetOppositePortId();
  // NOTE: We're creating |receiver| with nullptr |source_render_frame_host|,
  // which seems to work for native messaging tests. This might need further
  // checking in case any issues arise from it.
  std::unique_ptr<MessagePort> receiver(
      messaging_delegate_->CreateReceiverForNativeApp(
          context_, weak_factory_.GetWeakPtr(), source_render_frame_host,
          extension->id(), receiver_port_id, native_app_name,
          policy_permission == MessagingDelegate::PolicyPermission::ALLOW_ALL,
          &error));

  if (!receiver.get()) {
    // Abandon the channel.
    channel->opener->DispatchOnDisconnect(error);
    return;
  }

  channel->receiver = std::move(receiver);

  // Keep the opener alive until the channel is closed.
  channel->opener->set_should_have_strong_keepalive(true);
  channel->opener->IncrementLazyKeepaliveCount(Activity::MESSAGE_PORT);

  AddChannel(std::move(channel), receiver_port_id);
#else   // !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID))
  const char kNativeMessagingNotSupportedError[] =
      "Native Messaging is not supported on this platform.";
  opener_port->DispatchOnDisconnect(kNativeMessagingNotSupportedError);
#endif  // !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
}

void MessageService::OpenChannelToTabImpl(
    const ChannelEndpoint& source,
    const PortId& source_port_id,
    int tab_id,
    int frame_id,
    const std::string& document_id,
    const ExtensionId& extension_id,
    mojom::ChannelType channel_type,
    const std::string& channel_name,
    mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
    mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
        port_host) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_GE(frame_id, -1);
  DCHECK(source_port_id.is_opener);

  // RenderFrameHost or the worker thread might be gone.
  if (!source.IsValid())
    return;

  std::unique_ptr<ExtensionMessagePort> opener_port =
      ExtensionMessagePort::CreateForEndpoint(
          weak_factory_.GetWeakPtr(), source_port_id, extension_id, source,
          std::move(port), std::move(port_host));
  if (!opener_port->IsValidPort())
    return;

  BrowserContext* source_context = source.browser_context();
  DCHECK(
      ExtensionsBrowserClient::Get()->IsSameContext(source_context, context_));
  content::WebContents* receiver_contents =
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
      cef::GetWebContentByTabId(tab_id);
#else
      messaging_delegate_->GetWebContentsByTabId(source_context, tab_id);
#endif // ARKWEB_ARKWEB_EXTENSIONS
  if (!receiver_contents || receiver_contents->GetController().NeedsReload()) {
    // The tab isn't loaded yet. Don't attempt to connect.
    opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    return;
  }

  MaybeDisableBackForwardCacheForMessaging(
      receiver_contents->GetPrimaryMainFrame());

  const PortId receiver_port_id = source_port_id.GetOppositePortId();
  std::unique_ptr<MessagePort> receiver = CreateReceiverForTab(
      extension_id, receiver_port_id, receiver_contents, frame_id, document_id);
  if (!receiver.get()) {
    opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    return;
  }

  const Extension* extension = nullptr;
  if (!extension_id.empty()) {
    // Source extension == target extension so the extension must exist, or
    // where did the IPC come from?
    extension = ExtensionRegistry::Get(source_context)
                    ->enabled_extensions()
                    .GetByID(extension_id);
    DCHECK(extension);
  }

  // `ValidateSourceContextAndExtractExtensionId` is called before coming here.
  // Therefore, possible origins are either an extension origin or an opaque
  // origin created by an extension. See https://crbug.com/1407087.
  url::Origin source_origin = url::Origin();
  GURL source_url;
  if (source.is_for_render_frame()) {
    source_origin = source.GetRenderFrameHost()->GetLastCommittedOrigin();
    source_url = source.GetRenderFrameHost()->GetLastCommittedURL();
  } else if (source.is_for_service_worker() && extension) {
    source_origin = extension->origin();
    source_url = BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension);
  }

  BrowserContext* receiver_context = receiver_contents->GetBrowserContext();
  DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(receiver_context,
                                                       context_));
  std::unique_ptr<OpenChannelParams> params =
      std::make_unique<OpenChannelParams>(
          source,
          std::nullopt,  // No source_tab, as there is no frame.
          ExtensionApiFrameIdMap::FrameData(), receiver.release(),
          receiver_port_id, MessagingEndpoint::ForExtension(extension_id),
          std::move(opener_port), extension_id, source_url, source_origin,
          channel_type, channel_name,
          false);  // Connections to tabs aren't webview guests.
  OpenChannelImpl(receiver_context, std::move(params), extension,
                  false /* did_enqueue */);
}

std::unique_ptr<MessagePort> MessageService::CreateReceiverForTab(
    const ExtensionId& extension_id,
    const PortId& receiver_port_id,
    content::WebContents* receiver_contents,
    int receiver_frame_id,
    const std::string& receiver_document_id) {
  // Frame ID -1 is every frame in the tab.
  bool include_child_frames =
      receiver_frame_id == -1 && receiver_document_id.empty();

  content::RenderFrameHost* receiver_render_frame_host = nullptr;
  if (include_child_frames) {
    // The target is the active outermost main frame of the WebContents.
    receiver_render_frame_host = receiver_contents->GetPrimaryMainFrame();
  } else if (!receiver_document_id.empty()) {
    ExtensionApiFrameIdMap::DocumentId document_id =
        ExtensionApiFrameIdMap::DocumentIdFromString(receiver_document_id);

    // Return early for invalid documentIds.
    if (!document_id) {
      return nullptr;
    }

    receiver_render_frame_host =
        ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
            document_id);

    // If both |document_id| and |receiver_frame_id| are provided they
    // should find the same RenderFrameHost, if not return early.
    if (receiver_frame_id != -1 &&
        ExtensionApiFrameIdMap::GetRenderFrameHostById(receiver_contents,
                                                       receiver_frame_id) !=
            receiver_render_frame_host) {
      return nullptr;
    }
  } else {
    DCHECK_GT(receiver_frame_id, -1);
    receiver_render_frame_host = ExtensionApiFrameIdMap::GetRenderFrameHostById(
        receiver_contents, receiver_frame_id);
  }
  if (!receiver_render_frame_host) {
    return nullptr;
  }

  return ExtensionMessagePort::CreateForTab(
      weak_factory_.GetWeakPtr(), receiver_port_id, extension_id,
      receiver_render_frame_host, include_child_frames);
}

void MessageService::OpenChannelImpl(BrowserContext* browser_context,
                                     std::unique_ptr<OpenChannelParams> params,
                                     const Extension* target_extension,
                                     bool did_enqueue) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(
      ExtensionsBrowserClient::Get()->IsSameContext(browser_context, context_));
  // If `did_enqueue` is true (we waited for a lazy context), `target_extension`
  // might be null if the context failed to load or the extension was unloaded
  // while waiting, even if `target_extension_id` is set.
  if (!did_enqueue) {
    DCHECK_EQ(target_extension != nullptr,
              !params->target_extension_id.empty());
  }

  // Check whether the source got closed while in flight.
  const ChannelEndpoint& source = params->source;

  bool will_open_channel = true;
  // Default failure reason.
  auto failure_reason =
      MessageTracker::OpenChannelMessagePipelineResult::kWillNotOpenChannel;

  if (!source.IsValid()) {
    // Closed while in flight.
    will_open_channel = false;
  } else if (!params->opener_port || !params->opener_port->IsValidPort()) {
    // Check for null or invalid opener port.
    will_open_channel = false;
  } else if (!params->receiver || !params->receiver->IsValidPort()) {
    // Check for null or invalid receiver.
    will_open_channel = false;
    params->opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);

    // If we enqueued this channel (waiting for a lazy context) and the receiver
    // is now missing or invalid, it means the context likely failed to load
    // or the extension was unloaded.
    if (did_enqueue) {
      failure_reason =
          MessageTracker::OpenChannelMessagePipelineResult::kNoReceivers;
    }
  }

  auto* message_tracker = MessageTracker::Get(context_);

  if (!will_open_channel) {
    // The channel won't open. If this was a pending channel, remove it,
    // because now it will never open. This prevents the pending message
    // from being re-added indefinitely. See https://crbug.com/1231683.
    // TODO(crbug.com/40821724): This probably isn't the best solution.
    // Ideally, we should close the channel before we get to this point
    // if there's no chance it will ever open, remove it from pending
    // channels, and then only try to open the pending channel if it's
    // still valid.
    pending_lazy_context_channels_.erase(
        params->receiver_port_id.GetChannelId());
    for (const auto& tracking_ids : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(tracking_ids, failure_reason);
    }
    return;
  }

  const PortContext& port_context = source.port_context();
  params->opener_port->OpenPort(source.render_process_id(), port_context);
  params->opener_port->RevalidatePort();
  // TODO(richardzh) Move this property setting to port creation, or params
  // creation, to have cleaner code here.
  params->opener_port->set_is_for_onetime_channel(params->is_onetime_channel());

  params->receiver->RemoveCommonFrames(*params->opener_port);
  if (!params->receiver->IsValidPort()) {
    params->opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    pending_lazy_context_channels_.erase(
        params->receiver_port_id.GetChannelId());
    for (const auto& tracking_ids : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_ids, MessageTracker::OpenChannelMessagePipelineResult::
                            kOpenChannelReceiverInvalidPort);
    }
    return;
  }
  params->receiver->set_is_for_onetime_channel(params->is_onetime_channel());

  std::unique_ptr<MessageChannel> channel_ptr =
      std::make_unique<MessageChannel>();
  MessageChannel* channel = channel_ptr.get();
  channel->opener = std::move(params->opener_port);
  channel->receiver = std::move(params->receiver);
  AddChannel(std::move(channel_ptr), params->receiver_port_id);

  int guest_process_id = content::ChildProcessHost::kInvalidUniqueID;
  int guest_render_frame_routing_id = IPC::mojom::kRoutingIdNone;
#if BUILDFLAG(ENABLE_GUEST_VIEW)
  if (params->include_guest_process_info &&
      // TODO(lazyboy): Investigate <webview> SW messaging.
      source.is_for_render_frame()) {
    guest_process_id = params->source.render_process_id();
    DCHECK(port_context.frame);
    guest_render_frame_routing_id = port_context.frame->routing_id;

    DCHECK(WebViewGuest::FromRenderFrameHost(source.GetRenderFrameHost()));
  }
#endif

  // Send the connect event to the receiver.  Give it the opener's port ID (the
  // opener has the opposite port ID).
  channel->receiver->DispatchOnConnect(
      params->channel_type, params->channel_name, std::move(params->source_tab),
      params->source_frame, guest_process_id, guest_render_frame_routing_id,
      params->source_endpoint, params->target_extension_id, params->source_url,
      params->source_origin, params->open_channel_tracking_ids);

  // Report the event to the event router, if the target is an extension.
  //
  // First, determine what event this will be (runtime.onConnect vs
  // runtime.onMessage etc), and what the event target is (view vs background
  // page etc).
  //
  // Yes - even though this is opening a channel, they may actually be
  // runtime.onRequest/onMessage events because those single-use events are
  // built using the connect framework (see messaging.js).
  if (target_extension) {
    events::HistogramValue histogram_value = events::UNKNOWN;
    bool is_external = MessagingEndpoint::IsExternal(
        params->source_endpoint, params->target_extension_id);

    if (params->source_endpoint.type == MessagingEndpoint::Type::kNativeApp) {
      histogram_value = events::RUNTIME_ON_CONNECT_NATIVE;
    } else if (params->channel_name == "chrome.runtime.onRequest") {
      histogram_value = is_external ? events::RUNTIME_ON_REQUEST_EXTERNAL
                                    : events::RUNTIME_ON_REQUEST;
    } else if (params->channel_name == "chrome.runtime.onMessage") {
      histogram_value = is_external ? events::RUNTIME_ON_MESSAGE_EXTERNAL
                                    : events::RUNTIME_ON_MESSAGE;
    } else {
      histogram_value = is_external ? events::RUNTIME_ON_CONNECT_EXTERNAL
                                    : events::RUNTIME_ON_CONNECT;
    }
    EventRouter::Get(browser_context)
        ->ReportEvent(histogram_value, target_extension, did_enqueue);
  }

  // Check source and target of the connection and grant extended lifetime if
  // one of the ends is configured in the
  // ExtendedBackgroundLifetimeForPortConnectionsToUrls policy.
  std::vector<url::Origin> extended_lifetime_origins =
      GetServiceWorkerExtendedLifetimeOrigins(browser_context);
  url::Origin source_origin = url::Origin::Create(params->source_url);
  url::Origin target_origin =
      Extension::CreateOriginFromExtensionId(params->target_extension_id);

  for (const url::Origin& origin : extended_lifetime_origins) {
    if (origin == source_origin) {
      // Opener found in allowlist, keep receiver SW alive.
      channel->receiver->set_should_have_strong_keepalive(true);
    }
    if (origin == target_origin) {
      // Receiver found in allowlist, keep opener SW alive.
      channel->opener->set_should_have_strong_keepalive(true);
    }
  }

  // Keep both ends of the channel alive until the channel is closed.
  channel->opener->IncrementLazyKeepaliveCount(Activity::MESSAGE_PORT);
  channel->receiver->IncrementLazyKeepaliveCount(Activity::MESSAGE_PORT);
}

void MessageService::AddChannel(std::unique_ptr<MessageChannel> channel,
                                const PortId& receiver_port_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  ChannelId channel_id = receiver_port_id.GetChannelId();
  CHECK(!base::Contains(channels_, channel_id));
  channels_[channel_id] = std::move(channel);
  pending_lazy_context_channels_.erase(channel_id);
}

void MessageService::OpenPortImpl(const PortId& port_id,
                                  int process_id,
                                  const PortContext& port_context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!port_id.is_opener);

  ChannelId channel_id = port_id.GetChannelId();
  auto it = channels_.find(channel_id);
  if (it == channels_.end())
    return;

  it->second->receiver->OpenPort(process_id, port_context);
}
void MessageService::CloseChannel(const PortId& port_id,
                                  const std::string& error_message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ClosePortImpl(port_id, content::ChildProcessHost::kInvalidUniqueID,
                IPC::mojom::kRoutingIdNone, kMainThreadId, true, error_message);
}

void MessageService::ClosePort(const PortId& port_id,
                               int process_id,
                               const PortContext& port_context,
                               bool close_channel,
                               const std::string& error_message) {
  int routing_id = port_context.frame ? port_context.frame->routing_id
                                      : IPC::mojom::kRoutingIdNone;
  int worker_thread_id =
      port_context.worker ? port_context.worker->thread_id : kMainThreadId;
  ClosePortImpl(port_id, process_id, routing_id, worker_thread_id,
                close_channel, error_message);
}

void MessageService::ClosePortImpl(const PortId& port_id,
                                   int process_id,
                                   int routing_id,
                                   int worker_thread_id,
                                   bool close_channel,
                                   const std::string& error_message) {
  // Note: The channel might be not yet created (if the opener became invalid
  // before the channel initialization completed) or already gone (if the other
  // side closed first).
  ChannelId channel_id = port_id.GetChannelId();
  auto it = channels_.find(channel_id);
  if (it == channels_.end()) {
    auto pending = pending_lazy_context_channels_.find(channel_id);
    if (pending != pending_lazy_context_channels_.end()) {
      const LazyContextId& context_id = pending->second;
      context_id.GetTaskQueue()->AddPendingTask(
          context_id,
          base::BindOnce(&MessageService::PendingLazyContextClosePort,
                         weak_factory_.GetWeakPtr(), port_id, process_id,
                         routing_id, worker_thread_id, close_channel,
                         error_message));
    }
    return;
  }

  // The difference between closing a channel and port is that closing a port
  // does not necessarily have to destroy the channel if there are multiple
  // receivers, whereas closing a channel always forces all ports to be closed.
  if (close_channel) {
    CloseChannelImpl(it, port_id, error_message, true);
  } else if (port_id.is_opener) {
    it->second->opener->ClosePort(process_id, routing_id, worker_thread_id);
  } else {
    it->second->receiver->ClosePort(process_id, routing_id, worker_thread_id);
  }
}

void MessageService::CloseChannelImpl(MessageChannelMap::iterator channel_iter,
                                      const PortId& closing_port_id,
                                      const std::string& error_message,
                                      bool notify_other_port) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  std::unique_ptr<MessageChannel> channel = std::move(channel_iter->second);
  // Remove from map to make sure that it is impossible for CloseChannelImpl to
  // run twice for the same channel.
  channels_.erase(channel_iter);

  // Notify the other side.
  if (notify_other_port) {
    MessagePort* port = closing_port_id.is_opener ? channel->receiver.get()
                                                  : channel->opener.get();
    port->DispatchOnDisconnect(error_message);
  }

  // Balance the IncrementLazyKeepaliveCount() in OpenChannelImpl.
  channel->opener->DecrementLazyKeepaliveCount(Activity::MESSAGE_PORT);
  channel->receiver->DecrementLazyKeepaliveCount(Activity::MESSAGE_PORT);
}

void MessageService::PostMessage(const PortId& source_port_id,
                                 const Message& message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  ChannelId channel_id = source_port_id.GetChannelId();
  auto iter = channels_.find(channel_id);
  if (iter == channels_.end()) {
    // If this channel is pending, queue up the PostMessage to run once
    // the channel opens.
    EnqueuePendingMessage(source_port_id, channel_id, message);
    return;
  }

  DispatchMessage(source_port_id, iter->second.get(), message);
}

void MessageService::EnqueuePendingMessage(const PortId& source_port_id,
                                           const ChannelId& channel_id,
                                           const Message& message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto pending_for_incognito = pending_incognito_channels_.find(channel_id);
  if (pending_for_incognito != pending_incognito_channels_.end()) {
    pending_for_incognito->second.push_back(
        PendingMessage(source_port_id, message));
    // A channel should only be holding pending messages because it is in one
    // of these states.
    DCHECK(!base::Contains(pending_lazy_context_channels_, channel_id));
    return;
  }
  EnqueuePendingMessageForLazyBackgroundLoad(source_port_id, channel_id,
                                             message);
}

void MessageService::EnqueuePendingMessageForLazyBackgroundLoad(
    const PortId& source_port_id,
    const ChannelId& channel_id,
    const Message& message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto pending = pending_lazy_context_channels_.find(channel_id);
  // The message corresponds to an unknown channel. This could happen if
  // renderers shut down in various racy fashions. Drop the message on the
  // floor.
  if (pending == pending_lazy_context_channels_.end()) {
    return;
  }

  const LazyContextId& context_id = pending->second;
  ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
  if (!registry->enabled_extensions().Contains(context_id.extension_id())) {
    // Similarly, the extension might have been unloaded. Again, drop the
    // message on the floor. Possible fix for https://crbug.com/1428987.
    // TODO(devlin): Would it make sense to instead clean up
    // `pending_lazy_context_channels_` on extension unload instead? If this
    // fixes the bug above, investigate.
    return;
  }

  context_id.GetTaskQueue()->AddPendingTask(
      context_id,
      base::BindOnce(&MessageService::PendingLazyContextPostMessage,
                     weak_factory_.GetWeakPtr(), source_port_id, message));
}

void MessageService::DispatchMessage(const PortId& source_port_id,
                                     MessageChannel* channel,
                                     const Message& message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Figure out which port the ID corresponds to.
  MessagePort* dest_port = source_port_id.is_opener ? channel->receiver.get()
                                                    : channel->opener.get();

  dest_port->DispatchOnMessage(message);
}

void MessageService::NotifyResponsePending(const PortId& port_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!port_id.is_opener);

  ChannelId channel_id = port_id.GetChannelId();
  auto it = channels_.find(channel_id);
  if (it == channels_.end())
    return;

  it->second->receiver->NotifyResponsePending();
}

bool MessageService::HasPendingLazyContextChannelsForExtension(
    const ExtensionId& extension_id) const {
  return std::ranges::any_of(
      pending_lazy_context_channels_, [&extension_id](const auto& pair) {
        return pair.second.extension_id() == extension_id;
      });
}

bool MessageService::MaybeAddPendingLazyContextOpenChannelTask(
    BrowserContext* context,
    const Extension* extension,
    std::unique_ptr<OpenChannelParams>* params,
    const PendingMessagesQueue& pending_messages) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(context, context_));

  const bool is_event_page = BackgroundInfo::HasLazyBackgroundPage(extension);
  bool is_worker_based = false;
  if (!is_event_page)
    is_worker_based = BackgroundInfo::IsServiceWorkerBased(extension);
  if (!is_worker_based && !is_event_page)
    return false;

  // If the extension uses spanning incognito mode, make sure we're always
  // using the original profile since that is what the extension processes
  // will use.
  if (!IncognitoInfo::IsSplitMode(extension))
    context = ExtensionsBrowserClient::Get()->GetOriginalContext(context);

  const LazyContextId context_id = LazyContextIdFor(context, extension);
  LazyContextTaskQueue* task_queue = context_id.GetTaskQueue();
  if (!task_queue->ShouldEnqueueTask(context, extension))
    return false;

  ChannelId channel_id = (*params)->receiver_port_id.GetChannelId();
  pending_lazy_context_channels_.emplace(channel_id, context_id);

  base::UnguessableToken open_channel_wakeup_context_tracking_id;
  if (is_worker_based) {
    // Track only SW context wake up.
    open_channel_wakeup_context_tracking_id = base::UnguessableToken::Create();
    auto* message_tracker = MessageTracker::Get(context_);
    message_tracker->StartTrackingMessagingStage(
        open_channel_wakeup_context_tracking_id,
        "Extensions.MessagePipeline.OpenChannelWorkerWakeUpStatus",
        params->get()->channel_type);
  }

  task_queue->AddPendingTask(
      context_id,
      base::BindOnce(&MessageService::PendingLazyContextOpenChannel,
                     weak_factory_.GetWeakPtr(), std::move(*params),
                     std::move(open_channel_wakeup_context_tracking_id)));

  for (const PendingMessage& message : pending_messages) {
    EnqueuePendingMessageForLazyBackgroundLoad(message.first, channel_id,
                                               message.second);
  }
  return true;
}

void MessageService::OnOpenChannelAllowed(
    std::unique_ptr<OpenChannelParams> params,
    bool allowed) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  ChannelId channel_id = params->receiver_port_id.GetChannelId();

  auto pending_for_incognito = pending_incognito_channels_.find(channel_id);
  if (pending_for_incognito == pending_incognito_channels_.end()) {
    NOTREACHED();
  }
  PendingMessagesQueue pending_messages;
  pending_messages.swap(pending_for_incognito->second);
  pending_incognito_channels_.erase(pending_for_incognito);

  auto* message_tracker = MessageTracker::Get(context_);

  // Check whether the source got closed while in flight.
  const ChannelEndpoint& source = params->source;
  // Re-lookup the source process since it may no longer be valid.
  if (!source.IsValid()) {
    for (const auto& tracking_id : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOnOpenChannelSourceInvalid);
    }
    return;
  }

  if (!params->opener_port->IsValidPort()) {
    for (const auto& tracking_id : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOnOpenChannelOpenerPortInvalid);
    }
    return;
  }

  if (!allowed) {
    params->opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    for (const auto& tracking_id : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id,
          MessageTracker::OpenChannelMessagePipelineResult::kNoReceivers);
    }
    return;
  }

  BrowserContext* context = source.browser_context();
  DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(context, context_));

  ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  const Extension* target_extension =
      registry->enabled_extensions().GetByID(params->target_extension_id);
  if (!target_extension) {
    params->opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
    for (const auto& tracking_id : params->open_channel_tracking_ids) {
      message_tracker->StopTrackingMessagingStage(
          tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
                           kOnOpenChannelExtensionNotEnabled);
    }
    return;
  }

  BrowserContext* context_to_use_for_extension =
      IncognitoInfo::IsSplitMode(target_extension)
          ? context
          : ExtensionsBrowserClient::Get()->GetOriginalContext(context);
  params->receiver = ExtensionMessagePort::CreateForExtension(
      weak_factory_.GetWeakPtr(), params->receiver_port_id,
      params->target_extension_id, context_to_use_for_extension);

  // The target might be a lazy background page or a Service Worker. In that
  // case, we have to check if it is loaded and ready, and if not, queue up the
  // task and load the page.
  if (!MaybeAddPendingLazyContextOpenChannelTask(context, target_extension,
                                                 &params, pending_messages)) {
    OpenChannelImpl(context, std::move(params), target_extension,
                    /*did_enqueue=*/false);
    DispatchPendingMessages(pending_messages, channel_id);
  }
}

void MessageService::PendingLazyContextOpenChannel(
    std::unique_ptr<OpenChannelParams> params,
    const base::UnguessableToken& open_channel_wakeup_context_tracking_id,
    std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto* message_tracker = MessageTracker::Get(context_);

  if (context_info == nullptr) {
    // The lazy context failed to load (e.g. the worker failed to start).
    // Finish tracking the wake-up attempt with a failure.
    if (!open_channel_wakeup_context_tracking_id.is_empty()) {
      message_tracker->StopTrackingMessagingStage(
          open_channel_wakeup_context_tracking_id,
          MessageTracker::OpenChannelMessagePipelineResult::kNoReceivers);
    }
    // `OpenChannelImpl` will handle the cleanup and notify the opener.
    // We use `context_` as a fallback `BrowserContext`.
    OpenChannelImpl(context_, std::move(params), /*target_extension=*/nullptr,
                    /*did_enqueue=*/true);
    return;
  }

  // Finish tracking SW context wake up.
  if (!open_channel_wakeup_context_tracking_id.is_empty()) {
    message_tracker->StopTrackingMessagingStage(
        open_channel_wakeup_context_tracking_id,
        MessageTracker::OpenChannelMessagePipelineResult::kWorkerStarted);
  }

  params->receiver = ExtensionMessagePort::CreateForExtension(
      weak_factory_.GetWeakPtr(), params->receiver_port_id,
      params->target_extension_id, context_info->browser_context);
  const Extension* const extension =
      ExtensionRegistry::Get(context_info->browser_context)
          ->enabled_extensions()
          .GetByID(context_info->extension_id);

  // Verify the lazy context is properly registered. Otherwise, we'll fail to
  // find it when we try to connect.
  VerifyLazyContextActive(extension, *context_info);

  OpenChannelImpl(context_info->browser_context, std::move(params), extension,
                  /*did_enqueue=*/true);
}

void MessageService::DispatchPendingMessages(const PendingMessagesQueue& queue,
                                             const ChannelId& channel_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto channel_iter = channels_.find(channel_id);
  if (channel_iter != channels_.end()) {
    for (const PendingMessage& message : queue) {
      DispatchMessage(message.first, channel_iter->second.get(),
                      message.second);
    }
  }
}

}  // namespace extensions