inspector: auto collect webstorage data · nodejs/node@4f3f21b · GitHub
Skip to content

Commit 4f3f21b

Browse files
islandryuaduh95
authored andcommitted
inspector: auto collect webstorage data
PR-URL: #62145 Backport-PR-URL: #63176 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 36cc041 commit 4f3f21b

8 files changed

Lines changed: 340 additions & 17 deletions

File tree

Lines changed: 107 additions & 0 deletions

lib/internal/webstorage.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const {
33
ObjectDefineProperties,
44
} = primordials;
55
const { ERR_INVALID_ARG_VALUE } = require('internal/errors').codes;
6+
const { hasInspector } = internalBinding('config');
67
const { getOptionValue } = require('internal/options');
78
const { emitExperimentalWarning } = require('internal/util');
89
const { kConstructorKey, Storage } = internalBinding('webstorage');
@@ -15,6 +16,17 @@ module.exports = { Storage };
1516

1617
let lazyLocalStorage;
1718
let lazySessionStorage;
19+
let lazyInspectorStorage;
20+
21+
const experimentalStorageInspection =
22+
hasInspector && getOptionValue('--experimental-storage-inspection');
23+
24+
function getInspectorStorage() {
25+
if (lazyInspectorStorage === undefined) {
26+
lazyInspectorStorage = require('internal/inspector/webstorage');
27+
}
28+
return lazyInspectorStorage;
29+
}
1830

1931
ObjectDefineProperties(module.exports, {
2032
__proto__: null,
@@ -32,7 +44,12 @@ ObjectDefineProperties(module.exports, {
3244
'is an invalid localStorage location');
3345
}
3446

35-
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
47+
if (experimentalStorageInspection) {
48+
const { InspectorLocalStorage } = getInspectorStorage();
49+
lazyLocalStorage = new InspectorLocalStorage(kConstructorKey, getValidatedPath(location), true);
50+
} else {
51+
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
52+
}
3653
}
3754

3855
return lazyLocalStorage;
@@ -44,7 +61,12 @@ ObjectDefineProperties(module.exports, {
4461
enumerable: true,
4562
get() {
4663
if (lazySessionStorage === undefined) {
47-
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
64+
if (experimentalStorageInspection) {
65+
const { InspectorSessionStorage } = getInspectorStorage();
66+
lazySessionStorage = new InspectorSessionStorage(kConstructorKey, kInMemoryPath, false);
67+
} else {
68+
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
69+
}
4870
}
4971

5072
return lazySessionStorage;

src/inspector/dom_storage_agent.cc

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "dom_storage_agent.h"
2+
#include <optional>
23
#include "env-inl.h"
34
#include "inspector/inspector_object_utils.h"
5+
#include "util.h"
6+
#include "v8-exception.h"
47
#include "v8-isolate.h"
58

69
namespace node {
@@ -85,14 +88,27 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems(
8588
"DOMStorage domain is not enabled");
8689
}
8790
bool is_local_storage = storageId->getIsLocalStorage();
88-
const std::unordered_map<std::string, std::string>& storage_map =
89-
is_local_storage ? local_storage_map_ : session_storage_map_;
91+
const StorageMap* storage_map =
92+
is_local_storage ? &local_storage_map_ : &session_storage_map_;
93+
std::optional<StorageMap> storage_map_fallback;
94+
if (storage_map->empty()) {
95+
auto web_storage_obj = getWebStorage(is_local_storage);
96+
if (web_storage_obj) {
97+
storage_map_fallback = web_storage_obj.value()->GetAll();
98+
storage_map = &storage_map_fallback.value();
99+
}
100+
}
101+
90102
auto result =
91103
std::make_unique<protocol::Array<protocol::Array<protocol::String>>>();
92-
for (const auto& pair : storage_map) {
104+
for (const auto& pair : *storage_map) {
93105
auto item = std::make_unique<protocol::Array<protocol::String>>();
94-
item->push_back(pair.first);
95-
item->push_back(pair.second);
106+
item->push_back(protocol::StringUtil::fromUTF16(
107+
reinterpret_cast<const uint16_t*>(pair.first.data()),
108+
pair.first.size()));
109+
item->push_back(protocol::StringUtil::fromUTF16(
110+
reinterpret_cast<const uint16_t*>(pair.second.data()),
111+
pair.second.size()));
96112
result->push_back(std::move(item));
97113
}
98114
*items = std::move(result);
@@ -219,7 +235,7 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
219235
.ToLocal(&storage_map_obj)) {
220236
return;
221237
}
222-
std::unordered_map<std::string, std::string>& storage_map =
238+
StorageMap& storage_map =
223239
is_local_storage ? local_storage_map_ : session_storage_map_;
224240
Local<Array> property_names;
225241
if (!storage_map_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) {
@@ -235,9 +251,32 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
235251
if (!storage_map_obj->Get(context, key_value).ToLocal(&value_value)) {
236252
return;
237253
}
238-
node::Utf8Value key_utf8(isolate, key_value);
239-
node::Utf8Value value_utf8(isolate, value_value);
240-
storage_map[*key_utf8] = *value_utf8;
254+
node::TwoByteValue key_utf16(isolate, key_value);
255+
node::TwoByteValue value_utf16(isolate, value_value);
256+
storage_map[key_utf16.ToU16String()] = value_utf16.ToU16String();
257+
}
258+
}
259+
260+
std::optional<node::webstorage::Storage*> DOMStorageAgent::getWebStorage(
261+
bool is_local_storage) {
262+
v8::Isolate* isolate = env_->isolate();
263+
v8::HandleScope handle_scope(isolate);
264+
v8::Local<v8::Object> global = env_->context()->Global();
265+
v8::Local<v8::Value> web_storage_val;
266+
v8::TryCatch try_catch(isolate);
267+
if (!global
268+
->Get(env_->context(),
269+
is_local_storage
270+
? FIXED_ONE_BYTE_STRING(isolate, "localStorage")
271+
: FIXED_ONE_BYTE_STRING(isolate, "sessionStorage"))
272+
.ToLocal(&web_storage_val) ||
273+
!web_storage_val->IsObject() || try_catch.HasCaught()) {
274+
return std::nullopt;
275+
} else {
276+
node::webstorage::Storage* storage;
277+
ASSIGN_OR_RETURN_UNWRAP(
278+
&storage, web_storage_val.As<v8::Object>(), std::nullopt);
279+
return storage;
241280
}
242281
}
243282

src/inspector/dom_storage_agent.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#ifndef SRC_INSPECTOR_DOM_STORAGE_AGENT_H_
22
#define SRC_INSPECTOR_DOM_STORAGE_AGENT_H_
33

4+
#include <optional>
45
#include <string>
56
#include "env.h"
67
#include "node/inspector/protocol/DOMStorage.h"
8+
#include "node_webstorage.h"
79
#include "notification_emitter.h"
810
#include "v8.h"
911

@@ -50,9 +52,12 @@ class DOMStorageAgent : public protocol::DOMStorage::Backend,
5052
DOMStorageAgent& operator=(const DOMStorageAgent&) = delete;
5153

5254
private:
55+
typedef std::unordered_map<std::u16string, std::u16string> StorageMap;
56+
std::optional<node::webstorage::Storage*> getWebStorage(
57+
bool is_local_storage);
5358
std::unique_ptr<protocol::DOMStorage::Frontend> frontend_;
54-
std::unordered_map<std::string, std::string> local_storage_map_ = {};
55-
std::unordered_map<std::string, std::string> session_storage_map_ = {};
59+
StorageMap local_storage_map_ = {};
60+
StorageMap session_storage_map_ = {};
5661
bool enabled_ = false;
5762
Environment* env_;
5863
};

src/node_builtins.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
122122
"internal/inspector/network", "internal/inspector/network_http",
123123
"internal/inspector/network_http2", "internal/inspector/network_undici",
124124
"internal/inspector_async_hook", "internal/inspector_network_tracking",
125+
"internal/inspector/webstorage",
125126
#endif // !HAVE_INSPECTOR
126127

127128
#if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT)
@@ -145,6 +146,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
145146
"wasi", // Experimental.
146147
#if !HAVE_SQLITE
147148
"internal/webstorage", // Experimental.
149+
"internal/inspector/webstorage",
148150
#endif
149151
"internal/test/binding", "internal/v8_prof_polyfill",
150152
};

src/node_webstorage.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "node_webstorage.h"
2+
#include <string>
3+
#include <unordered_map>
24
#include "base_object-inl.h"
35
#include "debug_utils-inl.h"
46
#include "env-inl.h"
@@ -7,6 +9,7 @@
79
#include "node_errors.h"
810
#include "node_mem-inl.h"
911
#include "path.h"
12+
#include "simdutf.h"
1013
#include "sqlite3.h"
1114
#include "util-inl.h"
1215

@@ -278,6 +281,35 @@ MaybeLocal<Array> Storage::Enumerate() {
278281
return Array::New(env()->isolate(), values.data(), values.size());
279282
}
280283

284+
std::unordered_map<std::u16string, std::u16string> Storage::GetAll() {
285+
if (!Open().IsJust()) {
286+
return {};
287+
}
288+
289+
static constexpr std::string_view sql =
290+
"SELECT key, value FROM nodejs_webstorage";
291+
sqlite3_stmt* s = nullptr;
292+
int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, nullptr);
293+
auto stmt = stmt_unique_ptr(s);
294+
std::unordered_map<std::u16string, std::u16string> result;
295+
while ((r = sqlite3_step(stmt.get())) == SQLITE_ROW) {
296+
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB);
297+
CHECK(sqlite3_column_type(stmt.get(), 1) == SQLITE_BLOB);
298+
auto key_size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t);
299+
auto value_size = sqlite3_column_bytes(stmt.get(), 1) / sizeof(uint16_t);
300+
auto key_uint16(
301+
reinterpret_cast<const char16_t*>(sqlite3_column_blob(stmt.get(), 0)));
302+
auto value_uint16(
303+
reinterpret_cast<const char16_t*>(sqlite3_column_blob(stmt.get(), 1)));
304+
305+
std::u16string key(key_uint16, key_size);
306+
std::u16string value(value_uint16, value_size);
307+
308+
result.emplace(std::move(key), std::move(value));
309+
}
310+
return result;
311+
}
312+
281313
MaybeLocal<Value> Storage::Length() {
282314
if (!Open().IsJust()) {
283315
return {};

src/node_webstorage.h

Lines changed: 2 additions & 0 deletions

0 commit comments

Comments
 (0)