cef/libcef/browser/extensions/extension_function_details.cc

512 lines
17 KiB
C++

// Copyright 2017 the Chromium Embedded Framework Authors. Portions copyright
// 2014 The Chromium Authors. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.
#include "cef/libcef/browser/extensions/extension_function_details.h"
#include <memory>
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "cef/libcef/browser/alloy/alloy_browser_host_impl.h"
#include "cef/libcef/browser/browser_context.h"
#include "cef/libcef/browser/browser_info_manager.h"
#include "cef/libcef/browser/extensions/browser_extensions_util.h"
#include "cef/libcef/browser/extensions/extension_system.h"
#include "cef/libcef/browser/thread_util.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/common/error_utils.h"
using content::RenderViewHost;
using content::WebContents;
namespace extensions {
namespace keys = extensions::tabs_constants;
namespace {
class CefGetExtensionLoadFileCallbackImpl
: public CefGetExtensionResourceCallback {
public:
CefGetExtensionLoadFileCallbackImpl(
const std::string& file,
CefExtensionFunctionDetails::LoadFileCallback callback)
: file_(file), callback_(std::move(callback)) {}
CefGetExtensionLoadFileCallbackImpl(
const CefGetExtensionLoadFileCallbackImpl&) = delete;
CefGetExtensionLoadFileCallbackImpl& operator=(
const CefGetExtensionLoadFileCallbackImpl&) = delete;
~CefGetExtensionLoadFileCallbackImpl() override {
if (!callback_.is_null()) {
// The callback is still pending. Cancel it now.
if (CEF_CURRENTLY_ON_UIT()) {
RunNow(file_, std::move(callback_), nullptr);
} else {
CEF_POST_TASK(CEF_UIT, base::BindOnce(
&CefGetExtensionLoadFileCallbackImpl::RunNow,
file_, std::move(callback_), nullptr));
}
}
}
void Continue(CefRefPtr<CefStreamReader> stream) override {
if (CEF_CURRENTLY_ON_UIT()) {
if (!callback_.is_null()) {
// Always continue asynchronously.
CEF_POST_TASK(CEF_UIT, base::BindOnce(
&CefGetExtensionLoadFileCallbackImpl::RunNow,
file_, std::move(callback_), stream));
}
} else {
CEF_POST_TASK(CEF_UIT, base::BindOnce(
&CefGetExtensionLoadFileCallbackImpl::Continue,
this, stream));
}
}
void Cancel() override { Continue(nullptr); }
void Disconnect() { callback_.Reset(); }
private:
static void RunNow(const std::string& file,
CefExtensionFunctionDetails::LoadFileCallback callback,
CefRefPtr<CefStreamReader> stream) {
CEF_REQUIRE_UIT();
if (!stream) {
std::move(callback).Run(nullptr);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(LoadFileFromStream, file, stream), std::move(callback));
}
static std::unique_ptr<std::string> LoadFileFromStream(
const std::string& file,
CefRefPtr<CefStreamReader> stream) {
CEF_REQUIRE_BLOCKING();
// Move to the end of the stream.
stream->Seek(0, SEEK_END);
const int64_t size = stream->Tell();
if (size == 0) {
LOG(WARNING) << "Extension resource " << file << " is empty.";
return nullptr;
}
std::unique_ptr<std::string> result(new std::string());
result->resize(size);
// Move to the beginning of the stream.
stream->Seek(0, SEEK_SET);
// Read all stream contents into the string.
int64_t read, offset = 0;
do {
read =
static_cast<int>(stream->Read(&(*result)[offset], 1, size - offset));
offset += read;
} while (read > 0 && offset < size);
if (offset != size) {
LOG(WARNING) << "Extension resource " << file << " read failed; expected "
<< size << ", got " << offset << " bytes.";
return nullptr;
}
return result;
}
const std::string file_;
CefExtensionFunctionDetails::LoadFileCallback callback_;
IMPLEMENT_REFCOUNTING(CefGetExtensionLoadFileCallbackImpl);
};
CefRefPtr<AlloyBrowserHostImpl> GetBrowserForTabId(
int tab_id,
content::BrowserContext* browser_context) {
CEF_REQUIRE_UIT();
DCHECK(browser_context);
if (tab_id < 0 || !browser_context) {
return nullptr;
}
auto cef_browser_context =
CefBrowserContext::FromBrowserContext(browser_context);
for (const auto& browser_info :
CefBrowserInfoManager::GetInstance()->GetBrowserInfoList()) {
auto current_browser =
AlloyBrowserHostImpl::FromBaseChecked(browser_info->browser());
if (current_browser && current_browser->GetIdentifier() == tab_id) {
// Make sure we're operating in the same CefBrowserContext.
if (CefBrowserContext::FromBrowserContext(
current_browser->GetBrowserContext()) == cef_browser_context) {
return current_browser;
} else {
LOG(WARNING) << "Browser with tabId " << tab_id
<< " cannot be accessed because is uses a different "
"CefRequestContext";
break;
}
}
}
return nullptr;
}
} // namespace
CefExtensionFunctionDetails::CefExtensionFunctionDetails(
ExtensionFunction* function)
: function_(function) {}
CefExtensionFunctionDetails::~CefExtensionFunctionDetails() = default;
Profile* CefExtensionFunctionDetails::GetProfile() const {
return Profile::FromBrowserContext(function_->browser_context());
}
CefRefPtr<AlloyBrowserHostImpl> CefExtensionFunctionDetails::GetSenderBrowser()
const {
content::WebContents* web_contents = function_->GetSenderWebContents();
if (web_contents) {
return AlloyBrowserHostImpl::GetBrowserForContents(web_contents);
}
return nullptr;
}
CefRefPtr<AlloyBrowserHostImpl> CefExtensionFunctionDetails::GetCurrentBrowser()
const {
// Start with the browser hosting the extension.
CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
if (browser && browser->client()) {
CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
if (handler) {
// Give the handler an opportunity to specify a different browser.
CefRefPtr<CefBrowser> active_browser =
handler->GetActiveBrowser(GetCefExtension(), browser.get(),
function_->include_incognito_information());
if (active_browser && active_browser != browser) {
auto active_browser_impl = AlloyBrowserHostImpl::FromBaseChecked(
CefBrowserHostBase::FromBrowser(active_browser));
// Make sure we're operating in the same CefBrowserContext.
if (CefBrowserContext::FromBrowserContext(
browser->GetBrowserContext()) ==
CefBrowserContext::FromBrowserContext(
active_browser_impl->GetBrowserContext())) {
browser = active_browser_impl;
} else {
LOG(WARNING) << "Browser with tabId "
<< active_browser->GetIdentifier()
<< " cannot be accessed because is uses a different "
"CefRequestContext";
}
}
}
}
// May be null during startup/shutdown.
return browser;
}
bool CefExtensionFunctionDetails::CanAccessBrowser(
CefRefPtr<AlloyBrowserHostImpl> target) const {
DCHECK(target);
// Start with the browser hosting the extension.
CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
if (browser == target) {
// A sender can always access itself.
return true;
}
if (browser && browser->client()) {
CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
if (handler) {
return handler->CanAccessBrowser(
GetCefExtension(), browser.get(),
function_->include_incognito_information(), target);
}
}
// Default to allowing access.
return true;
}
CefRefPtr<AlloyBrowserHostImpl>
CefExtensionFunctionDetails::GetBrowserForTabIdFirstTime(
int tab_id,
std::string* error_message) const {
DCHECK(!get_browser_called_first_time_);
get_browser_called_first_time_ = true;
CefRefPtr<AlloyBrowserHostImpl> browser;
if (tab_id >= 0) {
// May be an invalid tabId or in the wrong BrowserContext.
browser = GetBrowserForTabId(tab_id, function_->browser_context());
if (!browser || !browser->web_contents() || !CanAccessBrowser(browser)) {
if (error_message) {
*error_message = ErrorUtils::FormatErrorMessage(
keys::kTabNotFoundError, base::NumberToString(tab_id));
}
return nullptr;
}
} else {
// May return NULL during shutdown.
browser = GetCurrentBrowser();
if (!browser || !browser->web_contents()) {
if (error_message) {
*error_message = keys::kNoCurrentWindowError;
}
return nullptr;
}
}
return browser;
}
CefRefPtr<AlloyBrowserHostImpl>
CefExtensionFunctionDetails::GetBrowserForTabIdAgain(
int tab_id,
std::string* error_message) const {
DCHECK_GE(tab_id, 0);
DCHECK(get_browser_called_first_time_);
// May return NULL during shutdown.
CefRefPtr<AlloyBrowserHostImpl> browser =
GetBrowserForTabId(tab_id, function_->browser_context());
if (!browser || !browser->web_contents()) {
if (error_message) {
*error_message = ErrorUtils::FormatErrorMessage(
keys::kTabNotFoundError, base::NumberToString(tab_id));
}
}
return browser;
}
bool CefExtensionFunctionDetails::LoadFile(const std::string& file,
LoadFileCallback callback) const {
// Start with the browser hosting the extension.
CefRefPtr<AlloyBrowserHostImpl> browser = GetSenderBrowser();
if (browser && browser->client()) {
CefRefPtr<CefExtensionHandler> handler = GetCefExtension()->GetHandler();
if (handler) {
CefRefPtr<CefGetExtensionLoadFileCallbackImpl> cef_callback(
new CefGetExtensionLoadFileCallbackImpl(file, std::move(callback)));
if (handler->GetExtensionResource(GetCefExtension(), browser.get(), file,
cef_callback)) {
return true;
}
cef_callback->Disconnect();
}
}
return false;
}
CefExtensionFunctionDetails::OpenTabParams::OpenTabParams() = default;
CefExtensionFunctionDetails::OpenTabParams::~OpenTabParams() = default;
std::unique_ptr<api::tabs::Tab> CefExtensionFunctionDetails::OpenTab(
const OpenTabParams& params,
bool user_gesture,
std::string* error_message) const {
CefRefPtr<AlloyBrowserHostImpl> sender_browser = GetSenderBrowser();
if (!sender_browser) {
return nullptr;
}
// windowId defaults to "current" window.
int window_id = extension_misc::kCurrentWindowId;
if (params.window_id.has_value()) {
window_id = *params.window_id;
}
// CEF doesn't have the concept of windows containing tab strips so we'll
// select an "active browser" for BrowserContext sharing instead.
CefRefPtr<AlloyBrowserHostImpl> active_browser =
GetBrowserForTabIdFirstTime(window_id, error_message);
if (!active_browser) {
return nullptr;
}
// If an opener browser was specified then we expect it to exist.
int opener_browser_id = -1;
if (params.opener_tab_id.has_value() && *params.opener_tab_id >= 0) {
if (GetBrowserForTabIdAgain(*params.opener_tab_id, error_message)) {
opener_browser_id = *params.opener_tab_id;
} else {
return nullptr;
}
}
GURL url;
if (params.url.has_value()) {
auto url_expected = ExtensionTabUtil::PrepareURLForNavigation(
*params.url, function()->extension(), function()->browser_context());
if (url_expected.has_value()) {
url = *url_expected;
} else {
*error_message = std::move(url_expected.error());
return nullptr;
}
}
// Default to foreground for the new tab. The presence of 'active' property
// will override this default.
bool active = true;
if (params.active.has_value()) {
active = *params.active;
}
// CEF doesn't use the index value but we let the client see/modify it.
int index = 0;
if (params.index.has_value()) {
index = *params.index;
}
auto cef_browser_context = CefBrowserContext::FromBrowserContext(
active_browser->GetBrowserContext());
// A CEF representation should always exist.
CefRefPtr<CefExtension> cef_extension =
cef_browser_context->GetExtension(function()->extension()->id());
DCHECK(cef_extension);
if (!cef_extension) {
return nullptr;
}
// Always use the same request context that the extension was registered with.
// GetLoaderContext() will return NULL for internal extensions.
CefRefPtr<CefRequestContext> request_context =
cef_extension->GetLoaderContext();
if (!request_context) {
return nullptr;
}
CefBrowserCreateParams create_params;
create_params.url = url.spec();
create_params.request_context = request_context;
CefWindowInfo windowInfo;
CefBrowserCreateParams::InitWindowInfo(&windowInfo, active_browser.get());
// Start with the active browser's settings.
create_params.client = active_browser->GetClient();
create_params.settings = active_browser->settings();
CefRefPtr<CefExtensionHandler> handler = cef_extension->GetHandler();
if (handler &&
handler->OnBeforeBrowser(cef_extension, sender_browser.get(),
active_browser.get(), index, create_params.url,
active, *create_params.window_info,
create_params.client, create_params.settings)) {
// Cancel the browser creation.
return nullptr;
}
create_params.MaybeSetWindowInfo(windowInfo, /*allow_alloy_style=*/true,
/*allow_chrome_style=*/false);
if (active_browser->is_views_hosted()) {
// The new browser will also be Views hosted.
create_params.popup_with_views_hosted_opener = true;
}
// Browser creation may fail under certain rare circumstances.
CefRefPtr<AlloyBrowserHostImpl> new_browser =
AlloyBrowserHostImpl::Create(create_params);
if (!new_browser) {
return nullptr;
}
// Return data about the newly created tab.
auto extension = function()->extension();
auto web_contents = new_browser->web_contents();
auto result = CreateTabObject(new_browser, opener_browser_id, active, index);
auto scrub_tab_behavior = ExtensionTabUtil::GetScrubTabBehavior(
extension, extensions::mojom::ContextType::kUnspecified, web_contents);
ExtensionTabUtil::ScrubTabForExtension(extension, web_contents, &result,
scrub_tab_behavior);
return base::WrapUnique(new api::tabs::Tab(std::move(result)));
}
api::tabs::Tab CefExtensionFunctionDetails::CreateTabObject(
CefRefPtr<AlloyBrowserHostImpl> new_browser,
int opener_browser_id,
bool active,
int index) const {
content::WebContents* contents = new_browser->web_contents();
api::tabs::Tab tab_object;
tab_object.id = new_browser->GetIdentifier();
tab_object.index = index;
tab_object.window_id = *tab_object.id;
tab_object.status = ExtensionTabUtil::GetLoadingStatus(contents);
tab_object.active = active;
tab_object.selected = true;
tab_object.highlighted = true;
tab_object.pinned = false;
// TODO(extensions): Use RecentlyAudibleHelper to populate |audible|.
tab_object.discarded = false;
tab_object.auto_discardable = false;
tab_object.muted_info = CreateMutedInfo(contents);
tab_object.incognito = false;
gfx::Size contents_size = contents->GetContainerBounds().size();
tab_object.width = contents_size.width();
tab_object.height = contents_size.height();
tab_object.url = contents->GetURL().spec();
tab_object.title = base::UTF16ToUTF8(contents->GetTitle());
content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
if (entry && entry->GetFavicon().valid) {
tab_object.fav_icon_url = entry->GetFavicon().url.spec();
}
if (opener_browser_id >= 0) {
tab_object.opener_tab_id = opener_browser_id;
}
return tab_object;
}
// static
api::tabs::MutedInfo CefExtensionFunctionDetails::CreateMutedInfo(
content::WebContents* contents) {
DCHECK(contents);
api::tabs::MutedInfo info;
info.muted = contents->IsAudioMuted();
// TODO(cef): Maybe populate |info.reason|.
return info;
}
CefRefPtr<CefExtension> CefExtensionFunctionDetails::GetCefExtension() const {
if (!cef_extension_) {
cef_extension_ =
CefBrowserContext::FromBrowserContext(function_->browser_context())
->GetExtension(function_->extension_id());
DCHECK(cef_extension_);
}
return cef_extension_;
}
} // namespace extensions