diff --git a/BUILD.gn b/BUILD.gn index 87cafa921..52518b9d1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -106,6 +106,7 @@ import("//media/media_options.gni") import("//mojo/public/tools/bindings/mojom.gni") import("//ppapi/buildflags/buildflags.gni") import("//printing/buildflags/buildflags.gni") +import("//testing/test.gni") import("//third_party/icu/config.gni") import("//third_party/widevine/cdm/widevine.gni") import("//tools/grit/repack.gni") @@ -262,12 +263,6 @@ if (is_clang) { assert(!clang_use_chrome_plugins) } -if (is_linux) { - # Use system fontconfig. This avoids a startup hang on Ubuntu 16.04.4 (see - # issue #2424). - assert(!use_bundled_fontconfig) -} - if (is_mac) { # Always generate dSYM files. The make_distrib script will fail if # enable_dsyms=true is not explicitly set when is_official_build=false. @@ -316,6 +311,7 @@ group("cef") { deps = [ ":cefsimple", ":ceftests", + ":libcef_static_unittests", ] if (!is_linux || use_x11) { @@ -373,6 +369,41 @@ if (is_win) { } } +# libcef_static source files that have unit tests. +source_set("libcef_static_unittested") { + sources = [ + "libcef/browser/devtools/devtools_util.cc", + "libcef/browser/devtools/devtools_util.h", + ] + + deps = [ + "//base", + ] + + configs += [ + "libcef/features:config", + "//build/config:precompiled_headers", + ] +} + +# Executable target for libcef_static unit tests. +test("libcef_static_unittests") { + sources = [ + "libcef/browser/devtools/devtools_util_unittest.cc", + ] + + deps = [ + ":libcef_static_unittested", + "//base/test:run_all_unittests", + "//testing/gtest", + ] + + configs += [ + "libcef/features:config", + "//build/config:precompiled_headers", + ] +} + static_library("libcef_static") { sources = includes_common + gypi_paths.autogen_cpp_includes + [ @@ -412,10 +443,14 @@ static_library("libcef_static") { "libcef/browser/context.h", "libcef/browser/context_menu_params_impl.cc", "libcef/browser/context_menu_params_impl.h", + "libcef/browser/devtools/devtools_controller.cc", + "libcef/browser/devtools/devtools_controller.h", "libcef/browser/devtools/devtools_file_manager.cc", "libcef/browser/devtools/devtools_file_manager.h", "libcef/browser/devtools/devtools_frontend.cc", "libcef/browser/devtools/devtools_frontend.h", + "libcef/browser/devtools/devtools_manager.cc", + "libcef/browser/devtools/devtools_manager.h", "libcef/browser/devtools/devtools_manager_delegate.cc", "libcef/browser/devtools/devtools_manager_delegate.h", "libcef/browser/download_item_impl.cc", @@ -739,6 +774,8 @@ static_library("libcef_static") { ":cef_make_headers", ":cef_service_manifests", + ":libcef_static_unittested", + # Generate API bindings for extensions. # TODO(cef): Enable if/when CEF exposes its own Mojo APIs. See # libcef/common/extensions/api/README.txt for details. diff --git a/cef_paths.gypi b/cef_paths.gypi index c0322f135..c15b2bb9e 100644 --- a/cef_paths.gypi +++ b/cef_paths.gypi @@ -8,7 +8,7 @@ # by hand. See the translator.README.txt file in the tools directory for # more information. # -# $hash=21f0ab1e9902e4a47bf2893a4a383d33bd8161e2$ +# $hash=30eed8c81da55c640eb6a491283d1c00fb59d635$ # { @@ -26,6 +26,7 @@ 'include/cef_context_menu_handler.h', 'include/cef_cookie.h', 'include/cef_crash_util.h', + 'include/cef_devtools_message_observer.h', 'include/cef_dialog_handler.h', 'include/cef_display_handler.h', 'include/cef_dom.h', @@ -122,6 +123,7 @@ 'include/capi/cef_context_menu_handler_capi.h', 'include/capi/cef_cookie_capi.h', 'include/capi/cef_crash_util_capi.h', + 'include/capi/cef_devtools_message_observer_capi.h', 'include/capi/cef_dialog_handler_capi.h', 'include/capi/cef_display_handler_capi.h', 'include/capi/cef_dom_capi.h', @@ -260,6 +262,8 @@ 'libcef_dll/ctocpp/domvisitor_ctocpp.h', 'libcef_dll/ctocpp/delete_cookies_callback_ctocpp.cc', 'libcef_dll/ctocpp/delete_cookies_callback_ctocpp.h', + 'libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.cc', + 'libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h', 'libcef_dll/ctocpp/dialog_handler_ctocpp.cc', 'libcef_dll/ctocpp/dialog_handler_ctocpp.h', 'libcef_dll/cpptoc/dictionary_value_cpptoc.cc', @@ -566,6 +570,8 @@ 'libcef_dll/cpptoc/domvisitor_cpptoc.h', 'libcef_dll/cpptoc/delete_cookies_callback_cpptoc.cc', 'libcef_dll/cpptoc/delete_cookies_callback_cpptoc.h', + 'libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.cc', + 'libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h', 'libcef_dll/cpptoc/dialog_handler_cpptoc.cc', 'libcef_dll/cpptoc/dialog_handler_cpptoc.h', 'libcef_dll/ctocpp/dictionary_value_ctocpp.cc', diff --git a/cef_paths2.gypi b/cef_paths2.gypi index aa3717f59..0d5d3c385 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -470,6 +470,7 @@ 'tests/ceftests/browser_info_map_unittest.cc', 'tests/ceftests/command_line_unittest.cc', 'tests/ceftests/cookie_unittest.cc', + 'tests/ceftests/devtools_message_unittest.cc', 'tests/ceftests/dialog_unittest.cc', 'tests/ceftests/display_unittest.cc', 'tests/ceftests/dom_unittest.cc', diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index 81182c422..4336c8d38 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=ba4033eaf40a8ee24408b89b78496bf1381e7e6b$ +// $hash=6cb00a0fa3631a46903abb3a783f315895511db2$ // #ifndef CEF_INCLUDE_CAPI_CEF_BROWSER_CAPI_H_ @@ -41,10 +41,12 @@ #pragma once #include "include/capi/cef_base_capi.h" +#include "include/capi/cef_devtools_message_observer_capi.h" #include "include/capi/cef_drag_data_capi.h" #include "include/capi/cef_frame_capi.h" #include "include/capi/cef_image_capi.h" #include "include/capi/cef_navigation_entry_capi.h" +#include "include/capi/cef_registration_capi.h" #include "include/capi/cef_request_context_capi.h" #ifdef __cplusplus @@ -484,6 +486,71 @@ typedef struct _cef_browser_host_t { /// int(CEF_CALLBACK* has_dev_tools)(struct _cef_browser_host_t* self); + /// + // Send a function call message over the DevTools protocol. |message| must be + // a UTF8-encoded JSON dictionary that contains "id" (int), "function" + // (string) and "params" (dictionary, optional) values. See the DevTools + // protocol documentation at https://chromedevtools.github.io/devtools- + // protocol/ for details of supported functions and the expected "params" + // dictionary contents. |message| will be copied if necessary. This function + // will return true (1) if called on the UI thread and the message was + // successfully submitted for validation, otherwise false (0). Validation will + // be applied asynchronously and any messages that fail due to formatting + // errors or missing parameters may be discarded without notification. Prefer + // ExecuteDevToolsMethod if a more structured approach to message formatting + // is desired. + // + // Every valid function call will result in an asynchronous function result or + // error message that references the sent message "id". Event messages are + // received while notifications are enabled (for example, between function + // calls for "Page.enable" and "Page.disable"). All received messages will be + // delivered to the observer(s) registered with AddDevToolsMessageObserver. + // See cef_dev_tools_message_observer_t::OnDevToolsMessage documentation for + // details of received message contents. + // + // Usage of the SendDevToolsMessage, ExecuteDevToolsMethod and + // AddDevToolsMessageObserver functions does not require an active DevTools + // front-end or remote-debugging session. Other active DevTools sessions will + // continue to function independently. However, any modification of global + // browser state by one session may not be reflected in the UI of other + // sessions. + // + // Communication with the DevTools front-end (when displayed) can be logged + // for development purposes by passing the `--devtools-protocol-log- + // file=` command-line flag. + /// + int(CEF_CALLBACK* send_dev_tools_message)(struct _cef_browser_host_t* self, + const void* message, + size_t message_size); + + /// + // Execute a function call over the DevTools protocol. This is a more + // structured version of SendDevToolsMessage. |message_id| is an incremental + // number that uniquely identifies the message (pass 0 to have the next number + // assigned automatically based on previous values). |function| is the + // function name. |params| are the function parameters, which may be NULL. See + // the DevTools protocol documentation (linked above) for details of supported + // functions and the expected |params| dictionary contents. This function will + // return the assigned message ID if called on the UI thread and the message + // was successfully submitted for validation, otherwise 0. See the + // SendDevToolsMessage documentation for additional usage information. + /// + int(CEF_CALLBACK* execute_dev_tools_method)( + struct _cef_browser_host_t* self, + int message_id, + const cef_string_t* method, + struct _cef_dictionary_value_t* params); + + /// + // Add an observer for DevTools protocol messages (function results and + // events). The observer will remain registered until the returned + // Registration object is destroyed. See the SendDevToolsMessage documentation + // for additional usage information. + /// + struct _cef_registration_t*(CEF_CALLBACK* add_dev_tools_message_observer)( + struct _cef_browser_host_t* self, + struct _cef_dev_tools_message_observer_t* observer); + /// // Retrieve a snapshot of current navigation entries as values sent to the // specified visitor. If |current_only| is true (1) only the current diff --git a/include/capi/cef_devtools_message_observer_capi.h b/include/capi/cef_devtools_message_observer_capi.h new file mode 100644 index 000000000..bb0a21c57 --- /dev/null +++ b/include/capi/cef_devtools_message_observer_capi.h @@ -0,0 +1,147 @@ +// Copyright (c) 2020 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// +// $hash=86906c2e971fea7e479738f59bbf85d71ce31953$ +// + +#ifndef CEF_INCLUDE_CAPI_CEF_DEVTOOLS_MESSAGE_OBSERVER_CAPI_H_ +#define CEF_INCLUDE_CAPI_CEF_DEVTOOLS_MESSAGE_OBSERVER_CAPI_H_ +#pragma once + +#include "include/capi/cef_base_capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct _cef_browser_t; + +/// +// Callback structure for cef_browser_host_t::AddDevToolsMessageObserver. The +// functions of this structure will be called on the browser process UI thread. +/// +typedef struct _cef_dev_tools_message_observer_t { + /// + // Base structure. + /// + cef_base_ref_counted_t base; + + /// + // Method that will be called on receipt of a DevTools protocol message. + // |browser| is the originating browser instance. |message| is a UTF8-encoded + // JSON dictionary representing either a function result or an event. + // |message| is only valid for the scope of this callback and should be copied + // if necessary. Return true (1) if the message was handled or false (0) if + // the message should be further processed and passed to the + // OnDevToolsMethodResult or OnDevToolsEvent functions as appropriate. + // + // Method result dictionaries include an "id" (int) value that identifies the + // orginating function call sent from cef_browser_host_t::SendDevToolsMessage, + // and optionally either a "result" (dictionary) or "error" (dictionary) + // value. The "error" dictionary will contain "code" (int) and "message" + // (string) values. Event dictionaries include a "function" (string) value and + // optionally a "params" (dictionary) value. See the DevTools protocol + // documentation at https://chromedevtools.github.io/devtools-protocol/ for + // details of supported function calls and the expected "result" or "params" + // dictionary contents. JSON dictionaries can be parsed using the CefParseJSON + // function if desired, however be aware of performance considerations when + // parsing large messages (some of which may exceed 1MB in size). + /// + int(CEF_CALLBACK* on_dev_tools_message)( + struct _cef_dev_tools_message_observer_t* self, + struct _cef_browser_t* browser, + const void* message, + size_t message_size); + + /// + // Method that will be called after attempted execution of a DevTools protocol + // function. |browser| is the originating browser instance. |message_id| is + // the "id" value that identifies the originating function call message. If + // the function succeeded |success| will be true (1) and |result| will be the + // UTF8-encoded JSON "result" dictionary value (which may be NULL). If the + // function failed |success| will be false (0) and |result| will be the + // UTF8-encoded JSON "error" dictionary value. |result| is only valid for the + // scope of this callback and should be copied if necessary. See the + // OnDevToolsMessage documentation for additional details on |result| + // contents. + /// + void(CEF_CALLBACK* on_dev_tools_method_result)( + struct _cef_dev_tools_message_observer_t* self, + struct _cef_browser_t* browser, + int message_id, + int success, + const void* result, + size_t result_size); + + /// + // Method that will be called on receipt of a DevTools protocol event. + // |browser| is the originating browser instance. |function| is the "function" + // value. |params| is the UTF8-encoded JSON "params" dictionary value (which + // may be NULL). |params| is only valid for the scope of this callback and + // should be copied if necessary. See the OnDevToolsMessage documentation for + // additional details on |params| contents. + /// + void(CEF_CALLBACK* on_dev_tools_event)( + struct _cef_dev_tools_message_observer_t* self, + struct _cef_browser_t* browser, + const cef_string_t* method, + const void* params, + size_t params_size); + + /// + // Method that will be called when the DevTools agent has attached. |browser| + // is the originating browser instance. This will generally occur in response + // to the first message sent while the agent is detached. + /// + void(CEF_CALLBACK* on_dev_tools_agent_attached)( + struct _cef_dev_tools_message_observer_t* self, + struct _cef_browser_t* browser); + + /// + // Method that will be called when the DevTools agent has detached. |browser| + // is the originating browser instance. Any function results that were pending + // before the agent became detached will not be delivered, and any active + // event subscriptions will be canceled. + /// + void(CEF_CALLBACK* on_dev_tools_agent_detached)( + struct _cef_dev_tools_message_observer_t* self, + struct _cef_browser_t* browser); +} cef_dev_tools_message_observer_t; + +#ifdef __cplusplus +} +#endif + +#endif // CEF_INCLUDE_CAPI_CEF_DEVTOOLS_MESSAGE_OBSERVER_CAPI_H_ diff --git a/include/capi/cef_parser_capi.h b/include/capi/cef_parser_capi.h index 5aa02bcd8..51e51bbe1 100644 --- a/include/capi/cef_parser_capi.h +++ b/include/capi/cef_parser_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=3bc4225f43428d8a3a24dcac1830dafac18b0caf$ +// $hash=14cf03e02d8ca3416e65f756470afd8185c7bc78$ // #ifndef CEF_INCLUDE_CAPI_CEF_PARSER_CAPI_H_ @@ -140,6 +140,16 @@ CEF_EXPORT struct _cef_value_t* cef_parse_json( const cef_string_t* json_string, cef_json_parser_options_t options); +/// +// Parses the specified UTF8-encoded |json| buffer of size |json_size| and +// returns a dictionary or list representation. If JSON parsing fails this +// function returns NULL. +/// +CEF_EXPORT struct _cef_value_t* cef_parse_json_buffer( + const void* json, + size_t json_size, + cef_json_parser_options_t options); + /// // Parses the specified |json_string| and returns a dictionary or list // representation. If JSON parsing fails this function returns NULL and diff --git a/include/capi/cef_request_handler_capi.h b/include/capi/cef_request_handler_capi.h index e92b0773e..cd6be4ffc 100644 --- a/include/capi/cef_request_handler_capi.h +++ b/include/capi/cef_request_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=a28219cc8c1cb53faacaf236374c3cf2c0c45bef$ +// $hash=0167eb1abe614bd6391d273a8085fa3e53e7c217$ // #ifndef CEF_INCLUDE_CAPI_CEF_REQUEST_HANDLER_CAPI_H_ @@ -253,6 +253,14 @@ typedef struct _cef_request_handler_t { struct _cef_request_handler_t* self, struct _cef_browser_t* browser, cef_termination_status_t status); + + /// + // Called on the browser process UI thread when the window.document object of + // the main frame has been created. + /// + void(CEF_CALLBACK* on_document_available_in_main_frame)( + struct _cef_request_handler_t* self, + struct _cef_browser_t* browser); } cef_request_handler_t; #ifdef __cplusplus diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index 6624e8640..91417e4a1 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -42,13 +42,13 @@ // way that may cause binary incompatibility with other builds. The universal // hash value will change if any platform is affected whereas the platform hash // values will change only if that particular platform is affected. -#define CEF_API_HASH_UNIVERSAL "a48c939496261acf27f45293484f2c9e4065ca6f" +#define CEF_API_HASH_UNIVERSAL "eb44c98407c9286a64b8d872f49c772b3fa3eb63" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "7ab8365e6f76e376342247954be2f90f69014776" +#define CEF_API_HASH_PLATFORM "365b52117c7adf0a8bb6d96638954eb313168ff7" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "01cc20ae8dbd5ce63ff50cac215855c4c58059f4" +#define CEF_API_HASH_PLATFORM "1d1bca66ca61d742f507c08d4efda9884c94baff" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "112ef821732253dfda925d4a1a6d490396bac6d2" +#define CEF_API_HASH_PLATFORM "a87fd1ea68d4e79c838538fa78d79f8dd05acab2" #endif #ifdef __cplusplus diff --git a/include/cef_browser.h b/include/cef_browser.h index 79c9259bd..172d0a147 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -40,10 +40,12 @@ #include #include "include/cef_base.h" +#include "include/cef_devtools_message_observer.h" #include "include/cef_drag_data.h" #include "include/cef_frame.h" #include "include/cef_image.h" #include "include/cef_navigation_entry.h" +#include "include/cef_registration.h" #include "include/cef_request_context.h" class CefBrowserHost; @@ -517,6 +519,69 @@ class CefBrowserHost : public virtual CefBaseRefCounted { /*--cef()--*/ virtual bool HasDevTools() = 0; + /// + // Send a method call message over the DevTools protocol. |message| must be a + // UTF8-encoded JSON dictionary that contains "id" (int), "method" (string) + // and "params" (dictionary, optional) values. See the DevTools protocol + // documentation at https://chromedevtools.github.io/devtools-protocol/ for + // details of supported methods and the expected "params" dictionary contents. + // |message| will be copied if necessary. This method will return true if + // called on the UI thread and the message was successfully submitted for + // validation, otherwise false. Validation will be applied asynchronously and + // any messages that fail due to formatting errors or missing parameters may + // be discarded without notification. Prefer ExecuteDevToolsMethod if a more + // structured approach to message formatting is desired. + // + // Every valid method call will result in an asynchronous method result or + // error message that references the sent message "id". Event messages are + // received while notifications are enabled (for example, between method calls + // for "Page.enable" and "Page.disable"). All received messages will be + // delivered to the observer(s) registered with AddDevToolsMessageObserver. + // See CefDevToolsMessageObserver::OnDevToolsMessage documentation for details + // of received message contents. + // + // Usage of the SendDevToolsMessage, ExecuteDevToolsMethod and + // AddDevToolsMessageObserver methods does not require an active DevTools + // front-end or remote-debugging session. Other active DevTools sessions will + // continue to function independently. However, any modification of global + // browser state by one session may not be reflected in the UI of other + // sessions. + // + // Communication with the DevTools front-end (when displayed) can be logged + // for development purposes by passing the + // `--devtools-protocol-log-file=` command-line flag. + /// + /*--cef()--*/ + virtual bool SendDevToolsMessage(const void* message, + size_t message_size) = 0; + + /// + // Execute a method call over the DevTools protocol. This is a more structured + // version of SendDevToolsMessage. |message_id| is an incremental number that + // uniquely identifies the message (pass 0 to have the next number assigned + // automatically based on previous values). |method| is the method name. + // |params| are the method parameters, which may be empty. See the DevTools + // protocol documentation (linked above) for details of supported methods and + // the expected |params| dictionary contents. This method will return the + // assigned message ID if called on the UI thread and the message was + // successfully submitted for validation, otherwise 0. See the + // SendDevToolsMessage documentation for additional usage information. + /// + /*--cef(optional_param=params)--*/ + virtual int ExecuteDevToolsMethod(int message_id, + const CefString& method, + CefRefPtr params) = 0; + + /// + // Add an observer for DevTools protocol messages (method results and events). + // The observer will remain registered until the returned Registration object + // is destroyed. See the SendDevToolsMessage documentation for additional + // usage information. + /// + /*--cef()--*/ + virtual CefRefPtr AddDevToolsMessageObserver( + CefRefPtr observer) = 0; + /// // Retrieve a snapshot of current navigation entries as values sent to the // specified visitor. If |current_only| is true only the current navigation diff --git a/include/cef_devtools_message_observer.h b/include/cef_devtools_message_observer.h new file mode 100644 index 000000000..c65aee9c2 --- /dev/null +++ b/include/cef_devtools_message_observer.h @@ -0,0 +1,130 @@ +// Copyright (c) 2020 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file must follow a specific format in order to +// support the CEF translator tool. See the translator.README.txt file in the +// tools directory for more information. +// + +#ifndef CEF_INCLUDE_CEF_DEVTOOLS_MESSAGE_OBSERVER_H_ +#define CEF_INCLUDE_CEF_DEVTOOLS_MESSAGE_OBSERVER_H_ +#pragma once + +#include "include/cef_base.h" + +class CefBrowser; + +/// +// Callback interface for CefBrowserHost::AddDevToolsMessageObserver. The +// methods of this class will be called on the browser process UI thread. +/// +/*--cef(source=client)--*/ +class CefDevToolsMessageObserver : public virtual CefBaseRefCounted { + public: + /// + // Method that will be called on receipt of a DevTools protocol message. + // |browser| is the originating browser instance. |message| is a UTF8-encoded + // JSON dictionary representing either a method result or an event. |message| + // is only valid for the scope of this callback and should be copied if + // necessary. Return true if the message was handled or false if the message + // should be further processed and passed to the OnDevToolsMethodResult or + // OnDevToolsEvent methods as appropriate. + // + // Method result dictionaries include an "id" (int) value that identifies the + // orginating method call sent from CefBrowserHost::SendDevToolsMessage, and + // optionally either a "result" (dictionary) or "error" (dictionary) value. + // The "error" dictionary will contain "code" (int) and "message" (string) + // values. Event dictionaries include a "method" (string) value and optionally + // a "params" (dictionary) value. See the DevTools protocol documentation at + // https://chromedevtools.github.io/devtools-protocol/ for details of + // supported method calls and the expected "result" or "params" dictionary + // contents. JSON dictionaries can be parsed using the CefParseJSON function + // if desired, however be aware of performance considerations when parsing + // large messages (some of which may exceed 1MB in size). + /// + /*--cef()--*/ + virtual bool OnDevToolsMessage(CefRefPtr browser, + const void* message, + size_t message_size) { + return false; + } + + /// + // Method that will be called after attempted execution of a DevTools protocol + // method. |browser| is the originating browser instance. |message_id| is the + // "id" value that identifies the originating method call message. If the + // method succeeded |success| will be true and |result| will be the + // UTF8-encoded JSON "result" dictionary value (which may be empty). If the + // method failed |success| will be false and |result| will be the UTF8-encoded + // JSON "error" dictionary value. |result| is only valid for the scope of this + // callback and should be copied if necessary. See the OnDevToolsMessage + // documentation for additional details on |result| contents. + /// + /*--cef(optional_param=result)--*/ + virtual void OnDevToolsMethodResult(CefRefPtr browser, + int message_id, + bool success, + const void* result, + size_t result_size) {} + + /// + // Method that will be called on receipt of a DevTools protocol event. + // |browser| is the originating browser instance. |method| is the "method" + // value. |params| is the UTF8-encoded JSON "params" dictionary value (which + // may be empty). |params| is only valid for the scope of this callback and + // should be copied if necessary. See the OnDevToolsMessage documentation for + // additional details on |params| contents. + /// + /*--cef(optional_param=params)--*/ + virtual void OnDevToolsEvent(CefRefPtr browser, + const CefString& method, + const void* params, + size_t params_size) {} + + /// + // Method that will be called when the DevTools agent has attached. |browser| + // is the originating browser instance. This will generally occur in response + // to the first message sent while the agent is detached. + /// + /*--cef()--*/ + virtual void OnDevToolsAgentAttached(CefRefPtr browser) {} + + /// + // Method that will be called when the DevTools agent has detached. |browser| + // is the originating browser instance. Any method results that were pending + // before the agent became detached will not be delivered, and any active + // event subscriptions will be canceled. + /// + /*--cef()--*/ + virtual void OnDevToolsAgentDetached(CefRefPtr browser) {} +}; + +#endif // CEF_INCLUDE_CEF_DEVTOOLS_MESSAGE_OBSERVER_H_ diff --git a/include/cef_parser.h b/include/cef_parser.h index ebe75103e..bae958bf7 100644 --- a/include/cef_parser.h +++ b/include/cef_parser.h @@ -133,6 +133,16 @@ CefString CefURIDecode(const CefString& text, CefRefPtr CefParseJSON(const CefString& json_string, cef_json_parser_options_t options); +/// +// Parses the specified UTF8-encoded |json| buffer of size |json_size| and +// returns a dictionary or list representation. If JSON parsing fails this +// method returns NULL. +/// +/*--cef(capi_name=cef_parse_json_buffer)--*/ +CefRefPtr CefParseJSON(const void* json, + size_t json_size, + cef_json_parser_options_t options); + /// // Parses the specified |json_string| and returns a dictionary or list // representation. If JSON parsing fails this method returns NULL and populates diff --git a/include/cef_request_handler.h b/include/cef_request_handler.h index 6a5fc281d..c255057f9 100644 --- a/include/cef_request_handler.h +++ b/include/cef_request_handler.h @@ -252,6 +252,13 @@ class CefRequestHandler : public virtual CefBaseRefCounted { /*--cef()--*/ virtual void OnRenderProcessTerminated(CefRefPtr browser, TerminationStatus status) {} + + /// + // Called on the browser process UI thread when the window.document object of + // the main frame has been created. + /// + /*--cef()--*/ + virtual void OnDocumentAvailableInMainFrame(CefRefPtr browser) {} }; #endif // CEF_INCLUDE_CEF_REQUEST_HANDLER_H_ diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 3416c50c5..19d451c0f 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -16,8 +16,7 @@ #include "libcef/browser/browser_util.h" #include "libcef/browser/content_browser_client.h" #include "libcef/browser/context.h" -#include "libcef/browser/devtools/devtools_frontend.h" -#include "libcef/browser/devtools/devtools_manager_delegate.h" +#include "libcef/browser/devtools/devtools_manager.h" #include "libcef/browser/extensions/browser_extensions_util.h" #include "libcef/browser/extensions/extension_background_host.h" #include "libcef/browser/extensions/extension_system.h" @@ -594,26 +593,6 @@ CefRefPtr CefBrowserHostImpl::GetBrowserForFrameRoute( // CefBrowserHostImpl methods. // ----------------------------------------------------------------------------- -// WebContentsObserver that will be notified when the frontend WebContents is -// destroyed so that the inspected browser can clear its DevTools references. -class CefBrowserHostImpl::DevToolsWebContentsObserver - : public content::WebContentsObserver { - public: - DevToolsWebContentsObserver(CefBrowserHostImpl* browser, - content::WebContents* frontend_web_contents) - : WebContentsObserver(frontend_web_contents), browser_(browser) {} - - // WebContentsObserver methods: - void WebContentsDestroyed() override { - browser_->OnDevToolsWebContentsDestroyed(); - } - - private: - CefBrowserHostImpl* browser_; - - DISALLOW_COPY_AND_ASSIGN(DevToolsWebContentsObserver); -}; - CefBrowserHostImpl::~CefBrowserHostImpl() {} CefRefPtr CefBrowserHostImpl::GetBrowser() { @@ -906,39 +885,29 @@ void CefBrowserHostImpl::ShowDevTools(const CefWindowInfo& windowInfo, CefRefPtr client, const CefBrowserSettings& settings, const CefPoint& inspect_element_at) { - if (CEF_CURRENTLY_ON_UIT()) { - if (!web_contents()) - return; - - if (devtools_frontend_) { - if (!inspect_element_at.IsEmpty()) { - devtools_frontend_->InspectElementAt(inspect_element_at.x, - inspect_element_at.y); - } - devtools_frontend_->Focus(); - return; - } - - devtools_frontend_ = CefDevToolsFrontend::Show( - this, windowInfo, client, settings, inspect_element_at); - devtools_observer_.reset(new DevToolsWebContentsObserver( - this, devtools_frontend_->frontend_browser()->web_contents())); - } else { + if (!CEF_CURRENTLY_ON_UIT()) { ShowDevToolsHelper* helper = new ShowDevToolsHelper( this, windowInfo, client, settings, inspect_element_at); CEF_POST_TASK(CEF_UIT, base::BindOnce(ShowDevToolsWithHelper, helper)); + return; } + + if (!EnsureDevToolsManager()) + return; + devtools_manager_->ShowDevTools(windowInfo, client, settings, + inspect_element_at); } void CefBrowserHostImpl::CloseDevTools() { - if (CEF_CURRENTLY_ON_UIT()) { - if (!devtools_frontend_) - return; - devtools_frontend_->Close(); - } else { + if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefBrowserHostImpl::CloseDevTools, this)); + return; } + + if (!devtools_manager_) + return; + devtools_manager_->CloseDevTools(); } bool CefBrowserHostImpl::HasDevTools() { @@ -947,7 +916,84 @@ bool CefBrowserHostImpl::HasDevTools() { return false; } - return (devtools_frontend_ != nullptr); + if (!devtools_manager_) + return false; + return devtools_manager_->HasDevTools(); +} + +bool CefBrowserHostImpl::SendDevToolsMessage(const void* message, + size_t message_size) { + if (!message || message_size == 0) + return false; + + if (!CEF_CURRENTLY_ON_UIT()) { + std::string message_str(static_cast(message), message_size); + CEF_POST_TASK( + CEF_UIT, + base::BindOnce( + [](CefRefPtr self, std::string message_str) { + self->SendDevToolsMessage(message_str.data(), message_str.size()); + }, + CefRefPtr(this), std::move(message_str))); + return false; + } + + if (!EnsureDevToolsManager()) + return false; + return devtools_manager_->SendDevToolsMessage(message, message_size); +} + +int CefBrowserHostImpl::ExecuteDevToolsMethod( + int message_id, + const CefString& method, + CefRefPtr params) { + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK( + CEF_UIT, base::BindOnce(base::IgnoreResult( + &CefBrowserHostImpl::ExecuteDevToolsMethod), + this, message_id, method, params)); + return 0; + } + + if (!EnsureDevToolsManager()) + return 0; + return devtools_manager_->ExecuteDevToolsMethod(message_id, method, params); +} + +CefRefPtr CefBrowserHostImpl::AddDevToolsMessageObserver( + CefRefPtr observer) { + if (!observer) + return nullptr; + auto registration = CefDevToolsManager::CreateRegistration(observer); + InitializeDevToolsRegistrationOnUIThread(registration); + return registration.get(); +} + +bool CefBrowserHostImpl::EnsureDevToolsManager() { + CEF_REQUIRE_UIT(); + if (!web_contents()) + return false; + + if (!devtools_manager_) { + devtools_manager_.reset(new CefDevToolsManager(this)); + } + return true; +} + +void CefBrowserHostImpl::InitializeDevToolsRegistrationOnUIThread( + CefRefPtr registration) { + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK( + CEF_UIT, + base::BindOnce( + &CefBrowserHostImpl::InitializeDevToolsRegistrationOnUIThread, this, + registration)); + return; + } + + if (!EnsureDevToolsManager()) + return; + devtools_manager_->InitializeRegistrationOnUIThread(registration); } void CefBrowserHostImpl::GetNavigationEntries( @@ -1611,6 +1657,8 @@ void CefBrowserHostImpl::DestroyBrowser() { recently_audible_timer_.Stop(); audio_capturer_.reset(nullptr); + devtools_manager_.reset(nullptr); + // Delete the platform delegate. platform_delegate_.reset(nullptr); @@ -2704,16 +2752,24 @@ void CefBrowserHostImpl::DidStopLoading() { } void CefBrowserHostImpl::DocumentAvailableInMainFrame() { - base::AutoLock lock_scope(state_lock_); - has_document_ = true; + { + base::AutoLock lock_scope(state_lock_); + has_document_ = true; + } + + if (client_) { + CefRefPtr handler = client_->GetRequestHandler(); + if (handler) + handler->OnDocumentAvailableInMainFrame(this); + } } void CefBrowserHostImpl::DidFailLoad( content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code) { - // The navigation failed after commit. OnLoadStart was called so we also call - // OnLoadEnd. + // The navigation failed after commit. OnLoadStart was called so we also + // call OnLoadEnd. auto frame = browser_info_->GetFrameForHost(render_frame_host); frame->RefreshAttributes(); OnLoadError(frame, validated_url, error_code); @@ -2949,18 +3005,6 @@ CefBrowserHostImpl::CefBrowserHostImpl( platform_delegate_(std::move(platform_delegate)), is_windowless_(platform_delegate_->IsWindowless()), is_views_hosted_(platform_delegate_->IsViewsHosted()), - host_window_handle_(kNullWindowHandle), - is_loading_(false), - can_go_back_(false), - can_go_forward_(false), - has_document_(false), - is_fullscreen_(false), - destruction_state_(DESTRUCTION_STATE_NONE), - window_destroyed_(false), - is_in_onsetfocus_(false), - focus_on_editable_field_(false), - mouse_cursor_change_disabled_(false), - devtools_frontend_(nullptr), extension_(extension) { if (opener.get() && !platform_delegate_->IsViewsHosted()) { // GetOpenerWindowHandle() only returns a value for non-views-hosted @@ -3171,11 +3215,6 @@ void CefBrowserHostImpl::OnTitleChange(const base::string16& title) { } } -void CefBrowserHostImpl::OnDevToolsWebContentsDestroyed() { - devtools_observer_.reset(); - devtools_frontend_ = nullptr; -} - void CefBrowserHostImpl::EnsureFileDialogManager() { CEF_REQUIRE_UIT(); if (!file_dialog_manager_.get() && platform_delegate_) { diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 94c0c37c3..4523aae0a 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -51,7 +51,7 @@ class Widget; class CefAudioCapturer; class CefBrowserInfo; class CefBrowserPlatformDelegate; -class CefDevToolsFrontend; +class CefDevToolsManager; class SiteInstance; // Implementation of CefBrowser. @@ -192,6 +192,12 @@ class CefBrowserHostImpl : public CefBrowserHost, const CefPoint& inspect_element_at) override; void CloseDevTools() override; bool HasDevTools() override; + bool SendDevToolsMessage(const void* message, size_t message_size) override; + int ExecuteDevToolsMethod(int message_id, + const CefString& method, + CefRefPtr params) override; + CefRefPtr AddDevToolsMessageObserver( + CefRefPtr observer) override; void GetNavigationEntries(CefRefPtr visitor, bool current_only) override; void SetMouseCursorChangeDisabled(bool disabled) override; @@ -523,8 +529,6 @@ class CefBrowserHostImpl : public CefBrowserHost, std::unique_ptr CreateNavigationLock(); private: - class DevToolsWebContentsObserver; - static CefRefPtr CreateInternal( const CefBrowserSettings& settings, CefRefPtr client, @@ -581,8 +585,6 @@ class CefBrowserHostImpl : public CefBrowserHost, void OnFullscreenModeChange(bool fullscreen); void OnTitleChange(const base::string16& title); - void OnDevToolsWebContentsDestroyed(); - // Create the CefFileDialogManager if it doesn't already exist. void EnsureFileDialogManager(); @@ -591,6 +593,10 @@ class CefBrowserHostImpl : public CefBrowserHost, void StartAudioCapturer(); void OnRecentlyAudibleTimerFired(); + bool EnsureDevToolsManager(); + void InitializeDevToolsRegistrationOnUIThread( + CefRefPtr registration); + CefBrowserSettings settings_; CefRefPtr client_; scoped_refptr browser_info_; @@ -599,7 +605,7 @@ class CefBrowserHostImpl : public CefBrowserHost, std::unique_ptr platform_delegate_; const bool is_windowless_; const bool is_views_hosted_; - CefWindowHandle host_window_handle_; + CefWindowHandle host_window_handle_ = kNullWindowHandle; // Non-nullptr if this object owns the WebContents. Will be nullptr for popup // browsers between the calls to WebContentsCreated() and AddNewContents(), @@ -609,18 +615,18 @@ class CefBrowserHostImpl : public CefBrowserHost, // Volatile state information. All access must be protected by the state lock. base::Lock state_lock_; - bool is_loading_; - bool can_go_back_; - bool can_go_forward_; - bool has_document_; - bool is_fullscreen_; + bool is_loading_ = false; + bool can_go_back_ = false; + bool can_go_forward_ = false; + bool has_document_ = false; + bool is_fullscreen_ = false; // The currently focused frame, or nullptr if the main frame is focused. CefRefPtr focused_frame_; // Represents the current browser destruction state. Only accessed on the UI // thread. - DestructionState destruction_state_; + DestructionState destruction_state_ = DESTRUCTION_STATE_NONE; // Navigation will not occur while |navigation_lock_count_| > 0. // |pending_navigation_action_| will be executed when the lock is released. @@ -630,18 +636,18 @@ class CefBrowserHostImpl : public CefBrowserHost, // True if the OS window hosting the browser has been destroyed. Only accessed // on the UI thread. - bool window_destroyed_; + bool window_destroyed_ = false; // True if currently in the OnSetFocus callback. Only accessed on the UI // thread. - bool is_in_onsetfocus_; + bool is_in_onsetfocus_ = false; // True if the focus is currently on an editable field on the page. Only // accessed on the UI thread. - bool focus_on_editable_field_; + bool focus_on_editable_field_ = false; // True if mouse cursor change is disabled. - bool mouse_cursor_change_disabled_; + bool mouse_cursor_change_disabled_ = false; // Used for managing notification subscriptions. std::unique_ptr registrar_; @@ -655,12 +661,8 @@ class CefBrowserHostImpl : public CefBrowserHost, // Used for creating and managing context menus. std::unique_ptr menu_manager_; - // Track the lifespan of the frontend WebContents associated with this - // browser. - std::unique_ptr devtools_observer_; - // CefDevToolsFrontend will delete itself when the frontend WebContents is - // destroyed. - CefDevToolsFrontend* devtools_frontend_; + // Used for creating and managing DevTools instances. + std::unique_ptr devtools_manager_; // Observers that want to be notified of changes to this object. base::ObserverList::Unchecked observers_; diff --git a/libcef/browser/devtools/devtools_controller.cc b/libcef/browser/devtools/devtools_controller.cc new file mode 100644 index 000000000..9b3496edf --- /dev/null +++ b/libcef/browser/devtools/devtools_controller.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2020 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/browser/devtools/devtools_controller.h" + +#include "libcef/browser/devtools/devtools_util.h" +#include "libcef/browser/thread_util.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "content/public/browser/devtools_agent_host.h" + +CefDevToolsController::CefDevToolsController( + content::WebContents* inspected_contents) + : inspected_contents_(inspected_contents), weak_ptr_factory_(this) { + DCHECK(inspected_contents_); +} + +CefDevToolsController::~CefDevToolsController() { + if (agent_host_) { + agent_host_->DetachClient(this); + AgentHostClosed(agent_host_.get()); + } + + for (auto& observer : observers_) { + observer.OnDevToolsControllerDestroyed(); + } +} + +bool CefDevToolsController::SendDevToolsMessage( + const base::StringPiece& message) { + CEF_REQUIRE_UIT(); + if (!EnsureAgentHost()) + return false; + + agent_host_->DispatchProtocolMessage( + this, base::as_bytes(base::make_span(message))); + return true; +} + +int CefDevToolsController::ExecuteDevToolsMethod( + int suggested_message_id, + const std::string& method, + const base::DictionaryValue* params) { + CEF_REQUIRE_UIT(); + if (!EnsureAgentHost()) + return 0; + + // Message IDs must always be increasing and unique. + int message_id = suggested_message_id; + if (message_id < next_message_id_) + message_id = next_message_id_++; + else + next_message_id_ = message_id + 1; + + base::DictionaryValue message; + message.SetIntKey("id", message_id); + message.SetStringKey("method", method); + if (params) + message.SetKey("params", params->Clone()); + + std::string protocol_message; + if (!base::JSONWriter::Write(message, &protocol_message)) + return 0; + + agent_host_->DispatchProtocolMessage( + this, base::as_bytes(base::make_span(protocol_message))); + return message_id; +} + +void CefDevToolsController::AgentHostClosed( + content::DevToolsAgentHost* agent_host) { + DCHECK(agent_host == agent_host_.get()); + agent_host_ = nullptr; + for (auto& observer : observers_) { + observer.OnDevToolsAgentDetached(); + } +} + +void CefDevToolsController::AddObserver(Observer* observer) { + CEF_REQUIRE_UIT(); + observers_.AddObserver(observer); +} + +void CefDevToolsController::RemoveObserver(Observer* observer) { + CEF_REQUIRE_UIT(); + observers_.RemoveObserver(observer); +} + +void CefDevToolsController::DispatchProtocolMessage( + content::DevToolsAgentHost* agent_host, + base::span message) { + if (!observers_.might_have_observers()) + return; + + base::StringPiece str_message(reinterpret_cast(message.data()), + message.size()); + if (!devtools_util::ProtocolParser::IsValidMessage(str_message)) { + LOG(WARNING) << "Invalid message: " << str_message.substr(0, 100); + return; + } + + devtools_util::ProtocolParser parser; + + for (auto& observer : observers_) { + if (observer.OnDevToolsMessage(str_message)) { + continue; + } + + // Only perform parsing a single time. + if (parser.Initialize(str_message) && parser.IsFailure()) { + LOG(WARNING) << "Failed to parse message: " << str_message.substr(0, 100); + } + + if (parser.IsEvent()) { + observer.OnDevToolsEvent(parser.method_, parser.params_); + } else if (parser.IsResult()) { + observer.OnDevToolsMethodResult(parser.message_id_, parser.success_, + parser.params_); + } + } +} + +bool CefDevToolsController::EnsureAgentHost() { + if (!agent_host_) { + agent_host_ = + content::DevToolsAgentHost::GetOrCreateFor(inspected_contents_); + if (agent_host_) { + agent_host_->AttachClient(this); + for (auto& observer : observers_) { + observer.OnDevToolsAgentAttached(); + } + } + } + return !!agent_host_; +} diff --git a/libcef/browser/devtools/devtools_controller.h b/libcef/browser/devtools/devtools_controller.h new file mode 100644 index 000000000..a1730b733 --- /dev/null +++ b/libcef/browser/devtools/devtools_controller.h @@ -0,0 +1,79 @@ +// Copyright (c) 2020 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. + +#ifndef CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_CONTROLLER_H_ +#define CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_CONTROLLER_H_ + +#include + +#include "content/public/browser/devtools_agent_host_client.h" + +#include "base/containers/span.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/values.h" + +namespace content { +class WebContents; +} + +class CefDevToolsController : public content::DevToolsAgentHostClient { + public: + class Observer : public base::CheckedObserver { + public: + // See CefDevToolsMessageObserver documentation. + virtual bool OnDevToolsMessage(const base::StringPiece& message) = 0; + virtual void OnDevToolsMethodResult(int message_id, + bool success, + const base::StringPiece& result) = 0; + virtual void OnDevToolsEvent(const base::StringPiece& method, + const base::StringPiece& params) = 0; + virtual void OnDevToolsAgentAttached() = 0; + virtual void OnDevToolsAgentDetached() = 0; + + virtual void OnDevToolsControllerDestroyed() = 0; + + protected: + ~Observer() override {} + }; + + // |inspected_contents| will outlive this object. + explicit CefDevToolsController(content::WebContents* inspected_contents); + ~CefDevToolsController() override; + + // See CefBrowserHost methods of the same name for documentation. + bool SendDevToolsMessage(const base::StringPiece& message); + int ExecuteDevToolsMethod(int message_id, + const std::string& method, + const base::DictionaryValue* params); + + // |observer| must outlive this object or be removed. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + base::WeakPtr GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + // content::DevToolsAgentHostClient implementation: + void AgentHostClosed(content::DevToolsAgentHost* agent_host) override; + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, + base::span message) override; + + bool EnsureAgentHost(); + + content::WebContents* const inspected_contents_; + scoped_refptr agent_host_; + int next_message_id_ = 1; + + base::ObserverList observers_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(CefDevToolsController); +}; + +#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_CONTROLLER_H_ diff --git a/libcef/browser/devtools/devtools_frontend.cc b/libcef/browser/devtools/devtools_frontend.cc index d05e559aa..5c3b3efb1 100644 --- a/libcef/browser/devtools/devtools_frontend.cc +++ b/libcef/browser/devtools/devtools_frontend.cc @@ -6,13 +6,18 @@ #include +#include #include #include "libcef/browser/browser_context.h" +#include "libcef/browser/content_browser_client.h" #include "libcef/browser/devtools/devtools_manager_delegate.h" #include "libcef/browser/net/devtools_scheme_handler.h" +#include "libcef/common/cef_switches.h" #include "base/base64.h" +#include "base/command_line.h" +#include "base/files/file_util.h" #include "base/guid.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -48,6 +53,13 @@ #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/cpp/simple_url_loader_stream_consumer.h" #include "services/network/public/mojom/url_response_head.mojom.h" +#include "storage/browser/file_system/native_file_util.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#endif namespace { @@ -86,6 +98,71 @@ std::unique_ptr BuildObjectForResponse( return response; } +const int kMaxLogLineLength = 1024; + +void WriteTimestamp(std::stringstream& stream) { +#if defined(OS_WIN) + SYSTEMTIME local_time; + GetLocalTime(&local_time); + stream << std::setfill('0') << std::setw(2) << local_time.wMonth + << std::setw(2) << local_time.wDay << '/' << std::setw(2) + << local_time.wHour << std::setw(2) << local_time.wMinute + << std::setw(2) << local_time.wSecond << '.' << std::setw(3) + << local_time.wMilliseconds; +#elif defined(OS_POSIX) + timeval tv; + gettimeofday(&tv, nullptr); + time_t t = tv.tv_sec; + struct tm local_time; + localtime_r(&t, &local_time); + struct tm* tm_time = &local_time; + stream << std::setfill('0') << std::setw(2) << 1 + tm_time->tm_mon + << std::setw(2) << tm_time->tm_mday << '/' << std::setw(2) + << tm_time->tm_hour << std::setw(2) << tm_time->tm_min << std::setw(2) + << tm_time->tm_sec << '.' << std::setw(6) << tv.tv_usec; +#else +#error Unsupported platform +#endif +} + +void LogProtocolMessage(const base::FilePath& log_file, + ProtocolMessageType type, + std::string to_log) { + // Track if logging has failed, in which case we don't keep trying. + static bool log_error = false; + if (log_error) + return; + + if (storage::NativeFileUtil::EnsureFileExists(log_file, nullptr) != + base::File::FILE_OK) { + LOG(ERROR) << "Failed to create file " << log_file.value(); + log_error = true; + return; + } + + std::string type_label; + switch (type) { + case ProtocolMessageType::METHOD: + type_label = "METHOD"; + break; + case ProtocolMessageType::RESULT: + type_label = "RESULT"; + break; + case ProtocolMessageType::EVENT: + type_label = "EVENT"; + break; + } + + std::stringstream stream; + WriteTimestamp(stream); + stream << ": " << type_label << ": " << to_log << "\n"; + const std::string& str = stream.str(); + if (!base::AppendToFile(log_file, str.c_str(), str.size())) { + LOG(ERROR) << "Failed to write file " << log_file.value(); + log_error = true; + } +} + } // namespace class CefDevToolsFrontend::NetworkResourceLoader @@ -156,11 +233,12 @@ const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; // static CefDevToolsFrontend* CefDevToolsFrontend::Show( - CefRefPtr inspected_browser, + CefBrowserHostImpl* inspected_browser, const CefWindowInfo& windowInfo, CefRefPtr client, const CefBrowserSettings& settings, - const CefPoint& inspect_element_at) { + const CefPoint& inspect_element_at, + base::OnceClosure frontend_destroyed_callback) { CefBrowserSettings new_settings = settings; if (!windowInfo.windowless_rendering_enabled && CefColorGetA(new_settings.background_color) != SK_AlphaOPAQUE) { @@ -187,7 +265,8 @@ CefDevToolsFrontend* CefDevToolsFrontend::Show( // destroyed. CefDevToolsFrontend* devtools_frontend = new CefDevToolsFrontend( static_cast(frontend_browser.get()), - inspected_contents, inspect_element_at); + inspected_contents, inspect_element_at, + std::move(frontend_destroyed_callback)); // Need to load the URL after creating the DevTools objects. frontend_browser->GetMainFrame()->LoadURL(GetFrontendURL()); @@ -216,23 +295,23 @@ void CefDevToolsFrontend::Close() { frontend_browser_.get(), true)); } -void CefDevToolsFrontend::DisconnectFromTarget() { - if (!agent_host_) - return; - agent_host_->DetachClient(this); - agent_host_ = nullptr; -} - CefDevToolsFrontend::CefDevToolsFrontend( - CefRefPtr frontend_browser, + CefBrowserHostImpl* frontend_browser, content::WebContents* inspected_contents, - const CefPoint& inspect_element_at) + const CefPoint& inspect_element_at, + base::OnceClosure frontend_destroyed_callback) : content::WebContentsObserver(frontend_browser->web_contents()), frontend_browser_(frontend_browser), inspected_contents_(inspected_contents), inspect_element_at_(inspect_element_at), - file_manager_(frontend_browser.get(), GetPrefs()), - weak_factory_(this) {} + frontend_destroyed_callback_(std::move(frontend_destroyed_callback)), + file_manager_(frontend_browser, GetPrefs()), + protocol_log_file_( + base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( + switches::kDevToolsProtocolLogFile)), + weak_factory_(this) { + DCHECK(!frontend_destroyed_callback_.is_null()); +} CefDevToolsFrontend::~CefDevToolsFrontend() {} @@ -279,6 +358,7 @@ void CefDevToolsFrontend::WebContentsDestroyed() { agent_host_->DetachClient(this); agent_host_ = nullptr; } + std::move(frontend_destroyed_callback_).Run(); delete this; } @@ -300,6 +380,9 @@ void CefDevToolsFrontend::HandleMessageFromDevToolsFrontend( std::string protocol_message; if (!agent_host_ || !params->GetString(0, &protocol_message)) return; + if (ProtocolLoggingEnabled()) { + LogProtocolMessage(ProtocolMessageType::METHOD, protocol_message); + } agent_host_->DispatchProtocolMessage( this, base::as_bytes(base::make_span(protocol_message))); } else if (method == "loadCompleted") { @@ -451,6 +534,14 @@ void CefDevToolsFrontend::DispatchProtocolMessage( base::span message) { base::StringPiece str_message(reinterpret_cast(message.data()), message.size()); + if (ProtocolLoggingEnabled()) { + // Quick check to avoid parsing the JSON object. Events begin with a + // "method" value whereas method results begin with an "id" value. + LogProtocolMessage(str_message.starts_with("{\"method\":") + ? ProtocolMessageType::EVENT + : ProtocolMessageType::RESULT, + str_message); + } if (str_message.length() < kMaxMessageChunkSize) { std::string param; base::EscapeJSONString(str_message, true, ¶m); @@ -504,6 +595,23 @@ void CefDevToolsFrontend::SendMessageAck(int request_id, CallClientFunction("DevToolsAPI.embedderMessageAck", &id_value, arg, nullptr); } +bool CefDevToolsFrontend::ProtocolLoggingEnabled() const { + return !protocol_log_file_.empty(); +} + +void CefDevToolsFrontend::LogProtocolMessage(ProtocolMessageType type, + const base::StringPiece& message) { + DCHECK(ProtocolLoggingEnabled()); + + std::string to_log = message.substr(0, kMaxLogLineLength).as_string(); + + // Execute in an ordered context that allows blocking. + auto task_runner = CefContentBrowserClient::Get()->background_task_runner(); + task_runner->PostTask( + FROM_HERE, base::BindOnce(::LogProtocolMessage, protocol_log_file_, type, + std::move(to_log))); +} + void CefDevToolsFrontend::AgentHostClosed( content::DevToolsAgentHost* agent_host) { DCHECK(agent_host == agent_host_.get()); diff --git a/libcef/browser/devtools/devtools_frontend.h b/libcef/browser/devtools/devtools_frontend.h index ef68c1048..90e2defe0 100644 --- a/libcef/browser/devtools/devtools_frontend.h +++ b/libcef/browser/devtools/devtools_frontend.h @@ -11,6 +11,7 @@ #include "libcef/browser/devtools/devtools_file_manager.h" #include "base/compiler_specific.h" +#include "base/files/file_path.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" @@ -31,36 +32,38 @@ class WebContents; class PrefService; +enum class ProtocolMessageType { + METHOD, + RESULT, + EVENT, +}; + class CefDevToolsFrontend : public content::WebContentsObserver, public content::DevToolsAgentHostClient { public: static CefDevToolsFrontend* Show( - CefRefPtr inspected_browser, + CefBrowserHostImpl* inspected_browser, const CefWindowInfo& windowInfo, CefRefPtr client, const CefBrowserSettings& settings, - const CefPoint& inspect_element_at); + const CefPoint& inspect_element_at, + base::OnceClosure frontend_destroyed_callback); void Activate(); void Focus(); void InspectElementAt(int x, int y); void Close(); - void DisconnectFromTarget(); - void CallClientFunction(const std::string& function_name, const base::Value* arg1, const base::Value* arg2, const base::Value* arg3); - CefRefPtr frontend_browser() const { - return frontend_browser_; - } - private: - CefDevToolsFrontend(CefRefPtr frontend_browser, + CefDevToolsFrontend(CefBrowserHostImpl* frontend_browser, content::WebContents* inspected_contents, - const CefPoint& inspect_element_at); + const CefPoint& inspect_element_at, + base::OnceClosure destroyed_callback); ~CefDevToolsFrontend() override; // content::DevToolsAgentHostClient implementation. @@ -78,12 +81,17 @@ class CefDevToolsFrontend : public content::WebContentsObserver, void SendMessageAck(int request_id, const base::Value* arg1); + bool ProtocolLoggingEnabled() const; + void LogProtocolMessage(ProtocolMessageType type, + const base::StringPiece& message); + PrefService* GetPrefs() const; CefRefPtr frontend_browser_; content::WebContents* inspected_contents_; scoped_refptr agent_host_; CefPoint inspect_element_at_; + base::OnceClosure frontend_destroyed_callback_; std::unique_ptr frontend_host_; class NetworkResourceLoader; @@ -93,6 +101,9 @@ class CefDevToolsFrontend : public content::WebContentsObserver, using ExtensionsAPIs = std::map; ExtensionsAPIs extensions_api_; CefDevToolsFileManager file_manager_; + + const base::FilePath protocol_log_file_; + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(CefDevToolsFrontend); diff --git a/libcef/browser/devtools/devtools_manager.cc b/libcef/browser/devtools/devtools_manager.cc new file mode 100644 index 000000000..c93b274c0 --- /dev/null +++ b/libcef/browser/devtools/devtools_manager.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2020 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/browser/devtools/devtools_manager.h" + +#include "libcef/browser/devtools/devtools_controller.h" +#include "libcef/browser/devtools/devtools_frontend.h" + +#include "content/public/browser/web_contents.h" + +namespace { + +// May be created on any thread but will be destroyed on the UI thread. +class CefDevToolsRegistrationImpl : public CefRegistration, + public CefDevToolsController::Observer { + public: + explicit CefDevToolsRegistrationImpl( + CefRefPtr observer) + : observer_(observer) { + DCHECK(observer_); + } + + ~CefDevToolsRegistrationImpl() override { + CEF_REQUIRE_UIT(); + + // May be null if OnDevToolsControllerDestroyed was called. + if (!controller_) + return; + + controller_->RemoveObserver(this); + } + + void Initialize(CefBrowserHostImpl* browser, + base::WeakPtr controller) { + CEF_REQUIRE_UIT(); + DCHECK(browser && controller); + DCHECK(!browser_ && !controller_); + browser_ = browser; + controller_ = controller; + + controller_->AddObserver(this); + } + + private: + // CefDevToolsController::Observer methods: + bool OnDevToolsMessage(const base::StringPiece& message) override { + CEF_REQUIRE_UIT(); + return observer_->OnDevToolsMessage(browser_, message.data(), + message.size()); + } + + void OnDevToolsMethodResult(int message_id, + bool success, + const base::StringPiece& result) override { + CEF_REQUIRE_UIT(); + observer_->OnDevToolsMethodResult(browser_, message_id, success, + result.data(), result.size()); + } + + void OnDevToolsEvent(const base::StringPiece& method, + const base::StringPiece& params) override { + CEF_REQUIRE_UIT(); + observer_->OnDevToolsEvent(browser_, method.as_string(), params.data(), + params.size()); + } + + void OnDevToolsAgentAttached() override { + CEF_REQUIRE_UIT(); + observer_->OnDevToolsAgentAttached(browser_); + } + + void OnDevToolsAgentDetached() override { + CEF_REQUIRE_UIT(); + observer_->OnDevToolsAgentDetached(browser_); + } + + void OnDevToolsControllerDestroyed() override { + CEF_REQUIRE_UIT(); + browser_ = nullptr; + controller_.reset(); + } + + CefRefPtr observer_; + + CefBrowserHostImpl* browser_ = nullptr; + base::WeakPtr controller_; + + IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefDevToolsRegistrationImpl); + DISALLOW_COPY_AND_ASSIGN(CefDevToolsRegistrationImpl); +}; + +} // namespace + +CefDevToolsManager::CefDevToolsManager(CefBrowserHostImpl* inspected_browser) + : inspected_browser_(inspected_browser), weak_ptr_factory_(this) { + CEF_REQUIRE_UIT(); +} + +CefDevToolsManager::~CefDevToolsManager() { + CEF_REQUIRE_UIT(); +} + +void CefDevToolsManager::ShowDevTools(const CefWindowInfo& windowInfo, + CefRefPtr client, + const CefBrowserSettings& settings, + const CefPoint& inspect_element_at) { + CEF_REQUIRE_UIT(); + if (devtools_frontend_) { + if (!inspect_element_at.IsEmpty()) { + devtools_frontend_->InspectElementAt(inspect_element_at.x, + inspect_element_at.y); + } + devtools_frontend_->Focus(); + return; + } + + devtools_frontend_ = CefDevToolsFrontend::Show( + inspected_browser_, windowInfo, client, settings, inspect_element_at, + base::BindOnce(&CefDevToolsManager::OnFrontEndDestroyed, + weak_ptr_factory_.GetWeakPtr())); +} + +void CefDevToolsManager::CloseDevTools() { + CEF_REQUIRE_UIT(); + if (!devtools_frontend_) + return; + devtools_frontend_->Close(); +} + +bool CefDevToolsManager::HasDevTools() { + CEF_REQUIRE_UIT(); + return !!devtools_frontend_; +} + +bool CefDevToolsManager::SendDevToolsMessage(const void* message, + size_t message_size) { + CEF_REQUIRE_UIT(); + if (!message || message_size == 0) + return false; + + if (!EnsureController()) + return false; + + return devtools_controller_->SendDevToolsMessage( + base::StringPiece(static_cast(message), message_size)); +} + +int CefDevToolsManager::ExecuteDevToolsMethod( + int message_id, + const CefString& method, + CefRefPtr params) { + CEF_REQUIRE_UIT(); + if (method.empty()) + return 0; + + if (!EnsureController()) + return 0; + + if (params && params->IsValid()) { + CefDictionaryValueImpl* impl = + static_cast(params.get()); + CefValueController::AutoLock lock_scope(impl->controller()); + return devtools_controller_->ExecuteDevToolsMethod(message_id, method, + impl->GetValueUnsafe()); + } else { + return devtools_controller_->ExecuteDevToolsMethod(message_id, method, + nullptr); + } +} + +// static +CefRefPtr CefDevToolsManager::CreateRegistration( + CefRefPtr observer) { + DCHECK(observer); + return new CefDevToolsRegistrationImpl(observer); +} + +void CefDevToolsManager::InitializeRegistrationOnUIThread( + CefRefPtr registration) { + CEF_REQUIRE_UIT(); + + if (!EnsureController()) + return; + + static_cast(registration.get()) + ->Initialize(inspected_browser_, devtools_controller_->GetWeakPtr()); +} + +void CefDevToolsManager::OnFrontEndDestroyed() { + devtools_frontend_ = nullptr; +} + +bool CefDevToolsManager::EnsureController() { + if (!devtools_controller_) { + devtools_controller_.reset( + new CefDevToolsController(inspected_browser_->web_contents())); + } + return true; +} diff --git a/libcef/browser/devtools/devtools_manager.h b/libcef/browser/devtools/devtools_manager.h new file mode 100644 index 000000000..ae1d5fe8e --- /dev/null +++ b/libcef/browser/devtools/devtools_manager.h @@ -0,0 +1,69 @@ +// Copyright (c) 2020 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. + +#ifndef CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_H_ +#define CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_H_ +#pragma once + +#include "include/cef_browser.h" + +#include "base/memory/weak_ptr.h" + +class CefBrowserHostImpl; +class CefDevToolsController; +class CefDevToolsFrontend; + +namespace content { +class WebContents; +} + +// Manages DevTools instances. Methods must be called on the UI thread unless +// otherwise indicated. +class CefDevToolsManager { + public: + // |inspected_browser| will outlive this object. + explicit CefDevToolsManager(CefBrowserHostImpl* inspected_browser); + ~CefDevToolsManager(); + + // See CefBrowserHost methods of the same name for documentation. + void ShowDevTools(const CefWindowInfo& windowInfo, + CefRefPtr client, + const CefBrowserSettings& settings, + const CefPoint& inspect_element_at); + void CloseDevTools(); + bool HasDevTools(); + bool SendDevToolsMessage(const void* message, size_t message_size); + int ExecuteDevToolsMethod(int message_id, + const CefString& method, + CefRefPtr param); + + // These methods are used to implement + // CefBrowserHost::AddDevToolsMessageObserver. CreateRegistration is safe to + // call on any thread. InitializeRegistrationOnUIThread should be called + // immediately afterwards on the UI thread. + static CefRefPtr CreateRegistration( + CefRefPtr observer); + void InitializeRegistrationOnUIThread( + CefRefPtr registration); + + private: + void OnFrontEndDestroyed(); + + bool EnsureController(); + + CefBrowserHostImpl* const inspected_browser_; + + // CefDevToolsFrontend will delete itself when the frontend WebContents is + // destroyed. + CefDevToolsFrontend* devtools_frontend_ = nullptr; + + // Used for sending DevTools protocol messages without an active frontend. + std::unique_ptr devtools_controller_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(CefDevToolsManager); +}; + +#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_H_ diff --git a/libcef/browser/devtools/devtools_util.cc b/libcef/browser/devtools/devtools_util.cc new file mode 100644 index 000000000..c0200cf38 --- /dev/null +++ b/libcef/browser/devtools/devtools_util.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2020 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/browser/devtools/devtools_util.h" + +#include "base/strings/string_number_conversions.h" + +namespace devtools_util { + +namespace { + +bool IsValidDictionary(const base::StringPiece& str, bool allow_empty) { + return str.length() >= (allow_empty ? 2 : 3) && str[0] == '{' && + str[str.length() - 1] == '}'; +} + +// Example: +// {"method":"Target.targetDestroyed","params":{"targetId":"1234..."}} +bool ParseEvent(const base::StringPiece& message, + base::StringPiece& method, + base::StringPiece& params) { + static const char kMethodStart[] = "{\"method\":\""; + static const char kMethodEnd[] = "\""; + static const char kParamsStart[] = ",\"params\":"; + + if (!message.starts_with(kMethodStart)) + return false; + + const size_t method_start = sizeof(kMethodStart) - 1; + const size_t method_end = message.find(kMethodEnd, method_start); + if (method_end < 0U) + return false; + method = message.substr(method_start, method_end - method_start); + if (method.empty()) + return false; + + size_t remainder_start = method_end + sizeof(kMethodEnd) - 1; + if (remainder_start == message.size() - 1) { + // No more contents. + params = base::StringPiece(); + } else { + const base::StringPiece& remainder = message.substr(remainder_start); + if (remainder.starts_with(kParamsStart)) { + // Stop immediately before the message closing bracket. + remainder_start += sizeof(kParamsStart) - 1; + params = + message.substr(remainder_start, message.size() - 1 - remainder_start); + } else { + // Invalid format. + return false; + } + + if (!IsValidDictionary(params, /*allow_empty=*/true)) + return false; + } + + return true; +} + +// Examples: +// {"id":3,"result":{}} +// {"id":4,"result":{"debuggerId":"-2193881606781505058.81393575456727957"}} +// {"id":5,"error":{"code":-32000,"message":"Not supported"}} +bool ParseResult(const base::StringPiece& message, + int& message_id, + bool& success, + base::StringPiece& result) { + static const char kIdStart[] = "{\"id\":"; + static const char kIdEnd[] = ","; + static const char kResultStart[] = "\"result\":"; + static const char kErrorStart[] = "\"error\":"; + + if (!message.starts_with(kIdStart)) + return false; + + const size_t id_start = sizeof(kIdStart) - 1; + const size_t id_end = message.find(kIdEnd, id_start); + if (id_end < 0U) + return false; + const base::StringPiece& id_str = message.substr(id_start, id_end - id_start); + if (id_str.empty() || !base::StringToInt(id_str, &message_id)) + return false; + + size_t remainder_start = id_end + sizeof(kIdEnd) - 1; + const base::StringPiece& remainder = message.substr(remainder_start); + if (remainder.starts_with(kResultStart)) { + // Stop immediately before the message closing bracket. + remainder_start += sizeof(kResultStart) - 1; + result = + message.substr(remainder_start, message.size() - 1 - remainder_start); + success = true; + } else if (remainder.starts_with(kErrorStart)) { + // Stop immediately before the message closing bracket. + remainder_start += sizeof(kErrorStart) - 1; + result = + message.substr(remainder_start, message.size() - 1 - remainder_start); + success = false; + } else { + // Invalid format. + return false; + } + + if (!IsValidDictionary(result, /*allow_empty=*/true)) + return false; + + return true; +} + +} // namespace + +// static +bool ProtocolParser::IsValidMessage(const base::StringPiece& message) { + return IsValidDictionary(message, /*allow_empty=*/false); +} + +bool ProtocolParser::Initialize(const base::StringPiece& message) { + if (status_ != UNINITIALIZED) + return false; + + if (ParseEvent(message, method_, params_)) { + status_ = EVENT; + } else if (ParseResult(message, message_id_, success_, params_)) { + status_ = RESULT; + } else { + status_ = FAILURE; + } + return true; +} + +} // namespace devtools_util diff --git a/libcef/browser/devtools/devtools_util.h b/libcef/browser/devtools/devtools_util.h new file mode 100644 index 000000000..848e1684e --- /dev/null +++ b/libcef/browser/devtools/devtools_util.h @@ -0,0 +1,72 @@ +// Copyright (c) 2020 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. + +#ifndef CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_UTIL_H_ +#define CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_UTIL_H_ +#pragma once + +#include "base/strings/string_piece.h" + +namespace devtools_util { + +// Fast parser for DevTools JSON protocol messages. This implementation makes +// certain assumptions about the JSON object structure (value order and +// formatting) to avoid stateful parsing of messages that may be large +// (sometimes > 1MB in size). The message must be a JSON dictionary that starts +// with a "method" or "id" value which is non-empty and of the expected data +// type. Messages that have a "method" value (event message) may optionally have +// a "params" dictionary. Messages that have an "id" value (result message) must +// have a "result" or "error" dictionary. The dictionary contents are not +// validated and may be empty ("{}"). +// +// Example event message: +// {"method":"Target.targetDestroyed","params":{"targetId":"1234..."}} +// +// Example result messages: +// {"id":3,"result":{}} +// {"id":4,"result":{"debuggerId":"-2193881606781505058.81393575456727957"}} +// {"id":5,"error":{"code":-32000,"message":"Not supported"}} +struct ProtocolParser { + ProtocolParser() = default; + + // Checks for a non-empty JSON dictionary. + static bool IsValidMessage(const base::StringPiece& message); + + // Returns false if already initialized. + bool Initialize(const base::StringPiece& message); + + bool IsInitialized() const { return status_ != UNINITIALIZED; } + bool IsEvent() const { return status_ == EVENT; } + bool IsResult() const { return status_ == RESULT; } + bool IsFailure() const { return status_ == FAILURE; } + + void Reset() { status_ = UNINITIALIZED; } + + // For event messages: + // "method" string: + base::StringPiece method_; + + // For result messages: + // "id" int: + int message_id_ = 0; + // true if "result" value, false if "error" value: + bool success_ = false; + + // For both: + // "params", "result" or "error" dictionary: + base::StringPiece params_; + + private: + enum Status { + UNINITIALIZED, + EVENT, // Event message. + RESULT, // Result message. + FAILURE, // Parsing failure. + }; + Status status_ = UNINITIALIZED; +}; + +} // namespace devtools_util + +#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_UTIL_H_ \ No newline at end of file diff --git a/libcef/browser/devtools/devtools_util_unittest.cc b/libcef/browser/devtools/devtools_util_unittest.cc new file mode 100644 index 000000000..0175aa040 --- /dev/null +++ b/libcef/browser/devtools/devtools_util_unittest.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2020 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/browser/devtools/devtools_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using namespace devtools_util; + +TEST(DevToolsUtil, ProtocolParser_IsValidMessage) { + // Empty dictionary is not valid. + EXPECT_FALSE(ProtocolParser::IsValidMessage("")); + EXPECT_FALSE(ProtocolParser::IsValidMessage("{}")); + + // Incorrectly formatted dictionary is not valid. + EXPECT_FALSE(ProtocolParser::IsValidMessage("{ ]")); + + // Everything else is valid (we don't verify JSON structure). + EXPECT_TRUE(ProtocolParser::IsValidMessage("{ }")); + EXPECT_TRUE(ProtocolParser::IsValidMessage("{blah blah}")); + EXPECT_TRUE(ProtocolParser::IsValidMessage("{method:\"foobar\"}")); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsFailure_Unknown) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Empty message is invalid. + EXPECT_TRUE(parser.Initialize("")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Empty dictionary is invalid. + EXPECT_TRUE(parser.Initialize("{}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Unrecognized dictionary type is invalid. + EXPECT_TRUE(parser.Initialize("{blah blah}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsFailure_EventMalformed) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Empty method is invalid. + EXPECT_TRUE(parser.Initialize("{\"method\":\"\"}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Unrecognized value is invalid. + EXPECT_TRUE(parser.Initialize("{\"method\":\"foo\",oops:false}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Params must be a dictionary. + EXPECT_TRUE(parser.Initialize("{\"method\":\",params:[]}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsEvent) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Method without params is valid. + std::string message = "{\"method\":\"Test.myMethod\"}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsEvent()); + EXPECT_STREQ("Test.myMethod", parser.method_.as_string().data()); + EXPECT_TRUE(parser.params_.empty()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Method with empty params dictionary is valid. + message = "{\"method\":\"Test.myMethod2\",\"params\":{}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsEvent()); + EXPECT_STREQ("Test.myMethod2", parser.method_.as_string().data()); + EXPECT_STREQ("{}", parser.params_.as_string().data()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Method with non-empty params dictionary is valid. + message = "{\"method\":\"Test.myMethod3\",\"params\":{\"foo\":\"bar\"}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsEvent()); + EXPECT_STREQ("Test.myMethod3", parser.method_.as_string().data()); + EXPECT_STREQ("{\"foo\":\"bar\"}", parser.params_.as_string().data()); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsFailure_ResultMalformed) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Empty ID is invalid. + EXPECT_TRUE(parser.Initialize("{\"id\":,result:{}}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Missing result or error value is invalid. + EXPECT_TRUE(parser.Initialize("{\"id\":1}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Unrecognized value is invalid. + EXPECT_TRUE(parser.Initialize("{\"id\":1,oops:false}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Result must be a dictionary. + EXPECT_TRUE(parser.Initialize("{\"id\":1,\"result\":[]}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Error must be a dictionary. + EXPECT_TRUE(parser.Initialize("{\"id\":1,\"error\":[]}")); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsFailure()); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsResult_Result) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Id with empty result dictionary is valid. + std::string message = "{\"id\":1,\"result\":{}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsResult()); + EXPECT_EQ(1, parser.message_id_); + EXPECT_TRUE(parser.success_); + EXPECT_STREQ("{}", parser.params_.as_string().data()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Id with non-empty result dictionary is valid. + message = "{\"id\":2,\"result\":{\"foo\":\"bar\"}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsResult()); + EXPECT_EQ(2, parser.message_id_); + EXPECT_TRUE(parser.success_); + EXPECT_STREQ("{\"foo\":\"bar\"}", parser.params_.as_string().data()); +} + +TEST(DevToolsUtil, ProtocolParser_Initialize_IsResult_Error) { + ProtocolParser parser; + EXPECT_FALSE(parser.IsInitialized()); + + // Id with empty error dictionary is valid. + std::string message = "{\"id\":1,\"error\":{}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsResult()); + EXPECT_EQ(1, parser.message_id_); + EXPECT_FALSE(parser.success_); + EXPECT_STREQ("{}", parser.params_.as_string().data()); + + parser.Reset(); + EXPECT_FALSE(parser.IsInitialized()); + + // Id with non-empty error dictionary is valid. + message = "{\"id\":2,\"error\":{\"foo\":\"bar\"}}"; + EXPECT_TRUE(parser.Initialize(message)); + EXPECT_TRUE(parser.IsInitialized()); + EXPECT_TRUE(parser.IsResult()); + EXPECT_EQ(2, parser.message_id_); + EXPECT_FALSE(parser.success_); + EXPECT_STREQ("{\"foo\":\"bar\"}", parser.params_.as_string().data()); +} diff --git a/libcef/common/cef_switches.cc b/libcef/common/cef_switches.cc index bbe78f0c2..1e56b3c12 100644 --- a/libcef/common/cef_switches.cc +++ b/libcef/common/cef_switches.cc @@ -114,11 +114,13 @@ const char kPluginPolicy_Block[] = "block"; const char kEnablePreferenceTesting[] = "enable-preference-testing"; // Enable print preview. -extern const char kEnablePrintPreview[] = "enable-print-preview"; +const char kEnablePrintPreview[] = "enable-print-preview"; // Disable the timeout for delivering new browser info to the renderer process. -extern const char kDisableNewBrowserInfoTimeout[] = - "disable-new-browser-info-timeout"; +const char kDisableNewBrowserInfoTimeout[] = "disable-new-browser-info-timeout"; + +// File used for logging DevTools protocol messages. +const char kDevToolsProtocolLogFile[] = "devtools-protocol-log-file"; #if defined(OS_MACOSX) // Path to the framework directory. diff --git a/libcef/common/cef_switches.h b/libcef/common/cef_switches.h index 12f9e588b..2731f7736 100644 --- a/libcef/common/cef_switches.h +++ b/libcef/common/cef_switches.h @@ -53,6 +53,7 @@ extern const char kPluginPolicy_Block[]; extern const char kEnablePreferenceTesting[]; extern const char kEnablePrintPreview[]; extern const char kDisableNewBrowserInfoTimeout[]; +extern const char kDevToolsProtocolLogFile[]; #if defined(OS_MACOSX) extern const char kFrameworkDirPath[]; diff --git a/libcef/common/json_impl.cc b/libcef/common/json_impl.cc index 32390b298..d4cf45e72 100644 --- a/libcef/common/json_impl.cc +++ b/libcef/common/json_impl.cc @@ -34,8 +34,17 @@ int GetJSONWriterOptions(cef_json_writer_options_t options) { CefRefPtr CefParseJSON(const CefString& json_string, cef_json_parser_options_t options) { const std::string& json = json_string.ToString(); - base::Optional parse_result = - base::JSONReader::Read(json, GetJSONReaderOptions(options)); + return CefParseJSON(json.data(), json.size(), options); +} + +CefRefPtr CefParseJSON(const void* json, + size_t json_size, + cef_json_parser_options_t options) { + if (!json || json_size == 0) + return nullptr; + base::Optional parse_result = base::JSONReader::Read( + base::StringPiece(static_cast(json), json_size), + GetJSONReaderOptions(options)); if (parse_result) { return new CefValueImpl( base::Value::ToUniquePtrValue(std::move(parse_result.value())) diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index 1e2785e66..124782dc4 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=a84451b4f759f2a4a0fe673d90be1fb5053bfd1a$ +// $hash=516b55b7ea53e2de2b096e85ba0eb83f2a2693f3$ // #include "libcef_dll/cpptoc/browser_host_cpptoc.h" @@ -18,8 +18,10 @@ #include "libcef_dll/cpptoc/drag_data_cpptoc.h" #include "libcef_dll/cpptoc/extension_cpptoc.h" #include "libcef_dll/cpptoc/navigation_entry_cpptoc.h" +#include "libcef_dll/cpptoc/registration_cpptoc.h" #include "libcef_dll/cpptoc/request_context_cpptoc.h" #include "libcef_dll/ctocpp/client_ctocpp.h" +#include "libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h" #include "libcef_dll/ctocpp/download_image_callback_ctocpp.h" #include "libcef_dll/ctocpp/navigation_entry_visitor_ctocpp.h" #include "libcef_dll/ctocpp/pdf_print_callback_ctocpp.h" @@ -522,6 +524,81 @@ int CEF_CALLBACK browser_host_has_dev_tools(struct _cef_browser_host_t* self) { return _retval; } +int CEF_CALLBACK +browser_host_send_dev_tools_message(struct _cef_browser_host_t* self, + const void* message, + size_t message_size) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: message; type: simple_byaddr + DCHECK(message); + if (!message) + return 0; + + // Execute + bool _retval = CefBrowserHostCppToC::Get(self)->SendDevToolsMessage( + message, message_size); + + // Return type: bool + return _retval; +} + +int CEF_CALLBACK +browser_host_execute_dev_tools_method(struct _cef_browser_host_t* self, + int message_id, + const cef_string_t* method, + struct _cef_dictionary_value_t* params) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: method; type: string_byref_const + DCHECK(method); + if (!method) + return 0; + // Unverified params: params + + // Execute + int _retval = CefBrowserHostCppToC::Get(self)->ExecuteDevToolsMethod( + message_id, CefString(method), CefDictionaryValueCppToC::Unwrap(params)); + + // Return type: simple + return _retval; +} + +struct _cef_registration_t* CEF_CALLBACK +browser_host_add_dev_tools_message_observer( + struct _cef_browser_host_t* self, + struct _cef_dev_tools_message_observer_t* observer) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return NULL; + // Verify param: observer; type: refptr_diff + DCHECK(observer); + if (!observer) + return NULL; + + // Execute + CefRefPtr _retval = + CefBrowserHostCppToC::Get(self)->AddDevToolsMessageObserver( + CefDevToolsMessageObserverCToCpp::Wrap(observer)); + + // Return type: refptr_same + return CefRegistrationCppToC::Wrap(_retval); +} + void CEF_CALLBACK browser_host_get_navigation_entries(struct _cef_browser_host_t* self, cef_navigation_entry_visitor_t* visitor, @@ -1285,6 +1362,10 @@ CefBrowserHostCppToC::CefBrowserHostCppToC() { GetStruct()->show_dev_tools = browser_host_show_dev_tools; GetStruct()->close_dev_tools = browser_host_close_dev_tools; GetStruct()->has_dev_tools = browser_host_has_dev_tools; + GetStruct()->send_dev_tools_message = browser_host_send_dev_tools_message; + GetStruct()->execute_dev_tools_method = browser_host_execute_dev_tools_method; + GetStruct()->add_dev_tools_message_observer = + browser_host_add_dev_tools_message_observer; GetStruct()->get_navigation_entries = browser_host_get_navigation_entries; GetStruct()->set_mouse_cursor_change_disabled = browser_host_set_mouse_cursor_change_disabled; diff --git a/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.cc b/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.cc new file mode 100644 index 000000000..600796ad3 --- /dev/null +++ b/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2020 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=c4a7b4d679f1f2ed47fc31df3e2099962d5cb9d6$ +// + +#include "libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h" +#include "libcef_dll/ctocpp/browser_ctocpp.h" +#include "libcef_dll/shutdown_checker.h" + +namespace { + +// MEMBER FUNCTIONS - Body may be edited by hand. + +int CEF_CALLBACK dev_tools_message_observer_on_dev_tools_message( + struct _cef_dev_tools_message_observer_t* self, + cef_browser_t* browser, + const void* message, + size_t message_size) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return 0; + // Verify param: message; type: simple_byaddr + DCHECK(message); + if (!message) + return 0; + + // Execute + bool _retval = CefDevToolsMessageObserverCppToC::Get(self)->OnDevToolsMessage( + CefBrowserCToCpp::Wrap(browser), message, message_size); + + // Return type: bool + return _retval; +} + +void CEF_CALLBACK dev_tools_message_observer_on_dev_tools_method_result( + struct _cef_dev_tools_message_observer_t* self, + cef_browser_t* browser, + int message_id, + int success, + const void* result, + size_t result_size) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + // Unverified params: result + + // Execute + CefDevToolsMessageObserverCppToC::Get(self)->OnDevToolsMethodResult( + CefBrowserCToCpp::Wrap(browser), message_id, success ? true : false, + result, result_size); +} + +void CEF_CALLBACK dev_tools_message_observer_on_dev_tools_event( + struct _cef_dev_tools_message_observer_t* self, + cef_browser_t* browser, + const cef_string_t* method, + const void* params, + size_t params_size) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + // Verify param: method; type: string_byref_const + DCHECK(method); + if (!method) + return; + // Unverified params: params + + // Execute + CefDevToolsMessageObserverCppToC::Get(self)->OnDevToolsEvent( + CefBrowserCToCpp::Wrap(browser), CefString(method), params, params_size); +} + +void CEF_CALLBACK dev_tools_message_observer_on_dev_tools_agent_attached( + struct _cef_dev_tools_message_observer_t* self, + cef_browser_t* browser) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefDevToolsMessageObserverCppToC::Get(self)->OnDevToolsAgentAttached( + CefBrowserCToCpp::Wrap(browser)); +} + +void CEF_CALLBACK dev_tools_message_observer_on_dev_tools_agent_detached( + struct _cef_dev_tools_message_observer_t* self, + cef_browser_t* browser) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefDevToolsMessageObserverCppToC::Get(self)->OnDevToolsAgentDetached( + CefBrowserCToCpp::Wrap(browser)); +} + +} // namespace + +// CONSTRUCTOR - Do not edit by hand. + +CefDevToolsMessageObserverCppToC::CefDevToolsMessageObserverCppToC() { + GetStruct()->on_dev_tools_message = + dev_tools_message_observer_on_dev_tools_message; + GetStruct()->on_dev_tools_method_result = + dev_tools_message_observer_on_dev_tools_method_result; + GetStruct()->on_dev_tools_event = + dev_tools_message_observer_on_dev_tools_event; + GetStruct()->on_dev_tools_agent_attached = + dev_tools_message_observer_on_dev_tools_agent_attached; + GetStruct()->on_dev_tools_agent_detached = + dev_tools_message_observer_on_dev_tools_agent_detached; +} + +// DESTRUCTOR - Do not edit by hand. + +CefDevToolsMessageObserverCppToC::~CefDevToolsMessageObserverCppToC() { + shutdown_checker::AssertNotShutdown(); +} + +template <> +CefRefPtr +CefCppToCRefCounted:: + UnwrapDerived(CefWrapperType type, cef_dev_tools_message_observer_t* s) { + NOTREACHED() << "Unexpected class type: " << type; + return nullptr; +} + +template <> +CefWrapperType + CefCppToCRefCounted::kWrapperType = + WT_DEV_TOOLS_MESSAGE_OBSERVER; diff --git a/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h b/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h new file mode 100644 index 000000000..9d39fd143 --- /dev/null +++ b/libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=d48f93f3817dab7daf0e30a6fa94b1a173356383$ +// + +#ifndef CEF_LIBCEF_DLL_CPPTOC_DEV_TOOLS_MESSAGE_OBSERVER_CPPTOC_H_ +#define CEF_LIBCEF_DLL_CPPTOC_DEV_TOOLS_MESSAGE_OBSERVER_CPPTOC_H_ +#pragma once + +#if !defined(WRAPPING_CEF_SHARED) +#error This file can be included wrapper-side only +#endif + +#include "include/capi/cef_browser_capi.h" +#include "include/capi/cef_devtools_message_observer_capi.h" +#include "include/cef_browser.h" +#include "include/cef_devtools_message_observer.h" +#include "libcef_dll/cpptoc/cpptoc_ref_counted.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefDevToolsMessageObserverCppToC + : public CefCppToCRefCounted { + public: + CefDevToolsMessageObserverCppToC(); + virtual ~CefDevToolsMessageObserverCppToC(); +}; + +#endif // CEF_LIBCEF_DLL_CPPTOC_DEV_TOOLS_MESSAGE_OBSERVER_CPPTOC_H_ diff --git a/libcef_dll/cpptoc/request_handler_cpptoc.cc b/libcef_dll/cpptoc/request_handler_cpptoc.cc index afb4d8cf6..bef261da9 100644 --- a/libcef_dll/cpptoc/request_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/request_handler_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=68840d18a25efe4d749e480225d7ac3caacd4723$ +// $hash=716501855da0203723eb18bfb9e518c7b155f110$ // #include "libcef_dll/cpptoc/request_handler_cpptoc.h" @@ -396,6 +396,26 @@ void CEF_CALLBACK request_handler_on_render_process_terminated( CefBrowserCToCpp::Wrap(browser), status); } +void CEF_CALLBACK request_handler_on_document_available_in_main_frame( + struct _cef_request_handler_t* self, + cef_browser_t* browser) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefRequestHandlerCppToC::Get(self)->OnDocumentAvailableInMainFrame( + CefBrowserCToCpp::Wrap(browser)); +} + } // namespace // CONSTRUCTOR - Do not edit by hand. @@ -414,6 +434,8 @@ CefRequestHandlerCppToC::CefRequestHandlerCppToC() { GetStruct()->on_render_view_ready = request_handler_on_render_view_ready; GetStruct()->on_render_process_terminated = request_handler_on_render_process_terminated; + GetStruct()->on_document_available_in_main_frame = + request_handler_on_document_available_in_main_frame; } // DESTRUCTOR - Do not edit by hand. diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index 6fde71c82..68b407ebc 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -9,11 +9,12 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=9073823898d373a435ccdc56f2914aad91cbba1e$ +// $hash=a362e11e85ce68488bbb0f5232b01f53cffeec1d$ // #include "libcef_dll/ctocpp/browser_host_ctocpp.h" #include "libcef_dll/cpptoc/client_cpptoc.h" +#include "libcef_dll/cpptoc/dev_tools_message_observer_cpptoc.h" #include "libcef_dll/cpptoc/download_image_callback_cpptoc.h" #include "libcef_dll/cpptoc/navigation_entry_visitor_cpptoc.h" #include "libcef_dll/cpptoc/pdf_print_callback_cpptoc.h" @@ -23,6 +24,7 @@ #include "libcef_dll/ctocpp/drag_data_ctocpp.h" #include "libcef_dll/ctocpp/extension_ctocpp.h" #include "libcef_dll/ctocpp/navigation_entry_ctocpp.h" +#include "libcef_dll/ctocpp/registration_ctocpp.h" #include "libcef_dll/ctocpp/request_context_ctocpp.h" #include "libcef_dll/shutdown_checker.h" #include "libcef_dll/transfer_util.h" @@ -468,6 +470,81 @@ NO_SANITIZE("cfi-icall") bool CefBrowserHostCToCpp::HasDevTools() { return _retval ? true : false; } +NO_SANITIZE("cfi-icall") +bool CefBrowserHostCToCpp::SendDevToolsMessage(const void* message, + size_t message_size) { + shutdown_checker::AssertNotShutdown(); + + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, send_dev_tools_message)) + return false; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: message; type: simple_byaddr + DCHECK(message); + if (!message) + return false; + + // Execute + int _retval = _struct->send_dev_tools_message(_struct, message, message_size); + + // Return type: bool + return _retval ? true : false; +} + +NO_SANITIZE("cfi-icall") +int CefBrowserHostCToCpp::ExecuteDevToolsMethod( + int message_id, + const CefString& method, + CefRefPtr params) { + shutdown_checker::AssertNotShutdown(); + + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, execute_dev_tools_method)) + return 0; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: method; type: string_byref_const + DCHECK(!method.empty()); + if (method.empty()) + return 0; + // Unverified params: params + + // Execute + int _retval = _struct->execute_dev_tools_method( + _struct, message_id, method.GetStruct(), + CefDictionaryValueCToCpp::Unwrap(params)); + + // Return type: simple + return _retval; +} + +NO_SANITIZE("cfi-icall") +CefRefPtr CefBrowserHostCToCpp::AddDevToolsMessageObserver( + CefRefPtr observer) { + shutdown_checker::AssertNotShutdown(); + + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, add_dev_tools_message_observer)) + return nullptr; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: observer; type: refptr_diff + DCHECK(observer.get()); + if (!observer.get()) + return nullptr; + + // Execute + cef_registration_t* _retval = _struct->add_dev_tools_message_observer( + _struct, CefDevToolsMessageObserverCppToC::Wrap(observer)); + + // Return type: refptr_same + return CefRegistrationCToCpp::Wrap(_retval); +} + NO_SANITIZE("cfi-icall") void CefBrowserHostCToCpp::GetNavigationEntries( CefRefPtr visitor, diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index dcf09ed26..4987e5a60 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=a9c5f9224663a0c2f2a29c35c2dea3f47973b82f$ +// $hash=c9abd1293472afbac964aac4cd7dd4cac9dd8e58$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_BROWSER_HOST_CTOCPP_H_ @@ -76,6 +76,12 @@ class CefBrowserHostCToCpp : public CefCToCppRefCounted params) OVERRIDE; + CefRefPtr AddDevToolsMessageObserver( + CefRefPtr observer) OVERRIDE; void GetNavigationEntries(CefRefPtr visitor, bool current_only) OVERRIDE; void SetMouseCursorChangeDisabled(bool disabled) OVERRIDE; diff --git a/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.cc b/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.cc new file mode 100644 index 000000000..c3d6210f4 --- /dev/null +++ b/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2020 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=e8d1eaf4b8cdfd79a260de33fb3be05a2948c81e$ +// + +#include "libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h" +#include "libcef_dll/cpptoc/browser_cpptoc.h" +#include "libcef_dll/shutdown_checker.h" + +// VIRTUAL METHODS - Body may be edited by hand. + +NO_SANITIZE("cfi-icall") +bool CefDevToolsMessageObserverCToCpp::OnDevToolsMessage( + CefRefPtr browser, + const void* message, + size_t message_size) { + shutdown_checker::AssertNotShutdown(); + + cef_dev_tools_message_observer_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dev_tools_message)) + return false; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return false; + // Verify param: message; type: simple_byaddr + DCHECK(message); + if (!message) + return false; + + // Execute + int _retval = _struct->on_dev_tools_message( + _struct, CefBrowserCppToC::Wrap(browser), message, message_size); + + // Return type: bool + return _retval ? true : false; +} + +NO_SANITIZE("cfi-icall") +void CefDevToolsMessageObserverCToCpp::OnDevToolsMethodResult( + CefRefPtr browser, + int message_id, + bool success, + const void* result, + size_t result_size) { + shutdown_checker::AssertNotShutdown(); + + cef_dev_tools_message_observer_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dev_tools_method_result)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + // Unverified params: result + + // Execute + _struct->on_dev_tools_method_result(_struct, CefBrowserCppToC::Wrap(browser), + message_id, success, result, result_size); +} + +NO_SANITIZE("cfi-icall") +void CefDevToolsMessageObserverCToCpp::OnDevToolsEvent( + CefRefPtr browser, + const CefString& method, + const void* params, + size_t params_size) { + shutdown_checker::AssertNotShutdown(); + + cef_dev_tools_message_observer_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dev_tools_event)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + // Verify param: method; type: string_byref_const + DCHECK(!method.empty()); + if (method.empty()) + return; + // Unverified params: params + + // Execute + _struct->on_dev_tools_event(_struct, CefBrowserCppToC::Wrap(browser), + method.GetStruct(), params, params_size); +} + +NO_SANITIZE("cfi-icall") +void CefDevToolsMessageObserverCToCpp::OnDevToolsAgentAttached( + CefRefPtr browser) { + shutdown_checker::AssertNotShutdown(); + + cef_dev_tools_message_observer_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dev_tools_agent_attached)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_dev_tools_agent_attached(_struct, + CefBrowserCppToC::Wrap(browser)); +} + +NO_SANITIZE("cfi-icall") +void CefDevToolsMessageObserverCToCpp::OnDevToolsAgentDetached( + CefRefPtr browser) { + shutdown_checker::AssertNotShutdown(); + + cef_dev_tools_message_observer_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dev_tools_agent_detached)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_dev_tools_agent_detached(_struct, + CefBrowserCppToC::Wrap(browser)); +} + +// CONSTRUCTOR - Do not edit by hand. + +CefDevToolsMessageObserverCToCpp::CefDevToolsMessageObserverCToCpp() {} + +// DESTRUCTOR - Do not edit by hand. + +CefDevToolsMessageObserverCToCpp::~CefDevToolsMessageObserverCToCpp() { + shutdown_checker::AssertNotShutdown(); +} + +template <> +cef_dev_tools_message_observer_t* CefCToCppRefCounted< + CefDevToolsMessageObserverCToCpp, + CefDevToolsMessageObserver, + cef_dev_tools_message_observer_t>::UnwrapDerived(CefWrapperType type, + CefDevToolsMessageObserver* + c) { + NOTREACHED() << "Unexpected class type: " << type; + return nullptr; +} + +template <> +CefWrapperType + CefCToCppRefCounted::kWrapperType = + WT_DEV_TOOLS_MESSAGE_OBSERVER; diff --git a/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h b/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h new file mode 100644 index 000000000..d793fa5d2 --- /dev/null +++ b/libcef_dll/ctocpp/dev_tools_message_observer_ctocpp.h @@ -0,0 +1,56 @@ +// Copyright (c) 2020 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. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=26b1ef9d351b0ce4a7ca0d61c53a8374ba21168b$ +// + +#ifndef CEF_LIBCEF_DLL_CTOCPP_DEV_TOOLS_MESSAGE_OBSERVER_CTOCPP_H_ +#define CEF_LIBCEF_DLL_CTOCPP_DEV_TOOLS_MESSAGE_OBSERVER_CTOCPP_H_ +#pragma once + +#if !defined(BUILDING_CEF_SHARED) +#error This file can be included DLL-side only +#endif + +#include "include/capi/cef_browser_capi.h" +#include "include/capi/cef_devtools_message_observer_capi.h" +#include "include/cef_browser.h" +#include "include/cef_devtools_message_observer.h" +#include "libcef_dll/ctocpp/ctocpp_ref_counted.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefDevToolsMessageObserverCToCpp + : public CefCToCppRefCounted { + public: + CefDevToolsMessageObserverCToCpp(); + virtual ~CefDevToolsMessageObserverCToCpp(); + + // CefDevToolsMessageObserver methods. + bool OnDevToolsMessage(CefRefPtr browser, + const void* message, + size_t message_size) override; + void OnDevToolsMethodResult(CefRefPtr browser, + int message_id, + bool success, + const void* result, + size_t result_size) override; + void OnDevToolsEvent(CefRefPtr browser, + const CefString& method, + const void* params, + size_t params_size) override; + void OnDevToolsAgentAttached(CefRefPtr browser) override; + void OnDevToolsAgentDetached(CefRefPtr browser) override; +}; + +#endif // CEF_LIBCEF_DLL_CTOCPP_DEV_TOOLS_MESSAGE_OBSERVER_CTOCPP_H_ diff --git a/libcef_dll/ctocpp/request_handler_ctocpp.cc b/libcef_dll/ctocpp/request_handler_ctocpp.cc index e09afbec1..0260f2377 100644 --- a/libcef_dll/ctocpp/request_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/request_handler_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=72e6ec9a5095a99eaa78ff867ca429f8cd28b101$ +// $hash=fdcb73f79369fe74f65f75d54f08aaec0f0e4dbf$ // #include "libcef_dll/ctocpp/request_handler_ctocpp.h" @@ -394,6 +394,27 @@ void CefRequestHandlerCToCpp::OnRenderProcessTerminated( _struct, CefBrowserCppToC::Wrap(browser), status); } +NO_SANITIZE("cfi-icall") +void CefRequestHandlerCToCpp::OnDocumentAvailableInMainFrame( + CefRefPtr browser) { + shutdown_checker::AssertNotShutdown(); + + cef_request_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_document_available_in_main_frame)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_document_available_in_main_frame(_struct, + CefBrowserCppToC::Wrap(browser)); +} + // CONSTRUCTOR - Do not edit by hand. CefRequestHandlerCToCpp::CefRequestHandlerCToCpp() {} diff --git a/libcef_dll/ctocpp/request_handler_ctocpp.h b/libcef_dll/ctocpp/request_handler_ctocpp.h index 220430d6c..8122cb6cd 100644 --- a/libcef_dll/ctocpp/request_handler_ctocpp.h +++ b/libcef_dll/ctocpp/request_handler_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=0d67e24bf6d8079bcdfd8b36ba48543a05a9d2fd$ +// $hash=be2bb80e8816f0eaad34382f05e2bec59dfe7fa7$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_REQUEST_HANDLER_CTOCPP_H_ @@ -82,6 +82,7 @@ class CefRequestHandlerCToCpp void OnRenderViewReady(CefRefPtr browser) override; void OnRenderProcessTerminated(CefRefPtr browser, TerminationStatus status) override; + void OnDocumentAvailableInMainFrame(CefRefPtr browser) override; }; #endif // CEF_LIBCEF_DLL_CTOCPP_REQUEST_HANDLER_CTOCPP_H_ diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index b406af48e..90df59aff 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=fbf992432bd4ff88bed34f803047f70b5b3400f7$ +// $hash=c42cd0225d8e4286df471fcc622f9bbf9ed977d3$ // #include "include/capi/cef_app_capi.h" @@ -589,6 +589,24 @@ CEF_EXPORT struct _cef_value_t* cef_parse_json( return CefValueCppToC::Wrap(_retval); } +CEF_EXPORT struct _cef_value_t* cef_parse_json_buffer( + const void* json, + size_t json_size, + cef_json_parser_options_t options) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: json; type: simple_byaddr + DCHECK(json); + if (!json) + return NULL; + + // Execute + CefRefPtr _retval = CefParseJSON(json, json_size, options); + + // Return type: refptr_same + return CefValueCppToC::Wrap(_retval); +} + CEF_EXPORT struct _cef_value_t* cef_parse_jsonand_return_error( const cef_string_t* json_string, cef_json_parser_options_t options, diff --git a/libcef_dll/wrapper/libcef_dll_dylib.cc b/libcef_dll/wrapper/libcef_dll_dylib.cc index 5b0d5687a..58a257d7c 100644 --- a/libcef_dll/wrapper/libcef_dll_dylib.cc +++ b/libcef_dll/wrapper/libcef_dll_dylib.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=bf82965f02cafae5a1afc80ab0c976436be9712e$ +// $hash=66b734973339eb27399083e978844f7d5a6a6c44$ // #include @@ -139,6 +139,8 @@ typedef cef_string_userfree_t (*cef_uridecode_ptr)(const cef_string_t*, cef_uri_unescape_rule_t); typedef struct _cef_value_t* (*cef_parse_json_ptr)(const cef_string_t*, cef_json_parser_options_t); +typedef struct _cef_value_t* ( + *cef_parse_json_buffer_ptr)(const void*, size_t, cef_json_parser_options_t); typedef struct _cef_value_t* (*cef_parse_jsonand_return_error_ptr)( const cef_string_t*, cef_json_parser_options_t, @@ -547,6 +549,7 @@ struct libcef_pointers { cef_uriencode_ptr cef_uriencode; cef_uridecode_ptr cef_uridecode; cef_parse_json_ptr cef_parse_json; + cef_parse_json_buffer_ptr cef_parse_json_buffer; cef_parse_jsonand_return_error_ptr cef_parse_jsonand_return_error; cef_write_json_ptr cef_write_json; cef_get_path_ptr cef_get_path; @@ -763,6 +766,7 @@ int libcef_init_pointers(const char* path) { INIT_ENTRY(cef_uriencode); INIT_ENTRY(cef_uridecode); INIT_ENTRY(cef_parse_json); + INIT_ENTRY(cef_parse_json_buffer); INIT_ENTRY(cef_parse_jsonand_return_error); INIT_ENTRY(cef_write_json); INIT_ENTRY(cef_get_path); @@ -1134,6 +1138,13 @@ struct _cef_value_t* cef_parse_json(const cef_string_t* json_string, return g_libcef_pointers.cef_parse_json(json_string, options); } +NO_SANITIZE("cfi-icall") +struct _cef_value_t* cef_parse_json_buffer(const void* json, + size_t json_size, + cef_json_parser_options_t options) { + return g_libcef_pointers.cef_parse_json_buffer(json, json_size, options); +} + NO_SANITIZE("cfi-icall") struct _cef_value_t* cef_parse_jsonand_return_error( const cef_string_t* json_string, diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index dd8c60e28..052e3a878 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=c565ea9030eeed15f24fe420b828453a23dbc6ba$ +// $hash=7db8dbe24a2510d9ae0649f1569909711017c064$ // #include "include/capi/cef_app_capi.h" @@ -557,6 +557,24 @@ CEF_GLOBAL CefRefPtr CefParseJSON(const CefString& json_string, return CefValueCToCpp::Wrap(_retval); } +NO_SANITIZE("cfi-icall") +CEF_GLOBAL CefRefPtr CefParseJSON(const void* json, + size_t json_size, + cef_json_parser_options_t options) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: json; type: simple_byaddr + DCHECK(json); + if (!json) + return nullptr; + + // Execute + cef_value_t* _retval = cef_parse_json_buffer(json, json_size, options); + + // Return type: refptr_same + return CefValueCToCpp::Wrap(_retval); +} + NO_SANITIZE("cfi-icall") CEF_GLOBAL CefRefPtr CefParseJSONAndReturnError( const CefString& json_string, diff --git a/libcef_dll/wrapper_types.h b/libcef_dll/wrapper_types.h index 3e806f22a..68cb7882b 100644 --- a/libcef_dll/wrapper_types.h +++ b/libcef_dll/wrapper_types.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=9bfe176dfac4770800e95e2bbc0fafffbf0aeeaf$ +// $hash=20268db646dcf8aa6d54b2342dbc70a6e624e202$ // #ifndef CEF_LIBCEF_DLL_WRAPPER_TYPES_H_ @@ -46,6 +46,7 @@ enum CefWrapperType { WT_DOMNODE, WT_DOMVISITOR, WT_DELETE_COOKIES_CALLBACK, + WT_DEV_TOOLS_MESSAGE_OBSERVER, WT_DIALOG_HANDLER, WT_DICTIONARY_VALUE, WT_DISPLAY, diff --git a/tests/cefclient/browser/client_handler.cc b/tests/cefclient/browser/client_handler.cc index 4644fa2eb..2cf2b3117 100644 --- a/tests/cefclient/browser/client_handler.cc +++ b/tests/cefclient/browser/client_handler.cc @@ -41,6 +41,7 @@ enum client_menu_ids { CLIENT_ID_INSPECT_ELEMENT, CLIENT_ID_SHOW_SSL_INFO, CLIENT_ID_CURSOR_CHANGE_DISABLED, + CLIENT_ID_OFFLINE, CLIENT_ID_TESTMENU_SUBMENU, CLIENT_ID_TESTMENU_CHECKITEM, CLIENT_ID_TESTMENU_RADIOITEM1, @@ -268,6 +269,7 @@ ClientHandler::ClientHandler(Delegate* delegate, CefCommandLine::GetGlobalCommandLine(); mouse_cursor_change_disabled_ = command_line->HasSwitch(switches::kMouseCursorChangeDisabled); + offline_ = command_line->HasSwitch(switches::kOffline); } void ClientHandler::DetachDelegate() { @@ -334,6 +336,11 @@ void ClientHandler::OnBeforeContextMenu(CefRefPtr browser, if (browser->GetHost()->IsMouseCursorChangeDisabled()) model->SetChecked(CLIENT_ID_CURSOR_CHANGE_DISABLED, true); + model->AddSeparator(); + model->AddItem(CLIENT_ID_OFFLINE, "Offline mode"); + if (offline_) + model->SetChecked(CLIENT_ID_OFFLINE, true); + // Test context menu features. BuildTestMenu(model); } @@ -366,6 +373,10 @@ bool ClientHandler::OnContextMenuCommand(CefRefPtr browser, browser->GetHost()->SetMouseCursorChangeDisabled( !browser->GetHost()->IsMouseCursorChangeDisabled()); return true; + case CLIENT_ID_OFFLINE: + offline_ = !offline_; + SetOfflineState(browser, offline_); + return true; default: // Allow default handling, if any. return ExecuteTestMenu(command_id); } @@ -588,6 +599,10 @@ void ClientHandler::OnAfterCreated(CefRefPtr browser) { if (mouse_cursor_change_disabled_) browser->GetHost()->SetMouseCursorChangeDisabled(true); + // Set offline mode if requested via the command-line flag. + if (offline_) + SetOfflineState(browser, true); + if (browser->GetHost()->GetExtension()) { // Browsers hosting extension apps should auto-resize. browser->GetHost()->SetAutoResizeEnabled(true, CefSize(20, 20), @@ -843,6 +858,16 @@ void ClientHandler::OnRenderProcessTerminated(CefRefPtr browser, frame->LoadURL(startup_url_); } +void ClientHandler::OnDocumentAvailableInMainFrame( + CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + // Restore offline mode after main frame navigation. Otherwise, offline state + // (e.g. `navigator.onLine`) might be wrong in the renderer process. + if (offline_) + SetOfflineState(browser, true); +} + cef_return_value_t ClientHandler::OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, @@ -1178,4 +1203,16 @@ bool ClientHandler::ExecuteTestMenu(int command_id) { return false; } +void ClientHandler::SetOfflineState(CefRefPtr browser, + bool offline) { + // See DevTools protocol docs for message format specification. + CefRefPtr params = CefDictionaryValue::Create(); + params->SetBool("offline", offline); + params->SetDouble("latency", 0); + params->SetDouble("downloadThroughput", 0); + params->SetDouble("uploadThroughput", 0); + browser->GetHost()->ExecuteDevToolsMethod( + /*message_id=*/0, "Network.emulateNetworkConditions", params); +} + } // namespace client diff --git a/tests/cefclient/browser/client_handler.h b/tests/cefclient/browser/client_handler.h index 578183c91..599ef7f08 100644 --- a/tests/cefclient/browser/client_handler.h +++ b/tests/cefclient/browser/client_handler.h @@ -256,6 +256,7 @@ class ClientHandler : public CefClient, CefRefPtr callback) OVERRIDE; void OnRenderProcessTerminated(CefRefPtr browser, TerminationStatus status) OVERRIDE; + void OnDocumentAvailableInMainFrame(CefRefPtr browser) OVERRIDE; // CefResourceRequestHandler methods cef_return_value_t OnBeforeResourceLoad( @@ -344,6 +345,8 @@ class ClientHandler : public CefClient, void BuildTestMenu(CefRefPtr model); bool ExecuteTestMenu(int command_id); + void SetOfflineState(CefRefPtr browser, bool offline); + // THREAD SAFE MEMBERS // The following members may be accessed from any thread. @@ -356,6 +359,9 @@ class ClientHandler : public CefClient, // True if mouse cursor change is disabled. bool mouse_cursor_change_disabled_; + // True if the browser is currently offline. + bool offline_; + // True if Favicon images should be downloaded. bool download_favicon_images_; diff --git a/tests/ceftests/devtools_message_unittest.cc b/tests/ceftests/devtools_message_unittest.cc new file mode 100644 index 000000000..13f070eb7 --- /dev/null +++ b/tests/ceftests/devtools_message_unittest.cc @@ -0,0 +1,375 @@ +// Copyright (c) 2020 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 +#include + +#include "include/base/cef_bind.h" +#include "include/cef_callback.h" +#include "include/cef_devtools_message_observer.h" +#include "include/cef_parser.h" +#include "include/wrapper/cef_closure_task.h" + +#include "tests/ceftests/test_handler.h" +#include "tests/gtest/include/gtest/gtest.h" + +namespace { + +const char kTestUrl1[] = "http://tests/DevToolsMessage1"; +const char kTestUrl2[] = "http://tests/DevToolsMessage2"; + +class DevToolsMessageTestHandler : public TestHandler { + public: + DevToolsMessageTestHandler() {} + + void RunTest() override { + // Add HTML resources. + AddResource(kTestUrl1, "Test1", "text/html"); + AddResource(kTestUrl2, "Test2", "text/html"); + + // Create the browser. + CreateBrowser(kTestUrl1); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + void OnAfterCreated(CefRefPtr browser) override { + TestHandler::OnAfterCreated(browser); + + // STEP 1: Add the DevTools observer. Wait for the 1st load. + registration_ = browser->GetHost()->AddDevToolsMessageObserver( + new TestMessageObserver(this)); + EXPECT_TRUE(registration_); + } + + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + if (!isLoading) { + load_ct_++; + if (load_ct_ == 1) { + // STEP 2: 1st load has completed. Now enable page domain notifications + // and wait for the method result. + ExecuteMethod("Page.enable", "", + base::Bind(&DevToolsMessageTestHandler::Navigate, this)); + } else if (load_ct_ == 2) { + MaybeDestroyTest(); + } + } + } + + void DestroyTest() override { + // STEP 7: Remove the DevTools observer. This should result in the observer + // object being destroyed. + EXPECT_TRUE(registration_); + registration_ = nullptr; + EXPECT_TRUE(observer_destroyed_); + + // Each send message variant should be called at least a single time. + EXPECT_GE(method_send_ct_, 1); + EXPECT_GE(method_execute_ct_, 1); + + // All sent messages should receive a result callback. + EXPECT_EQ(expected_method_ct_, method_send_ct_ + method_execute_ct_); + EXPECT_EQ(expected_method_ct_, result_ct_); + EXPECT_EQ(expected_method_ct_, last_result_id_); + + // Every received message should parse successfully to a result or event + // callback. + EXPECT_EQ(message_ct_, result_ct_ + event_ct_); + + // Should receive 1 or more events (probably just 1, but who knows?). + EXPECT_GE(event_ct_, 1); + + // OnLoadingStateChange(isLoading=false) should be called twice. + EXPECT_EQ(expected_load_ct_, load_ct_); + + // Should get callbacks for agent attached but not detached. + EXPECT_EQ(1, attached_ct_); + EXPECT_EQ(0, detached_ct_); + + TestHandler::DestroyTest(); + } + + private: + struct MethodResult { + int message_id; + bool success; + std::string result; + }; + + struct Event { + std::string method; + std::string params; + }; + + class TestMessageObserver : public CefDevToolsMessageObserver { + public: + explicit TestMessageObserver(DevToolsMessageTestHandler* handler) + : handler_(handler) {} + + virtual ~TestMessageObserver() { handler_->observer_destroyed_.yes(); } + + bool OnDevToolsMessage(CefRefPtr browser, + const void* message, + size_t message_size) override { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier()); + handler_->message_ct_++; + return false; + } + + void OnDevToolsMethodResult(CefRefPtr browser, + int message_id, + bool success, + const void* result, + size_t result_size) override { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier()); + handler_->result_ct_++; + + MethodResult mr; + mr.message_id = message_id; + mr.success = success; + if (result) { + // Intentionally truncating at small size. + mr.result = std::string(static_cast(result), + std::min(static_cast(result_size), 80)); + } + handler_->OnMethodResult(mr); + } + + void OnDevToolsEvent(CefRefPtr browser, + const CefString& method, + const void* params, + size_t params_size) override { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier()); + handler_->event_ct_++; + + Event ev; + ev.method = method; + if (params) { + // Intentionally truncating at small size. + ev.params = std::string(static_cast(params), + std::min(static_cast(params_size), 80)); + } + handler_->OnEvent(ev); + } + + void OnDevToolsAgentAttached(CefRefPtr browser) override { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier()); + handler_->attached_ct_++; + } + + void OnDevToolsAgentDetached(CefRefPtr browser) override { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(handler_->GetBrowserId(), browser->GetIdentifier()); + handler_->detached_ct_++; + } + + private: + DevToolsMessageTestHandler* handler_; + + IMPLEMENT_REFCOUNTING(TestMessageObserver); + DISALLOW_COPY_AND_ASSIGN(TestMessageObserver); + }; + + // Execute a DevTools method. Expected results will be verified in + // OnMethodResult, and |next_step| will then be executed. + // |expected_result| can be a fragment that the result should start with. + void ExecuteMethod(const std::string& method, + const std::string& params, + const base::Closure& next_step, + const std::string& expected_result = "{}", + bool expected_success = true) { + CHECK(!method.empty()); + CHECK(!next_step.is_null()); + + int message_id = next_message_id_++; + + std::stringstream message; + message << "{\"id\":" << message_id << ",\"method\":\"" << method << "\""; + if (!params.empty()) { + message << ",\"params\":" << params; + } + message << "}"; + + // Set expected result state. + pending_message_ = message.str(); + pending_result_next_ = next_step; + pending_result_ = {message_id, expected_success, expected_result}; + + if (message_id % 2 == 0) { + // Use the less structured method. + method_send_ct_++; + GetBrowser()->GetHost()->SendDevToolsMessage(pending_message_.data(), + pending_message_.size()); + } else { + // Use the more structured method. + method_execute_ct_++; + CefRefPtr dict; + if (!params.empty()) { + CefRefPtr value = + CefParseJSON(params.data(), params.size(), JSON_PARSER_RFC); + EXPECT_TRUE(value && value->GetType() == VTYPE_DICTIONARY) + << "failed to parse: " << params; + if (value && value->GetType() == VTYPE_DICTIONARY) { + dict = value->GetDictionary(); + } + } + GetBrowser()->GetHost()->ExecuteDevToolsMethod(message_id, method, dict); + } + } + + // Every call to ExecuteMethod should result in a single call to this method + // with the same message_id. + void OnMethodResult(const MethodResult& result) { + EXPECT_EQ(pending_result_.message_id, result.message_id) + << "with message=" << pending_message_; + if (result.message_id != pending_result_.message_id) + return; + + EXPECT_EQ(pending_result_.success, result.success) + << "with message=" << pending_message_; + + EXPECT_TRUE(result.result.find(pending_result_.result) == 0) + << "with message=" << pending_message_ + << "\nand actual result=" << result.result + << "\nand expected result=" << pending_result_.result; + + last_result_id_ = result.message_id; + + // Continue asynchronously to allow the callstack to unwind. + CefPostTask(TID_UI, pending_result_next_); + + // Clear expected result state. + pending_message_.clear(); + pending_result_next_.Reset(); + pending_result_ = {}; + } + + void OnEvent(const Event& event) { + if (event.method != pending_event_.method) + return; + + EXPECT_TRUE(event.params.find(pending_event_.params) == 0) + << "with method=" << event.method + << "\nand actual params=" << event.params + << "\nand expected params=" << pending_event_.params; + + // Continue asynchronously to allow the callstack to unwind. + CefPostTask(TID_UI, pending_event_next_); + + // Clear expected result state. + pending_event_ = {}; + pending_event_next_.Reset(); + } + + void Navigate() { + pending_event_ = {"Page.frameNavigated", "{\"frame\":"}; + pending_event_next_ = + base::Bind(&DevToolsMessageTestHandler::AfterNavigate, this); + + std::stringstream params; + params << "{\"url\":\"" << kTestUrl2 << "\"}"; + + // STEP 3: Page domain notifications are enabled. Now start a new + // navigation (but do nothing on method result) and wait for the + // "Page.frameNavigated" event. + ExecuteMethod("Page.navigate", params.str(), base::Bind(base::DoNothing), + /*expected_result=*/"{\"frameId\":"); + } + + void AfterNavigate() { + // STEP 4: Got the "Page.frameNavigated" event. Now disable page domain + // notifications. + ExecuteMethod( + "Page.disable", "", + base::Bind(&DevToolsMessageTestHandler::AfterPageDisabled, this)); + } + + void AfterPageDisabled() { + // STEP 5: Got the the "Page.disable" method result. Now call a non-existant + // method to verify an error result, and then destroy the test when done. + ExecuteMethod( + "Foo.doesNotExist", "", + base::Bind(&DevToolsMessageTestHandler::MaybeDestroyTest, this), + /*expected_result=*/ + "{\"code\":-32601,\"message\":\"'Foo.doesNotExist' wasn't found\"}", + /*expected_success=*/false); + } + + void MaybeDestroyTest() { + if (result_ct_ == expected_method_ct_ && load_ct_ == expected_load_ct_) { + // STEP 6: Got confirmation of all expected method results and load + // events. Now destroy the test. + DestroyTest(); + } + } + + // Total # of times we're planning to call ExecuteMethod. + const int expected_method_ct_ = 4; + + // Total # of times we're expecting OnLoadingStateChange(isLoading=false) to + // be called. + const int expected_load_ct_ = 2; + + // In ExecuteMethod: + // Next message ID to use. + int next_message_id_ = 1; + // Last message that was sent (used for debug messages only). + std::string pending_message_; + // SendDevToolsMessage call count. + int method_send_ct_ = 0; + // ExecuteDevToolsMethod call count. + int method_execute_ct_ = 0; + + // Expect |pending_result_.message_id| in OnMethodResult. + // The result should start with the |pending_result_.result| fragment. + MethodResult pending_result_; + // Tracks the last message ID received. + int last_result_id_ = -1; + // When received, execute this callback. + base::Closure pending_result_next_; + + // Wait for |pending_event_.method| in OnEvent. + // The params should start with the |pending_event_.params| fragment. + Event pending_event_; + // When received, execute this callback. + base::Closure pending_event_next_; + + CefRefPtr registration_; + + // Observer callback count. + int message_ct_ = 0; + int result_ct_ = 0; + int event_ct_ = 0; + int attached_ct_ = 0; + int detached_ct_ = 0; + + // OnLoadingStateChange(isLoading=false) count. + int load_ct_ = 0; + + TrackCallback observer_destroyed_; + + IMPLEMENT_REFCOUNTING(DevToolsMessageTestHandler); + DISALLOW_COPY_AND_ASSIGN(DevToolsMessageTestHandler); +}; + +} // namespace + +// Test everything related to DevTools messages: +// - CefDevToolsMessageObserver registration and life span. +// - SendDevToolsMessage/ExecuteDevToolsMethod calls. +// - CefDevToolsMessageObserver callbacks for method results and events. +TEST(DevToolsMessageTest, Messages) { + CefRefPtr handler = + new DevToolsMessageTestHandler(); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} diff --git a/tests/shared/common/client_switches.cc b/tests/shared/common/client_switches.cc index dab1d4c4b..9feef7cc2 100644 --- a/tests/shared/common/client_switches.cc +++ b/tests/shared/common/client_switches.cc @@ -28,6 +28,7 @@ const char kShowUpdateRect[] = "show-update-rect"; const char kSharedTextureEnabled[] = "shared-texture-enabled"; const char kExternalBeginFrameEnabled[] = "external-begin-frame-enabled"; const char kMouseCursorChangeDisabled[] = "mouse-cursor-change-disabled"; +const char kOffline[] = "offline"; const char kRequestContextPerBrowser[] = "request-context-per-browser"; const char kRequestContextSharedCache[] = "request-context-shared-cache"; const char kBackgroundColor[] = "background-color"; diff --git a/tests/shared/common/client_switches.h b/tests/shared/common/client_switches.h index 2c2cebac2..0ff5938c4 100644 --- a/tests/shared/common/client_switches.h +++ b/tests/shared/common/client_switches.h @@ -22,6 +22,7 @@ extern const char kShowUpdateRect[]; extern const char kSharedTextureEnabled[]; extern const char kExternalBeginFrameEnabled[]; extern const char kMouseCursorChangeDisabled[]; +extern const char kOffline[]; extern const char kRequestContextPerBrowser[]; extern const char kRequestContextSharedCache[]; extern const char kBackgroundColor[]; diff --git a/tools/gn_args.py b/tools/gn_args.py index f4ce27194..22e22cd52 100644 --- a/tools/gn_args.py +++ b/tools/gn_args.py @@ -266,10 +266,6 @@ def GetRequiredArgs(): # can't be enforced by assert(). result['enable_linux_installer'] = False - # Use system fontconfig. This avoids a startup hang on Ubuntu 16.04.4 (see - # issue #2424). - result['use_bundled_fontconfig'] = False - # Build without GTK dependencies (see issue #2014). result['use_gtk'] = False