blob: 7a6830e9e586cc20456d3b2ab84649070abb4650 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/guest_view/browser/guest_view_base.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "components/guest_view/browser/guest_view_event.h"
#include "components/guest_view/browser/guest_view_manager.h"
#include "components/guest_view/common/guest_view_constants.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/input/web_gesture_event.h"
#include "third_party/blink/public/common/page/page_zoom.h"
using content::WebContents;
namespace guest_view {
namespace {
using WebContentsGuestViewMap = std::map<const WebContents*, GuestViewBase*>;
base::LazyInstance<WebContentsGuestViewMap>::Leaky g_webcontents_guestview_map =
LAZY_INSTANCE_INITIALIZER;
void DestroyGuestIfUnattached(GuestViewBase* guest) {
std::unique_ptr<GuestViewBase> owned_guest =
guest->GetGuestViewManager()->TransferOwnership(guest);
owned_guest.reset();
}
} // namespace
SetSizeParams::SetSizeParams() = default;
SetSizeParams::~SetSizeParams() = default;
// This observer ensures that unattached guests don't outlive their owner
// WebContents. It also tracks when the embedder's fullscreen is toggled so the
// guest can change itself accordingly.
class GuestViewBase::OwnerContentsObserver : public WebContentsObserver {
public:
explicit OwnerContentsObserver(base::SafeRef<GuestViewBase> guest,
WebContents* owner_web_contents)
: WebContentsObserver(owner_web_contents), guest_(guest) {}
OwnerContentsObserver(const OwnerContentsObserver&) = delete;
OwnerContentsObserver& operator=(const OwnerContentsObserver&) = delete;
~OwnerContentsObserver() override = default;
// WebContentsObserver implementation.
void WebContentsDestroyed() override {
// Once attached, the guest can't outlive its owner WebContents.
DCHECK_EQ(guest_->element_instance_id(), kInstanceIDNone);
// Defensively clear the guest's `owner_web_contents_`, since a unique_ptr
// to the guest may be passed through asynchronous calls early in its
// initialization, and it's possible that its pointer to
// `owner_web_contents_` could become stale during this process.
guest_->owner_web_contents_ = nullptr;
DestroyGuestIfUnattached(&*guest_);
}
void DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) override {
if (!IsGuestInitialized()) {
return;
}
is_fullscreen_ = entered_fullscreen;
guest_->EmbedderFullscreenToggled(is_fullscreen_);
}
void PrimaryMainFrameWasResized(bool width_changed) override {
if (!IsGuestInitialized()) {
return;
}
bool current_fullscreen = web_contents()->IsFullscreen();
if (is_fullscreen_ && !current_fullscreen) {
is_fullscreen_ = false;
guest_->EmbedderFullscreenToggled(is_fullscreen_);
}
}
void DidUpdateAudioMutingState(bool muted) override {
if (IsGuestInitialized()) {
guest_->web_contents()->SetAudioMuted(muted);
}
}
private:
bool IsGuestInitialized() { return guest_->web_contents(); }
bool is_fullscreen_ = false;
const base::SafeRef<GuestViewBase> guest_;
};
// This observer ensures that a GuestViewBase is destroyed if if its opener
// WebContents goes away before the GuestViewBase is attached.
// TODO(mcnee): This behaviour is WebViewGuest specific and could be moved there
// instead.
class GuestViewBase::OpenerLifetimeObserver : public WebContentsObserver {
public:
explicit OpenerLifetimeObserver(GuestViewBase* guest)
: WebContentsObserver(guest->GetOpener()->web_contents()),
guest_(guest) {}
OpenerLifetimeObserver(const OpenerLifetimeObserver&) = delete;
OpenerLifetimeObserver& operator=(const OpenerLifetimeObserver&) = delete;
~OpenerLifetimeObserver() override = default;
// WebContentsObserver implementation.
void WebContentsDestroyed() override {
// If the opener is destroyed and the guest has not been attached, then
// destroy the guest.
// Destroys `this`.
DestroyGuestIfUnattached(guest_);
}
private:
raw_ptr<GuestViewBase> guest_;
};
GuestViewBase::GuestViewBase(WebContents* owner_web_contents)
: owner_web_contents_(owner_web_contents),
browser_context_(owner_web_contents->GetBrowserContext()),
guest_instance_id_(GetGuestViewManager()->GetNextInstanceID()) {
owner_contents_observer_ = std::make_unique<OwnerContentsObserver>(
weak_ptr_factory_.GetSafeRef(), owner_web_contents_);
SetOwnerHost();
}
GuestViewBase::~GuestViewBase() {
DCHECK(!is_being_destroyed_);
is_being_destroyed_ = true;
// If `this` was ever attached, it is important to clear `owner_web_contents_`
// after the call to StopTrackingEmbedderZoomLevel(), but before the rest of
// the statements in this function.
StopTrackingEmbedderZoomLevel();
owner_web_contents_ = nullptr;
// This is not necessarily redundant with the removal when the guest contents
// is destroyed, since we may never have initialized a guest WebContents.
GetGuestViewManager()->RemoveGuest(guest_instance_id_,
/*invalidate_id=*/true);
pending_events_.clear();
}
void GuestViewBase::Init(std::unique_ptr<GuestViewBase> owned_this,
const base::Value::Dict& create_params,
GuestCreatedCallback callback) {
if (!GetGuestViewManager()->IsGuestAvailableToContext(this)) {
// The derived class did not create a WebContents so this class serves no
// purpose. Let's self-destruct.
owned_this.reset();
std::move(callback).Run(nullptr);
return;
}
CreateWebContents(std::move(owned_this), create_params,
base::BindOnce(&GuestViewBase::CompleteInit,
weak_ptr_factory_.GetWeakPtr(),
create_params.Clone(), std::move(callback)));
}
void GuestViewBase::InitWithWebContents(const base::Value::Dict& create_params,
WebContents* guest_web_contents) {
DCHECK(guest_web_contents);
// Create a ZoomController to allow the guest's contents to be zoomed.
// Do this before adding the GuestView as a WebContents Observer so that
// the GuestView and its derived classes can re-configure the ZoomController
// after the latter has handled WebContentsObserver events (observers are
// notified of events in the same order they are added as observers). For
// example, GuestViewBase may wish to put its guest into isolated zoom mode
// in DidFinishNavigation, but since ZoomController always resets to default
// zoom mode on this event, GuestViewBase would need to do so after
// ZoomController::DidFinishNavigation has completed.
zoom::ZoomController::CreateForWebContents(guest_web_contents);
WebContentsObserver::Observe(guest_web_contents);
guest_web_contents->SetDelegate(this);
g_webcontents_guestview_map.Get().insert(
std::make_pair(guest_web_contents, this));
GetGuestViewManager()->AddGuest(guest_instance_id_, guest_web_contents);
// Populate the view instance ID if we have it on creation.
view_instance_id_ =
create_params.FindInt(kParameterInstanceId).value_or(view_instance_id_);
SetUpSizing(create_params);
// Observe guest zoom changes.
auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents());
zoom_controller_observations_.AddObservation(zoom_controller);
// Give the derived class an opportunity to perform additional initialization.
DidInitialize(create_params);
}
const absl::optional<
std::pair<base::Value::Dict, content::WebContents::CreateParams>>&
GuestViewBase::GetCreateParams() const {
return create_params_;
}
void GuestViewBase::SetCreateParams(
const base::Value::Dict& create_params,
const content::WebContents::CreateParams& web_contents_create_params) {
DCHECK_EQ(web_contents_create_params.browser_context, browser_context());
DCHECK_EQ(web_contents_create_params.guest_delegate, this);
create_params_ = {create_params.Clone(), web_contents_create_params};
}
void GuestViewBase::DispatchOnResizeEvent(const gfx::Size& old_size,
const gfx::Size& new_size) {
if (new_size == old_size)
return;
// Dispatch the onResize event.
base::Value::Dict args;
args.Set(kOldWidth, old_size.width());
args.Set(kOldHeight, old_size.height());
args.Set(kNewWidth, new_size.width());
args.Set(kNewHeight, new_size.height());
DispatchEventToGuestProxy(
std::make_unique<GuestViewEvent>(kEventResize, std::move(args)));
}
gfx::Size GuestViewBase::GetDefaultSize() const {
if (!is_full_page_plugin())
return gfx::Size(kDefaultWidth, kDefaultHeight);
// Full page plugins default to the size of the owner's viewport.
return owner_web_contents()
->GetRenderWidgetHostView()
->GetVisibleViewportSize();
}
void GuestViewBase::SetSize(const SetSizeParams& params) {
bool enable_auto_size = params.enable_auto_size.value_or(auto_size_enabled_);
gfx::Size min_size = params.min_size.value_or(min_auto_size_);
gfx::Size max_size = params.max_size.value_or(max_auto_size_);
if (params.normal_size)
normal_size_ = *params.normal_size;
min_auto_size_ = min_size;
min_auto_size_.SetToMin(max_size);
max_auto_size_ = max_size;
max_auto_size_.SetToMax(min_size);
enable_auto_size &= !min_auto_size_.IsEmpty() && !max_auto_size_.IsEmpty() &&
IsAutoSizeSupported();
content::RenderWidgetHostView* rwhv =
web_contents()->GetRenderWidgetHostView();
if (enable_auto_size) {
// Autosize is being enabled.
if (rwhv)
rwhv->EnableAutoResize(min_auto_size_, max_auto_size_);
normal_size_.SetSize(0, 0);
} else {
// Autosize is being disabled.
// Use default width/height if missing from partially defined normal size.
if (normal_size_.width() && !normal_size_.height())
normal_size_.set_height(GetDefaultSize().height());
if (!normal_size_.width() && normal_size_.height())
normal_size_.set_width(GetDefaultSize().width());
gfx::Size new_size;
if (!normal_size_.IsEmpty()) {
new_size = normal_size_;
} else if (!guest_size_.IsEmpty()) {
new_size = guest_size_;
} else {
new_size = GetDefaultSize();
}
bool changed_due_to_auto_resize = false;
if (auto_size_enabled_) {
// Autosize was previously enabled.
if (rwhv)
rwhv->DisableAutoResize(new_size);
changed_due_to_auto_resize = true;
} else {
// Autosize was already disabled. The RenderWidgetHostView is responsible
// for the GuestView's size.
}
UpdateGuestSize(new_size, changed_due_to_auto_resize);
}
auto_size_enabled_ = enable_auto_size;
}
// static
GuestViewBase* GuestViewBase::FromWebContents(const WebContents* web_contents) {
WebContentsGuestViewMap* guest_map = g_webcontents_guestview_map.Pointer();
auto it = guest_map->find(web_contents);
return it == guest_map->end() ? nullptr : it->second;
}
// static
GuestViewBase* GuestViewBase::FromRenderFrameHost(
content::RenderFrameHost* rfh) {
return FromWebContents(content::WebContents::FromRenderFrameHost(rfh));
}
// static
GuestViewBase* GuestViewBase::FromRenderFrameHostId(
const content::GlobalRenderFrameHostId& rfh_id) {
return FromRenderFrameHost(content::RenderFrameHost::FromID(rfh_id));
}
// static
GuestViewBase* GuestViewBase::FromInstanceID(int owner_process_id,
int guest_instance_id) {
auto* host = content::RenderProcessHost::FromID(owner_process_id);
if (!host)
return nullptr;
return GuestViewManager::FromBrowserContext(host->GetBrowserContext())
->GetGuestByInstanceIDSafely(guest_instance_id, owner_process_id);
}
// static
WebContents* GuestViewBase::GetTopLevelWebContents(WebContents* web_contents) {
while (GuestViewBase* guest = FromWebContents(web_contents))
web_contents = guest->owner_web_contents();
return web_contents;
}
// static
bool GuestViewBase::IsGuest(WebContents* web_contents) {
return !!FromWebContents(web_contents);
}
// static
bool GuestViewBase::IsGuest(content::RenderFrameHost* rfh) {
return !!FromRenderFrameHost(rfh);
}
// static
bool GuestViewBase::IsGuest(const content::GlobalRenderFrameHostId& rfh_id) {
return !!FromRenderFrameHostId(rfh_id);
}
bool GuestViewBase::IsAutoSizeSupported() const {
return false;
}
bool GuestViewBase::IsPreferredSizeModeEnabled() const {
return false;
}
bool GuestViewBase::ZoomPropagatesFromEmbedderToGuest() const {
return true;
}
content::NavigationController& GuestViewBase::GetController() {
// TODO(crbug/1261928): Migrate the implementation for MPArch.
return web_contents()->GetController();
}
GuestViewManager* GuestViewBase::GetGuestViewManager() {
return GuestViewManager::FromBrowserContext(browser_context());
}
std::unique_ptr<WebContents> GuestViewBase::CreateNewGuestWindow(
const WebContents::CreateParams& create_params) {
return GetGuestViewManager()->CreateGuestWithWebContentsParams(
GetViewType(), owner_web_contents(), create_params);
}
void GuestViewBase::DidAttach() {
DCHECK(attach_in_progress_);
// Clear this flag here, as functions called below may check attached().
attach_in_progress_ = false;
opener_lifetime_observer_.reset();
SetUpSizing(attach_params());
// The guest should have the same muting state as the owner.
web_contents()->SetAudioMuted(owner_web_contents()->IsAudioMuted());
// Give the derived class an opportunity to perform some actions.
DidAttachToEmbedder();
SendQueuedEvents();
}
WebContents* GuestViewBase::GetOwnerWebContents() {
return owner_web_contents_;
}
const GURL& GuestViewBase::GetOwnerSiteURL() const {
return owner_web_contents()
->GetPrimaryMainFrame()
->GetSiteInstance()
->GetSiteURL();
}
void GuestViewBase::SetAttachParams(const base::Value::Dict& params) {
attach_params_ = params.Clone();
view_instance_id_ =
attach_params_.FindInt(kParameterInstanceId).value_or(view_instance_id_);
}
void GuestViewBase::SetOpener(GuestViewBase* guest) {
DCHECK(guest);
opener_ = guest->weak_ptr_factory_.GetWeakPtr();
if (!attached()) {
opener_lifetime_observer_ = std::make_unique<OpenerLifetimeObserver>(this);
}
}
void GuestViewBase::WillAttach(
std::unique_ptr<GuestViewBase> owned_this,
WebContents* embedder_web_contents,
content::RenderFrameHost* outer_contents_frame,
int element_instance_id,
bool is_full_page_plugin,
base::OnceClosure completion_callback,
GuestViewMessageHandler::AttachToEmbedderFrameCallback
attachment_callback) {
// Stop tracking the old embedder's zoom level.
// TODO(crbug.com/533069): We should assert that we're not tracking the
// embedder at this point, since guest reattachment is no longer possible.
StopTrackingEmbedderZoomLevel();
if (owner_web_contents_ != embedder_web_contents) {
SetNewOwnerWebContents(embedder_web_contents);
}
// Start tracking the new embedder's zoom level.
StartTrackingEmbedderZoomLevel();
attach_in_progress_ = true;
element_instance_id_ = element_instance_id;
is_full_page_plugin_ = is_full_page_plugin;
WillAttachToEmbedder();
web_contents()->ResumeLoadingCreatedWebContents();
// From this point on, `this` is scoped to the guest contents' lifetime. We
// self-destruct in WebContentsDestroyed.
owned_this.release();
self_owned_ = true;
std::unique_ptr<WebContents> owned_guest_contents =
std::move(owned_guest_contents_);
DCHECK_EQ(owned_guest_contents.get(), web_contents());
// Since this inner WebContents is created from the browser side we do
// not have RemoteFrame mojo channels so we pass in
// NullAssociatedRemote/Receivers. New channels will be bound when the
// `CreateView` IPC is sent.
owner_web_contents_->AttachInnerWebContents(
std::move(owned_guest_contents), outer_contents_frame,
/*remote_frame=*/mojo::NullAssociatedRemote(),
/*remote_frame_host_receiver=*/mojo::NullAssociatedReceiver(),
is_full_page_plugin);
// We don't ACK until after AttachToOuterWebContentsFrame, so that
// |outer_contents_frame| gets swapped before the AttachToEmbedderFrame
// callback is run. We also need to send the ACK before queued events are sent
// in DidAttach.
if (attachment_callback)
std::move(attachment_callback).Run();
// Completing attachment will resume suspended resource loads and then send
// queued events.
SignalWhenReady(std::move(completion_callback));
}
void GuestViewBase::SignalWhenReady(base::OnceClosure callback) {
// The default behavior is to call the |callback| immediately. Derived classes
// can implement an alternative signal for readiness.
std::move(callback).Run();
}
int GuestViewBase::LogicalPixelsToPhysicalPixels(double logical_pixels) const {
DCHECK(logical_pixels >= 0);
double zoom_factor = GetEmbedderZoomFactor();
return lround(logical_pixels * zoom_factor);
}
double GuestViewBase::PhysicalPixelsToLogicalPixels(int physical_pixels) const {
DCHECK(physical_pixels >= 0);
double zoom_factor = GetEmbedderZoomFactor();
return physical_pixels / zoom_factor;
}
void GuestViewBase::DidStopLoading() {
content::RenderViewHost* rvh =
web_contents()->GetPrimaryMainFrame()->GetRenderViewHost();
if (IsPreferredSizeModeEnabled())
rvh->EnablePreferredSizeMode();
GuestViewDidStopLoading();
}
void GuestViewBase::RenderViewReady() {
GuestReady();
}
void GuestViewBase::WebContentsDestroyed() {
g_webcontents_guestview_map.Get().erase(web_contents());
GetGuestViewManager()->RemoveGuest(guest_instance_id_,
/*invalidate_id=*/false);
// Self-destruct.
if (self_owned_) {
DCHECK(!is_being_destroyed_);
delete this;
}
}
void GuestViewBase::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
// TODO(crbug.com/1261928): Due to the use of inner WebContents, a
// GuestViewBase's main frame is considered primary. This will no
// longer be the case once we migrate guest views to MPArch.
if (!navigation_handle->IsInPrimaryMainFrame() ||
!navigation_handle->HasCommitted())
return;
if (attached() && ZoomPropagatesFromEmbedderToGuest())
SetGuestZoomLevelToMatchEmbedder();
}
void GuestViewBase::ActivateContents(WebContents* web_contents) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return;
embedder_web_contents()->GetDelegate()->ActivateContents(
embedder_web_contents());
}
void GuestViewBase::ContentsMouseEvent(WebContents* source,
bool motion,
bool exited) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return;
embedder_web_contents()->GetDelegate()->ContentsMouseEvent(
embedder_web_contents(), motion, exited);
}
void GuestViewBase::ContentsZoomChange(bool zoom_in) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return;
embedder_web_contents()->GetDelegate()->ContentsZoomChange(zoom_in);
}
bool GuestViewBase::HandleKeyboardEvent(
WebContents* source,
const content::NativeWebKeyboardEvent& event) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return false;
// Send the keyboard events back to the embedder to reprocess them.
return embedder_web_contents()->GetDelegate()->HandleKeyboardEvent(
embedder_web_contents(), event);
}
void GuestViewBase::LoadingStateChanged(WebContents* source,
bool should_show_loading_ui) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return;
embedder_web_contents()->GetDelegate()->LoadingStateChanged(
embedder_web_contents(), should_show_loading_ui);
}
void GuestViewBase::ResizeDueToAutoResize(WebContents* web_contents,
const gfx::Size& new_size) {
UpdateGuestSize(new_size, auto_size_enabled_);
}
void GuestViewBase::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<content::FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
if (!attached() || !embedder_web_contents()->GetDelegate()) {
listener->FileSelectionCanceled();
return;
}
embedder_web_contents()->GetDelegate()->RunFileChooser(
render_frame_host, std::move(listener), params);
}
bool GuestViewBase::ShouldFocusPageAfterCrash() {
// Focus is managed elsewhere.
return false;
}
bool GuestViewBase::PreHandleGestureEvent(WebContents* source,
const blink::WebGestureEvent& event) {
// Pinch events which cause a scale change should not be routed to a guest.
// We still allow synthetic wheel events for touchpad pinch to go to the page.
DCHECK(!blink::WebInputEvent::IsPinchGestureEventType(event.GetType()) ||
(event.SourceDevice() == blink::WebGestureDevice::kTouchpad &&
event.NeedsWheelEvent()));
return false;
}
void GuestViewBase::UpdatePreferredSize(WebContents* target_web_contents,
const gfx::Size& pref_size) {
// In theory it's not necessary to check IsPreferredSizeModeEnabled() because
// there will only be events if it was enabled in the first place. However,
// something else may have turned on preferred size mode, so double check.
DCHECK_EQ(web_contents(), target_web_contents);
if (IsPreferredSizeModeEnabled()) {
OnPreferredSizeChanged(pref_size);
}
}
content::WebContents* GuestViewBase::GetResponsibleWebContents(
content::WebContents* source) {
return owner_web_contents();
}
void GuestViewBase::UpdateTargetURL(WebContents* source, const GURL& url) {
if (!attached() || !embedder_web_contents()->GetDelegate())
return;
embedder_web_contents()->GetDelegate()->UpdateTargetURL(
embedder_web_contents(), url);
}
bool GuestViewBase::ShouldResumeRequestsForCreatedWindow() {
// Delay so that the embedder page has a chance to call APIs such as
// webRequest in time to be applied to the initial navigation in the new guest
// contents. We resume during WillAttach.
return false;
}
content::RenderWidgetHost* GuestViewBase::GetOwnerRenderWidgetHost() {
// We assume guests live inside an owner RenderFrame but the RenderFrame may
// not be cross-process. In case a type of guest should be allowed to be
// embedded in a cross-process frame, this method should be overrode for that
// specific guest type. For all other guests, the owner RenderWidgetHost is
// that of the owner WebContents.
DCHECK(!CanBeEmbeddedInsideCrossProcessFrames());
auto* owner = GetOwnerWebContents();
if (owner && owner->GetRenderWidgetHostView())
return owner->GetRenderWidgetHostView()->GetRenderWidgetHost();
return nullptr;
}
content::SiteInstance* GuestViewBase::GetOwnerSiteInstance() {
// We assume guests live inside an owner RenderFrame but the RenderFrame may
// not be cross-process. In case a type of guest should be allowed to be
// embedded in a cross-process frame, this method should be overrode for that
// specific guest type. For all other guests, the owner site instance can be
// from the owner WebContents.
DCHECK(!CanBeEmbeddedInsideCrossProcessFrames());
if (auto* owner_contents = GetOwnerWebContents())
return owner_contents->GetSiteInstance();
return nullptr;
}
void GuestViewBase::AttachToOuterWebContentsFrame(
std::unique_ptr<GuestViewBase> owned_this,
content::RenderFrameHost* embedder_frame,
int32_t element_instance_id,
bool is_full_page_plugin,
GuestViewMessageHandler::AttachToEmbedderFrameCallback
attachment_callback) {
auto completion_callback =
base::BindOnce(&GuestViewBase::DidAttach, weak_ptr_factory_.GetWeakPtr());
WillAttach(std::move(owned_this),
WebContents::FromRenderFrameHost(embedder_frame), embedder_frame,
element_instance_id, is_full_page_plugin,
std::move(completion_callback), std::move(attachment_callback));
}
void GuestViewBase::OnZoomControllerDestroyed(zoom::ZoomController* source) {
DCHECK(zoom_controller_observations_.IsObservingSource(source));
zoom_controller_observations_.RemoveObservation(source);
}
void GuestViewBase::OnZoomChanged(
const zoom::ZoomController::ZoomChangedEventData& data) {
if (data.web_contents == embedder_web_contents()) {
// The embedder's zoom level has changed.
auto* guest_zoom_controller =
zoom::ZoomController::FromWebContents(web_contents());
if (blink::PageZoomValuesEqual(data.new_zoom_level,
guest_zoom_controller->GetZoomLevel())) {
return;
}
// When the embedder's zoom level doesn't match the guest's, then update the
// guest's zoom level to match.
guest_zoom_controller->SetZoomLevel(data.new_zoom_level);
return;
}
if (data.web_contents == web_contents()) {
// The guest's zoom level has changed.
GuestZoomChanged(data.old_zoom_level, data.new_zoom_level);
}
}
void GuestViewBase::DispatchEventToGuestProxy(
std::unique_ptr<GuestViewEvent> event) {
event->Dispatch(this, guest_instance_id_);
}
void GuestViewBase::DispatchEventToView(std::unique_ptr<GuestViewEvent> event) {
if (attached() && pending_events_.empty()) {
event->Dispatch(this, view_instance_id_);
return;
}
pending_events_.push_back(std::move(event));
}
void GuestViewBase::SendQueuedEvents() {
if (!attached())
return;
while (!pending_events_.empty()) {
std::unique_ptr<GuestViewEvent> event_ptr =
std::move(pending_events_.front());
pending_events_.pop_front();
event_ptr->Dispatch(this, view_instance_id_);
}
}
void GuestViewBase::CompleteInit(
base::Value::Dict create_params,
GuestCreatedCallback callback,
std::unique_ptr<GuestViewBase> owned_this,
std::unique_ptr<content::WebContents> guest_web_contents) {
if (!guest_web_contents) {
// The derived class did not create a WebContents so this class serves no
// purpose. Let's self-destruct.
owned_this.reset();
std::move(callback).Run(nullptr);
return;
}
InitWithWebContents(create_params, guest_web_contents.get());
TakeGuestContentsOwnership(std::move(guest_web_contents));
std::move(callback).Run(std::move(owned_this));
}
void GuestViewBase::TakeGuestContentsOwnership(
std::unique_ptr<WebContents> guest_web_contents) {
DCHECK(!owned_guest_contents_);
owned_guest_contents_ = std::move(guest_web_contents);
}
void GuestViewBase::ClearOwnedGuestContents() {
owned_guest_contents_.reset();
}
void GuestViewBase::SetNewOwnerWebContents(
content::WebContents* owner_web_contents) {
DCHECK(!attached());
DCHECK(owner_web_contents_);
DCHECK(owner_web_contents);
DCHECK_NE(owner_web_contents_, owner_web_contents);
DCHECK_EQ(owner_contents_observer_->web_contents(), owner_web_contents_);
owner_web_contents_ = owner_web_contents;
owner_contents_observer_ = std::make_unique<OwnerContentsObserver>(
weak_ptr_factory_.GetSafeRef(), owner_web_contents_);
SetOwnerHost();
}
double GuestViewBase::GetEmbedderZoomFactor() const {
if (!embedder_web_contents())
return 1.0;
return blink::PageZoomLevelToZoomFactor(
zoom::ZoomController::GetZoomLevelForWebContents(
embedder_web_contents()));
}
void GuestViewBase::SetUpSizing(const base::Value::Dict& params) {
// Read the autosize parameters passed in from the embedder.
absl::optional<bool> auto_size_enabled_opt =
params.FindBool(kAttributeAutoSize);
bool auto_size_enabled = auto_size_enabled_opt.value_or(auto_size_enabled_);
int max_height =
params.FindInt(kAttributeMaxHeight).value_or(max_auto_size_.height());
int max_width =
params.FindInt(kAttributeMaxWidth).value_or(max_auto_size_.width());
int min_height =
params.FindInt(kAttributeMinHeight).value_or(min_auto_size_.height());
int min_width =
params.FindInt(kAttributeMinWidth).value_or(min_auto_size_.width());
double element_height = params.FindDouble(kElementHeight).value_or(0.0);
double element_width = params.FindDouble(kElementWidth).value_or(0.0);
// Set the normal size to the element size so that the guestview will fit
// the element initially if autosize is disabled.
int normal_height = normal_size_.height();
int normal_width = normal_size_.width();
// If the element size was provided in logical units (versus physical), then
// it will be converted to physical units.
absl::optional<bool> element_size_is_logical_opt =
params.FindBool(kElementSizeIsLogical);
bool element_size_is_logical = element_size_is_logical_opt.value_or(false);
if (element_size_is_logical) {
// Convert the element size from logical pixels to physical pixels.
normal_height = LogicalPixelsToPhysicalPixels(element_height);
normal_width = LogicalPixelsToPhysicalPixels(element_width);
} else {
normal_height = lround(element_height);
normal_width = lround(element_width);
}
SetSizeParams set_size_params;
set_size_params.enable_auto_size = auto_size_enabled;
set_size_params.min_size.emplace(min_width, min_height);
set_size_params.max_size.emplace(max_width, max_height);
set_size_params.normal_size.emplace(normal_width, normal_height);
// Call SetSize to apply all the appropriate validation and clipping of
// values.
SetSize(set_size_params);
}
void GuestViewBase::SetGuestZoomLevelToMatchEmbedder() {
auto* embedder_zoom_controller =
zoom::ZoomController::FromWebContents(owner_web_contents());
if (!embedder_zoom_controller)
return;
zoom::ZoomController::FromWebContents(web_contents())
->SetZoomLevel(embedder_zoom_controller->GetZoomLevel());
}
void GuestViewBase::StartTrackingEmbedderZoomLevel() {
if (!ZoomPropagatesFromEmbedderToGuest())
return;
auto* embedder_zoom_controller =
zoom::ZoomController::FromWebContents(owner_web_contents());
// Chrome Apps do not have a ZoomController.
if (!embedder_zoom_controller)
return;
// Listen to the embedder's zoom changes.
zoom_controller_observations_.AddObservation(embedder_zoom_controller);
// Set the guest's initial zoom level to be equal to the embedder's.
SetGuestZoomLevelToMatchEmbedder();
}
void GuestViewBase::StopTrackingEmbedderZoomLevel() {
// TODO(wjmaclean): Remove the observer any time the GuestWebView transitions
// from propagating to not-propagating the zoom from the embedder.
if (!owner_web_contents())
return;
auto* embedder_zoom_controller =
zoom::ZoomController::FromWebContents(owner_web_contents());
// Chrome Apps do not have a ZoomController.
if (!embedder_zoom_controller)
return;
if (zoom_controller_observations_.IsObservingSource(
embedder_zoom_controller)) {
zoom_controller_observations_.RemoveObservation(embedder_zoom_controller);
}
}
void GuestViewBase::UpdateGuestSize(const gfx::Size& new_size,
bool due_to_auto_resize) {
if (due_to_auto_resize)
GuestSizeChangedDueToAutoSize(guest_size_, new_size);
DispatchOnResizeEvent(guest_size_, new_size);
guest_size_ = new_size;
}
void GuestViewBase::SetOwnerHost() {
auto* manager = GuestViewManager::FromBrowserContext(browser_context_);
owner_host_ = manager->IsOwnedByExtension(this)
? owner_web_contents()->GetLastCommittedURL().host()
: std::string();
}
bool GuestViewBase::CanBeEmbeddedInsideCrossProcessFrames() const {
return false;
}
bool GuestViewBase::RequiresSslInterstitials() const {
return false;
}
content::RenderFrameHost* GuestViewBase::GetGuestMainFrame() const {
// TODO(crbug/1261928): Migrate the implementation for MPArch.
return web_contents()->GetPrimaryMainFrame();
}
} // namespace guest_view