File: pdf_viewer_stream_manager.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (830 lines) | stat: -rw-r--r-- 31,711 bytes parent folder | download | duplicates (5)
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
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/pdf/pdf_viewer_stream_manager.h"

#include <stdint.h>

#include <memory>
#include <tuple>
#include <utility>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/supports_user_data.h"
#include "components/crash/core/common/crash_key.h"
#include "components/pdf/browser/pdf_frame_util.h"
#include "components/pdf/common/pdf_util.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "extensions/common/api/mime_handler.mojom.h"
#include "extensions/common/constants.h"
#include "extensions/common/mojom/guest_view.mojom.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"

namespace pdf {

namespace {

// Static factory instance (always nullptr for non-test).
PdfViewerStreamManager::Factory* g_factory = nullptr;

// Creates a claimed `EmbedderHostInfo` from the `embedder_host`.
PdfViewerStreamManager::EmbedderHostInfo GetEmbedderHostInfo(
    const content::RenderFrameHost* embedder_host) {
  return {embedder_host->GetFrameTreeNodeId(), embedder_host->GetGlobalId()};
}

// Creates a new unclaimed `EmbedderHostInfo` for the given frame tree node ID
// (without the `content::GlobalRenderFrameHostId`).
PdfViewerStreamManager::EmbedderHostInfo GetUnclaimedEmbedderHostInfo(
    content::FrameTreeNodeId frame_tree_node_id) {
  return {frame_tree_node_id, content::GlobalRenderFrameHostId()};
}

// Gets the embedder host from the PDF content host's navigation handle.
content::RenderFrameHost* GetEmbedderHostFromPdfContentNavigation(
    content::NavigationHandle* navigation_handle) {
  // Since `navigation_handle` is for a PDF content frame, the parent frame is
  // the PDF extension frame, and the grandparent frame is the embedder frame.
  content::RenderFrameHost* extension_host =
      navigation_handle->GetParentFrame();
  CHECK(extension_host);

  return extension_host->GetParent();
}

// Gets the `extensions::mojom::MimeHandlerViewContainerManager` from the
// `container_host`.
mojo::AssociatedRemote<extensions::mojom::MimeHandlerViewContainerManager>
GetMimeHandlerViewContainerManager(content::RenderFrameHost* container_host) {
  CHECK(container_host);

  mojo::AssociatedRemote<extensions::mojom::MimeHandlerViewContainerManager>
      container_manager;
  container_host->GetRemoteAssociatedInterfaces()->GetInterface(
      &container_manager);
  return container_manager;
}

// Debugging data for crbug.com/391459596.
// TODO(crbug.com/391459596): Remove once fixed.
struct PdfNavigationDebugData : public base::SupportsUserData::Data {
  bool did_start_navigation = false;
  bool did_start_navigation_with_parent = false;
};

static int g_debug_manager_instances = 0;
static int g_debug_ongoing_content_navigations = 0;

// Manager crash keys.
static crash_reporter::CrashKeyString<32> crash_key_manager_instances(
    "pdf-manager-instances");
static crash_reporter::CrashKeyString<32> crash_key_stream_count(
    "pdf-stream-count");
static crash_reporter::CrashKeyString<32> crash_key_ongoing_content_navigations(
    "pdf-ongoing-content-navigations");

// PDF content navigation-specific crash keys.
static crash_reporter::CrashKeyString<6> crash_key_did_start_navigation(
    "pdf-did-start-navigation");
static crash_reporter::CrashKeyString<6>
    crash_key_did_start_navigation_with_parent(
        "pdf-did-start-navigation-with-parent");

void SetManagerCrashKeys(size_t stream_count) {
  crash_key_manager_instances.Set(base::ToString(g_debug_manager_instances));
  crash_key_stream_count.Set(base::ToString(stream_count));
  crash_key_ongoing_content_navigations.Set(
      base::ToString(g_debug_ongoing_content_navigations));
}

void SetContentNavigationCrashKeys(
    const content::NavigationHandle* navigation_handle,
    const void* key) {
  auto* data = navigation_handle->GetUserData(key);
  if (!data) {
    // Can be nullptr in tests.
    return;
  }

  auto* debug_data = static_cast<PdfNavigationDebugData*>(data);

  crash_key_did_start_navigation.Set(
      base::ToString(debug_data->did_start_navigation));
  crash_key_did_start_navigation_with_parent.Set(
      base::ToString(debug_data->did_start_navigation_with_parent));
}

void ClearContentNavigationCrashKeys() {
  crash_key_did_start_navigation.Clear();
  crash_key_did_start_navigation_with_parent.Clear();
}

}  // namespace

bool PdfViewerStreamManager::EmbedderHostInfo::operator<(
    const PdfViewerStreamManager::EmbedderHostInfo& other) const {
  return std::tie(frame_tree_node_id, global_id) <
         std::tie(other.frame_tree_node_id, other.global_id);
}

PdfViewerStreamManager::StreamInfo::StreamInfo(
    const std::string& embed_internal_id,
    std::unique_ptr<extensions::StreamContainer> stream_container)
    : internal_id_(embed_internal_id), stream_(std::move(stream_container)) {
  // Make sure 0 is never used because some APIs (particularly WebRequest) have
  // special meaning for 0 IDs.
  static int32_t next_instance_id = 0;
  instance_id_ = ++next_instance_id;
}

PdfViewerStreamManager::StreamInfo::~StreamInfo() = default;

void PdfViewerStreamManager::StreamInfo::SetDidExtensionFinishNavigation() {
  CHECK(!did_extension_finish_navigation_);
  did_extension_finish_navigation_ = true;
}

bool PdfViewerStreamManager::StreamInfo::DidPdfExtensionStartNavigation()
    const {
  return !!extension_host_frame_tree_node_id_;
}

bool PdfViewerStreamManager::StreamInfo::DidPdfContentNavigate() const {
  return container_manager_.is_bound();
}

PdfViewerStreamManager::PdfViewerStreamManager(content::WebContents* contents)
    : content::WebContentsObserver(contents),
      content::WebContentsUserData<PdfViewerStreamManager>(*contents) {
  ++g_debug_manager_instances;
}

PdfViewerStreamManager::~PdfViewerStreamManager() {
  --g_debug_manager_instances;
}

// static
void PdfViewerStreamManager::Create(content::WebContents* contents) {
  if (FromWebContents(contents)) {
    return;
  }

  if (g_factory) {
    g_factory->CreatePdfViewerStreamManager(contents);
  } else {
    // Using `new` to access a non-public constructor.
    contents->SetUserData(
        UserDataKey(), base::WrapUnique(new PdfViewerStreamManager(contents)));
  }
}

// static
PdfViewerStreamManager* PdfViewerStreamManager::FromRenderFrameHost(
    content::RenderFrameHost* render_frame_host) {
  return FromWebContents(
      content::WebContents::FromRenderFrameHost(render_frame_host));
}

// static
void PdfViewerStreamManager::SetFactoryForTesting(Factory* factory) {
  if (factory) {
    CHECK(!g_factory);
  }
  g_factory = factory;
}

void PdfViewerStreamManager::AddStreamContainer(
    content::FrameTreeNodeId frame_tree_node_id,
    const std::string& internal_id,
    std::unique_ptr<extensions::StreamContainer> stream_container) {
  CHECK(stream_container);

  // If an entry with the same frame tree node ID already exists in
  // `stream_infos_`, then a new PDF navigation has occurred. If the
  // existing `StreamInfo` hasn't been claimed, replace the entry. This is safe,
  // since `GetStreamContainer()` verifies the original PDF URL. If the existing
  // `StreamInfo` has been claimed, then it will eventually be deleted, and the
  // new `StreamInfo` will be used instead. This can occur if a full page PDF
  // viewer refreshes or navigates to another PDF URL.
  auto embedder_host_info = GetUnclaimedEmbedderHostInfo(frame_tree_node_id);
  stream_infos_[embedder_host_info] =
      std::make_unique<StreamInfo>(internal_id, std::move(stream_container));
}

base::WeakPtr<extensions::StreamContainer>
PdfViewerStreamManager::GetStreamContainer(
    content::RenderFrameHost* embedder_host) {
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  if (!stream_info) {
    return nullptr;
  }

  // It's possible to have multiple `extensions::StreamContainer`s under the
  // same frame tree node ID. Verify the original URL in the stream container to
  // avoid a potential URL spoof.
  if (embedder_host->GetLastCommittedURL() !=
      stream_info->stream()->original_url()) {
    return nullptr;
  }

  return stream_info->stream()->GetWeakPtr();
}

bool PdfViewerStreamManager::IsPdfExtensionHost(
    const content::RenderFrameHost* render_frame_host) const {
  // The PDF extension host should always have a parent host (the embedder
  // host).
  const content::RenderFrameHost* parent_host = render_frame_host->GetParent();
  if (!parent_host) {
    return false;
  }

  return IsPdfExtensionFrameTreeNodeId(parent_host,
                                       render_frame_host->GetFrameTreeNodeId());
}

bool PdfViewerStreamManager::IsPdfExtensionFrameTreeNodeId(
    const content::RenderFrameHost* embedder_host,
    content::FrameTreeNodeId frame_tree_node_id) const {
  const auto* stream_info = GetClaimedStreamInfo(embedder_host);
  return stream_info &&
         frame_tree_node_id == stream_info->extension_host_frame_tree_node_id();
}

bool PdfViewerStreamManager::DidPdfExtensionFinishNavigation(
    const content::RenderFrameHost* embedder_host) const {
  const auto* stream_info = GetClaimedStreamInfo(embedder_host);
  return stream_info && stream_info->did_extension_finish_navigation();
}

bool PdfViewerStreamManager::IsPdfContentHost(
    const content::RenderFrameHost* render_frame_host) const {
  // The PDF content host should always have a parent host.
  content::RenderFrameHost* parent_host = render_frame_host->GetParent();
  if (!parent_host) {
    return false;
  }

  // The parent host should always be the PDF extension host.
  if (!IsPdfExtensionHost(parent_host)) {
    return false;
  }

  // The PDF extension host should always have a parent host (the embedder
  // host).
  content::RenderFrameHost* embedder_host = parent_host->GetParent();
  CHECK(embedder_host);
  return IsPdfContentFrameTreeNodeId(embedder_host,
                                     render_frame_host->GetFrameTreeNodeId());
}

bool PdfViewerStreamManager::IsPdfContentFrameTreeNodeId(
    const content::RenderFrameHost* embedder_host,
    content::FrameTreeNodeId frame_tree_node_id) const {
  const auto* stream_info = GetClaimedStreamInfo(embedder_host);
  return stream_info &&
         frame_tree_node_id == stream_info->content_host_frame_tree_node_id();
}

bool PdfViewerStreamManager::DidPdfContentNavigate(
    const content::RenderFrameHost* embedder_host) const {
  const auto* stream_info = GetClaimedStreamInfo(embedder_host);
  return stream_info && stream_info->DidPdfContentNavigate();
}

bool PdfViewerStreamManager::PluginCanSave(
    const content::RenderFrameHost* embedder_host) const {
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  return stream_info && stream_info->plugin_can_save();
}

void PdfViewerStreamManager::SetPluginCanSave(
    content::RenderFrameHost* embedder_host,
    bool plugin_can_save) {
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  if (!stream_info) {
    return;
  }

  stream_info->set_plugin_can_save(plugin_can_save);
}

void PdfViewerStreamManager::DeleteUnclaimedStreamInfo(
    content::FrameTreeNodeId frame_tree_node_id) {
  CHECK(stream_infos_.erase(GetUnclaimedEmbedderHostInfo(frame_tree_node_id)));

  if (stream_infos_.empty()) {
    web_contents()->RemoveUserData(UserDataKey());
    // DO NOT add code past this point. RemoveUserData() deleted `this`.
  }
}

void PdfViewerStreamManager::RenderFrameDeleted(
    content::RenderFrameHost* render_frame_host) {
  // When the PDF embedder frame is deleted, delete its stream.
  if (GetClaimedStreamInfo(render_frame_host)) {
    DeleteClaimedStreamInfo(render_frame_host);
    // DO NOT add code past this point. `this` may have been deleted.
    return;
  }

  // If `render_frame_host` isn't active, ignore. An unclaimed `StreamInfo`'s
  // FrameTreeNode may delete a speculative `content::RenderFrameHost` before
  // the embedder `content::RenderFrameHost` commits and claims the stream. The
  // speculative `content::RenderFrameHost` won't be considered active, and
  // shouldn't cause the stream to be deleted.
  if (!render_frame_host->IsActive()) {
    return;
  }

  // If `render_frame_host` is an unrelated host (there isn't an unclaimed
  // stream), ignore.
  content::FrameTreeNodeId frame_tree_node_id =
      render_frame_host->GetFrameTreeNodeId();
  if (!ContainsUnclaimedStreamInfo(frame_tree_node_id)) {
    return;
  }

  DeleteUnclaimedStreamInfo(frame_tree_node_id);
  // DO NOT add code past this point. `this` may have been deleted.
}

void PdfViewerStreamManager::RenderFrameHostChanged(
    content::RenderFrameHost* old_host,
    content::RenderFrameHost* new_host) {
  // If the `old_host` is null, then it means that a subframe is being created.
  // Don't treat this like a host change.
  if (!old_host) {
    return;
  }

  if (MaybeDeleteStreamOnPdfExtensionHostChanged(old_host) ||
      MaybeDeleteStreamOnPdfContentHostChanged(old_host)) {
    // DO NOT add code past this point. `this` may have been deleted.
    return;
  }

  // If this is an unrelated host, ignore.
  if (!GetClaimedStreamInfo(old_host)) {
    return;
  }

  // The `old_host`'s `StreamInfo` should be deleted since this event could be
  // triggered from navigating the embedder host to a non-PDF URL. If the
  // embedder host is navigating to another PDF URL, then a new `StreamInfo`
  // should have already been created and claimed by `new_host`, so it's still
  // safe to delete `old_host`'s `StreamInfo`.
  DeleteClaimedStreamInfo(old_host);
  // DO NOT add code past this point. `this` may have been deleted.
}

void PdfViewerStreamManager::FrameDeleted(
    content::FrameTreeNodeId frame_tree_node_id) {
  // If a PDF host is deleted, delete the associated `StreamInfo`.
  for (auto iter = stream_infos_.begin(); iter != stream_infos_.end();) {
    StreamInfo* stream_info = iter->second.get();
    // Check if `frame_tree_node_id` is a PDF host's frame tree node ID.
    //
    // Deleting the stream for the extension host and the content host here
    // should be almost equivalent to how
    // `MaybeDeleteStreamOnPdfExtensionHostChanged()` and
    // `MaybeDeleteStreamOnPdfContentHostChanged()` delete the stream. However,
    // there is only a frame tree node ID here and not a
    // `content::RenderFrameHost`, so deleting the stream requires iterating
    // over all `StreamInfo` instances.
    if (frame_tree_node_id == iter->first.frame_tree_node_id ||
        frame_tree_node_id ==
            stream_info->extension_host_frame_tree_node_id() ||
        frame_tree_node_id == stream_info->content_host_frame_tree_node_id()) {
      if (stream_info->mime_handler_view_container_manager()) {
        stream_info->mime_handler_view_container_manager()
            ->DestroyFrameContainer(stream_info->instance_id());
      }

      iter = stream_infos_.erase(iter);
    } else {
      ++iter;
    }
  }

  // Delete `this` if there are no remaining stream infos.
  if (stream_infos_.empty()) {
    web_contents()->RemoveUserData(UserDataKey());
    // DO NOT add code past this point. RemoveUserData() deleted `this`.
  }
}

void PdfViewerStreamManager::DidStartNavigation(
    content::NavigationHandle* navigation_handle) {
  // Set the content host frame tree node ID if the navigation is for a content
  // host. This needs to occur before the network request for the PDF content
  // navigation so that
  // `ChromePdfStreamDelegate::ShouldAllowPdfFrameNavigation()` can properly
  // check that the navigation is allowed.
  if (navigation_handle->IsPdf()) {
    ++g_debug_ongoing_content_navigations;
    auto debug_data = std::make_unique<PdfNavigationDebugData>();
    debug_data->did_start_navigation = true;
    debug_data->did_start_navigation_with_parent =
        navigation_handle->GetParentFrame();
    navigation_handle->SetUserData(UserDataKey(), std::move(debug_data));
    SetManagerCrashKeys(stream_infos_.size());
    SetContentNavigationCrashKeys(navigation_handle, UserDataKey());

    SetStreamContentHostFrameTreeNodeId(navigation_handle);
  }
}

void PdfViewerStreamManager::ReadyToCommitNavigation(
    content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsPdf()) {
    SetManagerCrashKeys(stream_infos_.size());
    SetContentNavigationCrashKeys(navigation_handle, UserDataKey());
  }

  // Maybe register a PDF subresource override in the PDF content host.
  if (MaybeRegisterPdfSubresourceOverride(navigation_handle)) {
    return;
  }

  // The initial load notification for the URL being served in the embedder
  // host. The `embedder_host` should claim the unclaimed `StreamInfo`. This
  // should replace any existing `StreamInfo` objects related to
  // `embedder_host`. This is safe since `GetStreamContainer()` checks the
  // original URL for URL spoofs, and any security-relevant changes in the
  // response should result in a different `content::RenderFrameHost`.
  content::RenderFrameHost* embedder_host =
      navigation_handle->GetRenderFrameHost();
  if (!ContainsUnclaimedStreamInfo(embedder_host->GetFrameTreeNodeId())) {
    return;
  }

  StreamInfo* claimed_stream_info = ClaimStreamInfo(embedder_host);

  // Set the internal ID to set up postMessage later, when the PDF content host
  // finishes navigating.
  auto container_manager = GetMimeHandlerViewContainerManager(embedder_host);
  container_manager->SetInternalId(claimed_stream_info->internal_id());
}

void PdfViewerStreamManager::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsPdf()) {
    --g_debug_ongoing_content_navigations;
    SetManagerCrashKeys(stream_infos_.size());
    ClearContentNavigationCrashKeys();
  }

  // Maybe set up postMessage support after the PDF content host finishes
  // navigating.
  if (MaybeSetUpPostMessage(navigation_handle)) {
    return;
  }

  // The rest of the method handles the extension host. The parent host should
  // be the tracked embedder host.
  content::RenderFrameHost* embedder_host = navigation_handle->GetParentFrame();
  if (!embedder_host) {
    return;
  }

  // The `StreamInfo` should already have been claimed by the time the extension
  // host navigates.
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  if (!stream_info) {
    return;
  }

  // If the extension host has already started its navigation to the PDF
  // extension URL, set the extension as finished navigating, ignoring other
  // children of the embedder host.
  if (stream_info->DidPdfExtensionStartNavigation()) {
    if (stream_info->extension_host_frame_tree_node_id() ==
        navigation_handle->GetFrameTreeNodeId()) {
      stream_info->SetDidExtensionFinishNavigation();
      if (navigation_handle->HasCommitted() &&
          !navigation_handle->IsErrorPage()) {
        // Setup zoom level for the PDF extension. Zoom level 0 corresponds
        // to zoom factor of 1, or 100%. This is done so the PDF viewer UI
        // does not change if the page zoom does. This is analogous to page
        // zoom not affecting the browser UI.
        const GURL pdf_extension_url = stream_info->stream()->handler_url();
        CHECK_EQ(pdf_extension_url, navigation_handle->GetURL());
        CHECK_EQ(extensions::kExtensionScheme, pdf_extension_url.scheme());
        content::HostZoomMap::Get(
            navigation_handle->GetRenderFrameHost()->GetSiteInstance())
            ->SetZoomLevelForHostAndScheme(pdf_extension_url.scheme(),
                                           pdf_extension_url.host(), 0);
        // Set ZoomController on the extension host.
        zoom::ZoomController::CreateForWebContentsAndRenderFrameHost(
            web_contents(),
            navigation_handle->GetRenderFrameHost()->GetGlobalId());
      }
    }
    return;
  }

  // During PDF navigation, in the embedder host, an about:blank embed is
  // inserted in a synthetic HTML document as a placeholder for the PDF
  // extension. Navigate the about:blank embed to the PDF extension URL to load
  // the PDF extension.
  if (!navigation_handle->GetURL().IsAboutBlank()) {
    return;
  }

  content::RenderFrameHost* about_blank_host =
      navigation_handle->GetRenderFrameHost();
  if (!about_blank_host) {
    return;
  }

  // `about_blank_host`'s FrameTreeNode will be reused for the extension
  // `content::RenderFrameHost`, so it is safe to set it in `stream_info` to
  // identify both hosts.
  content::FrameTreeNodeId extension_host_frame_tree_node_id =
      about_blank_host->GetFrameTreeNodeId();
  stream_info->set_extension_host_frame_tree_node_id(
      extension_host_frame_tree_node_id);

  NavigateToPdfExtensionUrl(extension_host_frame_tree_node_id, stream_info,
                            embedder_host->GetSiteInstance(),
                            about_blank_host->GetGlobalId());
}

void PdfViewerStreamManager::ClaimStreamInfoForTesting(
    content::RenderFrameHost* embedder_host) {
  ClaimStreamInfo(embedder_host);
}

void PdfViewerStreamManager::SetExtensionFrameTreeNodeIdForTesting(
    content::RenderFrameHost* embedder_host,
    content::FrameTreeNodeId frame_tree_node_id) {
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  CHECK(stream_info);

  stream_info->set_extension_host_frame_tree_node_id(frame_tree_node_id);
}

void PdfViewerStreamManager::SetContentFrameTreeNodeIdForTesting(
    content::RenderFrameHost* embedder_host,
    content::FrameTreeNodeId frame_tree_node_id) {
  auto* stream_info = GetClaimedStreamInfo(embedder_host);
  CHECK(stream_info);

  stream_info->set_content_host_frame_tree_node_id(frame_tree_node_id);
}

void PdfViewerStreamManager::NavigateToPdfExtensionUrl(
    content::FrameTreeNodeId extension_host_frame_tree_node_id,
    StreamInfo* stream_info,
    content::SiteInstance* source_site_instance,
    content::GlobalRenderFrameHostId global_id) {
  CHECK(stream_info);

  content::NavigationController::LoadURLParams params(
      stream_info->stream()->handler_url());
  params.frame_tree_node_id = extension_host_frame_tree_node_id;
  params.source_site_instance = source_site_instance;
  web_contents()->GetController().LoadURLWithParams(params);
}

PdfViewerStreamManager::StreamInfo*
PdfViewerStreamManager::GetClaimedStreamInfo(
    const content::RenderFrameHost* embedder_host) {
  auto iter = stream_infos_.find(GetEmbedderHostInfo(embedder_host));
  return iter != stream_infos_.end() ? iter->second.get() : nullptr;
}

const PdfViewerStreamManager::StreamInfo*
PdfViewerStreamManager::GetClaimedStreamInfo(
    const content::RenderFrameHost* embedder_host) const {
  auto iter = stream_infos_.find(GetEmbedderHostInfo(embedder_host));
  return iter != stream_infos_.end() ? iter->second.get() : nullptr;
}

PdfViewerStreamManager::StreamInfo*
PdfViewerStreamManager::GetClaimedStreamInfoFromPdfContentNavigation(
    content::NavigationHandle* navigation_handle) {
  if (!navigation_handle->IsPdf()) {
    return nullptr;
  }

  // `navigation_handle` is for a PDF content frame, as checked by
  // `NavigationHandle::IsPdf()`.
  content::RenderFrameHost* embedder_host =
      GetEmbedderHostFromPdfContentNavigation(navigation_handle);
  CHECK(embedder_host);

  return GetClaimedStreamInfo(embedder_host);
}

bool PdfViewerStreamManager::ContainsUnclaimedStreamInfo(
    content::FrameTreeNodeId frame_tree_node_id) const {
  return base::Contains(stream_infos_,
                        GetUnclaimedEmbedderHostInfo(frame_tree_node_id));
}

PdfViewerStreamManager::StreamInfo* PdfViewerStreamManager::ClaimStreamInfo(
    content::RenderFrameHost* embedder_host) {
  auto unclaimed_embedder_info =
      GetUnclaimedEmbedderHostInfo(embedder_host->GetFrameTreeNodeId());
  auto iter = stream_infos_.find(unclaimed_embedder_info);
  CHECK(iter != stream_infos_.end());

  PdfViewerStreamManager::StreamInfo* stream_info = iter->second.get();

  auto claimed_embedder_info = GetEmbedderHostInfo(embedder_host);
  stream_infos_[claimed_embedder_info] = std::move(iter->second);
  stream_infos_.erase(iter);

  return stream_info;
}

void PdfViewerStreamManager::DeleteClaimedStreamInfo(
    content::RenderFrameHost* embedder_host) {
  auto iter = stream_infos_.find(GetEmbedderHostInfo(embedder_host));
  CHECK(iter != stream_infos_.end());

  StreamInfo* stream_info = iter->second.get();
  if (stream_info->mime_handler_view_container_manager()) {
    stream_info->mime_handler_view_container_manager()->DestroyFrameContainer(
        stream_info->instance_id());
  }

  stream_infos_.erase(iter);

  if (stream_infos_.empty()) {
    web_contents()->RemoveUserData(UserDataKey());
    // DO NOT add code past this point. RemoveUserData() deleted `this`.
  }
}

bool PdfViewerStreamManager::MaybeDeleteStreamOnPdfExtensionHostChanged(
    content::RenderFrameHost* old_host) {
  if (!IsPdfExtensionHost(old_host)) {
    return false;
  }

  // In a PDF load, the initial RFH for the PDF extension frame commits an
  // initial about:blank URL. Don't delete the stream when this RFH changes.
  // Another RFH will be chosen to host the PDF extension, with the PDF
  // extension URL.
  if (old_host->GetLastCommittedURL().IsAboutBlank()) {
    return false;
  }

  content::RenderFrameHost* embedder_host = old_host->GetParent();
  CHECK(embedder_host);

  DeleteClaimedStreamInfo(embedder_host);
  // DO NOT add code past this point. `this` may have been deleted.

  return true;
}

bool PdfViewerStreamManager::MaybeDeleteStreamOnPdfContentHostChanged(
    content::RenderFrameHost* old_host) {
  if (!IsPdfContentHost(old_host)) {
    return false;
  }

  content::RenderFrameHost* embedder_host =
      pdf_frame_util::GetEmbedderHost(old_host);
  CHECK(embedder_host);
  auto* stream_info = GetClaimedStreamInfo(embedder_host);

  // In a PDF load, the initial RFH for the PDF content frame is created for the
  // navigation to the PDF stream URL. This navigation is canceled in
  // `pdf::PdfNavigationThrottle::WillStartRequest()` and never commits. The
  // initial RFH and the actual PDF content RFH have the same frame tree node
  // ID, but the actual PDF content RFH commits its navigation to the original
  // PDF URL. Don't delete the stream when the initial RFH changes.
  const GURL& url = old_host->GetLastCommittedURL();
  if (url.is_empty()) {
    return false;
  }
  CHECK(url == stream_info->stream()->original_url());

  DeleteClaimedStreamInfo(embedder_host);
  // DO NOT add code past this point. `this` may have been deleted.

  return true;
}

bool PdfViewerStreamManager::MaybeRegisterPdfSubresourceOverride(
    content::NavigationHandle* navigation_handle) {
  // Only register the subresource override if `navigation_handle` is for the
  // PDF content frame. Ignore all other navigations in different frames, such
  // as navigations in the embedder frame or PDF extension frame.
  auto* claimed_stream_info =
      GetClaimedStreamInfoFromPdfContentNavigation(navigation_handle);
  if (!claimed_stream_info) {
    return false;
  }

  navigation_handle->RegisterSubresourceOverride(
      claimed_stream_info->stream()->TakeTransferrableURLLoader());

  return true;
}

bool PdfViewerStreamManager::MaybeSetUpPostMessage(
    content::NavigationHandle* navigation_handle) {
  // Only set up postMessage if `navigation_handle` is for a PDF content frame.
  auto* claimed_stream_info =
      GetClaimedStreamInfoFromPdfContentNavigation(navigation_handle);
  if (!claimed_stream_info) {
    return false;
  }

  // If the user reloads the PDF URL before the PDF content frame finishes
  // loading, the initial PDF content frame navigation might reach
  // `MaybeSetUpPostMessage()` during the new PDF load and incorrectly set up
  // the stream info. Only continue PDF setup if the PDF content navigation is
  // for the new PDF load.
  if (navigation_handle->GetFrameTreeNodeId() !=
      claimed_stream_info->content_host_frame_tree_node_id()) {
    return false;
  }

  // `navigation_handle` is for a PDF content frame, as checked by
  // `NavigationHandle::IsPdf()`.
  content::RenderFrameHost* embedder_host =
      GetEmbedderHostFromPdfContentNavigation(navigation_handle);
  CHECK(embedder_host);

  // If `owner_type` is kEmbed or kObject, then the PDF is embedded onto another
  // HTML page. `container_host` should be the PDF embedder host's parent.
  // Otherwise, the PDF is full-page, in which `container_host` should be the
  // PDF embedder host itself.
  auto owner_type = embedder_host->GetFrameOwnerElementType();
  bool is_full_page = owner_type != blink::FrameOwnerElementType::kEmbed &&
                      owner_type != blink::FrameOwnerElementType::kObject;
  auto* container_host =
      is_full_page ? embedder_host : embedder_host->GetParent();
  CHECK(container_host);

  auto container_manager = GetMimeHandlerViewContainerManager(container_host);

  // Set up beforeunload support for full page PDF viewer, which will also help
  // set up postMessage support.
  if (is_full_page) {
    container_manager->CreateBeforeUnloadControl(
        base::BindOnce(&PdfViewerStreamManager::SetUpBeforeUnloadControl,
                       weak_factory_.GetWeakPtr()));
  }

  // Enable postMessage support.
  // The first parameter for DidLoad() is
  // mime_handler_view_guest_element_instance_id, which is used to identify and
  // delete `extensions::MimeHandlerViewFrameContainer` objects. However, OOPIF
  // PDF viewer doesn't have a guest element instance ID. Use the instance ID
  // instead, which is a unique ID for `StreamInfo`.
  container_manager->DidLoad(claimed_stream_info->instance_id(),
                             claimed_stream_info->stream()->original_url());
  claimed_stream_info->set_mime_handler_view_container_manager(
      std::move(container_manager));

  // Now that postMessage is set up, the PDF viewer has finished loading, so
  // update metrics.
  ReportPDFLoadStatus(embedder_host->IsInPrimaryMainFrame()
                          ? PDFLoadStatus::kLoadedFullPagePdfWithPdfium
                          : PDFLoadStatus::kLoadedEmbeddedPdfWithPdfium);
  // TODO(b:289010799): Call `RecordPDFOpenedWithA11yFeatureWithPdfOcr`in
  // pdf_ocr_util.cc after figuring out how to fix the build dependency issue.

  return true;
}

void PdfViewerStreamManager::SetStreamContentHostFrameTreeNodeId(
    content::NavigationHandle* navigation_handle) {
  auto* claimed_stream_info =
      GetClaimedStreamInfoFromPdfContentNavigation(navigation_handle);
  CHECK(claimed_stream_info);
  claimed_stream_info->set_content_host_frame_tree_node_id(
      navigation_handle->GetFrameTreeNodeId());
}

void PdfViewerStreamManager::SetUpBeforeUnloadControl(
    mojo::PendingRemote<extensions::mime_handler::BeforeUnloadControl>
        before_unload_control_remote) {
  // TODO(crbug.com/40268279): Currently a no-op. Support the beforeunload API.
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(PdfViewerStreamManager);

}  // namespace pdf