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
43 changes: 40 additions & 3 deletions src/inspector/dom_storage_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,26 @@ 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::unique_ptr<std::unordered_map<std::string, std::string>> storage_map =
is_local_storage
? std::make_unique<std::unordered_map<std::string, std::string>>(
local_storage_map_)
: std::make_unique<std::unordered_map<std::string, std::string>>(
session_storage_map_);
Copy link
Member

Choose a reason for hiding this comment

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

There's no need to make a copy of the map here in the !storage_map.empty() case – you can keep using a const std::unordered_map<...>* pointer instead of a reference, along with an std::optional<std::unordered_map<...>> that is only filled when needed

if (storage_map->empty()) {
auto web_storage_obj = getWebStorage(is_local_storage);
if (web_storage_obj) {
std::unordered_map<std::string, std::string> all_items =
web_storage_obj.value()->GetAll();
storage_map =
std::make_unique<std::unordered_map<std::string, std::string>>(
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);
Expand Down Expand Up @@ -241,6 +256,28 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
}
}

std::optional<node::webstorage::Storage*> DOMStorageAgent::getWebStorage(
bool is_local_storage) {
std::string var_name = is_local_storage ? "localStorage" : "sessionStorage";
Copy link
Member

Choose a reason for hiding this comment

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

Using std::string_view is preferred when possible. In this case, you can take it a bit further and use FIXED_ONE_BYTE_STRING.

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;
if (!global
->Get(env_->context(),
v8::String::NewFromUtf8(env_->isolate(), var_name.c_str())
.ToLocalChecked())
.ToLocal(&web_storage_val) ||
Copy link
Member

Choose a reason for hiding this comment

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

This feels like an exception that should be caught?

!web_storage_val->IsObject()) {
return std::nullopt;
} else {
node::webstorage::Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(
&storage, web_storage_val.As<v8::Object>(), std::nullopt);
return storage;
}
}

bool DOMStorageAgent::canEmit(const std::string& domain) {
return domain == "DOMStorage";
}
Expand Down
4 changes: 4 additions & 0 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,6 +52,8 @@ class DOMStorageAgent : public protocol::DOMStorage::Backend,
DOMStorageAgent& operator=(const DOMStorageAgent&) = delete;

private:
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_ = {};
Expand Down
36 changes: 36 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 Down Expand Up @@ -278,6 +280,40 @@ MaybeLocal<Array> Storage::Enumerate() {
return Array::New(env()->isolate(), values.data(), values.size());
}

std::unordered_map<std::string, std::string> Storage::GetAll() {
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
std::unordered_map<std::string, std::string> Storage::GetAll() {
std::unordered_map<std::string, std::string> Storage::GetAll() const {

?

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::string, std::string> 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 uint16_t*>(sqlite3_column_blob(stmt.get(), 0)));
auto value_uint16(
reinterpret_cast<const uint16_t*>(sqlite3_column_blob(stmt.get(), 1)));
std::string key;
for (size_t i = 0; i < key_size; ++i) {
key.push_back(static_cast<char>(key_uint16[i]));
}
std::string value;
for (size_t i = 0; i < value_size; ++i) {
value.push_back(static_cast<char>(value_uint16[i]));
}
Copy link
Member

Choose a reason for hiding this comment

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

This is a very slow way to create strings, and completely broken for characters outside of the ISO-8859-1 range.

You'll want to look at the UTF-16-to-UTF-8 conversion functions exposed by simdutf and use those instead. (Alternatively, you could return std::u16string here, but that requires extra handling on the caller side then.)


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::string, std::string> GetAll();

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