1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
|
// 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/extension_message_port.h"
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/optional_util.h"
#include "base/types/pass_key.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/message_tracker.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/browser/service_worker/service_worker_host.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/mojom/message_port.mojom-shared.h"
namespace extensions {
namespace {
std::string PortIdToString(const extensions::PortId& port_id) {
return base::StrCat({port_id.GetChannelId().first.ToString(), ":",
base::NumberToString(port_id.GetChannelId().second)});
}
using PassKey = base::PassKey<ExtensionMessagePort>;
const char kReceivingEndDoesntExistError[] =
// TODO(lazyboy): Test these in service worker implementation.
"Could not establish connection. Receiving end does not exist.";
const char kClosedWhileResponsePendingError[] =
"A listener indicated an asynchronous response by returning true, but the "
"message channel closed before a response was received";
const char kClosedWhenPageEntersBFCache[] =
"The page keeping the extension port is moved into back/forward cache, so "
"the message channel is closed.";
} // namespace
// Helper class to detect when contexts (frames or workers) are destroyed.
class ExtensionMessagePort::ContextTracker
: public content::WebContentsObserver,
public ProcessManagerObserver {
public:
explicit ContextTracker(ExtensionMessagePort* port) : port_(port) {}
ContextTracker(const ContextTracker&) = delete;
ContextTracker& operator=(const ContextTracker&) = delete;
~ContextTracker() override = default;
void TrackExtensionContexts() {
pm_observation_.Observe(ProcessManager::Get(port_->browser_context_));
}
void TrackTabFrames(content::WebContents* tab) { Observe(tab); }
private:
// content::WebContentsObserver overrides:
void RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) override {
port_->UnregisterFrame(render_frame_host);
}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->HasCommitted()) {
// Close the channel and force all the ports from the channel to be
// closed when the old RFH is going to be stored in BFCache. They will
// not be able to receive any message sent through the port.
content::RenderFrameHost* previous_rfh = content::RenderFrameHost::FromID(
navigation_handle->GetPreviousRenderFrameHostId());
if (previous_rfh &&
previous_rfh->GetLifecycleState() ==
content::RenderFrameHost::LifecycleState::kInBackForwardCache) {
if (port_->UnregisterFramesUnderMainFrame(
previous_rfh, kClosedWhenPageEntersBFCache)) {
// Since the channel and the port is already closed, we don't have to
// run the following block to unregister the frames any more.
return;
}
}
}
// There are a number of possible scenarios for the navigation:
// 1. Same-document navigation - Don't unregister the frame, since it can
// still use the port.
// 2. Cross-document navigation, reusing the RenderFrameHost - Unregister
// the frame, since the new document is not allowed to use the port.
// 3. Cross-document navigation, with a new RenderFrameHost - Since the
// navigated-to document has a new RFH, the port can not be registered for
// it, so it doesn't matter whether we unregister it or not. If the
// navigated-from document is stored in the back-forward cache, don't
// unregister the frame (see note below). If it is not cached, the frame
// will be unregistered when the RFH is deleted.
// 4. Restoring a cached frame from back-forward cache or activating a
// prerendered frame - This is similar to (3) in that the navigation changes
// RFH, with the difference that the RFH is not new and so may be
// registered. Don't unregister the frame in this case since it may still
// use the port.
// Note that we don't just disconnect channels when a frame is bf-cached
// since when such a document is later restored, there is no "load" and so a
// message channel won't be immediately available to extensions.
// Contrast this with a normal load where an extension is able to inject
// scripts at "document_start" and set up message ports.
if (navigation_handle->HasCommitted() &&
!navigation_handle->IsSameDocument() &&
!navigation_handle->IsPageActivation()) {
// Note: This unregisters the _new_ RenderFrameHost. In case a new RFH was
// created for this navigation, this will be a no-op, since we haven't
// seen it before. In case the RFH is reused for the navigation, this will
// correctly unregister the frame, to avoid messages intended for the
// previous document being sent to the new document. If the navigated-to
// RFH is served from cache, keep the port alive.
port_->UnregisterFrame(navigation_handle->GetRenderFrameHost());
}
}
// extensions::ProcessManagerObserver overrides:
void OnExtensionFrameUnregistered(
const ExtensionId& extension_id,
content::RenderFrameHost* render_frame_host) override {
if (extension_id == port_->extension_id_)
port_->UnregisterFrame(render_frame_host);
}
void OnStoppedTrackingServiceWorkerInstance(
const WorkerId& worker_id) override {
port_->UnregisterWorker(worker_id);
}
base::ScopedObservation<ProcessManager, ProcessManagerObserver>
pm_observation_{this};
raw_ptr<ExtensionMessagePort> port_; // Owns this ContextTracker.
};
std::unique_ptr<ExtensionMessagePort> ExtensionMessagePort::CreateForTab(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const ExtensionId& extension_id,
content::RenderFrameHost* render_frame_host,
bool include_child_frames) {
auto port = std::make_unique<ExtensionMessagePort>(
channel_delegate, port_id, extension_id,
render_frame_host->GetBrowserContext(), PassKey());
port->context_tracker_ = std::make_unique<ContextTracker>(port.get());
content::WebContents* tab =
content::WebContents::FromRenderFrameHost(render_frame_host);
CHECK(tab);
port->context_tracker_->TrackTabFrames(tab);
if (include_child_frames) {
// TODO(crbug.com/40189370) We don't yet support MParch for
// prerender so make sure `include_child_frames` is only provided for
// primary main frames.
CHECK(render_frame_host->IsInPrimaryMainFrame());
render_frame_host->ForEachRenderFrameHostWithAction(
[tab, &port](content::RenderFrameHost* render_frame_host) {
// RegisterFrame should only be called for frames associated with
// `tab` and not any inner WebContents.
if (content::WebContents::FromRenderFrameHost(render_frame_host) !=
tab) {
return content::RenderFrameHost::FrameIterationAction::
kSkipChildren;
}
port->RegisterFrame(render_frame_host);
return content::RenderFrameHost::FrameIterationAction::kContinue;
});
} else {
port->RegisterFrame(render_frame_host);
}
return port;
}
// static
std::unique_ptr<ExtensionMessagePort> ExtensionMessagePort::CreateForExtension(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const ExtensionId& extension_id,
content::BrowserContext* browser_context) {
auto port = std::make_unique<ExtensionMessagePort>(
channel_delegate, port_id, extension_id, browser_context, PassKey());
port->context_tracker_ = std::make_unique<ContextTracker>(port.get());
port->context_tracker_->TrackExtensionContexts();
port->for_all_extension_contexts_ = true;
auto* process_manager = ProcessManager::Get(browser_context);
auto all_hosts =
process_manager->GetRenderFrameHostsForExtension(extension_id);
for (content::RenderFrameHost* render_frame_host : all_hosts) {
port->RegisterFrame(render_frame_host);
}
std::vector<WorkerId> running_workers =
process_manager->GetServiceWorkersForExtension(extension_id);
for (const WorkerId& running_worker_id : running_workers)
port->RegisterWorker(running_worker_id);
return port;
}
// static
std::unique_ptr<ExtensionMessagePort> ExtensionMessagePort::CreateForEndpoint(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const ExtensionId& extension_id,
const ChannelEndpoint& endpoint,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> message_port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
message_port_host) {
auto port = std::make_unique<ExtensionMessagePort>(
channel_delegate, port_id, extension_id, endpoint.browser_context(),
PassKey());
port->context_tracker_ = std::make_unique<ContextTracker>(port.get());
if (endpoint.is_for_render_frame()) {
content::RenderFrameHost* render_frame_host = endpoint.GetRenderFrameHost();
content::WebContents* tab =
content::WebContents::FromRenderFrameHost(render_frame_host);
CHECK(tab);
port->context_tracker_->TrackTabFrames(tab);
auto& receiver = port->frames_[render_frame_host->GetGlobalFrameToken()];
receiver.Bind(std::move(message_port));
receiver.set_disconnect_handler(base::BindOnce(&ExtensionMessagePort::Prune,
base::Unretained(port.get()),
endpoint.port_context(),
// Unused for endpoints.
base::UnguessableToken()));
} else {
port->context_tracker_->TrackExtensionContexts();
auto& receiver = port->service_workers_[endpoint.GetWorkerId()];
receiver.Bind(std::move(message_port));
receiver.set_disconnect_handler(base::BindOnce(&ExtensionMessagePort::Prune,
base::Unretained(port.get()),
endpoint.port_context(),
// Unused for endpoints.
base::UnguessableToken()));
}
port->AddReceiver(std::move(message_port_host), endpoint.render_process_id(),
endpoint.port_context());
return port;
}
ExtensionMessagePort::ExtensionMessagePort(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const ExtensionId& extension_id,
content::BrowserContext* browser_context,
PassKey)
: MessagePort(std::move(channel_delegate), port_id),
extension_id_(extension_id),
browser_context_(browser_context) {}
ExtensionMessagePort::~ExtensionMessagePort() {
// We aren't interested in metrics when the browser is shutting down.
ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get();
if (!browser_client || !browser_client->IsValidContext(browser_context_)) {
return;
}
// Emit per connect dispatch IPC metric before class is destroyed.
auto* message_tracker = MessageTracker::Get(browser_context_);
for (const auto& tracking_id :
pending_open_channel_connect_dispatch_tracking_ids_) {
message_tracker->StopTrackingMessagingStage(
tracking_id, MessageTracker::OpenChannelMessagePipelineResult::
kOpenChannelClosedBeforeResponse);
}
pending_open_channel_connect_dispatch_tracking_ids_.clear();
}
void ExtensionMessagePort::Prune(
const PortContext& port_context,
const base::UnguessableToken& connect_dispatch_tracking_id) {
std::vector<content::GlobalRenderFrameHostToken> frames_to_unregister;
// Channel metrics tracking.
pending_contexts_to_respond_.erase(port_context);
// If we're on the last context we expect to see respond and the port
// was still not created then OnConnectResponse() didn't emit metrics. Let's
// catch this and report we weren't able to find any receivers.
if (pending_contexts_to_respond_.empty() && !port_was_created_) {
ReportOpenChannelResult(MessageTracker::OpenChannelMessagePipelineResult::
kOpenChannelDispatchNoReceivers);
}
// Per channel open connect IPC dispatch metrics tracking.
if (!connect_dispatch_tracking_id.is_empty()) {
ReportOpenChannelConnectDispatchResult(
connect_dispatch_tracking_id,
MessageTracker::OpenChannelMessagePipelineResult::
kOpenChannelPortDisconnectedBeforeResponse);
}
for (auto& frame : frames_) {
if (!frame.second.is_connected()) {
frames_to_unregister.push_back(frame.first);
}
}
for (const auto& frame_token : frames_to_unregister) {
if (UnregisterFrame(frame_token)) {
return;
}
}
std::vector<WorkerId> workers_to_unregister;
for (auto& worker : service_workers_) {
if (!worker.second.is_connected()) {
workers_to_unregister.push_back(worker.first);
}
}
for (const auto& worker : workers_to_unregister) {
if (UnregisterWorker(worker)) {
return;
}
}
}
void ExtensionMessagePort::ReportOpenChannelResult(
MessageTracker::OpenChannelMessagePipelineResult emit_value) {
// TODO(crbug.com/371011217): Cache this as a member var to avoid fetching it
// in so many places.
auto* message_tracker = MessageTracker::Get(browser_context_);
// MessageTracker ensures the metrics can only emit once (e.g. if
// another method calls this method again after this).
for (const auto& tracking_id : pending_open_channel_tracking_ids_) {
message_tracker->StopTrackingMessagingStage(tracking_id, emit_value);
}
pending_open_channel_tracking_ids_.clear();
}
void ExtensionMessagePort::ReportOpenChannelConnectDispatchResult(
const base::UnguessableToken& tracking_id,
MessageTracker::OpenChannelMessagePipelineResult emit_value) {
auto* message_tracker = MessageTracker::Get(browser_context_);
// MessageTracker ensures the metrics can only emit once (e.g. if
// another method calls this method again after this).
message_tracker->StopTrackingMessagingStage(tracking_id, emit_value);
pending_open_channel_connect_dispatch_tracking_ids_.erase(tracking_id);
}
void ExtensionMessagePort::RemoveCommonFrames(const MessagePort& port) {
// This should be called before OnConnect is called.
CHECK(frames_.empty());
// Avoid overlap in the set of frames to make sure that it does not matter
// when UnregisterFrame is called.
std::erase_if(
pending_frames_,
[&port](const content::GlobalRenderFrameHostToken& frame_token) {
return port.HasFrame(frame_token);
});
}
bool ExtensionMessagePort::HasFrame(
const content::GlobalRenderFrameHostToken& frame_token) const {
return base::Contains(frames_, frame_token) ||
base::Contains(pending_frames_, frame_token);
}
bool ExtensionMessagePort::IsValidPort() {
return !frames_.empty() || !service_workers_.empty() ||
!pending_frames_.empty() || !pending_service_workers_.empty();
}
void ExtensionMessagePort::RevalidatePort() {
// Checks whether the frames to which this port is tied at its construction
// are still aware of this port's existence. Frames that don't know about
// the port are removed from the set of frames. This should be used for opener
// ports because the frame may be navigated before the port was initialized.
// Only opener ports need to be revalidated, because these are created in the
// renderer before the browser knows about them.
DCHECK(!for_all_extension_contexts_);
DCHECK_EQ(frames_.size() + service_workers_.size() + pending_frames_.size() +
pending_service_workers_.size(),
1U)
<< "RevalidatePort() should only be called for opener ports which "
"correspond to a single 'context'.";
// NOTE: There is only one opener target.
if (!frames_.empty()) {
if (!frames_.begin()->second.is_connected()) {
UnregisterFrame(frames_.begin()->first);
}
return;
}
if (!service_workers_.empty()) {
if (!service_workers_.begin()->second.is_connected()) {
UnregisterWorker(service_workers_.begin()->first);
}
return;
}
}
void ExtensionMessagePort::DispatchOnConnect(
mojom::ChannelType channel_type,
const std::string& channel_name,
std::optional<base::Value::Dict> source_tab,
const ExtensionApiFrameIdMap::FrameData& source_frame,
int guest_process_id,
int guest_render_frame_routing_id,
const MessagingEndpoint& source_endpoint,
const std::string& target_extension_id,
const GURL& source_url,
std::optional<url::Origin> source_origin,
const std::set<base::UnguessableToken>& open_channel_tracking_ids) {
mojom::TabConnectionInfoPtr source = mojom::TabConnectionInfo::New();
// Source document ID should exist if and only if there is a source tab.
DCHECK_EQ(!!source_tab, !!source_frame.document_id);
if (source_tab) {
source->tab = source_tab->Clone();
source->document_id = source_frame.document_id.ToString();
source->document_lifecycle = ToString(source_frame.document_lifecycle);
}
source->frame_id = source_frame.frame_id;
mojom::ExternalConnectionInfoPtr info = mojom::ExternalConnectionInfo::New();
info->target_id = target_extension_id;
info->source_endpoint = source_endpoint;
info->source_url = source_url;
info->source_origin = std::move(source_origin);
info->guest_process_id = guest_process_id;
info->guest_render_frame_routing_id = guest_render_frame_routing_id;
pending_open_channel_tracking_ids_ = std::move(open_channel_tracking_ids);
// Open channel metrics for SW-based extensions dispatch.
if (!pending_service_workers_.empty()) {
base::UnguessableToken open_channel_dispatch_for_sw_tracking_id(
base::UnguessableToken::Create());
pending_open_channel_tracking_ids_.insert(
open_channel_dispatch_for_sw_tracking_id);
auto* message_tracker = MessageTracker::Get(browser_context_);
message_tracker->StartTrackingMessagingStage(
open_channel_dispatch_for_sw_tracking_id,
"Extensions.MessagePipeline."
"OpenChannelWorkerDispatchStatus",
channel_type);
}
// `ShouldSkipFrameForBFCache` could mutate `pending_frames_` so we
// take it before iterating on it.
auto pending_frames = std::move(pending_frames_);
for (const auto& frame_token : pending_frames) {
auto* frame = content::RenderFrameHost::FromFrameToken(frame_token);
if (!frame || ShouldSkipFrameForBFCache(frame)) {
continue;
}
mojo::PendingAssociatedReceiver<mojom::MessagePort> message_port;
mojo::PendingAssociatedRemote<mojom::MessagePortHost> message_port_host;
// Per channel open connect IPC dispatch metrics to frame.
base::UnguessableToken open_channel_dispatch_for_frame_tracking_id(
base::UnguessableToken::Create());
pending_open_channel_connect_dispatch_tracking_ids_.insert(
open_channel_dispatch_for_frame_tracking_id);
auto* message_tracker = MessageTracker::Get(browser_context_);
message_tracker->StartTrackingMessagingStage(
open_channel_dispatch_for_frame_tracking_id,
"Extensions.MessagePipeline.OpenChannelDispatchOnConnectStatus."
"ForFrame",
channel_type);
PortContext port_context = PortContext::ForFrame(frame->GetRoutingID());
auto& receiver = frames_[frame_token];
receiver.Bind(message_port.InitWithNewEndpointAndPassRemote());
receiver.set_disconnect_handler(base::BindOnce(
&ExtensionMessagePort::Prune, base::Unretained(this), port_context,
open_channel_dispatch_for_frame_tracking_id));
AddReceiver(message_port_host.InitWithNewEndpointAndPassReceiver(),
frame->GetProcess()->GetDeprecatedID(), port_context);
pending_contexts_to_respond_.insert(port_context);
ExtensionWebContentsObserver::GetForWebContents(
content::WebContents::FromRenderFrameHost(frame))
->GetLocalFrameChecked(frame)
.DispatchOnConnect(
port_id_, channel_type, channel_name, source.Clone(), info.Clone(),
std::move(message_port), std::move(message_port_host),
base::BindOnce(&ExtensionMessagePort::OnConnectResponse,
weak_ptr_factory_.GetWeakPtr(), port_context,
open_channel_dispatch_for_frame_tracking_id));
}
for (const auto& worker : pending_service_workers_) {
auto* host = ServiceWorkerHost::GetWorkerFor(worker);
if (host) {
auto* service_worker_remote = host->GetServiceWorker();
if (!service_worker_remote) {
continue;
}
mojo::PendingAssociatedReceiver<mojom::MessagePort> message_port;
mojo::PendingAssociatedRemote<mojom::MessagePortHost> message_port_host;
// Per channel open connect IPC dispatch metrics to worker.
base::UnguessableToken open_channel_dispatch_for_worker_tracking_id(
base::UnguessableToken::Create());
pending_open_channel_connect_dispatch_tracking_ids_.insert(
open_channel_dispatch_for_worker_tracking_id);
auto* message_tracker = MessageTracker::Get(browser_context_);
message_tracker->StartTrackingMessagingStage(
open_channel_dispatch_for_worker_tracking_id,
"Extensions.MessagePipeline.OpenChannelDispatchOnConnectStatus."
"ForWorker",
channel_type);
PortContext port_context =
PortContext::ForWorker(worker.thread_id, worker.version_id,
worker.render_process_id, worker.extension_id);
auto& receiver = service_workers_[worker];
receiver.Bind(message_port.InitWithNewEndpointAndPassRemote());
receiver.set_disconnect_handler(base::BindOnce(
&ExtensionMessagePort::Prune, base::Unretained(this), port_context,
open_channel_dispatch_for_worker_tracking_id));
AddReceiver(message_port_host.InitWithNewEndpointAndPassReceiver(),
worker.render_process_id, port_context);
pending_contexts_to_respond_.insert(port_context);
service_worker_remote->DispatchOnConnect(
port_id_, channel_type, channel_name, source.Clone(), info.Clone(),
std::move(message_port), std::move(message_port_host),
base::BindOnce(&ExtensionMessagePort::OnConnectResponse,
weak_ptr_factory_.GetWeakPtr(), port_context,
open_channel_dispatch_for_worker_tracking_id));
}
}
pending_frames_.clear();
pending_service_workers_.clear();
}
void ExtensionMessagePort::OnConnectResponse(
const PortContext& port_context,
const base::UnguessableToken& connect_dispatch_tracking_id,
bool success) {
// For the unsuccessful case the port will be cleaned up in `Prune` when
// the mojo channels are disconnected.
if (success) {
port_was_created_ = true;
}
// Overall channel open metrics tracking
pending_contexts_to_respond_.erase(port_context);
// Channel is considered opened if one response indicated that the port was
// created or if we've exhausted all responders and none have indicated a port
// was created. If this is never called MessageTracker handles the metric
// emit.
if (port_was_created_ || pending_contexts_to_respond_.empty()) {
ReportOpenChannelResult(
port_was_created_
? MessageTracker::OpenChannelMessagePipelineResult::kOpened
: MessageTracker::OpenChannelMessagePipelineResult::
kOpenChannelDispatchNoReceivers);
}
// Per channel open connect IPC dispatch metrics tracking.
ReportOpenChannelConnectDispatchResult(
connect_dispatch_tracking_id,
MessageTracker::OpenChannelMessagePipelineResult::kOpenChannelAcked);
// We'll continue tracking the messaging pipeline in MessageService where
// we'll track each dispatched message for this (now open) channel.
}
void ExtensionMessagePort::DispatchOnDisconnect(
const std::string& error_message) {
SendToPort(base::BindRepeating(
[](const std::string& error_message, mojom::MessagePort* port) {
port->DispatchDisconnect(error_message);
},
std::ref(error_message)));
}
void ExtensionMessagePort::DispatchOnMessage(const Message& message) {
// We increment activity for every message that passes through the channel.
// This is important for long-lived ports, which only keep an extension
// alive so long as they are being actively used.
IncrementLazyKeepaliveCount(Activity::MESSAGE);
// Since we are now receiving a message, we can mark any asynchronous reply
// that may have been pending for this port as no longer pending.
asynchronous_reply_pending_ = false;
SendToPort(base::BindRepeating(
[](const Message& message, mojom::MessagePort* port) {
port->DeliverMessage(message);
},
std::ref(message)));
DecrementLazyKeepaliveCount(Activity::MESSAGE);
}
void ExtensionMessagePort::IncrementLazyKeepaliveCount(
Activity::Type activity_type) {
ProcessManager* pm = ProcessManager::Get(browser_context_);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_);
if (host && BackgroundInfo::HasLazyBackgroundPage(host->extension())) {
pm->IncrementLazyKeepaliveCount(host->extension(), activity_type,
PortIdToString(port_id_));
}
// Keep track of the background host, so when we decrement, we only do so if
// the host hasn't reloaded.
background_host_ptr_ = host;
if (!IsServiceWorkerActivity(activity_type)) {
return;
}
// Increment keepalive count for service workers of the extension managed by
// this port.
// TODO(crbug.com/41487026): Add a check to only increment count if
// the port is in lazy context.
for (const auto& worker_id :
pm->GetServiceWorkersForExtension(extension_id_)) {
base::Uuid request_uuid = pm->IncrementServiceWorkerKeepaliveCount(
worker_id,
should_have_strong_keepalive()
? content::ServiceWorkerExternalRequestTimeoutType::kDoesNotTimeout
: content::ServiceWorkerExternalRequestTimeoutType::kDefault,
activity_type, PortIdToString(port_id_));
pending_keepalive_uuids_[worker_id].push_back(std::move(request_uuid));
}
}
void ExtensionMessagePort::DecrementLazyKeepaliveCount(
Activity::Type activity_type) {
ProcessManager* pm = ProcessManager::Get(browser_context_);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_);
if (host && host == background_host_ptr_) {
pm->DecrementLazyKeepaliveCount(host->extension(), activity_type,
PortIdToString(port_id_));
return;
}
if (!IsServiceWorkerActivity(activity_type)) {
return;
}
// Decrement keepalive count for service workers of the extension managed by
// this port.
// TODO(crbug.com/41487026): Add a check to only decrement count if
// the port is in lazy context.
for (const auto& worker_id :
pm->GetServiceWorkersForExtension(extension_id_)) {
auto iter = pending_keepalive_uuids_.find(worker_id);
if (iter == pending_keepalive_uuids_.end()) {
// We may not have a pending keepalive if this worker wasn't created at
// the time the message channel opened.
continue;
}
base::Uuid request_uuid = std::move(iter->second.back());
iter->second.pop_back();
if (iter->second.empty()) {
pending_keepalive_uuids_.erase(iter);
}
pm->DecrementServiceWorkerKeepaliveCount(
worker_id, request_uuid, activity_type, PortIdToString(port_id_));
}
}
void ExtensionMessagePort::NotifyResponsePending() {
asynchronous_reply_pending_ = true;
}
void ExtensionMessagePort::OpenPort(int process_id,
const PortContext& port_context) {
DCHECK((port_context.is_for_render_frame() &&
port_context.frame->routing_id != MSG_ROUTING_NONE) ||
(port_context.is_for_service_worker() &&
port_context.worker->thread_id != kMainThreadId) ||
for_all_extension_contexts_);
port_was_created_ = true;
}
void ExtensionMessagePort::ClosePort(int process_id,
int routing_id,
int worker_thread_id) {
const bool is_for_service_worker = worker_thread_id != kMainThreadId;
DCHECK(is_for_service_worker || routing_id != MSG_ROUTING_NONE);
if (is_for_service_worker) {
UnregisterWorker(process_id, worker_thread_id);
} else if (auto* render_frame_host =
content::RenderFrameHost::FromID(process_id, routing_id)) {
UnregisterFrame(render_frame_host);
}
}
void ExtensionMessagePort::CloseChannel(
std::optional<std::string> error_message) {
std::string error;
if (error_message.has_value()) {
error = std::move(error_message).value();
} else if (!port_was_created_) {
error = kReceivingEndDoesntExistError;
} else if (asynchronous_reply_pending_) {
error = kClosedWhileResponsePendingError;
}
if (weak_channel_delegate_) {
weak_channel_delegate_->CloseChannel(port_id_, error);
}
}
void ExtensionMessagePort::RegisterFrame(
content::RenderFrameHost* render_frame_host) {
// Only register a RenderFrameHost whose RenderFrame has been created, to
// ensure that we are notified of frame destruction. Without this check,
// `pending_frames_` can contain a stale token because RenderFrameDeleted
// is not triggered for `render_frame_host`.
if (render_frame_host->IsRenderFrameLive()) {
pending_frames_.insert(render_frame_host->GetGlobalFrameToken());
}
}
bool ExtensionMessagePort::UnregisterFrame(
content::RenderFrameHost* render_frame_host) {
return UnregisterFrame(render_frame_host->GetGlobalFrameToken());
}
bool ExtensionMessagePort::UnregisterFrame(
const content::GlobalRenderFrameHostToken& frame_token) {
frames_.erase(frame_token);
pending_frames_.erase(frame_token);
if (!IsValidPort()) {
CloseChannel();
return true;
}
return false;
}
bool ExtensionMessagePort::UnregisterFramesUnderMainFrame(
content::RenderFrameHost* main_frame,
std::optional<std::string> error_message) {
CHECK(pending_frames_.empty());
if (std::erase_if(frames_,
[&main_frame](const auto& item) {
auto* frame =
content::RenderFrameHost::FromFrameToken(item.first);
return !frame ||
frame->GetOutermostMainFrame() == main_frame;
}) != 0 &&
!IsValidPort()) {
CloseChannel(error_message);
return true;
}
return false;
}
void ExtensionMessagePort::RegisterWorker(const WorkerId& worker_id) {
DCHECK(!worker_id.extension_id.empty());
pending_service_workers_.insert(worker_id);
}
bool ExtensionMessagePort::UnregisterWorker(const WorkerId& worker_id) {
if (extension_id_ != worker_id.extension_id)
return false;
service_workers_.erase(worker_id);
pending_service_workers_.erase(worker_id);
if (!IsValidPort()) {
CloseChannel();
return true;
}
return false;
}
void ExtensionMessagePort::UnregisterWorker(int render_process_id,
int worker_thread_id) {
DCHECK_NE(kMainThreadId, worker_thread_id);
// Note: We iterate through *each* workers belonging to this port to find the
// worker we are interested in. Since there will only be a handful of such
// workers, this is OK.
for (auto iter = service_workers_.begin(); iter != service_workers_.end();) {
if (iter->first.render_process_id == render_process_id &&
iter->first.thread_id == worker_thread_id) {
service_workers_.erase(iter);
break;
} else {
++iter;
}
}
if (!IsValidPort()) {
CloseChannel();
}
}
void ExtensionMessagePort::SendToPort(SendCallback send_callback) {
// We should have called OnConnect before SentToPort.
CHECK(pending_frames_.empty());
CHECK(pending_service_workers_.empty());
std::vector<content::GlobalRenderFrameHostToken> frame_targets;
// Build the list of targets.
for (const auto& item : frames_) {
frame_targets.push_back(item.first);
}
for (const auto& target : frame_targets) {
auto item = frames_.find(target);
// `ShouldSkipFrameForBFCache` can mutate `frames_`, so verify the frame
// still exists.
if (item == frames_.end()) {
continue;
}
auto* frame = content::RenderFrameHost::FromFrameToken(item->first);
if (frame && !ShouldSkipFrameForBFCache(frame)) {
send_callback.Run(item->second.get());
}
}
for (const auto& running_worker : service_workers_) {
send_callback.Run(running_worker.second.get());
}
}
bool ExtensionMessagePort::IsServiceWorkerActivity(
Activity::Type activity_type) {
switch (activity_type) {
case Activity::MESSAGE:
return true;
case Activity::MESSAGE_PORT:
// long-lived message channels (such as through runtime.connect()) only
// increment keepalive when a message is sent so that a port doesn't count
// as a single, long-running task.
return is_for_onetime_channel() || should_have_strong_keepalive();
default:
// Extension message port should not check for other activity types.
NOTREACHED();
}
}
bool ExtensionMessagePort::ShouldSkipFrameForBFCache(
content::RenderFrameHost* render_frame_host) {
// Frames in the BackForwardCache are not allowed to receive messages (or
// even have them queued). In such a case, we evict the page from the cache
// and "drop" the message (See comment in `DidFinishNavigation()`).
// Note: Since this will cause the frame to be deleted, we do this here
// instead of in the loop above to avoid modifying `frames_` while it is
// being iterated.
//
// This could cause the same page to be evicted multiple times if it has
// multiple frames receiving this message. This is harmless as the reason is
// the same in every case. Also multiple extensions may send messages before
// the page is actually evicted. The last one will be the one the user
// sees. It is not worth the effort to present all of them to the user. It's
// unlikely they will see the same one every time and if they do, when they
// fix that one, they will see the others.
//
// TODO (crbug.com/1382623): currently we only make use of the base URL,
// it's also possible to get the full URL from extension ID so it could
// provide more useful context.
// The ExtensionMessagePort should be disconnected when the page enters
// BFCache, so no message will be sent to the BFCached target. There could
// be some messages that were created before the ExtensionMessagePort is
// disconnected, and they should be discarded.
return render_frame_host &&
render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
} // namespace extensions
|