Skip to content
Open
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
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,17 @@ added:

Enable experimental support for inspector network resources.

### `--experimental-inspector-storage-key=key`

<!-- YAML
added:
- REPLACEME
-->

> Stability: 1.1 - Active Development

Specify a inspector storage key used by the Node.js inspector when connecting to Chrome DevTools.

### `--experimental-loader=module`

<!-- YAML
Expand Down
100 changes: 100 additions & 0 deletions doc/api/inspector.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,106 @@ setNetworkResources().then(() => {

For more details, see the official CDP documentation: [Network.loadNetworkResource](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-loadNetworkResource)

### `inspector.DOMStorage.domStorageItemAdded`

<!-- YAML
added:
- REPLACEME
-->

* `params` {Object}
* `storageId` {Object}
* `securityOrigin` {string}
* `storageKey` {string}
* `isLocalStorage` {boolean}
* `key` {string}
* `newValue` {string}

This feature is only available with the
`--experimental-inspector-storage-key=key` flag enabled.

Broadcasts the `DOMStorage.domStorageItemAdded` event to connected frontends.
This event indicates that a new item has been added to the storage.

### `inspector.DOMStorage.domStorageItemRemoved`

<!-- YAML
added:
- REPLACEME
-->

* `params` {Object}
* `storageId` {Object}
* `securityOrigin` {string}
* `storageKey` {string}
* `isLocalStorage` {boolean}
* `key` {string}

This feature is only available with the
`--experimental-inspector-storage-key=key` flag enabled.

Broadcasts the `DOMStorage.domStorageItemRemoved` event to connected frontends.
This event indicates that an item has been removed from the storage.

### `inspector.DOMStorage.domStorageItemUpdated`

<!-- YAML
added:
- REPLACEME
-->

* `params` {Object}
* `storageId` {Object}
* `securityOrigin` {string}
* `storageKey` {string}
* `isLocalStorage` {boolean}
* `key` {string}
* `oldValue` {string}
* `newValue` {string}

This feature is only available with the
`--experimental-inspector-storage-key=key` flag enabled.

Broadcasts the `DOMStorage.domStorageItemUpdated` event to connected frontends.
This event indicates that a storage item has been updated.

### `inspector.DOMStorage.domStorageItemsCleared`

<!-- YAML
added:
- REPLACEME
-->

* `params` {Object}
* `storageId` {Object}
* `securityOrigin` {string}
* `storageKey` {string}
* `isLocalStorage` {boolean}

This feature is only available with the
`--experimental-inspector-storage-key=key` flag enabled.

Broadcasts the `DOMStorage.domStorageItemsCleared` event to connected
frontends. This event indicates that all items have been cleared from the
storage.

### `inspector.DOMStorage.registerStorage`

<!-- YAML
added:
- REPLACEME
-->

* `params` {Object}
* `isLocalStorage` {boolean}
* `storageMap` {Object}

This feature is only available with the
`--experimental-inspector-storage-key=key` flag enabled.

Registers a storage map with the inspector. The registered storage entries are
reported when getDOMStorageItems is called.

## Support of breakpoints

The Chrome DevTools Protocol [`Debugger` domain][] allows an
Expand Down
4 changes: 4 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ Enable the experimental QUIC support.
.
.It Fl -experimental-inspector-network-resource
Enable experimental support for inspector network resources.

.It Fl -experimental-inspector-storage-key
Specify a inspector storage key used by the Node.js inspector when connecting to Chrome DevTools.

.
.It Fl -force-context-aware
Disable loading native addons that are not context-aware.
Expand Down
11 changes: 11 additions & 0 deletions lib/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,16 @@ const NetworkResources = {
put,
};

const DOMStorage = {
domStorageItemAdded: (params) => broadcastToFrontend('DOMStorage.domStorageItemAdded', params),
domStorageItemRemoved: (params) => broadcastToFrontend('DOMStorage.domStorageItemRemoved', params),
domStorageItemUpdated: (params) => broadcastToFrontend('DOMStorage.domStorageItemUpdated', params),
domStorageItemsCleared: (params) => broadcastToFrontend('DOMStorage.domStorageItemsCleared', params),
// Pseudo-event: not part of the CDP DOMStorage domain.
// Call DOMStorageAgent::registerStorage in inspector/dom_storage_agent.cc.
registerStorage: (params) => broadcastToFrontend('DOMStorage.registerStorage', params),
};

module.exports = {
open: inspectorOpen,
close: _debugEnd,
Expand All @@ -238,4 +248,5 @@ module.exports = {
Session,
Network,
NetworkResources,
DOMStorage,
};
238 changes: 238 additions & 0 deletions src/inspector/dom_storage_agent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
#include "dom_storage_agent.h"

#include "crdtp/dispatch.h"
#include "env-inl.h"
#include "inspector/inspector_object_utils.h"
#include "v8-isolate.h"

namespace node {
namespace inspector {

using v8::Array;
using v8::HandleScope;
using v8::Local;
using v8::Object;
using v8::Value;

std::unique_ptr<protocol::DOMStorage::StorageId> createStorageIdFromObject(
v8::Local<v8::Context> context, v8::Local<v8::Object> storage_id_obj) {
protocol::String security_origin;
if (!ObjectGetProtocolString(context, storage_id_obj, "securityOrigin")
.To(&security_origin)) {
return {};
}
bool is_local_storage =
ObjectGetBool(context, storage_id_obj, "isLocalStorage").FromMaybe(false);
protocol::String storageKey;
if (!ObjectGetProtocolString(context, storage_id_obj, "storageKey")
.To(&storageKey)) {
return {};
}

return protocol::DOMStorage::StorageId::create()
.setSecurityOrigin(security_origin)
.setIsLocalStorage(is_local_storage)
.setStorageKey(storageKey)
.build();
}

DOMStorageAgent::DOMStorageAgent(Environment* env) : env_(env) {}

DOMStorageAgent::~DOMStorageAgent() {}

void DOMStorageAgent::Wire(protocol::UberDispatcher* dispatcher) {
frontend_ =
std::make_unique<protocol::DOMStorage::Frontend>(dispatcher->channel());
protocol::DOMStorage::Dispatcher::wire(dispatcher, this);
event_notifier_map_["domStorageItemAdded"] =
(EventNotifier)(&DOMStorageAgent::domStorageItemAdded);
event_notifier_map_["domStorageItemRemoved"] =
(EventNotifier)(&DOMStorageAgent::domStorageItemRemoved);
event_notifier_map_["domStorageItemUpdated"] =
(EventNotifier)(&DOMStorageAgent::domStorageItemUpdated);
event_notifier_map_["domStorageItemsCleared"] =
(EventNotifier)(&DOMStorageAgent::domStorageItemsCleared);
event_notifier_map_["registerStorage"] =
(EventNotifier)(&DOMStorageAgent::registerStorage);
}

protocol::DispatchResponse DOMStorageAgent::enable() {
this->enabled_ = true;
return protocol::DispatchResponse::Success();
}

protocol::DispatchResponse DOMStorageAgent::disable() {
this->enabled_ = false;
return protocol::DispatchResponse::Success();
}

protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems(
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
std::unique_ptr<protocol::Array<protocol::Array<protocol::String>>>*
items) {
if (!enabled_) {
return protocol::DispatchResponse::ServerError(
"DOMStorage domain is not enabled");
}
bool is_local_storage = storageId->getIsLocalStorage();
const std::unordered_map<std::string, std::string>& storage_map =
is_local_storage ? local_storage_map_ : session_storage_map_;
auto result =
std::make_unique<protocol::Array<protocol::Array<protocol::String>>>();
for (const auto& pair : storage_map) {
auto item = std::make_unique<protocol::Array<protocol::String>>();
item->push_back(pair.first);
item->push_back(pair.second);
result->push_back(std::move(item));
}
*items = std::move(result);
return protocol::DispatchResponse::Success();
}

protocol::DispatchResponse DOMStorageAgent::setDOMStorageItem(
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
const std::string& key,
const std::string& value) {
return protocol::DispatchResponse::ServerError("Not implemented");
}

protocol::DispatchResponse DOMStorageAgent::removeDOMStorageItem(
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
const std::string& key) {
return protocol::DispatchResponse::ServerError("Not implemented");
}

protocol::DispatchResponse DOMStorageAgent::clear(
std::unique_ptr<protocol::DOMStorage::StorageId> storageId) {
return protocol::DispatchResponse::ServerError("Not implemented");
}

void DOMStorageAgent::domStorageItemAdded(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Local<Object> storage_id_obj;
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
return;
}

std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
createStorageIdFromObject(context, storage_id_obj);
if (!storage_id) {
return;
}

protocol::String key;
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
return;
}
protocol::String new_value;
if (!ObjectGetProtocolString(context, params, "newValue").To(&new_value)) {
return;
}
frontend_->domStorageItemAdded(std::move(storage_id), key, new_value);
}

void DOMStorageAgent::domStorageItemRemoved(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Local<Object> storage_id_obj;
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
return;
}
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
createStorageIdFromObject(context, storage_id_obj);

if (!storage_id) {
return;
}

protocol::String key;
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
return;
}
frontend_->domStorageItemRemoved(std::move(storage_id), key);
}

void DOMStorageAgent::domStorageItemUpdated(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Local<Object> storage_id_obj;
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
return;
}

std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
createStorageIdFromObject(context, storage_id_obj);

if (!storage_id) {
return;
}

protocol::String key;
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
return;
}
protocol::String old_value;
if (!ObjectGetProtocolString(context, params, "oldValue").To(&old_value)) {
return;
}
protocol::String new_value;
if (!ObjectGetProtocolString(context, params, "newValue").To(&new_value)) {
return;
}
frontend_->domStorageItemUpdated(
std::move(storage_id), key, old_value, new_value);
}

void DOMStorageAgent::domStorageItemsCleared(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Local<Object> storage_id_obj;
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
return;
}
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
createStorageIdFromObject(context, storage_id_obj);

if (!storage_id) {
return;
}
frontend_->domStorageItemsCleared(std::move(storage_id));
}

void DOMStorageAgent::registerStorage(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
v8::Isolate* isolate = env_->isolate();
HandleScope handle_scope(isolate);
bool is_local_storage;
if (!ObjectGetBool(context, params, "isLocalStorage").To(&is_local_storage)) {
return;
}
Local<Object> storage_map_obj;
if (!ObjectGetObject(context, params, "storageMap")
.ToLocal(&storage_map_obj)) {
return;
}
std::unordered_map<std::string, std::string>& storage_map =
is_local_storage ? local_storage_map_ : session_storage_map_;
Local<Array> property_names;
if (!storage_map_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) {
return;
}
uint32_t length = property_names->Length();
for (uint32_t i = 0; i < length; ++i) {
Local<Value> key_value;
if (!property_names->Get(context, i).ToLocal(&key_value)) {
return;
}
Local<Value> value_value;
if (!storage_map_obj->Get(context, key_value).ToLocal(&value_value)) {
return;
}
node::Utf8Value key_utf8(isolate, key_value);
node::Utf8Value value_utf8(isolate, value_value);
storage_map[*key_utf8] = *value_utf8;
}
}

bool DOMStorageAgent::canEmit(const std::string& domain) {
return domain == "DOMStorage";
}
} // namespace inspector
} // namespace node
Loading
Loading