2025-06-08 23:35:46 +02:00
/*
* Copyright ( c ) 2025 , Aliaksandr Kalenik < kalenik . aliaksandr @ gmail . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/NonnullOwnPtr.h>
# include <AK/StdLibExtras.h>
2025-10-07 14:35:22 -04:00
# include <LibDatabase/Database.h>
2025-06-08 23:35:46 +02:00
# include <LibWebView/StorageJar.h>
namespace WebView {
// Quota size is specified in https://storage.spec.whatwg.org/#registered-storage-endpoints
static constexpr size_t LOCAL_STORAGE_QUOTA = 5 * MiB ;
2025-11-02 18:57:55 -05:00
// Increment this version when needing to alter the WebStorage schema.
static constexpr u32 WEB_STORAGE_VERSION = 2u ;
static constexpr u32 WEB_STORAGE_METADATA_KEY = 12389u ;
2025-10-07 14:35:22 -04:00
ErrorOr < NonnullOwnPtr < StorageJar > > StorageJar : : create ( Database : : Database & database )
2025-06-08 23:35:46 +02:00
{
Statements statements { } ;
2025-11-02 18:57:55 -05:00
auto create_metadata_table = TRY ( database . prepare_statement ( R " #(
CREATE TABLE IF NOT EXISTS WebStorageMetadata (
metadata_key INTEGER ,
version INTEGER ,
PRIMARY KEY ( metadata_key )
) ;
) # " sv));
database . execute_statement ( create_metadata_table , { } ) ;
auto create_storage_table = TRY ( database . prepare_statement ( R " #(
2025-06-08 23:35:46 +02:00
CREATE TABLE IF NOT EXISTS WebStorage (
storage_endpoint INTEGER ,
storage_key TEXT ,
bottle_key TEXT ,
bottle_value TEXT ,
PRIMARY KEY ( storage_endpoint , storage_key , bottle_key )
2025-11-02 18:57:55 -05:00
) ;
) # " sv));
database . execute_statement ( create_storage_table , { } ) ;
auto read_storage_version = TRY ( database . prepare_statement ( " SELECT version FROM WebStorageMetadata WHERE metadata_key = ?; " sv ) ) ;
auto storage_version = 0u ;
database . execute_statement (
read_storage_version ,
[ & ] ( auto statement_id ) { storage_version = database . result_column < u32 > ( statement_id , 0 ) ; } ,
WEB_STORAGE_METADATA_KEY ) ;
if ( storage_version ! = WEB_STORAGE_VERSION )
TRY ( upgrade_database ( database , storage_version ) ) ;
2025-06-08 23:35:46 +02:00
2025-11-02 17:22:13 -05:00
statements . get_item = TRY ( database . prepare_statement ( " SELECT bottle_value FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?; " sv ) ) ;
2025-11-03 07:17:07 -05:00
statements . set_item = TRY ( database . prepare_statement ( " INSERT OR REPLACE INTO WebStorage VALUES (?, ?, ?, ?, ?); " sv ) ) ;
2025-06-08 23:35:46 +02:00
statements . delete_item = TRY ( database . prepare_statement ( " DELETE FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?; " sv ) ) ;
2025-11-03 07:17:07 -05:00
statements . update_last_access_time = TRY ( database . prepare_statement ( " UPDATE WebStorage SET last_access_time = ? WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?; " sv ) ) ;
2025-06-08 23:35:46 +02:00
statements . clear = TRY ( database . prepare_statement ( " DELETE FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ?; " sv ) ) ;
statements . get_keys = TRY ( database . prepare_statement ( " SELECT bottle_key FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ?; " sv ) ) ;
statements . calculate_size_excluding_key = TRY ( database . prepare_statement ( " SELECT SUM(LENGTH(bottle_key) + LENGTH(bottle_value)) FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key != ?; " sv ) ) ;
return adopt_own ( * new StorageJar { PersistedStorage { database , statements } } ) ;
}
NonnullOwnPtr < StorageJar > StorageJar : : create ( )
{
return adopt_own ( * new StorageJar { OptionalNone { } } ) ;
}
StorageJar : : StorageJar ( Optional < PersistedStorage > persisted_storage )
: m_persisted_storage ( move ( persisted_storage ) )
{
}
StorageJar : : ~ StorageJar ( ) = default ;
2025-11-03 07:17:07 -05:00
ErrorOr < void > StorageJar : : upgrade_database ( Database : : Database & database , u32 current_version )
2025-11-02 18:57:55 -05:00
{
2025-11-03 07:17:07 -05:00
// Track the version numbers for each schema change:
static constexpr u32 VERSION_ADDED_LAST_ACCESS_TIME = 2u ;
if ( current_version < VERSION_ADDED_LAST_ACCESS_TIME ) {
auto add_last_access_time = TRY ( database . prepare_statement ( " ALTER TABLE WebStorage ADD COLUMN last_access_time INTEGER; " sv ) ) ;
database . execute_statement ( add_last_access_time , { } ) ;
auto set_last_access_time = TRY ( database . prepare_statement ( " UPDATE WebStorage SET last_access_time = ?; " sv ) ) ;
database . execute_statement ( set_last_access_time , { } , UnixDateTime : : now ( ) ) ;
}
2025-11-02 18:57:55 -05:00
auto set_storage_version = TRY ( database . prepare_statement ( " INSERT OR REPLACE INTO WebStorageMetadata VALUES (?, ?); " sv ) ) ;
database . execute_statement ( set_storage_version , { } , WEB_STORAGE_METADATA_KEY , WEB_STORAGE_VERSION ) ;
return { } ;
}
2025-06-08 23:35:46 +02:00
Optional < String > StorageJar : : get_item ( StorageEndpointType storage_endpoint , String const & storage_key , String const & bottle_key )
{
StorageLocation storage_location { storage_endpoint , storage_key , bottle_key } ;
2025-11-02 17:22:13 -05:00
2025-06-08 23:35:46 +02:00
if ( m_persisted_storage . has_value ( ) )
return m_persisted_storage - > get_item ( storage_location ) ;
return m_transient_storage . get_item ( storage_location ) ;
}
StorageOperationError StorageJar : : set_item ( StorageEndpointType storage_endpoint , String const & storage_key , String const & bottle_key , String const & bottle_value )
{
StorageLocation storage_location { storage_endpoint , storage_key , bottle_key } ;
2025-11-02 17:22:13 -05:00
2025-06-08 23:35:46 +02:00
if ( m_persisted_storage . has_value ( ) )
return m_persisted_storage - > set_item ( storage_location , bottle_value ) ;
return m_transient_storage . set_item ( storage_location , bottle_value ) ;
}
void StorageJar : : remove_item ( StorageEndpointType storage_endpoint , String const & storage_key , String const & key )
{
StorageLocation storage_location { storage_endpoint , storage_key , key } ;
2025-11-02 17:22:13 -05:00
if ( m_persisted_storage . has_value ( ) )
2025-06-08 23:35:46 +02:00
m_persisted_storage - > delete_item ( storage_location ) ;
2025-11-02 17:22:13 -05:00
else
2025-06-08 23:35:46 +02:00
m_transient_storage . delete_item ( storage_location ) ;
}
void StorageJar : : clear_storage_key ( StorageEndpointType storage_endpoint , String const & storage_key )
{
2025-11-02 17:22:13 -05:00
if ( m_persisted_storage . has_value ( ) )
2025-06-08 23:35:46 +02:00
m_persisted_storage - > clear ( storage_endpoint , storage_key ) ;
2025-11-02 17:22:13 -05:00
else
2025-06-08 23:35:46 +02:00
m_transient_storage . clear ( storage_endpoint , storage_key ) ;
}
Vector < String > StorageJar : : get_all_keys ( StorageEndpointType storage_endpoint , String const & storage_key )
{
if ( m_persisted_storage . has_value ( ) )
return m_persisted_storage - > get_keys ( storage_endpoint , storage_key ) ;
return m_transient_storage . get_keys ( storage_endpoint , storage_key ) ;
}
2025-11-02 17:22:13 -05:00
Optional < String > StorageJar : : TransientStorage : : get_item ( StorageLocation const & key )
{
2025-11-03 07:17:07 -05:00
if ( auto entry = m_storage_items . get ( key ) ; entry . has_value ( ) ) {
entry - > last_access_time = UnixDateTime : : now ( ) ;
return entry - > value ;
}
2025-11-02 17:22:13 -05:00
return { } ;
}
StorageOperationError StorageJar : : TransientStorage : : set_item ( StorageLocation const & key , String const & value )
{
u64 current_size = 0 ;
2025-11-03 07:17:07 -05:00
for ( auto const & [ existing_key , existing_entry ] : m_storage_items ) {
2025-11-02 17:22:13 -05:00
if ( existing_key . storage_endpoint = = key . storage_endpoint & & existing_key . storage_key = = key . storage_key & & existing_key . bottle_key ! = key . bottle_key ) {
current_size + = existing_key . bottle_key . bytes ( ) . size ( ) ;
2025-11-03 07:17:07 -05:00
current_size + = existing_entry . value . bytes ( ) . size ( ) ;
2025-11-02 17:22:13 -05:00
}
}
auto new_size = key . bottle_key . bytes ( ) . size ( ) + value . bytes ( ) . size ( ) ;
if ( current_size + new_size > LOCAL_STORAGE_QUOTA )
return StorageOperationError : : QuotaExceededError ;
2025-11-03 07:17:07 -05:00
m_storage_items . set ( key , { value , UnixDateTime : : now ( ) } ) ;
2025-11-02 17:22:13 -05:00
return StorageOperationError : : None ;
}
void StorageJar : : TransientStorage : : delete_item ( StorageLocation const & key )
{
m_storage_items . remove ( key ) ;
}
void StorageJar : : TransientStorage : : clear ( StorageEndpointType storage_endpoint , String const & storage_key )
{
Vector < StorageLocation > keys_to_remove ;
for ( auto const & [ key , value ] : m_storage_items ) {
if ( key . storage_endpoint = = storage_endpoint & & key . storage_key = = storage_key )
keys_to_remove . append ( key ) ;
}
for ( auto const & key : keys_to_remove )
m_storage_items . remove ( key ) ;
}
Vector < String > StorageJar : : TransientStorage : : get_keys ( StorageEndpointType storage_endpoint , String const & storage_key )
{
Vector < String > keys ;
for ( auto const & [ key , value ] : m_storage_items ) {
if ( key . storage_endpoint = = storage_endpoint & & key . storage_key = = storage_key )
keys . append ( key . bottle_key ) ;
}
return keys ;
}
Optional < String > StorageJar : : PersistedStorage : : get_item ( StorageLocation const & key )
{
Optional < String > result ;
database . execute_statement (
statements . get_item ,
[ & ] ( auto statement_id ) {
result = database . result_column < String > ( statement_id , 0 ) ;
} ,
to_underlying ( key . storage_endpoint ) ,
key . storage_key ,
key . bottle_key ) ;
2025-11-03 07:17:07 -05:00
if ( result . has_value ( ) ) {
database . execute_statement (
statements . update_last_access_time ,
{ } ,
UnixDateTime : : now ( ) ,
to_underlying ( key . storage_endpoint ) ,
key . storage_key ,
key . bottle_key ) ;
}
2025-11-02 17:22:13 -05:00
return result ;
}
2025-06-08 23:35:46 +02:00
StorageOperationError StorageJar : : PersistedStorage : : set_item ( StorageLocation const & key , String const & value )
{
size_t current_size = 0 ;
database . execute_statement (
statements . calculate_size_excluding_key ,
[ & ] ( auto statement_id ) {
current_size = database . result_column < int > ( statement_id , 0 ) ;
} ,
2025-11-02 17:22:13 -05:00
to_underlying ( key . storage_endpoint ) ,
2025-06-08 23:35:46 +02:00
key . storage_key ,
key . bottle_key ) ;
auto new_size = key . bottle_key . bytes ( ) . size ( ) + value . bytes ( ) . size ( ) ;
2025-11-02 17:22:13 -05:00
if ( current_size + new_size > LOCAL_STORAGE_QUOTA )
2025-06-08 23:35:46 +02:00
return StorageOperationError : : QuotaExceededError ;
database . execute_statement (
statements . set_item ,
{ } ,
2025-11-02 17:22:13 -05:00
to_underlying ( key . storage_endpoint ) ,
2025-06-08 23:35:46 +02:00
key . storage_key ,
key . bottle_key ,
2025-11-03 07:17:07 -05:00
value ,
UnixDateTime : : now ( ) ) ;
2025-06-08 23:35:46 +02:00
return StorageOperationError : : None ;
}
void StorageJar : : PersistedStorage : : delete_item ( StorageLocation const & key )
{
database . execute_statement (
statements . delete_item ,
{ } ,
2025-11-02 17:22:13 -05:00
to_underlying ( key . storage_endpoint ) ,
2025-06-08 23:35:46 +02:00
key . storage_key ,
key . bottle_key ) ;
}
void StorageJar : : PersistedStorage : : clear ( StorageEndpointType storage_endpoint , String const & storage_key )
{
database . execute_statement (
statements . clear ,
{ } ,
2025-11-02 17:22:13 -05:00
to_underlying ( storage_endpoint ) ,
2025-06-08 23:35:46 +02:00
storage_key ) ;
}
Vector < String > StorageJar : : PersistedStorage : : get_keys ( StorageEndpointType storage_endpoint , String const & storage_key )
{
Vector < String > keys ;
2025-11-02 17:22:13 -05:00
2025-06-08 23:35:46 +02:00
database . execute_statement (
statements . get_keys ,
[ & ] ( auto statement_id ) {
keys . append ( database . result_column < String > ( statement_id , 0 ) ) ;
} ,
2025-11-02 17:22:13 -05:00
to_underlying ( storage_endpoint ) ,
2025-06-08 23:35:46 +02:00
storage_key ) ;
return keys ;
}
}