• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "libcef/browser/devtools/devtools_frontend.h"
6 
7 #include <stddef.h>
8 
9 #include <iomanip>
10 #include <utility>
11 
12 #include "libcef/browser/browser_context.h"
13 #include "libcef/browser/devtools/devtools_manager_delegate.h"
14 #include "libcef/browser/net/devtools_scheme_handler.h"
15 #include "libcef/common/cef_switches.h"
16 #include "libcef/common/task_runner_manager.h"
17 
18 #include "base/base64.h"
19 #include "base/command_line.h"
20 #include "base/files/file_util.h"
21 #include "base/guid.h"
22 #include "base/json/json_reader.h"
23 #include "base/json/json_writer.h"
24 #include "base/json/string_escape.h"
25 #include "base/macros.h"
26 #include "base/memory/ptr_util.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/stringprintf.h"
30 #include "base/strings/utf_string_conversions.h"
31 #include "base/task/post_task.h"
32 #include "base/values.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/common/pref_names.h"
35 #include "components/prefs/scoped_user_pref_update.h"
36 #include "content/public/browser/browser_context.h"
37 #include "content/public/browser/browser_task_traits.h"
38 #include "content/public/browser/browser_thread.h"
39 #include "content/public/browser/file_url_loader.h"
40 #include "content/public/browser/navigation_handle.h"
41 #include "content/public/browser/render_frame_host.h"
42 #include "content/public/browser/render_view_host.h"
43 #include "content/public/browser/shared_cors_origin_access_list.h"
44 #include "content/public/browser/storage_partition.h"
45 #include "content/public/browser/web_contents.h"
46 #include "content/public/common/content_client.h"
47 #include "content/public/common/url_constants.h"
48 #include "content/public/common/url_utils.h"
49 #include "ipc/ipc_channel.h"
50 #include "net/base/completion_once_callback.h"
51 #include "net/base/io_buffer.h"
52 #include "net/base/net_errors.h"
53 #include "net/http/http_response_headers.h"
54 #include "net/traffic_annotation/network_traffic_annotation.h"
55 #include "services/network/public/cpp/simple_url_loader.h"
56 #include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
57 #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
58 #include "services/network/public/mojom/url_response_head.mojom.h"
59 #include "storage/browser/file_system/native_file_util.h"
60 
61 #if defined(OS_WIN)
62 #include <windows.h>
63 #elif defined(OS_POSIX)
64 #include <time.h>
65 #endif
66 
67 namespace {
68 
GetFrontendURL()69 static std::string GetFrontendURL() {
70   return base::StringPrintf("%s://%s/devtools_app.html",
71                             content::kChromeDevToolsScheme,
72                             scheme::kChromeDevToolsHost);
73 }
74 
BuildObjectForResponse(const net::HttpResponseHeaders * rh,bool success,int net_error)75 std::unique_ptr<base::DictionaryValue> BuildObjectForResponse(
76     const net::HttpResponseHeaders* rh,
77     bool success,
78     int net_error) {
79   auto response = std::make_unique<base::DictionaryValue>();
80   int responseCode = 200;
81   if (rh) {
82     responseCode = rh->response_code();
83   } else if (!success) {
84     // In case of no headers, assume file:// URL and failed to load
85     responseCode = 404;
86   }
87   response->SetInteger("statusCode", responseCode);
88   response->SetInteger("netError", net_error);
89   response->SetString("netErrorName", net::ErrorToString(net_error));
90 
91   auto headers = std::make_unique<base::DictionaryValue>();
92   size_t iterator = 0;
93   std::string name;
94   std::string value;
95   // TODO(caseq): this probably needs to handle duplicate header names
96   // correctly by folding them.
97   while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value))
98     headers->SetString(name, value);
99 
100   response->Set("headers", std::move(headers));
101   return response;
102 }
103 
104 const int kMaxLogLineLength = 1024;
105 
WriteTimestamp(std::stringstream & stream)106 void WriteTimestamp(std::stringstream& stream) {
107 #if defined(OS_WIN)
108   SYSTEMTIME local_time;
109   GetLocalTime(&local_time);
110   stream << std::setfill('0') << std::setw(2) << local_time.wMonth
111          << std::setw(2) << local_time.wDay << '/' << std::setw(2)
112          << local_time.wHour << std::setw(2) << local_time.wMinute
113          << std::setw(2) << local_time.wSecond << '.' << std::setw(3)
114          << local_time.wMilliseconds;
115 #elif defined(OS_POSIX)
116   timeval tv;
117   gettimeofday(&tv, nullptr);
118   time_t t = tv.tv_sec;
119   struct tm local_time;
120   localtime_r(&t, &local_time);
121   struct tm* tm_time = &local_time;
122   stream << std::setfill('0') << std::setw(2) << 1 + tm_time->tm_mon
123          << std::setw(2) << tm_time->tm_mday << '/' << std::setw(2)
124          << tm_time->tm_hour << std::setw(2) << tm_time->tm_min << std::setw(2)
125          << tm_time->tm_sec << '.' << std::setw(6) << tv.tv_usec;
126 #else
127 #error Unsupported platform
128 #endif
129 }
130 
LogProtocolMessage(const base::FilePath & log_file,ProtocolMessageType type,std::string to_log)131 void LogProtocolMessage(const base::FilePath& log_file,
132                         ProtocolMessageType type,
133                         std::string to_log) {
134   // Track if logging has failed, in which case we don't keep trying.
135   static bool log_error = false;
136   if (log_error)
137     return;
138 
139   if (storage::NativeFileUtil::EnsureFileExists(log_file, nullptr) !=
140       base::File::FILE_OK) {
141     LOG(ERROR) << "Failed to create file " << log_file.value();
142     log_error = true;
143     return;
144   }
145 
146   std::string type_label;
147   switch (type) {
148     case ProtocolMessageType::METHOD:
149       type_label = "METHOD";
150       break;
151     case ProtocolMessageType::RESULT:
152       type_label = "RESULT";
153       break;
154     case ProtocolMessageType::EVENT:
155       type_label = "EVENT";
156       break;
157   }
158 
159   std::stringstream stream;
160   WriteTimestamp(stream);
161   stream << ": " << type_label << ": " << to_log << "\n";
162   const std::string& str = stream.str();
163   if (!base::AppendToFile(log_file, str.c_str(), str.size())) {
164     LOG(ERROR) << "Failed to write file " << log_file.value();
165     log_error = true;
166   }
167 }
168 
169 }  // namespace
170 
171 class CefDevToolsFrontend::NetworkResourceLoader
172     : public network::SimpleURLLoaderStreamConsumer {
173  public:
NetworkResourceLoader(int stream_id,CefDevToolsFrontend * bindings,std::unique_ptr<network::SimpleURLLoader> loader,network::mojom::URLLoaderFactory * url_loader_factory,int request_id)174   NetworkResourceLoader(int stream_id,
175                         CefDevToolsFrontend* bindings,
176                         std::unique_ptr<network::SimpleURLLoader> loader,
177                         network::mojom::URLLoaderFactory* url_loader_factory,
178                         int request_id)
179       : stream_id_(stream_id),
180         bindings_(bindings),
181         loader_(std::move(loader)),
182         request_id_(request_id) {
183     loader_->SetOnResponseStartedCallback(base::BindOnce(
184         &NetworkResourceLoader::OnResponseStarted, base::Unretained(this)));
185     loader_->DownloadAsStream(url_loader_factory, this);
186   }
187 
188  private:
OnResponseStarted(const GURL & final_url,const network::mojom::URLResponseHead & response_head)189   void OnResponseStarted(const GURL& final_url,
190                          const network::mojom::URLResponseHead& response_head) {
191     response_headers_ = response_head.headers;
192   }
193 
OnDataReceived(base::StringPiece chunk,base::OnceClosure resume)194   void OnDataReceived(base::StringPiece chunk,
195                       base::OnceClosure resume) override {
196     base::Value chunkValue;
197 
198     bool encoded = !base::IsStringUTF8(chunk);
199     if (encoded) {
200       std::string encoded_string;
201       base::Base64Encode(chunk, &encoded_string);
202       chunkValue = base::Value(std::move(encoded_string));
203     } else {
204       chunkValue = base::Value(chunk);
205     }
206     base::Value id(stream_id_);
207     base::Value encodedValue(encoded);
208 
209     bindings_->CallClientFunction("DevToolsAPI.streamWrite", &id, &chunkValue,
210                                   &encodedValue);
211     std::move(resume).Run();
212   }
213 
OnComplete(bool success)214   void OnComplete(bool success) override {
215     auto response = BuildObjectForResponse(response_headers_.get(), success,
216                                            loader_->NetError());
217     bindings_->SendMessageAck(request_id_, response.get());
218 
219     bindings_->loaders_.erase(bindings_->loaders_.find(this));
220   }
221 
OnRetry(base::OnceClosure start_retry)222   void OnRetry(base::OnceClosure start_retry) override { NOTREACHED(); }
223 
224   const int stream_id_;
225   CefDevToolsFrontend* const bindings_;
226   std::unique_ptr<network::SimpleURLLoader> loader_;
227   int request_id_;
228   scoped_refptr<net::HttpResponseHeaders> response_headers_;
229 
230   DISALLOW_COPY_AND_ASSIGN(NetworkResourceLoader);
231 };
232 
233 // This constant should be in sync with
234 // the constant at devtools_ui_bindings.cc.
235 const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4;
236 
237 // static
Show(AlloyBrowserHostImpl * inspected_browser,const CefWindowInfo & windowInfo,CefRefPtr<CefClient> client,const CefBrowserSettings & settings,const CefPoint & inspect_element_at,base::OnceClosure frontend_destroyed_callback)238 CefDevToolsFrontend* CefDevToolsFrontend::Show(
239     AlloyBrowserHostImpl* inspected_browser,
240     const CefWindowInfo& windowInfo,
241     CefRefPtr<CefClient> client,
242     const CefBrowserSettings& settings,
243     const CefPoint& inspect_element_at,
244     base::OnceClosure frontend_destroyed_callback) {
245   CefBrowserSettings new_settings = settings;
246   if (!windowInfo.windowless_rendering_enabled &&
247       CefColorGetA(new_settings.background_color) != SK_AlphaOPAQUE) {
248     // Use white as the default background color for windowed DevTools instead
249     // of the CefSettings.background_color value.
250     new_settings.background_color = SK_ColorWHITE;
251   }
252 
253   CefBrowserCreateParams create_params;
254   if (!inspected_browser->is_views_hosted())
255     create_params.window_info.reset(new CefWindowInfo(windowInfo));
256   create_params.client = client;
257   create_params.settings = new_settings;
258   create_params.devtools_opener = inspected_browser;
259   create_params.request_context = inspected_browser->GetRequestContext();
260   create_params.extra_info = inspected_browser->browser_info()->extra_info();
261 
262   CefRefPtr<AlloyBrowserHostImpl> frontend_browser =
263       AlloyBrowserHostImpl::Create(create_params);
264 
265   content::WebContents* inspected_contents = inspected_browser->web_contents();
266 
267   // CefDevToolsFrontend will delete itself when the frontend WebContents is
268   // destroyed.
269   CefDevToolsFrontend* devtools_frontend = new CefDevToolsFrontend(
270       static_cast<AlloyBrowserHostImpl*>(frontend_browser.get()),
271       inspected_contents, inspect_element_at,
272       std::move(frontend_destroyed_callback));
273 
274   // Need to load the URL after creating the DevTools objects.
275   frontend_browser->GetMainFrame()->LoadURL(GetFrontendURL());
276 
277   return devtools_frontend;
278 }
279 
Activate()280 void CefDevToolsFrontend::Activate() {
281   frontend_browser_->ActivateContents(web_contents());
282 }
283 
Focus()284 void CefDevToolsFrontend::Focus() {
285   frontend_browser_->SetFocus(true);
286 }
287 
InspectElementAt(int x,int y)288 void CefDevToolsFrontend::InspectElementAt(int x, int y) {
289   if (inspect_element_at_.x != x || inspect_element_at_.y != y)
290     inspect_element_at_.Set(x, y);
291   if (agent_host_)
292     agent_host_->InspectElement(inspected_contents_->GetFocusedFrame(), x, y);
293 }
294 
Close()295 void CefDevToolsFrontend::Close() {
296   base::PostTask(FROM_HERE, {content::BrowserThread::UI},
297                  base::Bind(&AlloyBrowserHostImpl::CloseBrowser,
298                             frontend_browser_.get(), true));
299 }
300 
CefDevToolsFrontend(AlloyBrowserHostImpl * frontend_browser,content::WebContents * inspected_contents,const CefPoint & inspect_element_at,base::OnceClosure frontend_destroyed_callback)301 CefDevToolsFrontend::CefDevToolsFrontend(
302     AlloyBrowserHostImpl* frontend_browser,
303     content::WebContents* inspected_contents,
304     const CefPoint& inspect_element_at,
305     base::OnceClosure frontend_destroyed_callback)
306     : content::WebContentsObserver(frontend_browser->web_contents()),
307       frontend_browser_(frontend_browser),
308       inspected_contents_(inspected_contents),
309       inspect_element_at_(inspect_element_at),
310       frontend_destroyed_callback_(std::move(frontend_destroyed_callback)),
311       file_manager_(frontend_browser, GetPrefs()),
312       protocol_log_file_(
313           base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
314               switches::kDevToolsProtocolLogFile)),
315       weak_factory_(this) {
316   DCHECK(!frontend_destroyed_callback_.is_null());
317 }
318 
~CefDevToolsFrontend()319 CefDevToolsFrontend::~CefDevToolsFrontend() {}
320 
ReadyToCommitNavigation(content::NavigationHandle * navigation_handle)321 void CefDevToolsFrontend::ReadyToCommitNavigation(
322     content::NavigationHandle* navigation_handle) {
323   content::RenderFrameHost* frame = navigation_handle->GetRenderFrameHost();
324   if (navigation_handle->IsInMainFrame()) {
325     frontend_host_ = content::DevToolsFrontendHost::Create(
326         frame,
327         base::Bind(&CefDevToolsFrontend::HandleMessageFromDevToolsFrontend,
328                    base::Unretained(this)));
329     return;
330   }
331 
332   std::string origin = navigation_handle->GetURL().GetOrigin().spec();
333   auto it = extensions_api_.find(origin);
334   if (it == extensions_api_.end())
335     return;
336   std::string script = base::StringPrintf("%s(\"%s\")", it->second.c_str(),
337                                           base::GenerateGUID().c_str());
338   content::DevToolsFrontendHost::SetupExtensionsAPI(frame, script);
339 }
340 
DocumentAvailableInMainFrame(content::RenderFrameHost * render_frame_host)341 void CefDevToolsFrontend::DocumentAvailableInMainFrame(
342     content::RenderFrameHost* render_frame_host) {
343   // Don't call AttachClient multiple times for the same DevToolsAgentHost.
344   // Otherwise it will call AgentHostClosed which closes the DevTools window.
345   // This may happen in cases where the DevTools content fails to load.
346   scoped_refptr<content::DevToolsAgentHost> agent_host =
347       content::DevToolsAgentHost::GetOrCreateFor(inspected_contents_);
348   if (agent_host != agent_host_) {
349     if (agent_host_)
350       agent_host_->DetachClient(this);
351     agent_host_ = agent_host;
352     agent_host_->AttachClient(this);
353     if (!inspect_element_at_.IsEmpty()) {
354       agent_host_->InspectElement(inspected_contents_->GetFocusedFrame(),
355                                   inspect_element_at_.x, inspect_element_at_.y);
356     }
357   }
358 }
359 
WebContentsDestroyed()360 void CefDevToolsFrontend::WebContentsDestroyed() {
361   if (agent_host_) {
362     agent_host_->DetachClient(this);
363     agent_host_ = nullptr;
364   }
365   std::move(frontend_destroyed_callback_).Run();
366   delete this;
367 }
368 
HandleMessageFromDevToolsFrontend(const std::string & message)369 void CefDevToolsFrontend::HandleMessageFromDevToolsFrontend(
370     const std::string& message) {
371   std::string method;
372   base::ListValue* params = nullptr;
373   base::DictionaryValue* dict = nullptr;
374   base::Optional<base::Value> parsed_message = base::JSONReader::Read(message);
375   if (!parsed_message || !parsed_message->GetAsDictionary(&dict) ||
376       !dict->GetString("method", &method)) {
377     return;
378   }
379   int request_id = 0;
380   dict->GetInteger("id", &request_id);
381   dict->GetList("params", &params);
382 
383   if (method == "dispatchProtocolMessage" && params && params->GetSize() == 1) {
384     std::string protocol_message;
385     if (!agent_host_ || !params->GetString(0, &protocol_message))
386       return;
387     if (ProtocolLoggingEnabled()) {
388       LogProtocolMessage(ProtocolMessageType::METHOD, protocol_message);
389     }
390     agent_host_->DispatchProtocolMessage(
391         this, base::as_bytes(base::make_span(protocol_message)));
392   } else if (method == "loadCompleted") {
393     web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
394         u"DevToolsAPI.setUseSoftMenu(true);", base::NullCallback());
395   } else if (method == "loadNetworkResource" && params->GetSize() == 3) {
396     // TODO(pfeldman): handle some of the embedder messages in content.
397     std::string url;
398     std::string headers;
399     int stream_id;
400     if (!params->GetString(0, &url) || !params->GetString(1, &headers) ||
401         !params->GetInteger(2, &stream_id)) {
402       return;
403     }
404 
405     GURL gurl(url);
406     if (!gurl.is_valid()) {
407       base::DictionaryValue response;
408       response.SetInteger("statusCode", 404);
409       response.SetBoolean("urlValid", false);
410       SendMessageAck(request_id, &response);
411       return;
412     }
413 
414     net::NetworkTrafficAnnotationTag traffic_annotation =
415         net::DefineNetworkTrafficAnnotation(
416             "devtools_handle_front_end_messages", R"(
417             semantics {
418               sender: "Developer Tools"
419               description:
420                 "When user opens Developer Tools, the browser may fetch "
421                 "additional resources from the network to enrich the debugging "
422                 "experience (e.g. source map resources)."
423               trigger: "User opens Developer Tools to debug a web page."
424               data: "Any resources requested by Developer Tools."
425               destination: OTHER
426             }
427             policy {
428               cookies_allowed: YES
429               cookies_store: "user"
430               setting:
431                 "It's not possible to disable this feature from settings."
432               chrome_policy {
433                 DeveloperToolsAvailability {
434                   policy_options {mode: MANDATORY}
435                   DeveloperToolsAvailability: 2
436                 }
437               }
438             })");
439 
440     // Based on DevToolsUIBindings::LoadNetworkResource.
441     auto resource_request = std::make_unique<network::ResourceRequest>();
442     resource_request->url = gurl;
443     // TODO(caseq): this preserves behavior of URLFetcher-based
444     // implementation. We really need to pass proper first party origin from
445     // the front-end.
446     resource_request->site_for_cookies = net::SiteForCookies::FromUrl(gurl);
447     resource_request->headers.AddHeadersFromString(headers);
448 
449     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
450     if (gurl.SchemeIsFile()) {
451       mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote =
452           content::CreateFileURLLoaderFactory(
453               base::FilePath() /* profile_path */,
454               nullptr /* shared_cors_origin_access_list */);
455       url_loader_factory = network::SharedURLLoaderFactory::Create(
456           std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
457               std::move(pending_remote)));
458     } else if (content::HasWebUIScheme(gurl)) {
459       base::DictionaryValue response;
460       response.SetInteger("statusCode", 403);
461       SendMessageAck(request_id, &response);
462       return;
463     } else {
464       auto* partition = content::BrowserContext::GetStoragePartitionForUrl(
465           web_contents()->GetBrowserContext(), gurl);
466       url_loader_factory = partition->GetURLLoaderFactoryForBrowserProcess();
467     }
468 
469     auto simple_url_loader = network::SimpleURLLoader::Create(
470         std::move(resource_request), traffic_annotation);
471     auto resource_loader = std::make_unique<NetworkResourceLoader>(
472         stream_id, this, std::move(simple_url_loader), url_loader_factory.get(),
473         request_id);
474     loaders_.insert(std::move(resource_loader));
475     return;
476   } else if (method == "getPreferences") {
477     SendMessageAck(request_id,
478                    GetPrefs()->GetDictionary(prefs::kDevToolsPreferences));
479     return;
480   } else if (method == "setPreference") {
481     std::string name;
482     std::string value;
483     if (!params->GetString(0, &name) || !params->GetString(1, &value)) {
484       return;
485     }
486     DictionaryPrefUpdate update(GetPrefs(), prefs::kDevToolsPreferences);
487     update.Get()->SetKey(name, base::Value(value));
488   } else if (method == "removePreference") {
489     std::string name;
490     if (!params->GetString(0, &name))
491       return;
492     DictionaryPrefUpdate update(GetPrefs(), prefs::kDevToolsPreferences);
493     update.Get()->RemoveWithoutPathExpansion(name, nullptr);
494   } else if (method == "requestFileSystems") {
495     web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
496         u"DevToolsAPI.fileSystemsLoaded([]);", base::NullCallback());
497   } else if (method == "reattach") {
498     if (!agent_host_)
499       return;
500     agent_host_->DetachClient(this);
501     agent_host_->AttachClient(this);
502   } else if (method == "registerExtensionsAPI") {
503     std::string origin;
504     std::string script;
505     if (!params->GetString(0, &origin) || !params->GetString(1, &script))
506       return;
507     extensions_api_[origin + "/"] = script;
508   } else if (method == "save" && params->GetSize() == 3) {
509     std::string url;
510     std::string content;
511     bool save_as;
512     if (!params->GetString(0, &url) || !params->GetString(1, &content) ||
513         !params->GetBoolean(2, &save_as)) {
514       return;
515     }
516     file_manager_.SaveToFile(url, content, save_as);
517   } else if (method == "append" && params->GetSize() == 2) {
518     std::string url;
519     std::string content;
520     if (!params->GetString(0, &url) || !params->GetString(1, &content)) {
521       return;
522     }
523     file_manager_.AppendToFile(url, content);
524   } else {
525     return;
526   }
527 
528   if (request_id)
529     SendMessageAck(request_id, nullptr);
530 }
531 
532 void CefDevToolsFrontend::DispatchProtocolMessage(
533     content::DevToolsAgentHost* agent_host,
534     base::span<const uint8_t> message) {
535   if (!frontend_browser_->GetWebContents() ||
536       frontend_browser_->GetWebContents()->IsBeingDestroyed()) {
537     return;
538   }
539 
540   base::StringPiece str_message(reinterpret_cast<const char*>(message.data()),
541                                 message.size());
542   if (ProtocolLoggingEnabled()) {
543     // Quick check to avoid parsing the JSON object. Events begin with a
544     // "method" value whereas method results begin with an "id" value.
545     LogProtocolMessage(base::StartsWith(str_message, "{\"method\":")
546                            ? ProtocolMessageType::EVENT
547                            : ProtocolMessageType::RESULT,
548                        str_message);
549   }
550   if (str_message.length() < kMaxMessageChunkSize) {
551     std::string param;
552     base::EscapeJSONString(str_message, true, &param);
553     std::string code = "DevToolsAPI.dispatchMessage(" + param + ");";
554     std::u16string javascript = base::UTF8ToUTF16(code);
555     web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
556         javascript, base::NullCallback());
557     return;
558   }
559 
560   size_t total_size = str_message.length();
561   for (size_t pos = 0; pos < str_message.length();
562        pos += kMaxMessageChunkSize) {
563     std::string param;
564     base::EscapeJSONString(str_message.substr(pos, kMaxMessageChunkSize), true,
565                            &param);
566     std::string code = "DevToolsAPI.dispatchMessageChunk(" + param + "," +
567                        std::to_string(pos ? 0 : total_size) + ");";
568     std::u16string javascript = base::UTF8ToUTF16(code);
569     web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
570         javascript, base::NullCallback());
571   }
572 }
573 
574 void CefDevToolsFrontend::CallClientFunction(const std::string& function_name,
575                                              const base::Value* arg1,
576                                              const base::Value* arg2,
577                                              const base::Value* arg3) {
578   std::string javascript = function_name + "(";
579   if (arg1) {
580     std::string json;
581     base::JSONWriter::Write(*arg1, &json);
582     javascript.append(json);
583     if (arg2) {
584       base::JSONWriter::Write(*arg2, &json);
585       javascript.append(", ").append(json);
586       if (arg3) {
587         base::JSONWriter::Write(*arg3, &json);
588         javascript.append(", ").append(json);
589       }
590     }
591   }
592   javascript.append(");");
593   web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
594       base::UTF8ToUTF16(javascript), base::NullCallback());
595 }
596 
597 void CefDevToolsFrontend::SendMessageAck(int request_id,
598                                          const base::Value* arg) {
599   base::Value id_value(request_id);
600   CallClientFunction("DevToolsAPI.embedderMessageAck", &id_value, arg, nullptr);
601 }
602 
603 bool CefDevToolsFrontend::ProtocolLoggingEnabled() const {
604   return !protocol_log_file_.empty();
605 }
606 
607 void CefDevToolsFrontend::LogProtocolMessage(ProtocolMessageType type,
608                                              const base::StringPiece& message) {
609   DCHECK(ProtocolLoggingEnabled());
610 
611   std::string to_log = message.substr(0, kMaxLogLineLength).as_string();
612 
613   // Execute in an ordered context that allows blocking.
614   auto task_runner = CefTaskRunnerManager::Get()->GetBackgroundTaskRunner();
615   task_runner->PostTask(
616       FROM_HERE, base::BindOnce(::LogProtocolMessage, protocol_log_file_, type,
617                                 std::move(to_log)));
618 }
619 
620 void CefDevToolsFrontend::AgentHostClosed(
621     content::DevToolsAgentHost* agent_host) {
622   DCHECK(agent_host == agent_host_.get());
623   agent_host_ = nullptr;
624   Close();
625 }
626 
627 PrefService* CefDevToolsFrontend::GetPrefs() const {
628   return CefBrowserContext::FromBrowserContext(
629              frontend_browser_->web_contents()->GetBrowserContext())
630       ->AsProfile()
631       ->GetPrefs();
632 }
633