blob: 6c7803ef2305046b7bb93062b3f09a01e410c1f6 [file] [log] [blame]
// 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.
#ifndef CHROME_BROWSER_PDF_PDF_VIEWER_STREAM_MANAGER_H_
#define CHROME_BROWSER_PDF_PDF_VIEWER_STREAM_MANAGER_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <optional>
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "extensions/common/mojom/guest_view.mojom.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
namespace content {
struct GlobalRenderFrameHostId;
class NavigationHandle;
class RenderFrameHost;
class SiteInstance;
class WebContents;
} // namespace content
namespace extensions {
namespace mime_handler {
class BeforeUnloadControl;
}
class StreamContainer;
} // namespace extensions
namespace pdf {
// `PdfViewerStreamManager` is used for PDF navigation. It tracks all
// PDF navigation events in a `content::WebContents`. It handles multiple PDF
// viewer instances in a single `content::WebContents`. It is responsible for:
// 1. Storing the `extensions::StreamContainer` PDF data.
// 2. Observing for the PDF frames either navigating or closing (including by
// crashing). This is necessary to ensure that streams that aren't claimed
// are not leaked, by deleting the stream if any of those events occur.
// 3. Observing for the RFH created by the PDF embedder RFH to load the PDF
// extension URL.
// 4. Observing for the PDF content RFH to register the stream as a subresource
// override for the final PDF commit navigation and to set up postMessage
// support.
// `PdfViewerStreamManager` is scoped to the `content::WebContents` it tracks,
// but it may also delete itself if all PDF streams are no longer used.
// `extensions::StreamContainer` objects are stored from
// `PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse()` until
// the PDF viewer is no longer in use.
//
// Use `PdfViewerStreamManager::Create()` to create an instance.
// Use `PdfViewerStreamManager::FromWebContents()` to get an instance.
class PdfViewerStreamManager
: public content::WebContentsObserver,
public content::WebContentsUserData<PdfViewerStreamManager> {
public:
// A factory interface used to generate test PDF stream managers.
class Factory {
public:
// If PdfViewerStreamManager has a factory set, then
// `PdfViewerStreamManager::Create()` will automatically use
// `CreatePdfViewerStreamManager()` to create the PDF stream manager if
// necessary for PDF navigations.
virtual void CreatePdfViewerStreamManager(
content::WebContents* contents) = 0;
protected:
virtual ~Factory() = default;
};
// Information about the PDF embedder RFH needed to store and retrieve stream
// containers.
struct EmbedderHostInfo {
// Need this comparator since this struct is used as a key in the
// `stream_infos_` map.
bool operator<(const EmbedderHostInfo& other) const;
// Using the frame tree node ID to identify the embedder RFH is necessary
// because entries are added during
// `PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse()`,
// before the embedder's frame tree node has swapped from its previous RFH
// to the embedder RFH that will hold the PDF.
content::FrameTreeNodeId frame_tree_node_id;
content::GlobalRenderFrameHostId global_id;
};
// Creates a `PdfViewerStreamManager` for `contents`, if one doesn't already
// exist.
static void Create(content::WebContents* contents);
// Use `Create()` to create an instance instead.
static void CreateForWebContents(content::WebContents*) = delete;
PdfViewerStreamManager(const PdfViewerStreamManager&) = delete;
PdfViewerStreamManager& operator=(const PdfViewerStreamManager&) = delete;
~PdfViewerStreamManager() override;
// Returns a pointer to the `PdfViewerStreamManager` instance associated with
// the `content::WebContents` of `render_frame_host`.
static PdfViewerStreamManager* FromRenderFrameHost(
content::RenderFrameHost* render_frame_host);
// Overrides factory for testing. Default (nullptr) value indicates regular
// (non-test) environment.
static void SetFactoryForTesting(Factory* factory);
// Starts tracking a `StreamContainer` in an embedder FrameTreeNode, before
// the embedder host commits. The `StreamContainer` is considered unclaimed
// until the embedder host commits, at which point the `StreamContainer` is
// tracked by both the frame tree node ID and the render frame host ID.
// Replaces existing unclaimed entries with the same `frame_tree_node_id`.
// This can occur if an embedder frame navigating to a PDF starts navigating
// to another PDF URL before the original `StreamContainer` is claimed.
void AddStreamContainer(
content::FrameTreeNodeId frame_tree_node_id,
const std::string& internal_id,
std::unique_ptr<extensions::StreamContainer> stream_container);
// Returns a pointer to a stream container that `embedder_host` has claimed or
// nullptr if `embedder_host` hasn't claimed any stream containers.
base::WeakPtr<extensions::StreamContainer> GetStreamContainer(
content::RenderFrameHost* embedder_host);
// Returns true if `render_frame_host` is an extension host for a PDF. During
// a PDF load, the initial RFH for the extension frame commits to the
// about:blank URL. Another RFH will then be chosen to host the extension.
// This returns true for both hosts. Depending on what navigation step the
// frame is on, callers can also check the last committed origin to
// differentiate between the hosts.
bool IsPdfExtensionHost(
const content::RenderFrameHost* render_frame_host) const;
// Returns true if `frame_tree_node_id` is the frame tree node ID for the PDF
// extension frame under `embedder_host`, false otherwise.
bool IsPdfExtensionFrameTreeNodeId(
const content::RenderFrameHost* embedder_host,
content::FrameTreeNodeId frame_tree_node_id) const;
// Returns true if `embedder_host` has a PDF extension frame and it has
// already finished its navigation, false otherwise.
bool DidPdfExtensionFinishNavigation(
const content::RenderFrameHost* embedder_host) const;
// Returns true if `render_frame_host` is a content host for a PDF. During a
// PDF load, the initial RFH for the content frame attempts to navigate to the
// stream URL. Another RFH will then be chosen to host the content frame. This
// returns true for both hosts. Depending on what navigation step the frame is
// on, callers can also check the last committed URL to differentiate between
// the hosts.
bool IsPdfContentHost(
const content::RenderFrameHost* render_frame_host) const;
// Returns true if `frame_tree_node_id` is the frame tree node ID for the PDF
// content frame under `embedder_host`, false otherwise.
bool IsPdfContentFrameTreeNodeId(
const content::RenderFrameHost* embedder_host,
content::FrameTreeNodeId frame_tree_node_id) const;
// Returns true if `embedder_host` has a PDF content frame and it has already
// finished its navigation, false otherwise.
bool DidPdfContentNavigate(
const content::RenderFrameHost* embedder_host) const;
// Returns whether the PDF plugin should handle save events.
bool PluginCanSave(const content::RenderFrameHost* embedder_host) const;
// Set whether the PDF plugin should handle save events.
void SetPluginCanSave(content::RenderFrameHost* embedder_host,
bool plugin_can_save);
// Returns whether there's an unclaimed stream info with the default embedder
// host info.
bool ContainsUnclaimedStreamInfo(
content::FrameTreeNodeId frame_tree_node_id) const;
// Deletes the unclaimed stream info associated with `frame_tree_node_id`, and
// deletes `this` if there are no remaining stream infos. Callers must ensure
// such a stream info exists before calling this, otherwise crashes.
void DeleteUnclaimedStreamInfo(content::FrameTreeNodeId frame_tree_node_id);
// WebContentsObserver overrides.
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) override;
void FrameDeleted(content::FrameTreeNodeId frame_tree_node_id) override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
// For testing only. Mark an unclaimed stream info with the same frame tree
// node ID as `embedder_host` as claimed by `embedder_host`. Callers must
// ensure such a stream info exists before calling this.
void ClaimStreamInfoForTesting(content::RenderFrameHost* embedder_host);
// For testing only. Set `embedder_host`'s extension frame tree node ID as
// `frame_tree_node_id`. This is needed to listen for extension host deletion.
// Callers must ensure that `embedder_host` has a claimed stream info.
void SetExtensionFrameTreeNodeIdForTesting(
content::RenderFrameHost* embedder_host,
content::FrameTreeNodeId frame_tree_node_id);
// For testing only. Set `embedder_host`'s content frame tree node ID as
// `frame_tree_node_id`. This is needed to listen for content host deletion.
// Callers must ensure that `embedder_host` has a claimed stream info.
void SetContentFrameTreeNodeIdForTesting(
content::RenderFrameHost* embedder_host,
content::FrameTreeNodeId frame_tree_node_id);
protected:
// Stream container stored for a single PDF navigation.
class StreamInfo {
public:
StreamInfo(const std::string& embed_internal_id,
std::unique_ptr<extensions::StreamContainer> stream_container);
StreamInfo(const StreamInfo&) = delete;
StreamInfo& operator=(const StreamInfo&) = delete;
~StreamInfo();
const std::string& internal_id() const { return internal_id_; }
extensions::StreamContainer* stream() { return stream_.get(); }
bool did_extension_finish_navigation() const {
return did_extension_finish_navigation_;
}
const mojo::AssociatedRemote<
extensions::mojom::MimeHandlerViewContainerManager>&
mime_handler_view_container_manager() const {
return container_manager_;
}
void set_mime_handler_view_container_manager(
mojo::AssociatedRemote<
extensions::mojom::MimeHandlerViewContainerManager>
container_manager) {
container_manager_ = std::move(container_manager);
}
int32_t instance_id() const { return instance_id_; }
void SetDidExtensionFinishNavigation();
bool DidPdfExtensionStartNavigation() const;
bool DidPdfContentNavigate() const;
content::FrameTreeNodeId extension_host_frame_tree_node_id() const {
return extension_host_frame_tree_node_id_;
}
void set_extension_host_frame_tree_node_id(
content::FrameTreeNodeId frame_tree_node_id) {
extension_host_frame_tree_node_id_ = frame_tree_node_id;
}
content::FrameTreeNodeId content_host_frame_tree_node_id() const {
return content_host_frame_tree_node_id_;
}
void set_content_host_frame_tree_node_id(
content::FrameTreeNodeId frame_tree_node_id) {
content_host_frame_tree_node_id_ = frame_tree_node_id;
}
bool plugin_can_save() const { return plugin_can_save_; }
void set_plugin_can_save(bool plugin_can_save) {
plugin_can_save_ = plugin_can_save;
}
private:
// A unique ID for the PDF viewer instance. Used to set up postMessage
// support for the full-page PDF viewer.
const std::string internal_id_;
// A container for the PDF stream. Holds data needed to load the PDF in the
// PDF viewer.
const std::unique_ptr<extensions::StreamContainer> stream_;
// True if the extension host has finished navigating to the PDF extension
// URL.
bool did_extension_finish_navigation_ = false;
// The container manager used to provide postMessage support.
mojo::AssociatedRemote<extensions::mojom::MimeHandlerViewContainerManager>
container_manager_;
// The frame tree node ID of the extension host. Initialized when the
// initial about:blank navigation commits in the extension frame.
content::FrameTreeNodeId extension_host_frame_tree_node_id_;
// The frame tree node ID of the content host. Initialized when the
// navigation to the stream URL starts.
content::FrameTreeNodeId content_host_frame_tree_node_id_;
// A unique ID for this instance. Used for postMessage support to identify
// `extensions::MimeHandlerViewFrameContainer` objects.
int32_t instance_id_;
// True if the PDF plugin should handle save events.
bool plugin_can_save_ = false;
};
// Use `Create()` to create an instance instead.
explicit PdfViewerStreamManager(content::WebContents* contents);
// Returns the stream info claimed by `embedder_host`, or nullptr if there's
// no existing stream.
StreamInfo* GetClaimedStreamInfo(
const content::RenderFrameHost* embedder_host);
const StreamInfo* GetClaimedStreamInfo(
const content::RenderFrameHost* embedder_host) const;
// Returns the stream info for a PDF content navigation.
StreamInfo* GetClaimedStreamInfoFromPdfContentNavigation(
content::NavigationHandle* navigation_handle);
// Navigates the FrameTreeNode with ID `extension_host_frame_tree_node_id` to
// the PDF extension URL. Marks the PDF extension as navigated in
// `stream_info`, which must be non-null. `source_site_instance` should be the
// `content::SiteInstance` of the PDF embedder frame that will be initiating
// the navigation.
//
// Subclasses may override this for use in callbacks. If so, `global_id`,
// which is the ID for the intermediate about:blank host for the PDF extension
// frame, can be used to get the other parameters safely.
virtual void NavigateToPdfExtensionUrl(
content::FrameTreeNodeId extension_host_frame_tree_node_id,
StreamInfo* stream_info,
content::SiteInstance* source_site_instance,
content::GlobalRenderFrameHostId global_id);
private:
FRIEND_TEST_ALL_PREFIXES(PdfViewerStreamManagerTest,
AddAndGetStreamContainer);
friend class content::WebContentsUserData<PdfViewerStreamManager>;
WEB_CONTENTS_USER_DATA_KEY_DECL();
// Mark an unclaimed stream info with the same frame tree node ID as
// `embedder_host` as claimed by `embedder_host`. Returns a pointer to the
// claimed stream info. Callers must ensure such a stream info exists with
// `ContainsUnclaimedStreamInfo()` before calling this.
StreamInfo* ClaimStreamInfo(content::RenderFrameHost* embedder_host);
// Deletes the claimed stream info associated with `embedder_host`, and
// deletes `this` if there are no remaining stream infos.
void DeleteClaimedStreamInfo(content::RenderFrameHost* embedder_host);
// Intended to be called when a RenderFrameHost in the observed
// `content::WebContents` is replaced or deleted. If `render_frame_host` is a
// deleted PDF extension host, then delete the stream. Deletes `this` if there
// are no remaining streams. Returns true if the stream was deleted, false
// otherwise.
[[nodiscard]] bool MaybeDeleteStreamOnPdfExtensionHostChanged(
content::RenderFrameHost* old_host);
// Same as `MaybeDeleteStreamOnPdfExtensionHostChanged()`, but for the content
// host.
[[nodiscard]] bool MaybeDeleteStreamOnPdfContentHostChanged(
content::RenderFrameHost* old_host);
// Intended to be called during the PDF content frame's
// `ReadyToCommitNavigation()` event. Registers navigations occurring in a PDF
// content frame as a subresource.
bool MaybeRegisterPdfSubresourceOverride(
content::NavigationHandle* navigation_handle);
// Intended to be called during the PDF content frame's 'DidFinishNavigation'.
// Sets up postMessage communication between the embedder frame and the PDF
// extension frame after the PDF has finished loading.
bool MaybeSetUpPostMessage(content::NavigationHandle* navigation_handle);
// During the PDF content frame navigation, set the related PDF stream's
// content host frame tree node ID.
void SetStreamContentHostFrameTreeNodeId(
content::NavigationHandle* navigation_handle);
// Sets up beforeunload API support for full-page PDF viewers.
// TODO(crbug.com/40268279): Currently a no-op. Support the beforeunload API.
void SetUpBeforeUnloadControl(
mojo::PendingRemote<extensions::mime_handler::BeforeUnloadControl>
before_unload_control_remote);
// Stores stream info by embedder host info.
std::map<EmbedderHostInfo, std::unique_ptr<StreamInfo>> stream_infos_;
// Needed to avoid use-after-free when setting up beforeunload API support.
base::WeakPtrFactory<PdfViewerStreamManager> weak_factory_{this};
};
} // namespace pdf
#endif // CHROME_BROWSER_PDF_PDF_VIEWER_STREAM_MANAGER_H_