| // Copyright 2015 The Chromium Embedded Framework 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/renderer/render_manager.h" |
| |
| #include <tuple> |
| |
| #include "build/build_config.h" |
| |
| // Enable deprecation warnings on Windows. See http://crbug.com/585142. |
| #if BUILDFLAG(IS_WIN) |
| #if defined(__clang__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic error "-Wdeprecated-declarations" |
| #else |
| #pragma warning(push) |
| #pragma warning(default : 4996) |
| #endif |
| #endif |
| |
| #include "libcef/common/app_manager.h" |
| #include "libcef/common/cef_switches.h" |
| #include "libcef/common/net/scheme_info.h" |
| #include "libcef/common/values_impl.h" |
| #include "libcef/renderer/blink_glue.h" |
| #include "libcef/renderer/browser_impl.h" |
| #include "libcef/renderer/render_frame_observer.h" |
| #include "libcef/renderer/thread_util.h" |
| #include "libcef/renderer/v8_impl.h" |
| |
| #include "base/command_line.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "cef/libcef/common/mojom/cef.mojom.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "extensions/common/switches.h" |
| #include "mojo/public/cpp/bindings/binder_map.h" |
| #include "services/network/public/mojom/cors_origin_pattern.mojom.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/public/platform/web_url.h" |
| #include "third_party/blink/public/web/web_frame.h" |
| #include "third_party/blink/public/web/web_security_policy.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "third_party/blink/public/web/web_view_observer.h" |
| |
| namespace { |
| |
| CefRenderManager* g_manager = nullptr; |
| |
| } // namespace |
| |
| // Placeholder object for guest views. |
| class CefGuestView : public blink::WebViewObserver { |
| public: |
| CefGuestView(CefRenderManager* manager, |
| blink::WebView* web_view, |
| bool is_windowless) |
| : blink::WebViewObserver(web_view), |
| manager_(manager), |
| is_windowless_(is_windowless) {} |
| |
| bool is_windowless() const { return is_windowless_; } |
| |
| private: |
| // RenderViewObserver methods. |
| void OnDestruct() override { manager_->OnGuestViewDestroyed(this); } |
| |
| CefRenderManager* const manager_; |
| const bool is_windowless_; |
| }; |
| |
| CefRenderManager::CefRenderManager() { |
| DCHECK(!g_manager); |
| g_manager = this; |
| } |
| |
| CefRenderManager::~CefRenderManager() { |
| g_manager = nullptr; |
| } |
| |
| // static |
| CefRenderManager* CefRenderManager::Get() { |
| CEF_REQUIRE_RT_RETURN(nullptr); |
| return g_manager; |
| } |
| |
| void CefRenderManager::RenderThreadConnected() { |
| // Retrieve the new render thread information synchronously. |
| auto params = cef::mojom::NewRenderThreadInfo::New(); |
| GetBrowserManager()->GetNewRenderThreadInfo(¶ms); |
| |
| // Cross-origin entries need to be added after WebKit is initialized. |
| if (params->cross_origin_whitelist_entries) { |
| cross_origin_whitelist_entries_.swap( |
| *params->cross_origin_whitelist_entries); |
| } |
| |
| WebKitInitialized(); |
| } |
| |
| void CefRenderManager::RenderFrameCreated( |
| content::RenderFrame* render_frame, |
| CefRenderFrameObserver* render_frame_observer, |
| bool& browser_created, |
| absl::optional<bool>& is_windowless) { |
| auto browser = MaybeCreateBrowser(render_frame->GetWebView(), render_frame, |
| &browser_created, &is_windowless); |
| if (browser) { |
| // Attach the frame to the observer for message routing purposes. |
| render_frame_observer->AttachFrame( |
| browser->GetWebFrameImpl(render_frame->GetWebFrame()).get()); |
| } |
| } |
| |
| void CefRenderManager::WebViewCreated(blink::WebView* web_view, |
| bool& browser_created, |
| absl::optional<bool>& is_windowless) { |
| content::RenderFrame* render_frame = nullptr; |
| if (web_view->MainFrame()->IsWebLocalFrame()) { |
| render_frame = content::RenderFrame::FromWebFrame( |
| web_view->MainFrame()->ToWebLocalFrame()); |
| } |
| |
| MaybeCreateBrowser(web_view, render_frame, &browser_created, &is_windowless); |
| } |
| |
| void CefRenderManager::DevToolsAgentAttached() { |
| ++devtools_agent_count_; |
| } |
| |
| void CefRenderManager::DevToolsAgentDetached() { |
| --devtools_agent_count_; |
| if (devtools_agent_count_ == 0 && uncaught_exception_stack_size_ > 0) { |
| // When the last DevToolsAgent is detached the stack size is set to 0. |
| // Restore the user-specified stack size here. |
| CefV8SetUncaughtExceptionStackSize(uncaught_exception_stack_size_); |
| } |
| } |
| |
| void CefRenderManager::ExposeInterfacesToBrowser(mojo::BinderMap* binders) { |
| auto task_runner = base::SequencedTaskRunner::GetCurrentDefault(); |
| |
| binders->Add<cef::mojom::RenderManager>( |
| base::BindRepeating( |
| [](CefRenderManager* render_manager, |
| mojo::PendingReceiver<cef::mojom::RenderManager> receiver) { |
| render_manager->BindReceiver(std::move(receiver)); |
| }, |
| base::Unretained(this)), |
| task_runner); |
| } |
| |
| CefRefPtr<CefBrowserImpl> CefRenderManager::GetBrowserForView( |
| blink::WebView* view) { |
| BrowserMap::const_iterator it = browsers_.find(view); |
| if (it != browsers_.end()) { |
| return it->second; |
| } |
| return nullptr; |
| } |
| |
| CefRefPtr<CefBrowserImpl> CefRenderManager::GetBrowserForMainFrame( |
| blink::WebFrame* frame) { |
| BrowserMap::const_iterator it = browsers_.begin(); |
| for (; it != browsers_.end(); ++it) { |
| auto web_view = it->second->GetWebView(); |
| if (web_view && web_view->MainFrame() == frame) { |
| return it->second; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| mojo::Remote<cef::mojom::BrowserManager>& |
| CefRenderManager::GetBrowserManager() { |
| if (!browser_manager_) { |
| content::RenderThread::Get()->BindHostReceiver( |
| browser_manager_.BindNewPipeAndPassReceiver()); |
| } |
| return browser_manager_; |
| } |
| |
| // static |
| bool CefRenderManager::IsExtensionProcess() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| extensions::switches::kExtensionProcess); |
| } |
| |
| // static |
| bool CefRenderManager::IsPdfProcess() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kPdfRenderer); |
| } |
| |
| void CefRenderManager::BindReceiver( |
| mojo::PendingReceiver<cef::mojom::RenderManager> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void CefRenderManager::ModifyCrossOriginWhitelistEntry( |
| bool add, |
| cef::mojom::CrossOriginWhiteListEntryPtr entry) { |
| GURL gurl = GURL(entry->source_origin); |
| if (add) { |
| blink::WebSecurityPolicy::AddOriginAccessAllowListEntry( |
| gurl, blink::WebString::FromUTF8(entry->target_protocol), |
| blink::WebString::FromUTF8(entry->target_domain), |
| /*destination_port=*/0, |
| entry->allow_target_subdomains |
| ? network::mojom::CorsDomainMatchMode::kAllowSubdomains |
| : network::mojom::CorsDomainMatchMode::kDisallowSubdomains, |
| network::mojom::CorsPortMatchMode::kAllowAnyPort, |
| network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority); |
| } else { |
| blink::WebSecurityPolicy::ClearOriginAccessListForOrigin(gurl); |
| } |
| } |
| |
| void CefRenderManager::ClearCrossOriginWhitelist() { |
| blink::WebSecurityPolicy::ClearOriginAccessList(); |
| } |
| |
| void CefRenderManager::WebKitInitialized() { |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| |
| // Create global objects associated with the default Isolate. |
| CefV8IsolateCreated(); |
| |
| const CefAppManager::SchemeInfoList* schemes = |
| CefAppManager::Get()->GetCustomSchemes(); |
| if (!schemes->empty()) { |
| // Register the custom schemes. Some attributes are excluded here because |
| // they use url/url_util.h APIs instead. |
| CefAppManager::SchemeInfoList::const_iterator it = schemes->begin(); |
| for (; it != schemes->end(); ++it) { |
| const CefSchemeInfo& info = *it; |
| const blink::WebString& scheme = |
| blink::WebString::FromUTF8(info.scheme_name); |
| if (info.is_display_isolated) { |
| blink::WebSecurityPolicy::RegisterURLSchemeAsDisplayIsolated(scheme); |
| } |
| if (info.is_fetch_enabled) { |
| blink_glue::RegisterURLSchemeAsSupportingFetchAPI(scheme); |
| } |
| } |
| } |
| |
| if (!cross_origin_whitelist_entries_.empty()) { |
| // Add the cross-origin white list entries. |
| for (auto& entry : cross_origin_whitelist_entries_) { |
| ModifyCrossOriginWhitelistEntry(/*add=*/true, std::move(entry)); |
| } |
| cross_origin_whitelist_entries_.clear(); |
| } |
| |
| // The number of stack trace frames to capture for uncaught exceptions. |
| if (command_line->HasSwitch(switches::kUncaughtExceptionStackSize)) { |
| int uncaught_exception_stack_size = 0; |
| base::StringToInt(command_line->GetSwitchValueASCII( |
| switches::kUncaughtExceptionStackSize), |
| &uncaught_exception_stack_size); |
| |
| if (uncaught_exception_stack_size > 0) { |
| uncaught_exception_stack_size_ = uncaught_exception_stack_size; |
| CefV8SetUncaughtExceptionStackSize(uncaught_exception_stack_size_); |
| } |
| } |
| |
| // Notify the render process handler. |
| CefRefPtr<CefApp> application = CefAppManager::Get()->GetApplication(); |
| if (application.get()) { |
| CefRefPtr<CefRenderProcessHandler> handler = |
| application->GetRenderProcessHandler(); |
| if (handler.get()) { |
| handler->OnWebKitInitialized(); |
| } |
| } |
| } |
| |
| CefRefPtr<CefBrowserImpl> CefRenderManager::MaybeCreateBrowser( |
| blink::WebView* web_view, |
| content::RenderFrame* render_frame, |
| bool* browser_created, |
| absl::optional<bool>* is_windowless) { |
| if (browser_created) { |
| *browser_created = false; |
| } |
| |
| if (!web_view || !render_frame) { |
| return nullptr; |
| } |
| |
| // Don't create another browser or guest view object if one already exists for |
| // the view. |
| auto browser = GetBrowserForView(web_view); |
| if (browser) { |
| if (is_windowless) { |
| *is_windowless = browser->is_windowless(); |
| } |
| return browser; |
| } |
| |
| auto guest_view = GetGuestViewForView(web_view); |
| if (guest_view) { |
| if (is_windowless) { |
| *is_windowless = guest_view->is_windowless(); |
| } |
| return nullptr; |
| } |
| |
| const bool is_pdf = IsPdfProcess(); |
| |
| auto params = cef::mojom::NewBrowserInfo::New(); |
| if (!is_pdf) { |
| // Retrieve browser information synchronously. |
| GetBrowserManager()->GetNewBrowserInfo(render_frame->GetRoutingID(), |
| ¶ms); |
| if (params->browser_id == 0) { |
| // The popup may have been canceled during creation. |
| return nullptr; |
| } |
| } |
| |
| if (is_windowless) { |
| *is_windowless = params->is_windowless; |
| } |
| |
| if (is_pdf || params->is_guest_view || params->browser_id < 0) { |
| // Don't create a CefBrowser for a PDF renderer, guest view, or if the new |
| // browser info response has timed out. |
| guest_views_.insert(std::make_pair( |
| web_view, |
| std::make_unique<CefGuestView>(this, web_view, params->is_windowless))); |
| return nullptr; |
| } |
| |
| browser = new CefBrowserImpl(web_view, params->browser_id, params->is_popup, |
| params->is_windowless); |
| browsers_.insert(std::make_pair(web_view, browser)); |
| |
| // Notify the render process handler. |
| CefRefPtr<CefApp> application = CefAppManager::Get()->GetApplication(); |
| if (application.get()) { |
| CefRefPtr<CefRenderProcessHandler> handler = |
| application->GetRenderProcessHandler(); |
| if (handler.get()) { |
| CefRefPtr<CefDictionaryValueImpl> dictValuePtr; |
| if (params->extra_info) { |
| dictValuePtr = |
| new CefDictionaryValueImpl(std::move(*params->extra_info), |
| /*read_only=*/true); |
| } |
| handler->OnBrowserCreated(browser.get(), dictValuePtr.get()); |
| } |
| } |
| |
| if (browser_created) { |
| *browser_created = true; |
| } |
| |
| return browser; |
| } |
| |
| void CefRenderManager::OnBrowserDestroyed(CefBrowserImpl* browser) { |
| BrowserMap::iterator it = browsers_.begin(); |
| for (; it != browsers_.end(); ++it) { |
| if (it->second.get() == browser) { |
| browsers_.erase(it); |
| return; |
| } |
| } |
| |
| // No browser was found in the map. |
| DCHECK(false); |
| } |
| |
| CefGuestView* CefRenderManager::GetGuestViewForView(blink::WebView* view) { |
| CEF_REQUIRE_RT_RETURN(nullptr); |
| |
| GuestViewMap::const_iterator it = guest_views_.find(view); |
| if (it != guest_views_.end()) { |
| return it->second.get(); |
| } |
| return nullptr; |
| } |
| |
| void CefRenderManager::OnGuestViewDestroyed(CefGuestView* guest_view) { |
| GuestViewMap::iterator it = guest_views_.begin(); |
| for (; it != guest_views_.end(); ++it) { |
| if (it->second.get() == guest_view) { |
| guest_views_.erase(it); |
| return; |
| } |
| } |
| |
| // No guest view was found in the map. |
| DCHECK(false); |
| } |
| |
| // Enable deprecation warnings on Windows. See http://crbug.com/585142. |
| #if BUILDFLAG(IS_WIN) |
| #if defined(__clang__) |
| #pragma GCC diagnostic pop |
| #else |
| #pragma warning(pop) |
| #endif |
| #endif |