Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions lib/internal/inspector/webstorage.js
Original file line number Diff line number Diff line change
@@ -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,
};
26 changes: 23 additions & 3 deletions lib/internal/webstorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const {
ObjectDefineProperties,
} = primordials;
const { hasInspector } = internalBinding('config');
const { getOptionValue } = require('internal/options');
const { kConstructorKey, Storage } = internalBinding('webstorage');
const { getValidatedPath } = require('internal/fs/utils');
Expand All @@ -11,11 +12,21 @@ module.exports = { Storage };

let lazyLocalStorage;
let lazySessionStorage;
let lazyInspectorStorage;
let localStorageWarned = false;

// Check at load time if localStorage file is provided to determine enumerability.
// If not provided, localStorage is non-enumerable to avoid breaking {...globalThis}.
const localStorageLocation = getOptionValue('--localstorage-file');
const experimentalStorageInspection =
hasInspector && getOptionValue('--experimental-storage-inspection');

function getInspectorStorage() {
if (lazyInspectorStorage === undefined) {
lazyInspectorStorage = require('internal/inspector/webstorage');
}
return lazyInspectorStorage;
}

ObjectDefineProperties(module.exports, {
__proto__: null,
Expand All @@ -36,9 +47,13 @@ ObjectDefineProperties(module.exports, {
return undefined;
}

lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
if (experimentalStorageInspection) {
const { InspectorLocalStorage } = getInspectorStorage();
lazyLocalStorage = new InspectorLocalStorage(kConstructorKey, getValidatedPath(localStorageLocation), true);
} else {
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
}
}

return lazyLocalStorage;
},
},
Expand All @@ -48,7 +63,12 @@ ObjectDefineProperties(module.exports, {
enumerable: true,
get() {
if (lazySessionStorage === undefined) {
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
if (experimentalStorageInspection) {
const { InspectorSessionStorage } = getInspectorStorage();
lazySessionStorage = new InspectorSessionStorage(kConstructorKey, kInMemoryPath, false);
} else {
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
}
}

return lazySessionStorage;
Expand Down
60 changes: 51 additions & 9 deletions src/inspector/dom_storage_agent.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "dom_storage_agent.h"
#include <optional>
#include "env-inl.h"
#include "inspector/inspector_object_utils.h"
#include "util.h"
#include "v8-exception.h"
#include "v8-isolate.h"

namespace node {
Expand Down Expand Up @@ -85,14 +88,27 @@ protocol::DispatchResponse DOMStorageAgent::getDOMStorageItems(
"DOMStorage domain is not enabled");
}
bool is_local_storage = storageId->getIsLocalStorage();
const std::unordered_map<std::string, std::string>& storage_map =
is_local_storage ? local_storage_map_ : session_storage_map_;
const StorageMap* storage_map =
is_local_storage ? &local_storage_map_ : &session_storage_map_;
std::optional<StorageMap> storage_map_fallback;
if (storage_map->empty()) {
auto web_storage_obj = getWebStorage(is_local_storage);
if (web_storage_obj) {
storage_map_fallback = web_storage_obj.value()->GetAll();
storage_map = &storage_map_fallback.value();
}
}

auto result =
std::make_unique<protocol::Array<protocol::Array<protocol::String>>>();
for (const auto& pair : storage_map) {
for (const auto& pair : *storage_map) {
auto item = std::make_unique<protocol::Array<protocol::String>>();
item->push_back(pair.first);
item->push_back(pair.second);
item->push_back(protocol::StringUtil::fromUTF16(
reinterpret_cast<const uint16_t*>(pair.first.data()),
pair.first.size()));
item->push_back(protocol::StringUtil::fromUTF16(
reinterpret_cast<const uint16_t*>(pair.second.data()),
pair.second.size()));
result->push_back(std::move(item));
}
*items = std::move(result);
Expand Down Expand Up @@ -219,7 +235,7 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
.ToLocal(&storage_map_obj)) {
return;
}
std::unordered_map<std::string, std::string>& storage_map =
StorageMap& storage_map =
is_local_storage ? local_storage_map_ : session_storage_map_;
Local<Array> property_names;
if (!storage_map_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) {
Expand All @@ -235,9 +251,35 @@ void DOMStorageAgent::registerStorage(Local<Context> 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<const char16_t*>(*key_utf16),
key_utf16.length())] =
std::u16string(reinterpret_cast<const char16_t*>(*value_utf16),
value_utf16.length());
}
}

std::optional<node::webstorage::Storage*> DOMStorageAgent::getWebStorage(
bool is_local_storage) {
v8::Isolate* isolate = env_->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> global = env_->context()->Global();
v8::Local<v8::Value> web_storage_val;
v8::TryCatch try_catch(isolate);
if (!global
->Get(env_->context(),
is_local_storage
? FIXED_ONE_BYTE_STRING(isolate, "localStorage")
: FIXED_ONE_BYTE_STRING(isolate, "sessionStorage"))
.ToLocal(&web_storage_val) ||
!web_storage_val->IsObject() || try_catch.HasCaught()) {
return std::nullopt;
} else {
node::webstorage::Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(
&storage, web_storage_val.As<v8::Object>(), std::nullopt);
return storage;
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/inspector/dom_storage_agent.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#ifndef SRC_INSPECTOR_DOM_STORAGE_AGENT_H_
#define SRC_INSPECTOR_DOM_STORAGE_AGENT_H_

#include <optional>
#include <string>
#include "env.h"
#include "node/inspector/protocol/DOMStorage.h"
#include "node_webstorage.h"
#include "notification_emitter.h"
#include "v8.h"

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

private:
typedef std::unordered_map<std::u16string, std::u16string> StorageMap;
std::optional<node::webstorage::Storage*> getWebStorage(
bool is_local_storage);
std::unique_ptr<protocol::DOMStorage::Frontend> frontend_;
std::unordered_map<std::string, std::string> local_storage_map_ = {};
std::unordered_map<std::string, std::string> session_storage_map_ = {};
StorageMap local_storage_map_ = {};
StorageMap session_storage_map_ = {};
bool enabled_ = false;
Environment* env_;
};
Expand Down
2 changes: 2 additions & 0 deletions src/node_builtins.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
"internal/inspector/network", "internal/inspector/network_http",
"internal/inspector/network_http2", "internal/inspector/network_undici",
"internal/inspector_async_hook", "internal/inspector_network_tracking",
"internal/inspector/webstorage",
#endif // !HAVE_INSPECTOR

#if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT)
Expand All @@ -144,6 +145,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",
};
Expand Down
32 changes: 32 additions & 0 deletions src/node_webstorage.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "node_webstorage.h"
#include <string>
#include <unordered_map>
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
Expand All @@ -7,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"

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

std::unordered_map<std::u16string, std::u16string> 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<std::u16string, std::u16string> 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<const char16_t*>(sqlite3_column_blob(stmt.get(), 0)));
auto value_uint16(
reinterpret_cast<const char16_t*>(sqlite3_column_blob(stmt.get(), 1)));

std::u16string key(key_uint16, key_size);
std::u16string value(value_uint16, value_size);

result.emplace(std::move(key), std::move(value));
}
return result;
}

MaybeLocal<Value> Storage::Length() {
if (!Open().IsJust()) {
return {};
Expand Down
2 changes: 2 additions & 0 deletions src/node_webstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

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

SET_MEMORY_INFO_NAME(Storage)
SET_SELF_SIZE(Storage)
Expand Down
Loading
Loading