Skip to content

Commit d93935b

Browse files
authored
inspector: auto collect webstorage data
PR-URL: #62145 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent e775989 commit d93935b

File tree

8 files changed

+338
-17
lines changed

8 files changed

+338
-17
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use strict';
2+
3+
const { Storage } = internalBinding('webstorage');
4+
const { DOMStorage } = require('inspector');
5+
const path = require('path');
6+
const { getOptionValue } = require('internal/options');
7+
8+
class InspectorLocalStorage extends Storage {
9+
setItem(key, value) {
10+
const oldValue = this.getItem(key);
11+
super.setItem(key, value);
12+
if (oldValue == null) {
13+
itemAdded(key, value, true);
14+
} else {
15+
itemUpdated(key, oldValue, value, true);
16+
}
17+
}
18+
19+
removeItem(key) {
20+
super.removeItem(key);
21+
itemRemoved(key, true);
22+
}
23+
24+
clear() {
25+
super.clear();
26+
itemsCleared(true);
27+
}
28+
}
29+
30+
const InspectorSessionStorage = class extends Storage {
31+
setItem(key, value) {
32+
const oldValue = this.getItem(key);
33+
super.setItem(key, value);
34+
if (oldValue == null) {
35+
itemAdded(key, value, false);
36+
} else {
37+
itemUpdated(key, oldValue, value, false);
38+
}
39+
}
40+
41+
removeItem(key) {
42+
super.removeItem(key);
43+
itemRemoved(key, false);
44+
}
45+
46+
clear() {
47+
super.clear();
48+
itemsCleared(false);
49+
}
50+
};
51+
52+
function itemAdded(key, value, isLocalStorage) {
53+
DOMStorage.domStorageItemAdded({
54+
key,
55+
newValue: value,
56+
storageId: {
57+
securityOrigin: '',
58+
isLocalStorage,
59+
storageKey: getStorageKey(),
60+
},
61+
});
62+
}
63+
64+
function itemUpdated(key, oldValue, newValue, isLocalStorage) {
65+
DOMStorage.domStorageItemUpdated({
66+
key,
67+
oldValue,
68+
newValue,
69+
storageId: {
70+
securityOrigin: '',
71+
isLocalStorage,
72+
storageKey: getStorageKey(),
73+
},
74+
});
75+
}
76+
77+
function itemRemoved(key, isLocalStorage) {
78+
DOMStorage.domStorageItemRemoved({
79+
key,
80+
storageId: {
81+
securityOrigin: '',
82+
isLocalStorage,
83+
storageKey: getStorageKey(),
84+
},
85+
});
86+
}
87+
88+
function itemsCleared(isLocalStorage) {
89+
DOMStorage.domStorageItemsCleared({
90+
storageId: {
91+
securityOrigin: '',
92+
isLocalStorage,
93+
storageKey: getStorageKey(),
94+
},
95+
});
96+
}
97+
98+
function getStorageKey() {
99+
const localStorageFile = getOptionValue('--localstorage-file');
100+
const resolvedAbsolutePath = path.resolve(localStorageFile);
101+
return 'file://' + resolvedAbsolutePath;
102+
}
103+
104+
module.exports = {
105+
InspectorLocalStorage,
106+
InspectorSessionStorage,
107+
};

lib/internal/webstorage.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const {
33
ObjectDefineProperties,
44
} = primordials;
5+
const { hasInspector } = internalBinding('config');
56
const { getOptionValue } = require('internal/options');
67
const { kConstructorKey, Storage } = internalBinding('webstorage');
78
const { getValidatedPath } = require('internal/fs/utils');
@@ -11,11 +12,21 @@ module.exports = { Storage };
1112

1213
let lazyLocalStorage;
1314
let lazySessionStorage;
15+
let lazyInspectorStorage;
1416
let localStorageWarned = false;
1517

1618
// Check at load time if localStorage file is provided to determine enumerability.
1719
// If not provided, localStorage is non-enumerable to avoid breaking {...globalThis}.
1820
const localStorageLocation = getOptionValue('--localstorage-file');
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+
}
1930

2031
ObjectDefineProperties(module.exports, {
2132
__proto__: null,
@@ -36,9 +47,13 @@ ObjectDefineProperties(module.exports, {
3647
return undefined;
3748
}
3849

39-
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
50+
if (experimentalStorageInspection) {
51+
const { InspectorLocalStorage } = getInspectorStorage();
52+
lazyLocalStorage = new InspectorLocalStorage(kConstructorKey, getValidatedPath(localStorageLocation), true);
53+
} else {
54+
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
55+
}
4056
}
41-
4257
return lazyLocalStorage;
4358
},
4459
},
@@ -48,7 +63,12 @@ ObjectDefineProperties(module.exports, {
4863
enumerable: true,
4964
get() {
5065
if (lazySessionStorage === undefined) {
51-
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
66+
if (experimentalStorageInspection) {
67+
const { InspectorSessionStorage } = getInspectorStorage();
68+
lazySessionStorage = new InspectorSessionStorage(kConstructorKey, kInMemoryPath, false);
69+
} else {
70+
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
71+
}
5272
}
5373

5474
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)
@@ -147,6 +148,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
147148
"wasi", // Experimental.
148149
#if !HAVE_SQLITE
149150
"internal/webstorage", // Experimental.
151+
"internal/inspector/webstorage",
150152
#endif
151153
"internal/test/binding", "internal/v8_prof_polyfill",
152154
};

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
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6+
#include <unordered_map>
67
#include "base_object.h"
78
#include "node_mem.h"
89
#include "sqlite3.h"
@@ -40,6 +41,7 @@ class Storage : public BaseObject {
4041
v8::MaybeLocal<v8::Value> LoadKey(const int index);
4142
v8::Maybe<void> Remove(v8::Local<v8::Name> key);
4243
v8::Maybe<void> Store(v8::Local<v8::Name> key, v8::Local<v8::Value> value);
44+
std::unordered_map<std::u16string, std::u16string> GetAll();
4345

4446
SET_MEMORY_INFO_NAME(Storage)
4547
SET_SELF_SIZE(Storage)

0 commit comments

Comments
 (0)