blob: 0ab40dfd23d1071212c4d30d01fa8e7755458d4a [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 "extensions/browser/guest_view/extension_options/extension_options_guest.h"
#include <memory>
#include <string>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "components/crx_file/id_util.h"
#include "components/guest_view/browser/guest_view_event.h"
#include "components/guest_view/browser/guest_view_manager.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/guest_view/extension_options/extension_options_constants.h"
#include "extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h"
#include "extensions/common/api/extension_options_internal.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "extensions/strings/grit/extensions_strings.h"
using content::WebContents;
using guest_view::GuestViewBase;
using guest_view::GuestViewEvent;
namespace extensions {
// static
const char ExtensionOptionsGuest::Type[] = "extensionoptions";
const guest_view::GuestViewHistogramValue
ExtensionOptionsGuest::HistogramValue =
guest_view::GuestViewHistogramValue::kExtensionOptions;
ExtensionOptionsGuest::ExtensionOptionsGuest(
content::RenderFrameHost* owner_rfh)
: GuestView<ExtensionOptionsGuest>(owner_rfh),
extension_options_guest_delegate_(
extensions::ExtensionsAPIClient::Get()
->CreateExtensionOptionsGuestDelegate(this)) {}
ExtensionOptionsGuest::~ExtensionOptionsGuest() = default;
// static
std::unique_ptr<GuestViewBase> ExtensionOptionsGuest::Create(
content::RenderFrameHost* owner_rfh) {
return base::WrapUnique(new ExtensionOptionsGuest(owner_rfh));
}
void ExtensionOptionsGuest::CreateInnerPage(
std::unique_ptr<GuestViewBase> owned_this,
scoped_refptr<content::SiteInstance> site_instance,
const base::Value::Dict& create_params,
GuestPageCreatedCallback callback) {
// Get the extension's base URL.
const std::string* extension_id =
create_params.FindString(extensionoptions::kExtensionId);
if (!extension_id || !crx_file::id_util::IdIsValid(*extension_id)) {
RejectGuestCreation(std::move(owned_this), std::move(callback));
return;
}
GURL extension_url =
extensions::Extension::GetBaseURLFromExtensionId(*extension_id);
if (!extension_url.is_valid()) {
RejectGuestCreation(std::move(owned_this), std::move(callback));
return;
}
// Get the options page URL for later use.
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(browser_context());
const extensions::Extension* extension =
registry->enabled_extensions().GetByID(*extension_id);
if (!extension) {
// The ID was valid but the extension didn't exist. Typically this will
// happen when an extension is disabled.
RejectGuestCreation(std::move(owned_this), std::move(callback));
return;
}
options_page_ = extensions::OptionsPageInfo::GetOptionsPage(extension);
if (!options_page_.is_valid()) {
RejectGuestCreation(std::move(owned_this), std::move(callback));
return;
}
if (base::FeatureList::IsEnabled(features::kGuestViewMPArch)) {
std::move(callback).Run(
std::move(owned_this),
content::GuestPageHolder::Create(owner_web_contents(),
content::SiteInstance::CreateForURL(
browser_context(), extension_url),
GetGuestPageHolderDelegateWeakPtr()));
} else {
// Create a WebContents using the extension URL. The options page's
// WebContents should live in the same process as its parent extension's
// WebContents, so we can use |extension_url| for creating the SiteInstance.
WebContents::CreateParams params(
browser_context(),
content::SiteInstance::CreateForURL(browser_context(), extension_url));
params.guest_delegate = this;
std::move(callback).Run(std::move(owned_this), WebContents::Create(params));
}
}
void ExtensionOptionsGuest::DidInitialize(
const base::Value::Dict& create_params) {
if (base::FeatureList::IsEnabled(features::kGuestViewMPArch)) {
return;
}
ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
GetController().LoadURL(options_page_, content::Referrer(),
ui::PAGE_TRANSITION_LINK, std::string());
}
void ExtensionOptionsGuest::DidAttachToEmbedder() {
if (base::FeatureList::IsEnabled(features::kGuestViewMPArch)) {
GetController().LoadURL(options_page_, content::Referrer(),
ui::PAGE_TRANSITION_LINK, std::string());
}
}
void ExtensionOptionsGuest::MaybeRecreateGuestContents(
content::RenderFrameHost* outer_contents_frame) {
// This situation is not possible for ExtensionOptions.
NOTREACHED();
}
void ExtensionOptionsGuest::GuestViewDidStopLoading() {
DispatchEventToView(std::make_unique<GuestViewEvent>(
api::extension_options_internal::OnLoad::kEventName,
base::Value::Dict()));
}
const char* ExtensionOptionsGuest::GetAPINamespace() const {
return extensionoptions::kAPINamespace;
}
int ExtensionOptionsGuest::GetTaskPrefix() const {
return IDS_EXTENSION_TASK_MANAGER_EXTENSIONOPTIONS_TAG_PREFIX;
}
bool ExtensionOptionsGuest::IsPreferredSizeModeEnabled() const {
return true;
}
void ExtensionOptionsGuest::OnPreferredSizeChanged(const gfx::Size& pref_size) {
api::extension_options_internal::PreferredSizeChangedOptions options;
// Convert the size from physical pixels to logical pixels.
options.width = PhysicalPixelsToLogicalPixels(pref_size.width());
options.height = PhysicalPixelsToLogicalPixels(pref_size.height());
DispatchEventToView(std::make_unique<GuestViewEvent>(
api::extension_options_internal::OnPreferredSizeChanged::kEventName,
options.ToValue()));
}
WebContents* ExtensionOptionsGuest::AddNewContents(
WebContents* source,
std::unique_ptr<WebContents> new_contents,
const GURL& target_url,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& window_features,
bool user_gesture,
bool* was_blocked) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
// |new_contents| is potentially used as a non-embedded WebContents, so we
// check that it isn't a guest. The only place that this method should be
// called is WebContentsImpl::ViewSource - which generates a non-guest
// WebContents.
DCHECK(!ExtensionOptionsGuest::FromWebContents(new_contents.get()));
if (!attached() || !embedder_web_contents()->GetDelegate()) {
return nullptr;
}
embedder_web_contents()->GetDelegate()->AddNewContents(
source, std::move(new_contents), target_url, disposition, window_features,
user_gesture, was_blocked);
return nullptr;
}
WebContents* ExtensionOptionsGuest::OpenURLFromTab(
WebContents* source,
const content::OpenURLParams& params,
base::OnceCallback<void(content::NavigationHandle&)>
navigation_handle_callback) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
if (!extension_options_guest_delegate_) {
return nullptr;
}
// Don't allow external URLs with the CURRENT_TAB disposition be opened in
// this guest view, change the disposition to NEW_FOREGROUND_TAB.
if ((!params.url.SchemeIs(extensions::kExtensionScheme) ||
params.url.GetHost() != options_page_.GetHost()) &&
params.disposition == WindowOpenDisposition::CURRENT_TAB) {
return extension_options_guest_delegate_->OpenURLInNewTab(
content::OpenURLParams(params.url, params.referrer,
params.frame_tree_node_id,
WindowOpenDisposition::NEW_FOREGROUND_TAB,
params.transition, params.is_renderer_initiated),
std::move(navigation_handle_callback));
}
return extension_options_guest_delegate_->OpenURLInNewTab(
params, std::move(navigation_handle_callback));
}
void ExtensionOptionsGuest::CloseContents(WebContents* source) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
GuestClose();
}
bool ExtensionOptionsGuest::GuestHandleContextMenu(
content::RenderFrameHost& render_frame_host,
const content::ContextMenuParams& params) {
CHECK(base::FeatureList::IsEnabled(features::kGuestViewMPArch));
return extension_options_guest_delegate_ &&
extension_options_guest_delegate_->HandleContextMenu(render_frame_host,
params);
}
void ExtensionOptionsGuest::GuestClose() {
DispatchEventToView(std::make_unique<GuestViewEvent>(
api::extension_options_internal::OnClose::kEventName,
base::Value::Dict()));
}
bool ExtensionOptionsGuest::HandleContextMenu(
content::RenderFrameHost& render_frame_host,
const content::ContextMenuParams& params) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
if (!extension_options_guest_delegate_) {
return false;
}
return extension_options_guest_delegate_->HandleContextMenu(render_frame_host,
params);
}
bool ExtensionOptionsGuest::ShouldResumeRequestsForCreatedWindow() {
// Not reached due to the use of `CreateCustomWebContents`.
NOTREACHED();
}
bool ExtensionOptionsGuest::IsWebContentsCreationOverridden(
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
// This method handles opening links from within the guest. Since this guest
// view is used for displaying embedded extension options, we want any
// external links to be opened in a new tab, not in a new guest view so we
// override creation.
return true;
}
WebContents* ExtensionOptionsGuest::CreateCustomWebContents(
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
bool is_renderer_initiated,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const content::StoragePartitionConfig& partition_config,
content::SessionStorageNamespace* session_storage_namespace) {
CHECK(!base::FeatureList::IsEnabled(features::kGuestViewMPArch));
// To get links out of the guest view, we just open the URL in a new tab.
// TODO(ericzeng): Open the tab in the background if the click was a
// ctrl-click or middle mouse button click
if (extension_options_guest_delegate_) {
extension_options_guest_delegate_->OpenURLInNewTab(
content::OpenURLParams(target_url, content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false),
/*navigation_handle_callback=*/{});
}
// Returning nullptr here ensures that the guest-view can never get a
// reference to the new WebContents. It effectively forces a new browsing
// instance for all popups from an extensions guest.
return nullptr;
}
void ExtensionOptionsGuest::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!IsObservedNavigationWithinGuestMainFrame(navigation_handle) ||
!navigation_handle->HasCommitted() || !attached()) {
return;
}
GetZoomController()->SetZoomMode(zoom::ZoomController::ZOOM_MODE_ISOLATED);
SetGuestZoomLevelToMatchEmbedder();
if (!url::IsSameOriginWith(navigation_handle->GetURL(), options_page_)) {
bad_message::ReceivedBadMessage(GetGuestMainFrame()->GetProcess(),
bad_message::EOG_BAD_ORIGIN);
}
}
} // namespace extensions