| // 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 "libcef/browser/extensions/extension_function_details.h" |
| |
| #include "libcef/browser/browser_context.h" |
| #include "libcef/browser/extensions/browser_extensions_util.h" |
| #include "libcef/browser/extensions/extension_system.h" |
| #include "libcef/browser/navigate_params.h" |
| #include "libcef/browser/thread_util.h" |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.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() { |
| 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::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::ThreadPool(), 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 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 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); |
| DISALLOW_COPY_AND_ASSIGN(CefGetExtensionLoadFileCallbackImpl); |
| }; |
| |
| } // namespace |
| |
| CefExtensionFunctionDetails::CefExtensionFunctionDetails( |
| ExtensionFunction* function) |
| : function_(function) {} |
| |
| CefExtensionFunctionDetails::~CefExtensionFunctionDetails() {} |
| |
| Profile* CefExtensionFunctionDetails::GetProfile() const { |
| return Profile::FromBrowserContext(function_->browser_context()); |
| } |
| |
| CefRefPtr<CefBrowserHostImpl> CefExtensionFunctionDetails::GetSenderBrowser() |
| const { |
| content::WebContents* web_contents = function_->GetSenderWebContents(); |
| if (web_contents) |
| return CefBrowserHostImpl::GetBrowserForContents(web_contents); |
| return nullptr; |
| } |
| |
| CefRefPtr<CefBrowserHostImpl> CefExtensionFunctionDetails::GetCurrentBrowser() |
| const { |
| // Start with the browser hosting the extension. |
| CefRefPtr<CefBrowserHostImpl> 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) { |
| CefRefPtr<CefBrowserHostImpl> active_browser_impl = |
| static_cast<CefBrowserHostImpl*>(active_browser.get()); |
| |
| // Make sure we're operating in the same BrowserContextImpl. |
| if (CefBrowserContext::GetForContext(browser->GetBrowserContext()) == |
| CefBrowserContext::GetForContext( |
| 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<CefBrowserHostImpl> target) const { |
| DCHECK(target); |
| |
| // Start with the browser hosting the extension. |
| CefRefPtr<CefBrowserHostImpl> 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<CefBrowserHostImpl> |
| CefExtensionFunctionDetails::GetBrowserForTabIdFirstTime( |
| int tab_id, |
| std::string* error_message) const { |
| DCHECK(!get_browser_called_first_time_); |
| get_browser_called_first_time_ = true; |
| |
| CefRefPtr<CefBrowserHostImpl> 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<CefBrowserHostImpl> |
| 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<CefBrowserHostImpl> 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<CefBrowserHostImpl> 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() {} |
| |
| CefExtensionFunctionDetails::OpenTabParams::~OpenTabParams() {} |
| |
| base::DictionaryValue* CefExtensionFunctionDetails::OpenTab( |
| const OpenTabParams& params, |
| bool user_gesture, |
| std::string* error_message) const { |
| CefRefPtr<CefBrowserHostImpl> sender_browser = GetSenderBrowser(); |
| if (!sender_browser) |
| return nullptr; |
| |
| // windowId defaults to "current" window. |
| int window_id = extension_misc::kCurrentWindowId; |
| if (params.window_id.get()) |
| 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<CefBrowserHostImpl> 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.get() && *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.get()) { |
| std::string url_string = *params.url; |
| url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, |
| function()->extension()); |
| if (!url.is_valid()) { |
| if (error_message) { |
| *error_message = |
| ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string); |
| } |
| return nullptr; |
| } |
| } |
| |
| // Don't let extensions crash the browser or renderers. |
| if (ExtensionTabUtil::IsKillURL(url)) { |
| if (error_message) |
| *error_message = keys::kNoCrashBrowserError; |
| return nullptr; |
| } |
| |
| // Default to foreground for the new tab. The presence of 'active' property |
| // will override this default. |
| bool active = true; |
| if (params.active.get()) |
| active = *params.active; |
| |
| // CEF doesn't use the index value but we let the client see/modify it. |
| int index = 0; |
| if (params.index.get()) |
| index = *params.index; |
| |
| CefBrowserContext* browser_context_impl = |
| CefBrowserContext::GetForContext(active_browser->GetBrowserContext()); |
| |
| // A CEF representation should always exist. |
| CefRefPtr<CefExtension> cef_extension = |
| browser_context_impl->extension_system()->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; |
| |
| CefBrowserHostImpl::CreateParams create_params; |
| create_params.url = url; |
| create_params.request_context = request_context; |
| create_params.window_info.reset(new CefWindowInfo); |
| |
| #if defined(OS_WIN) |
| create_params.window_info->SetAsPopup(nullptr, CefString()); |
| #endif |
| |
| // 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.get() && |
| handler->OnBeforeBrowser(cef_extension, sender_browser.get(), |
| active_browser.get(), index, url.spec(), active, |
| *create_params.window_info, create_params.client, |
| create_params.settings)) { |
| // Cancel the browser creation. |
| return nullptr; |
| } |
| |
| if (active_browser->IsViewsHosted()) { |
| // The new browser will also be Views hosted. |
| create_params.window_info.reset(); |
| } |
| |
| // Browser creation may fail under certain rare circumstances. |
| CefRefPtr<CefBrowserHostImpl> new_browser = |
| CefBrowserHostImpl::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::Feature::Context::UNSPECIFIED_CONTEXT, |
| web_contents); |
| ExtensionTabUtil::ScrubTabForExtension(extension, web_contents, result.get(), |
| scrub_tab_behavior); |
| return result->ToValue().release(); |
| } |
| |
| std::unique_ptr<api::tabs::Tab> CefExtensionFunctionDetails::CreateTabObject( |
| CefRefPtr<CefBrowserHostImpl> new_browser, |
| int opener_browser_id, |
| bool active, |
| int index) const { |
| content::WebContents* contents = new_browser->web_contents(); |
| |
| bool is_loading = contents->IsLoading(); |
| auto tab_object = std::make_unique<api::tabs::Tab>(); |
| tab_object->id = std::make_unique<int>(new_browser->GetIdentifier()); |
| tab_object->index = index; |
| tab_object->window_id = *tab_object->id; |
| tab_object->status = is_loading ? api::tabs::TAB_STATUS_LOADING |
| : api::tabs::TAB_STATUS_COMPLETE; |
| 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 = std::make_unique<int>(contents_size.width()); |
| tab_object->height = std::make_unique<int>(contents_size.height()); |
| tab_object->url = std::make_unique<std::string>(contents->GetURL().spec()); |
| tab_object->title = |
| std::make_unique<std::string>(base::UTF16ToUTF8(contents->GetTitle())); |
| |
| content::NavigationEntry* entry = contents->GetController().GetVisibleEntry(); |
| if (entry && entry->GetFavicon().valid) { |
| tab_object->fav_icon_url = |
| std::make_unique<std::string>(entry->GetFavicon().url.spec()); |
| } |
| |
| if (opener_browser_id >= 0) |
| tab_object->opener_tab_id = std::make_unique<int>(opener_browser_id); |
| |
| return tab_object; |
| } |
| |
| // static |
| std::unique_ptr<api::tabs::MutedInfo> |
| CefExtensionFunctionDetails::CreateMutedInfo(content::WebContents* contents) { |
| DCHECK(contents); |
| std::unique_ptr<api::tabs::MutedInfo> info(new api::tabs::MutedInfo); |
| info->muted = contents->IsAudioMuted(); |
| // TODO(cef): Maybe populate |info->reason|. |
| return info; |
| } |
| |
| CefRefPtr<CefExtension> CefExtensionFunctionDetails::GetCefExtension() const { |
| if (!cef_extension_) { |
| cef_extension_ = |
| static_cast<CefBrowserContext*>(function_->browser_context()) |
| ->extension_system() |
| ->GetExtension(function_->extension_id()); |
| DCHECK(cef_extension_); |
| } |
| return cef_extension_; |
| } |
| |
| } // namespace extensions |