Skip to content

Commit cb8ff6d

Browse files
committed
inspector: initial support storage inspection
1 parent b5c9469 commit cb8ff6d

22 files changed

+900
-83
lines changed

doc/api/cli.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,17 @@ added:
11221122
11231123
Enable experimental support for inspector network resources.
11241124

1125+
### `--experimental-inspector-storage-key=key`
1126+
1127+
<!-- YAML
1128+
added:
1129+
- REPLACEME
1130+
-->
1131+
1132+
> Stability: 1.1 - Active Development
1133+
1134+
Specify a inspector storage key used by the Node.js inspector when connecting to Chrome DevTools.
1135+
11251136
### `--experimental-loader=module`
11261137

11271138
<!-- YAML

doc/api/inspector.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,106 @@ setNetworkResources().then(() => {
682682

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

685+
### `inspector.DOMStorage.domStorageItemAdded`
686+
687+
<!-- YAML
688+
added:
689+
- REPLACEME
690+
-->
691+
692+
* `params` {Object}
693+
* `storageId` {Object}
694+
* `securityOrigin` {string}
695+
* `storageKey` {string}
696+
* `isLocalStorage` {boolean}
697+
* `key` {string}
698+
* `newValue` {string}
699+
700+
This feature is only available with the
701+
`--experimental-inspector-storage-key=key` flag enabled.
702+
703+
Broadcasts the `DOMStorage.domStorageItemAdded` event to connected frontends.
704+
This event indicates that a new item has been added to the storage.
705+
706+
### `inspector.DOMStorage.domStorageItemRemoved`
707+
708+
<!-- YAML
709+
added:
710+
- REPLACEME
711+
-->
712+
713+
* `params` {Object}
714+
* `storageId` {Object}
715+
* `securityOrigin` {string}
716+
* `storageKey` {string}
717+
* `isLocalStorage` {boolean}
718+
* `key` {string}
719+
720+
This feature is only available with the
721+
`--experimental-inspector-storage-key=key` flag enabled.
722+
723+
Broadcasts the `DOMStorage.domStorageItemRemoved` event to connected frontends.
724+
This event indicates that an item has been removed from the storage.
725+
726+
### `inspector.DOMStorage.domStorageItemUpdated`
727+
728+
<!-- YAML
729+
added:
730+
- REPLACEME
731+
-->
732+
733+
* `params` {Object}
734+
* `storageId` {Object}
735+
* `securityOrigin` {string}
736+
* `storageKey` {string}
737+
* `isLocalStorage` {boolean}
738+
* `key` {string}
739+
* `oldValue` {string}
740+
* `newValue` {string}
741+
742+
This feature is only available with the
743+
`--experimental-inspector-storage-key=key` flag enabled.
744+
745+
Broadcasts the `DOMStorage.domStorageItemUpdated` event to connected frontends.
746+
This event indicates that a storage item has been updated.
747+
748+
### `inspector.DOMStorage.domStorageItemsCleared`
749+
750+
<!-- YAML
751+
added:
752+
- REPLACEME
753+
-->
754+
755+
* `params` {Object}
756+
* `storageId` {Object}
757+
* `securityOrigin` {string}
758+
* `storageKey` {string}
759+
* `isLocalStorage` {boolean}
760+
761+
This feature is only available with the
762+
`--experimental-inspector-storage-key=key` flag enabled.
763+
764+
Broadcasts the `DOMStorage.domStorageItemsCleared` event to connected
765+
frontends. This event indicates that all items have been cleared from the
766+
storage.
767+
768+
### `inspector.DOMStorage.registerStorage`
769+
770+
<!-- YAML
771+
added:
772+
- REPLACEME
773+
-->
774+
775+
* `params` {Object}
776+
* `isLocalStorage` {boolean}
777+
* `storageMap` {Object}
778+
779+
This feature is only available with the
780+
`--experimental-inspector-storage-key=key` flag enabled.
781+
782+
Registers a storage map with the inspector. The registered storage entries are
783+
reported when getDOMStorageItems is called.
784+
685785
## Support of breakpoints
686786

687787
The Chrome DevTools Protocol [`Debugger` domain][] allows an

doc/node.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ Enable the experimental QUIC support.
231231
.
232232
.It Fl -experimental-inspector-network-resource
233233
Enable experimental support for inspector network resources.
234+
235+
.It Fl -experimental-inspector-storage-key
236+
Specify a inspector storage key used by the Node.js inspector when connecting to Chrome DevTools.
237+
234238
.
235239
.It Fl -force-context-aware
236240
Disable loading native addons that are not context-aware.

lib/inspector.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ const NetworkResources = {
229229
put,
230230
};
231231

232+
const DOMStorage = {
233+
domStorageItemAdded: (params) => broadcastToFrontend('DOMStorage.domStorageItemAdded', params),
234+
domStorageItemRemoved: (params) => broadcastToFrontend('DOMStorage.domStorageItemRemoved', params),
235+
domStorageItemUpdated: (params) => broadcastToFrontend('DOMStorage.domStorageItemUpdated', params),
236+
domStorageItemsCleared: (params) => broadcastToFrontend('DOMStorage.domStorageItemsCleared', params),
237+
// Pseudo-event: not part of the CDP DOMStorage domain.
238+
// Call DOMStorageAgent::registerStorage in inspector/dom_storage_agent.cc.
239+
registerStorage: (params) => broadcastToFrontend('DOMStorage.registerStorage', params),
240+
};
241+
232242
module.exports = {
233243
open: inspectorOpen,
234244
close: _debugEnd,
@@ -238,4 +248,5 @@ module.exports = {
238248
Session,
239249
Network,
240250
NetworkResources,
251+
DOMStorage,
241252
};

src/inspector/dom_storage_agent.cc

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
#include "dom_storage_agent.h"
2+
3+
#include "crdtp/dispatch.h"
4+
#include "env-inl.h"
5+
#include "env.h"
6+
#include "inspector/inspector_object_utils.h"
7+
#include "v8-isolate.h"
8+
9+
namespace node {
10+
namespace inspector {
11+
12+
using v8::Array;
13+
using v8::HandleScope;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::Value;
17+
18+
std::unique_ptr<protocol::DOMStorage::StorageId> createStorageIdFromObject(
19+
v8::Local<v8::Context> context, v8::Local<v8::Object> storage_id_obj) {
20+
protocol::String security_origin;
21+
if (!ObjectGetProtocolString(context, storage_id_obj, "securityOrigin")
22+
.To(&security_origin)) {
23+
return {};
24+
}
25+
bool is_local_storage =
26+
ObjectGetBool(context, storage_id_obj, "isLocalStorage").FromMaybe(false);
27+
protocol::String storageKey;
28+
if (!ObjectGetProtocolString(context, storage_id_obj, "storageKey")
29+
.To(&storageKey)) {
30+
return {};
31+
}
32+
33+
return protocol::DOMStorage::StorageId::create()
34+
.setSecurityOrigin(security_origin)
35+
.setIsLocalStorage(is_local_storage)
36+
.setStorageKey(storageKey)
37+
.build();
38+
}
39+
40+
DOMStorageAgent::DOMStorageAgent(Environment* env) : env_(env) {}
41+
42+
DOMStorageAgent::~DOMStorageAgent() {}
43+
44+
void DOMStorageAgent::Wire(protocol::UberDispatcher* dispatcher) {
45+
frontend_ =
46+
std::make_unique<protocol::DOMStorage::Frontend>(dispatcher->channel());
47+
protocol::DOMStorage::Dispatcher::wire(dispatcher, this);
48+
event_notifier_map_["domStorageItemAdded"] =
49+
(EventNotifier)(&DOMStorageAgent::domStorageItemAdded);
50+
event_notifier_map_["domStorageItemRemoved"] =
51+
(EventNotifier)(&DOMStorageAgent::domStorageItemRemoved);
52+
event_notifier_map_["domStorageItemUpdated"] =
53+
(EventNotifier)(&DOMStorageAgent::domStorageItemUpdated);
54+
event_notifier_map_["domStorageItemsCleared"] =
55+
(EventNotifier)(&DOMStorageAgent::domStorageItemsCleared);
56+
event_notifier_map_["registerStorage"] =
57+
(EventNotifier)(&DOMStorageAgent::registerStorage);
58+
}
59+
60+
protocol::DispatchResponse DOMStorageAgent::enable() {
61+
this->enabled_ = true;
62+
return protocol::DispatchResponse::Success();
63+
}
64+
65+
protocol::DispatchResponse DOMStorageAgent::disable() {
66+
this->enabled_ = false;
67+
return protocol::DispatchResponse::Success();
68+
}
69+
70+
protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems(
71+
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
72+
std::unique_ptr<protocol::Array<protocol::Array<protocol::String>>>*
73+
items) {
74+
if (!enabled_) {
75+
return protocol::DispatchResponse::ServerError(
76+
"DOMStorage domain is not enabled");
77+
}
78+
bool is_local_storage = storageId->getIsLocalStorage();
79+
const std::unordered_map<std::string, std::string>& storage_map =
80+
is_local_storage ? local_storage_map_ : session_storage_map_;
81+
auto result =
82+
std::make_unique<protocol::Array<protocol::Array<protocol::String>>>();
83+
for (const auto& pair : storage_map) {
84+
auto item = std::make_unique<protocol::Array<protocol::String>>();
85+
item->push_back(pair.first);
86+
item->push_back(pair.second);
87+
result->push_back(std::move(item));
88+
}
89+
*items = std::move(result);
90+
return protocol::DispatchResponse::Success();
91+
}
92+
93+
protocol::DispatchResponse DOMStorageAgent::setDOMStorageItem(
94+
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
95+
const std::string& key,
96+
const std::string& value) {
97+
return protocol::DispatchResponse::ServerError("Not implemented");
98+
}
99+
100+
protocol::DispatchResponse DOMStorageAgent::removeDOMStorageItem(
101+
std::unique_ptr<protocol::DOMStorage::StorageId> storageId,
102+
const std::string& key) {
103+
return protocol::DispatchResponse::ServerError("Not implemented");
104+
}
105+
106+
protocol::DispatchResponse DOMStorageAgent::clear(
107+
std::unique_ptr<protocol::DOMStorage::StorageId> storageId) {
108+
return protocol::DispatchResponse::ServerError("Not implemented");
109+
}
110+
111+
void DOMStorageAgent::domStorageItemAdded(v8::Local<v8::Context> context,
112+
v8::Local<v8::Object> params) {
113+
Local<Object> storage_id_obj;
114+
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
115+
return;
116+
}
117+
118+
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
119+
createStorageIdFromObject(context, storage_id_obj);
120+
if (!storage_id) {
121+
return;
122+
}
123+
124+
protocol::String key;
125+
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
126+
return;
127+
}
128+
protocol::String new_value;
129+
if (!ObjectGetProtocolString(context, params, "newValue").To(&new_value)) {
130+
return;
131+
}
132+
frontend_->domStorageItemAdded(std::move(storage_id), key, new_value);
133+
}
134+
135+
void DOMStorageAgent::domStorageItemRemoved(v8::Local<v8::Context> context,
136+
v8::Local<v8::Object> params) {
137+
Local<Object> storage_id_obj;
138+
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
139+
return;
140+
}
141+
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
142+
createStorageIdFromObject(context, storage_id_obj);
143+
144+
if (!storage_id) {
145+
return;
146+
}
147+
148+
protocol::String key;
149+
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
150+
return;
151+
}
152+
frontend_->domStorageItemRemoved(std::move(storage_id), key);
153+
}
154+
155+
void DOMStorageAgent::domStorageItemUpdated(v8::Local<v8::Context> context,
156+
v8::Local<v8::Object> params) {
157+
Local<Object> storage_id_obj;
158+
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
159+
return;
160+
}
161+
162+
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
163+
createStorageIdFromObject(context, storage_id_obj);
164+
165+
if (!storage_id) {
166+
return;
167+
}
168+
169+
protocol::String key;
170+
if (!ObjectGetProtocolString(context, params, "key").To(&key)) {
171+
return;
172+
}
173+
protocol::String old_value;
174+
if (!ObjectGetProtocolString(context, params, "oldValue").To(&old_value)) {
175+
return;
176+
}
177+
protocol::String new_value;
178+
if (!ObjectGetProtocolString(context, params, "newValue").To(&new_value)) {
179+
return;
180+
}
181+
frontend_->domStorageItemUpdated(
182+
std::move(storage_id), key, old_value, new_value);
183+
}
184+
185+
void DOMStorageAgent::domStorageItemsCleared(v8::Local<v8::Context> context,
186+
v8::Local<v8::Object> params) {
187+
Local<Object> storage_id_obj;
188+
if (!ObjectGetObject(context, params, "storageId").ToLocal(&storage_id_obj)) {
189+
return;
190+
}
191+
std::unique_ptr<protocol::DOMStorage::StorageId> storage_id =
192+
createStorageIdFromObject(context, storage_id_obj);
193+
194+
if (!storage_id) {
195+
return;
196+
}
197+
frontend_->domStorageItemsCleared(std::move(storage_id));
198+
}
199+
200+
void DOMStorageAgent::registerStorage(v8::Local<v8::Context> context,
201+
v8::Local<v8::Object> params) {
202+
v8::Isolate* isolate = env_->isolate();
203+
HandleScope handle_scope(isolate);
204+
bool is_local_storage;
205+
if (!ObjectGetBool(context, params, "isLocalStorage").To(&is_local_storage)) {
206+
return;
207+
}
208+
Local<Object> storage_map_obj;
209+
if (!ObjectGetObject(context, params, "storageMap")
210+
.ToLocal(&storage_map_obj)) {
211+
return;
212+
}
213+
std::unordered_map<std::string, std::string>& storage_map =
214+
is_local_storage ? local_storage_map_ : session_storage_map_;
215+
Local<Array> property_names;
216+
if (!storage_map_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) {
217+
return;
218+
}
219+
uint32_t length = property_names->Length();
220+
for (uint32_t i = 0; i < length; ++i) {
221+
Local<Value> key_value;
222+
if (!property_names->Get(context, i).ToLocal(&key_value)) {
223+
return;
224+
}
225+
Local<Value> value_value;
226+
if (!storage_map_obj->Get(context, key_value).ToLocal(&value_value)) {
227+
return;
228+
}
229+
v8::String::Utf8Value key_utf8(isolate, key_value);
230+
v8::String::Utf8Value value_utf8(isolate, value_value);
231+
storage_map[*key_utf8] = *value_utf8;
232+
}
233+
}
234+
235+
bool DOMStorageAgent::canEmit(const std::string& domain) {
236+
return domain == "DOMStorage";
237+
}
238+
} // namespace inspector
239+
} // namespace node

0 commit comments

Comments
 (0)