blob: df3dcdec486b17d8f18475d27ace6ab3c8c6c0a2 [file] [log] [blame]
// Copyright 2015 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_extension_test_util.h"
#include <stddef.h>
#include <string>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/pdf/common/pdf_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/focus_changed_observer.h"
#include "content/public/test/hit_test_region_observer.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "pdf/pdf_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace pdf_extension_test_util {
namespace {
bool IsPluginFrame(content::RenderFrameHost& frame) {
if (!frame.GetProcess()->IsPdf())
return false;
EXPECT_TRUE(frame.IsCrossProcessSubframe());
return true;
}
} // namespace
content::RenderFrameHost* GetPdfExtensionHostFromEmbedder(
content::RenderFrameHost* embedder_host,
bool allow_multiple_frames) {
// Return nullptr if multiple frames aren't allowed and there's more than one
// child.
if (!allow_multiple_frames && content::ChildFrameAt(embedder_host, 1)) {
return nullptr;
}
// The extension host be the first child of the embedder host.
content::RenderFrameHost* child_host =
content::ChildFrameAt(embedder_host, 0);
return child_host &&
IsPdfExtensionOrigin(child_host->GetLastCommittedOrigin())
? child_host
: nullptr;
}
content::RenderFrameHost* GetOnlyPdfExtensionHost(
content::WebContents* contents) {
std::vector<content::RenderFrameHost*> extension_hosts =
GetPdfExtensionHosts(contents);
return extension_hosts.size() == 1 ? extension_hosts[0] : nullptr;
}
std::vector<content::RenderFrameHost*> GetPdfExtensionHosts(
content::WebContents* contents) {
std::vector<content::RenderFrameHost*> extension_hosts;
contents->ForEachRenderFrameHost(
[&extension_hosts](content::RenderFrameHost* host) {
if (IsPdfExtensionOrigin(host->GetLastCommittedOrigin())) {
extension_hosts.push_back(host);
}
});
return extension_hosts;
}
content::RenderFrameHost* GetOnlyPdfPluginFrame(
content::WebContents* contents) {
std::vector<content::RenderFrameHost*> plugin_frames =
GetPdfPluginFrames(contents);
return plugin_frames.size() == 1 ? plugin_frames[0] : nullptr;
}
std::vector<content::RenderFrameHost*> GetPdfPluginFrames(
content::WebContents* contents) {
std::vector<content::RenderFrameHost*> plugin_frames;
contents->ForEachRenderFrameHost(
[&plugin_frames](content::RenderFrameHost* frame) {
if (IsPluginFrame(*frame))
plugin_frames.push_back(frame);
});
return plugin_frames;
}
size_t CountPdfPluginProcesses(const Browser* browser) {
base::flat_set<content::RenderProcessHost*> pdf_processes;
const TabStripModel* tab_strip = browser->tab_strip_model();
for (int tab = 0; tab < tab_strip->count(); ++tab) {
for (content::RenderFrameHost* plugin_frame :
GetPdfPluginFrames(tab_strip->GetWebContentsAt(tab))) {
pdf_processes.insert(plugin_frame->GetProcess());
}
}
return pdf_processes.size();
}
testing::AssertionResult EnsurePDFHasLoaded(
const content::ToRenderFrameHost& frame) {
return EnsurePDFHasLoadedWithOptions(frame, EnsurePDFHasLoadedOptions());
}
testing::AssertionResult EnsurePDFHasLoadedWithOptions(
const content::ToRenderFrameHost& frame,
const EnsurePDFHasLoadedOptions& options) {
// OOPIF PDF intentionally doesn't support postMessage() API for embedders.
// postMessage() can still be used if the script is injected into the
// extension frame.
static constexpr char kEnsurePdfHasLoadedScript[] = R"(
new Promise(resolve => {
window.addEventListener('message', event => {
if (event.origin !==
'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai') {
return;
}
if (event.data.type === 'documentLoaded') {
resolve(event.data.load_state === 'success');
} else if (event.data.type === 'passwordPrompted') {
resolve(true);
}
});
%s.postMessage(
{type: 'initialize'});
});
)";
static constexpr char kOopifPostMessageTarget[] = "window";
static constexpr char kGuestViewPostMessageTarget[] =
"document.getElementsByTagName($1)[0]";
bool use_oopif =
base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif);
// For OOPIF PDF viewer, the target frame should be the PDF extension frame.
// Otherwise, it should be whatever frame was given.
content::RenderFrameHost* frame_rfh = frame.render_frame_host();
content::RenderFrameHost* target_frame =
use_oopif ? GetPdfExtensionHostFromEmbedder(frame_rfh,
options.allow_multiple_frames)
: frame_rfh;
if (use_oopif && !target_frame) {
return testing::AssertionFailure() << "Failed to get PDF extension frame.";
}
const std::string post_message_target =
use_oopif ? kOopifPostMessageTarget
: content::JsReplace(kGuestViewPostMessageTarget,
options.pdf_element);
bool load_success =
content::EvalJs(target_frame,
base::StringPrintf(kEnsurePdfHasLoadedScript,
post_message_target.c_str()))
.ExtractBool();
if (!load_success) {
return testing::AssertionFailure() << "Load failed.";
}
if (options.wait_for_hit_test_data) {
frame.render_frame_host()->ForEachRenderFrameHost(
[](content::RenderFrameHost* render_frame_host) {
return content::WaitForHitTestData(render_frame_host);
});
}
return testing::AssertionSuccess();
}
gfx::Point ConvertPageCoordToScreenCoord(
content::ToRenderFrameHost guest_main_frame,
const gfx::Point& point) {
if (!guest_main_frame.render_frame_host()) {
ADD_FAILURE() << "The guest main frame needs to be non-null";
return point;
}
static constexpr char kScript[] = R"(
var visiblePage = viewer.viewport.getMostVisiblePage();
var visiblePageDimensions = viewer.viewport.getPageScreenRect(visiblePage);
var viewportPosition = viewer.viewport.position;
var offsetParent = viewer.shadowRoot.querySelector('#container');
var scrollParent = viewer.shadowRoot.querySelector('#main');
var screenOffsetX = visiblePageDimensions.x - viewportPosition.x +
scrollParent.offsetLeft + offsetParent.offsetLeft;
var screenOffsetY = visiblePageDimensions.y - viewportPosition.y +
scrollParent.offsetTop + offsetParent.offsetTop;
var linkScreenPositionX = Math.floor(%d * viewer.viewport.internalZoom_ +
screenOffsetX);
var linkScreenPositionY = Math.floor(%d * viewer.viewport.internalZoom_ +
screenOffsetY);
)";
if (!content::ExecJs(guest_main_frame,
base::StringPrintf(kScript, point.x(), point.y()))) {
ADD_FAILURE() << "Error executing script";
return point;
}
int x =
content::EvalJs(guest_main_frame, "linkScreenPositionX;").ExtractInt();
int y =
content::EvalJs(guest_main_frame, "linkScreenPositionY;").ExtractInt();
return {x, y};
}
void SetInputFocusOnPlugin(content::RenderFrameHost* extension_host,
content::WebContents* embedder_web_contents) {
content::WaitForHitTestData(extension_host);
const gfx::Point point_in_root_coords =
extension_host->GetView()->TransformPointToRootCoordSpace(
ConvertPageCoordToScreenCoord(extension_host, {1, 1}));
content::FocusChangedObserver focus_observer(
content::WebContents::FromRenderFrameHost(extension_host));
content::SimulateMouseClickAt(
embedder_web_contents, blink::WebInputEvent::kNoModifiers,
blink::WebMouseEvent::Button::kLeft, point_in_root_coords);
focus_observer.Wait();
}
extensions::MimeHandlerViewGuest* GetOnlyMimeHandlerView(
content::WebContents* embedder_contents) {
extensions::MimeHandlerViewGuest* result = nullptr;
embedder_contents->ForEachRenderFrameHost([&result](
content::RenderFrameHost* rfh) {
auto* guest = extensions::MimeHandlerViewGuest::FromRenderFrameHost(rfh);
if (guest && guest->GetGuestMainFrame() == rfh) {
// Assume exactly one MimeHandlerView.
EXPECT_FALSE(result);
result = guest;
}
});
return result;
}
} // namespace pdf_extension_test_util