Skip to content

Commit 1e514ca

Browse files
ryuhei shimaryuhei shima
authored andcommitted
inspector: auto collect webstorage data
1 parent a6e9e32 commit 1e514ca

File tree

7 files changed

+312
-9
lines changed

7 files changed

+312
-9
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: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
const { getOptionValue } = require('internal/options');
66
const { kConstructorKey, Storage } = internalBinding('webstorage');
77
const { getValidatedPath } = require('internal/fs/utils');
8+
const { InspectorLocalStorage, InspectorSessionStorage } = require('internal/inspector/webstorage');
89
const kInMemoryPath = ':memory:';
910

1011
module.exports = { Storage };
@@ -36,9 +37,12 @@ ObjectDefineProperties(module.exports, {
3637
return undefined;
3738
}
3839

39-
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
40+
if (getOptionValue('--experimental-storage-inspection')) {
41+
lazyLocalStorage = new InspectorLocalStorage(kConstructorKey, getValidatedPath(localStorageLocation), true);
42+
} else {
43+
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
44+
}
4045
}
41-
4246
return lazyLocalStorage;
4347
},
4448
},
@@ -48,7 +52,11 @@ ObjectDefineProperties(module.exports, {
4852
enumerable: true,
4953
get() {
5054
if (lazySessionStorage === undefined) {
51-
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
55+
if (getOptionValue('--experimental-storage-inspection')) {
56+
lazySessionStorage = new InspectorSessionStorage(kConstructorKey, kInMemoryPath, false);
57+
} else {
58+
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
59+
}
5260
}
5361

5462
return lazySessionStorage;

src/inspector/dom_storage_agent.cc

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,26 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems(
8585
"DOMStorage domain is not enabled");
8686
}
8787
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_;
88+
std::unique_ptr<std::unordered_map<std::string, std::string>> storage_map =
89+
is_local_storage
90+
? std::make_unique<std::unordered_map<std::string, std::string>>(
91+
local_storage_map_)
92+
: std::make_unique<std::unordered_map<std::string, std::string>>(
93+
session_storage_map_);
94+
if (storage_map->empty()) {
95+
auto web_storage_obj = getWebStorage(is_local_storage);
96+
if (web_storage_obj) {
97+
std::unordered_map<std::string, std::string> all_items =
98+
web_storage_obj.value()->GetAll();
99+
storage_map =
100+
std::make_unique<std::unordered_map<std::string, std::string>>(
101+
std::move(all_items));
102+
}
103+
}
104+
90105
auto result =
91106
std::make_unique<protocol::Array<protocol::Array<protocol::String>>>();
92-
for (const auto& pair : storage_map) {
107+
for (const auto& pair : *storage_map) {
93108
auto item = std::make_unique<protocol::Array<protocol::String>>();
94109
item->push_back(pair.first);
95110
item->push_back(pair.second);
@@ -241,6 +256,28 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
241256
}
242257
}
243258

259+
std::optional<node::webstorage::Storage*> DOMStorageAgent::getWebStorage(
260+
bool is_local_storage) {
261+
std::string var_name = is_local_storage ? "localStorage" : "sessionStorage";
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+
if (!global
267+
->Get(env_->context(),
268+
v8::String::NewFromUtf8(env_->isolate(), var_name.c_str())
269+
.ToLocalChecked())
270+
.ToLocal(&web_storage_val) ||
271+
!web_storage_val->IsObject()) {
272+
return std::nullopt;
273+
} else {
274+
node::webstorage::Storage* storage;
275+
ASSIGN_OR_RETURN_UNWRAP(
276+
&storage, web_storage_val.As<v8::Object>(), std::nullopt);
277+
return storage;
278+
}
279+
}
280+
244281
bool DOMStorageAgent::canEmit(const std::string& domain) {
245282
return domain == "DOMStorage";
246283
}

src/inspector/dom_storage_agent.h

Lines changed: 4 additions & 0 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,6 +52,8 @@ class DOMStorageAgent : public protocol::DOMStorage::Backend,
5052
DOMStorageAgent& operator=(const DOMStorageAgent&) = delete;
5153

5254
private:
55+
std::optional<node::webstorage::Storage*> getWebStorage(
56+
bool is_local_storage);
5357
std::unique_ptr<protocol::DOMStorage::Frontend> frontend_;
5458
std::unordered_map<std::string, std::string> local_storage_map_ = {};
5559
std::unordered_map<std::string, std::string> session_storage_map_ = {};

src/node_webstorage.cc

Lines changed: 36 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"
@@ -278,6 +280,40 @@ MaybeLocal<Array> Storage::Enumerate() {
278280
return Array::New(env()->isolate(), values.data(), values.size());
279281
}
280282

283+
std::unordered_map<std::string, std::string> Storage::GetAll() {
284+
if (!Open().IsJust()) {
285+
return {};
286+
}
287+
288+
static constexpr std::string_view sql =
289+
"SELECT key, value FROM nodejs_webstorage";
290+
sqlite3_stmt* s = nullptr;
291+
int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, nullptr);
292+
auto stmt = stmt_unique_ptr(s);
293+
std::unordered_map<std::string, std::string> result;
294+
while ((r = sqlite3_step(stmt.get())) == SQLITE_ROW) {
295+
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB);
296+
CHECK(sqlite3_column_type(stmt.get(), 1) == SQLITE_BLOB);
297+
auto key_size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t);
298+
auto value_size = sqlite3_column_bytes(stmt.get(), 1) / sizeof(uint16_t);
299+
auto key_uint16(
300+
reinterpret_cast<const uint16_t*>(sqlite3_column_blob(stmt.get(), 0)));
301+
auto value_uint16(
302+
reinterpret_cast<const uint16_t*>(sqlite3_column_blob(stmt.get(), 1)));
303+
std::string key;
304+
for (size_t i = 0; i < key_size; ++i) {
305+
key.push_back(static_cast<char>(key_uint16[i]));
306+
}
307+
std::string value;
308+
for (size_t i = 0; i < value_size; ++i) {
309+
value.push_back(static_cast<char>(value_uint16[i]));
310+
}
311+
312+
result.emplace(std::move(key), std::move(value));
313+
}
314+
return result;
315+
}
316+
281317
MaybeLocal<Value> Storage::Length() {
282318
if (!Open().IsJust()) {
283319
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::string, std::string> GetAll();
4345

4446
SET_MEMORY_INFO_NAME(Storage)
4547
SET_SELF_SIZE(Storage)

0 commit comments

Comments
 (0)