From 1e514ca64cc38ea4c82171f592c16e4e9796b8ea Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Sat, 7 Mar 2026 19:06:00 +0900 Subject: [PATCH 1/6] inspector: auto collect webstorage data --- lib/internal/inspector/webstorage.js | 107 ++++++++++++++++++ lib/internal/webstorage.js | 14 ++- src/inspector/dom_storage_agent.cc | 43 +++++++- src/inspector/dom_storage_agent.h | 4 + src/node_webstorage.cc | 36 ++++++ src/node_webstorage.h | 2 + test/parallel/test-inspector-dom-storage.js | 115 +++++++++++++++++++- 7 files changed, 312 insertions(+), 9 deletions(-) create mode 100644 lib/internal/inspector/webstorage.js diff --git a/lib/internal/inspector/webstorage.js b/lib/internal/inspector/webstorage.js new file mode 100644 index 00000000000000..2bb6f5e6f8c743 --- /dev/null +++ b/lib/internal/inspector/webstorage.js @@ -0,0 +1,107 @@ +'use strict'; + +const { Storage } = internalBinding('webstorage'); +const { DOMStorage } = require('inspector'); +const path = require('path'); +const { getOptionValue } = require('internal/options'); + +class InspectorLocalStorage extends Storage { + setItem(key, value) { + const oldValue = this.getItem(key); + super.setItem(key, value); + if (oldValue == null) { + itemAdded(key, value, true); + } else { + itemUpdated(key, oldValue, value, true); + } + } + + removeItem(key) { + super.removeItem(key); + itemRemoved(key, true); + } + + clear() { + super.clear(); + itemsCleared(true); + } +} + +const InspectorSessionStorage = class extends Storage { + setItem(key, value) { + const oldValue = this.getItem(key); + super.setItem(key, value); + if (oldValue == null) { + itemAdded(key, value, false); + } else { + itemUpdated(key, oldValue, value, false); + } + } + + removeItem(key) { + super.removeItem(key); + itemRemoved(key, false); + } + + clear() { + super.clear(); + itemsCleared(false); + } +}; + +function itemAdded(key, value, isLocalStorage) { + DOMStorage.domStorageItemAdded({ + key, + newValue: value, + storageId: { + securityOrigin: '', + isLocalStorage, + storageKey: getStorageKey(), + }, + }); +} + +function itemUpdated(key, oldValue, newValue, isLocalStorage) { + DOMStorage.domStorageItemUpdated({ + key, + oldValue, + newValue, + storageId: { + securityOrigin: '', + isLocalStorage, + storageKey: getStorageKey(), + }, + }); +} + +function itemRemoved(key, isLocalStorage) { + DOMStorage.domStorageItemRemoved({ + key, + storageId: { + securityOrigin: '', + isLocalStorage, + storageKey: getStorageKey(), + }, + }); +} + +function itemsCleared(isLocalStorage) { + DOMStorage.domStorageItemsCleared({ + storageId: { + securityOrigin: '', + isLocalStorage, + storageKey: getStorageKey(), + }, + }); +} + +function getStorageKey() { + const localStorageFile = getOptionValue('--localstorage-file'); + const resolvedAbsolutePath = path.resolve(localStorageFile); + return 'file://' + resolvedAbsolutePath; +} + +module.exports = { + InspectorLocalStorage, + InspectorSessionStorage, +}; diff --git a/lib/internal/webstorage.js b/lib/internal/webstorage.js index 47c71676995f09..5d4978d9187982 100644 --- a/lib/internal/webstorage.js +++ b/lib/internal/webstorage.js @@ -5,6 +5,7 @@ const { const { getOptionValue } = require('internal/options'); const { kConstructorKey, Storage } = internalBinding('webstorage'); const { getValidatedPath } = require('internal/fs/utils'); +const { InspectorLocalStorage, InspectorSessionStorage } = require('internal/inspector/webstorage'); const kInMemoryPath = ':memory:'; module.exports = { Storage }; @@ -36,9 +37,12 @@ ObjectDefineProperties(module.exports, { return undefined; } - lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation)); + if (getOptionValue('--experimental-storage-inspection')) { + lazyLocalStorage = new InspectorLocalStorage(kConstructorKey, getValidatedPath(localStorageLocation), true); + } else { + lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation)); + } } - return lazyLocalStorage; }, }, @@ -48,7 +52,11 @@ ObjectDefineProperties(module.exports, { enumerable: true, get() { if (lazySessionStorage === undefined) { - lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath); + if (getOptionValue('--experimental-storage-inspection')) { + lazySessionStorage = new InspectorSessionStorage(kConstructorKey, kInMemoryPath, false); + } else { + lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath); + } } return lazySessionStorage; diff --git a/src/inspector/dom_storage_agent.cc b/src/inspector/dom_storage_agent.cc index d300266548ca87..bdf7360003853b 100644 --- a/src/inspector/dom_storage_agent.cc +++ b/src/inspector/dom_storage_agent.cc @@ -85,11 +85,26 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( "DOMStorage domain is not enabled"); } bool is_local_storage = storageId->getIsLocalStorage(); - const std::unordered_map& storage_map = - is_local_storage ? local_storage_map_ : session_storage_map_; + std::unique_ptr> storage_map = + is_local_storage + ? std::make_unique>( + local_storage_map_) + : std::make_unique>( + session_storage_map_); + if (storage_map->empty()) { + auto web_storage_obj = getWebStorage(is_local_storage); + if (web_storage_obj) { + std::unordered_map all_items = + web_storage_obj.value()->GetAll(); + storage_map = + std::make_unique>( + std::move(all_items)); + } + } + auto result = std::make_unique>>(); - for (const auto& pair : storage_map) { + for (const auto& pair : *storage_map) { auto item = std::make_unique>(); item->push_back(pair.first); item->push_back(pair.second); @@ -241,6 +256,28 @@ void DOMStorageAgent::registerStorage(Local context, } } +std::optional DOMStorageAgent::getWebStorage( + bool is_local_storage) { + std::string var_name = is_local_storage ? "localStorage" : "sessionStorage"; + v8::Isolate* isolate = env_->isolate(); + v8::HandleScope handle_scope(isolate); + v8::Local global = env_->context()->Global(); + v8::Local web_storage_val; + if (!global + ->Get(env_->context(), + v8::String::NewFromUtf8(env_->isolate(), var_name.c_str()) + .ToLocalChecked()) + .ToLocal(&web_storage_val) || + !web_storage_val->IsObject()) { + return std::nullopt; + } else { + node::webstorage::Storage* storage; + ASSIGN_OR_RETURN_UNWRAP( + &storage, web_storage_val.As(), std::nullopt); + return storage; + } +} + bool DOMStorageAgent::canEmit(const std::string& domain) { return domain == "DOMStorage"; } diff --git a/src/inspector/dom_storage_agent.h b/src/inspector/dom_storage_agent.h index 954f041d40ef58..f4c37793ef2a99 100644 --- a/src/inspector/dom_storage_agent.h +++ b/src/inspector/dom_storage_agent.h @@ -1,9 +1,11 @@ #ifndef SRC_INSPECTOR_DOM_STORAGE_AGENT_H_ #define SRC_INSPECTOR_DOM_STORAGE_AGENT_H_ +#include #include #include "env.h" #include "node/inspector/protocol/DOMStorage.h" +#include "node_webstorage.h" #include "notification_emitter.h" #include "v8.h" @@ -50,6 +52,8 @@ class DOMStorageAgent : public protocol::DOMStorage::Backend, DOMStorageAgent& operator=(const DOMStorageAgent&) = delete; private: + std::optional getWebStorage( + bool is_local_storage); std::unique_ptr frontend_; std::unordered_map local_storage_map_ = {}; std::unordered_map session_storage_map_ = {}; diff --git a/src/node_webstorage.cc b/src/node_webstorage.cc index 224f49e8596cf1..ff6d2775cddf66 100644 --- a/src/node_webstorage.cc +++ b/src/node_webstorage.cc @@ -1,4 +1,6 @@ #include "node_webstorage.h" +#include +#include #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" @@ -278,6 +280,40 @@ MaybeLocal Storage::Enumerate() { return Array::New(env()->isolate(), values.data(), values.size()); } +std::unordered_map Storage::GetAll() { + if (!Open().IsJust()) { + return {}; + } + + static constexpr std::string_view sql = + "SELECT key, value FROM nodejs_webstorage"; + sqlite3_stmt* s = nullptr; + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, nullptr); + auto stmt = stmt_unique_ptr(s); + std::unordered_map result; + while ((r = sqlite3_step(stmt.get())) == SQLITE_ROW) { + CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB); + CHECK(sqlite3_column_type(stmt.get(), 1) == SQLITE_BLOB); + auto key_size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t); + auto value_size = sqlite3_column_bytes(stmt.get(), 1) / sizeof(uint16_t); + auto key_uint16( + reinterpret_cast(sqlite3_column_blob(stmt.get(), 0))); + auto value_uint16( + reinterpret_cast(sqlite3_column_blob(stmt.get(), 1))); + std::string key; + for (size_t i = 0; i < key_size; ++i) { + key.push_back(static_cast(key_uint16[i])); + } + std::string value; + for (size_t i = 0; i < value_size; ++i) { + value.push_back(static_cast(value_uint16[i])); + } + + result.emplace(std::move(key), std::move(value)); + } + return result; +} + MaybeLocal Storage::Length() { if (!Open().IsJust()) { return {}; diff --git a/src/node_webstorage.h b/src/node_webstorage.h index c2548d32e993fd..66ea7882b5fba9 100644 --- a/src/node_webstorage.h +++ b/src/node_webstorage.h @@ -3,6 +3,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include #include "base_object.h" #include "node_mem.h" #include "sqlite3.h" @@ -40,6 +41,7 @@ class Storage : public BaseObject { v8::MaybeLocal LoadKey(const int index); v8::Maybe Remove(v8::Local key); v8::Maybe Store(v8::Local key, v8::Local value); + std::unordered_map GetAll(); SET_MEMORY_INFO_NAME(Storage) SET_SELF_SIZE(Storage) diff --git a/test/parallel/test-inspector-dom-storage.js b/test/parallel/test-inspector-dom-storage.js index f1cc3bbff3d076..2eb4291b73e663 100644 --- a/test/parallel/test-inspector-dom-storage.js +++ b/test/parallel/test-inspector-dom-storage.js @@ -8,8 +8,7 @@ const { DOMStorage, Session } = require('node:inspector/promises'); const { pathToFileURL } = require('node:url'); const path = require('node:path'); - -async function test() { +async function testRegisterStorage() { const session = new Session(); await session.connect(); @@ -26,6 +25,7 @@ async function test() { await checkStorage(true); await checkStorage(false); + session.disconnect(); async function checkStorage(isLocalStorage) { DOMStorage.registerStorage({ @@ -43,7 +43,9 @@ async function test() { securityOrigin: 'node-inspector://default-dom-storage', }, }); - const sortedEntries = result.entries.sort((a, b) => a[0].localeCompare(b[0])); + const sortedEntries = result.entries.sort((a, b) => + a[0].localeCompare(b[0]), + ); assert.deepStrictEqual(sortedEntries, [ ['1', '2'], ['key1', 'value1'], @@ -53,4 +55,111 @@ async function test() { } } +async function testGetData() { + await test(true); + await test(false); + + async function test(isLocalStorage) { + const webStorage = isLocalStorage ? localStorage : sessionStorage; + const session = new Session(); + webStorage.clear(); + await session.connect(); + + const storageKey = await session.post('Storage.getStorageKey'); + await session.post('DOMStorage.enable'); + + webStorage.setItem('key1', 'value'); + webStorage.setItem('key2', 1); + webStorage.setItem('key3', JSON.stringify({ a: 1 })); + + const result = await session.post('DOMStorage.getDOMStorageItems', { + storageId: { + isLocalStorage, + securityOrigin: '', + storageKey: storageKey.storageKey, + }, + }); + assert.strictEqual(result.entries.length, 3); + const entries = Object.fromEntries(result.entries); + assert.strictEqual(entries.key1, 'value'); + assert.strictEqual(entries.key2, '1'); + assert.strictEqual(entries.key3, JSON.stringify({ a: 1 })); + session.disconnect(); + } +} + +async function testEvents() { + await test(true); + await test(false); + async function test(isLocalStorage) { + const webStorage = isLocalStorage ? localStorage : sessionStorage; + webStorage.clear(); + const session = new Session(); + await session.connect(); + await session.post('DOMStorage.enable'); + const storageKey = await session.post('Storage.getStorageKey'); + session.on( + 'DOMStorage.domStorageItemAdded', + common.mustCall(({ params }) => { + assert.strictEqual(params.key, 'key'); + assert.strictEqual(params.newValue, 'value'); + assert.deepStrictEqual(params.storageId, { + securityOrigin: '', + isLocalStorage, + storageKey: storageKey.storageKey, + }); + }), + ); + webStorage.setItem('key', 'value'); + + session.on( + 'DOMStorage.domStorageItemUpdated', + common.mustCall(({ params }) => { + assert.strictEqual(params.key, 'key'); + assert.strictEqual(params.oldValue, 'value'); + assert.strictEqual(params.newValue, 'newValue'); + assert.deepStrictEqual(params.storageId, { + securityOrigin: '', + isLocalStorage, + storageKey: storageKey.storageKey, + }); + }), + ); + + webStorage.setItem('key', 'newValue'); + + session.on( + 'DOMStorage.domStorageItemRemoved', + common.mustCall(({ params }) => { + assert.strictEqual(params.key, 'key'); + assert.deepStrictEqual(params.storageId, { + securityOrigin: '', + isLocalStorage, + storageKey: storageKey.storageKey, + }); + }), + ); + + webStorage.removeItem('key'); + + session.on( + 'DOMStorage.domStorageItemsCleared', + common.mustCall(({ params }) => { + assert.deepStrictEqual(params.storageId, { + securityOrigin: '', + isLocalStorage, + storageKey: storageKey.storageKey, + }); + }), + ); + webStorage.clear(); + session.disconnect(); + } +} + +async function test() { + await testRegisterStorage(); + await testGetData(); + await testEvents(); +} test().then(common.mustCall()); From d897bd3b68150220f408b21143911fa566810055 Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Sun, 8 Mar 2026 20:11:04 +0900 Subject: [PATCH 2/6] fix review --- src/inspector/dom_storage_agent.cc | 20 ++++++++++++-------- src/node_webstorage.cc | 23 +++++++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/inspector/dom_storage_agent.cc b/src/inspector/dom_storage_agent.cc index bdf7360003853b..766d58df19b63e 100644 --- a/src/inspector/dom_storage_agent.cc +++ b/src/inspector/dom_storage_agent.cc @@ -1,6 +1,9 @@ #include "dom_storage_agent.h" +#include #include "env-inl.h" #include "inspector/inspector_object_utils.h" +#include "util.h" +#include "v8-exception.h" #include "v8-isolate.h" namespace node { @@ -85,11 +88,11 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( "DOMStorage domain is not enabled"); } bool is_local_storage = storageId->getIsLocalStorage(); - std::unique_ptr> storage_map = + std::optional> storage_map = is_local_storage - ? std::make_unique>( + ? std::make_optional>( local_storage_map_) - : std::make_unique>( + : std::make_optional>( session_storage_map_); if (storage_map->empty()) { auto web_storage_obj = getWebStorage(is_local_storage); @@ -97,7 +100,7 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( std::unordered_map all_items = web_storage_obj.value()->GetAll(); storage_map = - std::make_unique>( + std::make_optional>( std::move(all_items)); } } @@ -258,17 +261,18 @@ void DOMStorageAgent::registerStorage(Local context, std::optional DOMStorageAgent::getWebStorage( bool is_local_storage) { - std::string var_name = is_local_storage ? "localStorage" : "sessionStorage"; v8::Isolate* isolate = env_->isolate(); v8::HandleScope handle_scope(isolate); v8::Local global = env_->context()->Global(); v8::Local web_storage_val; + v8::TryCatch try_catch(isolate); if (!global ->Get(env_->context(), - v8::String::NewFromUtf8(env_->isolate(), var_name.c_str()) - .ToLocalChecked()) + is_local_storage + ? FIXED_ONE_BYTE_STRING(isolate, "localStorage") + : FIXED_ONE_BYTE_STRING(isolate, "sessionStorage")) .ToLocal(&web_storage_val) || - !web_storage_val->IsObject()) { + !web_storage_val->IsObject() || try_catch.HasCaught()) { return std::nullopt; } else { node::webstorage::Storage* storage; diff --git a/src/node_webstorage.cc b/src/node_webstorage.cc index ff6d2775cddf66..70edde4dd89cbb 100644 --- a/src/node_webstorage.cc +++ b/src/node_webstorage.cc @@ -9,6 +9,7 @@ #include "node_errors.h" #include "node_mem-inl.h" #include "path.h" +#include "simdutf.h" #include "sqlite3.h" #include "util-inl.h" @@ -297,17 +298,23 @@ std::unordered_map Storage::GetAll() { auto key_size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t); auto value_size = sqlite3_column_bytes(stmt.get(), 1) / sizeof(uint16_t); auto key_uint16( - reinterpret_cast(sqlite3_column_blob(stmt.get(), 0))); + reinterpret_cast(sqlite3_column_blob(stmt.get(), 0))); auto value_uint16( - reinterpret_cast(sqlite3_column_blob(stmt.get(), 1))); + reinterpret_cast(sqlite3_column_blob(stmt.get(), 1))); + size_t key_utf8_size = + simdutf::utf8_length_from_utf16(key_uint16, key_size); std::string key; - for (size_t i = 0; i < key_size; ++i) { - key.push_back(static_cast(key_uint16[i])); - } + key.resize(key_utf8_size); + size_t written = + simdutf::convert_utf16_to_utf8(key_uint16, key_size, key.data()); + key.resize(written); + size_t value_utf8_size = + simdutf::utf8_length_from_utf16(value_uint16, value_size); std::string value; - for (size_t i = 0; i < value_size; ++i) { - value.push_back(static_cast(value_uint16[i])); - } + value.resize(value_utf8_size); + written = + simdutf::convert_utf16_to_utf8(value_uint16, value_size, value.data()); + value.resize(written); result.emplace(std::move(key), std::move(value)); } From 5c82977e1a4f63ebc2afc1d1f5afe9fe84bd0db7 Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Mon, 9 Mar 2026 12:58:28 +0900 Subject: [PATCH 3/6] support utf16 --- src/inspector/dom_storage_agent.cc | 35 ++++++++++++++++-------------- src/inspector/dom_storage_agent.h | 5 +++-- src/node_webstorage.cc | 21 +++++------------- src/node_webstorage.h | 2 +- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/inspector/dom_storage_agent.cc b/src/inspector/dom_storage_agent.cc index 766d58df19b63e..cbfee2a64c972b 100644 --- a/src/inspector/dom_storage_agent.cc +++ b/src/inspector/dom_storage_agent.cc @@ -88,20 +88,16 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( "DOMStorage domain is not enabled"); } bool is_local_storage = storageId->getIsLocalStorage(); - std::optional> storage_map = + std::optional storage_map = is_local_storage - ? std::make_optional>( - local_storage_map_) - : std::make_optional>( - session_storage_map_); + ? std::make_optional(local_storage_map_) + : std::make_optional(session_storage_map_); if (storage_map->empty()) { auto web_storage_obj = getWebStorage(is_local_storage); if (web_storage_obj) { - std::unordered_map all_items = - web_storage_obj.value()->GetAll(); + StorageMap all_items = web_storage_obj.value()->GetAll(); storage_map = - std::make_optional>( - std::move(all_items)); + std::make_optional(std::move(all_items)); } } @@ -109,8 +105,12 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( std::make_unique>>(); for (const auto& pair : *storage_map) { auto item = std::make_unique>(); - item->push_back(pair.first); - item->push_back(pair.second); + item->push_back( + protocol::StringUtil::fromUTF16(reinterpret_cast(pair.first.data()), + pair.first.size())); + item->push_back( + protocol::StringUtil::fromUTF16(reinterpret_cast(pair.second.data()), + pair.second.size())); result->push_back(std::move(item)); } *items = std::move(result); @@ -237,7 +237,7 @@ void DOMStorageAgent::registerStorage(Local context, .ToLocal(&storage_map_obj)) { return; } - std::unordered_map& storage_map = + StorageMap& storage_map = is_local_storage ? local_storage_map_ : session_storage_map_; Local property_names; if (!storage_map_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) { @@ -253,10 +253,13 @@ void DOMStorageAgent::registerStorage(Local context, 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; - } + node::TwoByteValue key_utf16(isolate, key_value); + node::TwoByteValue value_utf16(isolate, value_value); + storage_map[std::u16string( + reinterpret_cast(*key_utf16), key_utf16.length())] = + std::u16string(reinterpret_cast(*value_utf16), + value_utf16.length()); + } } std::optional DOMStorageAgent::getWebStorage( diff --git a/src/inspector/dom_storage_agent.h b/src/inspector/dom_storage_agent.h index f4c37793ef2a99..a6eecbb8d37c95 100644 --- a/src/inspector/dom_storage_agent.h +++ b/src/inspector/dom_storage_agent.h @@ -52,11 +52,12 @@ class DOMStorageAgent : public protocol::DOMStorage::Backend, DOMStorageAgent& operator=(const DOMStorageAgent&) = delete; private: + typedef std::unordered_map StorageMap; std::optional getWebStorage( bool is_local_storage); std::unique_ptr frontend_; - std::unordered_map local_storage_map_ = {}; - std::unordered_map session_storage_map_ = {}; + StorageMap local_storage_map_ = {}; + StorageMap session_storage_map_ = {}; bool enabled_ = false; Environment* env_; }; diff --git a/src/node_webstorage.cc b/src/node_webstorage.cc index 70edde4dd89cbb..25f2826573aa37 100644 --- a/src/node_webstorage.cc +++ b/src/node_webstorage.cc @@ -281,7 +281,7 @@ MaybeLocal Storage::Enumerate() { return Array::New(env()->isolate(), values.data(), values.size()); } -std::unordered_map Storage::GetAll() { +std::unordered_map Storage::GetAll() { if (!Open().IsJust()) { return {}; } @@ -291,7 +291,7 @@ std::unordered_map Storage::GetAll() { sqlite3_stmt* s = nullptr; int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, nullptr); auto stmt = stmt_unique_ptr(s); - std::unordered_map result; + std::unordered_map result; while ((r = sqlite3_step(stmt.get())) == SQLITE_ROW) { CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB); CHECK(sqlite3_column_type(stmt.get(), 1) == SQLITE_BLOB); @@ -301,20 +301,9 @@ std::unordered_map Storage::GetAll() { reinterpret_cast(sqlite3_column_blob(stmt.get(), 0))); auto value_uint16( reinterpret_cast(sqlite3_column_blob(stmt.get(), 1))); - size_t key_utf8_size = - simdutf::utf8_length_from_utf16(key_uint16, key_size); - std::string key; - key.resize(key_utf8_size); - size_t written = - simdutf::convert_utf16_to_utf8(key_uint16, key_size, key.data()); - key.resize(written); - size_t value_utf8_size = - simdutf::utf8_length_from_utf16(value_uint16, value_size); - std::string value; - value.resize(value_utf8_size); - written = - simdutf::convert_utf16_to_utf8(value_uint16, value_size, value.data()); - value.resize(written); + + std::u16string key(key_uint16, key_size); + std::u16string value(value_uint16, value_size); result.emplace(std::move(key), std::move(value)); } diff --git a/src/node_webstorage.h b/src/node_webstorage.h index 66ea7882b5fba9..938a2333194b76 100644 --- a/src/node_webstorage.h +++ b/src/node_webstorage.h @@ -41,7 +41,7 @@ class Storage : public BaseObject { v8::MaybeLocal LoadKey(const int index); v8::Maybe Remove(v8::Local key); v8::Maybe Store(v8::Local key, v8::Local value); - std::unordered_map GetAll(); + std::unordered_map GetAll(); SET_MEMORY_INFO_NAME(Storage) SET_SELF_SIZE(Storage) From 3a7266718961cc399ca2b8e6c4572f014327f17c Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Mon, 9 Mar 2026 12:58:48 +0900 Subject: [PATCH 4/6] skip test if SQLite missinig --- test/parallel/test-inspector-dom-storage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-inspector-dom-storage.js b/test/parallel/test-inspector-dom-storage.js index 2eb4291b73e663..a380f18762d5fb 100644 --- a/test/parallel/test-inspector-dom-storage.js +++ b/test/parallel/test-inspector-dom-storage.js @@ -3,6 +3,7 @@ const common = require('../common'); const assert = require('assert'); +common.skipIfSQLiteMissing(); common.skipIfInspectorDisabled(); const { DOMStorage, Session } = require('node:inspector/promises'); const { pathToFileURL } = require('node:url'); @@ -35,6 +36,7 @@ async function testRegisterStorage() { key2: 'value2', [1]: 2, [true]: 'booleanKey', + ['ключ']: 'значение', }, }); const result = await session.post('DOMStorage.getDOMStorageItems', { @@ -51,6 +53,7 @@ async function testRegisterStorage() { ['key1', 'value1'], ['key2', 'value2'], ['true', 'booleanKey'], + ['ключ', 'значение'], ]); } } @@ -71,6 +74,7 @@ async function testGetData() { webStorage.setItem('key1', 'value'); webStorage.setItem('key2', 1); webStorage.setItem('key3', JSON.stringify({ a: 1 })); + webStorage.setItem('ключ', 'значение'); const result = await session.post('DOMStorage.getDOMStorageItems', { storageId: { @@ -79,11 +83,12 @@ async function testGetData() { storageKey: storageKey.storageKey, }, }); - assert.strictEqual(result.entries.length, 3); + assert.strictEqual(result.entries.length, 4); const entries = Object.fromEntries(result.entries); assert.strictEqual(entries.key1, 'value'); assert.strictEqual(entries.key2, '1'); assert.strictEqual(entries.key3, JSON.stringify({ a: 1 })); + assert.strictEqual(entries['ключ'], 'значение'); session.disconnect(); } } From 8fc09a71824b1aa4757a69df7c8ae8e5ba3087d5 Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Mon, 9 Mar 2026 13:09:23 +0900 Subject: [PATCH 5/6] lint --- src/inspector/dom_storage_agent.cc | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/inspector/dom_storage_agent.cc b/src/inspector/dom_storage_agent.cc index cbfee2a64c972b..aebfe845c2e3d1 100644 --- a/src/inspector/dom_storage_agent.cc +++ b/src/inspector/dom_storage_agent.cc @@ -89,15 +89,13 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( } bool is_local_storage = storageId->getIsLocalStorage(); std::optional storage_map = - is_local_storage - ? std::make_optional(local_storage_map_) - : std::make_optional(session_storage_map_); + is_local_storage ? std::make_optional(local_storage_map_) + : std::make_optional(session_storage_map_); if (storage_map->empty()) { auto web_storage_obj = getWebStorage(is_local_storage); if (web_storage_obj) { StorageMap all_items = web_storage_obj.value()->GetAll(); - storage_map = - std::make_optional(std::move(all_items)); + storage_map = std::make_optional(std::move(all_items)); } } @@ -105,12 +103,12 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems( std::make_unique>>(); for (const auto& pair : *storage_map) { auto item = std::make_unique>(); - item->push_back( - protocol::StringUtil::fromUTF16(reinterpret_cast(pair.first.data()), - pair.first.size())); - item->push_back( - protocol::StringUtil::fromUTF16(reinterpret_cast(pair.second.data()), - pair.second.size())); + item->push_back(protocol::StringUtil::fromUTF16( + reinterpret_cast(pair.first.data()), + pair.first.size())); + item->push_back(protocol::StringUtil::fromUTF16( + reinterpret_cast(pair.second.data()), + pair.second.size())); result->push_back(std::move(item)); } *items = std::move(result); @@ -255,11 +253,11 @@ void DOMStorageAgent::registerStorage(Local context, } node::TwoByteValue key_utf16(isolate, key_value); node::TwoByteValue value_utf16(isolate, value_value); - storage_map[std::u16string( - reinterpret_cast(*key_utf16), key_utf16.length())] = + storage_map[std::u16string(reinterpret_cast(*key_utf16), + key_utf16.length())] = std::u16string(reinterpret_cast(*value_utf16), value_utf16.length()); - } + } } std::optional DOMStorageAgent::getWebStorage( From 5debe1c28e2fd8ba45f05fff733ee9f0192982b9 Mon Sep 17 00:00:00 2001 From: ryuhei shima Date: Mon, 9 Mar 2026 18:03:29 +0900 Subject: [PATCH 6/6] fix No such binding --- src/node_builtins.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 5ef8d06933700c..08b029aec36476 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -144,6 +144,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { "wasi", // Experimental. #if !HAVE_SQLITE "internal/webstorage", // Experimental. + "internal/inspector/webstorage", #endif "internal/test/binding", "internal/v8_prof_polyfill", };