Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ interop guides for both presenters:
- [Content and resource loading](docfx/articles/content-and-resources.md)
- [Lifecycle and diagnostics](docfx/articles/lifecycle-and-diagnostics.md)
- [Compatibility and security](docfx/articles/compatibility-and-security.md)
- [Durable browser profiles](docs/browser-profiles.md)
- [Troubleshooting](docfx/articles/troubleshooting.md)

Build the documentation site locally with `./build-docs.sh`.
Expand Down
59 changes: 59 additions & 0 deletions docs/browser-profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Durable browser profiles

WebScene is ephemeral unless the host supplies both
`webscene_engine_options.storage_directory` and a stable
`storage_partition_key`. The same identity and root are shared by cookies,
`localStorage`, and IndexedDB. Origins remain separate below the profile, so a
stable application partition does not merge unrelated web origins.

## Lifecycle and failure model

- A profile takes a non-blocking operating-system file lock. A concurrent
process cannot open or clear that partition; storage falls back to ephemeral
operation and the standalone clear API returns `BUSY`.
- Persistent cookies and `localStorage` load when the runtime worker starts.
Mutations update memory synchronously and enqueue a coalesced profile
checkpoint. File creation, flush, and replacement run on the storage worker.
- Orderly engine destruction checkpoints the latest admitted snapshot before
returning. There is at most one active write and one coalesced pending
snapshot. Atomic replace means interruption exposes either the prior or next
complete schema, never a partial commit.
- The qualification budget for orderly shutdown is 100 ms per close on average
(5 seconds for 50 open/mutate/close cycles). The regression test also checks
that file descriptors and temporary files stay bounded across those cycles.
- The binary profile carries magic, schema version, partition identity, record
bounds, revision, payload length, and checksum. Unknown, truncated, or
corrupt data is ignored as an empty profile and replaced by the next valid
mutation. No cookie names, values, tokens, or serialized contents appear in
diagnostics.

Only cookies with a future `Expires` or positive `Max-Age` are checkpointed.
Session cookies and all `sessionStorage` data remain memory-only. Cookie
domain, host-only, path, creation order, expiry, `Secure`, `HttpOnly`, and
`SameSite` attributes round-trip through the profile. Normal replacement,
deletion, expiry pruning, visibility checks, and bounded cookie limits operate
on the restored jar.

`localStorage` is partitioned by serialized origin and retains insertion order.
Each origin has a 5 MiB limit (or the smaller configured profile quota), with a
synchronous `QuotaExceededError` before mutation. Opaque origins receive
`SecurityError`. The configured quota also bounds the complete profile file.

## Clearing and local-data threat model

Call `webscene_profile_clear_data_v1` only after closing engines for that
partition. Flags independently clear cookies, `localStorage`, or all data in
the partition; all-site-data also removes IndexedDB database files. Traversal
has a fixed entry bound. Partition keys are hashed and are never interpreted
as paths; clearing cannot escape `storage_directory` or delete the root itself.

Profile directories and files use owner-only permissions where the platform
supports POSIX modes. The data is not encrypted by WebScene and is readable by
the same OS account. Hosts should choose an OS-protected application-data
directory and may place it on encrypted storage or integrate platform
credential protection according to their threat model.

IndexedDB keeps its existing per-database transactional files from issue #56.
The browser profile composes with the same host root, partition hashing, atomic
replacement, quota, and worker-lifecycle model without treating cookies or Web
Storage mutations as IndexedDB transactions.
14 changes: 13 additions & 1 deletion experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ if(BUILD_TESTING)
COMMAND webscene_indexeddb_storage_tests)
set_tests_properties(webscene_indexeddb_storage_tests PROPERTIES
LABELS "storage;durability;performance" TIMEOUT 20)
add_executable(webscene_profile_storage_tests
tests/profile_storage_tests.cpp
native/webscene_profile_storage.cpp)
target_compile_features(webscene_profile_storage_tests PRIVATE cxx_std_20)
target_include_directories(webscene_profile_storage_tests PRIVATE native)
target_link_libraries(webscene_profile_storage_tests PRIVATE Threads::Threads)
add_test(NAME webscene_profile_storage_tests
COMMAND webscene_profile_storage_tests)
set_tests_properties(webscene_profile_storage_tests PROPERTIES
LABELS "storage;cookies;web-storage;durability;security;performance"
TIMEOUT 20)
add_executable(webscene_graphics_scene_abi_layout_tests tests/graphics_scene_abi_layout_tests.c)
target_compile_features(webscene_graphics_scene_abi_layout_tests PRIVATE c_std_11)
target_include_directories(webscene_graphics_scene_abi_layout_tests PRIVATE native)
Expand Down Expand Up @@ -250,7 +261,8 @@ endif()
add_library(webscene_native_engine ${webscene_runtime_kind}
native/webscene_native_engine.cpp
native/webscene_secure_random.cpp
native/webscene_indexeddb_storage.cpp)
native/webscene_indexeddb_storage.cpp
native/webscene_profile_storage.cpp)
if(TARGET webscene_core)
target_link_libraries(webscene_native_engine PRIVATE webscene_core)
else()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "webscene_native_dom.h"
#include "webscene_v8_runtime.h"
#include "webscene_runtime_diagnostics.h"
#include "webscene_profile_storage.h"
#include "webscene_frame_trace.h"
#include "graphics/engine_wake.h"
#include "graphics/webgpu_canvas_interop.h"
Expand All @@ -25,6 +26,7 @@
#include <cstdlib>
#include <cstring>
#include <deque>
#include <filesystem>
#include <iterator>
#include <memory>
#include <mutex>
Expand Down Expand Up @@ -140,8 +142,8 @@

std::array<queued_input_event, input_capacity> values_{};
std::mutex producer_mutex_;
alignas(64) std::atomic<uint32_t> write_{0};

Check warning on line 145 in experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.cpp

View workflow job for this annotation

GitHub Actions / Build win-x64

'`anonymous-namespace'::input_ring': structure was padded due to alignment specifier
alignas(64) std::atomic<uint32_t> read_{0};

Check warning on line 146 in experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.cpp

View workflow job for this annotation

GitHub Actions / Build win-x64

'`anonymous-namespace'::input_ring': structure was padded due to alignment specifier
};

struct canvas_layer_version final {
Expand Down Expand Up @@ -1779,6 +1781,51 @@
} catch (const std::bad_alloc&) { return WEBSCENE_SCENE_ACQUIRE_OUT_OF_MEMORY; }
catch (...) { return WEBSCENE_SCENE_ACQUIRE_INTERNAL_ERROR; }
}

uint32_t webscene_profile_clear_data_v1(
const char* storage_directory,
size_t storage_directory_length,
const char* storage_partition_key,
size_t storage_partition_key_length,
uint64_t storage_quota_bytes,
uint32_t flags)
{
if (storage_directory == nullptr || storage_directory_length == 0U
|| storage_partition_key == nullptr || storage_partition_key_length == 0U
|| (flags & (WEBSCENE_PROFILE_CLEAR_COOKIES_V1
| WEBSCENE_PROFILE_CLEAR_LOCAL_STORAGE_V1
| WEBSCENE_PROFILE_CLEAR_ALL_SITE_DATA_V1)) == 0U) {
return WEBSCENE_PROFILE_STATUS_INVALID_ARGUMENT_V1;
}
try {
const auto result = webscene_native::browser_profile_storage::clear_partition_sync(
std::filesystem::path(std::string(storage_directory, storage_directory_length)),
std::string(storage_partition_key, storage_partition_key_length),
storage_quota_bytes,
((flags & WEBSCENE_PROFILE_CLEAR_COOKIES_V1) != 0U
? webscene_native::profile_clear_cookies : 0U)
| ((flags & WEBSCENE_PROFILE_CLEAR_LOCAL_STORAGE_V1) != 0U
? webscene_native::profile_clear_local_storage : 0U)
| ((flags & WEBSCENE_PROFILE_CLEAR_ALL_SITE_DATA_V1) != 0U
? webscene_native::profile_clear_all_site_data : 0U));
switch (result.status) {
case webscene_native::profile_storage_status::ok:
case webscene_native::profile_storage_status::not_found:
return WEBSCENE_PROFILE_STATUS_OK_V1;
case webscene_native::profile_storage_status::busy:
return WEBSCENE_PROFILE_STATUS_BUSY_V1;
case webscene_native::profile_storage_status::quota_exceeded:
return WEBSCENE_PROFILE_STATUS_QUOTA_EXCEEDED_V1;
case webscene_native::profile_storage_status::corrupt:
return WEBSCENE_PROFILE_STATUS_CORRUPT_V1;
case webscene_native::profile_storage_status::unavailable:
case webscene_native::profile_storage_status::io_error:
return WEBSCENE_PROFILE_STATUS_IO_ERROR_V1;
}
} catch (...) {
}
return WEBSCENE_PROFILE_STATUS_IO_ERROR_V1;
}
}
webscene_scene_acquire_status webscene_engine_acquire_latest_scene_v3(webscene_engine* engine,
const webscene_scene_acquire_options_v3* options,const webscene_scene_view_v3** result)
Expand All @@ -1802,7 +1849,7 @@
uint32_t webscene_scene_gpu_image_count_v3(const webscene_scene_view_v3* view)
{
if (!view || view->struct_size < sizeof(*view) || view->scene_version!=3 || !view->lease_token) return 0;
return static_cast<const scene_lease_v3*>(view->lease_token)->cpu.value->gpu_images.size();

Check warning on line 1852 in experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.cpp

View workflow job for this annotation

GitHub Actions / Build win-x64

'return': conversion from 'size_t' to 'uint32_t', possible loss of data
}
webscene_scene_acquire_status webscene_gpu_image_retain_v3(
const webscene_gpu_image_lease_v3* image,webscene_gpu_image_lease_v3** result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ _webscene_engine_copy_runtime_failure
_webscene_engine_copy_scene_diagnostics
_webscene_engine_create
_webscene_engine_create_with_options
_webscene_profile_clear_data_v1
_webscene_engine_destroy
_webscene_engine_enqueue
_webscene_engine_enqueue_resize_frame
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,35 @@ typedef struct webscene_engine_options {
uint64_t storage_quota_bytes;
} webscene_engine_options;

/*
* Profile data is local application data, protected by owner-only filesystem
* permissions but not encrypted by WebScene. Hosts should select an OS-backed
* protected location and may layer platform credential/encryption facilities.
* Clear only while no engine has the partition open; BUSY is returned instead
* of racing a live writer. The partition key is hashed below storage_directory,
* so it is never interpreted as a path and cannot broaden the deletion scope.
*/
enum {
WEBSCENE_PROFILE_CLEAR_COOKIES_V1 = 1U << 0U,
WEBSCENE_PROFILE_CLEAR_LOCAL_STORAGE_V1 = 1U << 1U,
WEBSCENE_PROFILE_CLEAR_ALL_SITE_DATA_V1 = 1U << 2U
};
enum {
WEBSCENE_PROFILE_STATUS_OK_V1 = 0U,
WEBSCENE_PROFILE_STATUS_INVALID_ARGUMENT_V1 = 1U,
WEBSCENE_PROFILE_STATUS_BUSY_V1 = 2U,
WEBSCENE_PROFILE_STATUS_IO_ERROR_V1 = 3U,
WEBSCENE_PROFILE_STATUS_CORRUPT_V1 = 4U,
WEBSCENE_PROFILE_STATUS_QUOTA_EXCEEDED_V1 = 5U
};
WEBSCENE_API uint32_t webscene_profile_clear_data_v1(
const char* storage_directory,
size_t storage_directory_length,
const char* storage_partition_key,
size_t storage_partition_key_length,
uint64_t storage_quota_bytes,
uint32_t flags);

enum {
WEBSCENE_DOCUMENT_SCRIPT_ALL_FRAMES = 1U << 0U
};
Expand Down
Loading
Loading