Commit f23aead0 authored by Maksim Ivanov's avatar Maksim Ivanov Committed by Commit Bot

Implement diagnostics daemon starting talking to extensions

The extension should use the chrome.runtime.onConnectNative event
in order to listen for the messages from the daemon when the daemon
starts the communication itself. The application string passed to
the event will be 'com.google.wilco_dtc'.

The hardcoded whitelist for extensions that receive this event is
currently empty - it will have to be updated once we figure out
extension IDs that we'll use.

BUG=chromium:907932,b:123926112
TEST=manual

Change-Id: Ic5db3817ccc757bcf6bef4e4f800623d02e11660
Reviewed-on: https://chromium-review.googlesource.com/c/1464579Reviewed-by: default avatarMattias Nissler <mnissler@chromium.org>
Reviewed-by: default avatarPolina Bondarenko <pbond@chromium.org>
Commit-Queue: Maksim Ivanov <emaxx@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636864}
parent bd8a5583
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/process/process_handle.h" #include "base/process/process_handle.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.h"
#include "chrome/browser/chromeos/diagnosticsd/mojo_utils.h" #include "chrome/browser/chromeos/diagnosticsd/mojo_utils.h"
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/diagnosticsd_client.h" #include "chromeos/dbus/diagnosticsd_client.h"
...@@ -281,4 +282,35 @@ void DiagnosticsdBridge::PerformWebRequest( ...@@ -281,4 +282,35 @@ void DiagnosticsdBridge::PerformWebRequest(
std::move(request_body_content), std::move(callback)); std::move(request_body_content), std::move(callback));
} }
void DiagnosticsdBridge::SendDiagnosticsProcessorMessageToUi(
mojo::ScopedHandle json_message,
SendDiagnosticsProcessorMessageToUiCallback callback) {
// Extract the string value of the received message.
DCHECK(json_message);
std::unique_ptr<base::SharedMemory> json_message_shared_memory;
base::StringPiece json_message_string = GetStringPieceFromMojoHandle(
std::move(json_message), &json_message_shared_memory);
if (json_message_string.empty()) {
LOG(ERROR) << "Failed to read data from mojo handle";
std::move(callback).Run(mojo::ScopedHandle() /* response_json_message */);
return;
}
DeliverDiagnosticsdUiMessageToExtensions(
json_message_string.as_string(),
base::BindOnce(
[](SendDiagnosticsProcessorMessageToUiCallback callback,
const std::string& response) {
mojo::ScopedHandle response_mojo_handle;
if (!response.empty()) {
response_mojo_handle =
CreateReadOnlySharedMemoryMojoHandle(response);
if (!response_mojo_handle)
LOG(ERROR) << "Failed to create mojo handle for string";
}
std::move(callback).Run(std::move(response_mojo_handle));
},
std::move(callback)));
}
} // namespace chromeos } // namespace chromeos
...@@ -98,6 +98,9 @@ class DiagnosticsdBridge final ...@@ -98,6 +98,9 @@ class DiagnosticsdBridge final
std::vector<mojo::ScopedHandle> headers, std::vector<mojo::ScopedHandle> headers,
mojo::ScopedHandle request_body, mojo::ScopedHandle request_body,
PerformWebRequestCallback callback) override; PerformWebRequestCallback callback) override;
void SendDiagnosticsProcessorMessageToUi(
mojo::ScopedHandle json_message,
SendDiagnosticsProcessorMessageToUiCallback callback) override;
std::unique_ptr<Delegate> delegate_; std::unique_ptr<Delegate> delegate_;
......
...@@ -4,20 +4,34 @@ ...@@ -4,20 +4,34 @@
#include "chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.h" #include "chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.h"
#include <string> #include <algorithm>
#include <utility> #include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/shared_memory.h" #include "base/memory/shared_memory.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/unguessable_token.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/diagnosticsd/diagnosticsd_bridge.h" #include "chrome/browser/chromeos/diagnosticsd/diagnosticsd_bridge.h"
#include "chrome/browser/chromeos/diagnosticsd/mojo_utils.h" #include "chrome/browser/chromeos/diagnosticsd/mojo_utils.h"
#include "chrome/browser/extensions/api/messaging/native_message_port.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/services/diagnosticsd/public/mojom/diagnosticsd.mojom.h" #include "chrome/services/diagnosticsd/public/mojom/diagnosticsd.mojom.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/browser/api/messaging/native_message_host.h" #include "extensions/browser/api/messaging/native_message_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/extension.h"
#include "mojo/public/cpp/system/handle.h" #include "mojo/public/cpp/system/handle.h"
#include "url/gurl.h"
class Profile;
namespace chromeos { namespace chromeos {
...@@ -37,6 +51,16 @@ const int kDiagnosticsdUiMessageMaxSize = 1000000; ...@@ -37,6 +51,16 @@ const int kDiagnosticsdUiMessageMaxSize = 1000000;
namespace { namespace {
// List of extension IDs that will receive UI messages from the
// diagnostics_processor through the extensions native messaging system.
//
// Note: the list must be kept in sync with the allowed origins list in
// src/chrome/browser/extensions/api/messaging/native_message_host_chromeos.cc.
//
// TODO(crbug.com/907932,b/123926112): Populate the list once extension IDs are
// determined.
const char* const kAllowedExtensionIds[] = {};
// Extensions native message host implementation that is used when an // Extensions native message host implementation that is used when an
// extension requests a message channel to the diagnostics_processor daemon. // extension requests a message channel to the diagnostics_processor daemon.
// //
...@@ -181,6 +205,151 @@ class DiagnosticsdExtensionOwnedMessageHost final ...@@ -181,6 +205,151 @@ class DiagnosticsdExtensionOwnedMessageHost final
DISALLOW_COPY_AND_ASSIGN(DiagnosticsdExtensionOwnedMessageHost); DISALLOW_COPY_AND_ASSIGN(DiagnosticsdExtensionOwnedMessageHost);
}; };
// Extensions native message host implementation that is used when the
// diagnostics_processor daemon sends (via the diagnosticsd daemon) a message to
// the extension.
//
// A new instance of this class should be created for each instance of the
// extension(s) that are allowed to receive messages from the
// diagnostics_processor daemon. Once the extension responds by posting a
// message back to this message channel, |send_response_callback| will be
// called.
class DiagnosticsdDaemonOwnedMessageHost final
: public extensions::NativeMessageHost {
public:
DiagnosticsdDaemonOwnedMessageHost(
const std::string& json_message_to_send,
base::OnceCallback<void(const std::string& response)>
send_response_callback)
: json_message_to_send_(json_message_to_send),
send_response_callback_(std::move(send_response_callback)) {
DCHECK(send_response_callback_);
}
~DiagnosticsdDaemonOwnedMessageHost() override {
if (send_response_callback_) {
// If no response was received from the extension, pass the empty result
// to the callback to signal the error.
std::move(send_response_callback_).Run(std::string() /* response */);
}
}
// extensions::NativeMessageHost:
void Start(Client* client) override {
DCHECK(!client_);
client_ = client;
client_->PostMessageFromNativeHost(json_message_to_send_);
}
void OnMessage(const std::string& request_string) override {
DCHECK(client_);
if (!send_response_callback_) {
// This happens when the extension sent more than one message via the
// message channel, which is not supported in our case - therefore simply
// discard these extra messages.
return;
}
if (request_string.size() > kDiagnosticsdUiMessageMaxSize) {
std::move(send_response_callback_).Run(std::string() /* response */);
client_->CloseChannel(kDiagnosticsdUiMessageTooBigExtensionsError);
return;
}
std::move(send_response_callback_).Run(request_string /* response */);
client_->CloseChannel(std::string() /* error_message */);
}
scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override {
return task_runner_;
}
private:
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
base::ThreadTaskRunnerHandle::Get();
const std::string json_message_to_send_;
base::OnceCallback<void(const std::string& response)> send_response_callback_;
// Unowned.
Client* client_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(DiagnosticsdDaemonOwnedMessageHost);
};
// Helper that wraps the specified OnceCallback and encapsulates logic that
// executes it once either of the following becomes true (whichever happens to
// be earlier):
// * Non-empty data was provided to this class via the ProcessResponse() method;
// * The ProcessResponse() method has been called the |wrapper_callback_count|
// number of times.
class FirstNonEmptyMessageCallbackWrapper final {
public:
FirstNonEmptyMessageCallbackWrapper(
base::OnceCallback<void(const std::string& response)> original_callback,
int wrapper_callback_count)
: original_callback_(std::move(original_callback)),
pending_callback_count_(wrapper_callback_count) {
DCHECK(original_callback_);
DCHECK_GE(pending_callback_count_, 0);
if (!pending_callback_count_)
std::move(original_callback_).Run(std::string() /* response */);
}
~FirstNonEmptyMessageCallbackWrapper() {
if (original_callback_) {
// Not all responses were received before this instance is destroyed, so
// run the callback with an error result here.
std::move(original_callback_).Run(std::string() /* response */);
}
}
void ProcessResponse(const std::string& response) {
if (!original_callback_) {
// The response was already passed in one of the previous invocations.
return;
}
if (!response.empty()) {
std::move(original_callback_).Run(response);
return;
}
--pending_callback_count_;
DCHECK_GE(pending_callback_count_, 0);
if (pending_callback_count_ == 0) {
// This is the last response and all responses have been empty, so pass
// the empty response.
std::move(original_callback_).Run(std::string() /* response */);
return;
}
}
private:
base::OnceCallback<void(const std::string& response)> original_callback_;
int pending_callback_count_;
DISALLOW_COPY_AND_ASSIGN(FirstNonEmptyMessageCallbackWrapper);
};
void DeliverMessageToExtension(
Profile* profile,
const std::string& extension_id,
const std::string& json_message,
base::OnceCallback<void(const std::string& response)>
send_response_callback) {
const extensions::PortId port_id(base::UnguessableToken::Create(),
1 /* port_number */, true /* is_opener */);
extensions::MessageService* const message_service =
extensions::MessageService::Get(profile);
auto native_message_host =
std::make_unique<DiagnosticsdDaemonOwnedMessageHost>(
json_message, std::move(send_response_callback));
auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
message_service->GetChannelDelegate(), port_id,
std::move(native_message_host));
message_service->OpenChannelToExtension(
-1 /* source_process_id */, -1 /* source_routing_id */, port_id,
extensions::MessagingEndpoint::ForNativeApp(kDiagnosticsdUiMessageHost),
std::move(native_message_port), extension_id, GURL(),
std::string() /* channel_name */);
}
} // namespace } // namespace
std::unique_ptr<extensions::NativeMessageHost> std::unique_ptr<extensions::NativeMessageHost>
...@@ -188,4 +357,46 @@ CreateExtensionOwnedDiagnosticsdMessageHost() { ...@@ -188,4 +357,46 @@ CreateExtensionOwnedDiagnosticsdMessageHost() {
return std::make_unique<DiagnosticsdExtensionOwnedMessageHost>(); return std::make_unique<DiagnosticsdExtensionOwnedMessageHost>();
} }
void DeliverDiagnosticsdUiMessageToExtensions(
const std::string& json_message,
base::OnceCallback<void(const std::string& response)>
send_response_callback) {
if (json_message.size() > kDiagnosticsdUiMessageMaxSize) {
VLOG(1) << "Message received from the daemon is too big";
return;
}
// Determine beforehand which extension instances should receive the event, in
// order to be able to construct the wrapper callback with the needed counter.
std::vector<std::pair<Profile*, std::string>> recipient_extensions;
for (auto* profile :
g_browser_process->profile_manager()->GetLoadedProfiles()) {
for (const auto* extension_id : kAllowedExtensionIds) {
if (extensions::ExtensionRegistry::Get(profile)
->enabled_extensions()
.GetByID(extension_id)) {
recipient_extensions.emplace_back(profile, extension_id);
}
}
}
// Build the wrapper callback in order to call |send_response_callback| once
// when:
// * either the first non-empty response is received from one of the
// extensions;
// * or requests to all extensions completed with no response.
base::RepeatingCallback<void(const std::string& response)>
first_non_empty_message_forwarding_callback = base::BindRepeating(
&FirstNonEmptyMessageCallbackWrapper::ProcessResponse,
base::Owned(new FirstNonEmptyMessageCallbackWrapper(
std::move(send_response_callback),
static_cast<int>(recipient_extensions.size()))));
for (const auto& profile_and_extension : recipient_extensions) {
DeliverMessageToExtension(profile_and_extension.first,
profile_and_extension.second, json_message,
first_non_empty_message_forwarding_callback);
}
}
} // namespace chromeos } // namespace chromeos
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
#define CHROME_BROWSER_CHROMEOS_DIAGNOSTICSD_DIAGNOSTICSD_MESSAGING_H_ #define CHROME_BROWSER_CHROMEOS_DIAGNOSTICSD_DIAGNOSTICSD_MESSAGING_H_
#include <memory> #include <memory>
#include <string>
#include "base/callback_forward.h"
namespace extensions { namespace extensions {
class NativeMessageHost; class NativeMessageHost;
...@@ -26,6 +29,16 @@ extern const int kDiagnosticsdUiMessageMaxSize; ...@@ -26,6 +29,16 @@ extern const int kDiagnosticsdUiMessageMaxSize;
std::unique_ptr<extensions::NativeMessageHost> std::unique_ptr<extensions::NativeMessageHost>
CreateExtensionOwnedDiagnosticsdMessageHost(); CreateExtensionOwnedDiagnosticsdMessageHost();
// Delivers the UI message |json_message| from the diagnostics_processor daemon
// to the extensions that are allowed to receive it. The delivery is done via
// creating extensions native message hosts. |send_response_callback| will be
// called with the response from the extension (the first non-empty one in case
// of multiple extensions providing some responses).
void DeliverDiagnosticsdUiMessageToExtensions(
const std::string& json_message,
base::OnceCallback<void(const std::string& response)>
send_response_callback);
} // namespace chromeos } // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_DIAGNOSTICSD_DIAGNOSTICSD_MESSAGING_H_ #endif // CHROME_BROWSER_CHROMEOS_DIAGNOSTICSD_DIAGNOSTICSD_MESSAGING_H_
...@@ -89,4 +89,27 @@ interface DiagnosticsdClient { ...@@ -89,4 +89,27 @@ interface DiagnosticsdClient {
handle? request_body) handle? request_body)
=> (DiagnosticsdWebRequestStatus status, int32 http_status, => (DiagnosticsdWebRequestStatus status, int32 http_status,
handle? response_body); handle? response_body);
// Sends a message, originating from the diagnostics_processor daemon, to the
// diagnostics UI extension (hosted by the browser). The message contents are
// serialized JSON. Delivery of the message is not guaranteed (for example, if
// no user is currently logged in that has the diagnostics UI extension
// installed).
//
// NOTE: the size of the |json_message| must not exceed 1 MB (1'000'000
// bytes).
//
// The response will contain the JSON message returned by the extension. The
// response handle will be unset if the request wasn't delivered to the
// extension or the extension made no reply. The response is guaranteed to not
// exceed 1 MB (1'000'000 bytes).
//
// NOTE: Both request and response messages are opaque to Chrome and not
// interpreted by it in any way (except for the JSON validity verification
// of the input message that happens in the target extension's renderer
// process). This method simply transmits data between two endpoints that are
// implemented by third parties (the diagnostics UI extension and the
// diagnostics_processor daemon).
SendDiagnosticsProcessorMessageToUi@1(handle json_message)
=> (handle? response_json_message);
}; };
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment