Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reapply "[RUN-2863] finish devtools plumbing (#1032)" (#1078) #1132

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,26 @@ void RecordReplayAuthTokenService::BindAuthTokenStore(

void RecordReplayAuthTokenService::SetToken(const std::string& token) {
token_ = token;
// TODO persist the token to browser prefs
NotifyObservers();
}

void RecordReplayAuthTokenService::ClearToken() {
token_.clear(); // not sure about this.. should we be sending a null?
NotifyObservers();
}

void RecordReplayAuthTokenService::SetUser(const std::string& user) {
// TODO persist the user to browser prefs
}

void RecordReplayAuthTokenService::ClearUser() {
// TODO clear the user from browser prefs
}

void RecordReplayAuthTokenService::Login() {
}

void RecordReplayAuthTokenService::AddObserver(mojo::PendingRemote<mojom::RecordReplayAuthTokenStoreObserver> observer) {
observers_.Add(std::move(observer));
NotifyObservers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class RecordReplayAuthTokenService : public KeyedService, public mojom::RecordRe

// mojom::RecordReplayAuthTokenStore:
void SetToken(const std::string& token) override;
void ClearToken() override;

void SetUser(const std::string& user) override;
void ClearUser() override;

void Login() override;

void AddObserver(mojo::PendingRemote<mojom::RecordReplayAuthTokenStoreObserver> observer) override;

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ module auth_token.mojom;
// Interface to passing updated token around
interface RecordReplayAuthTokenStore {
SetToken(string token);
ClearToken();

SetUser (string user);
ClearUser();

// Opens an external browser to log the user in
Login();

AddObserver(pending_remote<RecordReplayAuthTokenStoreObserver> observer);
// how do we remove?
Expand Down
6 changes: 4 additions & 2 deletions third_party/blink/renderer/bindings/bindings.gni
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ blink_core_sources_bindings =
"core/v8/observable_array_exotic_object_impl.h",
"core/v8/profiler_trace_builder.cc",
"core/v8/profiler_trace_builder.h",
"core/v8/record_replay_interface.cc",
"core/v8/record_replay_interface.h",
"core/v8/record_replay_devtools_event_listener.cc",
"core/v8/record_replay_devtools_event_listener.h",
"core/v8/record_replay_events.cc",
"core/v8/record_replay_events.h",
"core/v8/record_replay_interface.cc",
"core/v8/record_replay_interface.h",
"core/v8/record_replay_network.cc",
"core/v8/record_replay_network.h",
"core/v8/referrer_script_info.cc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "base/metrics/single_sample_metrics.h"
#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/record_replay_devtools_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/record_replay_interface.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
Expand Down Expand Up @@ -268,17 +269,17 @@ void LocalWindowProxy::Initialize() {

// Add an event listener for the dispatched custom event the devtools uses to register
// its listener. Do this outside the recording.
SetupRecordReplayEventListener();
SetupRecordReplayDevtoolsEventListener();

if (World().IsMainWorld()) {
GetFrame()->Loader().DispatchDidClearWindowObjectInMainWorld();
}
}

void LocalWindowProxy::SetupRecordReplayEventListener() {
void LocalWindowProxy::SetupRecordReplayDevtoolsEventListener() {
LocalFrame* localFrame = GetFrame();

record_replay_listener_ = RecordReplayEventListener::Create(GetIsolate(), localFrame);
record_replay_listener_ = RecordReplayDevtoolsEventListener::Create(GetIsolate(), localFrame);

bool added = localFrame->DomWindow()->addEventListener("WebChannelMessageToChrome", record_replay_listener_.Get());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace blink {
class HTMLDocument;
class ScriptState;
class SecurityOrigin;
class RecordReplayEventListener;
class RecordReplayDevtoolsEventListener;

// Subclass of WindowProxy that only handles LocalFrame.
class LocalWindowProxy final : public WindowProxy {
Expand All @@ -73,7 +73,7 @@ class LocalWindowProxy final : public WindowProxy {
v8::Context::AbortScriptExecutionCallback callback);

private:
void SetupRecordReplayEventListener();
void SetupRecordReplayDevtoolsEventListener();

// LocalWindowProxy overrides:
bool IsLocal() const override { return true; }
Expand Down Expand Up @@ -116,7 +116,7 @@ class LocalWindowProxy final : public WindowProxy {

mojo::Remote<auth_token::mojom::RecordReplayAuthTokenStore> auth_token_store_;

Member<RecordReplayEventListener> record_replay_listener_;
Member<RecordReplayDevtoolsEventListener> record_replay_listener_;
Member<ScriptState> script_state_;
bool context_was_created_from_snapshot_ = false;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2021 Record Replay Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/bindings/core/v8/record_replay_devtools_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/events/custom_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"

namespace blink {

static v8::Local<v8::String> ToV8String(v8::Isolate* isolate, const char* value) {
return v8::String::NewFromUtf8(isolate, value,
v8::NewStringType::kInternalized).ToLocalChecked();
}
static const std::string V8ToString(v8::Isolate* isolate, v8::Local<v8::Value> str) {
v8::String::Utf8Value s(isolate, str);
return *s;
}

static bool GetStringProperty(v8::Local<v8::Context> context, v8::Local<v8::Object> obj, const char* name, v8::Local<v8::String>* out) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::String> v8Name = ToV8String(isolate, name);
v8::Local<v8::Value> v8Value = obj->Get(context, v8Name).ToLocalChecked();

return v8Value->ToString(context).ToLocal(out);
}

static bool GetObjectProperty(v8::Local<v8::Context> context, v8::Local<v8::Object> obj, const char* name, v8::Local<v8::Object>* out) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::String> v8Name = ToV8String(isolate, name);
v8::Local<v8::Value> v8Value = obj->Get(context, v8Name).ToLocalChecked();

return v8Value->ToObject(context).ToLocal(out);
}

static bool StringEquals(v8::Isolate* isolate, v8::Local<v8::String> str1, const char* str2) {
return str1->StringEquals(ToV8String(isolate, str2));
}


void RecordReplayDevtoolsEventListener::Invoke(ExecutionContext* context, Event* event) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Context> v8_context = isolate->GetCurrentContext();
ScriptState* scriptState = ScriptState::Current(isolate);
CustomEvent* customEvent = To<CustomEvent>(event);

if (!customEvent) {
return;
}

v8::Local<v8::Value> detail = customEvent->detail(scriptState).V8Value();
v8::Local<v8::String> detail_json;
if (!detail->ToString(v8_context).ToLocal(&detail_json)) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: detail is not a string";
return;
}

// for debugging:
// LOG(ERROR) << "RecordReplayDevtoolsEventListener: detail = " << V8ToString(isolate, detail_json);

// detail is a JSON stringified object with one of the following forms:

// { "id": "record-replay-token", "message": { "type": "connect" } } => register auth token observer
// { "id": "record-replay-token", "message": { "type": "login" } } => open external browser to login
// { "id": "record-replay-token", "message": { "token": <string|null> } } => set access token if string. clear if null (or undefined?)
// { "id": "record-replay", "message": { "user": <string|null> } } => set user if string. clear if null (or undefined?)

v8::Local<v8::Object> detail_obj;
if (!v8::JSON::Parse(v8_context, detail_json).ToLocalChecked()->ToObject(v8_context).ToLocal(&detail_obj)) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: detail is not a JSON object";
return;
}

// always pull out the id and message properties, and early out if id isn't a string or message isn't an object
v8::Local<v8::String> id_str;
if (!GetStringProperty(v8_context, detail_obj, "id", &id_str)) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: id is not an string";
return;
}

v8::Local<v8::Object> message_obj;
if (!GetObjectProperty(v8_context, detail_obj, "message", &message_obj)) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: message is not an object";
return;
}


if (StringEquals(isolate, id_str, "record-replay-token")) {
HandleRecordReplayTokenMessage(v8_context, message_obj);
} else if (StringEquals(isolate, id_str, "record-replay")) {
HandleRecordReplayMessage(v8_context, message_obj);
} else {
LOG(ERROR) << "[RUN-2863] Unknown event id: " << V8ToString(isolate, id_str);
}
}

void RecordReplayDevtoolsEventListener::HandleRecordReplayTokenMessage(v8::Local<v8::Context> context, v8::Local<v8::Object> message) {
v8::Isolate* isolate = context->GetIsolate();

// cases here:
// { "id": "record-replay-token", "message": { "type": "connect" } } => register auth token observer
// { "id": "record-replay-token", "message": { "type": "login" } } => open external browser to login
// { "id": "record-replay-token", "message": { "token": <string|null> } } => set access token if string. clear if null (or undefined?)

// first check if there's a type property to handle the first two cases above.
v8::Local<v8::Value> message_type = message->Get(context, ToV8String(isolate, "type")).ToLocalChecked();
if (message_type->IsString()) {
// message is either `{ type: "connect" }` or `{ type: "login" }`, with neither payload carrying additional info.
if (StringEquals(isolate, message_type.As<v8::String>(), "connect")) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: connect message received";
local_frame_->RecordReplayRegisterAuthTokenObserver();
return;
}

if (StringEquals(isolate, message_type.As<v8::String>(), "login")) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: login message received";
local_frame_->RecordReplayLogin();
return;
}

LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: unknown record-replay-token message type: " << V8ToString(isolate, message_type);
}

// if we're here, we should only be in the `{ token: ... }` case from the list above.
v8::Local<v8::Value> message_token = message->Get(context, ToV8String(isolate, "token")).ToLocalChecked();
if (message_token->IsString()) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: set access token message received, token = " << V8ToString(isolate, message_token);
local_frame_->RecordReplaySetToken(ToCoreString(message_token.As<v8::String>()));
return;
}

if (message_token->IsNullOrUndefined()) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: clear access token message received";
local_frame_->RecordReplayClearToken();
return;
}

LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: unknown record-replay-token message";
}

void RecordReplayDevtoolsEventListener::HandleRecordReplayMessage(v8::Local<v8::Context> context, v8::Local<v8::Object> message) {
v8::Isolate* isolate = context->GetIsolate();

// the only message handled here is `{ user: <string|null> }`
v8::Local<v8::Value> message_user = message->Get(context, ToV8String(isolate, "user")).ToLocalChecked();
if (message_user->IsString()) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: set user message received, user = " << V8ToString(isolate, message_user);
local_frame_->RecordReplaySetUser(ToCoreString(message_user.As<v8::String>()));
return;
}

if (message_user->IsNullOrUndefined()) {
LOG(ERROR) << "[RUN-2863] RecordReplayDevtoolsEventListener: clear user message received";
local_frame_->RecordReplayClearUser();
return;
}

LOG(ERROR) << "[RUN-2863] Unknown record-replay message type";
return;
}

void RecordReplayDevtoolsEventListener::Trace(Visitor* visitor) const {
visitor->Trace(local_frame_);
EventListener::Trace(visitor);
}

} // namespace blink
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2021 Record Replay Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_RECORD_REPLAY_DEVTOOLS_EVENT_LISTENER_H_
#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_RECORD_REPLAY_DEVTOOLS_EVENT_LISTENER_H_

#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "v8/include/v8.h"

namespace blink {

class RecordReplayDevtoolsEventListener : public NativeEventListener {
public:
RecordReplayDevtoolsEventListener(v8::Isolate* isolate, LocalFrame* localFrame)
: local_frame_(localFrame) {}

void Invoke(ExecutionContext*, Event*) override;

void Trace(Visitor*) const override;

static RecordReplayDevtoolsEventListener* Create(v8::Isolate* isolate,
LocalFrame* localFrame) {
return MakeGarbageCollected<RecordReplayDevtoolsEventListener>(isolate, localFrame);
}

private:
Member<LocalFrame> local_frame_;

void HandleRecordReplayTokenMessage(v8::Local<v8::Context> context, v8::Local<v8::Object> message);
void HandleRecordReplayMessage(v8::Local<v8::Context> context, v8::Local<v8::Object> message);
};

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_RECORD_REPLAY_DEVTOOLS_EVENT_LISTENER_H_
Loading