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
|
// 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);
// Deletes the unclaimed stream info associated with `frame_tree_node_id`, and
// deletes `this` if there are no remaining stream infos.
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();
// Returns whether there's an unclaimed stream info with the default embedder
// host info.
bool ContainsUnclaimedStreamInfo(
content::FrameTreeNodeId frame_tree_node_id) const;
// 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_
|