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
47 changes: 44 additions & 3 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,11 +88,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::optional<std::unordered_map<std::string, std::string>> storage_map =
is_local_storage
? std::make_optional<std::unordered_map<std::string, std::string>>(
local_storage_map_)
: std::make_optional<std::unordered_map<std::string, std::string>>(
session_storage_map_);
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_optional<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 +259,29 @@ void DOMStorageAgent::registerStorage(Local<Context> context,
}
}

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;
}
}

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
43 changes: 43 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,46 @@ MaybeLocal<Array> Storage::Enumerate() {
return Array::New(env()->isolate(), values.data(), values.size());
}

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

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