1 // Copyright 2020 The Chromium Embedded Framework Authors.
2 // Portions copyright 2012 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "libcef/browser/chrome/chrome_content_browser_client_cef.h"
7
8 #include <tuple>
9
10 #include "libcef/browser/browser_frame.h"
11 #include "libcef/browser/browser_info_manager.h"
12 #include "libcef/browser/browser_manager.h"
13 #include "libcef/browser/chrome/chrome_browser_host_impl.h"
14 #include "libcef/browser/chrome/chrome_browser_main_extra_parts_cef.h"
15 #include "libcef/browser/context.h"
16 #include "libcef/browser/net/chrome_scheme_handler.h"
17 #include "libcef/browser/net/throttle_handler.h"
18 #include "libcef/browser/net_service/cookie_manager_impl.h"
19 #include "libcef/browser/net_service/login_delegate.h"
20 #include "libcef/browser/net_service/proxy_url_loader_factory.h"
21 #include "libcef/browser/net_service/resource_request_handler_wrapper.h"
22 #include "libcef/browser/prefs/browser_prefs.h"
23 #include "libcef/browser/prefs/renderer_prefs.h"
24 #include "libcef/common/app_manager.h"
25 #include "libcef/common/cef_switches.h"
26 #include "libcef/common/command_line_impl.h"
27
28 #include "base/command_line.h"
29 #include "base/path_service.h"
30 #include "chrome/browser/chrome_browser_main.h"
31 #include "chrome/browser/net/system_network_context_manager.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "content/public/browser/navigation_throttle.h"
35 #include "content/public/browser/render_process_host.h"
36 #include "content/public/browser/render_view_host.h"
37 #include "content/public/browser/render_widget_host.h"
38 #include "content/public/browser/render_widget_host_view.h"
39 #include "content/public/browser/weak_document_ptr.h"
40 #include "content/public/common/content_switches.h"
41 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
42 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
43
44 namespace {
45
HandleExternalProtocolHelper(ChromeContentBrowserClientCef * self,content::WebContents::Getter web_contents_getter,int frame_tree_node_id,content::NavigationUIData * navigation_data,network::mojom::WebSandboxFlags sandbox_flags,const network::ResourceRequest & resource_request,const absl::optional<url::Origin> & initiating_origin,content::WeakDocumentPtr initiator_document)46 void HandleExternalProtocolHelper(
47 ChromeContentBrowserClientCef* self,
48 content::WebContents::Getter web_contents_getter,
49 int frame_tree_node_id,
50 content::NavigationUIData* navigation_data,
51 network::mojom::WebSandboxFlags sandbox_flags,
52 const network::ResourceRequest& resource_request,
53 const absl::optional<url::Origin>& initiating_origin,
54 content::WeakDocumentPtr initiator_document) {
55 // May return nullptr if frame has been deleted or a cross-document navigation
56 // has committed in the same RenderFrameHost.
57 auto initiator_rfh = initiator_document.AsRenderFrameHostIfValid();
58 if (!initiator_rfh)
59 return;
60
61 // Match the logic of the original call in
62 // NavigationURLLoaderImpl::PrepareForNonInterceptedRequest.
63 self->HandleExternalProtocol(
64 resource_request.url, web_contents_getter,
65 content::ChildProcessHost::kInvalidUniqueID, frame_tree_node_id,
66 navigation_data,
67 resource_request.resource_type ==
68 static_cast<int>(blink::mojom::ResourceType::kMainFrame),
69 sandbox_flags,
70 static_cast<ui::PageTransition>(resource_request.transition_type),
71 resource_request.has_user_gesture, initiating_origin, initiator_rfh,
72 nullptr);
73 }
74
75 } // namespace
76
77 ChromeContentBrowserClientCef::ChromeContentBrowserClientCef() = default;
78 ChromeContentBrowserClientCef::~ChromeContentBrowserClientCef() = default;
79
80 std::unique_ptr<content::BrowserMainParts>
CreateBrowserMainParts(content::MainFunctionParams parameters)81 ChromeContentBrowserClientCef::CreateBrowserMainParts(
82 content::MainFunctionParams parameters) {
83 auto main_parts =
84 ChromeContentBrowserClient::CreateBrowserMainParts(std::move(parameters));
85 browser_main_parts_ = new ChromeBrowserMainExtraPartsCef;
86 static_cast<ChromeBrowserMainParts*>(main_parts.get())
87 ->AddParts(
88 base::WrapUnique<ChromeBrowserMainExtraParts>(browser_main_parts_));
89 return main_parts;
90 }
91
AppendExtraCommandLineSwitches(base::CommandLine * command_line,int child_process_id)92 void ChromeContentBrowserClientCef::AppendExtraCommandLineSwitches(
93 base::CommandLine* command_line,
94 int child_process_id) {
95 ChromeContentBrowserClient::AppendExtraCommandLineSwitches(command_line,
96 child_process_id);
97
98 // Necessary to launch sub-processes in the correct mode.
99 command_line->AppendSwitch(switches::kEnableChromeRuntime);
100
101 // Necessary to populate DIR_USER_DATA in sub-processes.
102 // See resource_util.cc GetUserDataPath.
103 base::FilePath user_data_dir;
104 if (base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
105 command_line->AppendSwitchPath(switches::kUserDataDir, user_data_dir);
106 }
107
108 const base::CommandLine* browser_cmd = base::CommandLine::ForCurrentProcess();
109
110 {
111 // Propagate the following switches to all command lines (along with any
112 // associated values) if present in the browser command line.
113 static const char* const kSwitchNames[] = {
114 switches::kUserAgentProductAndVersion,
115 };
116 command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames,
117 base::size(kSwitchNames));
118 }
119
120 const std::string& process_type =
121 command_line->GetSwitchValueASCII(switches::kProcessType);
122 if (process_type == switches::kRendererProcess) {
123 // Propagate the following switches to the renderer command line (along with
124 // any associated values) if present in the browser command line.
125 static const char* const kSwitchNames[] = {
126 switches::kUncaughtExceptionStackSize,
127 };
128 command_line->CopySwitchesFrom(*browser_cmd, kSwitchNames,
129 base::size(kSwitchNames));
130 }
131
132 CefRefPtr<CefApp> app = CefAppManager::Get()->GetApplication();
133 if (app.get()) {
134 CefRefPtr<CefBrowserProcessHandler> handler =
135 app->GetBrowserProcessHandler();
136 if (handler.get()) {
137 CefRefPtr<CefCommandLineImpl> commandLinePtr(
138 new CefCommandLineImpl(command_line, false, false));
139 handler->OnBeforeChildProcessLaunch(commandLinePtr.get());
140 std::ignore = commandLinePtr->Detach(nullptr);
141 }
142 }
143 }
144
RenderProcessWillLaunch(content::RenderProcessHost * host)145 void ChromeContentBrowserClientCef::RenderProcessWillLaunch(
146 content::RenderProcessHost* host) {
147 ChromeContentBrowserClient::RenderProcessWillLaunch(host);
148
149 // If the renderer process crashes then the host may already have
150 // CefBrowserInfoManager as an observer. Try to remove it first before adding
151 // to avoid DCHECKs.
152 host->RemoveObserver(CefBrowserInfoManager::GetInstance());
153 host->AddObserver(CefBrowserInfoManager::GetInstance());
154 }
155
CanCreateWindow(content::RenderFrameHost * opener,const GURL & opener_url,const GURL & opener_top_level_frame_url,const url::Origin & source_origin,content::mojom::WindowContainerType container_type,const GURL & target_url,const content::Referrer & referrer,const std::string & frame_name,WindowOpenDisposition disposition,const blink::mojom::WindowFeatures & features,bool user_gesture,bool opener_suppressed,bool * no_javascript_access)156 bool ChromeContentBrowserClientCef::CanCreateWindow(
157 content::RenderFrameHost* opener,
158 const GURL& opener_url,
159 const GURL& opener_top_level_frame_url,
160 const url::Origin& source_origin,
161 content::mojom::WindowContainerType container_type,
162 const GURL& target_url,
163 const content::Referrer& referrer,
164 const std::string& frame_name,
165 WindowOpenDisposition disposition,
166 const blink::mojom::WindowFeatures& features,
167 bool user_gesture,
168 bool opener_suppressed,
169 bool* no_javascript_access) {
170 // The chrome layer has popup blocker, extensions, etc.
171 if (!ChromeContentBrowserClient::CanCreateWindow(
172 opener, opener_url, opener_top_level_frame_url, source_origin,
173 container_type, target_url, referrer, frame_name, disposition,
174 features, user_gesture, opener_suppressed, no_javascript_access)) {
175 return false;
176 }
177
178 return CefBrowserInfoManager::GetInstance()->CanCreateWindow(
179 opener, target_url, referrer, frame_name, disposition, features,
180 user_gesture, opener_suppressed, no_javascript_access);
181 }
182
OverrideWebkitPrefs(content::WebContents * web_contents,blink::web_pref::WebPreferences * prefs)183 void ChromeContentBrowserClientCef::OverrideWebkitPrefs(
184 content::WebContents* web_contents,
185 blink::web_pref::WebPreferences* prefs) {
186 renderer_prefs::SetDefaultPrefs(*prefs);
187
188 ChromeContentBrowserClient::OverrideWebkitPrefs(web_contents, prefs);
189
190 SkColor base_background_color;
191 auto browser = ChromeBrowserHostImpl::GetBrowserForContents(web_contents);
192 if (browser) {
193 renderer_prefs::SetCefPrefs(browser->settings(), *prefs);
194
195 // Set the background color for the WebView.
196 base_background_color = browser->GetBackgroundColor();
197 } else {
198 // We don't know for sure that the browser will be windowless but assume
199 // that the global windowless state is likely to be accurate.
200 base_background_color =
201 CefContext::Get()->GetBackgroundColor(nullptr, STATE_DEFAULT);
202 }
203
204 web_contents->SetPageBaseBackgroundColor(base_background_color);
205 }
206
WillCreateURLLoaderFactory(content::BrowserContext * browser_context,content::RenderFrameHost * frame,int render_process_id,URLLoaderFactoryType type,const url::Origin & request_initiator,absl::optional<int64_t> navigation_id,ukm::SourceIdObj ukm_source_id,mojo::PendingReceiver<network::mojom::URLLoaderFactory> * factory_receiver,mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient> * header_client,bool * bypass_redirect_checks,bool * disable_secure_dns,network::mojom::URLLoaderFactoryOverridePtr * factory_override)207 bool ChromeContentBrowserClientCef::WillCreateURLLoaderFactory(
208 content::BrowserContext* browser_context,
209 content::RenderFrameHost* frame,
210 int render_process_id,
211 URLLoaderFactoryType type,
212 const url::Origin& request_initiator,
213 absl::optional<int64_t> navigation_id,
214 ukm::SourceIdObj ukm_source_id,
215 mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver,
216 mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
217 header_client,
218 bool* bypass_redirect_checks,
219 bool* disable_secure_dns,
220 network::mojom::URLLoaderFactoryOverridePtr* factory_override) {
221 bool use_proxy = ChromeContentBrowserClient::WillCreateURLLoaderFactory(
222 browser_context, frame, render_process_id, type, request_initiator,
223 navigation_id, ukm_source_id, factory_receiver, header_client,
224 bypass_redirect_checks, disable_secure_dns, factory_override);
225 if (use_proxy) {
226 // The chrome layer will handle the request.
227 return use_proxy;
228 }
229
230 // Don't intercept requests for Profiles that were not created by CEF.
231 // For example, the User Manager profile created via
232 // profiles::CreateSystemProfileForUserManager.
233 auto profile = Profile::FromBrowserContext(browser_context);
234 if (!CefBrowserContext::FromProfile(profile))
235 return false;
236
237 auto request_handler = net_service::CreateInterceptedRequestHandler(
238 browser_context, frame, render_process_id,
239 type == URLLoaderFactoryType::kNavigation,
240 type == URLLoaderFactoryType::kDownload, request_initiator);
241
242 net_service::ProxyURLLoaderFactory::CreateProxy(
243 browser_context, factory_receiver, header_client,
244 std::move(request_handler));
245 return true;
246 }
247
HandleExternalProtocol(const GURL & url,content::WebContents::Getter web_contents_getter,int child_id,int frame_tree_node_id,content::NavigationUIData * navigation_data,bool is_main_frame,network::mojom::WebSandboxFlags sandbox_flags,ui::PageTransition page_transition,bool has_user_gesture,const absl::optional<url::Origin> & initiating_origin,content::RenderFrameHost * initiator_document,mojo::PendingRemote<network::mojom::URLLoaderFactory> * out_factory)248 bool ChromeContentBrowserClientCef::HandleExternalProtocol(
249 const GURL& url,
250 content::WebContents::Getter web_contents_getter,
251 int child_id,
252 int frame_tree_node_id,
253 content::NavigationUIData* navigation_data,
254 bool is_main_frame,
255 network::mojom::WebSandboxFlags sandbox_flags,
256 ui::PageTransition page_transition,
257 bool has_user_gesture,
258 const absl::optional<url::Origin>& initiating_origin,
259 content::RenderFrameHost* initiator_document,
260 mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory) {
261 // |out_factory| will be non-nullptr when this method is initially called
262 // from NavigationURLLoaderImpl::PrepareForNonInterceptedRequest.
263 if (out_factory) {
264 // Let the other HandleExternalProtocol variant handle the request.
265 return false;
266 }
267
268 // The request was unhandled and we've recieved a callback from
269 // HandleExternalProtocolHelper. Forward to the chrome layer for default
270 // handling.
271 return ChromeContentBrowserClient::HandleExternalProtocol(
272 url, web_contents_getter, child_id, frame_tree_node_id, navigation_data,
273 is_main_frame, sandbox_flags, page_transition, has_user_gesture,
274 initiating_origin, initiator_document, nullptr);
275 }
276
HandleExternalProtocol(content::WebContents::Getter web_contents_getter,int frame_tree_node_id,content::NavigationUIData * navigation_data,network::mojom::WebSandboxFlags sandbox_flags,const network::ResourceRequest & resource_request,const absl::optional<url::Origin> & initiating_origin,content::RenderFrameHost * initiator_document,mojo::PendingRemote<network::mojom::URLLoaderFactory> * out_factory)277 bool ChromeContentBrowserClientCef::HandleExternalProtocol(
278 content::WebContents::Getter web_contents_getter,
279 int frame_tree_node_id,
280 content::NavigationUIData* navigation_data,
281 network::mojom::WebSandboxFlags sandbox_flags,
282 const network::ResourceRequest& resource_request,
283 const absl::optional<url::Origin>& initiating_origin,
284 content::RenderFrameHost* initiator_document,
285 mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory) {
286 mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver =
287 out_factory->InitWithNewPipeAndPassReceiver();
288
289 auto weak_initiator_document = initiator_document
290 ? initiator_document->GetWeakDocumentPtr()
291 : content::WeakDocumentPtr();
292
293 // HandleExternalProtocolHelper may be called if nothing handles the request.
294 auto request_handler = net_service::CreateInterceptedRequestHandler(
295 web_contents_getter, frame_tree_node_id, resource_request,
296 base::BindRepeating(HandleExternalProtocolHelper, base::Unretained(this),
297 web_contents_getter, frame_tree_node_id,
298 navigation_data, sandbox_flags, resource_request,
299 initiating_origin,
300 std::move(weak_initiator_document)));
301
302 net_service::ProxyURLLoaderFactory::CreateProxy(
303 web_contents_getter, std::move(receiver), std::move(request_handler));
304 return true;
305 }
306
307 std::vector<std::unique_ptr<content::NavigationThrottle>>
CreateThrottlesForNavigation(content::NavigationHandle * navigation_handle)308 ChromeContentBrowserClientCef::CreateThrottlesForNavigation(
309 content::NavigationHandle* navigation_handle) {
310 auto throttles = ChromeContentBrowserClient::CreateThrottlesForNavigation(
311 navigation_handle);
312 throttle::CreateThrottlesForNavigation(navigation_handle, throttles);
313 return throttles;
314 }
315
ConfigureNetworkContextParams(content::BrowserContext * context,bool in_memory,const base::FilePath & relative_partition_path,network::mojom::NetworkContextParams * network_context_params,cert_verifier::mojom::CertVerifierCreationParams * cert_verifier_creation_params)316 bool ChromeContentBrowserClientCef::ConfigureNetworkContextParams(
317 content::BrowserContext* context,
318 bool in_memory,
319 const base::FilePath& relative_partition_path,
320 network::mojom::NetworkContextParams* network_context_params,
321 cert_verifier::mojom::CertVerifierCreationParams*
322 cert_verifier_creation_params) {
323 // This method may be called during shutdown when using multi-threaded
324 // message loop mode. In that case exit early to avoid crashes.
325 if (!SystemNetworkContextManager::GetInstance()) {
326 // Cancel NetworkContext creation in
327 // StoragePartitionImpl::InitNetworkContext.
328 return false;
329 }
330
331 ChromeContentBrowserClient::ConfigureNetworkContextParams(
332 context, in_memory, relative_partition_path, network_context_params,
333 cert_verifier_creation_params);
334
335 auto cef_context = CefBrowserContext::FromBrowserContext(context);
336 network_context_params->cookieable_schemes =
337 cef_context ? cef_context->GetCookieableSchemes()
338 : CefBrowserContext::GetGlobalCookieableSchemes();
339
340 // Prefer the CEF settings configuration, if specified, instead of the
341 // kAcceptLanguages preference which is controlled by the
342 // chrome://settings/languages configuration.
343 const std::string& accept_language_list =
344 browser_prefs::GetAcceptLanguageList(cef_context, /*browser=*/nullptr,
345 /*expand=*/true);
346 if (!accept_language_list.empty() &&
347 accept_language_list != network_context_params->accept_language) {
348 network_context_params->accept_language = accept_language_list;
349 }
350
351 return true;
352 }
353
354 std::unique_ptr<content::LoginDelegate>
CreateLoginDelegate(const net::AuthChallengeInfo & auth_info,content::WebContents * web_contents,const content::GlobalRequestID & request_id,bool is_request_for_main_frame,const GURL & url,scoped_refptr<net::HttpResponseHeaders> response_headers,bool first_auth_attempt,LoginAuthRequiredCallback auth_required_callback)355 ChromeContentBrowserClientCef::CreateLoginDelegate(
356 const net::AuthChallengeInfo& auth_info,
357 content::WebContents* web_contents,
358 const content::GlobalRequestID& request_id,
359 bool is_request_for_main_frame,
360 const GURL& url,
361 scoped_refptr<net::HttpResponseHeaders> response_headers,
362 bool first_auth_attempt,
363 LoginAuthRequiredCallback auth_required_callback) {
364 // |web_contents| is nullptr for CefURLRequests without an associated frame.
365 if (!web_contents || base::CommandLine::ForCurrentProcess()->HasSwitch(
366 switches::kDisableChromeLoginPrompt)) {
367 // Delegate auth callbacks to GetAuthCredentials.
368 return std::make_unique<net_service::LoginDelegate>(
369 auth_info, web_contents, request_id, url,
370 std::move(auth_required_callback));
371 }
372
373 return ChromeContentBrowserClient::CreateLoginDelegate(
374 auth_info, web_contents, request_id, is_request_for_main_frame, url,
375 response_headers, first_auth_attempt, std::move(auth_required_callback));
376 }
377
BrowserURLHandlerCreated(content::BrowserURLHandler * handler)378 void ChromeContentBrowserClientCef::BrowserURLHandlerCreated(
379 content::BrowserURLHandler* handler) {
380 // Register the Chrome handlers first for proper URL rewriting.
381 ChromeContentBrowserClient::BrowserURLHandlerCreated(handler);
382 scheme::BrowserURLHandlerCreated(handler);
383 }
384
IsWebUIAllowedToMakeNetworkRequests(const url::Origin & origin)385 bool ChromeContentBrowserClientCef::IsWebUIAllowedToMakeNetworkRequests(
386 const url::Origin& origin) {
387 return scheme::IsWebUIAllowedToMakeNetworkRequests(origin);
388 }
389
ExposeInterfacesToRenderer(service_manager::BinderRegistry * registry,blink::AssociatedInterfaceRegistry * associated_registry,content::RenderProcessHost * host)390 void ChromeContentBrowserClientCef::ExposeInterfacesToRenderer(
391 service_manager::BinderRegistry* registry,
392 blink::AssociatedInterfaceRegistry* associated_registry,
393 content::RenderProcessHost* host) {
394 ChromeContentBrowserClient::ExposeInterfacesToRenderer(
395 registry, associated_registry, host);
396
397 CefBrowserManager::ExposeInterfacesToRenderer(registry, associated_registry,
398 host);
399 }
400
RegisterBrowserInterfaceBindersForFrame(content::RenderFrameHost * render_frame_host,mojo::BinderMapWithContext<content::RenderFrameHost * > * map)401 void ChromeContentBrowserClientCef::RegisterBrowserInterfaceBindersForFrame(
402 content::RenderFrameHost* render_frame_host,
403 mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
404 ChromeContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
405 render_frame_host, map);
406
407 CefBrowserFrame::RegisterBrowserInterfaceBindersForFrame(render_frame_host,
408 map);
409 }
410
411 CefRefPtr<CefRequestContextImpl>
request_context() const412 ChromeContentBrowserClientCef::request_context() const {
413 return browser_main_parts_->request_context();
414 }
415
416 scoped_refptr<base::SingleThreadTaskRunner>
background_task_runner() const417 ChromeContentBrowserClientCef::background_task_runner() const {
418 return browser_main_parts_->background_task_runner();
419 }
420
421 scoped_refptr<base::SingleThreadTaskRunner>
user_visible_task_runner() const422 ChromeContentBrowserClientCef::user_visible_task_runner() const {
423 return browser_main_parts_->user_visible_task_runner();
424 }
425
426 scoped_refptr<base::SingleThreadTaskRunner>
user_blocking_task_runner() const427 ChromeContentBrowserClientCef::user_blocking_task_runner() const {
428 return browser_main_parts_->user_blocking_task_runner();
429 }
430