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,
};
14 changes: 11 additions & 3 deletions lib/internal/webstorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
},
},
Expand All @@ -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;
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_;
std::optional<StorageMap> storage_map =
is_local_storage ? std::make_optional<StorageMap>(local_storage_map_)
: std::make_optional<StorageMap>(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<StorageMap>(std::move(all_items));
}
}

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());
Comment on lines +256 to +259
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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());
storage_map[key_utf16.ToU16String()] = value_utf16.ToU16String();

}
}

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
1 change: 1 addition & 0 deletions src/node_builtins.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
};
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