mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Add support for direct DevTools protocol messaging (fixes issue #2961).
This change allows the client to directly send and receive DevTools protocol messages (send method calls, and receive method results and events) without requiring a DevTools front-end or remote-debugging session. This change includes additional supporting changes: - Add a new CefRequestHandler::OnDocumentAvailableInMainFrame callback (see issue #1454). - Add a CefParseJSON variant that accepts a UTF8-encoded buffer. - Add a `--devtools-protocol-log-file=<path>` command-line flag for logging protocol messages sent to/from the DevTools front-end while it is displayed. This is useful for understanding existing DevTools protocol usage. - Add a new "libcef_static_unittests" executable target to support light-weight unit tests of libcef_static internals (e.g. without requiring exposure via the CEF API). Files to be unittested are placed in the new "libcef_static_unittested" source_set which is then included by both the existing libcef_static library and the new unittests executable target. - Linux: Remove use_bundled_fontconfig=false, which is no longer required and causes unittest build errors (see issue #2424). This change also adds a cefclient demo for configuring offline mode using the DevTools protocol (fixes issue #245). This is controlled by the "Offline mode" context menu option and the `--offline` command-line switch which will launch cefclient in offline mode. When cefclient is offline all network requests will fail with ERR_INTERNET_DISCONNECTED and navigator.onLine will return false when called from JavaScript in any frame. This mode is per-browser so newly created browser windows will have the default mode. Note that configuring offline mode in this way will not update the Network tab UI ("Throtting" option) in a displayed DevTools front-end instance.
This commit is contained in:
@@ -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> 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<CefBrowser> CefBrowserHostImpl::GetBrowser() {
|
||||
@@ -906,39 +885,29 @@ void CefBrowserHostImpl::ShowDevTools(const CefWindowInfo& windowInfo,
|
||||
CefRefPtr<CefClient> 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<const char*>(message), message_size);
|
||||
CEF_POST_TASK(
|
||||
CEF_UIT,
|
||||
base::BindOnce(
|
||||
[](CefRefPtr<CefBrowserHostImpl> self, std::string message_str) {
|
||||
self->SendDevToolsMessage(message_str.data(), message_str.size());
|
||||
},
|
||||
CefRefPtr<CefBrowserHostImpl>(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<CefDictionaryValue> 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<CefRegistration> CefBrowserHostImpl::AddDevToolsMessageObserver(
|
||||
CefRefPtr<CefDevToolsMessageObserver> 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<CefRegistration> 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<CefRequestHandler> 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_) {
|
||||
|
@@ -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<CefDictionaryValue> params) override;
|
||||
CefRefPtr<CefRegistration> AddDevToolsMessageObserver(
|
||||
CefRefPtr<CefDevToolsMessageObserver> observer) override;
|
||||
void GetNavigationEntries(CefRefPtr<CefNavigationEntryVisitor> visitor,
|
||||
bool current_only) override;
|
||||
void SetMouseCursorChangeDisabled(bool disabled) override;
|
||||
@@ -523,8 +529,6 @@ class CefBrowserHostImpl : public CefBrowserHost,
|
||||
std::unique_ptr<NavigationLock> CreateNavigationLock();
|
||||
|
||||
private:
|
||||
class DevToolsWebContentsObserver;
|
||||
|
||||
static CefRefPtr<CefBrowserHostImpl> CreateInternal(
|
||||
const CefBrowserSettings& settings,
|
||||
CefRefPtr<CefClient> 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<CefRegistration> registration);
|
||||
|
||||
CefBrowserSettings settings_;
|
||||
CefRefPtr<CefClient> client_;
|
||||
scoped_refptr<CefBrowserInfo> browser_info_;
|
||||
@@ -599,7 +605,7 @@ class CefBrowserHostImpl : public CefBrowserHost,
|
||||
std::unique_ptr<CefBrowserPlatformDelegate> 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<CefFrameHostImpl> 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<content::NotificationRegistrar> registrar_;
|
||||
@@ -655,12 +661,8 @@ class CefBrowserHostImpl : public CefBrowserHost,
|
||||
// Used for creating and managing context menus.
|
||||
std::unique_ptr<CefMenuManager> menu_manager_;
|
||||
|
||||
// Track the lifespan of the frontend WebContents associated with this
|
||||
// browser.
|
||||
std::unique_ptr<DevToolsWebContentsObserver> 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<CefDevToolsManager> devtools_manager_;
|
||||
|
||||
// Observers that want to be notified of changes to this object.
|
||||
base::ObserverList<Observer>::Unchecked observers_;
|
||||
|
137
libcef/browser/devtools/devtools_controller.cc
Normal file
137
libcef/browser/devtools/devtools_controller.cc
Normal file
@@ -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<const uint8_t> message) {
|
||||
if (!observers_.might_have_observers())
|
||||
return;
|
||||
|
||||
base::StringPiece str_message(reinterpret_cast<const char*>(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_;
|
||||
}
|
79
libcef/browser/devtools/devtools_controller.h
Normal file
79
libcef/browser/devtools/devtools_controller.h
Normal file
@@ -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 <memory>
|
||||
|
||||
#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<CefDevToolsController> 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<const uint8_t> message) override;
|
||||
|
||||
bool EnsureAgentHost();
|
||||
|
||||
content::WebContents* const inspected_contents_;
|
||||
scoped_refptr<content::DevToolsAgentHost> agent_host_;
|
||||
int next_message_id_ = 1;
|
||||
|
||||
base::ObserverList<Observer> observers_;
|
||||
|
||||
base::WeakPtrFactory<CefDevToolsController> weak_ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefDevToolsController);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_CONTROLLER_H_
|
@@ -6,13 +6,18 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <utility>
|
||||
|
||||
#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 <windows.h>
|
||||
#elif defined(OS_POSIX)
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -86,6 +98,71 @@ std::unique_ptr<base::DictionaryValue> 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<CefBrowserHostImpl> inspected_browser,
|
||||
CefBrowserHostImpl* inspected_browser,
|
||||
const CefWindowInfo& windowInfo,
|
||||
CefRefPtr<CefClient> 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<CefBrowserHostImpl*>(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<CefBrowserHostImpl> 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<const uint8_t> message) {
|
||||
base::StringPiece str_message(reinterpret_cast<const char*>(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());
|
||||
|
@@ -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<CefBrowserHostImpl> inspected_browser,
|
||||
CefBrowserHostImpl* inspected_browser,
|
||||
const CefWindowInfo& windowInfo,
|
||||
CefRefPtr<CefClient> 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<CefBrowserHostImpl> frontend_browser() const {
|
||||
return frontend_browser_;
|
||||
}
|
||||
|
||||
private:
|
||||
CefDevToolsFrontend(CefRefPtr<CefBrowserHostImpl> 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<CefBrowserHostImpl> frontend_browser_;
|
||||
content::WebContents* inspected_contents_;
|
||||
scoped_refptr<content::DevToolsAgentHost> agent_host_;
|
||||
CefPoint inspect_element_at_;
|
||||
base::OnceClosure frontend_destroyed_callback_;
|
||||
std::unique_ptr<content::DevToolsFrontendHost> frontend_host_;
|
||||
|
||||
class NetworkResourceLoader;
|
||||
@@ -93,6 +101,9 @@ class CefDevToolsFrontend : public content::WebContentsObserver,
|
||||
using ExtensionsAPIs = std::map<std::string, std::string>;
|
||||
ExtensionsAPIs extensions_api_;
|
||||
CefDevToolsFileManager file_manager_;
|
||||
|
||||
const base::FilePath protocol_log_file_;
|
||||
|
||||
base::WeakPtrFactory<CefDevToolsFrontend> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefDevToolsFrontend);
|
||||
|
200
libcef/browser/devtools/devtools_manager.cc
Normal file
200
libcef/browser/devtools/devtools_manager.cc
Normal file
@@ -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<CefDevToolsMessageObserver> 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<CefDevToolsController> 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<CefDevToolsMessageObserver> observer_;
|
||||
|
||||
CefBrowserHostImpl* browser_ = nullptr;
|
||||
base::WeakPtr<CefDevToolsController> 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<CefClient> 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<const char*>(message), message_size));
|
||||
}
|
||||
|
||||
int CefDevToolsManager::ExecuteDevToolsMethod(
|
||||
int message_id,
|
||||
const CefString& method,
|
||||
CefRefPtr<CefDictionaryValue> params) {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (method.empty())
|
||||
return 0;
|
||||
|
||||
if (!EnsureController())
|
||||
return 0;
|
||||
|
||||
if (params && params->IsValid()) {
|
||||
CefDictionaryValueImpl* impl =
|
||||
static_cast<CefDictionaryValueImpl*>(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<CefRegistration> CefDevToolsManager::CreateRegistration(
|
||||
CefRefPtr<CefDevToolsMessageObserver> observer) {
|
||||
DCHECK(observer);
|
||||
return new CefDevToolsRegistrationImpl(observer);
|
||||
}
|
||||
|
||||
void CefDevToolsManager::InitializeRegistrationOnUIThread(
|
||||
CefRefPtr<CefRegistration> registration) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
if (!EnsureController())
|
||||
return;
|
||||
|
||||
static_cast<CefDevToolsRegistrationImpl*>(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;
|
||||
}
|
69
libcef/browser/devtools/devtools_manager.h
Normal file
69
libcef/browser/devtools/devtools_manager.h
Normal file
@@ -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<CefClient> 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<CefDictionaryValue> 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<CefRegistration> CreateRegistration(
|
||||
CefRefPtr<CefDevToolsMessageObserver> observer);
|
||||
void InitializeRegistrationOnUIThread(
|
||||
CefRefPtr<CefRegistration> 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<CefDevToolsController> devtools_controller_;
|
||||
|
||||
base::WeakPtrFactory<CefDevToolsManager> weak_ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CefDevToolsManager);
|
||||
};
|
||||
|
||||
#endif // CEF_LIBCEF_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_H_
|
131
libcef/browser/devtools/devtools_util.cc
Normal file
131
libcef/browser/devtools/devtools_util.cc
Normal file
@@ -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
|
72
libcef/browser/devtools/devtools_util.h
Normal file
72
libcef/browser/devtools/devtools_util.h
Normal file
@@ -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_
|
204
libcef/browser/devtools/devtools_util_unittest.cc
Normal file
204
libcef/browser/devtools/devtools_util_unittest.cc
Normal file
@@ -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());
|
||||
}
|
@@ -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.
|
||||
|
@@ -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[];
|
||||
|
@@ -34,8 +34,17 @@ int GetJSONWriterOptions(cef_json_writer_options_t options) {
|
||||
CefRefPtr<CefValue> CefParseJSON(const CefString& json_string,
|
||||
cef_json_parser_options_t options) {
|
||||
const std::string& json = json_string.ToString();
|
||||
base::Optional<base::Value> parse_result =
|
||||
base::JSONReader::Read(json, GetJSONReaderOptions(options));
|
||||
return CefParseJSON(json.data(), json.size(), options);
|
||||
}
|
||||
|
||||
CefRefPtr<CefValue> CefParseJSON(const void* json,
|
||||
size_t json_size,
|
||||
cef_json_parser_options_t options) {
|
||||
if (!json || json_size == 0)
|
||||
return nullptr;
|
||||
base::Optional<base::Value> parse_result = base::JSONReader::Read(
|
||||
base::StringPiece(static_cast<const char*>(json), json_size),
|
||||
GetJSONReaderOptions(options));
|
||||
if (parse_result) {
|
||||
return new CefValueImpl(
|
||||
base::Value::ToUniquePtrValue(std::move(parse_result.value()))
|
||||
|
Reference in New Issue
Block a user