-
-
Notifications
You must be signed in to change notification settings - Fork 34.9k
inspector: auto collect webstorage data #62145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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_); | ||
| 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); | ||
|
|
@@ -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"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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) || | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"; | ||
| } | ||
|
|
||
| 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" | ||||||
|
|
@@ -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() { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
? |
||||||
| 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])); | ||||||
| } | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
|
|
||||||
| result.emplace(std::move(key), std::move(value)); | ||||||
| } | ||||||
| return result; | ||||||
| } | ||||||
|
|
||||||
| MaybeLocal<Value> Storage::Length() { | ||||||
| if (!Open().IsJust()) { | ||||||
| return {}; | ||||||
|
|
||||||
There was a problem hiding this comment.
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 aconst std::unordered_map<...>*pointer instead of a reference, along with anstd::optional<std::unordered_map<...>>that is only filled when needed