[go: nahoru, domu]

blob: 5b812697969c4d659e02d4ceceae4dba4d25bd77 [file] [log] [blame]
// 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.
#include "extensions/browser/api/messaging/messaging_api_message_filter.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/types/optional_util.h"
#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/content_script_tracker.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/api/messaging/channel_type.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/trace_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
using content::BrowserThread;
using content::RenderProcessHost;
using perfetto::protos::pbzero::ChromeTrackEvent;
namespace extensions {
namespace {
class ShutdownNotifierFactory
: public BrowserContextKeyedServiceShutdownNotifierFactory {
public:
ShutdownNotifierFactory(const ShutdownNotifierFactory&) = delete;
ShutdownNotifierFactory& operator=(const ShutdownNotifierFactory&) = delete;
static ShutdownNotifierFactory* GetInstance() {
return base::Singleton<ShutdownNotifierFactory>::get();
}
private:
friend struct base::DefaultSingletonTraits<ShutdownNotifierFactory>;
ShutdownNotifierFactory()
: BrowserContextKeyedServiceShutdownNotifierFactory(
"ExtensionMessageFilter") {
DependsOn(EventRouterFactory::GetInstance());
}
~ShutdownNotifierFactory() override = default;
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override {
return ExtensionsBrowserClient::Get()->GetContextOwnInstance(
context, /*force_guest_profile=*/true);
}
};
// Returns true if `source_endpoint` can be legitimately claimed/used by
// `process`. Otherwise reports a bad IPC message and returns false (expecting
// the caller to not take any action based on the rejected, untrustworthy
// `source_endpoint`).
bool IsValidMessagingSource(RenderProcessHost& process,
const MessagingEndpoint& source_endpoint) {
switch (source_endpoint.type) {
case MessagingEndpoint::Type::kNativeApp:
// Requests for channels initiated by native applications don't originate
// from renderer processes.
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_CHANNEL_SOURCE_TYPE);
return false;
case MessagingEndpoint::Type::kExtension:
if (!source_endpoint.extension_id.has_value()) {
if (!base::FeatureList::IsEnabled(
extensions_features::kCheckingNoExtensionIdInExtensionIpcs)) {
base::UmaHistogramSparse(
"Stability.BadMessageTerminated.Extensions",
bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return true;
}
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return false;
}
if (!util::CanRendererHostExtensionOrigin(
process.GetID(), source_endpoint.extension_id.value())) {
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return false;
}
return true;
case MessagingEndpoint::Type::kContentScript: {
if (!source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT);
return false;
}
bool is_content_script_expected =
ContentScriptTracker::DidProcessRunContentScriptFromExtension(
process, *source_endpoint.extension_id);
if (!is_content_script_expected) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT);
return false;
}
return true;
}
case MessagingEndpoint::Type::kUserScript: {
if (!source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
bool is_user_script_expected =
ContentScriptTracker::DidProcessRunUserScriptFromExtension(
process, *source_endpoint.extension_id);
if (!is_user_script_expected) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
return true;
}
case MessagingEndpoint::Type::kWebPage:
// NOTE: We classify hosted apps as kWebPage, but we don't include
// the extension ID in the source for those messages.
if (source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_WEB_PAGE);
return false;
}
return true;
}
}
bool IsValidMessagingTarget(RenderProcessHost& process,
const MessagingEndpoint& source_endpoint,
const ExtensionId& target_id) {
switch (source_endpoint.type) {
case MessagingEndpoint::Type::kNativeApp:
case MessagingEndpoint::Type::kExtension:
case MessagingEndpoint::Type::kWebPage:
case MessagingEndpoint::Type::kContentScript:
// The API allows these to target any source. The connection may be
// refused (e.g. if the target extension isn't installed or doesn't accept
// a connection from the source), but it isn't a sign of a bad IPC.
return true;
case MessagingEndpoint::Type::kUserScript:
// User scripts can only target their own corresponding extension.
// `source_endpoint.extension_id` should have been validated above in
// `IsValidMessagingSource()`.
CHECK(source_endpoint.extension_id);
if (source_endpoint.extension_id != target_id) {
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_EXTERNAL_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
return true;
}
}
// Returns true if `source_context` can be legitimately claimed/used by
// `render_process_id`. Otherwise reports a bad IPC message and returns false
// (expecting the caller to not take any action based on the rejected,
// untrustworthy `source_context`).
bool IsValidSourceContext(RenderProcessHost& process,
const PortContext& source_context) {
if (source_context.is_for_service_worker()) {
const PortContext::WorkerContext& worker_context =
source_context.worker.value();
// Only crude checks via CanRendererHostExtensionOrigin are done here,
// because more granular, worker-specific checks (e.g. checking if a worker
// exists using ProcessManager::HasServiceWorker) might incorrectly return
// false=invalid-IPC for IPCs from workers that were recently torn down /
// made inactive.
if (!util::CanRendererHostExtensionOrigin(process.GetID(),
worker_context.extension_id)) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_WORKER_CONTEXT);
return false;
}
}
// This function doesn't validate frame-flavoured `source_context`s, because
// PortContext::FrameContext only contains frame's `routing_id` and therefore
// inherently cannot spoof frames in another process (a frame is identified
// by its `routing_id` *and* the `process_id` of the Renderer process hosting
// the frame; the latter is trustworthy / doesn't come from an IPC payload).
// This function doesn't validate native app `source_context`s, because
// `PortContext::ForNativeHost()` is called with trustoworthy inputs (e.g. it
// doesn't take input from IPCs sent by a Renderer process).
return true;
}
// Returns true if `source_url` can be legitimately claimed/used by `process`.
// Otherwise reports a bad IPC message and returns false (expecting the caller
// to not take any action based on the rejected, untrustworthy `source_url`).
bool IsValidSourceUrl(content::RenderProcessHost& process,
const GURL& source_url,
const PortContext& source_context) {
if (!base::FeatureList::IsEnabled(
extensions_features::kExtensionSourceUrlEnforcement)) {
return true;
}
// Some scenarios may end up with an empty `source_url` (e.g. this may have
// been triggered by the ExtensionApiTabTest.TabConnect test).
//
// TODO(https://crbug.com/1370079): Remove this workaround once the bug is
// fixed.
if (source_url.is_empty())
return true;
// Extract the `base_origin`.
//
// We don't use `ChildProcessSecurityPolicy::CanCommitURL` because: 1) it
// doesn't cover service workers (e.g. see https://crbug.com/1038996#c35), 2)
// it has bugs (e.g. https://crbug.com/1380576), and 3) we *can* extract the
// `base_origin` (via `source_context.worker->extension_id` or
// `GetLastCommittedOrigin`) and therefore *can* use the more fundamental
// `CanAccessDataForOrigin` (whereas `CanCommitURL` tries to work even if the
// base origin is not available).
url::Origin base_origin;
if (source_context.is_for_render_frame()) {
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
process.GetID(), source_context.frame->routing_id);
if (!frame) {
// Not calling ReceivedBadMessage because it is possible that the frame
// got deleted before the IPC arrived.
// Returning `false` will result in dropping the IPC by the caller - this
// is okay, because sending of the IPC was inherently racing with the
// deletion of the frame.
return false;
}
if (frame->GetLastCommittedURL() == source_url) {
// If the trustworthy, browser-side URL matches `source_url` from the IPC
// payload, then report that the IPC is valid. If the URLs don't match
// then we can't assume that the IPC is malformed and `return false`,
// because the renderer-side and browser-side URLs may differ in some
// scenarios (e.g. see https://crbug.com/1197308 or `document.write`). In
// such scenarios we want to fall back to `base_origin`-based /
// `source_url_origin``-based checks, but these checks are not 100%
// correct (see https://crbug.com/1449796), so `GetLastCommittedURL` is
// consulted first.
return true;
}
base_origin = frame->GetLastCommittedOrigin();
} else if (source_context.is_for_service_worker()) {
// Validate `source_context` before using it to validate `source_url`.
// IsValidSourceContext will call ReceivedBadMessage if needed.
if (!IsValidSourceContext(process, source_context))
return false;
// `base_origin` can be considered trustworthy, because `source_context` has
// been validated above.
base_origin = Extension::CreateOriginFromExtensionId(
source_context.worker->extension_id);
} else {
DCHECK(source_context.is_for_native_host());
// `ExtensionHostMsg_OpenChannelToExtension` is sent in
// `//extensions/renderer/ipc_message_sender.cc` only for frames and
// workers (and never for native hosts).
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_OPEN_CHANNEL_TO_EXTENSION_FROM_NATIVE_HOST);
return false;
}
// Verify `source_url` via CanAccessDataForOrigin.
//
// TODO(https://crbug.com/1449796): Stop partially/not-100%-correctly
// replicating checks from `RenderFrameHostImpl::CanCommitOriginAndUrl`.
// The code below correctly handles URLs like `about:blank`, but may diverge
// from //content checks in some cases (e.g. WebUI checks are not replicated
// here; MHTML divergence is avoided via GetLastCommittedURL() check above).
url::Origin source_url_origin = url::Origin::Resolve(source_url, base_origin);
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
if (!policy->CanAccessDataForOrigin(process.GetID(), source_url_origin)) {
SCOPED_CRASH_KEY_STRING256(
"EMF_INVALID_SOURCE_URL", "base_origin",
base_origin.GetDebugString(false /* include_nonce */));
bad_message::ReceivedBadMessage(&process,
bad_message::EMF_INVALID_SOURCE_URL);
return false;
}
return true;
}
base::debug::CrashKeyString* GetTargetIdCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-target_id", base::debug::CrashKeySize::Size64);
return crash_key;
}
base::debug::CrashKeyString* GetSourceOriginCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-source_origin",
base::debug::CrashKeySize::Size256);
return crash_key;
}
base::debug::CrashKeyString* GetSourceUrlCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-source_url", base::debug::CrashKeySize::Size256);
return crash_key;
}
class ScopedExternalConnectionInfoCrashKeys {
public:
explicit ScopedExternalConnectionInfoCrashKeys(
const ExtensionMsg_ExternalConnectionInfo& info)
: target_id_(GetTargetIdCrashKey(), info.target_id),
source_endpoint_(info.source_endpoint),
source_origin_(GetSourceOriginCrashKey(),
base::OptionalToPtr(info.source_origin)),
source_url_(GetSourceUrlCrashKey(),
info.source_url.possibly_invalid_spec()) {}
~ScopedExternalConnectionInfoCrashKeys() = default;
ScopedExternalConnectionInfoCrashKeys(
const ScopedExternalConnectionInfoCrashKeys&) = delete;
ScopedExternalConnectionInfoCrashKeys& operator=(
const ScopedExternalConnectionInfoCrashKeys&) = delete;
private:
base::debug::ScopedCrashKeyString target_id_;
extensions::debug::ScopedMessagingEndpointCrashKeys source_endpoint_;
url::debug::ScopedOriginCrashKey source_origin_;
base::debug::ScopedCrashKeyString source_url_;
};
// Validates whether `source_context` can be legitimately used in the IPC
// messages sent from the given renderer `process`. If the validation fails, or
// the sender is not associated with an extension, then `nullopt` is returned.
// The sender should ignore the IPC when `nullopt` is returned.
absl::optional<ExtensionId> ValidateSourceContextAndExtractExtensionId(
content::RenderProcessHost& process,
const PortContext& source_context) {
if (!IsValidSourceContext(process, source_context))
return absl::nullopt;
if (source_context.is_for_service_worker())
return source_context.worker->extension_id;
if (source_context.is_for_render_frame()) {
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
process.GetID(), source_context.frame->routing_id);
if (!frame) {
// Not calling ReceivedBadMessage because it is possible that the frame
// got deleted before the IPC arrived.
return absl::nullopt;
}
// These extension IPCs are on the same pipe as DidCommit() (and thus can't
// arrive out-of-order), and therefore we can rely on
// `frame->GetLastCommittedOrigin()` to return the origin of the IPC sender.
const url::Origin& origin = frame->GetLastCommittedOrigin();
// Sandboxed extension URLs have access to extension APIs (this is a bit
// unusual - typically an opaque origin has no capabilities associated with
// the original, precursor origin). To avoid breaking such scenarios we
// need to look at the precursor origin. See https://crbug.com/1407087 for
// an example of breakage avoided by GetTupleOrPrecursorTupleIfOpaque call.
const url::SchemeHostPort& scheme_host_port =
origin.GetTupleOrPrecursorTupleIfOpaque();
if (scheme_host_port.scheme() != kExtensionScheme) {
SCOPED_CRASH_KEY_STRING256(
"EMF_NON_EXTENSION_SENDER_FRAME", "origin",
origin.GetDebugString(false /* include_nonce */));
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NON_EXTENSION_SENDER_FRAME);
return absl::nullopt;
}
return scheme_host_port.host();
}
DCHECK(source_context.is_for_native_host());
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NON_EXTENSION_SENDER_NATIVE_HOST);
return absl::nullopt;
}
} // namespace
MessagingAPIMessageFilter::MessagingAPIMessageFilter(
int render_process_id,
content::BrowserContext* context)
: BrowserMessageFilter(ExtensionMsgStart),
render_process_id_(render_process_id),
browser_context_(context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
shutdown_notifier_subscription_ =
ShutdownNotifierFactory::GetInstance()->Get(context)->Subscribe(
base::BindRepeating(&MessagingAPIMessageFilter::Shutdown,
base::Unretained(this)));
}
MessagingAPIMessageFilter::~MessagingAPIMessageFilter() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void MessagingAPIMessageFilter::Shutdown() {
browser_context_ = nullptr;
shutdown_notifier_subscription_ = {};
}
content::RenderProcessHost* MessagingAPIMessageFilter::GetRenderProcessHost() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!browser_context_)
return nullptr;
// The IPC might race with RenderProcessHost destruction. This may only
// happen in scenarios that are already inherently racey, so returning nullptr
// (and dropping the IPC) is okay and won't lead to any additional risk of
// data loss.
return content::RenderProcessHost::FromID(render_process_id_);
}
void MessagingAPIMessageFilter::OverrideThreadForMessage(
const IPC::Message& message,
BrowserThread::ID* thread) {
switch (message.type()) {
case ExtensionHostMsg_OpenChannelToExtension::ID:
case ExtensionHostMsg_OpenChannelToTab::ID:
case ExtensionHostMsg_OpenChannelToNativeApp::ID:
case ExtensionHostMsg_OpenMessagePort::ID:
case ExtensionHostMsg_CloseMessagePort::ID:
case ExtensionHostMsg_PostMessage::ID:
case ExtensionHostMsg_ResponsePending::ID:
*thread = BrowserThread::UI;
break;
default:
break;
}
}
void MessagingAPIMessageFilter::OnDestruct() const {
BrowserThread::DeleteOnUIThread::Destruct(this);
}
bool MessagingAPIMessageFilter::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(MessagingAPIMessageFilter, message)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToExtension,
OnOpenChannelToExtension)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToTab, OnOpenChannelToTab)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToNativeApp,
OnOpenChannelToNativeApp)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenMessagePort, OnOpenMessagePort)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_CloseMessagePort, OnCloseMessagePort)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_PostMessage, OnPostMessage)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_ResponsePending, OnResponsePending)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void MessagingAPIMessageFilter::OnOpenChannelToExtension(
const PortContext& source_context,
const ExtensionMsg_ExternalConnectionInfo& info,
ChannelType channel_type,
const std::string& channel_name,
const PortId& port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process)
return;
TRACE_EVENT("extensions", "MessageFilter::OnOpenChannelToExtension",
ChromeTrackEvent::kRenderProcessHost, *process);
ScopedExternalConnectionInfoCrashKeys info_crash_keys(info);
debug::ScopedPortContextCrashKeys port_context_crash_keys(source_context);
if (!IsValidMessagingSource(*process, info.source_endpoint) ||
!IsValidMessagingTarget(*process, info.source_endpoint, info.target_id) ||
!IsValidSourceUrl(*process, info.source_url, source_context) ||
!IsValidSourceContext(*process, source_context)) {
// No need to call ReceivedBadMessage here, because it will be called (when
// appropriate) within IsValidSourceContext and/or IsValidMessagingSource.
return;
}
ChannelEndpoint source_endpoint(browser_context_, render_process_id_,
source_context);
MessageService::Get(browser_context_)
->OpenChannelToExtension(source_endpoint, port_id, info.source_endpoint,
nullptr /* opener_port */, info.target_id,
info.source_url, channel_type, channel_name);
}
void MessagingAPIMessageFilter::OnOpenChannelToNativeApp(
const PortContext& source_context,
const std::string& native_app_name,
const PortId& port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process)
return;
TRACE_EVENT("extensions", "MessageFilter::OnOpenChannelToNativeApp",
ChromeTrackEvent::kRenderProcessHost, *process);
debug::ScopedPortContextCrashKeys port_context_crash_keys(source_context);
if (!IsValidSourceContext(*process, source_context)) {
// No need to call ReceivedBadMessage here, because it will be called (when
// appropriate) within IsValidSourceContext.
return;
}
ChannelEndpoint source_endpoint(browser_context_, render_process_id_,
source_context);
MessageService::Get(browser_context_)
->OpenChannelToNativeApp(source_endpoint, port_id, native_app_name);
}
void MessagingAPIMessageFilter::OnOpenChannelToTab(
const PortContext& source_context,
const ExtensionMsg_TabTargetConnectionInfo& info,
ChannelType channel_type,
const std::string& channel_name,
const PortId& port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process)
return;
TRACE_EVENT("extensions", "MessageFilter::OnOpenChannelToTab",
ChromeTrackEvent::kRenderProcessHost, *process);
absl::optional<ExtensionId> extension_id =
ValidateSourceContextAndExtractExtensionId(*process, source_context);
if (!extension_id) {
// No need to call ReceivedBadMessage here, because it will be called (when
// appropriate) within ValidateSourceContextAndExtractExtensionId.
return;
}
ChannelEndpoint source_endpoint(browser_context_, render_process_id_,
source_context);
MessageService::Get(browser_context_)
->OpenChannelToTab(source_endpoint, port_id, info.tab_id, info.frame_id,
info.document_id, *extension_id, channel_type,
channel_name);
}
void MessagingAPIMessageFilter::OnOpenMessagePort(const PortContext& source,
const PortId& port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process) {
return;
}
TRACE_EVENT("extensions", "MessageFilter::OnOpenMessagePort",
ChromeTrackEvent::kRenderProcessHost, *process);
if (!IsValidSourceContext(*process, source)) {
return;
}
MessageService::Get(browser_context_)
->OpenPort(port_id, render_process_id_, source);
}
void MessagingAPIMessageFilter::OnCloseMessagePort(
const PortContext& port_context,
const PortId& port_id,
bool force_close) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process) {
return;
}
TRACE_EVENT("extensions", "MessageFilter::OnCloseMessagePort",
ChromeTrackEvent::kRenderProcessHost, *process);
if (!port_context.is_for_render_frame() &&
!port_context.is_for_service_worker()) {
bad_message::ReceivedBadMessage(render_process_id_,
bad_message::EMF_INVALID_PORT_CONTEXT);
return;
}
if (!IsValidSourceContext(*process, port_context)) {
return;
}
MessageService::Get(browser_context_)
->ClosePort(port_id, render_process_id_, port_context, force_close);
}
void MessagingAPIMessageFilter::OnPostMessage(const PortId& port_id,
const Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!browser_context_)
return;
MessageService::Get(browser_context_)->PostMessage(port_id, message);
}
void MessagingAPIMessageFilter::OnResponsePending(
const PortContext& port_context,
const PortId& port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = GetRenderProcessHost();
if (!process) {
return;
}
TRACE_EVENT("extensions", "MessageFilter::OnResponsePending",
ChromeTrackEvent::kRenderProcessHost, *process);
if (!IsValidSourceContext(*process, port_context)) {
return;
}
MessageService::Get(browser_context_)
->NotifyResponsePending(port_id, render_process_id_, port_context);
}
// static
void MessagingAPIMessageFilter::EnsureAssociatedFactoryBuilt() {
ShutdownNotifierFactory::GetInstance();
}
} // namespace extensions