| // 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); | 
 | } | 
 |  | 
 | bool PdfViewerStreamManager::ContainsUnclaimedStreamInfo( | 
 |     content::FrameTreeNodeId frame_tree_node_id) const { | 
 |   return base::Contains(stream_infos_, | 
 |                         GetUnclaimedEmbedderHostInfo(frame_tree_node_id)); | 
 | } | 
 |  | 
 | 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.GetScheme()); | 
 |         content::HostZoomMap::Get( | 
 |             navigation_handle->GetRenderFrameHost()->GetSiteInstance()) | 
 |             ->SetZoomLevelForHostAndScheme(pdf_extension_url.GetScheme(), | 
 |                                            pdf_extension_url.GetHost(), 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); | 
 | } | 
 |  | 
 | 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 |